From d5dc0c7ca5123a6633e007f41c093def7986a6a6 Mon Sep 17 00:00:00 2001 From: etm Date: Sat, 21 Feb 2026 16:57:46 +0100 Subject: [PATCH] Initial commit : Flutter app nymea energy monitor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NymeaService : auth complète (Hello → Authenticate → SetNotificationStatus) - Token top-level dans chaque requête JSON-RPC (fix critique GetThings) - Persistance token via shared_preferences par hôte - Dashboard : champs utilisateur/mot de passe dans le dialog de connexion - ThingDetailScreen : renommer, réglages (settingsTypes) et supprimer - NymeaThingClass : champ settingsTypes parsé depuis l'API - NymeaThing : copyWith(name) + settingValue() - Fix overflow _StateChip dans ThingsScreen Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 48 + .metadata | 45 + CLAUDE.md | 95 + README.md | 17 + analysis_options.yaml | 28 + android/.gitignore | 14 + android/app/build.gradle.kts | 44 + android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 45 + .../example/etm_powersync_app/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + android/app/src/main/res/values/styles.xml | 18 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle.kts | 24 + android/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle.kts | 26 + docs/nymea_api.md | 5938 +++++++++ docs/nymea_integrations.md | 10892 ++++++++++++++++ ios/.gitignore | 34 + ios/Flutter/AppFrameworkInfo.plist | 24 + ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Runner.xcodeproj/project.pbxproj | 620 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + ios/Runner/AppDelegate.swift | 16 + .../AppIcon.appiconset/Contents.json | 122 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + ios/Runner/Base.lproj/LaunchScreen.storyboard | 37 + ios/Runner/Base.lproj/Main.storyboard | 26 + ios/Runner/Info.plist | 70 + ios/Runner/Runner-Bridging-Header.h | 1 + ios/Runner/SceneDelegate.swift | 6 + ios/RunnerTests/RunnerTests.swift | 12 + lib/main.dart | 168 + lib/models/energy_data.dart | 101 + lib/models/nymea_models.dart | 247 + lib/models/thing_category.dart | 187 + lib/screens/ac_screen.dart | 333 + lib/screens/dashboard_screen.dart | 556 + lib/screens/energy_screen.dart | 403 + lib/screens/favorites_screen.dart | 706 + lib/screens/thing_detail_screen.dart | 655 + lib/screens/things_screen.dart | 596 + lib/services/nymea_service.dart | 946 ++ lib/theme/app_theme.dart | 36 + lib/widgets/energy_flow_widget.dart | 272 + lib/widgets/ev_charging_card.dart | 425 + lib/widgets/gains_card.dart | 63 + lib/widgets/production_card.dart | 177 + linux/.gitignore | 1 + linux/CMakeLists.txt | 128 + linux/flutter/CMakeLists.txt | 88 + linux/flutter/generated_plugin_registrant.cc | 11 + linux/flutter/generated_plugin_registrant.h | 15 + linux/flutter/generated_plugins.cmake | 23 + linux/runner/CMakeLists.txt | 26 + linux/runner/main.cc | 6 + linux/runner/my_application.cc | 148 + linux/runner/my_application.h | 21 + macos/.gitignore | 7 + macos/Flutter/Flutter-Debug.xcconfig | 1 + macos/Flutter/Flutter-Release.xcconfig | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 12 + macos/Runner.xcodeproj/project.pbxproj | 705 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 99 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + macos/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 68 + .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes macos/Runner/Base.lproj/MainMenu.xib | 343 + macos/Runner/Configs/AppInfo.xcconfig | 14 + macos/Runner/Configs/Debug.xcconfig | 2 + macos/Runner/Configs/Release.xcconfig | 2 + macos/Runner/Configs/Warnings.xcconfig | 13 + macos/Runner/DebugProfile.entitlements | 12 + macos/Runner/Info.plist | 32 + macos/Runner/MainFlutterWindow.swift | 15 + macos/Runner/Release.entitlements | 8 + macos/RunnerTests/RunnerTests.swift | 12 + pubspec.lock | 418 + pubspec.yaml | 94 + test/widget_test.dart | 16 + web/favicon.png | Bin 0 -> 917 bytes web/icons/Icon-192.png | Bin 0 -> 5292 bytes web/icons/Icon-512.png | Bin 0 -> 8252 bytes web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes web/index.html | 46 + web/manifest.json | 35 + windows/.gitignore | 17 + windows/CMakeLists.txt | 108 + windows/flutter/CMakeLists.txt | 109 + .../flutter/generated_plugin_registrant.cc | 11 + windows/flutter/generated_plugin_registrant.h | 15 + windows/flutter/generated_plugins.cmake | 23 + windows/runner/CMakeLists.txt | 40 + windows/runner/Runner.rc | 121 + windows/runner/flutter_window.cpp | 71 + windows/runner/flutter_window.h | 33 + windows/runner/main.cpp | 43 + windows/runner/resource.h | 16 + windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes windows/runner/runner.exe.manifest | 14 + windows/runner/utils.cpp | 65 + windows/runner/utils.h | 19 + windows/runner/win32_window.cpp | 288 + windows/runner/win32_window.h | 102 + 148 files changed, 27871 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/com/example/etm_powersync_app/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle.kts create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle.kts create mode 100644 docs/nymea_api.md create mode 100644 docs/nymea_integrations.md create mode 100644 ios/.gitignore create mode 100644 ios/Flutter/AppFrameworkInfo.plist create mode 100644 ios/Flutter/Debug.xcconfig create mode 100644 ios/Flutter/Release.xcconfig create mode 100644 ios/Runner.xcodeproj/project.pbxproj create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner/AppDelegate.swift create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 ios/Runner/Base.lproj/Main.storyboard create mode 100644 ios/Runner/Info.plist create mode 100644 ios/Runner/Runner-Bridging-Header.h create mode 100644 ios/Runner/SceneDelegate.swift create mode 100644 ios/RunnerTests/RunnerTests.swift create mode 100644 lib/main.dart create mode 100644 lib/models/energy_data.dart create mode 100644 lib/models/nymea_models.dart create mode 100644 lib/models/thing_category.dart create mode 100644 lib/screens/ac_screen.dart create mode 100644 lib/screens/dashboard_screen.dart create mode 100644 lib/screens/energy_screen.dart create mode 100644 lib/screens/favorites_screen.dart create mode 100644 lib/screens/thing_detail_screen.dart create mode 100644 lib/screens/things_screen.dart create mode 100644 lib/services/nymea_service.dart create mode 100644 lib/theme/app_theme.dart create mode 100644 lib/widgets/energy_flow_widget.dart create mode 100644 lib/widgets/ev_charging_card.dart create mode 100644 lib/widgets/gains_card.dart create mode 100644 lib/widgets/production_card.dart create mode 100644 linux/.gitignore create mode 100644 linux/CMakeLists.txt create mode 100644 linux/flutter/CMakeLists.txt create mode 100644 linux/flutter/generated_plugin_registrant.cc create mode 100644 linux/flutter/generated_plugin_registrant.h create mode 100644 linux/flutter/generated_plugins.cmake create mode 100644 linux/runner/CMakeLists.txt create mode 100644 linux/runner/main.cc create mode 100644 linux/runner/my_application.cc create mode 100644 linux/runner/my_application.h create mode 100644 macos/.gitignore create mode 100644 macos/Flutter/Flutter-Debug.xcconfig create mode 100644 macos/Flutter/Flutter-Release.xcconfig create mode 100644 macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 macos/Runner.xcodeproj/project.pbxproj create mode 100644 macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 macos/Runner/AppDelegate.swift create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 macos/Runner/Base.lproj/MainMenu.xib create mode 100644 macos/Runner/Configs/AppInfo.xcconfig create mode 100644 macos/Runner/Configs/Debug.xcconfig create mode 100644 macos/Runner/Configs/Release.xcconfig create mode 100644 macos/Runner/Configs/Warnings.xcconfig create mode 100644 macos/Runner/DebugProfile.entitlements create mode 100644 macos/Runner/Info.plist create mode 100644 macos/Runner/MainFlutterWindow.swift create mode 100644 macos/Runner/Release.entitlements create mode 100644 macos/RunnerTests/RunnerTests.swift create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/widget_test.dart create mode 100644 web/favicon.png create mode 100644 web/icons/Icon-192.png create mode 100644 web/icons/Icon-512.png create mode 100644 web/icons/Icon-maskable-192.png create mode 100644 web/icons/Icon-maskable-512.png create mode 100644 web/index.html create mode 100644 web/manifest.json create mode 100644 windows/.gitignore create mode 100644 windows/CMakeLists.txt create mode 100644 windows/flutter/CMakeLists.txt create mode 100644 windows/flutter/generated_plugin_registrant.cc create mode 100644 windows/flutter/generated_plugin_registrant.h create mode 100644 windows/flutter/generated_plugins.cmake create mode 100644 windows/runner/CMakeLists.txt create mode 100644 windows/runner/Runner.rc create mode 100644 windows/runner/flutter_window.cpp create mode 100644 windows/runner/flutter_window.h create mode 100644 windows/runner/main.cpp create mode 100644 windows/runner/resource.h create mode 100644 windows/runner/resources/app_icon.ico create mode 100644 windows/runner/runner.exe.manifest create mode 100644 windows/runner/utils.cpp create mode 100644 windows/runner/utils.h create mode 100644 windows/runner/win32_window.cpp create mode 100644 windows/runner/win32_window.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b45cc43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Claude Code +.claude/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..c0fe018 --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "90673a4eef275d1a6692c26ac80d6d746d41a73a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: android + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: ios + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: linux + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: macos + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: web + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: windows + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9804a7a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,95 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Contexte général + +Dossier parent de tous les projets : `/home/etm/Projects/` + +- **`etm_powersync_app/`** (ce projet) — application Flutter HEMS, client mobile +- **`etm-nymea/`** — serveur nymea et plugins associés (C++/Qt), le backend + +Ce projet est le **client Flutter** qui communique avec le serveur nymea via JSON-RPC 2.0. + +--- + +## Commandes + +```bash +flutter pub get # installer/mettre à jour les dépendances +flutter run # lancer sur device/émulateur connecté +flutter build apk # build Android release +flutter analyze # analyse statique Dart +flutter test # lancer les tests +``` + +## Architecture + +### State management — un seul Provider + +`NymeaService` (`lib/services/nymea_service.dart`) est le seul `ChangeNotifier`. Il est fourni à la racine dans `main.dart` via `ChangeNotifierProvider`. Tous les écrans le consomment avec `context.watch()` ou `context.read()`. Il n'y a aucune autre couche de state management. + +### Navigation + +`MainShell` dans `main.dart` utilise un `IndexedStack` de 5 écrans (l'état est préservé entre les onglets) : + +| Index | Écran | Fichier | +|-------|-------|---------| +| 0 | Dashboard | `lib/screens/dashboard_screen.dart` | +| 1 | Énergie | `lib/screens/energy_screen.dart` | +| 2 | Things | `lib/screens/things_screen.dart` | +| 3 | A/C | `lib/screens/ac_screen.dart` | +| 4 | Favoris | `lib/screens/favorites_screen.dart` | + +### Communication avec nymea + +`NymeaService.connect()` supporte deux transports : + +- **TCP brut** port 2222 (défaut) : nymea parle **en premier** (JSON délimité par `\n`). La fragmentation est gérée via `StringBuffer`. +- **WebSocket** port 4444 : c'est le **client qui parle en premier** en envoyant `JSONRPC.Hello`. + +Les requêtes utilisent des IDs auto-incrémentés stockés dans `_pendingRequests: Map`. Timeout global : 15 secondes. Les notifications push nymea (pas de champ `id`, ont un champ `notification`) sont dispatchées dans `_handleNotification()`. + +**Convention nymea énergie importante** : `currentPowerProduction` est **négatif** (producteur = négatif). Le service convertit en positif dans `_parsePowerBalance()`. + +### Mode simulation + +`NymeaService.startSimulation()` déconnecte toute connexion live, active `_isSimulation = true`, génère des things fictifs et une production PV simulée en sinus (timer 2 s). Le dashboard démarre automatiquement la simulation au premier affichage si non connecté. Toutes les méthodes du service font un garde `if (_isSimulation) return …` pour éviter les vrais appels RPC. + +### Modèles de données + +- **`EnergyData`** (`lib/models/energy_data.dart`) — objet immutable avec `copyWith`, regroupe toutes les métriques énergie temps-réel et journalières + état de la borne EV. +- **`NymeaThing` / `NymeaThingClass` / `NymeaStateType` / `NymeaActionType`** (`lib/models/nymea_models.dart`) — miroir de l'API Integrations nymea. `NymeaThingClass.primaryStateType` détermine l'état affiché sur les cards. +- **`ThingCategory` + `interfaceToCategoryMap`** (`lib/models/thing_category.dart`) — mappe les interfaces nymea (ex. `"evcharger"`, `"solarinverter"`) vers des catégories d'affichage utilisées par `ThingsScreen`. +- **`FavoriteWidget`** (`lib/models/nymea_models.dart`) — descripteurs de widgets stockés en mémoire dans `NymeaService._favoriteWidgets`. + +### Thème + +`AppTheme` (`lib/theme/app_theme.dart`) expose des constantes `Color` nommées : `primaryGreen`, `solarYellow`, `gridGray`, `homeBlue`, `batteryGreen`, `boostRed`, `pvGreen`, `minPvBlue`, etc. Toujours utiliser ces constantes plutôt que des valeurs hex brutes. + +## Conventions + +- **Code** : anglais — **Commentaires** : français +- **Nommage** : camelCase variables, PascalCase classes +- Les écrans utilisent `Consumer` ou `context.watch` — ne jamais dupliquer l'état du service dans un `State` +- **Unités** : Watts (W) pour les puissances instantanées, Wh pour les énergies journalières. L'API nymea retourne des totaux en kWh que le service multiplie par 1000. + +## Méthodes nymea principales + +| Méthode | Rôle | +|---------|------| +| `JSONRPC.Hello` | Handshake (WebSocket uniquement — envoyé par le client) | +| `Integrations.GetThings` | Charger les devices configurés | +| `Integrations.GetThingClasses` | Charger les définitions de classes (filtre `thingClassIds` supporté) | +| `Integrations.ExecuteAction` | Exécuter une action sur un thing | +| `Integrations.GetStateValue` | Lire une valeur d'état | +| `Energy.GetPowerBalance` | Snapshot du flux énergétique actuel | +| `Energy.GetEnergyLogs` | Historique énergie (échantillons horaires) | +| `Energy.SetChargingMode` | Mode borne EV : `pv`, `minpv`, `boost` | + +Notifications push gérées : `Energy.PowerBalanceChanged`, `Energy.RootMeterChanged`, `Integrations.StateChanged`, `Integrations.ThingAdded`, `Integrations.ThingRemoved`. + +## Documentation API + +- `docs/nymea_api.md` — introspection complète de l'API nymea +- `docs/nymea_integrations.md` — namespace Integrations en détail diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3be253 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# etm_powersync_app + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..54d3fee --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.etm_powersync_app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.etm_powersync_app" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..34c43ef --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/etm_powersync_app/MainActivity.kt b/android/app/src/main/kotlin/com/example/etm_powersync_app/MainActivity.kt new file mode 100644 index 0000000..9d2b5f0 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/etm_powersync_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.etm_powersync_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/docs/nymea_api.md b/docs/nymea_api.md new file mode 100644 index 0000000..bde9b7b --- /dev/null +++ b/docs/nymea_api.md @@ -0,0 +1,5938 @@ +# Nymea JSON-RPC API + +## AirConditioning.AddZone + +Create a zones. + +### Parameters + +- name +- o:indoorSensors +- o:notifications +- o:outdoorSensors +- o:thermostats +- o:windowSensors + +### Returns + +- airConditioningError +- o:zone + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.AddZone", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AirConditioning.GetZones + +Get all Zones. + +### Parameters + +- o:zoneId + +### Returns + +- airConditioningError +- zones + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.GetZones", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AirConditioning.RemoveZone + +Remove a zone. + +### Parameters + +- zoneId + +### Returns + +- airConditioningError + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.RemoveZone", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AirConditioning.SetZoneName + +Set zone name. + +### Parameters + +- name +- zoneId + +### Returns + +- airConditioningError + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.SetZoneName", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AirConditioning.SetZoneSetpointOverride + +Set zone setpoint override temperature. Parameter minutes gives the minutes until the setpoint should return to the standby/schedule. + +### Parameters + +- mode +- o:minutes +- setpointOverride +- zoneId + +### Returns + +- airConditioningError + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.SetZoneSetpointOverride", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AirConditioning.SetZoneStandbySetpoint + +Set zone standby setpoint temperature. + +### Parameters + +- standbySetpoint +- zoneId + +### Returns + +- airConditioningError + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.SetZoneStandbySetpoint", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AirConditioning.SetZoneThings + +Set Zone things + +### Parameters + +- o:indoorSensors +- o:notifications +- o:outdoorSensors +- o:thermostats +- o:windowSensors +- zoneId + +### Returns + +- airConditioningError + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.SetZoneThings", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AirConditioning.SetZoneWeekSchedule + +Set the time schedule for a zone. + +### Parameters + +- weekSchedule +- zoneId + +### Returns + +- airConditioningError + +### Example Request + +```json +{ + "id": 1, + "method": "AirConditioning.SetZoneWeekSchedule", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AppData.Load + +Retrieve an app data storage value that has previously been set with Store(). If no value had been set for this appId/key combination before, an empty value will be returned. + +### Parameters + +- appId +- key +- o:group + +### Returns + +- value + +### Example Request + +```json +{ + "id": 1, + "method": "AppData.Load", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## AppData.Store + +Store an app data entry to the server. App data can be used by the client application to store configuration values. The app data storage is a key-value pair storage. Each entry value is identified by an appId, a key and optionally a group. The value data is a bytearray and can contain arbitrary data, such as a JSON map or image data, however, be aware of the maximum packet size for the used transport. +This might be useful to a client application to sync settings across multiple instances of the same application. +The group parameter might be used to create groups for this application. +IMPORTANT: Currently no verification of the appId is done. The appid is merely a mechanism to prevent different different client apps from colliding by using the same key for data entries. This implies that the app data storage may not be suited for sensitive data given that anyone with a valid server token can read it. + + +### Parameters + +- appId +- key +- o:group +- value + +### Example Request + +```json +{ + "id": 1, + "method": "AppData.Store", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.DeleteMqttPolicy + +Delete a MQTT policy from the broker. + +### Parameters + +- clientId + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.DeleteMqttPolicy", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.DeleteMqttServerConfiguration + +Delete a MQTT Server interface of the server. + +### Parameters + +- id + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.DeleteMqttServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.DeleteTcpServerConfiguration + +Delete a TCP interface of the server. Note: if you are deleting the configuration for the interface you are currently connected to, the connection will be dropped. + +### Parameters + +- id + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.DeleteTcpServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.DeleteTunnelProxyServerConfiguration + +Delete a Tunnel Proxy Server interface of the server. Note: if you are deleting the configuration for the interface you are currently connected to, the connection will be dropped. + +### Parameters + +- id + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.DeleteTunnelProxyServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.DeleteWebServerConfiguration + +Delete a WebServer interface of the server. + +### Parameters + +- id + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.DeleteWebServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.DeleteWebSocketServerConfiguration + +Delete a WebSocket Server interface of the server. Note: if you are deleting the configuration for the interface you are currently connected to, the connection will be dropped. + +### Parameters + +- id + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.DeleteWebSocketServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.GetAvailableLanguages + +Returns a list of locale codes available for the server. i.e. en_US, de_AT + +### Returns + +- languages + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.GetAvailableLanguages", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.GetConfigurations + +Get all configuration parameters of the server. + +### Returns + +- basicConfiguration +- tcpServerConfigurations +- tunnelProxyServerConfigurations +- webServerConfigurations +- webSocketServerConfigurations + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.GetConfigurations", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.GetMqttPolicies + +Get all MQTT broker policies. + +### Returns + +- mqttPolicies + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.GetMqttPolicies", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.GetMqttServerConfigurations + +Get all MQTT Server configurations. + +### Returns + +- mqttServerConfigurations + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.GetMqttServerConfigurations", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.GetTimeZones + +Get the list of available timezones. + +### Returns + +- timeZones + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.GetTimeZones", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetDebugServerEnabled + +Enable or disable the debug server. + +### Parameters + +- enabled + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetDebugServerEnabled", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetLanguage + +Sets the server language to the given language. See also: "GetAvailableLanguages" + +### Parameters + +- language + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetLanguage", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetLocation + +Sets the server location. + +### Parameters + +- location + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetLocation", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetMqttPolicy + +Configure a MQTT broker policy. If the ID is an existing one, the existing policy will be modified, otherwise a new one will be added. + +### Parameters + +- policy + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetMqttPolicy", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetMqttServerConfiguration + +Configure a MQTT Server interface on the MQTT broker. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Setting authenticationEnabled to true will require MQTT clients to use credentials set in the MQTT broker policies. + +### Parameters + +- configuration + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetMqttServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetServerName + +Set the name of the server. Default is nymea. + +### Parameters + +- serverName + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetServerName", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetTcpServerConfiguration + +Configure a TCP interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Note: if you are changing the configuration for the interface you are currently connected to, the connection will be dropped. + +### Parameters + +- configuration + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetTcpServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetTimeZone + +Set the time zone of the server. See also: "GetTimeZones" + +### Parameters + +- timeZone + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetTimeZone", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetTunnelProxyServerConfiguration + +Configure a Tunnel Proxy Server interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Note: if you are changing the configuration for the interface you are currently connected to, the connection will be dropped. + +### Parameters + +- configuration + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetTunnelProxyServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetWebServerConfiguration + +Configure a WebServer interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. + +### Parameters + +- configuration + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetWebServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Configuration.SetWebSocketServerConfiguration + +Configure a WebSocket Server interface of the server. If the ID is an existing one, the existing config will be modified, otherwise a new one will be added. Note: if you are changing the configuration for the interface you are currently connected to, the connection will be dropped. + +### Parameters + +- configuration + +### Returns + +- configurationError + +### Example Request + +```json +{ + "id": 1, + "method": "Configuration.SetWebSocketServerConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Debug.GetLoggingCategories + +Get all available logging categories. + +### Returns + +- loggingCategories + +### Example Request + +```json +{ + "id": 1, + "method": "Debug.GetLoggingCategories", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Debug.SetLoggingCategoryLevel + +Set the logging category with the given name to the given logging level. + +### Parameters + +- level +- name + +### Returns + +- debugError + +### Example Request + +```json +{ + "id": 1, + "method": "Debug.SetLoggingCategoryLevel", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Energy.GetPowerBalance + +Get the current power balance. That is, production, consumption and acquisition. + +### Returns + +- currentPowerAcquisition +- currentPowerConsumption +- currentPowerProduction +- currentPowerStorage +- totalAcquisition +- totalConsumption +- totalProduction +- totalReturn + +### Example Request + +```json +{ + "id": 1, + "method": "Energy.GetPowerBalance", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Energy.GetPowerBalanceLogs + +Get logs for the power balance. If from is not give, the log will start at the beginning of recording. If to is not given, the logs will and at the last sample for this sample rate before now. + +### Parameters + +- o:from +- o:to +- sampleRate + +### Returns + +- powerBalanceLogEntries + +### Example Request + +```json +{ + "id": 1, + "method": "Energy.GetPowerBalanceLogs", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Energy.GetRootMeter + +Get the root meter ID. If there is no root meter set, the params will be empty. + +### Returns + +- o:rootMeterThingId + +### Example Request + +```json +{ + "id": 1, + "method": "Energy.GetRootMeter", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Energy.GetThingPowerLogs + +Get logs for one or more things power values. If thingIds is not given, logs for all energy related things will be returned. If from is not given, the log will start at the beginning of recording. If to is not given, the logs will and at the last sample for this sample rate before now. If the parameter "includeCurrent" is set to true, the result will contain the newest log entries available, regardless of the sample rate (that is, 1 minute). This may be useful to calculate the difference to the newest entry of the fetched sample rate and the current values to display the live value until the current sample is completed. + +### Parameters + +- o:from +- o:includeCurrent +- o:thingIds +- o:to +- sampleRate + +### Returns + +- o:currentEntries +- thingPowerLogEntries + +### Example Request + +```json +{ + "id": 1, + "method": "Energy.GetThingPowerLogs", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Energy.SetRootMeter + +Set the root meter. + +### Parameters + +- rootMeterThingId + +### Returns + +- energyError + +### Example Request + +```json +{ + "id": 1, + "method": "Energy.SetRootMeter", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## EvDash.AddUser + +Add a new user with the given username and password. + +### Parameters + +- password +- username + +### Returns + +- evDashError + +### Example Request + +```json +{ + "id": 1, + "method": "EvDash.AddUser", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## EvDash.GetEnabled + +Get the enabled status of EV Dash service. + +### Returns + +- enabled + +### Example Request + +```json +{ + "id": 1, + "method": "EvDash.GetEnabled", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## EvDash.GetUsers + +Get the list of available users names. + +### Returns + +- usernames + +### Example Request + +```json +{ + "id": 1, + "method": "EvDash.GetUsers", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## EvDash.RemoveUser + +Remove the user with the given username. + +### Parameters + +- username + +### Returns + +- evDashError + +### Example Request + +```json +{ + "id": 1, + "method": "EvDash.RemoveUser", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## EvDash.SetEnabled + +Enable/Disable the EV Dash service. + +### Parameters + +- enabled + +### Returns + +- evDashError + +### Example Request + +```json +{ + "id": 1, + "method": "EvDash.SetEnabled", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.AddThing + +Add a new thing to the system. Only things with a setupMethod of SetupMethodJustAdd can be added this way. For things with a setupMethod different than SetupMethodJustAdd, use PairThing. Things with CreateMethodJustAdd require all parameters to be supplied here. Things with CreateMethodDiscovery require the use of a thingDescriptorId. For discovered things, params are not required and will be taken from the ThingDescriptor, however, they may be overridden by supplying thingParams. + +### Parameters + +- name +- o:thingClassId +- o:thingDescriptorId +- o:thingParams + +### Returns + +- o:displayMessage +- o:thingId +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.AddThing", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.BrowseThing + +Browse a thing. If a ThingClass indicates a thing is browsable, this method will return the BrowserItems. If no parameter besides the thingId is used, the root node of this thingwill be returned. Any returned item which is browsable can be passed as node. Results will be children of the given node. +In case of an error during browsing, the error will be indicated and the displayMessage may contain additional information for the user. The displayMessage will be translated. A client UI showing this message to the user should be prepared for empty, but also longer strings. + +### Parameters + +- o:itemId +- thingId + +### Returns + +- o:displayMessage +- o:items +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.BrowseThing", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.ConfirmPairing + +Confirm an ongoing pairing. For SetupMethodUserAndPassword, provide the username in the "username" field and the password in the "secret" field. For SetupMethodEnterPin and provide the PIN in the "secret" field. In case of SetupMethodOAuth, the previously opened web view will eventually be redirected to http://128.0.0.1:8888 and the OAuth code as query parameters to this url. Provide the entire unmodified URL in the secret field. + +### Parameters + +- o:secret +- o:username +- pairingTransactionId + +### Returns + +- o:displayMessage +- o:thingId +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.ConfirmPairing", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.ConnectIO + +Connect two generic IO states. Input and output need to be compatible, that is, either a digital input and a digital output, or an analog input and an analog output. If successful, the connectionId will be returned. + +### Parameters + +- inputStateTypeId +- inputThingId +- o:inverted +- outputStateTypeId +- outputThingId + +### Returns + +- o:ioConnectionId +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.ConnectIO", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.DisconnectIO + +Disconnect an existing IO connection. + +### Parameters + +- ioConnectionId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.DisconnectIO", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.DiscoverThings + +Performs a thing discovery for things of the given thingClassId and returns the results. This function may take a while to return. Note that this method will include all the found things, that is, including things that may already have been added. Those things will have thingId set to the id of the already added thing. Such results may be used to reconfigure existing things and might be filtered in cases where only unknown things are of interest. + +### Parameters + +- o:discoveryParams +- thingClassId + +### Returns + +- o:displayMessage +- o:thingDescriptors +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.DiscoverThings", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.EditThing + +Edit the name of a thing. + +### Parameters + +- name +- thingId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.EditThing", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.ExecuteAction + +Execute a single action. + +### Parameters + +- actionTypeId +- o:params +- thingId + +### Returns + +- o:displayMessage +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.ExecuteAction", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.ExecuteBrowserItem + +Execute the item identified by itemId on the given thing. +In case of an error during execution, the error will be indicated and the displayMessage may contain additional information for the user. The displayMessage will be translated. A client UI showing this message to the user should be prepared for empty, but also longer strings. + +### Parameters + +- itemId +- thingId + +### Returns + +- o:displayMessage +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.ExecuteBrowserItem", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.ExecuteBrowserItemAction + +Execute the action for the browser item identified by actionTypeId and the itemId on the given thing. +In case of an error during execution, the error will be indicated and the displayMessage may contain additional information for the user. The displayMessage will be translated. A client UI showing this message to the user should be prepared for empty, but also longer strings. + +### Parameters + +- actionTypeId +- itemId +- o:params +- thingId + +### Returns + +- o:displayMessage +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.ExecuteBrowserItemAction", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetActionTypes + +Get action types for a specified thingClassId. + +### Parameters + +- thingClassId + +### Returns + +- actionTypes + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetActionTypes", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetBrowserItem + +Get a single item from the browser. This won't give any more info on an item than a regular BrowseThing call, but it allows to fetch details of an item if only the ID is known. +In case of an error during browsing, the error will be indicated and the displayMessage may contain additional information for the user. The displayMessage will be translated. A client UI showing this message to the user should be prepared for empty, but also longer strings. + +### Parameters + +- o:itemId +- thingId + +### Returns + +- o:displayMessage +- o:item +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetBrowserItem", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetEventTypes + +Get event types for a specified thingClassId. + +### Parameters + +- thingClassId + +### Returns + +- eventTypes + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetEventTypes", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetIOConnections + +Fetch IO connections. Optionally filtered by thingId and stateTypeId. + +### Parameters + +- o:thingId + +### Returns + +- o:ioConnections +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetIOConnections", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetPluginConfiguration + +Get a plugin's params. + +### Parameters + +- pluginId + +### Returns + +- o:configuration +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetPluginConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetPlugins + +Returns a list of loaded plugins. + +### Returns + +- plugins + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetPlugins", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetStateTypes + +Get state types for a specified thingClassId. + +### Parameters + +- thingClassId + +### Returns + +- stateTypes + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetStateTypes", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetStateValue + +Get the value of the given thing and the given stateType + +### Parameters + +- stateTypeId +- thingId + +### Returns + +- o:value +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetStateValue", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetStateValues + +Get all the state values of the given thing. + +### Parameters + +- thingId + +### Returns + +- o:values +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetStateValues", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetThingClasses + +Returns a list of supported thing classes, optionally filtered by vendorId or by a list of thing class ids. + +### Parameters + +- o:thingClassIds +- o:vendorId + +### Returns + +- o:thingClasses +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetThingClasses", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetThings + +Returns a list of configured things, optionally filtered by thingId. + +### Parameters + +- o:thingId + +### Returns + +- o:things +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetThings", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.GetVendors + +Returns a list of supported Vendors. + +### Returns + +- vendors + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.GetVendors", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.PairThing + +Pair a new thing. Use this to set up or reconfigure things for ThingClasses with a setupMethod different than SetupMethodJustAdd. Depending on the CreateMethod and whether a new thing is set up or an existing one is reconfigured, different parameters are required: +CreateMethodJustAdd takes the thingClassId and the parameters you want to have with that thing. If an existing thing should be reconfigured, the thingId of said thing should be given additionally. +CreateMethodDiscovery requires the use of a thingDescriptorId, previously obtained with DiscoverThings. Optionally, parameters can be overridden with the give thingParams. ThingDescriptors containing a thingId will reconfigure an existing thing, descriptors without a thingId will add a new thing to the system. +If success is true, the return values will contain a pairingTransactionId, a displayMessage and the setupMethod. Depending on the setupMethod, the application should present the use an appropriate login mask, that is, For SetupMethodDisplayPin the user should enter a pin that is displayed on the device or online service, for SetupMethodEnterPin the application should present the given PIN so the user can enter it on the device or online service. For SetupMethodPushButton, the displayMessage shall be presented to the user as informational hints to press a button on the device. For SetupMethodUserAndPassword a login mask for a user and password login should be presented to the user. In case of SetupMethodOAuth, an OAuth URL will be returned which shall be opened in a web view to allow the user logging in. +Once the login procedure has completed, the application shall proceed with ConfirmPairing, providing the results of the pairing procedure. + +### Parameters + +- o:name +- o:thingClassId +- o:thingDescriptorId +- o:thingId +- o:thingParams + +### Returns + +- o:displayMessage +- o:oAuthUrl +- o:pairingTransactionId +- o:pin +- o:setupMethod +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.PairThing", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.ReconfigureThing + +Reconfigure a thing. This comes down to removing and recreating a thing with new parameters but keeping its thing id the same (and with that keeping rules, tags etc). For things with create method CreateMethodDiscovery, a discovery (DiscoverThings) shall be performed first and this method is to be called with a thingDescriptorId of the re-discovered thing instead of the thingId directly. Thing parameters will be taken from the discovery, but can be overridden individually here by providing them in the thingParams parameter. Only writable parameters can be changed. + +### Parameters + +- o:thingDescriptorId +- o:thingId +- o:thingParams + +### Returns + +- o:displayMessage +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.ReconfigureThing", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.RemoveThing + +Remove a thing and all its childs from the system. RemovePolicy is deprecated and has no effect any more. + +### Parameters + +- d:o:removePolicy +- thingId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.RemoveThing", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.SetActionLogging + +Enable/disable logging for the given action type on the given thing. + +### Parameters + +- actionTypeId +- enabled +- thingId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.SetActionLogging", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.SetEventLogging + +Enable/disable logging for the given event type on the given thing. + +### Parameters + +- enabled +- eventTypeId +- thingId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.SetEventLogging", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.SetPluginConfiguration + +Set a plugin's params. + +### Parameters + +- configuration +- pluginId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.SetPluginConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.SetStateFilter + +Set the filter for the given state on the given thing. + +### Parameters + +- filter +- stateTypeId +- thingId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.SetStateFilter", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.SetStateLogging + +Enable/disable logging for the given state type on the given thing. + +### Parameters + +- enabled +- stateTypeId +- thingId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.SetStateLogging", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Integrations.SetThingSettings + +Change the settings of a thing. + +### Parameters + +- settings +- thingId + +### Returns + +- thingError + +### Example Request + +```json +{ + "id": 1, + "method": "Integrations.SetThingSettings", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.Authenticate + +Authenticate a client to the api via user & password challenge. Provide a device name which allows the user to identify the client and revoke the token in case the device is lost or stolen. This will return a new token to be used to authorize a client at the API. + +### Parameters + +- deviceName +- password +- username + +### Returns + +- o:scopes +- o:token +- o:username +- success + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.Authenticate", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.CreateUser + +Create a new user in the API. This is only allowed to be called when the initial setup is required. To create additional users, use Users.CreateUser instead. Call Authenticate after this to obtain a device token for the newly created user. + +### Parameters + +- o:displayName +- o:email +- password +- username + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.CreateUser", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.Hello + +Initiates a connection. Use this method to perform an initial handshake of the connection. Optionally, a parameter "locale" is can be passed to set up the used locale for this connection. Strings such as ThingClass displayNames etc will be localized to this locale. If this parameter is omitted, the default system locale (depending on the configuration) is used. The reply of this method contains information about this core instance such as version information, uuid and its name. The locale valueindicates the locale used for this connection. Note: This method can be called multiple times. The locale used in the last call for this connection will be used. Other values, like initialSetupRequired might change if the setup has been performed in the meantime. + The field cacheHashes may contain a map of methods and MD5 hashes. As long as the hash for a method does not change, a client may use a previously cached copy of the call instead of fetching the content again. While the Hello call doesn't necessarily require a token, this can be called with a token. If a token is provided, it will be verified and the reply contains information about the tokens validity and the user and permissions for the given token. + +### Parameters + +- o:locale + +### Returns + +- authenticationRequired +- initialSetupRequired +- language +- locale +- name +- o:authenticated +- o:cacheHashes +- o:experiences +- o:permissionScopes +- o:username +- protocol version +- pushButtonAuthAvailable +- server +- uuid +- version + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.Hello", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.Introspect + +Introspect this API. + +### Returns + +- methods +- notifications +- types + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.Introspect", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.KeepAlive + +This is basically a Ping/Pong mechanism a client app may use to check server connectivity. Currently, the server does not actually do anything with this information and will return the call providing the given sessionId back to the caller. It is up to the client whether to use this or not and not required by the server to keep the connection alive. + +### Parameters + +- sessionId + +### Returns + +- sessionId +- success + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.KeepAlive", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.RequestPushButtonAuth + +Authenticate a client to the api via Push Button method. Provide a device name which allows the user to identify the client and revoke the token in case the device is lost or stolen. If push button hardware is available, this will return with success and start listening for push button presses. When the push button is pressed, the PushButtonAuthFinished notification will be sent to the requesting client. The procedure will be cancelled when the connection is interrupted. If another client requests push button authentication while a procedure is still going on, the second call will take over and the first one will be notified by the PushButtonAuthFinished signal about the error. The application should make it clear to the user to not press the button when the procedure fails as this can happen for 2 reasons: a) a second user is trying to auth at the same time and only the currently active user should press the button or b) it might indicate an attacker trying to take over and snooping in for tokens. + +### Parameters + +- deviceName + +### Returns + +- success +- transactionId + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.RequestPushButtonAuth", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.SetNotificationStatus + +Enable/Disable notifications for this connections. Either "enabled" or "namespaces" needs to be given but not both of them. The boolean based "enabled" parameter will enable/disable all notifications at once. If instead the list-based "namespaces" parameter is provided, all given namespaceswill be enabled, the others will be disabled. The return value of "success" will indicate success of the operation. The "enabled" property in the return value is deprecated and used for legacy compatibilty only. It will be set to true if at least one namespace has been enabled. + +### Parameters + +- d:o:enabled +- o:namespaces + +### Returns + +- d:enabled +- namespaces + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.SetNotificationStatus", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## JSONRPC.Version + +Version of this nymea/JSONRPC interface. + +### Returns + +- protocol version +- qtBuildVersion +- qtVersion +- version + +### Example Request + +```json +{ + "id": 1, + "method": "JSONRPC.Version", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Logging.GetLogEntries + +Get the LogEntries matching the given filter. +"sources": Builtin sources are: "core", "rules", "scripts", "integrations". May be extended by experience plugins. +"columns": Columns to be returned. +"filter": A map of column:value entries. Only = is supported currently. +"startTime": The datetime of the oldest entry, in ms. +"endTime": The datetime of the newest entry, in ms. +"sampleRate": If given, returns a sampled series of the values, filling in gaps with the previous value. +"sortOrder": Sort order of results. Note that this impacts the filling of gaps when resampling. +"limit": Maximum amount of entries to be returned. +"offset": Offset to be skipped before returning entries. + +### Parameters + +- o:columns +- o:endTime +- o:filter +- o:limit +- o:offset +- o:sampleRate +- o:sortOrder +- o:startTime +- sources + +### Returns + +- count +- o:logEntries +- offset + +### Example Request + +```json +{ + "id": 1, + "method": "Logging.GetLogEntries", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ModbusRtu.AddModbusRtuMaster + +Add a new modbus RTU master with the given configuration. The timeout value is in milli seconds and the minimum value is 10 ms. + +### Parameters + +- baudrate +- dataBits +- numberOfRetries +- parity +- serialPort +- stopBits +- timeout + +### Returns + +- modbusError +- o:modbusUuid + +### Example Request + +```json +{ + "id": 1, + "method": "ModbusRtu.AddModbusRtuMaster", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ModbusRtu.GetModbusRtuMasters + +Get the list of configured modbus RTU masters. + +### Returns + +- modbusError +- o:modbusRtuMasters + +### Example Request + +```json +{ + "id": 1, + "method": "ModbusRtu.GetModbusRtuMasters", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ModbusRtu.GetSerialPorts + +Get the list of available serial ports from the host system. + +### Returns + +- serialPorts + +### Example Request + +```json +{ + "id": 1, + "method": "ModbusRtu.GetSerialPorts", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ModbusRtu.ReconfigureModbusRtuMaster + +Reconfigure the modbus RTU master with the given UUID and configuration. + +### Parameters + +- baudrate +- dataBits +- modbusUuid +- numberOfRetries +- parity +- serialPort +- stopBits +- timeout + +### Returns + +- modbusError + +### Example Request + +```json +{ + "id": 1, + "method": "ModbusRtu.ReconfigureModbusRtuMaster", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ModbusRtu.RemoveModbusRtuMaster + +Remove the modbus RTU master with the given modbus UUID. + +### Parameters + +- modbusUuid + +### Returns + +- modbusError + +### Example Request + +```json +{ + "id": 1, + "method": "ModbusRtu.RemoveModbusRtuMaster", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.ConnectWifiNetwork + +Connect to the wifi network with the given ssid and password. + +### Parameters + +- interface +- o:password +- ssid + +### Returns + +- networkManagerError + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.ConnectWifiNetwork", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.CreateWiredConnection + +Create a wired connection. + +### Parameters + +- interface +- o:dns +- o:gateway +- o:ip +- o:prefix +- type + +### Returns + +- networkManagerError + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.CreateWiredConnection", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.DisconnectInterface + +Disconnect the given network interface. The interface will remain disconnected until the user connect it again. + +### Parameters + +- interface + +### Returns + +- networkManagerError + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.DisconnectInterface", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.EnableNetworking + +Enable or disable networking in the NetworkManager. + +### Parameters + +- enable + +### Returns + +- networkManagerError + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.EnableNetworking", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.EnableWirelessNetworking + +Enable or disable wireless networking in the NetworkManager. + +### Parameters + +- enable + +### Returns + +- networkManagerError + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.EnableWirelessNetworking", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.GetNetworkDevices + +Get the list of current network devices. + +### Returns + +- networkManagerError +- o:wiredNetworkDevices +- o:wirelessNetworkDevices + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.GetNetworkDevices", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.GetNetworkStatus + +Get the current network manager status. + +### Returns + +- networkManagerError +- o:status + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.GetNetworkStatus", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.GetWirelessAccessPoints + +Get the current list of wireless network access points for the given interface. The interface has to be a WirelessNetworkDevice. + +### Parameters + +- interface + +### Returns + +- networkManagerError +- o:wirelessAccessPoints + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.GetWirelessAccessPoints", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.ScanWifiNetworks + +Start a wifi scan for searching new networks. + +### Parameters + +- interface + +### Returns + +- networkManagerError + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.ScanWifiNetworks", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NetworkManager.StartAccessPoint + +Start a WiFi Access point on the given interface with the given SSID and password. Use DisconnectInterface to stop it again. + +### Parameters + +- interface +- password +- ssid + +### Returns + +- networkManagerError + +### Example Request + +```json +{ + "id": 1, + "method": "NetworkManager.StartAccessPoint", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetAcquisitionTolerance + +Get the acquisition tolerance. This value will control how much solar surplus is required to start the charging. + +### Returns + +- acquisitionTolerance + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetAcquisitionTolerance", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetAvailableSpotMarketProviders + +Get the list of available spot market data providers. + +### Returns + +- providers + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetAvailableSpotMarketProviders", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetBatteryLevelConsideration + +Get the battery level consideration. This value will control how much the energy storage is taking in account + +### Returns + +- batteryLevelConsideration + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetBatteryLevelConsideration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetChargingInfos + +Get the charging info for all or a single EV charger + +### Parameters + +- o:evChargerId + +### Returns + +- chargingInfos + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetChargingInfos", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetChargingSchedules + +Get the planed charging schedules. + +### Returns + +- chargingSchedules + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetChargingSchedules", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetLockOnUnplug + +Get the lock on unplug setting. + +### Returns + +- lockOnUnplug + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetLockOnUnplug", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetPhasePowerLimit + +Get the phase power consumption limit. 0 if unset. This needs to be set in order for the smart charging to do anything. + +### Returns + +- phasePowerLimit + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetPhasePowerLimit", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetSpotMarketConfiguration + +Get the current spotmarket configuration. + +### Returns + +- available +- enabled +- o:providerId + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetSpotMarketConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.GetSpotMarketScoreEntries + +Get the current score entries from the current spot market. If spot market is not enabled or not available, the list will be empty. The weighting of the available score entries goes from 0 (worst) to 1.0 (best). + +### Returns + +- spotMarketScoreEntries + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.GetSpotMarketScoreEntries", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.SetAcquisitionTolerance + +Set the acquisition tolerance for the charging. + +### Parameters + +- acquisitionTolerance + +### Returns + +- energyError + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.SetAcquisitionTolerance", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.SetBatteryLevelConsideration + +Set the battery level consideration for the charging. + +### Parameters + +- batteryLevelConsideration + +### Returns + +- energyError + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.SetBatteryLevelConsideration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.SetChargingInfo + +Set the charging info for an EV charger. Only given properties will be set, others will be untouched. + +### Parameters + +- chargingInfo + +### Returns + +- energyError + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.SetChargingInfo", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.SetLockOnUnplug + +Set the lock on unplug setting. + +### Parameters + +- lockOnUnplug + +### Returns + +- energyError + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.SetLockOnUnplug", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.SetPhasePowerLimit + +Set the phase power consumption limit. This is the maximum allowed Ampere per phase. A value of 0 means unset. All smart charging will be disabled. + +### Parameters + +- phasePowerLimit + +### Returns + +- energyError + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.SetPhasePowerLimit", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## NymeaEnergy.SetSpotMarketConfiguration + +Set the current spotmarket configuration. If enabled, the provideId must be valid. You can get the available providers using GetAvailableSpotMarketProviders. + +### Parameters + +- enabled +- o:providerId + +### Returns + +- energyError + +### Example Request + +```json +{ + "id": 1, + "method": "NymeaEnergy.SetSpotMarketConfiguration", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.AddRule + +Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that only one of either eventDescriptor or eventDescriptorList may be passed at a time. A rule can be created but left disabled, meaning it won't actually be executed until set to enabled. If not given, enabled defaults to true. A rule can have a list of actions and exitActions. It must have at least one Action. For state based rules, actions will be executed when the system enters a state matching the stateDescriptor. The exitActions will be executed when the system leaves the described state again. For event based rules, actions will be executed when a matching event happens and if the stateEvaluator matches the system's state. ExitActions for such rules will be executed when a matching event happens and the stateEvaluator is not matching the system's state. A rule marked as executable can be executed via the API using Rules.ExecuteRule, that means, its actions will be executed regardless of the eventDescriptor and stateEvaluators. + +### Parameters + +- actions +- name +- o:enabled +- o:eventDescriptors +- o:executable +- o:exitActions +- o:stateEvaluator +- o:timeDescriptor + +### Returns + +- o:ruleId +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.AddRule", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.DisableRule + +Disable a rule. The rule won't be triggered by it's events or state changes while it is disabled. If successful, the notification "Rule.RuleConfigurationChanged" will be emitted. + +### Parameters + +- ruleId + +### Returns + +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.DisableRule", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.EditRule + +Edit the parameters of a rule. The configuration of the rule with the given ruleId will be replaced with the new given configuration. In ordert to enable or disable a Rule, please use the methods "Rules.EnableRule" and "Rules.DisableRule". If successful, the notification "Rule.RuleConfigurationChanged" will be emitted. + +### Parameters + +- actions +- name +- o:enabled +- o:eventDescriptors +- o:executable +- o:exitActions +- o:stateEvaluator +- o:timeDescriptor +- ruleId + +### Returns + +- o:rule +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.EditRule", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.EnableRule + +Enabled a rule that has previously been disabled.If successful, the notification "Rule.RuleConfigurationChanged" will be emitted. + +### Parameters + +- ruleId + +### Returns + +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.EnableRule", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.ExecuteActions + +Execute the action list of the rule with the given ruleId. + +### Parameters + +- ruleId + +### Returns + +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.ExecuteActions", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.ExecuteExitActions + +Execute the exit action list of the rule with the given ruleId. + +### Parameters + +- ruleId + +### Returns + +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.ExecuteExitActions", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.FindRules + +Find a list of rules containing any of the given parameters. + +### Parameters + +- thingId + +### Returns + +- ruleIds + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.FindRules", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.GetRuleDetails + +Get details for the rule identified by ruleId + +### Parameters + +- ruleId + +### Returns + +- o:rule +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.GetRuleDetails", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.GetRules + +Get the descriptions of all configured rules. If you need more information about a specific rule use the method Rules.GetRuleDetails. + +### Returns + +- ruleDescriptions + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.GetRules", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Rules.RemoveRule + +Remove a rule + +### Parameters + +- ruleId + +### Returns + +- ruleError + +### Example Request + +```json +{ + "id": 1, + "method": "Rules.RemoveRule", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Scripts.AddScript + +Add a script + +### Parameters + +- content +- name + +### Returns + +- o:errors +- o:script +- scriptError + +### Example Request + +```json +{ + "id": 1, + "method": "Scripts.AddScript", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Scripts.EditScript + +Edit a script + +### Parameters + +- id +- o:content +- o:name + +### Returns + +- o:errors +- scriptError + +### Example Request + +```json +{ + "id": 1, + "method": "Scripts.EditScript", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Scripts.GetScriptContent + +Get a scripts content. + +### Parameters + +- id + +### Returns + +- o:content +- scriptError + +### Example Request + +```json +{ + "id": 1, + "method": "Scripts.GetScriptContent", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Scripts.GetScripts + +Get all script, that is, their names and properties, but no content. + +### Returns + +- scripts + +### Example Request + +```json +{ + "id": 1, + "method": "Scripts.GetScripts", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Scripts.RemoveScript + +remove a script + +### Parameters + +- id + +### Returns + +- scriptError + +### Example Request + +```json +{ + "id": 1, + "method": "Scripts.RemoveScript", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.CheckForUpdates + +Instruct the system to poll the server for updates. Normally the system should automatically do this in regular intervals, however, if the client wants to allow the user to manually check for new updates now, this can be called. Returns true if the operation has been started successfully and the update manager will become busy. In order to know whether there are updates available, clients should walk through the list of packages retrieved from GetPackages and check whether there are packages with the updateAvailable flag set to true. + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.CheckForUpdates", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.EnableRepository + +Enable or disable a repository. + +### Parameters + +- enabled +- repositoryId + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.EnableRepository", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.GetCapabilities + +Get the list of capabilites on this system. The property "powerManagement" indicates whether restarting nymea and rebooting or shutting down is supported on this system. The property "updateManagement indicates whether system update features are available in this system. The property "timeManagement" indicates whether the system time can be configured on this system. Note that GetTime will be available in any case. + +### Returns + +- powerManagement +- timeManagement +- updateManagement + +### Example Request + +```json +{ + "id": 1, + "method": "System.GetCapabilities", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.GetPackages + +Get the list of packages currently available to the system. This might include installed available but not installed packages. Installed packages will have the installedVersion set to a non-empty value. + +### Returns + +- packages + +### Example Request + +```json +{ + "id": 1, + "method": "System.GetPackages", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.GetRepositories + +Get the list of repositories currently available to the system. + +### Returns + +- repositories + +### Example Request + +```json +{ + "id": 1, + "method": "System.GetRepositories", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.GetSystemInfo + +Returns information about the system nymea is running on. + +### Returns + +- deviceSerialNumber + +### Example Request + +```json +{ + "id": 1, + "method": "System.GetSystemInfo", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.GetTime + +Get the system time and configuraton. The "time" and "timeZone" properties give the current server time and time zone. "automaticTimeAvailable" indicates whether this system supports automatically setting the clock (e.g. using NTP). "automaticTime" will be true if the system is configured to automatically update the clock. + +### Returns + +- automaticTime +- automaticTimeAvailable +- time +- timeZone + +### Example Request + +```json +{ + "id": 1, + "method": "System.GetTime", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.GetTimeZones + +Returns the list of IANA specified time zone IDs which can be used to select a time zone. It is not required to use this method if the client toolkit already provides means to obtain a list of IANA time zone ids. + +### Returns + +- timeZones + +### Example Request + +```json +{ + "id": 1, + "method": "System.GetTimeZones", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.GetUpdateStatus + +Get the current status of the update system. "busy" indicates that the system is current busy with an operation regarding updates. This does not necessarily mean an actual update is running. When this is true, update related functions on the client should be marked as busy and no interaction with update components shall be allowed. An example for such a state is when the system queries the server if there are updates available, typically after a call to CheckForUpdates. "updateRunning" on the other hand indicates an actual update process is ongoing. The user should be informed about it, the system also might restart at any point while an update is running. + +### Returns + +- busy +- updateRunning + +### Example Request + +```json +{ + "id": 1, + "method": "System.GetUpdateStatus", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.Reboot + +Initiate a reboot of the system. The return value will indicate whether the procedure has been initiated successfully. + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.Reboot", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.RemovePackages + +Starts removing a package. Returns true if the removal has been started successfully. Before calling this method, clients should check whether the package can be removed (canRemove set to true). + +### Parameters + +- packageIds + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.RemovePackages", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.Restart + +Initiate a restart of the nymea service. The return value will indicate whether the procedure has been initiated successfully. + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.Restart", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.RollbackPackages + +Starts a rollback. Returns true if the rollback has been started successfully. Before calling this method, clients should check whether the package can be rolled back (canRollback set to true). + +### Parameters + +- packageIds + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.RollbackPackages", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.SetTime + +Set the system time configuraton. The system can be configured to update the time automatically by setting "automaticTime" to true. This will only work if the "timeManagement" capability is available on this system and "GetTime" indicates the availability of automatic time settings. If any of those requirements are not met, this method will return "false" in the "success" property. In order to manually configure the time, "automaticTime" should be set to false and "time" should be set. Note that if "automaticTime" is set to true and a manual "time" is still passed, the system will attempt to configure automatic time updates and only set the manual time if automatic mode fails. A time zone can always be passed optionally to change the system time zone and should be a IANA time zone id. + +### Parameters + +- o:automaticTime +- o:time +- o:timeZone + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.SetTime", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.Shutdown + +Initiate a shutdown of the system. The return value will indicate whether the procedure has been initiated successfully. + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.Shutdown", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## System.UpdatePackages + +Starts updating/installing packages with the given ids. Returns true if the upgrade has been started successfully. Note that it might still fail later. Before calling this method, clients should check the packages whether they are in a state where they can either be installed (no installedVersion set) or upgraded (updateAvailable set to true). + +### Parameters + +- o:packageIds + +### Returns + +- success + +### Example Request + +```json +{ + "id": 1, + "method": "System.UpdatePackages", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Tags.AddTag + +Add a Tag. A Tag must have a thingId OR a ruleId (call this method twice if you want to attach the same tag to a thing and a rule), an appId (Use the appId of your app), a tagId (e.g. "favorites") and a value. Upon success, a TagAdded notification will be emitted. Calling this method twice for the same ids (thing/rule, appId and tagId) but with a different value will update the tag's value and the TagValueChanged notification will be emitted. + +### Parameters + +- tag + +### Returns + +- tagError + +### Example Request + +```json +{ + "id": 1, + "method": "Tags.AddTag", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Tags.GetTags + +Get the Tags matching the given filter. Tags can be filtered by a thingID, a ruleId, an appId, a tagId or a combination of any (however, combining thingId and ruleId will return an empty result set). + +### Parameters + +- o:appId +- o:ruleId +- o:tagId +- o:thingId + +### Returns + +- o:tags +- tagError + +### Example Request + +```json +{ + "id": 1, + "method": "Tags.GetTags", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Tags.RemoveTag + +Remove a Tag. Tag value is optional and will be disregarded. If the ids match, the tag will be deleted and a TagRemoved notification will be emitted. + +### Parameters + +- tag + +### Returns + +- tagError + +### Example Request + +```json +{ + "id": 1, + "method": "Tags.RemoveTag", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.ChangePassword + +Change the password for the currently logged in user. + +### Parameters + +- newPassword + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "Users.ChangePassword", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.ChangeUserPassword + +Change the password for the given user. All tokens for this user will be removed in order to force all clients to log in again. + +### Parameters + +- newPassword +- username + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "Users.ChangeUserPassword", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.CreateUser + +Create a new user in the API with the given username and password. Use scopes to define the permissions for the new user. If the user has not the permission "PermissionScopeAccessAllThings", the list of things this user has access to can be defined in the "allowedThingIds" property. If no scopes are given, this user will be an admin user. Call Authenticate after this to obtain a device token for this user. + +### Parameters + +- o:allowedThingIds +- o:displayName +- o:email +- o:scopes +- password +- username + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "Users.CreateUser", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.GetTokens + +Get all the tokens for the current user. + +### Returns + +- error +- o:tokenInfoList + +### Example Request + +```json +{ + "id": 1, + "method": "Users.GetTokens", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.GetUserInfo + +Get info about the current token (the currently logged in user). + +### Returns + +- error +- o:userInfo + +### Example Request + +```json +{ + "id": 1, + "method": "Users.GetUserInfo", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.GetUserTokens + +Get all the tokens for the given username. + +### Parameters + +- username + +### Returns + +- error +- o:tokenInfoList + +### Example Request + +```json +{ + "id": 1, + "method": "Users.GetUserTokens", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.GetUsers + +Return a list of all users in the system. + +### Returns + +- users + +### Example Request + +```json +{ + "id": 1, + "method": "Users.GetUsers", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.RemoveToken + +Revoke access for a given token. Depending on the logged in user only the own tokens can be removed. If you are logged in as admin, any token can be removed. + +### Parameters + +- tokenId + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "Users.RemoveToken", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.RemoveUser + +Remove a user from the system. + +### Parameters + +- username + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "Users.RemoveUser", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.SetUserInfo + +Change user info. If username is given, info for the respective user is changed, otherwise the current user info is edited. Requires admin permissions to edit user info other than the own. + +### Parameters + +- o:displayName +- o:email +- o:username + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "Users.SetUserInfo", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Users.SetUserScopes + +Set the permissions (scopes) for a given user. If the user has not the permission "PermissionScopeAccessAllThings" the list of thing IDs this user has access to can be defined in the "allowedThingIds" property. + +### Parameters + +- o:allowedThingIds +- scopes +- username + +### Returns + +- error + +### Example Request + +```json +{ + "id": 1, + "method": "Users.SetUserScopes", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.AddNetwork + +Add a new Z-Wave network with the given serial port. + +### Parameters + +- serialPort + +### Returns + +- o:networkUuid +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.AddNetwork", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.AddNode + +Start the node inclusion procedure for the given Z-Wave network. + +### Parameters + +- networkUuid + +### Returns + +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.AddNode", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.CancelPendingOperation + +Cancel any running node inclusion or removal procedure for the given Z-Wave network. + +### Parameters + +- networkUuid + +### Returns + +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.CancelPendingOperation", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.FactoryResetNetwork + +Factory reset the controller for the given Z-Wave network. + +### Parameters + +- networkUuid + +### Returns + +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.FactoryResetNetwork", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.GetNetworks + +Get all the Z-Wave networks in the system. + +### Returns + +- networks + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.GetNetworks", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.GetNodes + +Get the list of nodes in a network + +### Parameters + +- networkUuid + +### Returns + +- o:nodes +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.GetNodes", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.GetSerialPorts + +Get the list of available serial ports from the host system. + +### Returns + +- serialPorts + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.GetSerialPorts", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.IsZWaveAvailable + +Query if the Z-Wave subsystem is available at all. + +### Returns + +- available + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.IsZWaveAvailable", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.RemoveFailedNode + +Remove the given failed node from the given Z-Wave network. This will not work if node is not marked as failed. + +### Parameters + +- networkUuid +- nodeId + +### Returns + +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.RemoveFailedNode", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.RemoveNetwork + +Remove the given Z-Wave network from the system. + +### Parameters + +- networkUuid + +### Returns + +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.RemoveNetwork", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## ZWave.RemoveNode + +Start the node removal procedure for the given Z-Wave network. + +### Parameters + +- networkUuid + +### Returns + +- zwaveError + +### Example Request + +```json +{ + "id": 1, + "method": "ZWave.RemoveNode", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.AddNetwork + +Create a new ZigBee network for the given 'serialPort', 'baudRate' and 'backend'. The serial ports can be fetched from the available adapters. See 'GetAdapters' for more information. The available backends can be fetched using the 'GetAvailableBackends' method. + +### Parameters + +- backend +- baudRate +- o:channelMask +- serialPort + +### Returns + +- o:networkUuid +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.AddNetwork", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.CreateBinding + +Create a binding. Use destinationAddress and destinationEndpointId to create a node to node binding, or use destinationGroupAddress to create a group binding. + +### Parameters + +- clusterId +- networkUuid +- o:destinationAddress +- o:destinationEndpointId +- o:destinationGroupAddress +- sourceAddress +- sourceEndpointId + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.CreateBinding", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.FactoryResetNetwork + +Factory reset the network with the given 'networkUuid'. The network does not have to be online for this procedure, and all associated nodes and things will be removed permanently. + +### Parameters + +- networkUuid + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.FactoryResetNetwork", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.GetAdapters + +Get the list of available ZigBee adapters and serial ports in order to set up the ZigBee network on the desired interface. The 'serialPort' property can be used as unique identifier for an adapter. If an adapter hardware has been recognized as a well known ZigBee adapter, the 'hardwareRecognized' property will be true and the 'baudRate' and 'backend' configurations can be used as they where given, otherwise the user might set the backend and baud rate manually. The available backends can be fetched using the GetAvailableBackends method. + +### Returns + +- adapters + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.GetAdapters", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.GetAvailableBackends + +Get the list of available ZigBee backends. + +### Returns + +- backends + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.GetAvailableBackends", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.GetNetworks + +Returns the list of configured ZigBee networks in the system. + +### Returns + +- zigbeeNetworks + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.GetNetworks", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.GetNodes + +Returns the list of ZigBee nodes from the network the given 'networkUuid' in the system. + +### Parameters + +- networkUuid + +### Returns + +- o:zigbeeNodes +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.GetNodes", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.RefreshBindings + +Refresh the binding table for the given node. + +### Parameters + +- ieeeAddress +- networkUuid + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.RefreshBindings", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.RefreshNeighborTables + +Refresh the neighbor table for all nodes. Note that calling this may cause a lot of traffic in the ZigBee network. + +### Parameters + +- networkUuid + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.RefreshNeighborTables", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.RemoveBinding + +Remove a binding. + +### Parameters + +- clusterId +- networkUuid +- o:destinationAddress +- o:destinationEndpointId +- o:destinationGroupAddress +- sourceAddress +- sourceEndpointId + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.RemoveBinding", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.RemoveNetwork + +Remove the ZigBee network with the given network uuid. + +### Parameters + +- networkUuid + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.RemoveNetwork", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.RemoveNode + +Remove a ZigBee node with the given 'ieeeAddress' from the network with the given 'networkUuid'. If there is a thing configured for this node, also the thing will be removed from the system. The coordinator node cannot be removed. + +### Parameters + +- ieeeAddress +- networkUuid + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.RemoveNode", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + +## Zigbee.SetPermitJoin + +Allow or deny nodes to join the network with the given 'networkUuid' for a specific 'duration' in seconds. The duration value has to be between 0 and 255 seconds. The 'permitJoinDuration' property of ZigBee network object indicates how long permit has been enabled and the 'permitJoiningRemaining' indicates the rest of the time. Those values can be used to show a countdown or progressbar. This method can be recalled for resetting the timeout. If the duration is set to 0 seconds, joining will be disabled immediatly for the entire network. The 'shortAddress' is optional and defaults to the broadcast address 0xfffc for all routers in the network. If the short address matches the address of a router node in the network, only that specific router will be able to allow new nodes to join the network. A new node will join to the router with the best link quality index (LQI). + +### Parameters + +- duration +- networkUuid +- o:shortAddress + +### Returns + +- zigbeeError + +### Example Request + +```json +{ + "id": 1, + "method": "Zigbee.SetPermitJoin", + "params": {} +} +``` + +### Example Response + +```json +{ + "id": 1, + "status": "success", + "params": {} +} +``` + diff --git a/docs/nymea_integrations.md b/docs/nymea_integrations.md new file mode 100644 index 0000000..9af1ccb --- /dev/null +++ b/docs/nymea_integrations.md @@ -0,0 +1,10892 @@ +# Nymea Integrations Documentation + +## Plugins + +### Tado +- ID: `b4f2d2ee-50bb-4786-b7f5-261fed204fa5` +- Version: + +### daylightSensor +- ID: `4b7d63a7-fc39-4a50-a459-457fa7653089` +- Version: + +### netatmo +- ID: `69d14951-0c02-4877-bcef-dffdf48b7ccb` +- Version: + +### homeConnect +- ID: `109abdc7-5d53-4f63-a4b2-851e97cea8ea` +- Version: + +### DateTime +- ID: `c26014c6-87fb-4233-85ed-01d18625018d` +- Version: + +### genericShading +- ID: `82f10aec-22cf-4fa9-aa46-f2f9d0db32a4` +- Version: + +### genericGarages +- ID: `5049e7d8-bed7-43c6-9107-b0c1c558ae35` +- Version: + +### genericSensors +- ID: `ee7b2965-d38f-4f73-aed0-e8887423ff1a` +- Version: + +### genericEnergy +- ID: `b3188696-2585-4806-bf98-30ab576ce5c8` +- Version: + +### OpenWeatherMap +- ID: `bc6af567-2338-41d5-aac1-462dec6e4783` +- Version: + +### tplink +- ID: `024ff2e3-30df-44a1-9c8d-63cc416f1fb8` +- Version: + +### ZigbeeEurotronic +- ID: `1fa06c99-ddb8-4dd2-abf8-8fa1abfe6d48` +- Version: + +### solarlog +- ID: `606adc5a-c9a5-45fa-9652-ea385dcb0b81` +- Version: + +### shelly +- ID: `6162773b-0435-408c-a4f8-7860d38031a9` +- Version: + +### Bose +- ID: `472a3f24-b05c-49b3-ad9a-dfda608b6760` +- Version: + +### fronius +- ID: `02319cfc-8b55-49ba-99bc-0588bbfab063` +- Version: + +### genericHeatingCooling +- ID: `0d582885-8295-41bc-99fb-b4bcee4f626c` +- Version: + +### genericLights +- ID: `8b803622-0e48-4987-970e-927d92ad18d5` +- Version: + +### PhilipsHue +- ID: `5f2e634b-b7f3-48ee-976a-b5ae22aa5c55` +- Version: + +### ZigbeeGewiss +- ID: `98959344-92a2-47c5-b071-36db610f147c` +- Version: + +### genericThings +- ID: `27869a19-3084-4b48-8b00-63bdd3125c7c` +- Version: + +### Pushbullet +- ID: `46986575-0e62-483d-b5a8-76ac356fcce7` +- Version: + +### Nanoleaf +- ID: `360867ec-1594-498d-8182-fbab1fe17489` +- Version: + +### ZigbeeDevelco +- ID: `1b9e976d-e842-40e1-9fad-87d71a69c721` +- Version: + +### MailNotification +- ID: `1ae35df1-1b51-4c93-94fa-3febc77e0318` +- Version: + +### ZigbeeTradfri +- ID: `ad9480c8-05f0-4ba1-92b1-4dd8e2ae2830` +- Version: + +### ZWaveQubino +- ID: `8b55a5c9-3770-49ca-a8fe-3bcb735c67a2` +- Version: + +### ZigbeeLumi +- ID: `4a6e97da-0e4b-4731-8bfe-c93d806a9383` +- Version: + +### AirQualityIndex +- ID: `57d69b76-4d2d-41ec-bef6-949a79ffbe6b` +- Version: + +### doorBird +- ID: `6fe1614a-fc47-4eb2-a47c-13c50f1798ee` +- Version: + +### ZWaveGeneric +- ID: `5605f31e-e05b-4d30-bdf4-a8fcc2ec201d` +- Version: + +### pushNotifications +- ID: `fdcea3b3-8d79-4fbb-8101-1ab45d71cedb` +- Version: + +### Kodi +- ID: `e7186890-99fa-4c5b-8247-09c6d450d490` +- Version: + +### Telegram +- ID: `bc95b5c8-f2f1-4bcb-bf88-8d165ee06114` +- Version: + +### genericThing +- ID: `3c7bb595-b72a-4c3c-8414-1b2ac26bc021` +- Version: + +### genericButtons +- ID: `6e22161e-39b7-4416-8623-39e730721efb` +- Version: + +### unifi +- ID: `88bc00c7-9ea8-4aa6-8aec-831639e8fccc` +- Version: + +### ZigbeeTuya +- ID: `02948158-2e0a-4c8a-8a13-0b3d8cfdf4da` +- Version: + +### bgeTech +- ID: `e373f492-4527-4d5f-aa48-34d38c55b0d6` +- Version: + +### anelElektronik +- ID: `7a3e5b64-20e4-42bd-b86b-989b84afc22a` +- Version: + +### SomfyTahoma +- ID: `4e8be1c1-daa8-4e21-9e85-b2372ab1a450` +- Version: + +### sma +- ID: `b8442bbf-9d3f-4aa2-9443-b3a31ae09bac` +- Version: + +### ZigbeeSchneiderElectric +- ID: `01f285a6-f927-4648-a4eb-4f2124f9d490` +- Version: + +### Denon +- ID: `cd758269-dbbb-4ef0-80ab-48bd9a8a2765` +- Version: + +### myStrom +- ID: `fc7f1a24-42a8-45ce-9ffb-136292eb0164` +- Version: + +### ZigbeeGeneric +- ID: `6a4343be-9fd6-4015-9ff5-38542651c534` +- Version: + +### GpioController +- ID: `127ead55-996a-44ac-ba82-fc3c634e018a` +- Version: + +### ZWaveSpringsWindowFashions +- ID: `2872e07a-d6fe-433f-8d6c-cd2f880d6fa3` +- Version: + +### Lifx +- ID: `4e00ee30-79e2-447b-8dcc-c34470f41992` +- Version: + +### NetworkDetector +- ID: `8e0f791e-b273-4267-8605-b7c2f55a68ab` +- Version: + +### flowerCare +- ID: `74e2106a-3407-4e89-a27a-1c890d78bee7` +- Version: + +### Sonos +- ID: `cdb07719-c445-4fa5-9c7a-564ee02a4412` +- Version: + +### ZigbeePhilipsHue +- ID: `0ca340b7-061a-42e6-ab7d-d7b4fd235d02` +- Version: + +### ZigbeeJung +- ID: `d0184b49-6e6d-42fe-92e1-e2ede67ca7df` +- Version: + +### Keba +- ID: `9142b09f-30a9-43d0-9ede-2f8debe075ac` +- Version: + +### tasmota +- ID: `d136e0c0-0cbf-4731-aabb-b2201088d6cb` +- Version: + +### ZWaveFibaro +- ID: `fc5336be-bc9e-48d1-9ad4-d2365321c000` +- Version: + +### OpenUv +- ID: `9b7d9cc8-77df-4197-a6fc-8a365747a3b1` +- Version: + +### genericCar +- ID: `83ede9b2-aa5f-49ff-8f7d-5ca30d8964df` +- Version: + + +## Thing Classes + +### smokedetector + +- ThingClassId: `e883a8ef-1fb7-4d7f-b40b-9cbc0133e58a` + +#### States + +- fireDetected (Bool) → stateTypeId: `6c3ee0ce-fc0f-477c-9a01-be61e061463b` +- batteryCritical (Bool) → stateTypeId: `8f67b692-243b-4ce8-9983-0d1206439f16` +- signalStrength (Uint) → stateTypeId: `47f8506c-af7f-4928-b0ce-8c52ce4f740b` +- connected (Bool) → stateTypeId: `98cf101c-cb0e-47a9-9c27-b6a0cdf9ba4b` + + +--- + +### airQualityIndex + +- ThingClassId: `23ea32c9-38b0-4155-bacc-3afa8c09f6ee` + +#### States + +- connected (Bool) → stateTypeId: `7b9135cd-2461-4d33-b2b3-3dc600983895` +- stationName (String) → stateTypeId: `8385f3d5-62f7-482e-927c-b5d61a70d607` +- pm25 (Double) → stateTypeId: `bc8c4c83-d229-4be4-8732-bc4f2390f399` +- pm10 (Double) → stateTypeId: `24b41ec4-e26b-4dfb-b52c-8e2b1bbdafc6` +- o3 (Double) → stateTypeId: `4e88526d-009f-4820-9a84-09b3646d23c9` +- no2 (Double) → stateTypeId: `6ed6c505-f36e-44c4-a982-f395b04e539b` +- co (Double) → stateTypeId: `54ac72f3-6444-46a8-a43d-210c2a6fbfb5` +- so2 (Double) → stateTypeId: `f3a05e65-a9b3-48fd-be43-688d4c293cc9` +- temperature (Double) → stateTypeId: `94219802-0a82-4761-99b3-c6b6dfc096db` +- humidity (Double) → stateTypeId: `4fc45fca-25ab-45a0-b862-817eea1f51e3` +- pressure (Double) → stateTypeId: `5f799040-08f8-44d1-aa0a-4cab7caad839` +- windSpeed (Double) → stateTypeId: `c4366608-2511-428b-964e-2ad9e37f8f3c` + + +--- + +### kasaPowerStrip300 + +- ThingClassId: `bbdee989-9431-4187-b135-5030a7a11be2` + +#### States + +- connected (Bool) → stateTypeId: `9bcec21d-7207-44c6-8ebf-d2720bb7a63a` +- signalStrength (Uint) → stateTypeId: `1a587f62-8274-4c2e-bdc6-1b0b6368e5f7` +- totalEnergyConsumed (Double) → stateTypeId: `f2992226-1f36-477f-b053-04dc5dc3a31c` +- currentPower (Double) → stateTypeId: `f973b4e8-99fb-4331-8193-e287bd37f5a0` + + +--- + +### controller + +- ThingClassId: `1da7534c-dd51-4cd2-ab56-48428892c436` + +#### States + +- connected (Bool) → stateTypeId: `2efc35f6-dc58-4cd2-98cc-7e0a1a4f4e01` + + +--- + +### powerSocket + +- ThingClassId: `800a8df8-06cb-4d93-8334-944fcce9651a` + +#### States + +- connected (Bool) → stateTypeId: `b5abd47e-95f1-4e35-94fa-be87c396073a` +- signalStrength (Uint) → stateTypeId: `21889797-32c0-4f1b-903d-8af71981882b` +- version (String) → stateTypeId: `41c74445-4d2d-46dc-8a63-c98f1df8e742` +- power (Bool) → stateTypeId: `2c8268b9-d76b-415a-b2e9-6bfeabd98a76` + +#### Actions + +- power → actionTypeId: `2c8268b9-d76b-415a-b2e9-6bfeabd98a76` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2c8268b9-d76b-415a-b2e9-6bfeabd98a76", + "params": { + "power": "" + } + } + } + +- alert → actionTypeId: `4e3b1430-d98e-4f05-bae6-301dac34bd4b` + +--- + +### tadoAccount + +- ThingClassId: `69be7d15-5658-4442-872e-42abbd8bff81` + +#### States + +- connected (Bool) → stateTypeId: `2f79bc1d-27ed-480a-b583-728363c83ea6` +- loggedIn (Bool) → stateTypeId: `2aed240b-8c5c-418b-a9d1-0d75412c1c27` +- userDisplayName (String) → stateTypeId: `33f55afc-a673-47a4-9fb0-75fdac6a66f4` + + +--- + +### closableSensor + +- ThingClassId: `82a859f0-41ed-4a53-93b0-260a1435ac75` + +#### States + +- closed (Bool) → stateTypeId: `6d4523c2-66f1-4fbf-b0d9-bd11a1920b50` +- tampered (Bool) → stateTypeId: `948bc2da-23fb-44fd-a74c-c917bf657bf0` +- batteryLevel (Int) → stateTypeId: `34a8f7f1-19f8-47be-a1b2-4d7ea52fee1e` +- batteryCritical (Bool) → stateTypeId: `455e1ebc-5534-4053-8db3-c0b6b79f8c41` +- connected (Bool) → stateTypeId: `10d8aa94-943e-40f6-a10e-0ac77bcdecd3` +- signalStrength (Uint) → stateTypeId: `4ef57e47-2b83-4d41-8cfb-21bf2f5ee62d` + + +--- + +### powerSocket + +- ThingClassId: `b2a9a251-f957-42c8-90d6-c421bdb98547` + +#### States + +- connected (Bool) → stateTypeId: `7b0f1f5f-f174-4672-af27-c0459829bee8` +- signalStrength (Uint) → stateTypeId: `528ed056-0b50-4002-833a-78ffcf64442f` +- power (Bool) → stateTypeId: `ac01cd1c-009c-4c83-b27b-273920bd775e` + +#### Actions + +- power → actionTypeId: `ac01cd1c-009c-4c83-b27b-273920bd775e` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ac01cd1c-009c-4c83-b27b-273920bd775e", + "params": { + "power": "" + } + } + } + + +--- + +### pressureSensor + +- ThingClassId: `9a8d804b-d1dc-450a-8c41-be491e5cdda0` + +#### States + +- pressure (Double) → stateTypeId: `e645a979-1465-4592-b8b0-f4c123db0800` +- input (Double) → stateTypeId: `c320577c-371a-442b-ac80-b692ff2064c9` + +#### Actions + +- pressure → actionTypeId: `e645a979-1465-4592-b8b0-f4c123db0800` + - Parameters: + - pressure (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e645a979-1465-4592-b8b0-f4c123db0800", + "params": { + "pressure": "" + } + } + } + +- input → actionTypeId: `c320577c-371a-442b-ac80-b692ff2064c9` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c320577c-371a-442b-ac80-b692ff2064c9", + "params": { + "input": "" + } + } + } + + +--- + +### dryer + +- ThingClassId: `f360cc43-41fc-454a-b6df-09ec0a66c22a` + +#### States + +- connected (Bool) → stateTypeId: `485f895a-5c2d-4e1a-8f77-a2d020363635` +- closed (Bool) → stateTypeId: `81aa609e-69c0-4d5c-98f0-46e6f14c7eaa` +- operationState (String) → stateTypeId: `52fde732-ed14-437e-8fbf-461d2ed19654` +- localControlState (Bool) → stateTypeId: `d73c04c4-7082-4259-a225-10c453247103` +- remoteControlActivationState (Bool) → stateTypeId: `ed8a56a1-18db-469f-b51d-487111037c71` +- remoteStartAllowanceState (Bool) → stateTypeId: `d18d89c1-d04f-491c-b817-7234ea531deb` +- progress (Int) → stateTypeId: `eaf84b93-bacd-42b1-afaf-a890c8b2a099` +- selectedProgram (String) → stateTypeId: `4ad5424c-6ccd-4e29-9aba-41f960618401` +- dryingTarget (String) → stateTypeId: `829c1ec6-5b5a-490a-b487-52287eab91ba` +- endTime (String) → stateTypeId: `6586f2d9-daec-43fa-8f7f-f3afc9d70046` + +#### Actions + +- dryingTarget → actionTypeId: `829c1ec6-5b5a-490a-b487-52287eab91ba` + - Parameters: + - dryingTarget (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "829c1ec6-5b5a-490a-b487-52287eab91ba", + "params": { + "dryingTarget": "" + } + } + } + +- start → actionTypeId: `aedd85a6-0f9a-4bc4-b2ba-90ba11d1a955` +- stop → actionTypeId: `8c9923f6-2a2d-48bb-aaf4-5f887565c58a` + +--- + +### gasSensor + +- ThingClassId: `25c6725f-5c15-485b-9ec4-cb0e11ab6b32` + +#### States + +- gasLevel (Double) → stateTypeId: `5135648f-57b7-44b0-b9d0-c5accad09356` +- input (Double) → stateTypeId: `e3eb500a-ac00-426c-932a-dee4fd6fbcf5` + +#### Actions + +- gasLevel → actionTypeId: `5135648f-57b7-44b0-b9d0-c5accad09356` + - Parameters: + - gasLevel (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5135648f-57b7-44b0-b9d0-c5accad09356", + "params": { + "gasLevel": "" + } + } + } + +- input → actionTypeId: `e3eb500a-ac00-426c-932a-dee4fd6fbcf5` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e3eb500a-ac00-426c-932a-dee4fd6fbcf5", + "params": { + "input": "" + } + } + } + + +--- + +### ventilation + +- ThingClassId: `24af8dd3-ddf0-47f0-bf09-70fdfd8dceab` + +#### States + +- power (Bool) → stateTypeId: `846711b7-ea5a-4c66-a267-001c60406509` + +#### Actions + +- power → actionTypeId: `846711b7-ea5a-4c66-a267-001c60406509` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "846711b7-ea5a-4c66-a267-001c60406509", + "params": { + "power": "" + } + } + } + + +--- + +### virtualPowerSwitch + +- ThingClassId: `8ea0a168-74ff-4445-8c13-74aab195af4e` + +#### States + +- power (Bool) → stateTypeId: `bd3be4e3-7377-421a-a374-104dd451de6d` + +#### Actions + +- power → actionTypeId: `bd3be4e3-7377-421a-a374-104dd451de6d` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bd3be4e3-7377-421a-a374-104dd451de6d", + "params": { + "power": "" + } + } + } + + +--- + +### tasmotaLight + +- ThingClassId: `83e5d9e6-5ac8-4e41-9717-481415048d49` + +#### States + +- connected (Bool) → stateTypeId: `72050de9-c318-4e53-93e5-36f7c2fc7cab` +- signalStrength (Uint) → stateTypeId: `dcc39f5e-6bcb-4c04-910e-8dd5f9ad5402` +- power (Bool) → stateTypeId: `88dbdf8e-45ff-466f-8352-8654a6b5fe68` + +#### Actions + +- power → actionTypeId: `88dbdf8e-45ff-466f-8352-8654a6b5fe68` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "88dbdf8e-45ff-466f-8352-8654a6b5fe68", + "params": { + "power": "" + } + } + } + + +--- + +### humiditySensor + +- ThingClassId: `d295bc64-773c-42a9-83e2-80db5fa0d1ce` + +#### States + +- humidity (Double) → stateTypeId: `925225d9-2965-444a-9c42-63c2873700fb` +- input (Double) → stateTypeId: `a8223e65-e704-4f84-9bbe-d8fc42597047` + +#### Actions + +- humidity → actionTypeId: `925225d9-2965-444a-9c42-63c2873700fb` + - Parameters: + - humidity (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "925225d9-2965-444a-9c42-63c2873700fb", + "params": { + "humidity": "" + } + } + } + +- input → actionTypeId: `a8223e65-e704-4f84-9bbe-d8fc42597047` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a8223e65-e704-4f84-9bbe-d8fc42597047", + "params": { + "input": "" + } + } + } + + +--- + +### impulseSmartMeter + +- ThingClassId: `c3123967-f741-4fe1-a0d4-9a3e405d7e52` + +#### States + +- currentPower (Double) → stateTypeId: `5983d714-5f80-42d8-bee2-9228b6382b3b` +- totalEnergyConsumed (Double) → stateTypeId: `5821edb7-e6cb-4e5a-9d0b-3375126d3367` +- impulseInput (Bool) → stateTypeId: `9cd7e5ca-f8f8-48d5-9785-911ae75158c3` + +#### Actions + +- impulseInput → actionTypeId: `9cd7e5ca-f8f8-48d5-9785-911ae75158c3` + - Parameters: + - impulseInput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9cd7e5ca-f8f8-48d5-9785-911ae75158c3", + "params": { + "impulseInput": "" + } + } + } + + +--- + +### AVRX1000 + +- ThingClassId: `1cd3d67e-aba0-450e-9e2a-483a1527aba6` + +#### States + +- connected (Bool) → stateTypeId: `fc1dee8b-8fcc-4ec2-8fe6-6be4f5f47a5c` +- power (Bool) → stateTypeId: `1cdb6b54-6831-4900-95b2-c78f64497701` +- mute (Bool) → stateTypeId: `3e11470d-a5b7-499c-be55-9b1b4fe5eedf` +- volume (Int) → stateTypeId: `773636b9-304d-463a-8755-fc7488dc0ff3` +- toneControl (Bool) → stateTypeId: `d57c1e5e-2cc9-4638-999c-1523f16dbb83` +- bass (Int) → stateTypeId: `2c92b22e-d5b2-4991-a523-64222bffc9e7` +- treble (Int) → stateTypeId: `38a3be02-6ed4-4a84-903e-eb923b933989` +- inputSource (String) → stateTypeId: `f29ffa2c-31d6-4d88-b160-a38288c82ce1` +- surroundMode (String) → stateTypeId: `4f203bdd-691c-4384-a934-2d49a5448f0a` +- artist (String) → stateTypeId: `50b34df4-90f7-41aa-9a57-6f2d31a18430` +- collection (String) → stateTypeId: `d4f14d93-8cae-4e1b-9ee7-a5c21c0211df` +- title (String) → stateTypeId: `d32493d8-5faf-4c7a-ba94-847dc9b81615` +- artwork (String) → stateTypeId: `6c4a208b-5b04-40cc-b14b-8f79aff30307` +- playerType (String) → stateTypeId: `2f372374-16f3-4900-afdc-834f51075d07` +- playbackStatus (String) → stateTypeId: `8ef6708c-812a-4e6a-a608-9e480aa3b7bf` +- shuffle (Bool) → stateTypeId: `8ad33cb9-e758-433d-a013-2e4d43157c92` +- repeat (String) → stateTypeId: `9478987b-14e4-4572-a059-a18a5a9db229` + +#### Actions + +- power → actionTypeId: `1cdb6b54-6831-4900-95b2-c78f64497701` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1cdb6b54-6831-4900-95b2-c78f64497701", + "params": { + "power": "" + } + } + } + +- mute → actionTypeId: `3e11470d-a5b7-499c-be55-9b1b4fe5eedf` + - Parameters: + - mute (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3e11470d-a5b7-499c-be55-9b1b4fe5eedf", + "params": { + "mute": "" + } + } + } + +- volume → actionTypeId: `773636b9-304d-463a-8755-fc7488dc0ff3` + - Parameters: + - volume (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "773636b9-304d-463a-8755-fc7488dc0ff3", + "params": { + "volume": "" + } + } + } + +- toneControl → actionTypeId: `d57c1e5e-2cc9-4638-999c-1523f16dbb83` + - Parameters: + - toneControl (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d57c1e5e-2cc9-4638-999c-1523f16dbb83", + "params": { + "toneControl": "" + } + } + } + +- bass → actionTypeId: `2c92b22e-d5b2-4991-a523-64222bffc9e7` + - Parameters: + - bass (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2c92b22e-d5b2-4991-a523-64222bffc9e7", + "params": { + "bass": "" + } + } + } + +- treble → actionTypeId: `38a3be02-6ed4-4a84-903e-eb923b933989` + - Parameters: + - treble (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "38a3be02-6ed4-4a84-903e-eb923b933989", + "params": { + "treble": "" + } + } + } + +- inputSource → actionTypeId: `f29ffa2c-31d6-4d88-b160-a38288c82ce1` + - Parameters: + - inputSource (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f29ffa2c-31d6-4d88-b160-a38288c82ce1", + "params": { + "inputSource": "" + } + } + } + +- surroundMode → actionTypeId: `4f203bdd-691c-4384-a934-2d49a5448f0a` + - Parameters: + - surroundMode (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4f203bdd-691c-4384-a934-2d49a5448f0a", + "params": { + "surroundMode": "" + } + } + } + +- playbackStatus → actionTypeId: `8ef6708c-812a-4e6a-a608-9e480aa3b7bf` + - Parameters: + - playbackStatus (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8ef6708c-812a-4e6a-a608-9e480aa3b7bf", + "params": { + "playbackStatus": "" + } + } + } + +- shuffle → actionTypeId: `8ad33cb9-e758-433d-a013-2e4d43157c92` + - Parameters: + - shuffle (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8ad33cb9-e758-433d-a013-2e4d43157c92", + "params": { + "shuffle": "" + } + } + } + +- repeat → actionTypeId: `9478987b-14e4-4572-a059-a18a5a9db229` + - Parameters: + - repeat (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9478987b-14e4-4572-a059-a18a5a9db229", + "params": { + "repeat": "" + } + } + } + +- increaseVolume → actionTypeId: `4ae686d6-2307-40a0-bd38-2cd3a92342cc` + - Parameters: + - step (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4ae686d6-2307-40a0-bd38-2cd3a92342cc", + "params": { + "step": "" + } + } + } + +- decreaseVolume → actionTypeId: `d3752c32-92e3-4396-8e2f-ab5e57c6cfb1` + - Parameters: + - step (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d3752c32-92e3-4396-8e2f-ab5e57c6cfb1", + "params": { + "step": "" + } + } + } + +- skipBack → actionTypeId: `3f2eb789-918c-475a-a295-14c0c24338b8` +- stop → actionTypeId: `ddab0869-5b90-4fd4-9c40-9ad57101b87c` +- play → actionTypeId: `d04eb30b-838d-4fbd-b781-c01005b59756` +- pause → actionTypeId: `3de38047-006f-4d97-9326-08bb5ad79b05` +- skipNext → actionTypeId: `bf9664e4-a53e-474c-afcc-88f25e6fe365` + +--- + +### lumiLongpressButtonSensor + +- ThingClassId: `ae3c8713-4660-427b-8916-a08723183ba8` + +#### States + +- connected (Bool) → stateTypeId: `a4285c0e-52c5-47a3-9129-1454421bdd01` +- signalStrength (Uint) → stateTypeId: `d6f425ee-ff76-4e41-93c8-53efd828a169` +- version (String) → stateTypeId: `db532b4b-2d0f-4d77-bbb5-6f81e1981161` + + +--- + +### co2Sensor + +- ThingClassId: `a58a2dae-4148-4a4d-ab34-2a11124454a0` + +#### States + +- co2 (Double) → stateTypeId: `755c4c7f-093e-41dd-a38d-9ee18c9890d6` +- input (Double) → stateTypeId: `7dc765f8-fa9b-4199-8f21-49ed452f226d` + +#### Actions + +- co2 → actionTypeId: `755c4c7f-093e-41dd-a38d-9ee18c9890d6` + - Parameters: + - co2 (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "755c4c7f-093e-41dd-a38d-9ee18c9890d6", + "params": { + "co2": "" + } + } + } + +- input → actionTypeId: `7dc765f8-fa9b-4199-8f21-49ed452f226d` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7dc765f8-fa9b-4199-8f21-49ed452f226d", + "params": { + "input": "" + } + } + } + + +--- + +### solarlog + +- ThingClassId: `1374b47e-5555-4ec1-b2cd-6474048dd30c` + +#### States + +- connected (Bool) → stateTypeId: `eb9f8575-71e3-42a9-aa4d-ffea9586cc4f` +- lastupdate (String) → stateTypeId: `b944c513-a7f0-4bc1-941e-aae9849557fe` +- currentPower (Double) → stateTypeId: `0bf515d8-0f48-4eba-9255-f774d68c80fe` +- pdc (Double) → stateTypeId: `e4183a70-c848-447a-962a-f19dc5974140` +- uac (Double) → stateTypeId: `7dc46b2e-5fba-4cc6-a159-09472cdfac62` +- dcVoltage (Double) → stateTypeId: `bb891a05-59d8-4a3b-a0ea-b63af58558f7` +- yieldDay (Double) → stateTypeId: `53ed041a-e3c3-4ae5-9a79-1cd7ad82e9a8` +- yieldYesterday (Double) → stateTypeId: `bd96d0b3-e921-49eb-8b34-0b3be5bb27fa` +- yieldMonth (Double) → stateTypeId: `9a77662a-2034-4031-8e7a-1e6347089d97` +- yieldYear (Double) → stateTypeId: `d18169ce-deeb-4f7b-b737-818a91760041` +- totalEnergyProduced (Double) → stateTypeId: `3afb6ea1-fef8-4c17-8307-c7547a7a6f3c` +- powerUsage (Double) → stateTypeId: `5b665247-278a-4b59-9046-add40763e937` +- consYieldDay (Double) → stateTypeId: `40ff1d14-a5d2-4fdb-919c-58dfbcdca123` +- consYieldYesterday (Double) → stateTypeId: `5d6d5ba5-ebc3-42ce-9d08-802da694b4da` +- consYieldMonth (Double) → stateTypeId: `a45a557a-a937-4382-8ef5-76f1ff5940e4` +- consYieldYear (Double) → stateTypeId: `1d42619b-3a50-4bde-b325-67a8014332ef` +- consYieldTotal (Double) → stateTypeId: `34f60062-5dec-45ed-9a27-4fbc083cb36e` +- totalPower (Double) → stateTypeId: `0aa21401-2a3d-4149-803b-f0ba8c66b2f7` + + +--- + +### lightSensor + +- ThingClassId: `d4d873fd-f0fc-433f-b615-91c1506a1890` + +#### States + +- lightIntensity (Double) → stateTypeId: `fe12ef32-9f2f-41cd-acb7-6b8e4acffa3b` +- input (Double) → stateTypeId: `520e5d27-7d15-4d79-94cf-5d01f5a09ea8` + +#### Actions + +- lightIntensity → actionTypeId: `fe12ef32-9f2f-41cd-acb7-6b8e4acffa3b` + - Parameters: + - lightIntensity (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "fe12ef32-9f2f-41cd-acb7-6b8e4acffa3b", + "params": { + "lightIntensity": "" + } + } + } + +- input → actionTypeId: `520e5d27-7d15-4d79-94cf-5d01f5a09ea8` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "520e5d27-7d15-4d79-94cf-5d01f5a09ea8", + "params": { + "input": "" + } + } + } + + +--- + +### flushShutter + +- ThingClassId: `08d2df02-e205-43ff-a68c-08f1b2a023dc` + +#### States + +- connected (Bool) → stateTypeId: `a53d1348-0336-481f-8412-bb42b7ca7ffd` +- signalStrength (Uint) → stateTypeId: `5b6b1e02-68dc-4a07-844e-46b6884752a7` +- moving (Bool) → stateTypeId: `a51dea82-6745-4855-8728-3f3cfee0f7f4` +- percentage (Int) → stateTypeId: `4c7bc094-d39a-4fef-b7fb-87d5f2fb2f67` +- currentPower (Double) → stateTypeId: `e53721d4-c0f7-4350-8ee0-9cddc712bc64` +- totalEnergyConsumed (Double) → stateTypeId: `8e9b9169-1848-400d-a8ab-c4600d870ac2` +- calibration (Bool) → stateTypeId: `c0540714-4973-45c8-a65e-d04def8d3ad2` + +#### Actions + +- percentage → actionTypeId: `4c7bc094-d39a-4fef-b7fb-87d5f2fb2f67` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4c7bc094-d39a-4fef-b7fb-87d5f2fb2f67", + "params": { + "percentage": "" + } + } + } + +- calibration → actionTypeId: `c0540714-4973-45c8-a65e-d04def8d3ad2` + - Parameters: + - calibration (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c0540714-4973-45c8-a65e-d04def8d3ad2", + "params": { + "calibration": "" + } + } + } + +- open → actionTypeId: `6862c2f8-9a62-4db9-b7ab-c74bb3bf0b4b` +- close → actionTypeId: `21ffb9be-49df-44c2-af2e-b2c5af553496` +- stop → actionTypeId: `ad9a446f-23e6-4413-a530-b5ba1cbc061a` + +--- + +### smartPlug + +- ThingClassId: `01438844-0048-4276-91f8-c93ac0a5171d` + +#### States + +- power (Bool) → stateTypeId: `77198588-cfd0-44ea-beb5-3a7ce06d4c1d` +- connected (Bool) → stateTypeId: `6fdf4b26-6b93-4db9-9ff4-e755f5da0a3c` + +#### Actions + +- power → actionTypeId: `77198588-cfd0-44ea-beb5-3a7ce06d4c1d` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "77198588-cfd0-44ea-beb5-3a7ce06d4c1d", + "params": { + "power": "" + } + } + } + + +--- + +### dimmerSwitch + +- ThingClassId: `b2711164-a848-4715-8ddf-33ca86f9f4cf` + +#### States + +- connected (Bool) → stateTypeId: `5ac101b2-4bb7-4b5c-8493-08b1ae7ca0c1` +- signalStrength (Uint) → stateTypeId: `5a6e325e-a6ee-4a36-b429-f5d8c8adb80b` +- batteryLevel (Int) → stateTypeId: `3e28e0b3-fe23-4293-8876-8384def6c4fb` +- batteryCritical (Bool) → stateTypeId: `4223a3bc-9616-4ed2-ae50-704b9df62d0e` +- updateStatus (String) → stateTypeId: `92ecca97-4b27-47a3-a91b-948be70d36da` +- currentVersion (String) → stateTypeId: `d04a07c7-f1ef-4645-8de3-f2af2ee602f3` +- availableVersion (String) → stateTypeId: `33f0fa54-2c33-4b9c-a2a5-5908ad1ac32c` +- updateProgress (Uint) → stateTypeId: `9eb669bf-db22-4a58-843a-063457b0f586` + +#### Actions + +- performUpdate → actionTypeId: `22a24b4c-4c1a-490d-abe1-25b120ceef71` + +--- + +### shellyPlus1 + +- ThingClassId: `83766db4-a553-4df7-aeff-35fb18c01f3a` + +#### States + +- power (Bool) → stateTypeId: `231365ce-45d6-4087-8ed6-1de3b0ef0485` +- connected (Bool) → stateTypeId: `30c5bbb2-7b02-445b-af16-8a082321383b` +- signalStrength (Uint) → stateTypeId: `f2b1f70c-dab0-474d-81bd-816ff3c63f47` +- updateStatus (String) → stateTypeId: `2c312a9e-ae76-4042-8030-89c8c86320e9` +- currentVersion (String) → stateTypeId: `3fed15bd-500b-4cce-9b21-a931f411b587` +- availableVersion (String) → stateTypeId: `d236c735-012c-44c7-919e-809cae89de55` + +#### Actions + +- power → actionTypeId: `231365ce-45d6-4087-8ed6-1de3b0ef0485` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "231365ce-45d6-4087-8ed6-1de3b0ef0485", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `212aaf17-faf1-4495-8475-0718b9b39628` +- performUpdate → actionTypeId: `fa3a8bd6-182f-443f-ae41-d2c4aee6339d` + +--- + +### sonoff_basic + +- ThingClassId: `f39fdfa8-73e0-4cf4-8d05-dc237ced7a57` + +#### States + +- connected (Bool) → stateTypeId: `9cde6321-2abf-4a58-a1d6-c7418edb9747` +- signalStrength (Uint) → stateTypeId: `f70e202a-c178-4fbe-a778-713220d11b23` +- power (Bool) → stateTypeId: `0b5a48c9-73b8-42ab-9909-71b8dc2227e3` + +#### Actions + +- power → actionTypeId: `0b5a48c9-73b8-42ab-9909-71b8dc2227e3` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "0b5a48c9-73b8-42ab-9909-71b8dc2227e3", + "params": { + "power": "" + } + } + } + + +--- + +### lumiAirMonitor + +- ThingClassId: `1a9fb2ea-543e-46b2-a10d-9a977232367b` + +#### States + +- connected (Bool) → stateTypeId: `92f74c82-5a3e-4bb7-b3f3-205a6a19620f` +- signalStrength (Uint) → stateTypeId: `f948308d-ed05-47fb-a893-f1c855db10af` +- version (String) → stateTypeId: `42e7712d-ff8b-4af5-be33-e4a55527a0b1` +- temperature (Double) → stateTypeId: `7ce4b747-dfc2-40db-ba55-fa430de97416` +- humidity (Double) → stateTypeId: `85bd3b3f-11b7-409f-95bd-f5b5edc95158` +- voc (Uint) → stateTypeId: `028b0c34-db30-4a3a-bfad-f146b1a96b35` +- batteryCritical (Bool) → stateTypeId: `0689e60d-8022-4537-9284-53d7cd91a78b` +- batteryLevel (Int) → stateTypeId: `0e143e89-d021-40af-a349-de219ceb2084` +- currentVersion (String) → stateTypeId: `2c77e659-c1a0-4cfa-b9de-0a131e491ab6` +- updateStatus (String) → stateTypeId: `7affb09a-6df6-4fb7-88df-a3785b7aa0b5` +- availableVersion (String) → stateTypeId: `799bd935-be61-453f-8e24-a89546e4213d` +- updateProgress (Uint) → stateTypeId: `a6b7f2e2-3d7b-431f-91d8-72f0d47522dd` + +#### Actions + +- performUpdate → actionTypeId: `208a06cd-bbe5-4eb7-afb0-149d257b19b0` + +--- + +### powerSocket + +- ThingClassId: `856bb6a7-65e8-44cb-b77a-ce46f742f33d` + +#### States + +- connected (Bool) → stateTypeId: `99117bce-b9c3-4b43-b853-65585a99926e` +- signalStrength (Uint) → stateTypeId: `51a5f34e-880c-4b77-899c-763f2b9bef97` +- power (Bool) → stateTypeId: `7d28e7b0-0c4a-4d69-a595-53b9e9b512c9` +- currentVersion (String) → stateTypeId: `301b9aee-4a30-4873-bfd9-cfcacc40fbf1` +- updateStatus (String) → stateTypeId: `66b60151-5777-45e3-be51-9b490b650990` +- availableVersion (String) → stateTypeId: `844eecd8-2a4f-45a5-ac2d-5bb73eaf5a29` +- updateProgress (Uint) → stateTypeId: `4977cd0d-8c73-43c1-a9a6-c0db8d54219d` + +#### Actions + +- power → actionTypeId: `7d28e7b0-0c4a-4d69-a595-53b9e9b512c9` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7d28e7b0-0c4a-4d69-a595-53b9e9b512c9", + "params": { + "power": "" + } + } + } + +- performUpdate → actionTypeId: `b5b4841d-0c50-4d21-8101-36a631c1aa00` + +--- + +### motionSensor + +- ThingClassId: `8e260080-3aa3-42fc-9908-05dd163f9dcf` + +#### States + +- isPresent (Bool) → stateTypeId: `48e4d1c6-5415-4c03-9935-6db0c818557d` +- tampered (Bool) → stateTypeId: `f4586208-a20e-41c9-9ba7-01254617ddd6` +- batteryLevel (Int) → stateTypeId: `02523370-2bb6-4103-bd1e-ddb9952dd43a` +- batteryCritical (Bool) → stateTypeId: `bf7ad9fc-4ed2-4671-8e32-a63f8eb6327c` +- connected (Bool) → stateTypeId: `076fca3a-31af-4634-b571-fd82b4a5542c` +- signalStrength (Uint) → stateTypeId: `821f9296-4dc1-435b-89ef-e193361f54cb` + + +--- + +### hood + +- ThingClassId: `62ce7eee-f6bb-43e5-8adf-dfd030276987` + +#### States + +- connected (Bool) → stateTypeId: `bb137bcd-bb6e-464b-8fdc-fb376efd1f59` +- localControlState (Bool) → stateTypeId: `a7680cc1-528c-47ac-a917-d9f3866e9504` +- operationState (String) → stateTypeId: `d1cf12ef-c0e9-452f-99bf-5e65a4dd622c` +- ventingLevel (Int) → stateTypeId: `09e73cec-5b98-4d31-86fb-cd671f210196` + + +--- + +### homeConnectAccount + +- ThingClassId: `babc1a39-730a-4516-95bf-ff51a8ce887a` + +#### States + +- connected (Bool) → stateTypeId: `1180576a-1de2-4815-b442-877b572ce586` +- loggedIn (Bool) → stateTypeId: `ff40d5c7-3095-4b3e-9e10-4c0774336764` +- userDisplayName (String) → stateTypeId: `5d3b2396-6528-47c2-b5a4-f751531bccea` + + +--- + +### temperatureSensor + +- ThingClassId: `cf3d65db-6f68-457b-968c-cfb66cbd5311` + +#### States + +- temperature (Double) → stateTypeId: `d0b6c4be-339e-4b0f-a234-0611b7565395` +- input (Double) → stateTypeId: `fed37466-1264-4ac1-84fd-aff3a1f7ff04` + +#### Actions + +- temperature → actionTypeId: `d0b6c4be-339e-4b0f-a234-0611b7565395` + - Parameters: + - temperature (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d0b6c4be-339e-4b0f-a234-0611b7565395", + "params": { + "temperature": "" + } + } + } + +- input → actionTypeId: `fed37466-1264-4ac1-84fd-aff3a1f7ff04` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "fed37466-1264-4ac1-84fd-aff3a1f7ff04", + "params": { + "input": "" + } + } + } + + +--- + +### pushNotifications + +- ThingClassId: `f0dd4c03-0aca-42cc-8f34-9902457b05de` + +#### Actions + +- notify → actionTypeId: `ed9a0196-6c24-4e05-9cbc-c6834de38005` + - Parameters: + - title (String) + - body (String) + - notificationId (String) + - sound (Bool) + - remove (Bool) + - data (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ed9a0196-6c24-4e05-9cbc-c6834de38005", + "params": { + "title": "", + "body": "", + "notificationId": "", + "sound": "", + "remove": "", + "data": "" + } + } + } + + +--- + +### powerMeterSocket + +- ThingClassId: `7cd9f139-013b-49da-ab71-794cd1e22f5d` + +#### States + +- connected (Bool) → stateTypeId: `3f0cb179-c74d-4e24-9b81-c0539de83cd2` +- signalStrength (Uint) → stateTypeId: `94ae4ee4-269b-4637-818a-25621fe40dc7` +- version (String) → stateTypeId: `04ae808c-4720-4d06-b6d8-9772b4bd9e3c` +- power (Bool) → stateTypeId: `bfe70a26-f6a2-412c-94af-b31b9e91d5ea` +- currentPower (Double) → stateTypeId: `33a572a4-fd5b-46e7-8618-eba3b7770309` +- totalEnergyConsumed (Double) → stateTypeId: `b653dea0-8028-49f8-be09-f0fd5521f43c` + +#### Actions + +- power → actionTypeId: `bfe70a26-f6a2-412c-94af-b31b9e91d5ea` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bfe70a26-f6a2-412c-94af-b31b9e91d5ea", + "params": { + "power": "" + } + } + } + +- alert → actionTypeId: `fac41f4f-dcba-48ef-adc5-d6478e67746d` + +--- + +### washer + +- ThingClassId: `aaec390e-a61f-40ea-b42c-80f69428690b` + +#### States + +- connected (Bool) → stateTypeId: `950a8bf0-83c4-4e1b-9c00-167a4d3e3c22` +- closed (Bool) → stateTypeId: `99296e86-09bf-4b74-b122-ee82b6bfdb62` +- operationState (String) → stateTypeId: `2ad3e2f8-c955-4e1d-b394-1e71a16f03bb` +- localControlState (Bool) → stateTypeId: `f3aa6f6e-bfa3-4465-906c-3d5137d4098c` +- remoteControlActivationState (Bool) → stateTypeId: `3c9c7163-b5f5-47cc-86b1-aa95273a22f6` +- remoteStartAllowanceState (Bool) → stateTypeId: `f7a68b23-6d74-451b-89c8-b2d2fb5d0839` +- progress (Int) → stateTypeId: `312d817d-3384-41fb-a8ba-15c137844ee0` +- selectedProgram (String) → stateTypeId: `d10066ad-0ad3-49d2-a3c3-ee6002c1a18d` +- temperature (String) → stateTypeId: `29ed9597-2ace-48f5-91bc-171ac2601bfc` +- spinSpeed (String) → stateTypeId: `ab65bfa0-37d8-4df6-80f0-80377a43a520` +- endTime (String) → stateTypeId: `e9b93ad1-fe9e-4ba2-ac7c-9407fa80c846` + +#### Actions + +- start → actionTypeId: `c87d6825-f37d-4da6-8545-945bd589dbc2` +- stop → actionTypeId: `044582ca-30e6-4d37-9377-809fb5fa120a` + +--- + +### counterRpi + +- ThingClassId: `75b13371-a064-47a7-bb82-e9d93a5b5027` + +#### States + +- counter (Int) → stateTypeId: `891bc1ce-2f9b-4518-aed9-90e78bc2409e` + + +--- + +### cookProcessor + +- ThingClassId: `8b792638-11dc-4288-ac94-5b41d9e11a4e` + +#### States + +- connected (Bool) → stateTypeId: `434c5690-a62d-4815-ad8b-e3c306745d9f` +- operationState (String) → stateTypeId: `9aed899f-09cc-4f20-965a-9724372a1093` + + +--- + +### ioModule + +- ThingClassId: `3fb419ef-795d-4f4d-b801-e7eaff16cdb0` + +#### States + +- connected (Bool) → stateTypeId: `16d5ebb7-21d1-4294-98a6-ca06eea8e3a3` +- signalStrength (Uint) → stateTypeId: `0e09e6a6-8b3f-4b63-acb1-ac04ab31957a` +- version (String) → stateTypeId: `f28c9a61-b8ab-419c-bd85-58692df26ac7` +- output1 (Bool) → stateTypeId: `aaeda2c6-439a-452a-b829-45d6249bdee6` +- output2 (Bool) → stateTypeId: `105cf314-35b5-4a8b-8e6d-d011009f97ff` +- input1 (Bool) → stateTypeId: `bc23c7e8-f4ad-47c4-b938-f9af2dbd3393` +- input2 (Bool) → stateTypeId: `065b0dc0-6d31-40ec-b356-02fea57e2fe1` +- input3 (Bool) → stateTypeId: `8f2b052a-30b7-49aa-a8d4-503cb0b9b66b` +- input4 (Bool) → stateTypeId: `caef986c-da13-4ef3-ab81-316244c7be1e` + +#### Actions + +- output1 → actionTypeId: `aaeda2c6-439a-452a-b829-45d6249bdee6` + - Parameters: + - output1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "aaeda2c6-439a-452a-b829-45d6249bdee6", + "params": { + "output1": "" + } + } + } + +- output2 → actionTypeId: `105cf314-35b5-4a8b-8e6d-d011009f97ff` + - Parameters: + - output2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "105cf314-35b5-4a8b-8e6d-d011009f97ff", + "params": { + "output2": "" + } + } + } + +- alert → actionTypeId: `62c5562a-8bdf-49b4-8e1d-f27442b2b360` +- impulseOutput1 → actionTypeId: `45ec5c65-0719-4148-82ea-3e69b34be939` +- impulseOutput2 → actionTypeId: `d780946c-4ddf-4b59-a669-dbf6ecfda5d6` + +--- + +### remote + +- ThingClassId: `bb482d39-67ef-46dc-88e9-7b181d642b28` + +#### States + +- connected (Bool) → stateTypeId: `9f9fac89-2dcd-4db6-b214-0065662af62a` +- batteryLevel (Int) → stateTypeId: `683e493a-9796-4d5e-b0e3-61cb178d5819` +- batteryCritical (Bool) → stateTypeId: `f8516899-6312-4110-bb97-70ffa81dc530` + + +--- + +### gwa1501BinaryInput + +- ThingClassId: `5daf398f-2eba-47c1-8805-ca34f075c7ea` + +#### States + +- connected (Bool) → stateTypeId: `c4a8d75d-fce0-4243-a25d-9a10a6c7e811` +- signalStrength (Uint) → stateTypeId: `17150f4b-6ef1-444a-b178-293a057275dc` +- batteryLevel (Int) → stateTypeId: `9dcf9715-267e-43c6-bcc7-9822e5361228` +- batteryCritical (Bool) → stateTypeId: `8b4b82e8-69e4-4160-90be-3040d455fd97` +- input1 (Bool) → stateTypeId: `9005783a-88ec-4f93-a486-992b2d6ae3ab` +- input2 (Bool) → stateTypeId: `cd6362a6-2d94-41e4-ad0c-31a1cf910f33` + + +--- + +### kasaSocket + +- ThingClassId: `2f40e28c-2484-4e09-bda9-e7accc29ab41` + +#### States + +- connected (Bool) → stateTypeId: `59feb3a8-d35e-4c10-ac2e-01361e711a5e` +- signalStrength (Uint) → stateTypeId: `16b71516-2ff4-4a2f-b0fc-53fff11f3a16` +- power (Bool) → stateTypeId: `b1d006a6-f2f5-475c-ab9e-8b431b1ac5f8` + +#### Actions + +- power → actionTypeId: `b1d006a6-f2f5-475c-ab9e-8b431b1ac5f8` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b1d006a6-f2f5-475c-ab9e-8b431b1ac5f8", + "params": { + "power": "" + } + } + } + + +--- + +### generic + +- ThingClassId: `f5c02d35-b06e-4950-8238-847d50cabfdf` + +#### States + +- stringState (String) → stateTypeId: `f477e227-0d2d-4d88-a545-7dedf36266d5` +- digitalInput (Bool) → stateTypeId: `3cf5eb01-dae8-49cd-8c5f-b0224be0044f` +- digitalOutput (Bool) → stateTypeId: `605b4d8e-71a7-441e-900a-e07c305acd93` +- analogInput (Double) → stateTypeId: `810fcd3c-7854-4bb1-82f6-1403ba54875e` +- analogOutput (Double) → stateTypeId: `de8ec1b1-8026-4fd8-aefb-dedc07616aa4` + +#### Actions + +- stringState → actionTypeId: `f477e227-0d2d-4d88-a545-7dedf36266d5` + - Parameters: + - stringState (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f477e227-0d2d-4d88-a545-7dedf36266d5", + "params": { + "stringState": "" + } + } + } + +- digitalInput → actionTypeId: `3cf5eb01-dae8-49cd-8c5f-b0224be0044f` + - Parameters: + - digitalInput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3cf5eb01-dae8-49cd-8c5f-b0224be0044f", + "params": { + "digitalInput": "" + } + } + } + +- digitalOutput → actionTypeId: `605b4d8e-71a7-441e-900a-e07c305acd93` + - Parameters: + - digitalOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "605b4d8e-71a7-441e-900a-e07c305acd93", + "params": { + "digitalOutput": "" + } + } + } + +- analogInput → actionTypeId: `810fcd3c-7854-4bb1-82f6-1403ba54875e` + - Parameters: + - analogInput (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "810fcd3c-7854-4bb1-82f6-1403ba54875e", + "params": { + "analogInput": "" + } + } + } + +- analogOutput → actionTypeId: `de8ec1b1-8026-4fd8-aefb-dedc07616aa4` + - Parameters: + - analogOutput (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "de8ec1b1-8026-4fd8-aefb-dedc07616aa4", + "params": { + "analogOutput": "" + } + } + } + + +--- + +### virtualButton + +- ThingClassId: `820b2f2d-0d92-48c8-8fd4-f94ce8fc4103` + +#### Actions + +- press → actionTypeId: `01f38af1-b2ab-4ec3-844e-ef52f0f229a9` + +--- + +### motionSensor + +- ThingClassId: `500a8b65-ad34-4bf0-a35d-c167510999f2` + +#### States + +- isPresent (Bool) → stateTypeId: `73523dee-93c1-4143-abff-7685b2d1bb1c` +- tampered (Bool) → stateTypeId: `c9c0343a-e396-4a21-ab7f-5f3d88cc55c5` +- batteryLevel (Int) → stateTypeId: `4ed91f61-298f-411a-a1b8-403d77eecf34` +- batteryCritical (Bool) → stateTypeId: `ca81d872-dcf0-430d-8b00-98e6e4e4ccf4` +- connected (Bool) → stateTypeId: `5d12e9da-2f5c-4d40-a20a-b5d378fd0387` +- signalStrength (Uint) → stateTypeId: `5062aac1-aa2d-4a35-9435-482ff45a4d02` + + +--- + +### light + +- ThingClassId: `c50d3216-f307-4f9f-8190-4391510c385c` + +#### States + +- power (Bool) → stateTypeId: `8b6e4a67-6522-408b-b676-8d2f09ed2d54` + +#### Actions + +- power → actionTypeId: `8b6e4a67-6522-408b-b676-8d2f09ed2d54` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8b6e4a67-6522-408b-b676-8d2f09ed2d54", + "params": { + "power": "" + } + } + } + + +--- + +### awning + +- ThingClassId: `d3a3bb40-4b2d-4bdc-989f-5254f03b4c90` + +#### States + +- percentage (Int) → stateTypeId: `c409cb9b-82ef-4f59-ae89-eb783d4ebe97` +- moving (Bool) → stateTypeId: `2507ac5a-9658-42cb-80f6-73f673c32771` +- signalStrength (Uint) → stateTypeId: `b2ad6f4a-c507-45c3-a951-b344603cc3fc` +- connected (Bool) → stateTypeId: `8f972969-10dd-4954-9c8b-de56070a6668` + +#### Actions + +- percentage → actionTypeId: `c409cb9b-82ef-4f59-ae89-eb783d4ebe97` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c409cb9b-82ef-4f59-ae89-eb783d4ebe97", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `9612954c-02cb-4159-9a29-f36eaf1c7f6a` +- stop → actionTypeId: `33bec73b-4d15-493a-b553-bcee32c40ee1` +- close → actionTypeId: `20cae53b-f36d-425b-b937-3e46519893a3` + +--- + +### flowerCare + +- ThingClassId: `297e2efa-168c-4eb8-bf3f-f8694dc0a2b9` + +#### States + +- connected (Bool) → stateTypeId: `6254103a-ff8b-4f62-afda-6b5be1c933ec` +- batteryLevel (Int) → stateTypeId: `801a00ab-ce3b-4b10-9236-2f845c469a26` +- batteryCritical (Bool) → stateTypeId: `700a83e6-9fbb-4330-b00e-e9d0874b4f66` +- temperature (Double) → stateTypeId: `4577a032-bafd-49ca-8625-6dcf90819e88` +- moisture (Double) → stateTypeId: `5e3ac82a-1dda-4399-888e-529b391a814a` +- lightIntensity (Double) → stateTypeId: `7e0a3b3d-72f4-4547-bd1e-2711be61c1a7` +- conductivity (Double) → stateTypeId: `f77608b3-8b5b-43a9-aab6-0dc35c694ec3` + + +--- + +### client + +- ThingClassId: `cf1a99ce-ad17-4cc7-8558-480daba20e72` + +#### States + +- isPresent (Bool) → stateTypeId: `7c2420eb-31eb-43b8-b28c-0dba4a4a3910` +- lastSeenTime (Int) → stateTypeId: `8491c998-6100-4a1c-b0b7-6d44696aceba` + + +--- + +### inverter + +- ThingClassId: `540aa956-8b8f-4982-9f58-343a76cea846` + +#### States + +- connected (Bool) → stateTypeId: `eda29c50-73ac-40e0-9c92-26fee352e688` +- currentPower (Double) → stateTypeId: `788accbc-b86e-471b-b37f-14c9c6411526` +- energyDay (Double) → stateTypeId: `b6af1bf5-753d-47b6-a151-e4d801fe6ff8` +- energyYear (Int) → stateTypeId: `7fd2fa28-9bcc-4f01-a823-459437d185f6` +- totalEnergyProduced (Double) → stateTypeId: `d6dbb879-4cbc-4db3-830e-b92ba91a13e5` + + +--- + +### sunnyWebBox + +- ThingClassId: `49304127-ce9b-45dd-8511-05030a4ac003` + +#### States + +- connected (Bool) → stateTypeId: `c05e6a1a-252c-4f2b-8b31-09cf113d01c1` +- currentPower (Double) → stateTypeId: `ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2` +- dayEnergyProduced (Double) → stateTypeId: `16f34c5c-8dbb-4dcc-9faa-4b782d57226c` +- totalEnergyProduced (Double) → stateTypeId: `0bb4e227-7e38-49ca-9b32-ce4621c9305b` +- mode (String) → stateTypeId: `1974550b-6059-4b0e-83f4-70177e20dac3` +- error (String) → stateTypeId: `4e64f9ca-7e5a-4897-8035-6f2ae88fde89` + + +--- + +### lumiPowerSocket + +- ThingClassId: `76afde12-e781-4723-94ca-5704e800e821` + +#### States + +- connected (Bool) → stateTypeId: `58e01e24-fa6e-4518-ad05-f3b118901f69` +- signalStrength (Uint) → stateTypeId: `81926f63-4dc8-4701-a62d-07b20a849b37` +- currentVersion (String) → stateTypeId: `e4a8e90c-4e38-4b3b-b5e6-fd336e3fa2a8` +- updateStatus (String) → stateTypeId: `21e03a17-7bb8-41f7-8cba-9194354892f9` +- availableVersion (String) → stateTypeId: `79ca6068-5532-4b3c-b8e2-d2533b03cc93` +- updateProgress (Uint) → stateTypeId: `6655f819-b020-4573-843d-32d17a931db7` +- power (Bool) → stateTypeId: `4c81aa6b-3a8a-4d2c-be87-29486ca242af` +- currentPower (Double) → stateTypeId: `2c05bb81-3be9-46a9-8bc6-94d72ee4a668` +- totalEnergyConsumed (Double) → stateTypeId: `5616730e-6097-4bcb-8dfe-47667c5d197a` + +#### Actions + +- power → actionTypeId: `4c81aa6b-3a8a-4d2c-be87-29486ca242af` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4c81aa6b-3a8a-4d2c-be87-29486ca242af", + "params": { + "power": "" + } + } + } + +- alert → actionTypeId: `67052e4d-350a-406f-91a9-3ce3f8eaaa7f` +- performUpdate → actionTypeId: `447936ea-96aa-4e37-a5ae-c62e3f7f3d05` + +--- + +### tasmotaSwitch + +- ThingClassId: `8a5e69c0-14ad-4ae8-9ff9-10055de6ffdf` + +#### States + +- connected (Bool) → stateTypeId: `b4607e5d-70c4-4e76-9d9a-c6de7c50377e` +- signalStrength (Uint) → stateTypeId: `34e54234-d2d6-4c93-8d52-bada19e5ff23` +- power (Bool) → stateTypeId: `413503d7-fc9f-417a-95fa-5c350a6f69f9` + +#### Actions + +- power → actionTypeId: `413503d7-fc9f-417a-95fa-5c350a6f69f9` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "413503d7-fc9f-417a-95fa-5c350a6f69f9", + "params": { + "power": "" + } + } + } + + +--- + +### socket + +- ThingClassId: `9d8da004-a8a1-457f-a8ee-b86133828a49` + +#### States + +- connected (Bool) → stateTypeId: `e7e868a0-2de4-46ba-8ce7-87eaa4fc8e06` +- power (Bool) → stateTypeId: `47329958-c33f-478f-b2a0-910abd150da8` + +#### Actions + +- power → actionTypeId: `47329958-c33f-478f-b2a0-910abd150da8` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "47329958-c33f-478f-b2a0-910abd150da8", + "params": { + "power": "" + } + } + } + + +--- + +### gateway + +- ThingClassId: `6c09e0b9-f0cc-4dea-9994-9e039eff78f1` + +#### States + +- connected (Bool) → stateTypeId: `18ba7fb7-c9e8-4c61-86b3-a8d3b825ed00` + + +--- + +### shelly1 + +- ThingClassId: `f810b66a-7177-4397-9771-4229abaabbb6` + +#### States + +- power (Bool) → stateTypeId: `5b7eeb6c-6113-41f3-a61b-3076d087c9fe` +- connected (Bool) → stateTypeId: `e5d41e05-2296-457e-97d8-98a5ac0de615` +- signalStrength (Uint) → stateTypeId: `74c631ed-fc3d-49e8-9dec-99cafa70c559` +- updateStatus (String) → stateTypeId: `68bf3780-8f7f-4ecb-8498-830e257c192c` +- currentVersion (String) → stateTypeId: `e033cca0-03fa-4b2e-9d7b-d4a2c8ffbb8c` +- availableVersion (String) → stateTypeId: `6e794011-d184-4ab2-9c3a-3b2205880cbc` + +#### Actions + +- power → actionTypeId: `5b7eeb6c-6113-41f3-a61b-3076d087c9fe` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5b7eeb6c-6113-41f3-a61b-3076d087c9fe", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `b4067d54-36c5-4d30-bbc3-c8c712d6fd32` +- performUpdate → actionTypeId: `6f814339-9a48-4027-a3f8-760742ff22ba` + +--- + +### bridge + +- ThingClassId: `642aa4c7-19aa-45ed-ba06-aa1ae6c9edf7` + +#### States + +- connected (Bool) → stateTypeId: `15794d26-fde8-4a61-8f83-d7830534975f` +- apiVersion (String) → stateTypeId: `7a230e89-c4ce-4276-90e0-6a9ddb890603` +- updateStatus (String) → stateTypeId: `16a126f3-0cef-4931-bb2b-9e1b49bec7fc` +- currentVersion (String) → stateTypeId: `4c707b18-6604-4e6d-b6bc-4e27769c2adc` + +#### Actions + +- searchNewDevices → actionTypeId: `cca3f171-6318-44e7-a2ac-d841857c1c24` + - Parameters: + - serial (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "cca3f171-6318-44e7-a2ac-d841857c1c24", + "params": { + "serial": "" + } + } + } + +- checkForUpdates → actionTypeId: `07a85e91-d064-4bce-b017-13fd0c320c0b` +- performUpdate → actionTypeId: `6dfbc7c0-7372-42f6-82ba-e777cb32dc4c` + +--- + +### smokeSensor + +- ThingClassId: `258dd38e-72ba-4295-b04b-ea9ef8e3312b` + +#### States + +- connected (Bool) → stateTypeId: `f1a65b25-c0f9-4535-9f85-45b42d91cfa1` +- signalStrength (Uint) → stateTypeId: `f0b38b1c-0f1d-4359-b732-06aae73b9edf` +- fireDetected (Bool) → stateTypeId: `58b073a2-62a0-4754-9e57-4dac80095ed1` +- smokeConcentration (Double) → stateTypeId: `aaef289e-d1fc-4e44-8c1d-bbb6ad28ea33` +- tampered (Bool) → stateTypeId: `cca93a41-206c-41fa-809c-07f4f34b010f` +- batteryLevel (Int) → stateTypeId: `9abc0e1b-242a-4f4b-a656-26563c761ed9` +- batteryCritical (Bool) → stateTypeId: `37bae504-1471-4689-b8fa-cf494a5522a8` + + +--- + +### simpleHeatPump + +- ThingClassId: `9fdb33bb-8e38-4108-8234-3b1d07bbc801` + +#### States + +- power (Bool) → stateTypeId: `ac2ac0f9-058c-4586-8871-2d9dbc6e1758` + +#### Actions + +- power → actionTypeId: `ac2ac0f9-058c-4586-8871-2d9dbc6e1758` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ac2ac0f9-058c-4586-8871-2d9dbc6e1758", + "params": { + "power": "" + } + } + } + + +--- + +### windModule + +- ThingClassId: `160096e8-5ad0-4ca5-b7f2-60d64865ca71` + +#### States + +- updateTime (Int) → stateTypeId: `aa4be9dc-06e4-48a2-b83e-45c523da116b` +- windSpeed (Double) → stateTypeId: `9f6c1540-9933-4ef1-96d7-aecade2a8199` +- windDirection (Int) → stateTypeId: `65ad8900-583a-4bf7-8bff-f738225ca017` +- signalStrength (Uint) → stateTypeId: `7eafdb74-c99a-40c4-95ca-99f5ddf82355` +- connected (Bool) → stateTypeId: `5d369774-78df-4259-bdff-cc82b946470b` +- batteryLevel (Int) → stateTypeId: `560e0bd7-3c75-430e-8ef3-65bbbe85a3af` +- batteryCritical (Bool) → stateTypeId: `5dcddcf9-da07-4bac-9988-018168a516ad` + + +--- + +### colorLight + +- ThingClassId: `d32953a5-ac30-479e-bd8d-e523c745902d` + +#### States + +- connected (Bool) → stateTypeId: `e4954ad7-812b-491f-9187-ba6cd6cd1755` +- signalStrength (Uint) → stateTypeId: `3a65294c-a5b8-475e-ab1c-8a5df5c3ab51` +- power (Bool) → stateTypeId: `3ab42d68-2394-40ff-8704-571e5b1b28e9` +- brightness (Int) → stateTypeId: `58f16042-b027-439b-96f6-39ae4b5772f8` +- colorTemperature (Int) → stateTypeId: `472d3582-cff9-42d1-914a-433ebdf8bf37` +- color (Color) → stateTypeId: `92338bb1-c3ef-46ee-8e87-3dcabad6d0d2` +- updateStatus (String) → stateTypeId: `7d59df4c-7964-4c05-af04-a2672e30d919` +- currentVersion (String) → stateTypeId: `985ef8bf-22c7-40b8-8de6-8ba5f54ab287` +- availableVersion (String) → stateTypeId: `8ae98171-c2bc-4d87-a1e1-8d73f669fb4f` +- updateProgress (Uint) → stateTypeId: `b2fd7717-c6ce-425e-9b39-d9e953f5d687` + +#### Actions + +- power → actionTypeId: `3ab42d68-2394-40ff-8704-571e5b1b28e9` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3ab42d68-2394-40ff-8704-571e5b1b28e9", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `58f16042-b027-439b-96f6-39ae4b5772f8` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "58f16042-b027-439b-96f6-39ae4b5772f8", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `472d3582-cff9-42d1-914a-433ebdf8bf37` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "472d3582-cff9-42d1-914a-433ebdf8bf37", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `92338bb1-c3ef-46ee-8e87-3dcabad6d0d2` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "92338bb1-c3ef-46ee-8e87-3dcabad6d0d2", + "params": { + "color": "" + } + } + } + +- alert → actionTypeId: `2a0dc479-34e8-4f56-bd24-b004d4b762e9` +- performUpdate → actionTypeId: `392cd1ac-af9c-4035-ae6c-20c033ebf317` + +--- + +### gpioOutputBbb + +- ThingClassId: `75d54a59-f9b0-4bc4-a86c-6b1fc47e0663` + +#### States + +- power (Bool) → stateTypeId: `82b567c6-a33c-484e-b5e7-e04795498d00` + +#### Actions + +- power → actionTypeId: `82b567c6-a33c-484e-b5e7-e04795498d00` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "82b567c6-a33c-484e-b5e7-e04795498d00", + "params": { + "power": "" + } + } + } + + +--- + +### soundRemote + +- ThingClassId: `a18e398d-c14b-4bcd-8e6d-b64737436814` + +#### States + +- connected (Bool) → stateTypeId: `41ca0f43-998e-4a5e-ad6b-ae7deab38461` +- signalStrength (Uint) → stateTypeId: `9485fea7-adfc-47aa-a7c4-76f38eff49ca` +- batteryLevel (Int) → stateTypeId: `1a0e2e7b-1b2a-461c-a24f-1323c2aea4a1` +- batteryCritical (Bool) → stateTypeId: `59312570-9cab-4e64-ac2f-2eda1c5ac314` +- level (Uint) → stateTypeId: `58657999-e062-4a12-81e7-f81d7842d18b` +- currentVersion (String) → stateTypeId: `1958f3f7-509b-4222-9598-b6edd1bb9c1c` +- updateStatus (String) → stateTypeId: `73255d32-c21e-467a-86aa-3458660ac2ea` +- availableVersion (String) → stateTypeId: `0780be81-d4e3-4dc2-86dd-85bd8624737e` +- updateProgress (Uint) → stateTypeId: `7f53f37a-fccc-4909-8cb0-223b75046c79` + +#### Actions + +- performUpdate → actionTypeId: `5f8d2a0e-b80c-4479-8699-8ac46c98c34b` + +--- + +### shellyFlood + +- ThingClassId: `d59f08e4-64e3-49f1-96e1-60b63136e3d9` + +#### States + +- waterDetected (Bool) → stateTypeId: `c8ecb7c4-da20-49b4-a086-8c137d3dc349` +- temperature (Double) → stateTypeId: `1f62ad7a-5200-4807-ad5d-7d3bff1f8966` +- connected (Bool) → stateTypeId: `0c445ba9-091d-4cca-bca1-b15c51a95d0f` +- signalStrength (Uint) → stateTypeId: `bbb6d325-9712-4f29-81b6-65fd09264a99` +- batteryLevel (Int) → stateTypeId: `e321f13e-8efc-4882-8498-def48b3e8ae4` +- batteryCritical (Bool) → stateTypeId: `0b657f84-f09c-46af-ac28-069b2b67bac3` +- updateStatus (String) → stateTypeId: `a5120193-9396-45c5-bd16-e92a229899db` +- currentVersion (String) → stateTypeId: `15c1f27c-2379-4df0-afa9-086b63282d2a` +- availableVersion (String) → stateTypeId: `e86ef323-4531-459f-9c8c-d150a93ad208` + +#### Actions + +- performUpdate → actionTypeId: `0263209a-65b2-4521-a241-db597fb003ea` + +--- + +### airQualitySensor + +- ThingClassId: `9f966405-4d63-4ae8-8472-a3ab7bbcadaa` + +#### States + +- connected (Bool) → stateTypeId: `c6f65bb8-1234-40f2-8855-84ca28f48150` +- signalStrength (Uint) → stateTypeId: `19cfb35b-6a2e-4efe-9366-287c2f13c027` +- version (String) → stateTypeId: `5d1c46ca-9306-473a-a721-165831ff53cc` +- temperature (Double) → stateTypeId: `2d31d98e-dcd7-4700-a90e-a0d051ad6b40` +- humidity (Double) → stateTypeId: `5bc2825c-666f-4013-9583-3a88ceebecc2` +- batteryCritical (Bool) → stateTypeId: `048ed452-785f-4361-b7a1-f955f4a622b2` +- voc (Uint) → stateTypeId: `1ba14107-4241-46d0-b65f-eb6e8c6f8e08` +- updateStatus (String) → stateTypeId: `0ce38986-4700-47ef-a672-51853485e5f5` +- currentVersion (String) → stateTypeId: `b66948ad-59fd-420b-a7cd-4937b63050d5` +- availableVersion (String) → stateTypeId: `80434d6d-2cde-4671-a050-8352f6aff2de` +- updateProgress (Uint) → stateTypeId: `0b9488fa-458f-4c9b-9035-60f7d3d559e4` + +#### Actions + +- performUpdate → actionTypeId: `24b2a7ec-66c7-4e9e-8f92-1dee3373ff63` + +--- + +### presenceSensor + +- ThingClassId: `17978e89-290f-4101-8a9e-85cb1cb5e12a` + +#### States + +- connected (Bool) → stateTypeId: `af307ed4-6dd0-4e5e-9ea0-822f473613e7` +- signalStrength (Uint) → stateTypeId: `dc265b4f-da86-43d0-9d55-aa83a9dff323` +- isPresent (Bool) → stateTypeId: `98d3096b-4e42-4b6e-ae29-21b876fbfe3b` +- targetDistance (Double) → stateTypeId: `020e64e0-82a3-4bb6-8869-fc32368695f6` +- lightIntensity (Double) → stateTypeId: `242c732d-47d3-496e-9004-1a36647b398a` +- selfTest (String) → stateTypeId: `91e6ba5e-8dd1-4211-9a18-9d1511198614` + + +--- + +### shellyEm + +- ThingClassId: `bcc7326d-555a-4763-80ce-7354e67cc700` + +#### States + +- connected (Bool) → stateTypeId: `75f1a571-b21c-43c2-b4a3-ab8e9d7ef08c` +- signalStrength (Uint) → stateTypeId: `87664175-4b84-4cfe-9bee-5d1bcf2566f8` +- updateStatus (String) → stateTypeId: `ad5d523e-9a4d-47d2-912c-c8ec5081f9ff` +- currentVersion (String) → stateTypeId: `f5f03ab2-fec6-4502-8a47-100211878a8a` +- availableVersion (String) → stateTypeId: `97ab36bb-355b-4559-838a-fe49a9cbd13e` +- power (Bool) → stateTypeId: `9a2c6304-91d6-45fc-8ef7-75355457eca5` + +#### Actions + +- power → actionTypeId: `9a2c6304-91d6-45fc-8ef7-75355457eca5` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9a2c6304-91d6-45fc-8ef7-75355457eca5", + "params": { + "power": "" + } + } + } + +- performUpdate → actionTypeId: `6a30f435-2b35-4df5-8a20-ef3dbec817c9` +- reset → actionTypeId: `09f6d675-4c22-4a9f-b9f2-3349ab947529` + +--- + +### netPwrCtlHome + +- ThingClassId: `d70433ac-9738-49ca-932f-6d3e20bcc6d4` + +#### States + +- connected (Bool) → stateTypeId: `9cde6321-2abf-4a58-a1d6-c7418edb9747` + + +--- + +### shellyGas + +- ThingClassId: `f032e312-0911-450e-9456-67c27f31bebd` + +#### States + +- gasLevel (Double) → stateTypeId: `f7549376-226f-42aa-ae0e-1a62884908d8` +- connected (Bool) → stateTypeId: `4c912d66-398f-4c82-9ae1-a08d191cfbc7` +- signalStrength (Uint) → stateTypeId: `71a6ef7a-c116-4cc6-94ab-943c0aad4d5f` +- updateStatus (String) → stateTypeId: `8ceb701b-7a6d-4f81-a09e-72e8ace12057` +- currentVersion (String) → stateTypeId: `98911e5d-16f6-4502-a7df-111c7007a660` +- availableVersion (String) → stateTypeId: `8c805225-86fb-4a50-b8af-4bbe0b4d7dde` +- sensorOperation (String) → stateTypeId: `efbc314f-d3e4-4558-9736-e7499d2bd8ba` +- selfTest (String) → stateTypeId: `36d55e34-27a5-4e1d-9c87-9f89c65e8aed` +- gas (String) → stateTypeId: `eb69d7d4-0947-4829-a3de-c0c0f94124bd` +- valveState (String) → stateTypeId: `5bc36b2c-4110-44ec-816f-8a3421343bb4` + +#### Actions + +- performUpdate → actionTypeId: `8aa54702-b76f-46a1-a23b-1fc6d699eea8` +- selfTest → actionTypeId: `28016a8c-fb0e-4c1b-9f8d-787eedadf0e0` +- mute → actionTypeId: `c9cf63b5-0582-441f-9508-1d45c468d9f4` +- unmute → actionTypeId: `53455722-50b5-4597-b8d9-195b6dc65e96` +- openValve → actionTypeId: `ae583f53-aced-439d-a7d6-82bd1e48477d` +- closeValve → actionTypeId: `9e46233b-d6d4-42f7-9cdc-a08cf72a5b49` + +--- + +### sdm630 + +- ThingClassId: `f37597bb-35fe-48f2-9617-343dd54c0903` + +#### States + +- connected (Bool) → stateTypeId: `8050bd0b-1dad-4a7e-b632-c71ead3c9f8b` +- voltagePhaseA (Double) → stateTypeId: `db018146-0441-4dc0-9834-6d43ebaf8311` +- voltagePhaseB (Double) → stateTypeId: `406f6d02-d5eb-49b3-87da-3247568e6054` +- voltagePhaseC (Double) → stateTypeId: `ace6294d-deaa-4d9a-af78-d64379bcb229` +- currentPhaseA (Double) → stateTypeId: `4baf1d08-5ffa-49cf-95ef-9527b0c6f081` +- currentPhaseB (Double) → stateTypeId: `99e47d06-0a6a-4bfd-b164-61ecb6ba2818` +- currentPhaseC (Double) → stateTypeId: `4a092a66-352d-4d60-90ab-6ac5f58b92fe` +- currentPower (Double) → stateTypeId: `c824e97b-a6d1-4030-9d7a-00af6fb8e1c3` +- currentPowerPhaseA (Double) → stateTypeId: `3982fb12-b179-40f7-9b27-36adb1cadd37` +- currentPowerPhaseB (Double) → stateTypeId: `2a231c58-b095-4037-8394-a730431e70b8` +- currentPowerPhaseC (Double) → stateTypeId: `ee8c4f0c-2b69-4210-9966-1553a592b06d` +- frequency (Double) → stateTypeId: `ab24f26c-dc15-4ec3-8d76-06a48285440b` +- totalEnergyConsumed (Double) → stateTypeId: `98d858a8-22e8-4262-b5c7-25bb027942ad` +- totalEnergyProduced (Double) → stateTypeId: `e469b3ff-a4c2-42da-af35-ccafaef214af` +- energyConsumedPhaseA (Double) → stateTypeId: `6ca06c81-fe75-4448-a22f-47c303421440` +- energyConsumedPhaseB (Double) → stateTypeId: `fa2b879b-2a81-4bc8-9577-98082c4d9330` +- energyConsumedPhaseC (Double) → stateTypeId: `4c084c9e-7a5d-42d1-96b2-a8a4b4a25713` +- energyProducedPhaseA (Double) → stateTypeId: `308fa88e-6054-4c79-b12a-be2d0a404ef6` +- energyProducedPhaseB (Double) → stateTypeId: `48ab6e61-dfb4-4f85-b5cc-9d89e53c6b39` +- energyProducedPhaseC (Double) → stateTypeId: `6b3ddf15-3d4b-4dc1-8e5a-84fbf90b49ff` + + +--- + +### shellyMotion + +- ThingClassId: `a82737bb-f2d6-442e-a468-5acc0a2e4cd7` + +#### States + +- isPresent (Bool) → stateTypeId: `45c0cc07-0e13-449c-86a7-ab65d5cdf637` +- lightIntensity (Double) → stateTypeId: `a1e12487-ebab-4993-a075-78275aac7b0d` +- vibration (Bool) → stateTypeId: `76438c2d-9742-4680-9139-d4b4e988cfd2` +- connected (Bool) → stateTypeId: `9ed997fa-1ec8-44d2-ac44-c99d4e259dae` +- signalStrength (Uint) → stateTypeId: `ecc61ce2-cb66-47ce-a65c-3b9e43ce6fdc` +- updateStatus (String) → stateTypeId: `a604a66c-3143-45ce-a6e3-17a339f428ab` +- currentVersion (String) → stateTypeId: `e2d98172-5833-454b-9b4b-04693c6b9232` +- availableVersion (String) → stateTypeId: `0c9e8da4-1b1c-4047-8e9d-c5c580bcf43f` +- batteryCritical (Bool) → stateTypeId: `1d03941e-9c41-446b-b698-f8dff335bf11` +- batteryLevel (Int) → stateTypeId: `f6d89aa6-7dba-4bb0-89cf-36d85208933f` + +#### Actions + +- performUpdate → actionTypeId: `17327674-f160-44e1-8a3d-fc2b6e1ee319` + +--- + +### dimmableLight + +- ThingClassId: `b89762f6-056f-442d-8edc-6ab877ae9a57` + +#### States + +- connected (Bool) → stateTypeId: `d6f7b579-08c2-4193-9eca-9b37a77e9a65` +- signalStrength (Uint) → stateTypeId: `9edb92b1-8a78-4628-9e2c-c0640860dbf8` +- power (Bool) → stateTypeId: `a0945256-b0ba-4767-b856-6c518eb9f6df` +- brightness (Int) → stateTypeId: `55a1b729-b865-4f05-a6b7-388a50e275f7` +- updateStatus (String) → stateTypeId: `0173bd05-ce4e-46df-a0dd-e565dd56db4a` +- currentVersion (String) → stateTypeId: `0c1388fb-fb1d-4b66-ac3c-4e0ba907b030` +- availableVersion (String) → stateTypeId: `6c202e40-f7f8-4472-8d76-a3845dca3439` +- updateProgress (Uint) → stateTypeId: `26d1277d-6868-4738-a2c2-f95b97da4117` + +#### Actions + +- power → actionTypeId: `a0945256-b0ba-4767-b856-6c518eb9f6df` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a0945256-b0ba-4767-b856-6c518eb9f6df", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `55a1b729-b865-4f05-a6b7-388a50e275f7` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "55a1b729-b865-4f05-a6b7-388a50e275f7", + "params": { + "brightness": "" + } + } + } + +- alert → actionTypeId: `c4ef7e85-b3cc-4c17-8347-e3e3c28771eb` +- performUpdate → actionTypeId: `7e644f8e-5f95-44db-9abc-a1d66986cee0` + +--- + +### kasaSwitch200 + +- ThingClassId: `d527e576-d65e-4ae6-ae43-c89b594fb4c1` + +#### States + +- connected (Bool) → stateTypeId: `8792b3d1-f83d-44c3-b649-249a935d1eb4` +- signalStrength (Uint) → stateTypeId: `6387c4e3-b0c1-4151-a3c9-925929c2d51e` +- power (Bool) → stateTypeId: `3b0daf6c-ba04-4ff1-a381-685c869665b1` + +#### Actions + +- power → actionTypeId: `3b0daf6c-ba04-4ff1-a381-685c869665b1` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3b0daf6c-ba04-4ff1-a381-685c869665b1", + "params": { + "power": "" + } + } + } + + +--- + +### shellyEmChannel + +- ThingClassId: `67ccc046-c8b5-4584-8f7f-6fe0a0c6a860` + +#### States + +- connected (Bool) → stateTypeId: `d96d7505-f270-49ad-abb2-4f29ac11fb84` +- signalStrength (Uint) → stateTypeId: `d1928e72-73e6-4009-846e-149f80ad5899` +- totalEnergyConsumed (Double) → stateTypeId: `4ce53fa0-d6b7-4c1b-87d9-edcaeedb640e` +- totalEnergyProduced (Double) → stateTypeId: `7fe88e8f-a1c4-4e8d-a1de-9135b80bc7e3` +- currentPower (Double) → stateTypeId: `a85041e8-a19e-4695-8404-3e3a06b1e92a` +- currentPhaseA (Double) → stateTypeId: `1d457f50-0951-4ba5-8d8e-b79ea5a75535` +- voltagePhaseA (Double) → stateTypeId: `d6cb777f-c9af-46d8-845a-883ac05c206a` + + +--- + +### thermostat + +- ThingClassId: `ca9af6cf-2d15-4d54-ba07-3d2ce03445b8` + +#### States + +- targetTemperature (Double) → stateTypeId: `88ad3957-2912-4de1-b53d-b360565dd361` +- temperature (Double) → stateTypeId: `497af03a-a893-438c-aba2-1bf3ecfc66c5` +- heatingOn (Bool) → stateTypeId: `88d5dda1-b8f6-49f1-a55a-20415f9157b3` +- coolingOn (Bool) → stateTypeId: `c77a3d3f-46c7-4026-b9ab-02ab88077cc4` +- connected (Bool) → stateTypeId: `e9fb2b10-96da-4b70-afda-46e948399af8` +- signalStrength (Uint) → stateTypeId: `8f0512ab-ced2-4dcb-90fe-aaa532efd0dd` +- version (String) → stateTypeId: `c8bd12d2-b425-4422-9e0e-a65542a47b70` +- batteryLevel (Int) → stateTypeId: `3a733e99-850b-4c56-b058-d39850ef2fee` +- batteryCritical (Bool) → stateTypeId: `5cec4399-ba7c-4c78-8c30-c91040ad99cf` + +#### Actions + +- targetTemperature → actionTypeId: `88ad3957-2912-4de1-b53d-b360565dd361` + - Parameters: + - targetTemperature (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "88ad3957-2912-4de1-b53d-b360565dd361", + "params": { + "targetTemperature": "" + } + } + } + + +--- + +### pushNotification + +- ThingClassId: `56029c6d-777b-4889-9e17-29f813d34da4` + +#### Actions + +- notify → actionTypeId: `1378480f-6583-44e2-8fdd-22afe979a9a3` + - Parameters: + - title (String) + - body (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1378480f-6583-44e2-8fdd-22afe979a9a3", + "params": { + "title": "", + "body": "" + } + } + } + + +--- + +### powerSocket + +- ThingClassId: `e0ac647a-34a2-40ee-b8a3-8da6668f8a35` + +#### States + +- connected (Bool) → stateTypeId: `8ffdef7e-138b-4671-9309-ce81f2f9b818` +- signalStrength (Uint) → stateTypeId: `e4448f7c-719f-4ece-abb6-05b4e8b7a262` +- power (Bool) → stateTypeId: `b89f9bf8-59eb-4d04-8f93-eef4b8d88989` +- currentPower (Double) → stateTypeId: `97836b20-dc36-418f-b2af-74b71c6b27f3` +- totalEnergyConsumed (Double) → stateTypeId: `6ae69f17-7fcd-4cb3-a071-ff48ebb720ff` +- currentVersion (String) → stateTypeId: `1dc9223c-7d23-40b1-b46a-21eecd817ea4` +- availableVersion (String) → stateTypeId: `140b1a2c-d4da-48fc-bc82-80e2b17a1534` +- updateStatus (String) → stateTypeId: `07eece60-8b52-4a92-9f06-be2dc19145e5` +- updateProgress (Uint) → stateTypeId: `36bd5cea-a38a-4b9a-8ee3-272558788f70` + +#### Actions + +- power → actionTypeId: `b89f9bf8-59eb-4d04-8f93-eef4b8d88989` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b89f9bf8-59eb-4d04-8f93-eef4b8d88989", + "params": { + "power": "" + } + } + } + +- performUpdate → actionTypeId: `ecef7366-d925-4e44-8757-d39b546c1744` + +--- + +### shelly25 + +- ThingClassId: `465efb0d-da68-4177-a040-940c7f451e29` + +#### States + +- connected (Bool) → stateTypeId: `36a50e24-707e-4d5d-8eba-d2a73f626e2b` +- signalStrength (Uint) → stateTypeId: `35503b8a-1919-4df5-91cf-b7e381af654a` +- updateStatus (String) → stateTypeId: `0f03f1f3-5575-4375-9889-499a172c66c4` +- currentVersion (String) → stateTypeId: `69f6cb77-1775-4d94-8592-798fdc26a9fc` +- availableVersion (String) → stateTypeId: `eb8ce8b3-979f-4661-a0ed-d31d3e217e77` +- channel1 (Bool) → stateTypeId: `118d572c-cc12-4037-82d8-7d8f6fb4a364` +- channel2 (Bool) → stateTypeId: `7952aec0-cd27-4ef9-87a6-c499564bc1d4` + +#### Actions + +- channel1 → actionTypeId: `118d572c-cc12-4037-82d8-7d8f6fb4a364` + - Parameters: + - channel1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "118d572c-cc12-4037-82d8-7d8f6fb4a364", + "params": { + "channel1": "" + } + } + } + +- channel2 → actionTypeId: `7952aec0-cd27-4ef9-87a6-c499564bc1d4` + - Parameters: + - channel2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7952aec0-cd27-4ef9-87a6-c499564bc1d4", + "params": { + "channel2": "" + } + } + } + +- reboot → actionTypeId: `4085a6a0-746f-490d-9e38-897c24f866b6` +- performUpdate → actionTypeId: `14a24bc3-8215-430e-b4ff-e25ccee29691` + +--- + +### light + +- ThingClassId: `e569a3cc-6e79-4e24-af35-c5fa327a7314` + +#### States + +- brightness (Int) → stateTypeId: `1b51ce68-1f7e-4f06-b68d-bfca2d61b353` +- power (Bool) → stateTypeId: `654ddcdf-b0b7-4c38-a70d-878f0f3857a5` +- signalStrength (Uint) → stateTypeId: `cfaa5533-d26e-4545-9f44-6567c9d7888a` +- connected (Bool) → stateTypeId: `fb8dcd84-70ad-4f3e-97c4-93296608e33d` + +#### Actions + +- brightness → actionTypeId: `1b51ce68-1f7e-4f06-b68d-bfca2d61b353` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1b51ce68-1f7e-4f06-b68d-bfca2d61b353", + "params": { + "brightness": "" + } + } + } + +- power → actionTypeId: `654ddcdf-b0b7-4c38-a70d-878f0f3857a5` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "654ddcdf-b0b7-4c38-a70d-878f0f3857a5", + "params": { + "power": "" + } + } + } + + +--- + +### o2Sensor + +- ThingClassId: `a8cf20ec-132b-4f6f-b5ec-73dc2f873b43` + +#### States + +- o2saturation (Double) → stateTypeId: `a3ff7641-87d9-4b07-b8e5-e6aa92b90088` +- input (Double) → stateTypeId: `7e74fca7-84d0-4f3e-9f26-4232b7b9f507` + +#### Actions + +- o2saturation → actionTypeId: `a3ff7641-87d9-4b07-b8e5-e6aa92b90088` + - Parameters: + - o2saturation (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a3ff7641-87d9-4b07-b8e5-e6aa92b90088", + "params": { + "o2saturation": "" + } + } + } + +- input → actionTypeId: `7e74fca7-84d0-4f3e-9f26-4232b7b9f507` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7e74fca7-84d0-4f3e-9f26-4232b7b9f507", + "params": { + "input": "" + } + } + } + + +--- + +### powerSocket + +- ThingClassId: `695b54dc-99f8-4fb2-a773-57e268b7a193` + +#### States + +- connected (Bool) → stateTypeId: `7db10726-fec9-43d7-a807-b94295836a8a` +- signalStrength (Uint) → stateTypeId: `abe47f33-787c-4820-8bb3-6a5ccd1de24d` +- power (Bool) → stateTypeId: `a51dea82-6745-4855-8728-3f3cfee0f7f4` +- currentPower (Double) → stateTypeId: `be16e4f3-4a4d-4447-9d74-660f46b6d580` +- totalEnergyConsumed (Double) → stateTypeId: `628b37bc-28ed-4c4c-aa83-80383fd99316` + +#### Actions + +- power → actionTypeId: `a51dea82-6745-4855-8728-3f3cfee0f7f4` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a51dea82-6745-4855-8728-3f3cfee0f7f4", + "params": { + "power": "" + } + } + } + + +--- + +### xiaomiMotionSensor + +- ThingClassId: `73bfe746-4fc7-4a2a-a3c2-32dc44214170` + +#### States + +- connected (Bool) → stateTypeId: `d0239a0a-9320-45b6-8652-5e6af4f544d8` +- signalStrength (Uint) → stateTypeId: `84a2061d-da9c-4755-87b2-770dc92a2a53` +- version (String) → stateTypeId: `ad45268e-54f0-44fe-9a8f-25ccdca92452` +- isPresent (Bool) → stateTypeId: `9a30ff91-64cb-432c-8db7-cf6a751e8601` +- lastSeenTime (Int) → stateTypeId: `55229e22-e42b-4d38-af4b-fb668439d38a` + + +--- + +### onOffLight + +- ThingClassId: `7fbc3165-e618-4dc0-8255-8dac2cc08dfa` + +#### States + +- connected (Bool) → stateTypeId: `7548ac08-4ea1-4dcd-98b9-9d58bbc06ab7` +- signalStrength (Uint) → stateTypeId: `5857943e-ed90-4321-966e-b56eddaf2441` +- version (String) → stateTypeId: `f5b5e0b3-2c34-4ccc-baef-8e29b84e7a2b` +- power (Bool) → stateTypeId: `ae03155c-f17f-4d9f-bb40-157a8cb1f83c` + +#### Actions + +- power → actionTypeId: `ae03155c-f17f-4d9f-bb40-157a8cb1f83c` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ae03155c-f17f-4d9f-bb40-157a8cb1f83c", + "params": { + "power": "" + } + } + } + +- alert → actionTypeId: `9388290d-5db2-484a-a702-53a0d842d412` + +--- + +### coffeeMaker + +- ThingClassId: `f6b39ce2-8276-4db7-b2a3-4a04cafacbb9` + +#### States + +- connected (Bool) → stateTypeId: `796aa7d3-db32-4b6a-88b0-323813feceb3` +- closed (Bool) → stateTypeId: `5011efa7-9915-4ecc-b717-6ced369abcb7` +- operationState (String) → stateTypeId: `693fc8d2-d9a9-4109-b8c5-f3d22091500c` +- localControlState (Bool) → stateTypeId: `dc0fbd4b-364e-4856-a237-7d789a99f321` +- remoteStartAllowanceState (Bool) → stateTypeId: `598985c1-7c9a-4b04-aabd-f72e6dd8cab8` +- progress (Int) → stateTypeId: `a121f86a-ab69-4be9-bbe2-631e2210fee7` +- selectedProgram (String) → stateTypeId: `648e3a8e-74c4-41e8-bcbb-dd8b5757d375` +- strength (String) → stateTypeId: `714a12bc-63a5-4423-aad9-47d77f41ae47` +- fillQuantity (Int) → stateTypeId: `b3aea372-bc51-4e92-b92f-5b9613c20a9c` +- temperature (String) → stateTypeId: `29dc7edb-c34f-41da-9d2a-e46bda34885b` + +#### Actions + +- strength → actionTypeId: `714a12bc-63a5-4423-aad9-47d77f41ae47` + - Parameters: + - strength (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "714a12bc-63a5-4423-aad9-47d77f41ae47", + "params": { + "strength": "" + } + } + } + +- fillQuantity → actionTypeId: `b3aea372-bc51-4e92-b92f-5b9613c20a9c` + - Parameters: + - fillQuantity (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b3aea372-bc51-4e92-b92f-5b9613c20a9c", + "params": { + "fillQuantity": "" + } + } + } + +- temperature → actionTypeId: `29dc7edb-c34f-41da-9d2a-e46bda34885b` + - Parameters: + - temperature (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "29dc7edb-c34f-41da-9d2a-e46bda34885b", + "params": { + "temperature": "" + } + } + } + +- start → actionTypeId: `025f7cb5-83a7-40d3-9628-05eda18f5568` +- stop → actionTypeId: `12f5b09e-a23a-43ee-ba4b-8b0f34792496` + +--- + +### shellyRgbw2 + +- ThingClassId: `17f24cec-e6ed-4abd-9d42-60999f391dba` + +#### States + +- connected (Bool) → stateTypeId: `98b6e1ba-8d5c-4cb1-82a0-2d06c71cdba6` +- signalStrength (Uint) → stateTypeId: `194ecf76-c45f-4c36-8695-08a26ce065f3` +- power (Bool) → stateTypeId: `14abcd30-9db2-4065-ae81-501a55fbb145` +- brightness (Int) → stateTypeId: `3f74eb92-d95b-48c2-8ac6-29bea9f65ce3` +- colorTemperature (Int) → stateTypeId: `a32a457f-fdc0-46ce-9106-6f9d4f4a6b16` +- color (Color) → stateTypeId: `6ef7c686-350d-4069-9c41-9b90b3906748` +- whiteChannel (Uint) → stateTypeId: `8006331c-53ca-4386-8d5c-da62c175af01` +- currentPower (Double) → stateTypeId: `82ce0c4f-cb81-43c7-bc07-003f8a3cfbc8` +- updateStatus (String) → stateTypeId: `3d22110c-db53-4420-8e0f-314555484926` +- currentVersion (String) → stateTypeId: `8f2fd109-553a-48df-9b08-6a7fdcba46c1` +- availableVersion (String) → stateTypeId: `e0d58e73-8a36-4f36-901b-f6be6a84942d` + +#### Actions + +- power → actionTypeId: `14abcd30-9db2-4065-ae81-501a55fbb145` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "14abcd30-9db2-4065-ae81-501a55fbb145", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `3f74eb92-d95b-48c2-8ac6-29bea9f65ce3` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3f74eb92-d95b-48c2-8ac6-29bea9f65ce3", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `a32a457f-fdc0-46ce-9106-6f9d4f4a6b16` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a32a457f-fdc0-46ce-9106-6f9d4f4a6b16", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `6ef7c686-350d-4069-9c41-9b90b3906748` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "6ef7c686-350d-4069-9c41-9b90b3906748", + "params": { + "color": "" + } + } + } + +- whiteChannel → actionTypeId: `8006331c-53ca-4386-8d5c-da62c175af01` + - Parameters: + - whiteChannel (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8006331c-53ca-4386-8d5c-da62c175af01", + "params": { + "whiteChannel": "" + } + } + } + +- reboot → actionTypeId: `1390811d-dc71-4d89-a6e1-a808e03225f9` +- performUpdate → actionTypeId: `abca86b9-74b0-4a17-a8ee-89811b5981a0` + +--- + +### colorLight + +- ThingClassId: `8d157281-73c0-4d2c-96a0-572d26e9dd4f` + +#### States + +- connected (Bool) → stateTypeId: `bc56076a-cfa3-4f93-bbf5-2fe9b067cdc3` +- signalStrength (Uint) → stateTypeId: `62789a2c-c6e6-47b4-b022-29a2d183f690` +- version (String) → stateTypeId: `084b8268-4ae6-482d-b42b-67a57b5a7147` +- power (Bool) → stateTypeId: `16cfb549-5347-4dc9-b5de-e4028cb4dddd` +- brightness (Int) → stateTypeId: `7329b2eb-fb7e-4098-a551-c3ff24fc2962` +- colorTemperature (Int) → stateTypeId: `c2817667-afd0-434b-b0a3-e020d2c90082` +- color (Color) → stateTypeId: `51e8e6da-426c-4812-9c00-70f1356190dc` + +#### Actions + +- power → actionTypeId: `16cfb549-5347-4dc9-b5de-e4028cb4dddd` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "16cfb549-5347-4dc9-b5de-e4028cb4dddd", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `7329b2eb-fb7e-4098-a551-c3ff24fc2962` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7329b2eb-fb7e-4098-a551-c3ff24fc2962", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `c2817667-afd0-434b-b0a3-e020d2c90082` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c2817667-afd0-434b-b0a3-e020d2c90082", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `51e8e6da-426c-4812-9c00-70f1356190dc` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "51e8e6da-426c-4812-9c00-70f1356190dc", + "params": { + "color": "" + } + } + } + +- alert → actionTypeId: `062fa97d-50b6-48ee-ab74-03701e4c7220` + +--- + +### shellyPlus25 + +- ThingClassId: `86b037f5-c7f8-4778-b6d1-2b11c4ba1027` + +#### States + +- connected (Bool) → stateTypeId: `8f4fe4d4-7036-4a1a-9960-091a4a7f6f02` +- signalStrength (Uint) → stateTypeId: `01158eee-3605-45c2-8b6d-2c0a69321c5b` +- updateStatus (String) → stateTypeId: `395a188b-c2e2-40d1-a5f2-9ed4a9ff558f` +- currentVersion (String) → stateTypeId: `c1f5d20a-3b06-4405-afca-20dfd41e3a34` +- availableVersion (String) → stateTypeId: `32134deb-8e37-4d65-ae10-aad0cb44ff7b` +- channel1 (Bool) → stateTypeId: `bf969a1f-aa8a-40aa-8e50-af37b8b0ffe6` +- channel2 (Bool) → stateTypeId: `43b41b49-def1-469b-b993-788647dc8839` + +#### Actions + +- channel1 → actionTypeId: `bf969a1f-aa8a-40aa-8e50-af37b8b0ffe6` + - Parameters: + - channel1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bf969a1f-aa8a-40aa-8e50-af37b8b0ffe6", + "params": { + "channel1": "" + } + } + } + +- channel2 → actionTypeId: `43b41b49-def1-469b-b993-788647dc8839` + - Parameters: + - channel2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "43b41b49-def1-469b-b993-788647dc8839", + "params": { + "channel2": "" + } + } + } + +- reboot → actionTypeId: `7714490b-117d-4d25-a695-9e151997f128` +- performUpdate → actionTypeId: `9d5e04b8-cac4-49ce-9e32-efc3a06beb4b` + +--- + +### sonosConnection + +- ThingClassId: `22df416d-7732-44f1-b6b9-e41296211178` + +#### States + +- connected (Bool) → stateTypeId: `5aa4360c-61de-47d0-a72e-a19d57712e1c` +- loggedIn (Bool) → stateTypeId: `48b5c1bf-7df0-45d0-9ba3-290fc3acddc3` +- userDisplayName (String) → stateTypeId: `fb993eab-f1b5-44dd-9b99-041faec5a3b9` + + +--- + +### remoteControl + +- ThingClassId: `b909dc16-61fa-4c5f-ad7d-195327f78f8d` + +#### States + +- batteryCritical (Bool) → stateTypeId: `445e279e-31e7-4a80-b382-62417ad61414` +- batteryLevel (Int) → stateTypeId: `812daed6-1630-4d01-a039-3977edc965c0` +- connected (Bool) → stateTypeId: `5b563fa9-1140-4f1f-8b87-4c1a287ccad5` +- signalStrength (Uint) → stateTypeId: `599ca06d-a532-4633-8b79-30a50325e4a2` +- version (String) → stateTypeId: `87ac4e00-bf34-4a15-9cc3-a98c5099c1eb` + + +--- + +### wallSwitch + +- ThingClassId: `e967027f-f8fc-410c-8b48-6ac4c42e2777` + +#### States + +- connected (Bool) → stateTypeId: `b51071af-1290-41f1-b2eb-e84527342ade` +- batteryLevel (Int) → stateTypeId: `b025cab6-d128-43eb-ba63-b16861d6ab10` +- batteryCritical (Bool) → stateTypeId: `7fcf84e4-5638-46ce-9a7c-85b8bd466b38` + + +--- + +### tahoma + +- ThingClassId: `fedd72b8-547d-4e4f-b73e-71344a8ba0c1` + +#### States + +- connected (Bool) → stateTypeId: `10ebf650-a93a-4ee3-945b-fba10d4e35a5` +- loggedIn (Bool) → stateTypeId: `97fefa85-db79-4efd-8d83-4a15d72996e1` +- userDisplayName (String) → stateTypeId: `75609987-be60-4932-94f6-ead791b5fa58` + + +--- + +### netPwrCtlHut + +- ThingClassId: `bc9a90e0-b320-46f1-8d7b-2d24f40af5ea` + +#### States + +- connected (Bool) → stateTypeId: `3b30c586-4756-4903-8405-b00bc9d34685` +- temperature (Double) → stateTypeId: `7689239c-e6c3-48cc-ae90-bd1cad44b631` +- humidity (Double) → stateTypeId: `6e38c9f7-cdd7-4909-9312-2047812d1883` +- lightIntensity (Double) → stateTypeId: `60792bc4-fc67-47f6-8c3f-45717a072d59` + + +--- + +### shellyDimmer + +- ThingClassId: `3a1d6fc1-c623-4b45-9c81-1573fcc15f99` + +#### States + +- connected (Bool) → stateTypeId: `49ff0891-1798-459f-a97c-f1cc4e643a46` +- signalStrength (Uint) → stateTypeId: `a6855b46-7da8-4160-b698-7707600581b5` +- power (Bool) → stateTypeId: `e4a6ac87-31fb-4516-9cf3-f135621e902c` +- brightness (Int) → stateTypeId: `f41c93ac-6911-45fc-9221-7dd26bf65fd0` +- currentPower (Double) → stateTypeId: `b3336ca6-1577-4230-8708-98875148606e` +- updateStatus (String) → stateTypeId: `8d3b3d63-86f1-46cb-92ef-d27c0d9d0a4e` +- currentVersion (String) → stateTypeId: `2fac7af0-1aa0-4e8d-b3fb-584b49647887` +- availableVersion (String) → stateTypeId: `e6418140-db00-4f9b-a7fc-bf4a56a3a8a7` + +#### Actions + +- power → actionTypeId: `e4a6ac87-31fb-4516-9cf3-f135621e902c` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e4a6ac87-31fb-4516-9cf3-f135621e902c", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `f41c93ac-6911-45fc-9221-7dd26bf65fd0` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f41c93ac-6911-45fc-9221-7dd26bf65fd0", + "params": { + "brightness": "" + } + } + } + +- reboot → actionTypeId: `dca1d1bb-b377-41b8-ac26-579060448fd7` +- performUpdate → actionTypeId: `893031b8-9abd-4a83-8da4-ea57acb621ad` + +--- + +### venetianBlind + +- ThingClassId: `e6b96ced-8d50-45da-91c8-792d364d2795` + +#### States + +- openingOutput (Bool) → stateTypeId: `6041dacf-5303-4dc0-ba3c-7ecaa438f2dd` +- closingOutput (Bool) → stateTypeId: `84dd2fa1-85fe-47f3-9e32-e6083432d39c` +- status (String) → stateTypeId: `6fb7826e-b6d8-42f8-b712-719496046436` +- moving (Bool) → stateTypeId: `6234c07e-4200-4f2c-8cbd-bff24c38c243` +- percentage (Int) → stateTypeId: `33dc8019-336d-4d50-8d60-dff8508338ca` +- angle (Int) → stateTypeId: `fcb700c4-5da8-4385-85b0-6616e807974e` + +#### Actions + +- openingOutput → actionTypeId: `6041dacf-5303-4dc0-ba3c-7ecaa438f2dd` + - Parameters: + - openingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "6041dacf-5303-4dc0-ba3c-7ecaa438f2dd", + "params": { + "openingOutput": "" + } + } + } + +- closingOutput → actionTypeId: `84dd2fa1-85fe-47f3-9e32-e6083432d39c` + - Parameters: + - closingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "84dd2fa1-85fe-47f3-9e32-e6083432d39c", + "params": { + "closingOutput": "" + } + } + } + +- percentage → actionTypeId: `33dc8019-336d-4d50-8d60-dff8508338ca` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "33dc8019-336d-4d50-8d60-dff8508338ca", + "params": { + "percentage": "" + } + } + } + +- angle → actionTypeId: `fcb700c4-5da8-4385-85b0-6616e807974e` + - Parameters: + - angle (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "fcb700c4-5da8-4385-85b0-6616e807974e", + "params": { + "angle": "" + } + } + } + +- open → actionTypeId: `3e728e50-3d45-4035-b215-1e604cf3159b` +- stop → actionTypeId: `6e3eeb5d-d7ed-4175-9795-e76451e0a00b` +- close → actionTypeId: `1c71f050-f6cb-4929-9c9d-7c262f77c143` + +--- + +### shutter + +- ThingClassId: `7917c2e7-d7d2-4c47-a38d-41f7dd7693d9` + +#### States + +- openingOutput (Bool) → stateTypeId: `cc547728-b309-4695-b355-49748ef2521c` +- closingOutput (Bool) → stateTypeId: `1c35df0e-4c41-455f-893a-0145377952a0` +- status (String) → stateTypeId: `6d6e72dc-4d2b-4ec1-82c2-54405a682711` + +#### Actions + +- openingOutput → actionTypeId: `cc547728-b309-4695-b355-49748ef2521c` + - Parameters: + - openingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "cc547728-b309-4695-b355-49748ef2521c", + "params": { + "openingOutput": "" + } + } + } + +- closingOutput → actionTypeId: `1c35df0e-4c41-455f-893a-0145377952a0` + - Parameters: + - closingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1c35df0e-4c41-455f-893a-0145377952a0", + "params": { + "closingOutput": "" + } + } + } + +- open → actionTypeId: `9deb662d-2378-4345-a898-8742d41e43c1` +- stop → actionTypeId: `db5f3332-1f4e-4f9e-84d2-93c5d7de315c` +- close → actionTypeId: `cf5303f1-67c7-4cef-b11c-eb9de6fc8a87` + +--- + +### shellyAddonTempSensor + +- ThingClassId: `dffcc66b-bb93-4de0-9ad4-0b2ca0fa813a` + +#### States + +- connected (Bool) → stateTypeId: `982846ff-e045-4e4d-8959-b5ea65b83082` +- signalStrength (Uint) → stateTypeId: `5e9ed1dd-a1f7-4755-ac6f-30013580d363` +- temperature (Double) → stateTypeId: `b0062fc6-8fa1-4754-904a-c18614114998` + + +--- + +### extendedBlind + +- ThingClassId: `40aa9f3c-a23c-4f7f-8786-fcf3554f3e19` + +#### States + +- openingOutput (Bool) → stateTypeId: `e559f077-e904-4bbc-8ec3-344b814d2eab` +- closingOutput (Bool) → stateTypeId: `1a4a5839-a30d-4239-a124-63bfdc74a8f6` +- status (String) → stateTypeId: `c2354d7e-198a-43ae-aa5f-c6710010c7e1` +- moving (Bool) → stateTypeId: `941d1e1f-8dd7-4493-812f-6cefefd88c2e` +- percentage (Int) → stateTypeId: `181df603-d45f-4d3d-a358-97aa3e4ac0bd` + +#### Actions + +- openingOutput → actionTypeId: `e559f077-e904-4bbc-8ec3-344b814d2eab` + - Parameters: + - openingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e559f077-e904-4bbc-8ec3-344b814d2eab", + "params": { + "openingOutput": "" + } + } + } + +- closingOutput → actionTypeId: `1a4a5839-a30d-4239-a124-63bfdc74a8f6` + - Parameters: + - closingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1a4a5839-a30d-4239-a124-63bfdc74a8f6", + "params": { + "closingOutput": "" + } + } + } + +- percentage → actionTypeId: `181df603-d45f-4d3d-a358-97aa3e4ac0bd` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "181df603-d45f-4d3d-a358-97aa3e4ac0bd", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `5a7599fa-8351-4ed6-9b98-fa2f3be54304` +- stop → actionTypeId: `ab67e4bf-c7b6-489b-9b49-3e0a1c7d33ca` +- close → actionTypeId: `97d6351d-7440-47f3-bdba-a31bb15368ac` + +--- + +### dimmableLight + +- ThingClassId: `fdfbd11f-bd99-4876-8254-7f91f96ca4fc` + +#### States + +- power (Bool) → stateTypeId: `74535080-b6d9-41db-8e35-ef90e9042b75` +- brightness (Int) → stateTypeId: `88786787-8025-4967-b7a6-f3b670216ac0` + +#### Actions + +- power → actionTypeId: `74535080-b6d9-41db-8e35-ef90e9042b75` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "74535080-b6d9-41db-8e35-ef90e9042b75", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `88786787-8025-4967-b7a6-f3b670216ac0` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "88786787-8025-4967-b7a6-f3b670216ac0", + "params": { + "brightness": "" + } + } + } + + +--- + +### shellyI3 + +- ThingClassId: `a4557bcc-bdae-4178-b774-5881a8ae490a` + +#### States + +- connected (Bool) → stateTypeId: `9b17b10d-07ee-4a3d-813f-ef37e79e7241` +- signalStrength (Uint) → stateTypeId: `54fc5565-5d40-4847-b59e-95bfd8845381` +- input1 (Bool) → stateTypeId: `61e98cc9-4449-4fb2-818e-692303244ce2` +- input2 (Bool) → stateTypeId: `04497ff2-a231-4d3d-adeb-66275a3b128b` +- input3 (Bool) → stateTypeId: `5895fc2d-19a4-40c2-8522-7c4462e55a3e` +- updateStatus (String) → stateTypeId: `889d7f2c-488c-4c91-905d-8669125d5c8b` +- currentVersion (String) → stateTypeId: `33e7d186-7c6b-4c4e-89c3-80362ef76615` +- availableVersion (String) → stateTypeId: `c3ff6d31-7301-412f-9aff-72ac36ba83c9` + +#### Actions + +- reboot → actionTypeId: `162e7791-6890-4075-8e57-a4c15b9359bb` +- performUpdate → actionTypeId: `1c677ecb-c54e-4c95-a3f7-e68fabeeda08` + +--- + +### colorTemperatureLight + +- ThingClassId: `34ee15f7-8e94-4927-bfd2-7123aac0b1be` + +#### States + +- power (Bool) → stateTypeId: `316c8852-6e53-4404-891a-83c1f0fa0c84` +- brightness (Int) → stateTypeId: `76b54a6d-fae9-4cb6-8603-a3a615e153f9` +- colorTemperature (Int) → stateTypeId: `3a2459ed-af20-4ea0-8bd7-97b59be594f7` + +#### Actions + +- power → actionTypeId: `316c8852-6e53-4404-891a-83c1f0fa0c84` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "316c8852-6e53-4404-891a-83c1f0fa0c84", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `76b54a6d-fae9-4cb6-8603-a3a615e153f9` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "76b54a6d-fae9-4cb6-8603-a3a615e153f9", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `3a2459ed-af20-4ea0-8bd7-97b59be594f7` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3a2459ed-af20-4ea0-8bd7-97b59be594f7", + "params": { + "colorTemperature": "" + } + } + } + + +--- + +### tap + +- ThingClassId: `2b8c1fb8-67ee-42e9-947b-16e0a09f0d4e` + +#### States + +- connected (Bool) → stateTypeId: `5e21b032-1230-4e93-8543-0c4773da17d3` + + +--- + +### shellyPlusPlug + +- ThingClassId: `2c470ea4-6ef2-4aa2-b2f3-b6d8750ac577` + +#### States + +- power (Bool) → stateTypeId: `87a60ee4-a6b8-463a-a4bc-c7c5e412239f` +- connected (Bool) → stateTypeId: `ee89944e-eab9-4554-9c7b-0b433f3cf287` +- signalStrength (Uint) → stateTypeId: `e244927d-0003-4107-83e1-3fad923cf738` +- updateStatus (String) → stateTypeId: `8e16b8c8-6981-4eb9-88fe-a863f263f5ba` +- currentVersion (String) → stateTypeId: `78174fac-2cec-4f1b-94c4-d413dd1a54ba` +- availableVersion (String) → stateTypeId: `89da9eb8-a01f-4bc2-9d96-d2d6305a08d7` +- totalEnergyConsumed (Double) → stateTypeId: `5493ecc3-8596-436b-9334-0fe4154656ec` +- currentPower (Double) → stateTypeId: `1dcc910d-ab0e-4c31-b6fa-8105324e416b` + +#### Actions + +- power → actionTypeId: `87a60ee4-a6b8-463a-a4bc-c7c5e412239f` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "87a60ee4-a6b8-463a-a4bc-c7c5e412239f", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `e4a997c5-072f-4212-9ccc-7bfebd20b333` +- performUpdate → actionTypeId: `ae862b2d-0bad-4c37-9d7f-b1fca93f00a0` + +--- + +### awning + +- ThingClassId: `9e69585f-90ba-44e4-ad90-5b4bffbe345a` + +#### States + +- openingOutput (Bool) → stateTypeId: `4bb951a4-ea23-4cf0-9269-41d2c4eaf5a4` +- closingOutput (Bool) → stateTypeId: `59bfd575-709f-4e43-9726-de26e6d4ca8b` +- status (String) → stateTypeId: `ff6f2565-2a2e-4d34-b10f-d3f73b676399` + +#### Actions + +- openingOutput → actionTypeId: `4bb951a4-ea23-4cf0-9269-41d2c4eaf5a4` + - Parameters: + - openingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4bb951a4-ea23-4cf0-9269-41d2c4eaf5a4", + "params": { + "openingOutput": "" + } + } + } + +- closingOutput → actionTypeId: `59bfd575-709f-4e43-9726-de26e6d4ca8b` + - Parameters: + - closingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "59bfd575-709f-4e43-9726-de26e6d4ca8b", + "params": { + "closingOutput": "" + } + } + } + +- open → actionTypeId: `979e9c51-5a93-4635-85e3-01874306b229` +- stop → actionTypeId: `555cafe4-bd12-42c6-bab1-8cd59af6468e` +- close → actionTypeId: `53b5ba77-9a34-4cd6-ad24-fb01fc465f98` + +--- + +### lumiVibrationSensor + +- ThingClassId: `775be45e-08d8-4ae8-bc95-5721a5317856` + +#### States + +- connected (Bool) → stateTypeId: `12970c44-5c59-4c6b-a244-6bc413d32c66` +- signalStrength (Uint) → stateTypeId: `eb8bbd63-1f1c-4499-8385-06fbe8e301db` +- version (String) → stateTypeId: `8a4aceef-c8ee-487a-946c-20f26fc2d5ed` +- angleX (Double) → stateTypeId: `8ef735ef-332a-40f5-aa57-0408c5d05d46` +- angleY (Double) → stateTypeId: `0d398c75-9adf-4835-883b-e578e124af98` +- angleZ (Double) → stateTypeId: `7ead841c-5a20-456b-85ed-983bda234718` +- angleAbsoluteX (Double) → stateTypeId: `10be3741-5bfc-4173-bf4f-e95af0e60d03` +- angleAbsoluteY (Double) → stateTypeId: `97bf3f80-f70e-4bb1-b4e0-546f153dd3bc` + + +--- + +### dimmableBulb + +- ThingClassId: `a5b02af8-7c97-4a78-9c78-bafee7407b5e` + +#### States + +- connected (Bool) → stateTypeId: `d33f98ef-5e0f-464c-afed-88b95cc701cd` +- power (Bool) → stateTypeId: `9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0` +- brightness (Int) → stateTypeId: `a0a1bdcc-2761-4d90-85d1-5ce887546611` +- colorTemperature (Int) → stateTypeId: `95797dee-b836-4047-98d5-afbbce4f8c42` + +#### Actions + +- power → actionTypeId: `9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9e1344ea-cd05-4dd8-8948-8d2f5e00e1b0", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `a0a1bdcc-2761-4d90-85d1-5ce887546611` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a0a1bdcc-2761-4d90-85d1-5ce887546611", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `95797dee-b836-4047-98d5-afbbce4f8c42` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "95797dee-b836-4047-98d5-afbbce4f8c42", + "params": { + "colorTemperature": "" + } + } + } + + +--- + +### shortcutButton + +- ThingClassId: `7a81dd57-0b79-4735-840d-69775d937095` + +#### States + +- connected (Bool) → stateTypeId: `29cb833d-125f-45b0-add4-da69f22d8dcc` +- signalStrength (Uint) → stateTypeId: `24c227cd-aaeb-440e-b45d-0711074ef633` +- batteryLevel (Int) → stateTypeId: `164e8d24-e4b7-4d88-a5d4-5649dfaf0180` +- batteryCritical (Bool) → stateTypeId: `13a68aa5-4118-4dc9-b55e-e715bc423e08` +- currentVersion (String) → stateTypeId: `065f348a-54f2-4203-9a6c-b747e92d169a` +- updateStatus (String) → stateTypeId: `a1254e8e-ac96-40b4-a2dd-dcdbbb3653a8` +- availableVersion (String) → stateTypeId: `352aa728-61e4-4d04-9bb9-9c438ccfb531` +- updateProgress (Uint) → stateTypeId: `af65f485-117f-4a26-911c-ece1d6e07ce9` + +#### Actions + +- performUpdate → actionTypeId: `83b33b50-a8fd-4234-be89-7b6e7da83b06` + +--- + +### outdoor + +- ThingClassId: `6cc01d62-7317-4ec4-8ac4-a4cab762c179` + +#### States + +- updateTime (Int) → stateTypeId: `154aad5c-4998-43c2-b9ee-0b997eb6dd69` +- temperature (Double) → stateTypeId: `f98776bd-887e-4b01-a87f-3d8224180563` +- temperatureMin (Double) → stateTypeId: `b71e0c8b-3c94-421e-830e-dab97b6c104e` +- temperatureMax (Double) → stateTypeId: `aae071dc-70d5-4a6a-8daa-3dca0d150bd7` +- humidity (Double) → stateTypeId: `7ba6ddeb-5142-4b87-9729-487fcda394df` +- signalStrength (Uint) → stateTypeId: `0faa3d08-9004-46fb-a5aa-a59b75e454cc` +- connected (Bool) → stateTypeId: `21d7f2b7-e45b-4986-b3da-d6e8c0421bfc` +- batteryLevel (Int) → stateTypeId: `15d8fae1-ba47-42e1-994d-530e8017c965` +- batteryCritical (Bool) → stateTypeId: `f8aeb144-014d-4ccb-81db-64ffc70f1c97` + + +--- + +### modbusBatteryInverter + +- ThingClassId: `06bed8fd-cadb-4cef-8440-7806fb0165e6` + +#### States + +- connected (Bool) → stateTypeId: `9c4999a1-304d-4724-99cd-eb0cd27590ef` +- batteryLevel (Int) → stateTypeId: `fe5ca68e-ddc2-45e7-aac2-b0e67ac40f87` +- batteryCritical (Bool) → stateTypeId: `56f18b28-ed88-4c1a-a297-a5cad109b055` +- currentPower (Double) → stateTypeId: `e1a91af1-8d1a-4564-9ade-b5488d63b90d` +- capacity (Double) → stateTypeId: `13cdb994-dd9e-49ac-a347-d2ab9aef5b45` +- chargingState (String) → stateTypeId: `a313b416-5ded-43c9-b1a1-a9af50492d0b` +- firmwareVersion (String) → stateTypeId: `952b3d30-1c09-4b0e-b303-56c89d3fa108` + + +--- + +### colorTemperatureLight + +- ThingClassId: `35f749f7-b60a-4922-bd25-1bdd2eddcbe3` + +#### States + +- connected (Bool) → stateTypeId: `38f3ddea-ceb0-47ae-bbd8-e70b5a89b1bf` +- power (Bool) → stateTypeId: `4e579f6a-e4b3-4876-804a-9fcc066f90f9` +- colorTemperature (Int) → stateTypeId: `fee57738-45c7-48fe-a06b-1397376361f0` +- brightness (Int) → stateTypeId: `bdf6f831-b448-4ff6-9f85-12e26b4e5534` + +#### Actions + +- power → actionTypeId: `4e579f6a-e4b3-4876-804a-9fcc066f90f9` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4e579f6a-e4b3-4876-804a-9fcc066f90f9", + "params": { + "power": "" + } + } + } + +- colorTemperature → actionTypeId: `fee57738-45c7-48fe-a06b-1397376361f0` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "fee57738-45c7-48fe-a06b-1397376361f0", + "params": { + "colorTemperature": "" + } + } + } + +- brightness → actionTypeId: `bdf6f831-b448-4ff6-9f85-12e26b4e5534` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bdf6f831-b448-4ff6-9f85-12e26b4e5534", + "params": { + "brightness": "" + } + } + } + +- alert → actionTypeId: `8818e8b9-14ed-4344-96fb-c852abc85921` + - Parameters: + - alert (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8818e8b9-14ed-4344-96fb-c852abc85921", + "params": { + "alert": "" + } + } + } + + +--- + +### smartButton + +- ThingClassId: `edfa223b-aade-4369-a4e2-77b99989f49d` + +#### States + +- batteryLevel (Int) → stateTypeId: `2837540f-a97b-4a92-9e17-900db38577e4` +- batteryCritical (Bool) → stateTypeId: `3d79d36e-008e-41c8-9bb5-28db235b93ce` +- connected (Bool) → stateTypeId: `4748fc45-5ffd-4472-97a2-9fef256937db` +- signalStrength (Uint) → stateTypeId: `0eb19a1e-04d1-44ce-9554-f70c47a4f48a` +- updateStatus (String) → stateTypeId: `2aa27a75-e336-4599-b95b-55175bad4acd` +- currentVersion (String) → stateTypeId: `7c1c798d-334a-4132-8677-f8a49eab0804` +- availableVersion (String) → stateTypeId: `f475fbca-d17b-4751-81cd-74f9da657553` +- updateProgress (Uint) → stateTypeId: `634434d6-9ccd-4e8b-992d-d8b062b47ed7` + +#### Actions + +- performUpdate → actionTypeId: `43a72851-7c66-49e4-841b-72ca05eae733` + +--- + +### doorBird + +- ThingClassId: `0485eb61-2a22-42ba-9dd2-a5961485bf08` + +#### States + +- connected (Bool) → stateTypeId: `186c270b-923c-46e4-a7da-33e45427cdbb` +- isPresent (Bool) → stateTypeId: `0f5eb200-6c0d-45c5-9156-3060fd66d332` +- lastSeenTime (Int) → stateTypeId: `295c9700-b598-4681-898f-d63e2889cedf` + +#### Actions + +- openDoor → actionTypeId: `b6c3377b-91de-411a-9d48-8b509c39d67c` + - Parameters: + - number (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b6c3377b-91de-411a-9d48-8b509c39d67c", + "params": { + "number": "" + } + } + } + +- lightOn → actionTypeId: `3a6cfc5d-804c-4d21-91b5-999913d4f1a5` +- restart → actionTypeId: `e874242e-5acb-4d98-94c7-0a70db65150c` + +--- + +### socket + +- ThingClassId: `4e7261af-a27b-4446-8346-914ea59f7547` + +#### States + +- power (Bool) → stateTypeId: `018038d7-1d02-4b17-8fe3-babca044b087` + +#### Actions + +- power → actionTypeId: `018038d7-1d02-4b17-8fe3-babca044b087` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "018038d7-1d02-4b17-8fe3-babca044b087", + "params": { + "power": "" + } + } + } + + +--- + +### shelly1l + +- ThingClassId: `20754114-1591-48b5-af2f-8c9966adb7c4` + +#### States + +- power (Bool) → stateTypeId: `94276bb9-ef68-47ab-8e74-34ebe54b411f` +- connected (Bool) → stateTypeId: `2a333f45-2bba-4643-b973-1bb47d56306a` +- signalStrength (Uint) → stateTypeId: `cce7906d-fdef-4804-b118-b5abefa6c6ed` +- updateStatus (String) → stateTypeId: `0e47815d-0084-4a30-957a-f73ed100bed5` +- currentVersion (String) → stateTypeId: `2303ffb7-8de4-4993-b3ac-95e4c035b790` +- availableVersion (String) → stateTypeId: `5f357a69-6096-4f31-9b5b-b2d5478739da` +- totalEnergyConsumed (Double) → stateTypeId: `0f879e7b-2124-4d98-9828-e5bbd1b344ce` +- currentPower (Double) → stateTypeId: `194f4f1b-86a7-4c04-abf0-2459ae7e2821` + +#### Actions + +- power → actionTypeId: `94276bb9-ef68-47ab-8e74-34ebe54b411f` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "94276bb9-ef68-47ab-8e74-34ebe54b411f", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `5a05c09a-c6a0-4141-aeeb-86afdb49ffaf` +- performUpdate → actionTypeId: `fa6a52fa-6a57-4d65-8c41-1e61b545dd9b` + +--- + +### lumiWaterSensor + +- ThingClassId: `e8e18bbe-27bb-4fd7-98fc-f5cef3b5f213` + +#### States + +- connected (Bool) → stateTypeId: `03fc0e4d-dcd9-41dc-8aac-dbe735f11ef7` +- signalStrength (Uint) → stateTypeId: `b3bba018-b9d0-47a8-a2cf-d8d2d6fcecce` +- version (String) → stateTypeId: `bae1014f-67f5-4dcc-b36f-e15eb43afd6e` +- waterDetected (Bool) → stateTypeId: `e43532a7-cbee-446e-9e4a-aff88026ad1a` +- batteryCritical (Bool) → stateTypeId: `72aa617c-88cc-4992-a1a2-400b5e19106e` + + +--- + +### sonoff_dimmer + +- ThingClassId: `4fb65caa-7f84-4496-91f6-1cd35574cc2e` + +#### States + +- connected (Bool) → stateTypeId: `1078dd25-b5bd-4005-8c1f-005ddca7ea21` +- signalStrength (Uint) → stateTypeId: `662d99de-bfce-4cf0-8029-108e9911866c` +- power (Bool) → stateTypeId: `c8ec62d4-024b-4770-b893-6288b64f0dfe` +- brightness (Int) → stateTypeId: `5cd8d0df-1188-4da1-9a87-bff991d3b2af` + +#### Actions + +- power → actionTypeId: `c8ec62d4-024b-4770-b893-6288b64f0dfe` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c8ec62d4-024b-4770-b893-6288b64f0dfe", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `5cd8d0df-1188-4da1-9a87-bff991d3b2af` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5cd8d0df-1188-4da1-9a87-bff991d3b2af", + "params": { + "brightness": "" + } + } + } + + +--- + +### lightSensor + +- ThingClassId: `2c35236c-bc5c-4736-beb6-d8dfd0c6bc40` + +#### States + +- lightIntensity (Double) → stateTypeId: `241629dd-15e4-4698-bb56-f4a16b82d6fa` +- batteryCritical (Bool) → stateTypeId: `235a87fd-524c-4177-a707-7ec004d94f54` +- connected (Bool) → stateTypeId: `1415b7dc-8339-4022-99fa-e1b4c42ef591` +- signalStrength (Uint) → stateTypeId: `2ec0dc23-d665-469d-b7b1-2205a6056783` +- version (String) → stateTypeId: `c71e2426-69f3-45d2-acde-4870f010071e` + + +--- + +### presenceSensor + +- ThingClassId: `339a0c54-4086-404f-8d36-bcf20621b785` + +#### States + +- isPresent (Bool) → stateTypeId: `22c1eac6-9bb5-4cc1-ac0d-e3e1499edd3c` +- lastSeenTime (Int) → stateTypeId: `be4842cb-140c-425f-9630-8bb19fdb943e` + +#### Actions + +- isPresent → actionTypeId: `22c1eac6-9bb5-4cc1-ac0d-e3e1499edd3c` + - Parameters: + - isPresent (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "22c1eac6-9bb5-4cc1-ac0d-e3e1499edd3c", + "params": { + "isPresent": "" + } + } + } + + +--- + +### shellyRoller + +- ThingClassId: `d681a4cb-481a-4469-a49a-e6bbb11eb9c9` + +#### States + +- connected (Bool) → stateTypeId: `d446719d-628e-477d-882c-a84210c85869` +- signalStrength (Uint) → stateTypeId: `f46c52ce-58dd-4d07-bb69-e8b8719c41bc` +- totalEnergyConsumed (Double) → stateTypeId: `a7d88654-7503-474d-9a7c-02150d61a6dc` +- currentPower (Double) → stateTypeId: `0d7cb1cf-3fff-4d0b-96c2-c02e9a92af57` +- moving (Bool) → stateTypeId: `2729d4e0-c38c-47b8-a0e8-26959090fe74` +- percentage (Int) → stateTypeId: `86270b8b-bce4-4d8a-9bc9-d72af36b991c` + +#### Actions + +- percentage → actionTypeId: `86270b8b-bce4-4d8a-9bc9-d72af36b991c` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "86270b8b-bce4-4d8a-9bc9-d72af36b991c", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `b96a8f85-c39e-499b-abbd-40b18788e907` +- close → actionTypeId: `87eb0290-615e-4c98-9ec8-a21104fcf05d` +- stop → actionTypeId: `2266303c-df0c-4eae-b15e-6a86e73c9699` +- calibrate → actionTypeId: `4e2d8a7b-821e-4ee8-9f9b-f774d631845f` + +--- + +### gwa1521Actuator + +- ThingClassId: `6bce244f-b93c-474e-b8eb-65e44ad96329` + +#### States + +- connected (Bool) → stateTypeId: `5d23d473-f063-49dd-b675-d31741d6976b` +- signalStrength (Uint) → stateTypeId: `3e3dba24-37d5-4c4b-99b1-b33ad612a817` +- relay (Bool) → stateTypeId: `3d4993c4-9142-409a-8389-3377eeae0ad6` + +#### Actions + +- relay → actionTypeId: `3d4993c4-9142-409a-8389-3377eeae0ad6` + - Parameters: + - relay (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3d4993c4-9142-409a-8389-3377eeae0ad6", + "params": { + "relay": "" + } + } + } + + +--- + +### colorTemperatureLight + +- ThingClassId: `498815c1-a6a3-48e9-9c9b-bff974694b26` + +#### States + +- connected (Bool) → stateTypeId: `dd4eb1fe-4ddd-42fa-97bf-df690728c866` +- signalStrength (Uint) → stateTypeId: `45e5b606-1e4d-42b8-9ee1-0442751247ef` +- version (String) → stateTypeId: `477a1531-5f87-4f21-9b32-01161be93cd2` +- power (Bool) → stateTypeId: `a8c56366-5b2d-4f4b-b910-38a87851c484` +- brightness (Int) → stateTypeId: `b72177f9-b673-49b5-864a-345936e5c821` +- colorTemperature (Int) → stateTypeId: `69e94f88-a664-4bb9-ba04-2c3ea6cd7569` + +#### Actions + +- power → actionTypeId: `a8c56366-5b2d-4f4b-b910-38a87851c484` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a8c56366-5b2d-4f4b-b910-38a87851c484", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `b72177f9-b673-49b5-864a-345936e5c821` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b72177f9-b673-49b5-864a-345936e5c821", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `69e94f88-a664-4bb9-ba04-2c3ea6cd7569` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "69e94f88-a664-4bb9-ba04-2c3ea6cd7569", + "params": { + "colorTemperature": "" + } + } + } + +- alert → actionTypeId: `4f8a38f4-652f-40e1-86dd-028dcd64349c` +- removeFromNetwork → actionTypeId: `a3e8d1c7-81c7-4255-b410-3bc4d5c7b4e9` + +--- + +### lumiButtonSensor + +- ThingClassId: `42c1edba-cc5f-4eb9-84f8-1b0d47a6f95e` + +#### States + +- connected (Bool) → stateTypeId: `f0da4762-4778-4b3b-ba68-466ff504a3d3` +- signalStrength (Uint) → stateTypeId: `6e9de8ee-cb28-4f7d-a0f6-fe9e64df0463` +- version (String) → stateTypeId: `11d03ab4-7450-4caf-81ec-0b2b73e56b08` + + +--- + +### today + +- ThingClassId: `fbf665fb-9aca-423f-a5f2-924e50ebe6ca` + +#### States + +- timeZone (String) → stateTypeId: `ab16997c-be29-438e-b588-2507d723d264` +- city (String) → stateTypeId: `65d2a5ff-bcab-43a8-b043-f88cf01586f4` +- country (String) → stateTypeId: `a534bd8b-6826-4d71-b9f9-df1433964b94` +- day (Int) → stateTypeId: `eb5231ea-6a1b-4d7e-a95f-d49e7b25122e` +- month (Int) → stateTypeId: `fcd8ec96-4488-438a-8b30-58bfe2a7fae2` +- monthName (String) → stateTypeId: `a37acc9c-5cfb-4687-adce-e56beb32586f` +- year (Int) → stateTypeId: `79d4ae9b-ea27-4346-8229-1d90f1ddfc9d` +- weekday (Int) → stateTypeId: `452c0388-7fa1-414e-aeb2-8c7c385824b4` +- weekdayName (String) → stateTypeId: `f627d052-cee6-4727-b9c6-0e935d41e04a` +- weekend (Bool) → stateTypeId: `4de5b57b-bb1a-4d66-9ce3-22bb280b075d` +- duskTime (Int) → stateTypeId: `44a99419-fbcd-4d8e-9441-897848b8f77c` +- sunriseTime (Int) → stateTypeId: `3a08824d-285b-412e-a515-9664b491a85c` +- noonTime (Int) → stateTypeId: `d92be29a-929c-4240-91a0-30153850f838` +- dawnTime (Int) → stateTypeId: `a5779b11-0499-4a6d-a7bd-8143dcc546b4` +- sunsetTime (Int) → stateTypeId: `377f04a7-df58-42ad-a234-e9e23bdc2f85` +- daylight (Bool) → stateTypeId: `1c3d6179-3b00-456c-841a-2d26ce960c25` + + +--- + +### motionSensor + +- ThingClassId: `43844f3d-c620-44ab-b1fa-741b79dfc289` + +#### States + +- connected (Bool) → stateTypeId: `8b16ee46-0132-409a-857e-863d20e68a77` +- signalStrength (Uint) → stateTypeId: `12334fb5-5ea4-44d3-8c60-f187097702db` +- isPresent (Bool) → stateTypeId: `b84574b4-43c9-49c4-a05a-92265bb044d7` +- lightIntensity (Double) → stateTypeId: `25568c64-b904-4099-8d5c-452c3130e130` +- batteryLevel (Int) → stateTypeId: `763dabb7-f1fb-4202-a31a-7af9f63f09e1` +- batteryCritical (Bool) → stateTypeId: `323d801e-a563-4680-8e58-f96e148c2ad1` + + +--- + +### energyMeter + +- ThingClassId: `7bba50d7-e18a-41a8-8267-7aa4876ec475` + +#### States + +- currentPower (Double) → stateTypeId: `d5f3fb79-3654-4aaf-ae30-3a761fe2dfd8` +- totalEnergyConsumed (Double) → stateTypeId: `db36a37d-7c48-4208-8b6e-a1b93acb827f` +- totalEnergyProduced (Double) → stateTypeId: `e73d72f3-b172-4db0-9120-0f713e210bdf` +- currentPhaseA (Double) → stateTypeId: `e493ec25-fb8a-4e36-84d9-4430418b3b6f` +- currentPhaseB (Double) → stateTypeId: `37097a51-063e-4b04-aa7b-67ddda8a3c81` +- currentPhaseC (Double) → stateTypeId: `6c3497fd-9894-4485-a295-15b0d829d023` +- currentPowerPhaseA (Double) → stateTypeId: `91e82743-2d97-41e9-8bf2-cb6034818079` +- currentPowerPhaseB (Double) → stateTypeId: `7ecbec82-71ec-4df3-b298-837e7f37aa4b` +- currentPowerPhaseC (Double) → stateTypeId: `eded281b-217b-48f5-8b91-b5142ea87505` +- voltagePhaseA (Double) → stateTypeId: `03deaf24-e515-4649-8979-ef8ae8ee7c02` +- voltagePhaseB (Double) → stateTypeId: `7e1514fb-7e69-4849-90c4-8f27dbd6453a` +- voltagePhaseC (Double) → stateTypeId: `b6e69a07-a362-4eef-87b7-5a670e332919` + +#### Actions + +- currentPower → actionTypeId: `d5f3fb79-3654-4aaf-ae30-3a761fe2dfd8` + - Parameters: + - currentPower (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d5f3fb79-3654-4aaf-ae30-3a761fe2dfd8", + "params": { + "currentPower": "" + } + } + } + +- totalEnergyConsumed → actionTypeId: `db36a37d-7c48-4208-8b6e-a1b93acb827f` + - Parameters: + - totalEnergyConsumed (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "db36a37d-7c48-4208-8b6e-a1b93acb827f", + "params": { + "totalEnergyConsumed": "" + } + } + } + +- totalEnergyProduced → actionTypeId: `e73d72f3-b172-4db0-9120-0f713e210bdf` + - Parameters: + - totalEnergyProduced (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e73d72f3-b172-4db0-9120-0f713e210bdf", + "params": { + "totalEnergyProduced": "" + } + } + } + +- currentPhaseA → actionTypeId: `e493ec25-fb8a-4e36-84d9-4430418b3b6f` + - Parameters: + - currentPhaseA (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e493ec25-fb8a-4e36-84d9-4430418b3b6f", + "params": { + "currentPhaseA": "" + } + } + } + +- currentPhaseB → actionTypeId: `37097a51-063e-4b04-aa7b-67ddda8a3c81` + - Parameters: + - currentPhaseB (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "37097a51-063e-4b04-aa7b-67ddda8a3c81", + "params": { + "currentPhaseB": "" + } + } + } + +- currentPhaseC → actionTypeId: `6c3497fd-9894-4485-a295-15b0d829d023` + - Parameters: + - currentPhaseC (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "6c3497fd-9894-4485-a295-15b0d829d023", + "params": { + "currentPhaseC": "" + } + } + } + +- currentPowerPhaseA → actionTypeId: `91e82743-2d97-41e9-8bf2-cb6034818079` + - Parameters: + - currentPowerPhaseA (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "91e82743-2d97-41e9-8bf2-cb6034818079", + "params": { + "currentPowerPhaseA": "" + } + } + } + +- currentPowerPhaseB → actionTypeId: `7ecbec82-71ec-4df3-b298-837e7f37aa4b` + - Parameters: + - currentPowerPhaseB (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7ecbec82-71ec-4df3-b298-837e7f37aa4b", + "params": { + "currentPowerPhaseB": "" + } + } + } + +- currentPowerPhaseC → actionTypeId: `eded281b-217b-48f5-8b91-b5142ea87505` + - Parameters: + - currentPowerPhaseC (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "eded281b-217b-48f5-8b91-b5142ea87505", + "params": { + "currentPowerPhaseC": "" + } + } + } + +- voltagePhaseA → actionTypeId: `03deaf24-e515-4649-8979-ef8ae8ee7c02` + - Parameters: + - voltagePhaseA (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "03deaf24-e515-4649-8979-ef8ae8ee7c02", + "params": { + "voltagePhaseA": "" + } + } + } + +- voltagePhaseB → actionTypeId: `7e1514fb-7e69-4849-90c4-8f27dbd6453a` + - Parameters: + - voltagePhaseB (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7e1514fb-7e69-4849-90c4-8f27dbd6453a", + "params": { + "voltagePhaseB": "" + } + } + } + +- voltagePhaseC → actionTypeId: `b6e69a07-a362-4eef-87b7-5a670e332919` + - Parameters: + - voltagePhaseC (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b6e69a07-a362-4eef-87b7-5a670e332919", + "params": { + "voltagePhaseC": "" + } + } + } + + +--- + +### car + +- ThingClassId: `dbe0a9ff-94ba-4a94-ae52-51da3f05c717` + +#### States + +- capacity (Double) → stateTypeId: `363a2a39-61b6-4109-9cd9-aca7367d12c7` +- batteryCritical (Bool) → stateTypeId: `d33982d6-6467-4281-b448-68c1b1db088d` +- batteryLevel (Int) → stateTypeId: `3f1cca10-8988-4ec6-b937-0775653cde12` +- minChargingCurrent (Uint) → stateTypeId: `20faf2b8-2b40-4bee-b228-97dbaf0cdffc` +- maxChargingCurrent (Uint) → stateTypeId: `ad17e812-ba07-47a8-99a4-eefb8b54fc85` +- phaseCount (Uint) → stateTypeId: `c886c062-bf1a-46c5-8e54-aed804a22d57` + +#### Actions + +- batteryLevel → actionTypeId: `3f1cca10-8988-4ec6-b937-0775653cde12` + - Parameters: + - batteryLevel (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "3f1cca10-8988-4ec6-b937-0775653cde12", + "params": { + "batteryLevel": "" + } + } + } + + +--- + +### shellyButton1 + +- ThingClassId: `3eba6b29-f634-4ade-80a3-2159803373cc` + +#### States + +- connected (Bool) → stateTypeId: `d23e25a1-f723-4de1-806a-83fb073f01f4` +- signalStrength (Uint) → stateTypeId: `fff3aa51-ec42-40c7-b603-cbd2d58d781e` +- batteryLevel (Int) → stateTypeId: `338355e5-9506-48b1-be86-757d69b34755` +- batteryCritical (Bool) → stateTypeId: `18edddee-1b30-48e4-b233-1e3b68bd6ff1` +- updateStatus (String) → stateTypeId: `aa3cbd93-192a-4035-8f46-c5ff68fe331b` +- currentVersion (String) → stateTypeId: `b17a7df2-952b-4cdd-8d28-a8e8582b49d4` +- availableVersion (String) → stateTypeId: `46f33cf8-82bf-4798-9100-69f54aabd9e0` + +#### Actions + +- performUpdate → actionTypeId: `87b24064-5db7-4590-a9d8-f6d8fd02ed6e` + +--- + +### kasaPlug100 + +- ThingClassId: `48f981ba-3836-4810-9744-a349088f4545` + +#### States + +- connected (Bool) → stateTypeId: `79bcdd81-2e9d-4312-a4a7-85f808d046ef` +- signalStrength (Uint) → stateTypeId: `1de7fac2-87f1-4c2b-9286-1b0895c5ac49` +- power (Bool) → stateTypeId: `a8991ea0-89f0-477b-97fb-414dd7d185b3` + +#### Actions + +- power → actionTypeId: `a8991ea0-89f0-477b-97fb-414dd7d185b3` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a8991ea0-89f0-477b-97fb-414dd7d185b3", + "params": { + "power": "" + } + } + } + + +--- + +### doorSensor + +- ThingClassId: `2d936f56-48ee-4be1-9b3e-28a335ad8085` + +#### States + +- closed (Bool) → stateTypeId: `7f5fdc2b-7916-4e90-9c24-558162304f71` + +#### Actions + +- closed → actionTypeId: `7f5fdc2b-7916-4e90-9c24-558162304f71` + - Parameters: + - closed (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7f5fdc2b-7916-4e90-9c24-558162304f71", + "params": { + "closed": "" + } + } + } + + +--- + +### colorLight + +- ThingClassId: `0edba26c-96ab-44fb-a6a2-c0574d19630e` + +#### States + +- connected (Bool) → stateTypeId: `19bb8d10-1b28-4ba3-99b7-a634138dcfde` +- power (Bool) → stateTypeId: `90aaffe5-6a76-47d2-a14a-550f60390245` +- colorTemperature (Int) → stateTypeId: `c0f4206f-f219-4f06-93c4-4ca515a56f79` +- color (Color) → stateTypeId: `d25423e7-b924-4b20-80b6-77eecc65d089` +- brightness (Int) → stateTypeId: `90e91f64-a208-468c-a5a2-7f47e08859e2` +- effect (String) → stateTypeId: `0b7cdd8d-4db8-4183-abe2-f3c01d1c9afc` + +#### Actions + +- power → actionTypeId: `90aaffe5-6a76-47d2-a14a-550f60390245` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "90aaffe5-6a76-47d2-a14a-550f60390245", + "params": { + "power": "" + } + } + } + +- colorTemperature → actionTypeId: `c0f4206f-f219-4f06-93c4-4ca515a56f79` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c0f4206f-f219-4f06-93c4-4ca515a56f79", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `d25423e7-b924-4b20-80b6-77eecc65d089` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d25423e7-b924-4b20-80b6-77eecc65d089", + "params": { + "color": "" + } + } + } + +- brightness → actionTypeId: `90e91f64-a208-468c-a5a2-7f47e08859e2` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "90e91f64-a208-468c-a5a2-7f47e08859e2", + "params": { + "brightness": "" + } + } + } + +- effect → actionTypeId: `0b7cdd8d-4db8-4183-abe2-f3c01d1c9afc` + - Parameters: + - effect (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "0b7cdd8d-4db8-4183-abe2-f3c01d1c9afc", + "params": { + "effect": "" + } + } + } + +- alert → actionTypeId: `d25dcfbc-d28c-4905-80e3-300ffb1248f5` + - Parameters: + - alert (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d25dcfbc-d28c-4905-80e3-300ffb1248f5", + "params": { + "alert": "" + } + } + } + + +--- + +### outdoorSensor + +- ThingClassId: `32dc6390-600f-4eb4-b349-cc2d6796a82a` + +#### States + +- connected (Bool) → stateTypeId: `9fe43e6b-3c29-43a9-bb96-3b80eacc10db` +- batteryLevel (Int) → stateTypeId: `19b18531-61e5-4998-89d1-765d740e24eb` +- batteryCritical (Bool) → stateTypeId: `617aa352-789c-46e7-bf55-7455b1e5018e` +- temperature (Double) → stateTypeId: `88f5b708-65bb-41a7-885f-01be46074713` +- lightIntensity (Double) → stateTypeId: `4fb12c06-981c-4c42-b55c-46bdfe68681a` +- isPresent (Bool) → stateTypeId: `680f79cf-c17c-4ffd-96fa-a5b286e2c117` +- lastSeenTime (Int) → stateTypeId: `6fa16fb2-053c-4c3c-a39b-9548c1b15089` + + +--- + +### impulseGaragedooor + +- ThingClassId: `d6699a12-f4a6-4c50-951c-f4f1cd0501dc` + +#### States + +- impulse (Bool) → stateTypeId: `5ccaf0aa-01a4-441f-b6ca-18940da096a5` + +#### Actions + +- triggerImpulse → actionTypeId: `ff5461c6-70fc-435e-8424-96fa59ed321e` + +--- + +### smartMeterConsumer + +- ThingClassId: `c26478b3-dd64-445f-9859-8ef816025cb9` + +#### States + +- currentPower (Double) → stateTypeId: `5a083458-e476-487d-ab8f-75e92cf16489` +- totalEnergyConsumed (Double) → stateTypeId: `ba10ef2c-e41f-4de2-a620-dff2ec5f025a` + +#### Actions + +- currentPower → actionTypeId: `5a083458-e476-487d-ab8f-75e92cf16489` + - Parameters: + - currentPower (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5a083458-e476-487d-ab8f-75e92cf16489", + "params": { + "currentPower": "" + } + } + } + +- totalEnergyConsumed → actionTypeId: `ba10ef2c-e41f-4de2-a620-dff2ec5f025a` + - Parameters: + - totalEnergyConsumed (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ba10ef2c-e41f-4de2-a620-dff2ec5f025a", + "params": { + "totalEnergyConsumed": "" + } + } + } + + +--- + +### gpioInputRpi + +- ThingClassId: `6aff228b-0410-4ef9-9593-51e8639aacea` + +#### States + +- power (Bool) → stateTypeId: `57f1b7cc-26c8-434b-ba04-d3077dc886c8` + + +--- + +### rollerShade + +- ThingClassId: `df32f5d9-25f0-41ff-924a-586ec0e983d6` + +#### States + +- connected (Bool) → stateTypeId: `de078e37-e94c-45c3-a2e9-ed249a4ab92a` +- signalStrength (Uint) → stateTypeId: `4817e1d2-583f-42c4-8b3c-85cfcb9ddb8c` +- moving (Bool) → stateTypeId: `407e7db4-7257-4657-9aa5-04b243f8f601` +- percentage (Int) → stateTypeId: `03bd4eb3-485c-4676-9901-a29dd5a61867` +- batteryLevel (Int) → stateTypeId: `c3e68496-da84-450c-8e1e-e26d9ac6c807` +- batteryCritical (Bool) → stateTypeId: `179a9d7c-ee22-4aaa-98b2-faa3d69058b8` + +#### Actions + +- percentage → actionTypeId: `03bd4eb3-485c-4676-9901-a29dd5a61867` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "03bd4eb3-485c-4676-9901-a29dd5a61867", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `847e271e-428c-45ce-aced-203de9a79662` +- close → actionTypeId: `72d6a77d-1b8b-41f2-bf95-6798106a6b2e` +- stop → actionTypeId: `777dd1ec-06de-49d8-979c-e48737313b6c` + +--- + +### shellyPro3EM + +- ThingClassId: `d7962c7c-82fe-4d2c-8e79-0b9ff2ee5573` + +#### States + +- connected (Bool) → stateTypeId: `a29300d0-cfde-465b-9aca-9f48f15360bc` +- signalStrength (Uint) → stateTypeId: `837618f5-cb59-40c1-a7b7-c036d9d8772f` +- totalEnergyConsumed (Double) → stateTypeId: `c09b5307-b0f8-4cc3-95f2-544513424623` +- totalEnergyProduced (Double) → stateTypeId: `8e6ddedc-7764-470d-b319-36b39c9ff62e` +- currentPower (Double) → stateTypeId: `eec71598-e307-4917-abb3-53a2b8c14625` +- currentPowerPhaseA (Double) → stateTypeId: `a2577f97-70c5-406c-90df-63228c6ce30b` +- currentPhaseA (Double) → stateTypeId: `821e323f-0f51-4a81-a1c4-c31b8f41fa19` +- voltagePhaseA (Double) → stateTypeId: `134e412f-5a2e-4e65-b4f2-e8f0698e7000` +- energyConsumedPhaseA (Double) → stateTypeId: `0cce4d7b-c673-401b-a654-b2e3ab27bd61` +- energyProducedPhaseA (Double) → stateTypeId: `9042012f-64e7-4594-a900-599f635f93b1` +- currentPowerPhaseB (Double) → stateTypeId: `0852caaa-6704-4b03-8ba6-c1460c2f023d` +- currentPhaseB (Double) → stateTypeId: `f1ea6d35-c74a-43e3-8f06-8387a4595b51` +- voltagePhaseB (Double) → stateTypeId: `37bcd9a2-1f98-45e1-83bc-e9bfb40d0353` +- energyConsumedPhaseB (Double) → stateTypeId: `aef8d193-75c6-431f-b013-0a5c08d6225d` +- energyProducedPhaseB (Double) → stateTypeId: `74c234fd-22c9-4eab-ab8f-e0ba17b7403a` +- currentPowerPhaseC (Double) → stateTypeId: `0c615174-562b-428a-9879-687cfeb5a9bd` +- currentPhaseC (Double) → stateTypeId: `b75373ed-acd5-4980-a0f1-565db2f1351c` +- voltagePhaseC (Double) → stateTypeId: `f6d5128a-305f-488b-b586-b52ba163c8a7` +- energyConsumedPhaseC (Double) → stateTypeId: `cf1761b5-09b5-4688-8875-40627fa945bc` +- energyProducedPhaseC (Double) → stateTypeId: `6a30cfcf-601d-4fdc-9465-f58b5d0caab8` +- updateStatus (String) → stateTypeId: `839f3192-1841-4293-b60f-8e7976510d36` +- currentVersion (String) → stateTypeId: `a8c8e190-ab9f-49f7-9690-f12fb49920cb` +- availableVersion (String) → stateTypeId: `2acba99c-9c4a-40bb-850a-036312995e8b` +- power (Bool) → stateTypeId: `f8621a0a-5b03-4a78-be48-934472b82900` + +#### Actions + +- power → actionTypeId: `f8621a0a-5b03-4a78-be48-934472b82900` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f8621a0a-5b03-4a78-be48-934472b82900", + "params": { + "power": "" + } + } + } + +- performUpdate → actionTypeId: `9b268961-5aa4-4a59-a533-aeaf8c376919` +- reset → actionTypeId: `b0043a22-21e4-4dfc-a813-2a11908a6c04` + +--- + +### waterSensor + +- ThingClassId: `20c843dc-b60e-4d06-8f96-be7950a98fc6` + +#### States + +- connected (Bool) → stateTypeId: `6e26a196-6ecd-4971-a36c-0dcbd508e557` +- signalStrength (Uint) → stateTypeId: `9d92c9c7-901b-4a99-84d0-3d19c8be1635` +- version (String) → stateTypeId: `a59bb290-0c3e-48d8-933b-2f0783cc5615` +- waterDetected (Bool) → stateTypeId: `ff751a4f-ea4e-4706-ad4f-627c327a3d6a` +- temperature (Double) → stateTypeId: `98d4f3a5-539f-4f75-a86e-88d4ab4b026f` +- batteryCritical (Bool) → stateTypeId: `b5eb9897-eabf-455b-9958-91db6b1c4868` +- updateStatus (String) → stateTypeId: `74a82258-5a7c-44f5-af98-3aa3afcbe08e` +- currentVersion (String) → stateTypeId: `16ff5d40-77f0-46fa-bda0-1ef35eae12fc` +- availableVersion (String) → stateTypeId: `a4fdb89d-230f-454b-a4b4-12bc84a19e8c` +- updateProgress (Uint) → stateTypeId: `9dcde658-9048-41da-b4ac-3cbe62a8609e` + +#### Actions + +- performUpdate → actionTypeId: `4d40f75d-fb8a-47af-9a03-98f828765887` + +--- + +### button + +- ThingClassId: `a3a73bf2-ebe4-47fa-8a0a-dc91927c6ce3` + +#### Actions + +- Press → actionTypeId: `3a7bda8e-b92c-454b-8ea2-b798fcec8e4b` + +--- + +### lightPanels + +- ThingClassId: `d44ee383-9fa5-4751-babd-1129ac20896a` + +#### States + +- connected (Bool) → stateTypeId: `a3102107-a825-4ec8-a9ec-b2c2a9fb5c89` +- power (Bool) → stateTypeId: `44bee9ec-513d-4834-991a-ee9ae69d9f2a` +- colorTemperature (Int) → stateTypeId: `41248127-844b-40be-87e6-38aee48b6687` +- color (Color) → stateTypeId: `d4a52cdc-93b2-44fc-a36c-ae65f1d98f2e` +- brightness (Int) → stateTypeId: `4e5d6460-d42e-4b7c-a8f3-6e953451c1ef` +- colorMode (String) → stateTypeId: `bdd2ea1e-9ef9-4967-9678-2c601b826199` +- effectName (String) → stateTypeId: `57f9831e-1b98-41c1-a21c-6073ff327237` + +#### Actions + +- power → actionTypeId: `44bee9ec-513d-4834-991a-ee9ae69d9f2a` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "44bee9ec-513d-4834-991a-ee9ae69d9f2a", + "params": { + "power": "" + } + } + } + +- colorTemperature → actionTypeId: `41248127-844b-40be-87e6-38aee48b6687` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "41248127-844b-40be-87e6-38aee48b6687", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `d4a52cdc-93b2-44fc-a36c-ae65f1d98f2e` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d4a52cdc-93b2-44fc-a36c-ae65f1d98f2e", + "params": { + "color": "" + } + } + } + +- brightness → actionTypeId: `4e5d6460-d42e-4b7c-a8f3-6e953451c1ef` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4e5d6460-d42e-4b7c-a8f3-6e953451c1ef", + "params": { + "brightness": "" + } + } + } + +- alert → actionTypeId: `47a6a1a1-fb90-4f24-be8c-b4dba0aaaa84` + +--- + +### indoorModule + +- ThingClassId: `a6706b11-325b-4be3-b0d1-c09e027955b4` + +#### States + +- updateTime (Int) → stateTypeId: `024eb4d2-ed0b-428b-b5c4-ce9aba1fee1e` +- temperature (Double) → stateTypeId: `33957a0c-75b7-45d0-8e07-731c7c20df0e` +- temperatureMin (Double) → stateTypeId: `2f9e4042-b699-4036-be69-a84e561ac38c` +- temperatureMax (Double) → stateTypeId: `304472e3-56fc-4ee6-96df-aeb4b570a1d1` +- humidity (Double) → stateTypeId: `e39c6ea6-8d53-42c7-bfce-e9e4aa3dfec9` +- co2 (Double) → stateTypeId: `a3fe53a3-0940-4fcb-915d-0297ae584917` +- pressure (Double) → stateTypeId: `9d2c409e-0d4f-4e03-89ad-fe0cad776e66` +- signalStrength (Uint) → stateTypeId: `36fd51d9-7693-42ee-b0ae-b623e219d950` +- connected (Bool) → stateTypeId: `5b819dbf-698d-49ae-aeb5-a15db9ef515a` +- batteryLevel (Int) → stateTypeId: `d9fe5e52-8f42-41fd-aa5f-fd157ad6a6be` +- batteryCritical (Bool) → stateTypeId: `7cde2c79-2b90-4ced-97ac-2653d314cf73` + + +--- + +### heosPlayer + +- ThingClassId: `fce5247f-4c6d-408f-ac62-e5973dc6adfa` + +#### States + +- connected (Bool) → stateTypeId: `9a4e527e-057c-4b19-8a02-605cc8349f5e` +- mute (Bool) → stateTypeId: `fcc89c7c-b793-4b6f-a3dc-0e0e3a86748f` +- volume (Int) → stateTypeId: `6d4886a1-fa5d-4889-96c5-7a1c206f59be` +- playbackStatus (String) → stateTypeId: `6db3b484-4cd4-477b-b822-275865d308db` +- shuffle (Bool) → stateTypeId: `4b581237-acf5-4d8f-9e83-9b24e9ac900a` +- repeat (String) → stateTypeId: `4e60cd17-5845-4351-aa2c-2504610e1532` +- source (String) → stateTypeId: `eee22722-3ee5-48f7-8af8-275dc04b21eb` +- artist (String) → stateTypeId: `0a9183a4-b633-4773-ba7a-f4266895157e` +- collection (String) → stateTypeId: `9cd60864-f141-4e03-a85b-357690cad1b8` +- title (String) → stateTypeId: `bbeecf30-6feb-48d5-ade3-57b2a4eea05f` +- artwork (String) → stateTypeId: `a7f0ba95-383a-4efd-adc5-a36e50a04018` +- playerType (String) → stateTypeId: `c59835ac-ee6e-4e6c-aa20-aeb3501937c5` + +#### Actions + +- mute → actionTypeId: `fcc89c7c-b793-4b6f-a3dc-0e0e3a86748f` + - Parameters: + - mute (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "fcc89c7c-b793-4b6f-a3dc-0e0e3a86748f", + "params": { + "mute": "" + } + } + } + +- volume → actionTypeId: `6d4886a1-fa5d-4889-96c5-7a1c206f59be` + - Parameters: + - volume (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "6d4886a1-fa5d-4889-96c5-7a1c206f59be", + "params": { + "volume": "" + } + } + } + +- playbackStatus → actionTypeId: `6db3b484-4cd4-477b-b822-275865d308db` + - Parameters: + - playbackStatus (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "6db3b484-4cd4-477b-b822-275865d308db", + "params": { + "playbackStatus": "" + } + } + } + +- shuffle → actionTypeId: `4b581237-acf5-4d8f-9e83-9b24e9ac900a` + - Parameters: + - shuffle (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4b581237-acf5-4d8f-9e83-9b24e9ac900a", + "params": { + "shuffle": "" + } + } + } + +- repeat → actionTypeId: `4e60cd17-5845-4351-aa2c-2504610e1532` + - Parameters: + - repeat (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4e60cd17-5845-4351-aa2c-2504610e1532", + "params": { + "repeat": "" + } + } + } + +- alert → actionTypeId: `db8e001a-fcae-4c41-9811-325e14c06109` +- skipBack → actionTypeId: `a718f7e9-0b54-4403-b661-49f7b0d13085` +- stop → actionTypeId: `c4b29c09-e3b3-4843-b6d9-e032f3fc1d78` +- play → actionTypeId: `c64964e4-cea0-468a-a9bf-8f69657b74e9` +- pause → actionTypeId: `21c1cbe6-278f-4688-a65f-6620be1ee5ea` +- skipNext → actionTypeId: `57697e9c-ce5e-4b8f-b42e-16662829ceb2` +- increaseVolume → actionTypeId: `ebdc35f8-24a2-4984-855e-4c1a158289b7` + - Parameters: + - step (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ebdc35f8-24a2-4984-855e-4c1a158289b7", + "params": { + "step": "" + } + } + } + +- decreaseVolume → actionTypeId: `e19cc4e0-00ef-4df4-8314-85902017d095` + - Parameters: + - step (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e19cc4e0-00ef-4df4-8314-85902017d095", + "params": { + "step": "" + } + } + } + + +--- + +### vibrationSensor + +- ThingClassId: `6fb1f16f-ffb2-43e7-92ee-20b1f8137e17` + +#### States + +- connected (Bool) → stateTypeId: `9ecf841f-e542-4f24-b135-458683a43e92` +- signalStrength (Uint) → stateTypeId: `b21adc6e-3c6e-4793-b49c-326e220b6059` +- batteryLevel (Int) → stateTypeId: `1990fbc3-c122-424d-96f3-187867d75b82` +- batteryCritical (Bool) → stateTypeId: `ab8d043e-88d7-47e3-b157-a52543ae542a` + + +--- + +### motionSensor + +- ThingClassId: `6cbf86ea-0d31-4ce7-b0fd-02ff707d3553` + +#### States + +- connected (Bool) → stateTypeId: `1c109b37-f002-4fb4-8667-2ee62bba2626` +- signalStrength (Uint) → stateTypeId: `bbd0eef3-b2cd-4fcf-bd2a-e76295d0b232` +- version (String) → stateTypeId: `1060399a-3e15-44ce-8bdc-4b7fd6f1e194` +- tampered (Bool) → stateTypeId: `5ed06626-db36-4520-8c09-b0224df570f8` +- isPresent (Bool) → stateTypeId: `80bb5618-2115-4909-a6ea-be0ccdaeffbd` +- temperature (Double) → stateTypeId: `d7d096fe-8937-4fcf-a54c-8eb4a9aa830b` +- lightIntensity (Double) → stateTypeId: `5368431e-a622-4d36-b1c0-61a456744ea8` +- batteryCritical (Bool) → stateTypeId: `0a9d580f-70e6-42f9-9d77-d7aeb245ee84` +- updateStatus (String) → stateTypeId: `5ccd02af-b646-4dcf-867c-867b0d5011f9` +- currentVersion (String) → stateTypeId: `0e864bc8-45d0-4cf3-9ad9-cee15a4e2311` +- availableVersion (String) → stateTypeId: `187dd962-4f44-4d1b-9b34-7e491c71d951` +- updateProgress (Uint) → stateTypeId: `0736ac21-0f21-4d50-ade5-90cd18f64a1c` + +#### Actions + +- performUpdate → actionTypeId: `915dccbb-4220-45d0-b074-9446079b47e6` + +--- + +### speedwireBattery + +- ThingClassId: `b459dad2-f78b-4a87-a7f3-22f3147b83d8` + +#### States + +- connected (Bool) → stateTypeId: `7f242169-c01a-4c9a-ac71-4f9fa5409875` +- voltage (Double) → stateTypeId: `d2144cad-e507-433b-a9d3-2ab9cf0c1014` +- current (Double) → stateTypeId: `541c110d-2f56-44bb-8f7e-de55759b942d` +- temperature (Double) → stateTypeId: `6a146a40-84da-4392-8466-4176b21280d2` +- currentPower (Double) → stateTypeId: `f0f69109-83a4-4b2a-9e16-66aa33c2e169` +- capacity (Double) → stateTypeId: `38a413cd-3d09-482d-8d25-b602db3b6540` +- batteryCritical (Bool) → stateTypeId: `d815aedf-e836-4274-9b51-2f0128420c46` +- batteryLevel (Int) → stateTypeId: `ec534954-8ee4-46f4-94b6-b48b375b1d7d` +- chargingState (String) → stateTypeId: `93310fa3-8237-423b-9062-62e0626e8c70` + + +--- + +### shellyPlug + +- ThingClassId: `22229a6d-2af8-44e0-bea9-310a0f2769ef` + +#### States + +- connected (Bool) → stateTypeId: `584b3558-5fb5-40a9-81ad-dc71ba68fd45` +- signalStrength (Uint) → stateTypeId: `0b0a73a5-d732-47eb-a075-8e22810eea55` +- power (Bool) → stateTypeId: `d813b35f-e11e-4783-b3b3-dbecb956ffb5` +- totalEnergyConsumed (Double) → stateTypeId: `962fec29-6be0-452e-87c5-5ff71435c40f` +- currentPower (Double) → stateTypeId: `202ea409-650e-48b2-9aae-d4ebe9d505fd` +- updateStatus (String) → stateTypeId: `ccec3806-cc48-42cf-94d7-811ff569d407` +- currentVersion (String) → stateTypeId: `5b286e92-ec05-4a70-8813-646b63995213` +- availableVersion (String) → stateTypeId: `ccf9d5e9-e89d-4dda-ae99-2f3e9c4e7948` + +#### Actions + +- power → actionTypeId: `d813b35f-e11e-4783-b3b3-dbecb956ffb5` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d813b35f-e11e-4783-b3b3-dbecb956ffb5", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `7dc03565-d7cb-4add-ba08-b751ce0821d5` +- performUpdate → actionTypeId: `140026a6-2d16-4a0c-9ba9-dea0740584fd` + +--- + +### spirit + +- ThingClassId: `57ce4f62-d5a3-4e79-8352-96942c6e4425` + +#### States + +- targetTemperature (Double) → stateTypeId: `20e9006f-09ec-43ea-a249-46e4894698d2` +- temperature (Double) → stateTypeId: `68230bfe-ed04-49d6-b2ed-731fdfc35df2` +- heatingOn (Bool) → stateTypeId: `e52ad2fd-ef3e-4e99-8854-4db07281e658` +- connected (Bool) → stateTypeId: `ebd54153-68ec-4e3c-a370-5b0dca35741b` +- signalStrength (Uint) → stateTypeId: `3bd33c04-37ca-4c26-b583-b6caf58b443d` +- batteryLevel (Int) → stateTypeId: `a6129509-e3dd-448f-8964-1817ca4c3888` +- batteryCritical (Bool) → stateTypeId: `78e2e1b5-0de6-49f7-9b90-63d6922234af` +- boost (Bool) → stateTypeId: `daeb8f1e-4637-406c-8b09-e441f2f53c9c` +- windowOpen (Bool) → stateTypeId: `ddde7d49-19d4-4196-b09f-f614e04220b2` +- mirrorDisplay (Bool) → stateTypeId: `89cdbb14-d4b1-444a-a193-e760d6b6bd87` +- childLock (Bool) → stateTypeId: `e31bb876-cc60-4127-bb6b-7d3a4b07fa11` +- updateStatus (String) → stateTypeId: `89c21b2c-0007-4da6-9bbf-4170b716858d` +- currentVersion (String) → stateTypeId: `9a3987df-87d5-4b97-9f31-1e0b0a6468ad` + +#### Actions + +- targetTemperature → actionTypeId: `20e9006f-09ec-43ea-a249-46e4894698d2` + - Parameters: + - targetTemperature (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "20e9006f-09ec-43ea-a249-46e4894698d2", + "params": { + "targetTemperature": "" + } + } + } + +- boost → actionTypeId: `daeb8f1e-4637-406c-8b09-e441f2f53c9c` + - Parameters: + - boost (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "daeb8f1e-4637-406c-8b09-e441f2f53c9c", + "params": { + "boost": "" + } + } + } + +- windowOpen → actionTypeId: `ddde7d49-19d4-4196-b09f-f614e04220b2` + - Parameters: + - windowOpen (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ddde7d49-19d4-4196-b09f-f614e04220b2", + "params": { + "windowOpen": "" + } + } + } + +- mirrorDisplay → actionTypeId: `89cdbb14-d4b1-444a-a193-e760d6b6bd87` + - Parameters: + - mirrorDisplay (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "89cdbb14-d4b1-444a-a193-e760d6b6bd87", + "params": { + "mirrorDisplay": "" + } + } + } + +- childLock → actionTypeId: `e31bb876-cc60-4127-bb6b-7d3a4b07fa11` + - Parameters: + - childLock (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e31bb876-cc60-4127-bb6b-7d3a4b07fa11", + "params": { + "childLock": "" + } + } + } + +- performUpdate → actionTypeId: `9dfcb8ce-298f-45c0-9abf-714c9ece9357` + +--- + +### speedwireMeter + +- ThingClassId: `0c5097af-e136-4430-9fb4-0ccbb30c3e1c` + +#### States + +- connected (Bool) → stateTypeId: `35733d27-4fe0-439a-be71-7c1597481659` +- voltagePhaseA (Double) → stateTypeId: `44ee2491-8376-41cd-a21d-185c736152ec` +- voltagePhaseB (Double) → stateTypeId: `56ae3555-f874-4c2d-8833-17573dce477a` +- voltagePhaseC (Double) → stateTypeId: `51cbb29b-29f0-480a-9d7d-b8f4e6a205ae` +- currentPhaseA (Double) → stateTypeId: `45bbdbef-1832-4870-bff5-299e580fb4da` +- currentPhaseB (Double) → stateTypeId: `b3a4fdd2-b6b8-4c58-9da3-2084ad414022` +- currentPhaseC (Double) → stateTypeId: `b3655188-3854-4336-ae3c-61d3bda6fc4d` +- currentPower (Double) → stateTypeId: `d4ac7f37-e30a-44e4-93cb-ad16df18b8f1` +- currentPowerPhaseA (Double) → stateTypeId: `c5d09c63-7461-4fb8-a6fe-bc7aa919be30` +- currentPowerPhaseB (Double) → stateTypeId: `c52d4422-b521-4804-a7a7-c4398e91e760` +- currentPowerPhaseC (Double) → stateTypeId: `555e892c-3ca7-4100-9832-6ac13b87eb04` +- totalEnergyConsumed (Double) → stateTypeId: `4fb0a4c1-18ed-4d02-b6d0-c07e9b96a56d` +- totalEnergyProduced (Double) → stateTypeId: `76ca68d8-6781-4d2a-8663-440aec40b4de` +- energyConsumedPhaseA (Double) → stateTypeId: `b4ff2c71-f81d-4904-bbac-0c0c6e8a5a33` +- energyConsumedPhaseB (Double) → stateTypeId: `c4e5f569-ac5d-4761-a898-888880bfd59f` +- energyConsumedPhaseC (Double) → stateTypeId: `aabc02d7-8dc3-4637-8bf2-dc2e0e737ad3` +- energyProducedPhaseA (Double) → stateTypeId: `754c3b67-768a-47f7-99d8-f66c198f0835` +- energyProducedPhaseB (Double) → stateTypeId: `7eb08c45-24cf-40ce-be28-f3564f087672` +- energyProducedPhaseC (Double) → stateTypeId: `1eb2bf01-5ec6-42e5-b348-ac1e95199d14` +- firmwareVersion (String) → stateTypeId: `a685393c-8b7e-42c5-bb41-f9907c074626` + + +--- + +### waterSensor + +- ThingClassId: `22a389b6-1b2b-4b88-a47a-d9fd16c8ab2c` + +#### States + +- waterDetected (Bool) → stateTypeId: `d5657846-3f19-4e94-841b-12681bbe4f8e` +- batteryCritical (Bool) → stateTypeId: `75ce3761-24fa-48e1-9a86-94d67c80b7e8` +- connected (Bool) → stateTypeId: `c677bdd4-e319-4c7b-ba76-3851a69ad3df` +- signalStrength (Uint) → stateTypeId: `48d7361e-c99a-4eab-8143-dfccfd4a16da` +- version (String) → stateTypeId: `326dc29b-355d-4d31-bf2f-8b570c342af1` + + +--- + +### kasaPlug110 + +- ThingClassId: `32830124-9efb-4614-8227-ee269b1889b0` + +#### States + +- connected (Bool) → stateTypeId: `b66825ec-9f1b-48da-af18-f36913291c0e` +- signalStrength (Uint) → stateTypeId: `cb16cfb0-6608-486f-abe5-264aa652704d` +- power (Bool) → stateTypeId: `f1a5fda4-87a6-46f6-9499-16811a5f4f4d` +- totalEnergyConsumed (Double) → stateTypeId: `a3533121-69ee-44fd-8394-13373e8f960e` +- currentPower (Double) → stateTypeId: `ccb52b57-5800-4f03-b7fa-f36dcebe1d4e` + +#### Actions + +- power → actionTypeId: `f1a5fda4-87a6-46f6-9499-16811a5f4f4d` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f1a5fda4-87a6-46f6-9499-16811a5f4f4d", + "params": { + "power": "" + } + } + } + + +--- + +### flsAirlink4 + +- ThingClassId: `38693f57-004b-4d0b-aea3-df1ba7ca244e` + +#### States + +- connected (Bool) → stateTypeId: `07cf64a6-f0b4-4c7b-ac26-9388900e6cdc` +- signalStrength (Uint) → stateTypeId: `40313883-bb05-40a6-b98e-6d31f89352db` +- batteryLevel (Int) → stateTypeId: `21b3af55-a149-4844-94f2-49f7f63336c4` +- batteryCritical (Bool) → stateTypeId: `352e2b5b-e9d9-4df5-a482-c771966bb0f5` + + +--- + +### shellyPro1Pm + +- ThingClassId: `d21a9182-2f5b-47c1-9a27-6dd8ebd50b5f` + +#### States + +- power (Bool) → stateTypeId: `9942b350-9033-4491-8a99-538c042a9251` +- connected (Bool) → stateTypeId: `4913d3fd-f6b4-4a23-b495-ae83df8473c8` +- signalStrength (Uint) → stateTypeId: `903a1744-5551-4298-bacd-13066acc16a9` +- updateStatus (String) → stateTypeId: `93ab8203-cc26-4ae9-a61e-d8f0182221b0` +- currentVersion (String) → stateTypeId: `1dd09d4c-33e4-4496-8dc9-27b62a4092b0` +- availableVersion (String) → stateTypeId: `07ca990a-96c7-4213-9af6-d69cb946cc46` +- totalEnergyConsumed (Double) → stateTypeId: `be8ca18d-7417-4e5b-a2b2-2d1c87a5ed34` +- currentPower (Double) → stateTypeId: `d5fd6531-defb-4dac-9025-62a35a20e2c6` + +#### Actions + +- power → actionTypeId: `9942b350-9033-4491-8a99-538c042a9251` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9942b350-9033-4491-8a99-538c042a9251", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `faae305a-fa7d-46b9-9ef8-6a7f12455380` +- performUpdate → actionTypeId: `89504849-9284-466d-b9d5-498e5f193c50` + +--- + +### dimmableLight + +- ThingClassId: `4fa568ef-7a3a-422b-b0c0-206d37cb4eed` + +#### States + +- connected (Bool) → stateTypeId: `57f80677-745e-4cb1-b894-87d8542a7e8c` +- power (Bool) → stateTypeId: `5995ecb7-b5e5-4f6a-b4d6-33c93497e5fb` +- brightness (Int) → stateTypeId: `2f062912-1159-423b-8143-48a8e69b9348` + +#### Actions + +- power → actionTypeId: `5995ecb7-b5e5-4f6a-b4d6-33c93497e5fb` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5995ecb7-b5e5-4f6a-b4d6-33c93497e5fb", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `2f062912-1159-423b-8143-48a8e69b9348` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2f062912-1159-423b-8143-48a8e69b9348", + "params": { + "brightness": "" + } + } + } + +- alert → actionTypeId: `ab30a83a-539e-4b3a-860a-434e87ca165f` + - Parameters: + - alert (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ab30a83a-539e-4b3a-860a-434e87ca165f", + "params": { + "alert": "" + } + } + } + + +--- + +### airPurifier + +- ThingClassId: `1580b37e-9f4f-474a-9a38-a40060f93861` + +#### States + +- connected (Bool) → stateTypeId: `31565f01-0287-4f82-9dbf-894c20e06f69` +- signalStrength (Uint) → stateTypeId: `ad88fa3d-eebf-42e2-98f4-e58281da03e0` +- power (Bool) → stateTypeId: `7bcce1da-fc9c-4972-8df2-f17baee95023` +- auto (Bool) → stateTypeId: `15ae0037-e85a-406f-878a-907bcf5bea63` +- flowRate (Int) → stateTypeId: `7ad8ce8c-36ab-41b8-8768-4f765a1fa291` +- filterRuntime (Uint) → stateTypeId: `7b1a33b8-6f81-46c2-b1f1-a1a46fffd990` +- replaceFilter (Bool) → stateTypeId: `d01b170d-8985-4b33-b907-1c7e0a35bff6` +- lightPower (Bool) → stateTypeId: `c71f3fb0-7cd1-4868-846d-a261c24627c2` +- pm25 (Double) → stateTypeId: `a71956ec-af44-4dab-86b1-dd7a2baece1c` +- childLock (Bool) → stateTypeId: `d8e6ff6d-5b0e-4071-a749-1e2f19175e4d` +- currentVersion (String) → stateTypeId: `d7bea835-e91b-4a04-826a-aaacf7233faa` +- updateStatus (String) → stateTypeId: `5636514d-40a9-464e-bdfb-5877b22b3e69` +- availableVersion (String) → stateTypeId: `676491ec-2c20-4a05-a75d-c515f4f4bd18` +- updateProgress (Uint) → stateTypeId: `acbac78e-d65e-4698-bed7-9b15057cbf44` + +#### Actions + +- power → actionTypeId: `7bcce1da-fc9c-4972-8df2-f17baee95023` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7bcce1da-fc9c-4972-8df2-f17baee95023", + "params": { + "power": "" + } + } + } + +- auto → actionTypeId: `15ae0037-e85a-406f-878a-907bcf5bea63` + - Parameters: + - auto (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "15ae0037-e85a-406f-878a-907bcf5bea63", + "params": { + "auto": "" + } + } + } + +- flowRate → actionTypeId: `7ad8ce8c-36ab-41b8-8768-4f765a1fa291` + - Parameters: + - flowRate (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7ad8ce8c-36ab-41b8-8768-4f765a1fa291", + "params": { + "flowRate": "" + } + } + } + +- lightPower → actionTypeId: `c71f3fb0-7cd1-4868-846d-a261c24627c2` + - Parameters: + - lightPower (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c71f3fb0-7cd1-4868-846d-a261c24627c2", + "params": { + "lightPower": "" + } + } + } + +- childLock → actionTypeId: `d8e6ff6d-5b0e-4071-a749-1e2f19175e4d` + - Parameters: + - childLock (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d8e6ff6d-5b0e-4071-a749-1e2f19175e4d", + "params": { + "childLock": "" + } + } + } + +- performUpdate → actionTypeId: `5f195fde-2b76-4789-9c7f-8f335a5b450a` + +--- + +### insta + +- ThingClassId: `f81ad8c0-29e3-4284-ad3a-393e11c2cc37` + +#### States + +- connected (Bool) → stateTypeId: `bf6d6426-a967-4084-86bf-4edd2d074316` +- signalStrength (Uint) → stateTypeId: `3859cc17-b534-4e00-be3b-6fbdb67ed0fe` +- updateStatus (String) → stateTypeId: `50196581-9dfe-4371-bb97-ba6498ca4ffc` +- currentVersion (String) → stateTypeId: `6e5285d5-1a80-47b8-a9b5-e951a4236477` +- availableVersion (String) → stateTypeId: `81d7f41e-e09d-40cc-8a18-6f19c0877f5b` +- updateProgress (Uint) → stateTypeId: `a39bef41-5aae-4bdf-8e47-23417b214bcf` + +#### Actions + +- performUpdate → actionTypeId: `299bfa23-da89-495d-85b0-d8233481ced3` + +--- + +### lumiRemote + +- ThingClassId: `f8aa2ba1-ce74-4691-b97d-85427f4cf8bd` + +#### States + +- connected (Bool) → stateTypeId: `d1afa3ba-f3bd-47e5-90fa-9d80dcce91d8` +- signalStrength (Uint) → stateTypeId: `a764dbfb-7b86-439b-94ea-aec742dcb13e` +- version (String) → stateTypeId: `8164c1d0-657e-4347-99fb-f3fdc07af6de` + + +--- + +### phSensor + +- ThingClassId: `e2934fa1-59c3-4de8-82f2-66181136085c` + +#### States + +- ph (Double) → stateTypeId: `743514b1-8018-4525-b87a-c5f414146ff2` +- input (Double) → stateTypeId: `614a039b-9ed9-4b62-9c74-01ba4ac42f16` + +#### Actions + +- ph → actionTypeId: `743514b1-8018-4525-b87a-c5f414146ff2` + - Parameters: + - ph (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "743514b1-8018-4525-b87a-c5f414146ff2", + "params": { + "ph": "" + } + } + } + +- input → actionTypeId: `614a039b-9ed9-4b62-9c74-01ba4ac42f16` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "614a039b-9ed9-4b62-9c74-01ba4ac42f16", + "params": { + "input": "" + } + } + } + + +--- + +### motionSensor + +- ThingClassId: `031c45d1-e782-4f91-9f05-259d6b81c7ef` + +#### States + +- connected (Bool) → stateTypeId: `0d0a5ee1-41f2-4b5a-a3c0-7c8cca1c064d` +- signalStrength (Uint) → stateTypeId: `d4f85c34-896a-4bab-af71-8b80d9889815` +- batteryLevel (Int) → stateTypeId: `00a4450b-d2fe-4e2b-92c8-1ab3a98d1628` +- batteryCritical (Bool) → stateTypeId: `9341f65a-b0aa-4648-82eb-e8400779f817` +- isPresent (Bool) → stateTypeId: `66e3b840-4f4c-4f4a-a71a-69fb751f2a3b` +- lastSeenTime (Int) → stateTypeId: `128777a9-75e7-4cc6-b196-691d2ffddbc9` +- lightIntensity (Double) → stateTypeId: `8077e335-1d18-4e3f-9b5e-6f71af4a33b1` +- temperature (Double) → stateTypeId: `978f069c-57c0-4e73-a81b-c593bc2e7624` +- updateStatus (String) → stateTypeId: `0bf7e6dd-4fa6-45da-a404-c35af88fe74c` +- currentVersion (String) → stateTypeId: `d79ca86e-124a-4d0b-b1fc-c032b6383b87` +- availableVersion (String) → stateTypeId: `6d205b65-a53c-4cdd-9a7c-2e02fe8c0a16` +- updateProgress (Uint) → stateTypeId: `feeb0630-dcfb-4b41-a269-eedf2936c500` + +#### Actions + +- performUpdate → actionTypeId: `01b2e79f-ede7-49c1-8c38-3923513c1329` + +--- + +### kodi + +- ThingClassId: `d09953e3-c5bd-415b-973b-0d0bf2be3f69` + +#### States + +- connected (Bool) → stateTypeId: `09dfbd40-c97c-4a20-9ecd-f80e389a4864` +- mute (Bool) → stateTypeId: `bc98cdb0-4d0e-48ca-afc7-922e49bb7813` +- volume (Int) → stateTypeId: `9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9` +- playbackStatus (String) → stateTypeId: `2dd512b7-40c2-488e-8d4f-6519edaa6f74` +- playerType (String) → stateTypeId: `0af58b87-4e45-4f0a-9ef2-0ade74c7c22c` +- title (String) → stateTypeId: `f2209fec-cceb-46ad-8189-4caf42166e6b` +- artist (String) → stateTypeId: `8cb920a3-3bf1-4231-92d4-8ac27e7b3d65` +- collection (String) → stateTypeId: `ce399eec-9f6a-4903-9916-0e90e38b255e` +- artwork (String) → stateTypeId: `44304c82-c2f6-433b-b62b-815382617d0b` +- shuffle (Bool) → stateTypeId: `5913aa2a-629d-4de5-bf44-a4a1f130c118` +- repeat (String) → stateTypeId: `bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e` + +#### Actions + +- mute → actionTypeId: `bc98cdb0-4d0e-48ca-afc7-922e49bb7813` + - Parameters: + - mute (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bc98cdb0-4d0e-48ca-afc7-922e49bb7813", + "params": { + "mute": "" + } + } + } + +- volume → actionTypeId: `9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9` + - Parameters: + - volume (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9", + "params": { + "volume": "" + } + } + } + +- playbackStatus → actionTypeId: `2dd512b7-40c2-488e-8d4f-6519edaa6f74` + - Parameters: + - playbackStatus (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2dd512b7-40c2-488e-8d4f-6519edaa6f74", + "params": { + "playbackStatus": "" + } + } + } + +- shuffle → actionTypeId: `5913aa2a-629d-4de5-bf44-a4a1f130c118` + - Parameters: + - shuffle (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5913aa2a-629d-4de5-bf44-a4a1f130c118", + "params": { + "shuffle": "" + } + } + } + +- repeat → actionTypeId: `bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e` + - Parameters: + - repeat (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e", + "params": { + "repeat": "" + } + } + } + +- skipBack → actionTypeId: `a180807d-1265-4831-9d86-a421767418dd` +- fastRewind → actionTypeId: `7e70b47b-7e79-4521-be34-04a3c427e5b1` +- stop → actionTypeId: `ae3cbe03-ee3e-410e-abbd-efabc2402198` +- play → actionTypeId: `4d2ee668-a2e3-4795-8b96-0c800b703b46` +- pause → actionTypeId: `3cf341cb-fe63-40bc-a450-9678d18e91e3` +- fastForward → actionTypeId: `6985250e-1188-4e44-b9dd-93303b5738a1` +- skipNext → actionTypeId: `85d7126a-b123-4a28-aeb4-d84bcfb4d14f` +- increaseVolume → actionTypeId: `3ec57983-01e7-4cc4-b266-8de86dfca82d` +- decreaseVolume → actionTypeId: `54497942-9bf8-492a-8f9c-9820a03e754e` +- notify → actionTypeId: `dc0aa3b5-4eae-4e58-a4ac-d4c124da53f1` + - Parameters: + - title (String) + - body (String) + - type (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "dc0aa3b5-4eae-4e58-a4ac-d4c124da53f1", + "params": { + "title": "", + "body": "", + "type": "" + } + } + } + +- navigate → actionTypeId: `28060803-aa85-44a4-9dec-ee669dfb629f` + - Parameters: + - to (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "28060803-aa85-44a4-9dec-ee669dfb629f", + "params": { + "to": "" + } + } + } + +- system → actionTypeId: `8d51cd64-ecef-44f8-85b0-697bef252ce5` + - Parameters: + - systemCommand (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8d51cd64-ecef-44f8-85b0-697bef252ce5", + "params": { + "systemCommand": "" + } + } + } + + +--- + +### fireSensor + +- ThingClassId: `6b49bd7e-4331-4e1b-bc1b-7860906fda15` + +#### States + +- fireDetected (Bool) → stateTypeId: `d139200e-a6b8-4dcd-a891-35a448461fcb` +- batteryCritical (Bool) → stateTypeId: `6d0a6178-5e86-4bb5-b1a2-929a6d732195` +- connected (Bool) → stateTypeId: `0b393a8a-3d4c-47fe-aeea-c9e201841901` +- signalStrength (Uint) → stateTypeId: `80dfad41-ecdf-4f4d-a63f-77611b107818` +- version (String) → stateTypeId: `7cd6219c-a7af-4789-a174-ce1167d97164` + +#### Actions + +- alarm → actionTypeId: `f19e752b-03d9-4848-9dd1-e92c9c21b749` + - Parameters: + - duration (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f19e752b-03d9-4848-9dd1-e92c9c21b749", + "params": { + "duration": "" + } + } + } + + +--- + +### wallSwitchModule + +- ThingClassId: `1a81dffb-42e9-48f4-8fc5-b5917a7548b4` + +#### States + +- connected (Bool) → stateTypeId: `d36454c9-a90e-415a-870a-5488ba981006` +- signalStrength (Uint) → stateTypeId: `ce55dd06-6d27-49f4-ad40-4a85bc126c2d` +- batteryLevel (Int) → stateTypeId: `6a5f652c-5fa1-4477-a68c-ec1f2a3c1f91` +- batteryCritical (Bool) → stateTypeId: `2e4453a3-b8de-4494-b49e-f09ca855ff77` +- updateStatus (String) → stateTypeId: `cd5f93c5-858c-41e2-b8af-1c23dc968636` +- currentVersion (String) → stateTypeId: `9b00b4fd-700f-4bb4-aa45-c3801cd11557` +- availableVersion (String) → stateTypeId: `6ab24ea7-2af6-4999-8033-9e84d95150c0` +- updateProgress (Uint) → stateTypeId: `454e6350-d58d-4dd0-ae9a-cfae25f5a8b9` + +#### Actions + +- performUpdate → actionTypeId: `a2706922-67da-4b83-8e5e-00627e607290` + +--- + +### powerSocket + +- ThingClassId: `760ecd1b-7365-4142-993b-9e4a3dc6f83f` + +#### States + +- connected (Bool) → stateTypeId: `d4e2983a-f4a3-401f-853e-8b0e2f1d245e` +- signalStrength (Uint) → stateTypeId: `0d200118-f5fc-4c88-851f-6c9440830b21` +- power (Bool) → stateTypeId: `799129a5-8f4f-4836-822f-d2f26465288b` +- currentPower (Double) → stateTypeId: `e98e7f15-31b1-44cd-ab9d-cce14a2d2df9` +- totalEnergyConsumed (Double) → stateTypeId: `dd36b0be-f34f-406b-be27-0fc374b333f9` + +#### Actions + +- power → actionTypeId: `799129a5-8f4f-4836-822f-d2f26465288b` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "799129a5-8f4f-4836-822f-d2f26465288b", + "params": { + "power": "" + } + } + } + + +--- + +### gpioButtonBbb + +- ThingClassId: `4a4b79b9-7e43-4fd7-840f-31108bef0ee2` + + +--- + +### htlcdSensor + +- ThingClassId: `036d0b38-8d5b-497b-8534-9d1dd555c376` + +#### States + +- connected (Bool) → stateTypeId: `69bde74d-4ef5-49f0-958c-0609687883e8` +- signalStrength (Uint) → stateTypeId: `dc4292ba-1d75-40dd-b55a-1e0d9aaf02ef` +- temperature (Double) → stateTypeId: `c7eef19a-d5c8-4f2c-8161-9545eb123ed6` +- humidity (Double) → stateTypeId: `3c256135-07ee-4e77-b3a5-91f05e977798` +- batteryLevel (Int) → stateTypeId: `45260385-cd14-4018-bf6e-d8df88a1b0b4` +- batteryCritical (Bool) → stateTypeId: `72ce81f9-57c8-4b43-afa2-3ecee459c409` + + +--- + +### openweathermap + +- ThingClassId: `985195aa-17ad-4530-88a4-cdd753d747d7` + +#### States + +- weatherCondition (String) → stateTypeId: `f16891f3-1174-44f6-a940-cffc8b64bdc1` +- updateTime (Int) → stateTypeId: `36b2f09b-7d77-4fbc-a68f-23d735dda0b1` +- temperature (Double) → stateTypeId: `6013402f-b5b1-46b3-8490-f0c20d62fe61` +- temperatureMin (Double) → stateTypeId: `14ec2781-cb04-4bbf-b097-7d01ef982630` +- temperatureMax (Double) → stateTypeId: `fefe5563-452f-4833-b5cf-49c3cc67c772` +- humidity (Int) → stateTypeId: `6f32ec73-3240-4630-ada9-1c10b8e98123` +- pressure (Double) → stateTypeId: `4a42eea9-00eb-440b-915e-dbe42180f83b` +- windSpeed (Double) → stateTypeId: `2bf63430-e9e2-4fbf-88e6-6f1b4770f287` +- windDirection (Int) → stateTypeId: `589e2ea5-65b2-4afd-9b72-e3708a589a12` +- cloudiness (Int) → stateTypeId: `798553bc-45c7-42eb-9105-430bddb5d9b7` +- visibility (Int) → stateTypeId: `1e10d129-cb88-48b0-9244-e3e7e7b175d9` +- weatherDescription (String) → stateTypeId: `f9539108-0e0e-4736-a306-6408f8e20a26` +- sunsetTime (Int) → stateTypeId: `af155e94-9492-44e1-8608-7d0ee8b5d50d` +- sunriseTime (Int) → stateTypeId: `a1dddc3d-549f-4f20-b78b-be850548f286` +- daylight (Bool) → stateTypeId: `e0a14b66-c8e1-49fb-8eff-b48e5dce80de` + +#### Actions + +- refreshWeather → actionTypeId: `cfbc6504-d86f-4856-8dfa-97b6fbb385e4` + +--- + +### motionSensor + +- ThingClassId: `25b79fff-4b88-4af8-b06c-2fe246238790` + +#### States + +- connected (Bool) → stateTypeId: `19c28b69-a9c2-4908-8255-7681f72c2d92` +- batteryLevel (Int) → stateTypeId: `ac463b30-24af-4352-84da-19a3ffc906bd` +- batteryCritical (Bool) → stateTypeId: `d7c4e143-6f03-411e-a12e-dd22806270fd` +- temperature (Double) → stateTypeId: `63ee79f7-702b-48c1-86cf-8ddebb78bae6` +- lightIntensity (Double) → stateTypeId: `064f48c1-f86d-4a0a-bdae-3420123dff3f` +- isPresent (Bool) → stateTypeId: `e38ee39c-c77f-40b5-b122-4efc411da0ed` +- lastSeenTime (Int) → stateTypeId: `ef2e564e-2443-448f-bcd9-f85a1126ee6a` + + +--- + +### powerSwitch + +- ThingClassId: `57daa147-dd6f-4673-a757-d8f01a2054c7` + +#### States + +- power (Bool) → stateTypeId: `08087af6-6a3b-4e4a-ac6d-56f23ce63edf` + +#### Actions + +- power → actionTypeId: `08087af6-6a3b-4e4a-ac6d-56f23ce63edf` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "08087af6-6a3b-4e4a-ac6d-56f23ce63edf", + "params": { + "power": "" + } + } + } + + +--- + +### lumiMotionSensor + +- ThingClassId: `b5530192-0891-4934-88a2-7338b069be24` + +#### States + +- connected (Bool) → stateTypeId: `007e2fe5-80d5-4f98-941d-b458b555965a` +- signalStrength (Uint) → stateTypeId: `c5913170-e377-484b-a330-c5d5b590fee2` +- version (String) → stateTypeId: `d1337b3b-0dfb-4505-90f1-e5b2b45be9bd` +- isPresent (Bool) → stateTypeId: `fed7582b-c057-42ab-b7c0-d562a2e7917c` +- lastSeenTime (Int) → stateTypeId: `021ce190-a775-40e0-b19b-c6307c73e343` +- lightIntensity (Double) → stateTypeId: `eb3fb43e-a9bb-4498-880b-464cbb752ff6` + + +--- + +### sonoff_tri + +- ThingClassId: `e1073af2-f207-4dc9-8caa-e933421885db` + +#### States + +- connected (Bool) → stateTypeId: `e6439ea4-4373-4ac1-a790-748604cf2830` +- signalStrength (Uint) → stateTypeId: `08b4ec84-1aec-4e68-a3b4-ae251b8cbe6d` +- powerCH1 (Bool) → stateTypeId: `27bdb42d-ef95-4905-ac3d-925bcc8a1ba1` +- powerCH2 (Bool) → stateTypeId: `705d8277-896a-4d56-b9ff-0a614ecfd39c` +- powerCH3 (Bool) → stateTypeId: `156f285d-e474-43d9-9a9d-17b3bcaef893` + +#### Actions + +- powerCH1 → actionTypeId: `27bdb42d-ef95-4905-ac3d-925bcc8a1ba1` + - Parameters: + - powerCH1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "27bdb42d-ef95-4905-ac3d-925bcc8a1ba1", + "params": { + "powerCH1": "" + } + } + } + +- powerCH2 → actionTypeId: `705d8277-896a-4d56-b9ff-0a614ecfd39c` + - Parameters: + - powerCH2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "705d8277-896a-4d56-b9ff-0a614ecfd39c", + "params": { + "powerCH2": "" + } + } + } + +- powerCH3 → actionTypeId: `156f285d-e474-43d9-9a9d-17b3bcaef893` + - Parameters: + - powerCH3 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "156f285d-e474-43d9-9a9d-17b3bcaef893", + "params": { + "powerCH3": "" + } + } + } + + +--- + +### energyStorage + +- ThingClassId: `81418556-cae3-4803-aa0c-9c4b3304a3eb` + +#### States + +- currentPower (Double) → stateTypeId: `8d1c43f9-4528-4797-8f6a-f7b91fbb91d0` +- batteryLevel (Int) → stateTypeId: `d37eed4a-befc-40d4-b435-40a761f99314` +- charging (Bool) → stateTypeId: `a61c0328-b982-46a5-9cc0-b4fa4d0ab84d` +- batteryCritical (Bool) → stateTypeId: `e95dc037-002d-40a2-8c15-cd5489bd354a` +- capacity (Double) → stateTypeId: `bf076970-20fe-46f7-b4bc-fe34ec833829` + +#### Actions + +- currentPower → actionTypeId: `8d1c43f9-4528-4797-8f6a-f7b91fbb91d0` + - Parameters: + - currentPower (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8d1c43f9-4528-4797-8f6a-f7b91fbb91d0", + "params": { + "currentPower": "" + } + } + } + +- batteryLevel → actionTypeId: `d37eed4a-befc-40d4-b435-40a761f99314` + - Parameters: + - batteryLevel (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d37eed4a-befc-40d4-b435-40a761f99314", + "params": { + "batteryLevel": "" + } + } + } + +- charging → actionTypeId: `a61c0328-b982-46a5-9cc0-b4fa4d0ab84d` + - Parameters: + - charging (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a61c0328-b982-46a5-9cc0-b4fa4d0ab84d", + "params": { + "charging": "" + } + } + } + + +--- + +### extendedAwning + +- ThingClassId: `be9ffcc5-28b9-4db9-aaec-d6e758776a60` + +#### States + +- openingOutput (Bool) → stateTypeId: `43c174e2-31d2-4256-a81f-09997ec1c0a3` +- closingOutput (Bool) → stateTypeId: `4992092c-d765-4764-a3d5-658171a74093` +- status (String) → stateTypeId: `c24f6229-91df-4ca5-8555-d6d5d948062c` +- moving (Bool) → stateTypeId: `010d3f47-7c05-4e71-a2f9-3f53958e8095` +- percentage (Int) → stateTypeId: `ce82e5d1-dbb8-4916-a966-a2ab85e4cda7` + +#### Actions + +- openingOutput → actionTypeId: `43c174e2-31d2-4256-a81f-09997ec1c0a3` + - Parameters: + - openingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "43c174e2-31d2-4256-a81f-09997ec1c0a3", + "params": { + "openingOutput": "" + } + } + } + +- closingOutput → actionTypeId: `4992092c-d765-4764-a3d5-658171a74093` + - Parameters: + - closingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4992092c-d765-4764-a3d5-658171a74093", + "params": { + "closingOutput": "" + } + } + } + +- percentage → actionTypeId: `ce82e5d1-dbb8-4916-a966-a2ab85e4cda7` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ce82e5d1-dbb8-4916-a966-a2ab85e4cda7", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `4ee66b18-0c2b-4578-9ae1-34d816e84563` +- stop → actionTypeId: `48ae5686-7bd6-4f10-a6a4-2ac502aafa5f` +- close → actionTypeId: `6e71e4a1-af8b-4a93-a66c-ef1782e03621` + +--- + +### colorBulb + +- ThingClassId: `12907c9c-e7f0-47f2-bd58-39d52ffdf24e` + +#### States + +- connected (Bool) → stateTypeId: `dc4c1640-90f3-4fe0-af9b-db7fa105f18a` +- power (Bool) → stateTypeId: `12de3f8f-2454-4057-aa12-9290296fdbdd` +- colorTemperature (Int) → stateTypeId: `dd7d7e70-5552-4531-8789-2d0f750488be` +- color (Color) → stateTypeId: `a47d8164-5023-4ffb-8298-73293e93e7f6` +- brightness (Int) → stateTypeId: `8bd20350-0e79-45dc-b68a-84da99356863` +- effect (String) → stateTypeId: `65f88396-2958-480e-b0be-c4695400a343` + +#### Actions + +- power → actionTypeId: `12de3f8f-2454-4057-aa12-9290296fdbdd` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "12de3f8f-2454-4057-aa12-9290296fdbdd", + "params": { + "power": "" + } + } + } + +- colorTemperature → actionTypeId: `dd7d7e70-5552-4531-8789-2d0f750488be` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "dd7d7e70-5552-4531-8789-2d0f750488be", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `a47d8164-5023-4ffb-8298-73293e93e7f6` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a47d8164-5023-4ffb-8298-73293e93e7f6", + "params": { + "color": "" + } + } + } + +- brightness → actionTypeId: `8bd20350-0e79-45dc-b68a-84da99356863` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8bd20350-0e79-45dc-b68a-84da99356863", + "params": { + "brightness": "" + } + } + } + +- effect → actionTypeId: `65f88396-2958-480e-b0be-c4695400a343` + - Parameters: + - effect (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "65f88396-2958-480e-b0be-c4695400a343", + "params": { + "effect": "" + } + } + } + + +--- + +### shelly2 + +- ThingClassId: `f277d3a3-62e0-49c1-90b2-4108578e80bf` + +#### States + +- connected (Bool) → stateTypeId: `966ddc70-aa75-4c86-a75e-c1a010698d93` +- signalStrength (Uint) → stateTypeId: `ed8d3275-4d47-406b-9adf-0355a9bff31e` +- updateStatus (String) → stateTypeId: `8f544e5f-f200-47aa-82c4-46aa9838c96e` +- currentVersion (String) → stateTypeId: `b38b19da-4403-40a2-8aa3-81518d092505` +- availableVersion (String) → stateTypeId: `6f081a46-df7d-43df-acb0-a7f226c0fd71` +- channel1 (Bool) → stateTypeId: `e3179799-96ca-47a4-8771-888f523247ac` +- channel2 (Bool) → stateTypeId: `0e50c443-786a-4067-b1df-2b183434a546` + +#### Actions + +- channel1 → actionTypeId: `e3179799-96ca-47a4-8771-888f523247ac` + - Parameters: + - channel1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e3179799-96ca-47a4-8771-888f523247ac", + "params": { + "channel1": "" + } + } + } + +- channel2 → actionTypeId: `0e50c443-786a-4067-b1df-2b183434a546` + - Parameters: + - channel2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "0e50c443-786a-4067-b1df-2b183434a546", + "params": { + "channel2": "" + } + } + } + +- reboot → actionTypeId: `f444ff28-07b1-40fe-b450-c373a6341411` +- performUpdate → actionTypeId: `4ca44813-1ee3-4aee-89c7-a667fe1c8ddc` + +--- + +### countdown + +- ThingClassId: `805c8948-e663-4ba6-aa67-df7446ed7098` + +#### States + +- running (Bool) → stateTypeId: `b21ccd53-1ebd-41a0-a2a3-662874e79837` + +#### Actions + +- start → actionTypeId: `436e9923-6eff-444e-bde7-a61228a4d748` +- stop → actionTypeId: `bf4d1def-4159-4254-a76d-fd31aaa84e2d` +- restart → actionTypeId: `14aad613-b972-411f-93c9-f00bfd254285` + +--- + +### sonoff_dual + +- ThingClassId: `425ab191-833c-4618-8ac8-aff02370b99d` + +#### States + +- connected (Bool) → stateTypeId: `e2f55332-e706-412e-beb6-abf76b3bcff3` +- signalStrength (Uint) → stateTypeId: `3d6a567f-d266-4711-bcf1-3ac1f53abadb` +- powerCH1 (Bool) → stateTypeId: `b8bf3085-8061-4cd6-af1f-f76a03054f46` +- powerCH2 (Bool) → stateTypeId: `4fbcaeb6-9242-4aa6-b462-a214fb38bcc9` + +#### Actions + +- powerCH1 → actionTypeId: `b8bf3085-8061-4cd6-af1f-f76a03054f46` + - Parameters: + - powerCH1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b8bf3085-8061-4cd6-af1f-f76a03054f46", + "params": { + "powerCH1": "" + } + } + } + +- powerCH2 → actionTypeId: `4fbcaeb6-9242-4aa6-b462-a214fb38bcc9` + - Parameters: + - powerCH2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4fbcaeb6-9242-4aa6-b462-a214fb38bcc9", + "params": { + "powerCH2": "" + } + } + } + + +--- + +### moistureSensor + +- ThingClassId: `33e610cf-ff30-481b-9f0b-d6857bcd41a5` + +#### States + +- moisture (Double) → stateTypeId: `7a62e1d2-92f2-424c-876c-870478a4b2bd` +- input (Double) → stateTypeId: `ce64a425-d990-4fc1-966b-be6de445792b` + +#### Actions + +- moisture → actionTypeId: `7a62e1d2-92f2-424c-876c-870478a4b2bd` + - Parameters: + - moisture (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7a62e1d2-92f2-424c-876c-870478a4b2bd", + "params": { + "moisture": "" + } + } + } + +- input → actionTypeId: `ce64a425-d990-4fc1-966b-be6de445792b` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ce64a425-d990-4fc1-966b-be6de445792b", + "params": { + "input": "" + } + } + } + + +--- + +### extendedStatefulGaragedoor + +- ThingClassId: `7341e689-4495-4422-851a-3e7c790394b8` + +#### States + +- state (String) → stateTypeId: `0cc74edb-7116-47cf-953a-409933f26557` +- moving (Bool) → stateTypeId: `963bed3d-2ccb-4dd0-b609-c7e9f25d32d6` +- percentage (Int) → stateTypeId: `f9244c14-0bc9-49ce-90a5-437e66245594` +- openingOutput (Bool) → stateTypeId: `ecc799c7-4d74-4d1f-94e5-2d74e77493ae` +- closingOutput (Bool) → stateTypeId: `e1c14bcd-6131-494b-8dd1-46738e9c8f5e` + +#### Actions + +- percentage → actionTypeId: `f9244c14-0bc9-49ce-90a5-437e66245594` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f9244c14-0bc9-49ce-90a5-437e66245594", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `4a3a3b88-47e9-436f-86be-b5955f3fc2f7` +- close → actionTypeId: `2420fcdb-03d3-4edb-aa89-e3b93c7d6d18` +- stop → actionTypeId: `109c3eaf-26a0-4121-8be2-1363253178fd` + +--- + +### gpioInputBbb + +- ThingClassId: `ffd2aa29-55cc-4824-ba95-c311784f7824` + +#### States + +- power (Bool) → stateTypeId: `22440876-417a-4d57-8e01-efe26ef9f235` + + +--- + +### orpSensor + +- ThingClassId: `3edeec39-ab9d-4697-90cc-0bba089384cf` + +#### States + +- orp (Double) → stateTypeId: `e5ed3535-2e5d-412e-85f8-782cbccd7731` +- input (Double) → stateTypeId: `020ff04f-a157-4925-9c55-2b45389208fc` + +#### Actions + +- orp → actionTypeId: `e5ed3535-2e5d-412e-85f8-782cbccd7731` + - Parameters: + - orp (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e5ed3535-2e5d-412e-85f8-782cbccd7731", + "params": { + "orp": "" + } + } + } + +- input → actionTypeId: `020ff04f-a157-4925-9c55-2b45389208fc` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "020ff04f-a157-4925-9c55-2b45389208fc", + "params": { + "input": "" + } + } + } + + +--- + +### waterSensor + +- ThingClassId: `09b9cb86-f551-4efe-9a7d-2f7624e9510f` + +#### States + +- waterDetected (Bool) → stateTypeId: `b2427b85-1f62-4635-b546-1961191426ef` + +#### Actions + +- waterDetected → actionTypeId: `b2427b85-1f62-4635-b546-1961191426ef` + - Parameters: + - waterDetected (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b2427b85-1f62-4635-b546-1961191426ef", + "params": { + "waterDetected": "" + } + } + } + + +--- + +### onOffLight + +- ThingClassId: `f720f31d-9523-4a74-9f10-19cbc9edeb58` + +#### States + +- connected (Bool) → stateTypeId: `111a5a4a-63d4-4d1d-ac72-b5b5a29fd0aa` +- power (Bool) → stateTypeId: `5dc5e71b-789e-4c68-abb6-1534c8af019e` + +#### Actions + +- power → actionTypeId: `5dc5e71b-789e-4c68-abb6-1534c8af019e` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5dc5e71b-789e-4c68-abb6-1534c8af019e", + "params": { + "power": "" + } + } + } + + +--- + +### powerMeterChannel + +- ThingClassId: `805487dd-768b-47d5-a0a9-c7021bbec6ff` + +#### States + +- connected (Bool) → stateTypeId: `f268fa63-e17f-41ab-9536-ec2e21a9164c` +- signalStrength (Uint) → stateTypeId: `5ef7fb87-901f-4f1f-8db0-1aa0e0ee633e` +- currentPower (Double) → stateTypeId: `bcd91dba-d063-445a-9680-32d64da29fc0` +- totalEnergyConsumed (Double) → stateTypeId: `ebd82e2f-4d23-451a-a26d-9c95fd3c00be` + + +--- + +### shelly1pm + +- ThingClassId: `30e74e9f-57f4-4bbc-b0df-f2c4f28b2f06` + +#### States + +- power (Bool) → stateTypeId: `ff44d332-52c3-4142-83e3-01d56c2eb42e` +- connected (Bool) → stateTypeId: `df23a5ea-5f54-42ac-ab6b-aea8f71224f0` +- signalStrength (Uint) → stateTypeId: `58531cb9-933e-457e-af07-7c6c22c28e51` +- updateStatus (String) → stateTypeId: `a290c9ad-356c-4387-9d82-2bf522643e16` +- currentVersion (String) → stateTypeId: `681f3a7e-d981-4cb2-a7dc-b889a9393276` +- availableVersion (String) → stateTypeId: `bd77d5f6-4958-4a8a-98f5-25931333a5d3` +- totalEnergyConsumed (Double) → stateTypeId: `23594959-1cd4-4e23-a7ae-b0b7fbd29daa` +- currentPower (Double) → stateTypeId: `3ec03053-7cf5-44fb-ad92-041eed9edd9a` + +#### Actions + +- power → actionTypeId: `ff44d332-52c3-4142-83e3-01d56c2eb42e` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ff44d332-52c3-4142-83e3-01d56c2eb42e", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `3133cf3d-729d-4f36-9a6d-1eeecb07edde` +- performUpdate → actionTypeId: `edc735dd-8c6b-4f76-b05b-32c934b4fe3b` + +--- + +### modbusSolarInverter + +- ThingClassId: `12e0429e-e8ce-48bd-a11c-faaf0bd71856` + +#### States + +- connected (Bool) → stateTypeId: `3c60e2a7-31f3-4b0b-a3f9-ede042e82f22` +- voltagePhaseA (Double) → stateTypeId: `5a717aff-6bdb-4679-94d6-ec1bce7fa2af` +- voltagePhaseB (Double) → stateTypeId: `34eb5b54-7683-42ff-8320-9b2527d6381c` +- voltagePhaseC (Double) → stateTypeId: `2bd0a069-9d16-4d58-9f78-df682d92005d` +- currentPhaseA (Double) → stateTypeId: `48d4a7b7-b09a-4255-83dd-9eab8ea3a51c` +- currentPhaseB (Double) → stateTypeId: `479b27c4-01fc-45ef-a462-b8d8499b3422` +- currentPhaseC (Double) → stateTypeId: `f82bbba1-c68a-4c43-a3e5-10b00ed924d7` +- currentPower (Double) → stateTypeId: `225beb67-95ca-495c-aca8-cd3fd4efedd5` +- currentPowerPhaseA (Double) → stateTypeId: `9283d5a9-b185-4678-beb1-1c6ce6f76930` +- currentPowerPhaseB (Double) → stateTypeId: `8a87319c-f6ab-4eb1-bb17-a65f80289a56` +- currentPowerPhaseC (Double) → stateTypeId: `1f930456-5947-476c-b74b-480f1e81a799` +- totalEnergyProduced (Double) → stateTypeId: `5e0ed108-7e93-4724-a831-319109d9daf8` +- energyProducedToday (Double) → stateTypeId: `b8fb66fa-46b5-4ed7-82a7-29fe5257caa9` +- firmwareVersion (String) → stateTypeId: `3f290cbc-0578-479a-ab98-d89b5549184d` + + +--- + +### htSensor + +- ThingClassId: `3976d3fc-6bd3-4574-8c5b-90c9a07c6440` + +#### States + +- temperature (Double) → stateTypeId: `bb6ee147-2e36-4445-b3e9-449a5dfa9923` +- humidity (Double) → stateTypeId: `5cc739a8-b84a-49e4-af01-08eea0815abb` +- batteryCritical (Bool) → stateTypeId: `c12ee882-9e4e-4b62-b317-4a5a6162b00a` +- connected (Bool) → stateTypeId: `b15bc020-d6fd-4e0e-b3ce-6b7828416e02` +- signalStrength (Uint) → stateTypeId: `4b045aba-366c-49b5-a87e-389805319efe` +- version (String) → stateTypeId: `67cc511f-ef3f-47e9-a60d-4cb8b881703d` + + +--- + +### venetianblind + +- ThingClassId: `c7160205-d864-4194-b418-060fff60f0cb` + +#### States + +- percentage (Int) → stateTypeId: `77ca50db-42a7-4434-83e2-8b5fc4438924` +- angle (Int) → stateTypeId: `079c7a80-8a1c-4fd7-b40c-6800120c70fb` +- moving (Bool) → stateTypeId: `48d5de0a-11ab-4801-94e4-a1dd458c341d` +- signalStrength (Uint) → stateTypeId: `aee4f4e3-3445-441d-bdbb-631b0c5db942` +- connected (Bool) → stateTypeId: `57361115-edbe-49fb-9847-408b571d3108` + +#### Actions + +- percentage → actionTypeId: `77ca50db-42a7-4434-83e2-8b5fc4438924` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "77ca50db-42a7-4434-83e2-8b5fc4438924", + "params": { + "percentage": "" + } + } + } + +- angle → actionTypeId: `079c7a80-8a1c-4fd7-b40c-6800120c70fb` + - Parameters: + - angle (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "079c7a80-8a1c-4fd7-b40c-6800120c70fb", + "params": { + "angle": "" + } + } + } + +- open → actionTypeId: `004e7294-59e6-498b-a0aa-e58eaeefdf2b` +- stop → actionTypeId: `31b07407-65ef-4fd1-880b-b5d9f69a9d07` +- close → actionTypeId: `1a9707e7-9d64-4237-b150-234edcfed12a` + +--- + +### wallbox + +- ThingClassId: `6f453eaa-7594-4be8-a92d-14878193071a` + +#### States + +- maxChargingCurrent (Double) → stateTypeId: `1e0890e5-065a-455a-a068-10b0231897f4` +- power (Bool) → stateTypeId: `51c7b606-1eb0-418a-8203-3f0fb9d6e454` +- pluggedIn (Bool) → stateTypeId: `e9cc633d-35a6-4a86-8e97-14b12ba06d8e` +- charging (Bool) → stateTypeId: `b5d43614-7ed0-4c6e-b48a-f74aa9884769` +- phaseCount (Uint) → stateTypeId: `08fbe34e-735d-4ce5-81c7-ea1c7b07877b` +- desiredPhaseCount (Uint) → stateTypeId: `68239a43-c627-4b31-bc48-e8a814066cc5` +- currentPower (Double) → stateTypeId: `596a002d-77f4-410c-8ca9-3a4bb8051328` +- totalEnergyConsumed (Double) → stateTypeId: `c3b4303f-7267-4401-b2da-2378d39d762d` + +#### Actions + +- maxChargingCurrent → actionTypeId: `1e0890e5-065a-455a-a068-10b0231897f4` + - Parameters: + - maxChargingCurrent (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1e0890e5-065a-455a-a068-10b0231897f4", + "params": { + "maxChargingCurrent": "" + } + } + } + +- power → actionTypeId: `51c7b606-1eb0-418a-8203-3f0fb9d6e454` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "51c7b606-1eb0-418a-8203-3f0fb9d6e454", + "params": { + "power": "" + } + } + } + +- pluggedIn → actionTypeId: `e9cc633d-35a6-4a86-8e97-14b12ba06d8e` + - Parameters: + - pluggedIn (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e9cc633d-35a6-4a86-8e97-14b12ba06d8e", + "params": { + "pluggedIn": "" + } + } + } + +- charging → actionTypeId: `b5d43614-7ed0-4c6e-b48a-f74aa9884769` + - Parameters: + - charging (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b5d43614-7ed0-4c6e-b48a-f74aa9884769", + "params": { + "charging": "" + } + } + } + +- phaseCount → actionTypeId: `08fbe34e-735d-4ce5-81c7-ea1c7b07877b` + - Parameters: + - phaseCount (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "08fbe34e-735d-4ce5-81c7-ea1c7b07877b", + "params": { + "phaseCount": "" + } + } + } + +- desiredPhaseCount → actionTypeId: `68239a43-c627-4b31-bc48-e8a814066cc5` + - Parameters: + - desiredPhaseCount (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "68239a43-c627-4b31-bc48-e8a814066cc5", + "params": { + "desiredPhaseCount": "" + } + } + } + +- currentPower → actionTypeId: `596a002d-77f4-410c-8ca9-3a4bb8051328` + - Parameters: + - currentPower (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "596a002d-77f4-410c-8ca9-3a4bb8051328", + "params": { + "currentPower": "" + } + } + } + +- totalEnergyConsumed → actionTypeId: `c3b4303f-7267-4401-b2da-2378d39d762d` + - Parameters: + - totalEnergyConsumed (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c3b4303f-7267-4401-b2da-2378d39d762d", + "params": { + "totalEnergyConsumed": "" + } + } + } + + +--- + +### cookTop + +- ThingClassId: `6ab99f5b-c8b6-4391-84d3-2e258a529174` + +#### States + +- connected (Bool) → stateTypeId: `d8113d26-045d-4fd5-a2f5-f202968408e0` + + +--- + +### tapDial + +- ThingClassId: `58e579b4-f917-478c-9006-f1f8d4df2ded` + +#### States + +- connected (Bool) → stateTypeId: `7c81af92-3643-4211-9d62-407cbc596619` +- batteryLevel (Int) → stateTypeId: `9d0d190f-2898-4c9a-a423-ec7187bfcb5e` +- batteryCritical (Bool) → stateTypeId: `79efe477-2757-4457-bdfb-a3fbeb198e7a` +- level (Uint) → stateTypeId: `23528402-b98f-49b8-90cf-7b68f251ad59` + + +--- + +### netPwrCtlPro + +- ThingClassId: `1492f911-1c09-42ce-b920-084548a689ea` + +#### States + +- connected (Bool) → stateTypeId: `820c54bd-0d4f-4e13-8160-a8efa77c9db5` +- temperature (Double) → stateTypeId: `2973ec1e-9c26-45ad-a97b-dd5eccbf650a` + + +--- + +### simpleGaragedoor + +- ThingClassId: `572403b1-bc22-4620-8170-53147a546033` + +#### States + +- openingOutput (Bool) → stateTypeId: `fcd421eb-54f2-4195-bdef-ffa69e9dcc57` +- closingOutput (Bool) → stateTypeId: `44ef060a-e6fc-4f33-84d2-b24ac7d31ff1` + +#### Actions + +- open → actionTypeId: `3edb25af-ad51-495b-9ac9-ab97669339b7` +- close → actionTypeId: `1111c0ed-69b6-480c-9fd5-15292600d4f4` +- stop → actionTypeId: `f346766f-44ee-452b-bb9c-e89ec0f56016` + +--- + +### keba + +- ThingClassId: `900dacec-cae7-4a37-95ba-501846368ea2` + +#### States + +- connected (Bool) → stateTypeId: `ce813458-d7d8-4f40-9648-dba4c41e92f0` +- power (Bool) → stateTypeId: `83ed0774-2a91-434d-b03c-d920d02f2981` +- systemEnabled (Bool) → stateTypeId: `e5631593-f486-47cb-9951-b7597d0b769b` +- phaseCount (Uint) → stateTypeId: `6713b2e7-41b3-4596-a304-3065726bdbe4` +- activity (String) → stateTypeId: `539e5602-6dd9-465d-9705-3bb59bcf8982` +- plugState (String) → stateTypeId: `3b4d29f3-3101-47ad-90fd-269b6348783b` +- pluggedIn (Bool) → stateTypeId: `6c227717-f420-4dcd-bd52-49973715603b` +- charging (Bool) → stateTypeId: `c9785626-2501-478d-8c18-c42ad5d9a269` +- maxChargingCurrent (Double) → stateTypeId: `593656f0-babf-4308-8767-68f34e10fb15` +- maxChargingCurrentPercent (Uint) → stateTypeId: `3c7b83a0-0e42-47bf-9788-dde6aab5ceea` +- maxChargingCurrentHardware (Uint) → stateTypeId: `33e2ed95-f01e-44db-8156-34d124a8ecc8` +- voltagePhaseA (Int) → stateTypeId: `4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9` +- voltagePhaseB (Int) → stateTypeId: `c8344ca5-21ac-4cd1-8f4b-e5ed202c5862` +- voltagePhaseC (Int) → stateTypeId: `5f01e86c-0943-4849-a01a-db441916ebd5` +- currentPhaseA (Double) → stateTypeId: `31ec17b0-11e3-4332-92b0-fea821cf024f` +- currentPhaseB (Double) → stateTypeId: `cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97` +- currentPhaseC (Double) → stateTypeId: `da838dc8-85f0-4e55-b4b5-cb93a43b373d` +- currentPower (Double) → stateTypeId: `7af9e93b-099d-4d9d-a480-9c0f66aecd8b` +- powerFactor (Double) → stateTypeId: `889c3c9a-96b4-4408-bd9a-d79e36ed9296` +- sessionId (Uint) → stateTypeId: `1d30ce60-2ea0-450f-817e-5c88f59ebfbf` +- sessionTime (Int) → stateTypeId: `a6f35ea0-aaea-438b-b818-6d161762611e` +- sessionEnergy (Double) → stateTypeId: `8e277efe-21ef-4536-bfc0-901b32d44d7c` +- totalEnergyConsumed (Double) → stateTypeId: `41e179b3-29a2-43ec-b537-023a527081e8` +- outputX2 (Bool) → stateTypeId: `96b2d176-6460-4109-8824-3af4679c6573` +- input (Bool) → stateTypeId: `ba600276-8b36-4404-b8ec-415245e5bc15` +- uptime (Int) → stateTypeId: `3421ecf9-c95f-4dc1-ad0c-144e9b6ae056` +- error1 (Int) → stateTypeId: `b44bc948-1234-4f87-9a22-bfb6de09df4d` +- error2 (Int) → stateTypeId: `afca201a-5213-43fe-bfec-cae6ce7509d2` +- failsafeMode (Bool) → stateTypeId: `f1758c5c-2c02-41cb-93ec-b778a3c78d28` +- firmware (String) → stateTypeId: `e941ace5-fb7f-4dc2-b3f2-188233f4e934` +- hostAddress (String) → stateTypeId: `11501a4b-8b83-4b92-be3c-a714d507f158` + +#### Actions + +- power → actionTypeId: `83ed0774-2a91-434d-b03c-d920d02f2981` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "83ed0774-2a91-434d-b03c-d920d02f2981", + "params": { + "power": "" + } + } + } + +- maxChargingCurrent → actionTypeId: `593656f0-babf-4308-8767-68f34e10fb15` + - Parameters: + - maxChargingCurrent (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "593656f0-babf-4308-8767-68f34e10fb15", + "params": { + "maxChargingCurrent": "" + } + } + } + +- outputX2 → actionTypeId: `96b2d176-6460-4109-8824-3af4679c6573` + - Parameters: + - outputX2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "96b2d176-6460-4109-8824-3af4679c6573", + "params": { + "outputX2": "" + } + } + } + +- failsafeMode → actionTypeId: `f1758c5c-2c02-41cb-93ec-b778a3c78d28` + - Parameters: + - failsafeMode (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f1758c5c-2c02-41cb-93ec-b778a3c78d28", + "params": { + "failsafeMode": "" + } + } + } + +- display → actionTypeId: `158b1a8f-fde9-4191-bf42-4ece5fe582e6` + - Parameters: + - message (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "158b1a8f-fde9-4191-bf42-4ece5fe582e6", + "params": { + "message": "" + } + } + } + + +--- + +### signalRepeater + +- ThingClassId: `ac51f426-195e-472a-837f-4ba7e4666e6e` + +#### States + +- connected (Bool) → stateTypeId: `7415ea93-9b72-4740-b3b1-a2ebb73d1db5` +- signalStrength (Uint) → stateTypeId: `05758f08-33a8-485b-8572-f87021e3af36` +- currentVersion (String) → stateTypeId: `05d9a4f4-5d7e-474b-b57c-445f795a0d90` +- updateStatus (String) → stateTypeId: `7cf5dbb5-672f-4c17-ab52-bd62a4289f44` +- availableVersion (String) → stateTypeId: `b092b5e3-496d-44a2-a15b-00cb6b7462f4` +- updateProgress (Uint) → stateTypeId: `3ff0a3ab-f3fe-4dac-8b1a-d30676fde7f3` + +#### Actions + +- performUpdate → actionTypeId: `4e6544cc-0f99-460f-92aa-f00c77f8131a` + +--- + +### airHousekeeper + +- ThingClassId: `bed2680c-ebce-49ab-86cc-7c3c69e34372` + +#### States + +- connected (Bool) → stateTypeId: `984b266b-e8b9-4566-a6ef-cc2aaf41333e` +- signalStrength (Uint) → stateTypeId: `cf87d713-bdb6-4768-ad48-92b1ce5835ce` +- temperature (Double) → stateTypeId: `2cb06847-a98e-4440-ab5a-990a4073f7e6` +- humidity (Double) → stateTypeId: `021ad175-3e09-4f9c-a457-231b71681699` +- voc (Uint) → stateTypeId: `0e5d6b08-d415-43e2-9314-e97e526e105d` +- pm25 (Double) → stateTypeId: `1f9460ba-8495-4a00-a3ac-de4efcb40c1c` +- co2 (Double) → stateTypeId: `f0621e7f-46b0-40a6-8bce-54164e8518ae` +- formaldehyd (Double) → stateTypeId: `2a8353ca-b1aa-4d7b-8951-e24febe87bbc` +- batteryLevel (Int) → stateTypeId: `24c68257-5e33-493f-acee-6c3cdce1bbf1` +- batteryCritical (Bool) → stateTypeId: `1cadb85c-3c1b-4cd4-8878-d32c71234758` + + +--- + +### heos + +- ThingClassId: `28bbf4c6-dfd8-4d9d-aa27-5daf2c25d63c` + +#### States + +- connected (Bool) → stateTypeId: `4d1790bf-28c6-4c1f-8892-ba1a0ef140f5` +- loggedIn (Bool) → stateTypeId: `ab689a6e-eb71-4a41-a267-ba1afe7e2f56` +- userDisplayName (String) → stateTypeId: `77756132-5fa4-409e-969e-d23bcee72356` + +#### Actions + +- reboot → actionTypeId: `4f8b7fe8-7a18-483a-859d-ed5acb0b9a20` + +--- + +### kebaSimple + +- ThingClassId: `c5bca9d2-2a17-40c4-8bb2-ba89783a6dd1` + +#### States + +- connected (Bool) → stateTypeId: `995f2ccf-2082-434e-a46d-c506862e6d6a` +- power (Bool) → stateTypeId: `63f84293-62aa-420d-bc0d-cc48618c6526` +- systemEnabled (Bool) → stateTypeId: `8ade4b68-e44e-425c-87ea-a35d176f337d` +- phaseCount (Uint) → stateTypeId: `2473e39b-9641-4236-be56-e706d7797161` +- activity (String) → stateTypeId: `955ffd64-42f6-4000-94c5-c7f862daa438` +- plugState (String) → stateTypeId: `82aa0d67-eea6-4a5e-b7ab-2848a4012490` +- pluggedIn (Bool) → stateTypeId: `faf68cc9-f014-4db5-94fa-0f10a0b85fb1` +- charging (Bool) → stateTypeId: `38affdf2-f62e-458c-b738-8db81aa13790` +- maxChargingCurrent (Double) → stateTypeId: `2a72ad9e-96bd-4281-afb7-ce4f5c6f5052` +- maxChargingCurrentPercent (Uint) → stateTypeId: `33631b7f-a675-4625-8095-31e09e03a010` +- maxChargingCurrentHardware (Uint) → stateTypeId: `f94a2381-28a8-478e-ac44-0902a5be8885` +- outputX2 (Bool) → stateTypeId: `043ea799-4348-44f9-985d-bee2ba280957` +- input (Bool) → stateTypeId: `0ca0921d-5516-44fb-9483-242d9bb7a2d0` +- uptime (Int) → stateTypeId: `2cffff03-63b2-468d-b2ef-a4741401d7c8` +- error1 (Int) → stateTypeId: `8380c340-84ee-4d62-84b0-7c5738ab66bc` +- error2 (Int) → stateTypeId: `afe287a2-35e2-4762-a6bf-79d7c31d32ab` +- failsafeMode (Bool) → stateTypeId: `bfad6a1a-40e0-4b32-9f42-09efd5a7e94c` +- firmware (String) → stateTypeId: `d473770e-c5b4-4845-8215-0dea304ea202` +- hostAddress (String) → stateTypeId: `eef1d569-f383-4980-8cb8-06ccc5069b0b` + +#### Actions + +- power → actionTypeId: `63f84293-62aa-420d-bc0d-cc48618c6526` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "63f84293-62aa-420d-bc0d-cc48618c6526", + "params": { + "power": "" + } + } + } + +- maxChargingCurrent → actionTypeId: `2a72ad9e-96bd-4281-afb7-ce4f5c6f5052` + - Parameters: + - maxChargingCurrent (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2a72ad9e-96bd-4281-afb7-ce4f5c6f5052", + "params": { + "maxChargingCurrent": "" + } + } + } + +- outputX2 → actionTypeId: `043ea799-4348-44f9-985d-bee2ba280957` + - Parameters: + - outputX2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "043ea799-4348-44f9-985d-bee2ba280957", + "params": { + "outputX2": "" + } + } + } + +- failsafeMode → actionTypeId: `bfad6a1a-40e0-4b32-9f42-09efd5a7e94c` + - Parameters: + - failsafeMode (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bfad6a1a-40e0-4b32-9f42-09efd5a7e94c", + "params": { + "failsafeMode": "" + } + } + } + +- display → actionTypeId: `e756c842-bec5-42ee-a28b-280d48e834b1` + - Parameters: + - message (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e756c842-bec5-42ee-a28b-280d48e834b1", + "params": { + "message": "" + } + } + } + + +--- + +### lumiMotionSensor2 + +- ThingClassId: `6e9c7ced-cfc6-4ea3-9a68-b3820c9b0030` + +#### States + +- connected (Bool) → stateTypeId: `e80bb10c-953b-4845-8c61-2f51e84f9cfe` +- signalStrength (Uint) → stateTypeId: `7e736041-29a7-4f7f-82b6-3e17d01b3943` +- version (String) → stateTypeId: `2081fea2-8fac-4541-b414-3f5584aed67c` +- isPresent (Bool) → stateTypeId: `c70a2674-5b91-4a48-b696-ae9e80cc6e3a` +- lastSeenTime (Int) → stateTypeId: `bdaab055-8a1d-42cd-95e4-d77ab065292a` +- lightIntensity (Double) → stateTypeId: `bb34c29b-1519-453a-a19b-57d024977d49` + + +--- + +### irrigation + +- ThingClassId: `d013b980-20d5-4791-9c4f-b411c39241d7` + +#### States + +- power (Bool) → stateTypeId: `0212a287-c5ae-4644-8803-adfdd8caeb9a` + +#### Actions + +- power → actionTypeId: `0212a287-c5ae-4644-8803-adfdd8caeb9a` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "0212a287-c5ae-4644-8803-adfdd8caeb9a", + "params": { + "power": "" + } + } + } + + +--- + +### blind + +- ThingClassId: `17ee3657-6ad8-4ae2-8959-3cf66cec8d13` + +#### States + +- openingOutput (Bool) → stateTypeId: `b2dbf25c-27e5-4f7e-a57d-2ef6d087fa2b` +- closingOutput (Bool) → stateTypeId: `9b673430-572d-4a9c-85d3-dafadbe541cd` +- status (String) → stateTypeId: `5fdec1e0-51f6-48b9-b743-ba572504b2c1` + +#### Actions + +- openingOutput → actionTypeId: `b2dbf25c-27e5-4f7e-a57d-2ef6d087fa2b` + - Parameters: + - openingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "b2dbf25c-27e5-4f7e-a57d-2ef6d087fa2b", + "params": { + "openingOutput": "" + } + } + } + +- closingOutput → actionTypeId: `9b673430-572d-4a9c-85d3-dafadbe541cd` + - Parameters: + - closingOutput (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9b673430-572d-4a9c-85d3-dafadbe541cd", + "params": { + "closingOutput": "" + } + } + } + +- open → actionTypeId: `120dc265-dbbb-4f19-9d31-c372c23479c0` +- stop → actionTypeId: `1a924c9a-5dcb-4b1c-8fd6-ab101098e007` +- close → actionTypeId: `86e9cf21-7487-47c4-b4be-4a940d7235fb` + +--- + +### doorSensor + +- ThingClassId: `fd43b442-1764-4005-acaf-9075e6d11e3a` + +#### States + +- closed (Bool) → stateTypeId: `2fb8e0a0-52f2-4c1d-b1c9-c7a295a83b44` +- batteryLevel (Int) → stateTypeId: `3c120c1c-0093-4c5e-8d7e-8892e4ce21b3` +- batteryCritical (Bool) → stateTypeId: `9628450e-5580-4f30-8af7-f1c3093dcfb7` +- connected (Bool) → stateTypeId: `4fae59a7-fe89-4bc7-bc04-fc18e94b5c3e` +- signalStrength (Uint) → stateTypeId: `a1bf9c90-6e12-4513-a82a-a1d15351f140` + + +--- + +### motionSensor + +- ThingClassId: `fe7e6f2f-5ba2-4c39-b4c4-36d69e7c4738` + +#### States + +- connected (Bool) → stateTypeId: `56a58de9-fc2d-4ba4-b986-6494468c1297` +- signalStrength (Uint) → stateTypeId: `4d9dc0ee-4a91-48d0-8fad-bb1a88a30aef` +- isPresent (Bool) → stateTypeId: `baebd40c-a942-4228-a8cf-2bfbd5599cb3` +- batteryCritical (Bool) → stateTypeId: `b777b208-8cb1-457e-b3c0-52ec7e29bd4a` +- batteryLevel (Int) → stateTypeId: `209c5af4-d13e-4411-92db-e204aae3a34a` +- tampered (Bool) → stateTypeId: `044c823e-e4da-4a69-91fe-c4a94d774493` +- temperature (Double) → stateTypeId: `29207d04-0a34-4200-b933-a6bb3653fc56` +- lightIntensity (Double) → stateTypeId: `278c9d59-8948-4435-a94c-0eecc756b676` +- seismicIntensity (Double) → stateTypeId: `b69041c8-5262-45b8-b3bd-a0a7cbc8f3a2` + + +--- + +### smartMeterProducer + +- ThingClassId: `986acf96-3cdd-4a8d-8f5c-2ffe12fef1c9` + +#### States + +- currentPower (Double) → stateTypeId: `8fd67ad4-322d-46d7-a0fd-20fdbfc12bc5` +- totalEnergyProduced (Double) → stateTypeId: `58a1212c-ab6e-4631-b08d-712cdde295c7` + +#### Actions + +- currentPower → actionTypeId: `8fd67ad4-322d-46d7-a0fd-20fdbfc12bc5` + - Parameters: + - currentPower (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8fd67ad4-322d-46d7-a0fd-20fdbfc12bc5", + "params": { + "currentPower": "" + } + } + } + +- totalEnergyProduced → actionTypeId: `58a1212c-ab6e-4631-b08d-712cdde295c7` + - Parameters: + - totalEnergyProduced (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "58a1212c-ab6e-4631-b08d-712cdde295c7", + "params": { + "totalEnergyProduced": "" + } + } + } + + +--- + +### telegram + +- ThingClassId: `f71bac7a-90e1-4810-9c31-531ac5f596fb` + +#### Actions + +- notify → actionTypeId: `36b4ddf9-1a05-46c6-b0c9-2c3b8c835d42` + - Parameters: + - title (String) + - body (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "36b4ddf9-1a05-46c6-b0c9-2c3b8c835d42", + "params": { + "title": "", + "body": "" + } + } + } + + +--- + +### netPwrCtlAdv + +- ThingClassId: `99987919-d32d-4d8f-938c-bcf9683003b6` + +#### States + +- connected (Bool) → stateTypeId: `d143b775-e004-4119-a317-6c508686d473` +- temperature (Double) → stateTypeId: `aed7464a-6c36-4858-adb7-776d97c5498e` + + +--- + +### garagedoor + +- ThingClassId: `cb206d74-b13c-4466-98c6-070b19ebd23a` + +#### States + +- percentage (Int) → stateTypeId: `284816aa-842b-4a86-bb4e-ef5353b76762` +- moving (Bool) → stateTypeId: `07175175-f95d-4cd9-a398-9aab8232c2a9` +- signalStrength (Uint) → stateTypeId: `0a194091-3073-4912-9d84-f1d52c8534bd` +- connected (Bool) → stateTypeId: `5a32cbd3-bc1c-4724-ae53-9f36cb75bf84` +- state (String) → stateTypeId: `12af28f1-475e-4d05-9bbb-adbb86dcd69c` + +#### Actions + +- percentage → actionTypeId: `284816aa-842b-4a86-bb4e-ef5353b76762` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "284816aa-842b-4a86-bb4e-ef5353b76762", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `5738edd9-a1d1-4031-8505-85a919afe6f7` +- stop → actionTypeId: `75e345b8-0a25-4ba6-ba83-f2611252f87f` +- close → actionTypeId: `1e6552e3-8eb0-4070-b9cf-13fd13671eef` + +--- + +### smokeSensor + +- ThingClassId: `f41ea8de-6df0-40b6-8fc1-95334659a964` + +#### States + +- connected (Bool) → stateTypeId: `67df8dca-5da0-4909-84d0-684cdda5340a` +- signalStrength (Uint) → stateTypeId: `fc21bb2f-b35f-4847-a459-8db0ce66107c` +- version (String) → stateTypeId: `f4275d7b-5911-4422-8a97-a7a01300f281` +- fireDetected (Bool) → stateTypeId: `9ab61466-0ead-4d95-a6d5-c54951445bfb` +- temperature (Double) → stateTypeId: `3bcabb7b-8545-447b-98ba-761a1112f72b` +- batteryCritical (Bool) → stateTypeId: `d34ecef2-ceea-4e6f-8cd7-06f54593d594` +- updateStatus (String) → stateTypeId: `b3cdd902-dc00-4a6f-9ed0-c97347726397` +- currentVersion (String) → stateTypeId: `9170282f-eb7e-40ca-a6a6-cd51f7f3d2bb` +- availableVersion (String) → stateTypeId: `b7e5325d-640c-4781-91f0-68ce669cf873` +- updateProgress (Uint) → stateTypeId: `ac365d01-b58a-4974-a6c0-57472fd2553a` + +#### Actions + +- alarm → actionTypeId: `dc28424e-7414-4cba-b48d-e16a4e2cc1ec` + - Parameters: + - duration (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "dc28424e-7414-4cba-b48d-e16a4e2cc1ec", + "params": { + "duration": "" + } + } + } + +- performUpdate → actionTypeId: `2860002a-4d15-450a-b973-8877da4ad3ad` + +--- + +### vibrationSensor + +- ThingClassId: `3e03b237-a098-442c-a226-d51468bdbecc` + +#### Actions + +- triggerVibrationAlert → actionTypeId: `f6710702-5f1a-4819-8cc8-2ddb53134870` + +--- + +### heating + +- ThingClassId: `392854c4-3d14-4cf8-96cd-d933526bd197` + +#### States + +- power (Bool) → stateTypeId: `409b635e-a754-4b5c-b3f0-d1c5a0fb3f03` + +#### Actions + +- power → actionTypeId: `409b635e-a754-4b5c-b3f0-d1c5a0fb3f03` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "409b635e-a754-4b5c-b3f0-d1c5a0fb3f03", + "params": { + "power": "" + } + } + } + + +--- + +### storage + +- ThingClassId: `b00139fa-7386-48b1-8697-2fdd21a57ced` + +#### States + +- connected (Bool) → stateTypeId: `2f7e1267-b0be-4b78-9aa3-832b86c4efad` +- chargingState (String) → stateTypeId: `7a045257-d829-4e58-a769-047b3aeec7c5` +- currentPower (Double) → stateTypeId: `5a89cd3f-3abf-4f51-ab2b-4039f1d211d9` +- capacity (Double) → stateTypeId: `3b163deb-67a2-41d1-8441-b2d53ad846ef` +- batteryLevel (Int) → stateTypeId: `5c6da672-9662-41bc-8c8c-aa0f32481251` +- cellTemperature (Double) → stateTypeId: `4417499c-1757-4309-868a-be5cf3455c4a` +- batteryCritical (Bool) → stateTypeId: `e5396312-b50e-4d6f-b628-5b51448971d3` + + +--- + +### shelly3EM + +- ThingClassId: `c4428100-b270-4d95-91ae-492da8176311` + +#### States + +- connected (Bool) → stateTypeId: `3da67c4e-0a70-423b-a3bf-c6fb035ece0c` +- signalStrength (Uint) → stateTypeId: `a199035a-ede2-4b7e-8bbc-4346bddfc8ef` +- totalEnergyConsumed (Double) → stateTypeId: `1c3cf72d-46e8-47b7-b80b-312d23a16311` +- totalEnergyProduced (Double) → stateTypeId: `a7a74cfa-ade2-4527-af7e-1475fff5b88f` +- currentPower (Double) → stateTypeId: `a49564b6-71e6-4765-9758-af61898632c1` +- currentPowerPhaseA (Double) → stateTypeId: `687b3de6-7f02-45d2-ae23-015b8341be0b` +- currentPhaseA (Double) → stateTypeId: `0a5e4116-f486-49a8-bb49-2d911ff64fd8` +- voltagePhaseA (Double) → stateTypeId: `a17fc5ba-7250-4c29-85eb-06491925b3ce` +- energyConsumedPhaseA (Double) → stateTypeId: `39533108-28d9-4ec5-af17-b42c95dd73bc` +- energyProducedPhaseA (Double) → stateTypeId: `962bc7d3-d80f-461d-b08e-c01f23af3197` +- currentPowerPhaseB (Double) → stateTypeId: `549a50bf-11a0-47dd-99bc-8dcca9c31989` +- currentPhaseB (Double) → stateTypeId: `eb475aad-faf4-4c75-bc1a-b301176604bb` +- voltagePhaseB (Double) → stateTypeId: `b0dcc6d0-5449-4716-9dbe-859513349b64` +- energyConsumedPhaseB (Double) → stateTypeId: `6f238eb1-9c4f-4beb-889a-73aa95a837ff` +- energyProducedPhaseB (Double) → stateTypeId: `b3c78052-4d92-4103-9ab3-f23e256e92a3` +- currentPowerPhaseC (Double) → stateTypeId: `aae6fcb0-d6ca-4b51-a913-5f2134c14026` +- currentPhaseC (Double) → stateTypeId: `89ba2b3a-4fc7-4266-812b-7738d5b4c33d` +- voltagePhaseC (Double) → stateTypeId: `3ea8e4cf-c1ad-4843-b2de-a3196231224e` +- energyConsumedPhaseC (Double) → stateTypeId: `38f76e32-46b0-46bc-8dd3-93a422996584` +- energyProducedPhaseC (Double) → stateTypeId: `3d0c568d-d478-4f1f-83df-1e07eb57d04b` +- updateStatus (String) → stateTypeId: `d667bec3-e0a2-48fc-86be-2da1ff487bb3` +- currentVersion (String) → stateTypeId: `3f08799c-74ff-415a-86fd-d89d29dc8f10` +- availableVersion (String) → stateTypeId: `d742d57b-4b60-484a-ae3b-ba1730e6bc45` +- power (Bool) → stateTypeId: `d8505ed8-f7cc-4bfd-939a-90f99aaa242b` + +#### Actions + +- power → actionTypeId: `d8505ed8-f7cc-4bfd-939a-90f99aaa242b` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d8505ed8-f7cc-4bfd-939a-90f99aaa242b", + "params": { + "power": "" + } + } + } + +- performUpdate → actionTypeId: `971a7849-f5b1-496c-9f7c-0f61a6c5182b` +- reset → actionTypeId: `f4e2db2b-3832-4ffb-bd75-60e180fc59dd` + +--- + +### connection + +- ThingClassId: `4fd79fed-42f1-4df9-be64-3df7b2e0bda2` + +#### States + +- connected (Bool) → stateTypeId: `98e4476f-e745-4a7f-b795-19269cb70c40` +- version (String) → stateTypeId: `8fd0c0ed-af89-4887-bf0f-040b13c25268` + + +--- + +### daylightSensor + +- ThingClassId: `1d43d0ed-efe2-46ab-a567-af632e550931` + +#### States + +- daylight (Bool) → stateTypeId: `6e7bc630-a19c-41ec-b6a3-2e0cbcc4fadf` +- sunriseTime (Int) → stateTypeId: `d7c1f356-1efe-4ab7-ad4b-e7ab12cb53b7` +- sunsetTime (Int) → stateTypeId: `6d064789-b9c3-44c8-a5ea-b722fc2f56b7` + + +--- + +### onOffSwitch + +- ThingClassId: `2007e8ae-4849-47f6-98d6-ea6769c8ebd9` + +#### States + +- connected (Bool) → stateTypeId: `b5abd47e-95f1-4e35-94fa-be87c396073a` +- signalStrength (Uint) → stateTypeId: `21889797-32c0-4f1b-903d-8af71981882b` +- batteryLevel (Int) → stateTypeId: `103fba1d-79a6-4b48-b7a1-f91090f1c699` +- batteryCritical (Bool) → stateTypeId: `a47e27ce-f443-4c9a-96fc-d59ec4989b49` +- currentVersion (String) → stateTypeId: `41c74445-4d2d-46dc-8a63-c98f1df8e742` +- updateStatus (String) → stateTypeId: `69495a25-e6da-4fb3-956e-5218a925db93` +- availableVersion (String) → stateTypeId: `86759c02-7007-4cb6-a349-c63c851f9849` +- updateProgress (Uint) → stateTypeId: `743113cd-d2bc-4279-bcaa-cee2483b072a` + +#### Actions + +- performUpdate → actionTypeId: `4760aeee-c8ec-4645-b676-344d427b40ed` + +--- + +### gpioButtonRpi + +- ThingClassId: `1abd4dad-0757-4160-8df1-75c45bd61e6e` + + +--- + +### motionSensor + +- ThingClassId: `30a1090e-8058-4a5b-aee3-4ba112ba3ba7` + +#### States + +- connected (Bool) → stateTypeId: `6bf470bb-4756-43fa-bddf-5e6ce002caa3` +- signalStrength (Uint) → stateTypeId: `0a54036e-ca25-40fd-8f24-3516c5141c0b` +- batteryLevel (Int) → stateTypeId: `e2e414c1-5f21-4244-942a-ee17cb25f1c0` +- batteryCritical (Bool) → stateTypeId: `21e7c58e-595e-4b7d-bd21-c0a424c876dc` +- isPresent (Bool) → stateTypeId: `16ee7fe2-1a86-40e9-aae5-610f712ca155` +- lastSeenTime (Int) → stateTypeId: `12041993-c731-4cc1-8010-904b5655d920` +- currentVersion (String) → stateTypeId: `09704fdb-f2da-4d1c-bddd-f85f85847e29` +- updateStatus (String) → stateTypeId: `8ff400f3-0b53-401e-b330-1a419f078734` +- availableVersion (String) → stateTypeId: `d634f289-e6d6-4685-8636-64c41481adb2` +- updateProgress (Uint) → stateTypeId: `15e02d98-badf-4961-a3db-b70d515d064c` + +#### Actions + +- performUpdate → actionTypeId: `aea08384-f841-4d46-97c6-d9803632a476` + +--- + +### dimmableLight + +- ThingClassId: `3068324d-d5cd-4943-a3cd-95668b09cbc8` + +#### States + +- connected (Bool) → stateTypeId: `892dd19a-c47f-41a4-9b0b-40f43b58c7f9` +- signalStrength (Uint) → stateTypeId: `c2ce2865-bab3-44ef-84f1-06b60d1bb514` +- power (Bool) → stateTypeId: `d40ac497-3716-4ec4-a7b3-21cecf751241` +- brightness (Int) → stateTypeId: `76ef975e-6713-497b-8e4b-a0ebae8b1f87` +- updateStatus (String) → stateTypeId: `5fce38cd-07a7-4071-b020-fe405033fb98` +- currentVersion (String) → stateTypeId: `8893cd94-f5e1-409f-baca-4d3eb173844e` +- availableVersion (String) → stateTypeId: `b4fe0394-0df0-475f-b24c-19470067070a` +- updateProgress (Uint) → stateTypeId: `ab7905f3-b202-4646-82f4-3db5d8fd1418` + +#### Actions + +- power → actionTypeId: `d40ac497-3716-4ec4-a7b3-21cecf751241` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d40ac497-3716-4ec4-a7b3-21cecf751241", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `76ef975e-6713-497b-8e4b-a0ebae8b1f87` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "76ef975e-6713-497b-8e4b-a0ebae8b1f87", + "params": { + "brightness": "" + } + } + } + +- alert → actionTypeId: `cc0cfcfa-97e2-4d7f-92f1-dbd0b8d479d2` +- performUpdate → actionTypeId: `6fe8bfd5-4f8d-428d-a67c-3d88ab910128` + +--- + +### networkDevice + +- ThingClassId: `bd216356-f1ec-4324-9785-6982d2174e17` + +#### States + +- address (String) → stateTypeId: `acee9260-4d01-471a-85c5-4d6116b35ff1` +- hostName (String) → stateTypeId: `29c65dcf-090e-4316-8554-68f038a8416f` +- macManufacturerName (String) → stateTypeId: `395623b2-5b25-4582-803e-61cd6d40844c` +- networkInterface (String) → stateTypeId: `412f0e24-26e7-450b-8e60-bfaf938ea23e` +- isPresent (Bool) → stateTypeId: `cb43e1b5-4f61-4538-bfa2-c33055c542cf` +- lastSeenTime (Int) → stateTypeId: `b51d54c9-cce1-43f0-a35d-52fc2d8d302c` + +#### Actions + +- ping → actionTypeId: `e98793b6-2dad-4090-91db-77f8493b4e45` +- arpRequest → actionTypeId: `2ca3f99f-7315-4f17-b48d-99f0c151a62c` +- lookupHost → actionTypeId: `9797dac5-d99a-4bcf-a99a-5b9356bbce76` + +--- + +### dimmerSwitch2 + +- ThingClassId: `60836676-c87b-499b-9802-a72b788ef00b` + +#### States + +- connected (Bool) → stateTypeId: `0e35957c-bfdf-4b00-9cb2-ecc663f67d55` +- signalStrength (Uint) → stateTypeId: `a20e79de-4710-4295-a582-3e63eedd19fa` +- batteryLevel (Int) → stateTypeId: `a182b72c-4fc5-4ae4-87c0-8b1eb3de00d4` +- batteryCritical (Bool) → stateTypeId: `3a446876-a960-43c5-b7bc-6e06a071391a` +- updateStatus (String) → stateTypeId: `b47a5b4d-f10e-4d6f-965f-b2a1347fcab6` +- currentVersion (String) → stateTypeId: `f4442612-970e-4233-9242-b4f2873702ee` +- availableVersion (String) → stateTypeId: `373848e9-cad7-4986-a9a5-0982f58efb71` +- updateProgress (Uint) → stateTypeId: `fd232036-9b2f-4c20-a6de-6227f3c8b947` + +#### Actions + +- performUpdate → actionTypeId: `2d8b7527-f8a3-41a7-aef2-706f8dd25fb1` + +--- + +### colorLight + +- ThingClassId: `b11a3f24-8011-42ca-939c-ab110c5f350e` + +#### States + +- connected (Bool) → stateTypeId: `e78b8264-d888-4cbd-bf90-2eaec0c83ee0` +- signalStrength (Uint) → stateTypeId: `84990224-7579-4d99-8a19-e98b62381881` +- power (Bool) → stateTypeId: `c3c02756-6cc7-455a-ae59-dd97b9e9a1e6` +- brightness (Int) → stateTypeId: `e81e7c79-96a4-4e80-ae85-d33278c75de0` +- colorTemperature (Int) → stateTypeId: `d78650c5-68d3-4d08-991c-cb054cdcf6fd` +- color (Color) → stateTypeId: `9e49f581-eed3-499f-8927-270dfde472e9` +- updateStatus (String) → stateTypeId: `8e6ff7a1-d1d7-4aaf-a719-7bb35c3b0f89` +- currentVersion (String) → stateTypeId: `54bfb270-59bf-434b-ac5c-539d05aaa4a6` +- availableVersion (String) → stateTypeId: `5201e76b-2cc2-4de3-a0f2-43aee3d5fad7` +- updateProgress (Uint) → stateTypeId: `01c7f30d-108b-4fe6-b9cc-6a0dca7e5c2f` + +#### Actions + +- power → actionTypeId: `c3c02756-6cc7-455a-ae59-dd97b9e9a1e6` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "c3c02756-6cc7-455a-ae59-dd97b9e9a1e6", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `e81e7c79-96a4-4e80-ae85-d33278c75de0` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e81e7c79-96a4-4e80-ae85-d33278c75de0", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `d78650c5-68d3-4d08-991c-cb054cdcf6fd` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d78650c5-68d3-4d08-991c-cb054cdcf6fd", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `9e49f581-eed3-499f-8927-270dfde472e9` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9e49f581-eed3-499f-8927-270dfde472e9", + "params": { + "color": "" + } + } + } + +- alert → actionTypeId: `b0175457-b763-45d6-89fd-573f279c40b3` +- performUpdate → actionTypeId: `c7c24bb7-9ad1-4584-bc4d-96ce848918da` + +--- + +### rollershutter + +- ThingClassId: `6b187fe0-a987-462d-90ac-c48efc0d0fc0` + +#### States + +- percentage (Int) → stateTypeId: `f954ffc7-a6aa-4d30-aee0-0484631c3344` +- moving (Bool) → stateTypeId: `fa9446ba-da30-4d49-8fb6-f410ecc7dba0` +- signalStrength (Uint) → stateTypeId: `67594d96-47a2-4360-a1b8-79e4f22f9ed0` +- connected (Bool) → stateTypeId: `7a49865d-5ea5-43be-b61f-4e454c48e87e` + +#### Actions + +- percentage → actionTypeId: `f954ffc7-a6aa-4d30-aee0-0484631c3344` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "f954ffc7-a6aa-4d30-aee0-0484631c3344", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `a0460180-e799-4bc6-83ba-11731ef124a3` +- stop → actionTypeId: `cbccf714-1188-4ac9-9c91-17fe2c99acb3` +- close → actionTypeId: `baf377c6-9fba-44cf-9f14-af0101f874b5` + +--- + +### dimmerSwitch2 + +- ThingClassId: `2b40aea0-e0f3-4cde-b034-3ae8a69a5d9d` + +#### States + +- connected (Bool) → stateTypeId: `45f75511-7d72-410e-aed0-5720cc497bf8` +- batteryLevel (Int) → stateTypeId: `cb6e045c-e305-4950-9cd4-fb3989912156` +- batteryCritical (Bool) → stateTypeId: `88cc3794-3e83-47d4-8889-0b3246336bf7` + + +--- + +### indoor + +- ThingClassId: `1c809049-04f2-4710-99f5-6ed379a2934f` + +#### States + +- updateTime (Int) → stateTypeId: `50da9f6b-c350-401c-a72e-2e4036f3975d` +- temperature (Double) → stateTypeId: `3cb25538-e463-40ae-92f9-8f34f0c06b92` +- temperatureMin (Double) → stateTypeId: `ae8bb713-8805-4efd-89a1-bca44a1f1690` +- temperatureMax (Double) → stateTypeId: `dd30507e-037b-4c74-bcca-e04b94c7c5fe` +- humidity (Double) → stateTypeId: `e2db5f01-196a-48d1-8874-6b8cbfe0d8c9` +- pressure (Double) → stateTypeId: `03b0a7b7-987d-4d3b-b3f0-21d9f92ad326` +- noise (Double) → stateTypeId: `906cea9d-1daf-4e9c-90b9-e40f43052a34` +- co2 (Double) → stateTypeId: `e5710bd1-79fa-4bd4-9052-8416aae909b9` +- signalStrength (Uint) → stateTypeId: `6ea906d4-5740-454d-a730-6fdb9fa0d624` +- connected (Bool) → stateTypeId: `d84ba5d7-5202-4786-b983-958b594c341f` + + +--- + +### shellyPlus1pm + +- ThingClassId: `4c497fe1-c399-463a-9a73-0c5f4ebac100` + +#### States + +- power (Bool) → stateTypeId: `bc425ba1-7776-4004-ba67-b256db7c511d` +- connected (Bool) → stateTypeId: `1b0ecbb2-5ed6-439e-81fb-5788ed8679e2` +- signalStrength (Uint) → stateTypeId: `bffa3472-a09f-4e85-9d31-0b966e415cd2` +- updateStatus (String) → stateTypeId: `c9515efd-f16a-4a29-97e1-516d1d5a6a93` +- currentVersion (String) → stateTypeId: `e3859bc0-f96d-4fa1-958b-0e087a39a0e6` +- availableVersion (String) → stateTypeId: `0dda20f7-e13e-494d-8351-c4401ded6c1e` +- totalEnergyConsumed (Double) → stateTypeId: `76e87d1f-bc65-4f63-b692-106861827633` +- currentPower (Double) → stateTypeId: `40e63755-7220-4c7c-9c6a-fe5d7e230a35` + +#### Actions + +- power → actionTypeId: `bc425ba1-7776-4004-ba67-b256db7c511d` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bc425ba1-7776-4004-ba67-b256db7c511d", + "params": { + "power": "" + } + } + } + +- reboot → actionTypeId: `b18211d8-528b-4bff-93c2-ce6a8b55759e` +- performUpdate → actionTypeId: `0cabc075-9abc-49be-a50d-e8a8d7637d03` + +--- + +### shellySmoke + +- ThingClassId: `7317eb8a-fa6d-41a3-9ff5-0da3feacc960` + +#### States + +- fireDetected (Bool) → stateTypeId: `a16585f7-7cc6-49b0-848c-d7da2237ba77` +- temperature (Double) → stateTypeId: `3e3606ba-8c2d-41b0-a434-171dccd97795` +- connected (Bool) → stateTypeId: `a1d0350f-d71d-4bd2-b5c7-f928d9c561bb` +- signalStrength (Uint) → stateTypeId: `dd7205f4-2431-4ed5-8127-d31800c442a7` +- batteryLevel (Int) → stateTypeId: `61090a03-b3ff-4b21-a5b8-2ad27bbdf1a5` +- batteryCritical (Bool) → stateTypeId: `1ce8e795-65a7-4e10-815d-0cb8e9f2ec7e` +- updateStatus (String) → stateTypeId: `34b839d7-0043-44c9-8bf9-136059141ff9` +- currentVersion (String) → stateTypeId: `4750db5f-6380-4528-9a5e-216c32dc1709` +- availableVersion (String) → stateTypeId: `0633052d-5b6c-480b-8208-23b25018c11f` + +#### Actions + +- performUpdate → actionTypeId: `09dc2e58-149c-4131-bd1b-e039a5133f72` + +--- + +### colorTemperatureLight + +- ThingClassId: `be811592-6fe7-41be-b924-4a0a7e33295b` + +#### States + +- connected (Bool) → stateTypeId: `95c072ba-516a-43b5-8d68-a4712c5989b6` +- signalStrength (Uint) → stateTypeId: `c6d12225-e5bb-4cc7-a69f-601b8ea07e70` +- power (Bool) → stateTypeId: `d815fd02-ebe2-456a-ba3f-6d649b90a094` +- brightness (Int) → stateTypeId: `7c4b6949-46ab-41f7-8a78-09e4eaf2a01f` +- colorTemperature (Int) → stateTypeId: `889f758f-f0b9-40ba-abe2-e2100188b86e` +- updateStatus (String) → stateTypeId: `99aa4d81-cd35-478c-88b8-cfa9e26bf8ae` +- currentVersion (String) → stateTypeId: `2bf3f759-4793-426b-ad05-bf4eb199e6de` +- availableVersion (String) → stateTypeId: `a5dc8a78-f818-461e-b0a7-97c6d2cdbd3c` +- updateProgress (Uint) → stateTypeId: `a2279a96-8c72-455e-a0c4-38db3fe76f17` + +#### Actions + +- power → actionTypeId: `d815fd02-ebe2-456a-ba3f-6d649b90a094` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d815fd02-ebe2-456a-ba3f-6d649b90a094", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `7c4b6949-46ab-41f7-8a78-09e4eaf2a01f` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7c4b6949-46ab-41f7-8a78-09e4eaf2a01f", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `889f758f-f0b9-40ba-abe2-e2100188b86e` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "889f758f-f0b9-40ba-abe2-e2100188b86e", + "params": { + "colorTemperature": "" + } + } + } + +- alert → actionTypeId: `a1677c81-a68a-4268-a289-1d62b31605e3` +- performUpdate → actionTypeId: `7aaa9757-3b78-4dd2-86a6-07b72883e0fa` + +--- + +### dishwasher + +- ThingClassId: `bb0fcd0b-9594-4368-99d9-3ad5e5a8136b` + +#### States + +- connected (Bool) → stateTypeId: `7c056989-d91b-492c-9206-ef77fb81b0c8` +- closed (Bool) → stateTypeId: `8cbb3746-7e04-4fc8-93eb-b774b606a057` +- operationState (String) → stateTypeId: `2b45fbfc-d3d7-4dd4-91f8-4a789405246e` +- remoteControlActivationState (Bool) → stateTypeId: `c8bf60d2-e8e3-44dd-9cea-c7bcbe17c2b1` +- remoteStartAllowanceState (Bool) → stateTypeId: `a8ce4a4e-fae3-4100-ad05-7c390b774b4a` +- progress (Int) → stateTypeId: `f0484744-a180-4a14-9c84-f49a489e3510` +- selectedProgram (String) → stateTypeId: `692ea106-557c-4870-b569-ab82a4909b2c` +- endTime (String) → stateTypeId: `f61cd171-2e8e-434c-98f9-e7fc7df1c928` + +#### Actions + +- start → actionTypeId: `7dc1e238-cdc2-42e8-9a2a-e93d497966d9` + - Parameters: + - startTime (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "7dc1e238-cdc2-42e8-9a2a-e93d497966d9", + "params": { + "startTime": "" + } + } + } + +- stop → actionTypeId: `c2c7e1bd-bf0f-4ded-93e0-e4d770f03f6f` + +--- + +### temperatureSensor + +- ThingClassId: `ff9657f3-4076-41b9-ae7d-537866fd0ddc` + +#### States + +- batteryCritical (Bool) → stateTypeId: `cf20355b-1640-4eda-b7e7-e3363c02bed6` +- connected (Bool) → stateTypeId: `13d91dce-cba3-452c-9618-3c505a56fbc2` +- signalStrength (Uint) → stateTypeId: `8f77c980-a1e0-4f4b-a042-1272a8fe3459` +- temperature (Double) → stateTypeId: `1ca9594f-fa48-4ea3-be0e-d8aea3e5ad0a` + + +--- + +### meter + +- ThingClassId: `c3cb53a4-32dd-434d-9d9c-aada41f8129c` + +#### States + +- connected (Bool) → stateTypeId: `b70b61a4-54cb-47ec-b62a-b498eb1f650e` +- currentPower (Double) → stateTypeId: `e5056ea1-88a2-410b-9c5e-6322aca4cb17` +- voltagePhaseA (Double) → stateTypeId: `267bc59f-1113-4aff-a502-4618a591aa16` +- voltagePhaseB (Double) → stateTypeId: `bbcedb80-30f1-493e-81f0-5f77f2847353` +- voltagePhaseC (Double) → stateTypeId: `8037557b-40dc-411b-8937-bcd1695f898a` +- currentPhaseA (Double) → stateTypeId: `a9673688-d84a-4848-8583-a70739130252` +- currentPhaseB (Double) → stateTypeId: `15632e49-95f9-496d-830c-53a31ca6d98e` +- currentPhaseC (Double) → stateTypeId: `10a24ba9-a57a-48a9-98f3-52671c09e855` +- totalEnergyProduced (Double) → stateTypeId: `ca14cca5-d9f0-49c5-a8f7-907d4c0825f0` +- totalEnergyConsumed (Double) → stateTypeId: `f3451818-48d2-42a5-94fd-ad094c06967f` +- currentPowerPhaseA (Double) → stateTypeId: `6dbbb062-447b-47d6-b2e4-dceac9aff795` +- currentPowerPhaseB (Double) → stateTypeId: `f230e78e-15b0-47a4-b494-bae65be00755` +- currentPowerPhaseC (Double) → stateTypeId: `56b5d550-d902-4c33-9288-8ee972735a75` +- frequency (Double) → stateTypeId: `9ff64b29-e023-4395-abd4-b6c366acfd9e` + + +--- + +### colorTemperatureLight + +- ThingClassId: `8a91f8e7-8162-437e-a664-9ed999e44e62` + +#### States + +- connected (Bool) → stateTypeId: `be8617fb-174f-4008-b60d-6a748b72c509` +- signalStrength (Uint) → stateTypeId: `e666efcb-564e-41a1-8585-d0d99665c8c0` +- power (Bool) → stateTypeId: `5f7c7a38-5dc0-44f2-ae25-576dd4b7740b` +- brightness (Int) → stateTypeId: `30643f4e-9f1a-4848-abb6-7ce4081f1878` +- colorTemperature (Int) → stateTypeId: `18f2b025-e638-4296-ade4-3dde89ffc5ab` +- updateStatus (String) → stateTypeId: `91464b14-5989-48d0-98bf-31dd61429cd6` +- currentVersion (String) → stateTypeId: `3fb91687-1cb1-4a86-817c-1c69be2bea1d` +- availableVersion (String) → stateTypeId: `c53a4c3d-0889-47c9-ac88-ca7ed92b28b8` +- updateProgress (Uint) → stateTypeId: `45c7b068-e19a-4d44-81eb-6d116496f1c9` + +#### Actions + +- power → actionTypeId: `5f7c7a38-5dc0-44f2-ae25-576dd4b7740b` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5f7c7a38-5dc0-44f2-ae25-576dd4b7740b", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `30643f4e-9f1a-4848-abb6-7ce4081f1878` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "30643f4e-9f1a-4848-abb6-7ce4081f1878", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `18f2b025-e638-4296-ade4-3dde89ffc5ab` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "18f2b025-e638-4296-ade4-3dde89ffc5ab", + "params": { + "colorTemperature": "" + } + } + } + +- alert → actionTypeId: `b6c75191-fd7d-475c-9ed0-40fe7bdfbc28` +- performUpdate → actionTypeId: `b805feb6-2920-4204-a4be-49f5890a7114` + +--- + +### doorSensor + +- ThingClassId: `adc6d53a-34a9-486e-a501-34b84395201d` + +#### States + +- closed (Bool) → stateTypeId: `99bfe225-cfca-4fe6-ab86-b50a4dba0345` +- batteryLevel (Int) → stateTypeId: `4d3fdaee-eca3-42c1-8aa8-f315d6aed6e5` +- batteryCritical (Bool) → stateTypeId: `56c95d21-11da-436a-9631-9f667d433076` +- connected (Bool) → stateTypeId: `564245ee-34b8-4af3-9ede-8661c5c8bf7e` +- signalStrength (Uint) → stateTypeId: `d2ae9d3b-fad0-412a-9c0b-f31bbf0658d2` + + +--- + +### lumiRelay + +- ThingClassId: `8e381cb1-2caa-4261-be77-e76a538bf67a` + +#### States + +- connected (Bool) → stateTypeId: `b12d1765-0b2e-4e95-b5ef-780d27fdda44` +- signalStrength (Uint) → stateTypeId: `bc22b220-003f-4f19-9aef-21c4049c5a0e` +- version (String) → stateTypeId: `ca1a8d08-4423-4401-bca7-d52b11e54ed9` +- relay1 (Bool) → stateTypeId: `486e0df0-cbda-4b67-b844-6f70e5af6891` +- relay2 (Bool) → stateTypeId: `2caa4f0f-661e-4b59-83e5-8417058e48fd` + +#### Actions + +- relay1 → actionTypeId: `486e0df0-cbda-4b67-b844-6f70e5af6891` + - Parameters: + - relay1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "486e0df0-cbda-4b67-b844-6f70e5af6891", + "params": { + "relay1": "" + } + } + } + +- relay2 → actionTypeId: `2caa4f0f-661e-4b59-83e5-8417058e48fd` + - Parameters: + - relay2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2caa4f0f-661e-4b59-83e5-8417058e48fd", + "params": { + "relay2": "" + } + } + } + + +--- + +### lumiHTSensor + +- ThingClassId: `dfabab0e-d483-43f8-82c6-720899e70c86` + +#### States + +- connected (Bool) → stateTypeId: `eb09e3f4-44ea-4ca9-9eac-9a693595fe37` +- signalStrength (Uint) → stateTypeId: `0cb72e84-01c4-4041-9aee-1dabf26a062e` +- version (String) → stateTypeId: `968ff8bb-b852-48d8-bde2-28cfa810b20e` +- temperature (Double) → stateTypeId: `152b475e-6bd8-4e6f-8765-0636c0bc8e9d` +- humidity (Double) → stateTypeId: `e6559d9e-2c64-485d-bb2b-8a9aec0ac656` +- batteryLevel (Int) → stateTypeId: `4091259a-f1db-435e-9296-9eeb17b6f835` +- batteryCritical (Bool) → stateTypeId: `2c59a1f8-f33e-4dab-83fe-46b95d107fed` + + +--- + +### zone + +- ThingClassId: `1a7bb944-fb9c-490a-8a4c-794b27282292` + +#### States + +- connected (Bool) → stateTypeId: `9f45a703-6a15-447c-a77a-0df731cda48e` +- mode (String) → stateTypeId: `4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b` +- tadoMode (String) → stateTypeId: `8b800998-5c2d-4940-9d0e-036979cf49ca` +- power (Bool) → stateTypeId: `e886377d-34b7-4908-ad0d-ed463fc6181d` +- windowOpenDetected (Bool) → stateTypeId: `c7a04e26-bb22-406e-b117-262bdb8b9c0e` +- temperature (Double) → stateTypeId: `80098178-7d92-43dd-a216-23704cc0eaa2` +- targetTemperature (Double) → stateTypeId: `684fcc62-f12b-4669-988e-4b79f153b0f2` +- humidity (Double) → stateTypeId: `0faaaff1-2a33-44ec-b68d-d8855f584b02` + +#### Actions + +- mode → actionTypeId: `4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b` + - Parameters: + - mode (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b", + "params": { + "mode": "" + } + } + } + +- power → actionTypeId: `e886377d-34b7-4908-ad0d-ed463fc6181d` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e886377d-34b7-4908-ad0d-ed463fc6181d", + "params": { + "power": "" + } + } + } + +- targetTemperature → actionTypeId: `684fcc62-f12b-4669-988e-4b79f153b0f2` + - Parameters: + - targetTemperature (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "684fcc62-f12b-4669-988e-4b79f153b0f2", + "params": { + "targetTemperature": "" + } + } + } + + +--- + +### lifxAccount + +- ThingClassId: `387c87f6-3e5b-4d6a-ba4d-372d0efad79f` + +#### States + +- loggedIn (Bool) → stateTypeId: `0db34069-5de0-4233-baec-27f039228524` +- userDisplayName (String) → stateTypeId: `554afd9b-a2ec-4d28-9065-2b9ab3a9e3b2` +- connected (Bool) → stateTypeId: `3e7b358b-d7de-4db4-8a3a-b9860eae186f` + + +--- + +### sonoff_quad + +- ThingClassId: `ae845ec9-be61-4bdf-9015-4c156f937da7` + +#### States + +- connected (Bool) → stateTypeId: `5b422d28-9f60-4ea9-ab23-42a0ec605b9e` +- signalStrength (Uint) → stateTypeId: `d80d8f37-f3c0-4364-998b-312339fd1fa8` +- powerCH1 (Bool) → stateTypeId: `e8fd95c3-2323-40d8-89cf-40e0068977d8` +- powerCH2 (Bool) → stateTypeId: `baf0ca62-7f09-45ea-aea1-6de34764e6cb` +- powerCH3 (Bool) → stateTypeId: `8b939d15-3e45-40aa-bb53-9b3fb47f3cb3` +- powerCH4 (Bool) → stateTypeId: `4060baa0-2b11-4905-908c-b6f1c3b6a892` + +#### Actions + +- powerCH1 → actionTypeId: `e8fd95c3-2323-40d8-89cf-40e0068977d8` + - Parameters: + - powerCH1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e8fd95c3-2323-40d8-89cf-40e0068977d8", + "params": { + "powerCH1": "" + } + } + } + +- powerCH2 → actionTypeId: `baf0ca62-7f09-45ea-aea1-6de34764e6cb` + - Parameters: + - powerCH2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "baf0ca62-7f09-45ea-aea1-6de34764e6cb", + "params": { + "powerCH2": "" + } + } + } + +- powerCH3 → actionTypeId: `8b939d15-3e45-40aa-bb53-9b3fb47f3cb3` + - Parameters: + - powerCH3 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "8b939d15-3e45-40aa-bb53-9b3fb47f3cb3", + "params": { + "powerCH3": "" + } + } + } + +- powerCH4 → actionTypeId: `4060baa0-2b11-4905-908c-b6f1c3b6a892` + - Parameters: + - powerCH4 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4060baa0-2b11-4905-908c-b6f1c3b6a892", + "params": { + "powerCH4": "" + } + } + } + + +--- + +### shellyPlusSmoke + +- ThingClassId: `8013cb36-2375-4d52-b042-4b61a290be3b` + +#### States + +- fireDetected (Bool) → stateTypeId: `12cf10d4-c74f-49e3-ac5a-3647ffb0cc64` +- mute (Bool) → stateTypeId: `724cd341-95b3-4bc6-b9c9-d8e85c0f8b7a` +- connected (Bool) → stateTypeId: `8e5095d7-ab1c-42ba-934d-8e6c290f6b49` +- signalStrength (Uint) → stateTypeId: `bc08e45b-8976-45da-9f26-80a0cdf92a3d` +- batteryLevel (Int) → stateTypeId: `201dc87b-7fd3-4252-9bc8-e3cc3ac88f77` +- batteryCritical (Bool) → stateTypeId: `735eca96-b388-49f5-8e38-41361a4b85ad` +- updateStatus (String) → stateTypeId: `7626bcf4-5a16-400a-bc00-6deb64b699c1` +- currentVersion (String) → stateTypeId: `ce6114c9-d422-4e32-bfc3-4f3435915da5` +- availableVersion (String) → stateTypeId: `39a37cb7-9485-4895-842f-348770c9cdc3` + +#### Actions + +- mute → actionTypeId: `724cd341-95b3-4bc6-b9c9-d8e85c0f8b7a` + - Parameters: + - mute (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "724cd341-95b3-4bc6-b9c9-d8e85c0f8b7a", + "params": { + "mute": "" + } + } + } + +- performUpdate → actionTypeId: `ac36efdb-1929-4e13-befd-b981439eaeab` + +--- + +### fridge + +- ThingClassId: `6cbf309d-bde8-4e6e-ad6d-b85c8fc1843f` + +#### States + +- connected (Bool) → stateTypeId: `16931afd-44f6-4b13-bd3e-f6d30ac54ea0` +- closed (Bool) → stateTypeId: `99bba70c-5ead-4076-8b71-720a931668a4` +- fridgeTemperature (Int) → stateTypeId: `bc13977e-0ea1-4804-af00-311ae62c6c06` +- fridgeTemperatureSetting (Int) → stateTypeId: `5a1a8665-835b-4909-8f79-42874c5c4643` +- freezerTemperature (Int) → stateTypeId: `523adac4-a98a-49b5-96d4-d506831aeab7` +- freezerTemperatureSetting (Int) → stateTypeId: `081ceedc-7c09-4cd9-8015-13a59ce653bf` + + +--- + +### tasmotaBlinds + +- ThingClassId: `70ae35db-68bf-42d7-872a-85582d27d128` + +#### States + +- connected (Bool) → stateTypeId: `686ad054-0ee5-4135-8f2d-dda993532262` +- signalStrength (Uint) → stateTypeId: `dc58b863-1e84-4469-b226-cd4349d82050` + +#### Actions + +- open → actionTypeId: `df8025a0-3553-413b-9986-61248d7a8440` +- close → actionTypeId: `b548fde8-1fdc-4605-9507-964076437ffb` +- stop → actionTypeId: `f985c1ce-3ddd-415d-a10b-060fc0fa995c` + +--- + +### shellyTrv + +- ThingClassId: `52932a47-38cd-4dce-b338-88122ce4ab8a` + +#### States + +- temperature (Double) → stateTypeId: `d9a26a08-5735-403a-ab02-7638bd0a471f` +- targetTemperature (Double) → stateTypeId: `9800babf-a6cc-4eda-b42e-8f5481b61aea` +- valvePosition (Uint) → stateTypeId: `e442ca7a-ee17-482b-aae4-579915029abf` +- heatingOn (Bool) → stateTypeId: `1935b7fa-72a5-4aee-877e-d656cd79d688` +- boost (Bool) → stateTypeId: `ef74da4d-70f4-49cd-9697-a8e2bf25dee1` +- windowOpen (Bool) → stateTypeId: `a5944856-6b0f-4b45-9d9f-fe0f3c2de8aa` +- connected (Bool) → stateTypeId: `92fa20b0-2b66-4d68-8819-6eeb43f1c0fb` +- signalStrength (Uint) → stateTypeId: `42e080f3-c00c-49d2-b046-7bd26331b8f0` +- batteryLevel (Int) → stateTypeId: `f45dff98-41ac-43bb-a005-24294973611b` +- batteryCritical (Bool) → stateTypeId: `48c3a619-331e-44eb-b080-877fdcfd03f6` +- updateStatus (String) → stateTypeId: `113625e0-e171-48e6-a9ca-6a13b75b9234` +- currentVersion (String) → stateTypeId: `ad5cdb22-42ce-4592-ad72-61a854981f69` +- availableVersion (String) → stateTypeId: `f3e00825-cd05-45e6-bc99-457bf74255c5` + +#### Actions + +- targetTemperature → actionTypeId: `9800babf-a6cc-4eda-b42e-8f5481b61aea` + - Parameters: + - targetTemperature (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9800babf-a6cc-4eda-b42e-8f5481b61aea", + "params": { + "targetTemperature": "" + } + } + } + +- valvePosition → actionTypeId: `e442ca7a-ee17-482b-aae4-579915029abf` + - Parameters: + - valvePosition (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "e442ca7a-ee17-482b-aae4-579915029abf", + "params": { + "valvePosition": "" + } + } + } + +- boost → actionTypeId: `ef74da4d-70f4-49cd-9697-a8e2bf25dee1` + - Parameters: + - boost (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ef74da4d-70f4-49cd-9697-a8e2bf25dee1", + "params": { + "boost": "" + } + } + } + +- performUpdate → actionTypeId: `27e4c7f5-1828-443c-a18d-6d79382e001d` +- reboot → actionTypeId: `4cef8e3a-853b-4313-8f70-d22122e7bb04` + +--- + +### speedwireInverter + +- ThingClassId: `b63a0669-f2ac-4769-abea-e14cafb2309a` + +#### States + +- connected (Bool) → stateTypeId: `aaff72c3-c70a-4a2f-bed1-89f38cebe442` +- voltagePhaseA (Double) → stateTypeId: `6ef4eb16-a3d6-4bc9-972d-5e7cb81173a5` +- voltagePhaseB (Double) → stateTypeId: `d9a5768b-1bf5-4933-810d-84dd7a688f71` +- voltagePhaseC (Double) → stateTypeId: `fc168dc6-eecf-40b4-b214-3e28da0dbb12` +- currentPhaseA (Double) → stateTypeId: `2a6c59ca-853a-47d6-96fb-0c85edf32f52` +- currentPhaseB (Double) → stateTypeId: `4db96fec-737c-4c4b-bf07-5ef2fd62508a` +- currentPhaseC (Double) → stateTypeId: `0f23fb0e-a440-4ac2-9aff-896bc65feb2c` +- currentPower (Double) → stateTypeId: `d7ceb482-5df8-4c0c-82bd-62ce7ba22c43` +- currentPowerMpp1 (Double) → stateTypeId: `b366f680-6134-488b-8362-b1b824a8daca` +- currentPowerMpp2 (Double) → stateTypeId: `87d9b654-5558-47a3-9db9-ffd7c23b4774` +- totalEnergyProduced (Double) → stateTypeId: `51cadd66-2cf1-485a-a2a9-191d11abfbd1` +- energyProducedToday (Double) → stateTypeId: `e8bc8f81-e5c5-4900-b429-93fcaa262fcb` +- frequency (Double) → stateTypeId: `fdccf5de-7413-4480-9ca0-1151665dede8` +- firmwareVersion (String) → stateTypeId: `6d76cc7b-9e00-4561-be7b-4e2a6b8f7b66` + + +--- + +### foh + +- ThingClassId: `692bc4be-07b8-4b77-ab0b-a36185b17d76` + +#### States + +- connected (Bool) → stateTypeId: `840b220c-656b-4f56-bbaa-ce818cffad64` + + +--- + +### cleaningRobot + +- ThingClassId: `a7bfe39f-14d1-481a-a255-eea7f40e0c07` + +#### States + +- connected (Bool) → stateTypeId: `f0f81c21-b10e-44ef-8606-600ebaf646e0` +- operationState (String) → stateTypeId: `ac2b896e-d7ff-4b17-affa-ad5bdc486640` + + +--- + +### colorLight + +- ThingClassId: `886a856d-1373-4fff-ac34-dbdfa5531c2a` + +#### States + +- power (Bool) → stateTypeId: `1a366728-e67c-4767-b125-bec5dd6515f5` +- brightness (Int) → stateTypeId: `392e418c-d1e0-45ae-ba90-0fbc53227d70` +- colorTemperature (Int) → stateTypeId: `4db6a6d9-f7f4-4dca-85c4-4f5f8e57c897` +- color (Color) → stateTypeId: `ecce80e5-c992-49cd-bd67-7721d2efad2b` + +#### Actions + +- power → actionTypeId: `1a366728-e67c-4767-b125-bec5dd6515f5` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1a366728-e67c-4767-b125-bec5dd6515f5", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `392e418c-d1e0-45ae-ba90-0fbc53227d70` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "392e418c-d1e0-45ae-ba90-0fbc53227d70", + "params": { + "brightness": "" + } + } + } + +- colorTemperature → actionTypeId: `4db6a6d9-f7f4-4dca-85c4-4f5f8e57c897` + - Parameters: + - colorTemperature (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4db6a6d9-f7f4-4dca-85c4-4f5f8e57c897", + "params": { + "colorTemperature": "" + } + } + } + +- color → actionTypeId: `ecce80e5-c992-49cd-bd67-7721d2efad2b` + - Parameters: + - color (Color) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "ecce80e5-c992-49cd-bd67-7721d2efad2b", + "params": { + "color": "" + } + } + } + + +--- + +### shellyEm3 + +- ThingClassId: `ba293550-d2af-4463-b973-e1812ab67b96` + +#### States + +- connected (Bool) → stateTypeId: `08bd7743-af98-4328-bbca-64280afc5a87` +- signalStrength (Uint) → stateTypeId: `0f147c29-aefd-4926-a979-35dfeaec12c0` +- totalEnergyConsumed (Double) → stateTypeId: `67050a5a-cc78-4d11-a7d9-a9db528029ff` +- totalEnergyProduced (Double) → stateTypeId: `088cb7df-9187-4206-ae5b-18a00e4f1969` +- currentPower (Double) → stateTypeId: `3fc2a87f-cd33-4d1f-b7a6-75ffcb4e7cc4` +- currentPowerPhaseA (Double) → stateTypeId: `432ba180-936d-4700-907e-766264bfdd35` +- powerFactorPhaseA (Double) → stateTypeId: `50be490b-ba5d-4b1f-806c-9e15b915c1eb` +- currentPhaseA (Double) → stateTypeId: `5fa79319-756b-4b2c-87b1-59ff996b8435` +- voltagePhaseA (Double) → stateTypeId: `5977ffab-cdcf-409c-940b-aa0a59de84a5` +- energyConsumedPhaseA (Double) → stateTypeId: `ba25ef68-bb52-4e96-a8fb-137aae966104` +- energyProducedPhaseA (Double) → stateTypeId: `34562cd3-b178-4f68-903d-a01e20d0ad76` +- currentPowerPhaseB (Double) → stateTypeId: `02edeedb-8a93-41f8-8bc5-09031b7d2d4d` +- powerFactorPhaseB (Double) → stateTypeId: `f56504bb-0c6c-4425-831c-771b23aadf19` +- currentPhaseB (Double) → stateTypeId: `a4151601-fe77-418a-a2c1-6376e32da3bd` +- voltagePhaseB (Double) → stateTypeId: `7c846993-fb06-48ef-987c-7b35d9671070` +- energyConsumedPhaseB (Double) → stateTypeId: `6636e6a0-e3ca-4654-9506-4302c4e8eed7` +- energyProducedPhaseB (Double) → stateTypeId: `d70a0d1a-cac1-4250-85fa-4859ad2dc947` +- currentPowerPhaseC (Double) → stateTypeId: `82277a4e-49cc-45f4-8b29-470ce99333b6` +- powerFactorPhaseC (Double) → stateTypeId: `2e2c622f-1575-4d0b-a0c0-78bc03748c1e` +- currentPhaseC (Double) → stateTypeId: `ab78aa9c-aa73-4f5d-8d21-38c83c5e9e7c` +- voltagePhaseC (Double) → stateTypeId: `cd7af1b2-d5f0-4c2e-b85c-84f23ae1fbb9` +- energyConsumedPhaseC (Double) → stateTypeId: `452c2159-aa2f-4217-80e5-4b492b69671e` +- energyProducedPhaseC (Double) → stateTypeId: `de248e26-b617-4d22-9175-752e2d695274` +- updateStatus (String) → stateTypeId: `8e25bbc2-54d8-4aa1-8ca5-f61b5f17a03b` +- currentVersion (String) → stateTypeId: `f9ddb9f1-1251-484e-bdce-531d584d8f6a` +- availableVersion (String) → stateTypeId: `27f1e394-642f-4a90-88bf-1ef9ba88b10b` +- power (Bool) → stateTypeId: `639dda4c-e354-43ca-a785-fbe6806986e2` + +#### Actions + +- power → actionTypeId: `639dda4c-e354-43ca-a785-fbe6806986e2` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "639dda4c-e354-43ca-a785-fbe6806986e2", + "params": { + "power": "" + } + } + } + +- performUpdate → actionTypeId: `f230d689-53f8-4542-9b20-9993b1c2eb27` +- reset → actionTypeId: `87772e43-1bf7-496b-b8be-46db39f71700` + +--- + +### cooling + +- ThingClassId: `09edbc07-d382-48a4-9b16-99992014aff9` + +#### States + +- power (Bool) → stateTypeId: `918cfd2c-6692-4faa-acc6-18ebf93611ec` + +#### Actions + +- power → actionTypeId: `918cfd2c-6692-4faa-acc6-18ebf93611ec` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "918cfd2c-6692-4faa-acc6-18ebf93611ec", + "params": { + "power": "" + } + } + } + + +--- + +### gpioOutputRpi + +- ThingClassId: `3885c520-e202-4435-88f6-3c35c362b2e6` + +#### States + +- power (Bool) → stateTypeId: `06843766-358e-44b0-8d52-2b46ef98459a` + +#### Actions + +- power → actionTypeId: `06843766-358e-44b0-8d52-2b46ef98459a` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "06843766-358e-44b0-8d52-2b46ef98459a", + "params": { + "power": "" + } + } + } + + +--- + +### counterBbb + +- ThingClassId: `3e311ef1-60c4-4b0e-a2fb-186bff9bd792` + +#### States + +- counter (Int) → stateTypeId: `fb5181d0-644b-4ab7-afa0-b7ddc8951526` + + +--- + +### netatmoConnection + +- ThingClassId: `728d5a67-27a3-400e-b83c-2765f5196f69` + +#### States + +- connected (Bool) → stateTypeId: `2f79bc1d-27ed-480a-b583-728363c83ea6` +- loggedIn (Bool) → stateTypeId: `d1ca8579-5d5a-4372-9139-4b083efead2e` +- username (String) → stateTypeId: `b9c98ae6-3687-4279-8fda-bc02c7b7ea38` + + +--- + +### fyrtur + +- ThingClassId: `0d2286dd-23aa-4fba-b905-c833f0c7d486` + +#### States + +- connected (Bool) → stateTypeId: `c44b7fb4-d87b-4fd0-b4c6-5131f3156751` +- signalStrength (Uint) → stateTypeId: `af6e5985-c185-4fe0-a976-a41f7040f4f4` +- moving (Bool) → stateTypeId: `01ade6d2-f778-49e9-8585-3bc8e8391364` +- percentage (Int) → stateTypeId: `51e6dd8a-769f-4096-8839-ad691ee85424` +- currentVersion (String) → stateTypeId: `da474607-f49c-4a07-9310-33d93e890647` +- updateStatus (String) → stateTypeId: `04bd682b-e1b9-491b-b771-22ba44ddc703` +- availableVersion (String) → stateTypeId: `bca2f9c8-5bd9-4c8b-a57d-92dda2c10bc5` +- updateProgress (Uint) → stateTypeId: `eeae6626-d53d-42ad-99e2-6f04bd8770b2` + +#### Actions + +- percentage → actionTypeId: `51e6dd8a-769f-4096-8839-ad691ee85424` + - Parameters: + - percentage (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "51e6dd8a-769f-4096-8839-ad691ee85424", + "params": { + "percentage": "" + } + } + } + +- open → actionTypeId: `9c4074bc-fc91-42da-acba-003ad4bf9db6` +- close → actionTypeId: `9bf470ff-8afa-4595-969d-fddf458d266f` +- stop → actionTypeId: `03a0253f-a2b7-412a-933e-f55028e310ab` +- performUpdate → actionTypeId: `c2f10178-976e-4eba-b191-202bfc0bf95b` + +--- + +### smartButton + +- ThingClassId: `1e34a056-9f37-4741-b249-a5eca7a4ab4e` + +#### States + +- connected (Bool) → stateTypeId: `b449cca5-19a0-483f-b4bd-b9b43b4f8ed4` +- batteryLevel (Int) → stateTypeId: `a0a1b480-6822-49bc-b1b1-50c39764d255` +- batteryCritical (Bool) → stateTypeId: `7c1fd7c1-f322-4be7-9c22-7d14d0ec38ea` + + +--- + +### doorSensor + +- ThingClassId: `714e2bd2-c100-4f2a-b13e-c9d5c87f7adb` + +#### States + +- connected (Bool) → stateTypeId: `fd09cac9-e98a-4161-80c5-d63d813e466e` +- signalStrength (Uint) → stateTypeId: `70f6e8c9-3d4c-4b03-879b-9a469f2f3b58` +- version (String) → stateTypeId: `68862900-b07c-4bd7-be85-64b2ce5384f9` +- closed (Bool) → stateTypeId: `279e7781-19cf-4628-a9da-efbe51c6e439` +- temperature (Double) → stateTypeId: `be94e9a8-b225-4610-9a49-686967e84a28` +- batteryCritical (Bool) → stateTypeId: `14907cf7-0acf-4dcb-b397-bd7bd3055479` +- updateStatus (String) → stateTypeId: `a59533d3-6974-47a1-aca7-a2483981a134` +- currentVersion (String) → stateTypeId: `5ca4251c-4f35-405c-bf16-373c3f791ada` +- availableVersion (String) → stateTypeId: `a3ec91c5-5348-4eb5-99a1-f0303227bee5` +- updateProgress (Uint) → stateTypeId: `9e4b4603-470c-4bea-ae5c-0dbeaf6da570` + +#### Actions + +- performUpdate → actionTypeId: `51d98b39-9d88-4947-be46-72c7ebde2a79` + +--- + +### soundtouch + +- ThingClassId: `f9b7a3f5-6353-48b1-afc1-66f914412f82` + +#### States + +- connected (Bool) → stateTypeId: `09dfbd40-c97c-4a20-9ecd-f80e389a4864` +- power (Bool) → stateTypeId: `5bac4ad7-f55c-4301-8d72-f2783d9909ff` +- mute (Bool) → stateTypeId: `bc98cdb0-4d0e-48ca-afc7-922e49bb7813` +- volume (Int) → stateTypeId: `9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9` +- playerType (String) → stateTypeId: `f784072d-b8da-4c8f-9497-dccf38aa9b16` +- playbackStatus (String) → stateTypeId: `2dd512b7-40c2-488e-8d4f-6519edaa6f74` +- title (String) → stateTypeId: `f2209fec-cceb-46ad-8189-4caf42166e6b` +- artist (String) → stateTypeId: `8cb920a3-3bf1-4231-92d4-8ac27e7b3d65` +- collection (String) → stateTypeId: `ce399eec-9f6a-4903-9916-0e90e38b255e` +- artwork (String) → stateTypeId: `44304c82-c2f6-433b-b62b-815382617d0b` +- shuffle (Bool) → stateTypeId: `5913aa2a-629d-4de5-bf44-a4a1f130c118` +- repeat (String) → stateTypeId: `bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e` +- source (String) → stateTypeId: `f4684de8-ff5f-41f3-a5c3-f5e5754519d2` +- bass (Int) → stateTypeId: `91bc53ec-4f3b-438d-8e32-129b7c27aae4` + +#### Actions + +- power → actionTypeId: `5bac4ad7-f55c-4301-8d72-f2783d9909ff` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5bac4ad7-f55c-4301-8d72-f2783d9909ff", + "params": { + "power": "" + } + } + } + +- mute → actionTypeId: `bc98cdb0-4d0e-48ca-afc7-922e49bb7813` + - Parameters: + - mute (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bc98cdb0-4d0e-48ca-afc7-922e49bb7813", + "params": { + "mute": "" + } + } + } + +- volume → actionTypeId: `9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9` + - Parameters: + - volume (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9", + "params": { + "volume": "" + } + } + } + +- playbackStatus → actionTypeId: `2dd512b7-40c2-488e-8d4f-6519edaa6f74` + - Parameters: + - playbackStatus (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2dd512b7-40c2-488e-8d4f-6519edaa6f74", + "params": { + "playbackStatus": "" + } + } + } + +- shuffle → actionTypeId: `5913aa2a-629d-4de5-bf44-a4a1f130c118` + - Parameters: + - shuffle (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5913aa2a-629d-4de5-bf44-a4a1f130c118", + "params": { + "shuffle": "" + } + } + } + +- repeat → actionTypeId: `bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e` + - Parameters: + - repeat (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e", + "params": { + "repeat": "" + } + } + } + +- bass → actionTypeId: `91bc53ec-4f3b-438d-8e32-129b7c27aae4` + - Parameters: + - bass (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "91bc53ec-4f3b-438d-8e32-129b7c27aae4", + "params": { + "bass": "" + } + } + } + +- skipBack → actionTypeId: `a180807d-1265-4831-9d86-a421767418dd` +- stop → actionTypeId: `ae3cbe03-ee3e-410e-abbd-efabc2402198` +- play → actionTypeId: `4d2ee668-a2e3-4795-8b96-0c800b703b46` +- pause → actionTypeId: `3cf341cb-fe63-40bc-a450-9678d18e91e3` +- skipNext → actionTypeId: `85d7126a-b123-4a28-aeb4-d84bcfb4d14f` +- savePreset → actionTypeId: `6c47e736-e2e0-47d4-9b96-7f56d2342b4d` + - Parameters: + - presetNumber (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "6c47e736-e2e0-47d4-9b96-7f56d2342b4d", + "params": { + "presetNumber": "" + } + } + } + +- alert → actionTypeId: `de24c5ec-1e36-4149-bfaa-71fb05264aa3` + - Parameters: + - sound (String) + - message (String) + - volume (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "de24c5ec-1e36-4149-bfaa-71fb05264aa3", + "params": { + "sound": "", + "message": "", + "volume": "" + } + } + } + +- increaseVolume → actionTypeId: `d0169ff4-f5a5-4db2-8ef0-b819d3022bde` + - Parameters: + - step (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d0169ff4-f5a5-4db2-8ef0-b819d3022bde", + "params": { + "step": "" + } + } + } + +- decreaseVolume → actionTypeId: `30873274-7662-42ec-ac31-2ef709e430ca` + - Parameters: + - step (Uint) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "30873274-7662-42ec-ac31-2ef709e430ca", + "params": { + "step": "" + } + } + } + + +--- + +### fireSensor + +- ThingClassId: `14e458d8-0480-4d2f-8e8b-2371cd4136f2` + +#### States + +- fireDetected (Bool) → stateTypeId: `4bb41637-8469-4aea-ad46-6cef3ba3afa3` + +#### Actions + +- fireDetected → actionTypeId: `4bb41637-8469-4aea-ad46-6cef3ba3afa3` + - Parameters: + - fireDetected (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "4bb41637-8469-4aea-ad46-6cef3ba3afa3", + "params": { + "fireDetected": "" + } + } + } + + +--- + +### lumiWeatherSensor + +- ThingClassId: `0b582616-0b05-4ac9-8b59-51b66079b571` + +#### States + +- connected (Bool) → stateTypeId: `003f7e37-326b-455d-828b-5a9493b56a69` +- signalStrength (Uint) → stateTypeId: `684f642e-08ed-4912-b7a9-597baef400c0` +- version (String) → stateTypeId: `cc644362-19ca-489d-8122-4d1bc55f100f` +- temperature (Double) → stateTypeId: `b1641cec-3bf6-4654-b9c0-b81acb3b4481` +- humidity (Double) → stateTypeId: `27a1e85a-f654-48d8-905d-05b3e2bc499e` +- pressure (Double) → stateTypeId: `7c3861f3-a9db-407e-9459-90a511d7f797` +- batteryLevel (Int) → stateTypeId: `706eb697-4265-440d-a79a-11df3f6db335` +- batteryCritical (Bool) → stateTypeId: `dc419970-7bb4-404f-8146-232b46668778` + + +--- + +### customMail + +- ThingClassId: `f4844c97-7ca6-4349-904e-ff9749a9fe74` + +#### Actions + +- notify → actionTypeId: `054613b0-3666-4dad-9252-e0ebca187edc` + - Parameters: + - title (String) + - body (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "054613b0-3666-4dad-9252-e0ebca187edc", + "params": { + "title": "", + "body": "" + } + } + } + + +--- + +### waterLevelSensor + +- ThingClassId: `f1576df0-fb45-4bf0-89fa-a83c4118c326` + +#### States + +- waterLevel (Double) → stateTypeId: `07563165-e42d-4d0f-ac60-31cdd19170f2` +- input (Double) → stateTypeId: `d344887d-da5d-4742-83bd-608754b2d0aa` + +#### Actions + +- input → actionTypeId: `d344887d-da5d-4742-83bd-608754b2d0aa` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d344887d-da5d-4742-83bd-608754b2d0aa", + "params": { + "input": "" + } + } + } + + +--- + +### shellyPowerMeterChannel + +- ThingClassId: `e2d2f11b-922f-4ff0-81e1-6fbf4c965521` + +#### States + +- connected (Bool) → stateTypeId: `1524dc63-7df8-47fc-a466-69ca896db361` +- signalStrength (Uint) → stateTypeId: `c3d6a520-dad3-463e-b321-91fa1dff6e07` +- totalEnergyConsumed (Double) → stateTypeId: `670b966c-f882-4846-86ce-54db7da68917` +- currentPower (Double) → stateTypeId: `9d6a6965-3024-424d-b71b-cf1358a2ece1` + + +--- + +### coSensor + +- ThingClassId: `262a29b2-b5df-4ee2-822c-e672f50d97b6` + +#### States + +- co (Double) → stateTypeId: `9eebe9d2-b34b-4847-bc25-109e2b8cab24` +- input (Double) → stateTypeId: `cf3ec5ec-7098-47c5-9666-b3c52eba1b1d` + +#### Actions + +- co → actionTypeId: `9eebe9d2-b34b-4847-bc25-109e2b8cab24` + - Parameters: + - co (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9eebe9d2-b34b-4847-bc25-109e2b8cab24", + "params": { + "co": "" + } + } + } + +- input → actionTypeId: `cf3ec5ec-7098-47c5-9666-b3c52eba1b1d` + - Parameters: + - input (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "cf3ec5ec-7098-47c5-9666-b3c52eba1b1d", + "params": { + "input": "" + } + } + } + + +--- + +### dimmableLight + +- ThingClassId: `b2eeb554-ca5c-46b5-a897-bc443fb186ea` + +#### States + +- connected (Bool) → stateTypeId: `2dc93a53-c506-41a5-9242-b5d045f51c40` +- signalStrength (Uint) → stateTypeId: `ab75980d-12f5-4ddc-be2d-dc54514a3b75` +- version (String) → stateTypeId: `9390d2a6-7a17-4b42-8b35-cdb8b3d4d723` +- power (Bool) → stateTypeId: `19d82a60-0009-4cff-966d-3b1c4fe358e1` +- brightness (Int) → stateTypeId: `6376376f-3221-4e6b-bce3-41872a6fc98a` + +#### Actions + +- power → actionTypeId: `19d82a60-0009-4cff-966d-3b1c4fe358e1` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "19d82a60-0009-4cff-966d-3b1c4fe358e1", + "params": { + "power": "" + } + } + } + +- brightness → actionTypeId: `6376376f-3221-4e6b-bce3-41872a6fc98a` + - Parameters: + - brightness (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "6376376f-3221-4e6b-bce3-41872a6fc98a", + "params": { + "brightness": "" + } + } + } + +- alert → actionTypeId: `a04d9e2b-e854-4361-bb34-2da74f8f028f` + +--- + +### ventilationFlow + +- ThingClassId: `a7eec5f7-3cc3-4edb-baa7-1f6b6f935bf8` + +#### States + +- power (Bool) → stateTypeId: `adf9a8dd-da13-4070-a3bd-9d4be26ebcd1` +- flowRate (Int) → stateTypeId: `5a273bba-0bef-49fb-870a-7f2df8fc14b6` + +#### Actions + +- power → actionTypeId: `adf9a8dd-da13-4070-a3bd-9d4be26ebcd1` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "adf9a8dd-da13-4070-a3bd-9d4be26ebcd1", + "params": { + "power": "" + } + } + } + +- flowRate → actionTypeId: `5a273bba-0bef-49fb-870a-7f2df8fc14b6` + - Parameters: + - flowRate (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5a273bba-0bef-49fb-870a-7f2df8fc14b6", + "params": { + "flowRate": "" + } + } + } + + +--- + +### lumiMagnetSensor + +- ThingClassId: `b3e30f37-7467-4c66-8694-9fe624aebd10` + +#### States + +- connected (Bool) → stateTypeId: `76f6c739-73e0-4a36-97c9-043af5854af5` +- signalStrength (Uint) → stateTypeId: `7fef2f25-91ac-4818-bc7a-e427bdeff438` +- version (String) → stateTypeId: `ac0e4cd1-553b-4e43-b08a-6dd8d617e765` +- closed (Bool) → stateTypeId: `6fad76ec-9d36-408d-bbf1-00e5d18bf6b0` +- batteryLevel (Int) → stateTypeId: `c2dbbf61-109d-4267-a9ad-904bc0f3e8ca` +- batteryCritical (Bool) → stateTypeId: `da0cb4d6-4d72-4163-93fe-a63cabf1adf0` + + +--- + +### remote + +- ThingClassId: `19ee3f56-f958-4b04-8ea3-747b72d7c7d6` + +#### States + +- connected (Bool) → stateTypeId: `9d5d116c-f742-4766-baf9-e7662f5003a9` +- signalStrength (Uint) → stateTypeId: `b87d310a-28ba-4546-9f47-822e5182836c` +- batteryLevel (Int) → stateTypeId: `4a72d569-44be-4689-bda6-addc0c6351d4` +- batteryCritical (Bool) → stateTypeId: `87b60c83-d980-48f5-bc31-0daa241d727e` +- currentVersion (String) → stateTypeId: `66c95514-6c23-4029-a3de-79f69f30cddb` +- updateStatus (String) → stateTypeId: `73f99785-7a4b-4cd3-96e7-fc2deef92227` +- availableVersion (String) → stateTypeId: `f6979f9f-0f41-4520-899d-8dc52905cbaf` +- updateProgress (Uint) → stateTypeId: `a0fa549a-12c3-47fb-88b5-373c4f81794b` + +#### Actions + +- performUpdate → actionTypeId: `28f2ae38-a592-4a7c-8f2a-58e1c3f43ac0` + +--- + +### tasmotaShutter + +- ThingClassId: `c63b02f2-3695-4e8c-9789-1b8a705f3a53` + +#### States + +- connected (Bool) → stateTypeId: `7bbf0bbf-abb3-487e-b5e9-077f7b00d8ef` +- signalStrength (Uint) → stateTypeId: `1de7fac2-87f1-4c2b-9286-1b0895c5ac49` + +#### Actions + +- open → actionTypeId: `8e7fb2f4-2819-4d14-a5ae-95624b097bf7` +- close → actionTypeId: `efbe9290-affd-4902-abb7-dd4ea74ccd1b` +- stop → actionTypeId: `c9c9c569-e224-4f63-abed-782cba04d61b` + +--- + +### powerMeterSocket + +- ThingClassId: `22feafcc-40cb-4699-91dd-0903693287b9` + +#### States + +- connected (Bool) → stateTypeId: `34ffc41b-5596-499f-90b9-f39f8220a7f0` +- signalStrength (Uint) → stateTypeId: `738ae904-d28c-46b0-b2db-0dc82e14245c` +- power (Bool) → stateTypeId: `a0bc7583-2600-4b6f-ae89-252029dda249` +- currentPower (Double) → stateTypeId: `d486ccbf-0803-4ac8-bbde-db26116a4754` +- totalEnergyConsumed (Double) → stateTypeId: `6583f3ff-838c-4dfd-98e4-2d7113f0824e` + +#### Actions + +- power → actionTypeId: `a0bc7583-2600-4b6f-ae89-252029dda249` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "a0bc7583-2600-4b6f-ae89-252029dda249", + "params": { + "power": "" + } + } + } + + +--- + +### sonosGroup + +- ThingClassId: `72d9332b-2b25-4136-87a6-e534eae4cc80` + +#### States + +- connected (Bool) → stateTypeId: `09dfbd40-c97c-4a20-9ecd-f80e389a4864` +- mute (Bool) → stateTypeId: `bc98cdb0-4d0e-48ca-afc7-922e49bb7813` +- volume (Int) → stateTypeId: `9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9` +- playbackStatus (String) → stateTypeId: `2dd512b7-40c2-488e-8d4f-6519edaa6f74` +- title (String) → stateTypeId: `f2209fec-cceb-46ad-8189-4caf42166e6b` +- artist (String) → stateTypeId: `8cb920a3-3bf1-4231-92d4-8ac27e7b3d65` +- collection (String) → stateTypeId: `ce399eec-9f6a-4903-9916-0e90e38b255e` +- artwork (String) → stateTypeId: `44304c82-c2f6-433b-b62b-815382617d0b` +- shuffle (Bool) → stateTypeId: `5913aa2a-629d-4de5-bf44-a4a1f130c118` +- repeat (String) → stateTypeId: `bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e` +- playerType (String) → stateTypeId: `addbd4ee-019b-4756-a59a-6412116105e9` + +#### Actions + +- mute → actionTypeId: `bc98cdb0-4d0e-48ca-afc7-922e49bb7813` + - Parameters: + - mute (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bc98cdb0-4d0e-48ca-afc7-922e49bb7813", + "params": { + "mute": "" + } + } + } + +- volume → actionTypeId: `9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9` + - Parameters: + - volume (Int) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9", + "params": { + "volume": "" + } + } + } + +- playbackStatus → actionTypeId: `2dd512b7-40c2-488e-8d4f-6519edaa6f74` + - Parameters: + - playbackStatus (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "2dd512b7-40c2-488e-8d4f-6519edaa6f74", + "params": { + "playbackStatus": "" + } + } + } + +- shuffle → actionTypeId: `5913aa2a-629d-4de5-bf44-a4a1f130c118` + - Parameters: + - shuffle (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "5913aa2a-629d-4de5-bf44-a4a1f130c118", + "params": { + "shuffle": "" + } + } + } + +- repeat → actionTypeId: `bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e` + - Parameters: + - repeat (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e", + "params": { + "repeat": "" + } + } + } + +- skipBack → actionTypeId: `a180807d-1265-4831-9d86-a421767418dd` +- fastRewind → actionTypeId: `7e70b47b-7e79-4521-be34-04a3c427e5b1` +- stop → actionTypeId: `ae3cbe03-ee3e-410e-abbd-efabc2402198` +- play → actionTypeId: `4d2ee668-a2e3-4795-8b96-0c800b703b46` +- pause → actionTypeId: `3cf341cb-fe63-40bc-a450-9678d18e91e3` +- skipNext → actionTypeId: `85d7126a-b123-4a28-aeb4-d84bcfb4d14f` +- increaseVolume → actionTypeId: `02abbda5-d23c-4fde-84cf-09782f319804` +- decreaseVolume → actionTypeId: `7f71d0f7-3950-46fb-a785-f16f9c3fd85f` + +--- + +### shellyHT + +- ThingClassId: `cc75be5c-8e34-4f72-a55d-c96e4c85d4be` + +#### States + +- temperature (Double) → stateTypeId: `507e7ca7-e1ab-4e7c-8097-4aedf924f797` +- humidity (Double) → stateTypeId: `18a3c71b-f4ef-45d8-a5db-58f533fb6e19` +- connected (Bool) → stateTypeId: `b35ace90-8afb-49f1-924d-899bf1c03c3a` +- signalStrength (Uint) → stateTypeId: `f6a09411-4efa-49e0-9cac-1c8e550e9cb6` +- batteryLevel (Int) → stateTypeId: `15914ad5-816b-471c-996b-00160100f2bc` +- batteryCritical (Bool) → stateTypeId: `56053726-92dc-4a80-b05e-a9a857c02bc1` +- updateStatus (String) → stateTypeId: `fdda809d-0807-4495-9d50-f9e2a12894ac` +- currentVersion (String) → stateTypeId: `5655b88c-f852-4efe-955f-cf5f88e8ba6b` +- availableVersion (String) → stateTypeId: `770b6b3a-3815-4ba3-8a2d-3fb949c914de` + +#### Actions + +- performUpdate → actionTypeId: `e32aaff9-b2f8-437e-b7b4-5b519d9e5e21` + +--- + +### rainGauge + +- ThingClassId: `33a7230b-588f-471c-895d-4a1ddd417c2a` + +#### States + +- updateTime (Int) → stateTypeId: `3c87cbf7-73ad-4673-9abd-188e1f4facfb` +- rainfallLastHour (Uint) → stateTypeId: `6c55bcd5-b1b3-4ea2-8b4c-2689941d43bb` +- rainfallLastDay (Uint) → stateTypeId: `3f475b34-ad5f-4821-8452-2f6869ef46b4` +- signalStrength (Uint) → stateTypeId: `d44a1e64-c5e5-4dae-944b-bce23192f610` +- connected (Bool) → stateTypeId: `d726cd1e-75c5-4d50-adbd-962301ea9b91` +- batteryLevel (Int) → stateTypeId: `e06955c4-963b-4f8f-9c0c-975c1cd75edb` +- batteryCritical (Bool) → stateTypeId: `dd8f8620-fcca-408b-835f-2c257e18aee2` + + +--- + +### shellySwitch + +- ThingClassId: `6de35a17-0f54-4397-894d-4321b64c53d1` + +#### States + +- connected (Bool) → stateTypeId: `0c233312-7b8f-4ca3-880d-523cab9b3ccb` +- signalStrength (Uint) → stateTypeId: `5088cd2d-8f71-4cfb-a120-4a2d4a84355d` +- power (Bool) → stateTypeId: `20f74d88-0683-4d3a-9513-6b29b5112b7b` + + +--- + +### doorLock + +- ThingClassId: `f5f60f66-215a-492b-9088-ef8e2b58c802` + +#### States + +- connected (Bool) → stateTypeId: `6fc8b1ad-5eb0-4038-8a65-847343c5a432` +- signalStrength (Uint) → stateTypeId: `02af2c5f-87ec-4f86-8784-3a089d5928e9` +- batteryLevel (Int) → stateTypeId: `b0a977d8-a789-4bc9-ad11-afb372b15e69` +- batteryCritical (Bool) → stateTypeId: `ba8147de-d38b-4295-a694-952a28c3e9d7` +- state (String) → stateTypeId: `9e1ce33e-ab39-4fce-8b68-54dfa970fea4` + +#### Actions + +- lock → actionTypeId: `72c23e40-e6f9-4e21-9e8f-d9a56711054c` +- unlock → actionTypeId: `6ff7d9d2-8169-4dd0-befc-9c4f0b4c110d` + +--- + +### remoteControl + +- ThingClassId: `ea5d0123-266e-4879-98f9-ecd4e2603e15` + +#### States + +- connected (Bool) → stateTypeId: `33394ace-3c4f-44b0-959f-5eb4721ec982` +- signalStrength (Uint) → stateTypeId: `dfb91a2d-04f3-4dce-a822-e14f802e5416` +- batteryLevel (Int) → stateTypeId: `fc61ac93-8c85-4d2a-80ca-47070be0777b` +- batteryCritical (Bool) → stateTypeId: `3bb7a2c8-d1e7-4ce6-a409-910d13f7d839` + + +--- + +### thermostat + +- ThingClassId: `6a34ba99-e5ec-4fc9-a61f-695ed8573fa1` + +#### States + +- temperature (Double) → stateTypeId: `0f808803-0e63-47df-b024-9685998ba663` +- targetTemperature (Double) → stateTypeId: `60169ebf-c1e3-41ed-a1bb-4b858e93ec50` +- heatingOn (Bool) → stateTypeId: `1f6a0c39-4417-4e31-86db-9926cf81c345` +- coolingOn (Bool) → stateTypeId: `cab7d4bd-f612-4d12-b3a4-0649e189810f` + +#### Actions + +- temperature → actionTypeId: `0f808803-0e63-47df-b024-9685998ba663` + - Parameters: + - temperature (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "0f808803-0e63-47df-b024-9685998ba663", + "params": { + "temperature": "" + } + } + } + +- targetTemperature → actionTypeId: `60169ebf-c1e3-41ed-a1bb-4b858e93ec50` + - Parameters: + - targetTemperature (Double) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "60169ebf-c1e3-41ed-a1bb-4b858e93ec50", + "params": { + "targetTemperature": "" + } + } + } + +- heatingOn → actionTypeId: `1f6a0c39-4417-4e31-86db-9926cf81c345` + - Parameters: + - heatingOn (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1f6a0c39-4417-4e31-86db-9926cf81c345", + "params": { + "heatingOn": "" + } + } + } + +- coolingOn → actionTypeId: `cab7d4bd-f612-4d12-b3a4-0649e189810f` + - Parameters: + - coolingOn (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "cab7d4bd-f612-4d12-b3a4-0649e189810f", + "params": { + "coolingOn": "" + } + } + } + + +--- + +### alarm + +- ThingClassId: `3f3c7ecc-9915-4e4e-95a1-e11f4f9d174d` + + +--- + +### oven + +- ThingClassId: `96845b7d-4c20-43a0-a810-ec505df3ee88` + +#### States + +- connected (Bool) → stateTypeId: `e0a6c618-d849-4206-9e3c-cd01352664e7` +- closed (Bool) → stateTypeId: `e892ca9e-5b31-41f5-a568-44474091f0f6` +- operationState (String) → stateTypeId: `c9f9bd33-513f-4834-a504-c2c1611fb4be` +- localControlState (Bool) → stateTypeId: `8843a1ce-796a-40c3-ab24-cf97b821d45c` +- remoteControlActivationState (Bool) → stateTypeId: `6afd574a-3c22-46cd-bc28-4dfcba01c51d` +- remoteStartAllowanceState (Bool) → stateTypeId: `9f9cb8ea-8527-48c9-8bf2-f5148d5b53f1` +- targetTemperature (Int) → stateTypeId: `f3f5f257-5887-4d64-b61a-8179424912b3` +- currentTemperature (Int) → stateTypeId: `1dcf634c-04c1-4415-9656-06f5b61bde17` +- duration (Int) → stateTypeId: `3527a28d-e4df-481b-a8b6-88717b77c7a6` +- progress (Int) → stateTypeId: `85a333dc-0e0b-454c-9c9a-d14dd6484275` +- selectedProgram (String) → stateTypeId: `2e6f27bb-371a-44a4-b057-51f765bdeb86` +- endTime (String) → stateTypeId: `6e828e48-3634-4d92-9dee-2c03a51c7189` + + +--- + +### openUv + +- ThingClassId: `37e17c00-91e1-4276-a1e3-12283d2cae4c` + +#### States + +- connected (Bool) → stateTypeId: `47c7dd57-3592-4d05-af4c-1034268223e1` +- uv (Int) → stateTypeId: `1d63cee1-3e28-4e90-ad4f-acbc2081ea8c` +- uvTime (Int) → stateTypeId: `35c799a1-d4b5-4d26-8b63-d884cf96ff2c` +- uvMax (Int) → stateTypeId: `59fe2255-3b61-44f0-84eb-afa6eea838c2` +- uvMaxTime (Int) → stateTypeId: `4abd1745-c290-4685-bed4-108d31721b67` +- ozone (Int) → stateTypeId: `48e51a7c-edbb-499e-95a0-6789bf51437c` +- ozoneTime (Int) → stateTypeId: `27a48154-218f-4a27-aefb-41d943ecf7e8` +- safeExposureTimeSt1 (Int) → stateTypeId: `13bc0387-acfe-4461-85c8-46f5e93ee0aa` +- safeExposureTimeSt2 (Int) → stateTypeId: `0a60b028-97c9-4231-90a4-95ace8c6cf28` +- safeExposureTimeSt3 (Int) → stateTypeId: `fb9c63aa-8ec6-479d-bd87-75865b8fcd77` +- safeExposureTimeSt4 (Int) → stateTypeId: `31fc498f-1f17-4443-b0f8-65aff70bbf1c` +- safeExposureTimeSt5 (Int) → stateTypeId: `268637f0-73a9-4945-ace9-fc2bfc250675` +- safeExposureTimeSt6 (Int) → stateTypeId: `fd98313c-a6fb-4cff-8478-7399d33e7794` + + +--- + +### sgReady + +- ThingClassId: `83cf138d-02dc-4015-9b03-6360c2f3cfb1` + +#### States + +- relay1 (Bool) → stateTypeId: `d080466e-7271-441f-9916-1199a6668545` +- relay2 (Bool) → stateTypeId: `caf22937-fdf7-41e1-a87b-d11e66c4f4c6` +- sgReadyMode (String) → stateTypeId: `1e694bc2-12e1-4e1d-a0ec-70af2c7b16ab` +- operatingModeDescription (String) → stateTypeId: `b421d098-f6e9-40e2-b536-58acdb3a49fe` + +#### Actions + +- relay1 → actionTypeId: `d080466e-7271-441f-9916-1199a6668545` + - Parameters: + - relay1 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "d080466e-7271-441f-9916-1199a6668545", + "params": { + "relay1": "" + } + } + } + +- relay2 → actionTypeId: `caf22937-fdf7-41e1-a87b-d11e66c4f4c6` + - Parameters: + - relay2 (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "caf22937-fdf7-41e1-a87b-d11e66c4f4c6", + "params": { + "relay2": "" + } + } + } + +- sgReadyMode → actionTypeId: `1e694bc2-12e1-4e1d-a0ec-70af2c7b16ab` + - Parameters: + - sgReadyMode (String) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "1e694bc2-12e1-4e1d-a0ec-70af2c7b16ab", + "params": { + "sgReadyMode": "" + } + } + } + + +--- + +### switch + +- ThingClassId: `27dc49c0-58cd-4d5f-a95b-0c437dd828cf` + +#### States + +- connected (Bool) → stateTypeId: `8864755d-4e6a-4e45-b2db-3eb5d4f3d53d` +- signalStrength (Uint) → stateTypeId: `71723ea3-d2a1-48c4-9a2c-532d938e5022` +- power (Bool) → stateTypeId: `717f2593-1544-483b-aac8-f9800b299e4d` +- totalEnergyConsumed (Double) → stateTypeId: `a3533121-69ee-44fd-8394-13373e8f960e` +- currentPower (Double) → stateTypeId: `ccb52b57-5800-4f03-b7fa-f36dcebe1d4e` + +#### Actions + +- power → actionTypeId: `717f2593-1544-483b-aac8-f9800b299e4d` + - Parameters: + - power (Bool) + + Example: + + { + "id": 1, + "method": "Integrations.ExecuteAction", + "params": { + "thingId": "", + "actionTypeId": "717f2593-1544-483b-aac8-f9800b299e4d", + "params": { + "power": "" + } + } + } + + +--- + +### doorLock + +- ThingClassId: `34cb3d09-dd9f-4b95-95d0-30a1cd94adac` + +#### States + +- connected (Bool) → stateTypeId: `206b0508-b477-4aa2-b420-aba2259fb6e6` +- signalStrength (Uint) → stateTypeId: `6c8f8db5-464c-408a-9c65-4e8096663019` +- version (String) → stateTypeId: `9e27850b-99d8-40df-9bc7-4b3c7c01faf8` +- batteryLevel (Int) → stateTypeId: `568e5bdd-47f3-4ccb-a1d8-ff3a5ea87ad8` +- batteryCritical (Bool) → stateTypeId: `89abea26-b772-4258-9b56-e026b80c2028` + +#### Actions + +- open → actionTypeId: `6e112e3b-080f-47e4-8e2b-453029f0eacb` +- close → actionTypeId: `c26e1908-25d0-4475-8f82-5aaf034640f1` + +--- + diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6396007 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,620 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..6524dbe --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Etm Powersync App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + etm_powersync_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..5154688 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; + +import 'services/nymea_service.dart'; +import 'screens/dashboard_screen.dart'; +import 'screens/energy_screen.dart'; +import 'screens/things_screen.dart'; +import 'screens/ac_screen.dart'; +import 'screens/favorites_screen.dart'; +import 'theme/app_theme.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.dark, + ), + ); + + runApp( + ChangeNotifierProvider( + create: (_) => NymeaService(), + child: const NymeaEnergyApp(), + ), + ); +} + +class NymeaEnergyApp extends StatelessWidget { + const NymeaEnergyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Nymea Energy', + debugShowCheckedModeBanner: false, + theme: AppTheme.theme, + home: const MainShell(), + ); + } +} + +class MainShell extends StatefulWidget { + const MainShell({super.key}); + + @override + State createState() => _MainShellState(); +} + +class _MainShellState extends State { + int _currentIndex = 0; + + final List _screens = const [ + DashboardScreen(), + EnergyScreen(), + ThingsScreen(), + ACScreen(), + FavoritesScreen(), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: IndexedStack( + index: _currentIndex, + children: _screens, + ), + bottomNavigationBar: _BottomNav( + currentIndex: _currentIndex, + onTap: (i) => setState(() => _currentIndex = i), + ), + ); + } +} + +class _BottomNav extends StatelessWidget { + final int currentIndex; + final ValueChanged onTap; + + const _BottomNav({required this.currentIndex, required this.onTap}); + + @override + Widget build(BuildContext context) { + final items = [ + _NavItem(icon: Icons.home_rounded, label: 'Dashboard'), + _NavItem(icon: Icons.bar_chart_rounded, label: 'Énergie'), + _NavItem(icon: Icons.device_hub_rounded, label: 'Things'), + _NavItem(icon: Icons.ac_unit_rounded, label: 'A/C'), + _NavItem(icon: Icons.star_rounded, label: 'Favoris'), + ]; + + return Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha:0.08), + blurRadius: 12, + offset: const Offset(0, -2), + ), + ], + ), + child: SafeArea( + child: SizedBox( + height: 62, + child: Row( + children: items.asMap().entries.map((e) { + final i = e.key; + final item = e.value; + final isSelected = currentIndex == i; + + return Expanded( + child: GestureDetector( + onTap: () => onTap(i), + behavior: HitTestBehavior.opaque, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: isSelected + ? AppTheme.primaryGreen.withValues(alpha:0.12) + : Colors.transparent, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + item.icon, + color: isSelected + ? AppTheme.primaryGreen + : AppTheme.textLight, + size: 22, + ), + ), + const SizedBox(height: 2), + Text( + item.label, + style: TextStyle( + fontSize: 10, + color: isSelected + ? AppTheme.primaryGreen + : AppTheme.textLight, + fontWeight: isSelected + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + ); + }).toList(), + ), + ), + ), + ); + } +} + +class _NavItem { + final IconData icon; + final String label; + const _NavItem({required this.icon, required this.label}); +} diff --git a/lib/models/energy_data.dart b/lib/models/energy_data.dart new file mode 100644 index 0000000..89a41f7 --- /dev/null +++ b/lib/models/energy_data.dart @@ -0,0 +1,101 @@ +class EnergyData { + final double pvPower; // Watts - production solaire + final double homePower; // Watts - consommation maison + final double batteryPower; // Watts - puissance batterie (+ charge, - décharge) + final double gridPower; // Watts - réseau (+ injection, - soutif) + final double batterySOC; // % - état de charge batterie + final double temperature; // °C + final String weatherIcon; + + // Journée + final double dayProductionWh; + final double daySelfConsumptionWh; + final double dayGridInjectionWh; + final double selfConsumptionRate; // % + final double autonomyRate; // % + final double dayGains; // € + + // Borne de recharge + final ChargingMode chargingMode; + final double chargingPower; // kW + final double solarSourcePercent; // % + final bool gridLimitOk; + + const EnergyData({ + this.pvPower = 0, + this.homePower = 0, + this.batteryPower = 0, + this.gridPower = 0, + this.batterySOC = 0, + this.temperature = 0, + this.weatherIcon = 'cloud_snow', + this.dayProductionWh = 0, + this.daySelfConsumptionWh = 0, + this.dayGridInjectionWh = 0, + this.selfConsumptionRate = 0, + this.autonomyRate = 0, + this.dayGains = 0, + this.chargingMode = ChargingMode.pv, + this.chargingPower = 3.6, + this.solarSourcePercent = 82, + this.gridLimitOk = true, + }); + + EnergyData copyWith({ + double? pvPower, + double? homePower, + double? batteryPower, + double? gridPower, + double? batterySOC, + double? temperature, + String? weatherIcon, + double? dayProductionWh, + double? daySelfConsumptionWh, + double? dayGridInjectionWh, + double? selfConsumptionRate, + double? autonomyRate, + double? dayGains, + ChargingMode? chargingMode, + double? chargingPower, + double? solarSourcePercent, + bool? gridLimitOk, + }) { + return EnergyData( + pvPower: pvPower ?? this.pvPower, + homePower: homePower ?? this.homePower, + batteryPower: batteryPower ?? this.batteryPower, + gridPower: gridPower ?? this.gridPower, + batterySOC: batterySOC ?? this.batterySOC, + temperature: temperature ?? this.temperature, + weatherIcon: weatherIcon ?? this.weatherIcon, + dayProductionWh: dayProductionWh ?? this.dayProductionWh, + daySelfConsumptionWh: daySelfConsumptionWh ?? this.daySelfConsumptionWh, + dayGridInjectionWh: dayGridInjectionWh ?? this.dayGridInjectionWh, + selfConsumptionRate: selfConsumptionRate ?? this.selfConsumptionRate, + autonomyRate: autonomyRate ?? this.autonomyRate, + dayGains: dayGains ?? this.dayGains, + chargingMode: chargingMode ?? this.chargingMode, + chargingPower: chargingPower ?? this.chargingPower, + solarSourcePercent: solarSourcePercent ?? this.solarSourcePercent, + gridLimitOk: gridLimitOk ?? this.gridLimitOk, + ); + } +} + +enum ChargingMode { pv, minPv, boost } + +class HistoryPoint { + final DateTime time; + final double pvWh; + final double homeWh; + final double gridWh; + final double batteryWh; + + const HistoryPoint({ + required this.time, + this.pvWh = 0, + this.homeWh = 0, + this.gridWh = 0, + this.batteryWh = 0, + }); +} \ No newline at end of file diff --git a/lib/models/nymea_models.dart b/lib/models/nymea_models.dart new file mode 100644 index 0000000..6c36730 --- /dev/null +++ b/lib/models/nymea_models.dart @@ -0,0 +1,247 @@ +// ── Thing models ────────────────────────────────────────────────────────────── + +class NymeaThing { + final String id; + final String name; + final String thingClassId; + final String setupStatus; + final List> paramValues; + final List states; + + const NymeaThing({ + required this.id, + required this.name, + required this.thingClassId, + required this.setupStatus, + required this.paramValues, + this.states = const [], + }); + + factory NymeaThing.fromJson(Map j) { + return NymeaThing( + id: j['id'] as String? ?? '', + name: j['name'] as String? ?? 'Unknown', + thingClassId: j['thingClassId'] as String? ?? '', + setupStatus: j['setupStatus'] as String? ?? '', + paramValues: List>.from(j['paramValues'] ?? []), + states: (j['states'] as List? ?? []) + .map((s) => NymeaStateValue.fromJson(s)) + .toList(), + ); + } + + NymeaThing copyWith({String? name, List? states}) => NymeaThing( + id: id, + name: name ?? this.name, + thingClassId: thingClassId, + setupStatus: setupStatus, + paramValues: paramValues, + states: states ?? this.states, + ); + + dynamic settingValue(String settingTypeId) { + try { + return paramValues + .firstWhere((p) => p['paramTypeId'] == settingTypeId)['value']; + } catch (_) { + return null; + } + } + + dynamic stateValue(String stateTypeId) { + try { + return states.firstWhere((s) => s.stateTypeId == stateTypeId).value; + } catch (_) { + return null; + } + } + + bool get isSetupComplete => setupStatus == 'ThingSetupStatusComplete'; +} + +class NymeaStateValue { + final String stateTypeId; + final dynamic value; + + const NymeaStateValue({required this.stateTypeId, required this.value}); + + factory NymeaStateValue.fromJson(Map j) => NymeaStateValue( + stateTypeId: j['stateTypeId'] as String? ?? '', + value: j['value'], + ); +} + +// ── State type definition (from GetThingClasses) ────────────────────────────── +class NymeaStateType { + final String id; + final String name; + final String displayName; + final String type; // "Double", "Bool", "Int", "String" + final String unit; // "W", "V", "A", "Percentage", "" + final dynamic defaultValue; + final bool writable; + + const NymeaStateType({ + required this.id, + required this.name, + required this.displayName, + required this.type, + required this.unit, + this.defaultValue, + this.writable = false, + }); + + factory NymeaStateType.fromJson(Map j) => NymeaStateType( + id: j['id'] as String? ?? '', + name: j['name'] as String? ?? '', + displayName: j['displayName'] as String? ?? j['name'] as String? ?? '', + type: j['type'] as String? ?? 'String', + unit: j['unit'] as String? ?? '', + defaultValue: j['defaultValue'], + writable: j['writable'] as bool? ?? false, + ); + + String formatValue(dynamic value) { + if (value == null) return '—'; + switch (unit) { + case 'W': return '${_fmt(value)} W'; + case 'kW': return '${_fmt(value)} kW'; + case 'Wh': return '${_fmt(value)} Wh'; + case 'kWh': return '${_fmt(value)} kWh'; + case 'V': return '${_fmt(value)} V'; + case 'A': return '${_fmt(value)} A'; + case 'Percentage': return '${_fmt(value)} %'; + case 'Celsius': return '${_fmt(value)} °C'; + case 'Hz': return '${_fmt(value)} Hz'; + case 'VoltAmpereReactive': return '${_fmt(value)} VAr'; + case 'KiloVoltAmpereHour': return '${_fmt(value)} kVAh'; + case 'Rpm': return '${_fmt(value)} rpm'; + default: + if (type == 'Bool') return (value == true || value == 'true') ? 'Oui' : 'Non'; + return value.toString(); + } + } + + String _fmt(dynamic value) { + if (value is double) { + if (value.abs() >= 1000) return value.toStringAsFixed(0); + if (value.abs() >= 10) return value.toStringAsFixed(1); + return value.toStringAsFixed(2); + } + return value.toString(); + } +} + +// ── Action type definition ───────────────────────────────────────────────────── +class NymeaActionType { + final String id; + final String name; + final String displayName; + final List> paramTypes; + + const NymeaActionType({ + required this.id, + required this.name, + required this.displayName, + this.paramTypes = const [], + }); + + factory NymeaActionType.fromJson(Map j) => NymeaActionType( + id: j['id'] as String? ?? '', + name: j['name'] as String? ?? '', + displayName: j['displayName'] as String? ?? j['name'] as String? ?? '', + paramTypes: List>.from(j['paramTypes'] ?? []), + ); +} + +class NymeaThingClass { + final String id; + final String name; + final String displayName; + final List interfaces; // ← clé pour la catégorisation + final List stateTypes; + final List actionTypes; + final List> paramTypes; + final List settingsTypes; + + const NymeaThingClass({ + required this.id, + required this.name, + required this.displayName, + this.interfaces = const [], + this.stateTypes = const [], + this.actionTypes = const [], + this.paramTypes = const [], + this.settingsTypes = const [], + }); + + factory NymeaThingClass.fromJson(Map j) => NymeaThingClass( + id: j['id'] as String? ?? '', + name: j['name'] as String? ?? '', + displayName: j['displayName'] as String? ?? '', + interfaces: List.from(j['interfaces'] ?? []), + stateTypes: (j['stateTypes'] as List? ?? []) + .map((s) => NymeaStateType.fromJson(s as Map)) + .toList(), + actionTypes: (j['actionTypes'] as List? ?? []) + .map((a) => NymeaActionType.fromJson(a as Map)) + .toList(), + paramTypes: List>.from(j['paramTypes'] ?? []), + settingsTypes: (j['settingsTypes'] as List? ?? []) + .map((s) => NymeaStateType.fromJson(s as Map)) + .toList(), + ); + + /// Retourne la stateType par son id + NymeaStateType? stateTypeById(String id) { + try { return stateTypes.firstWhere((s) => s.id == id); } + catch (_) { return null; } + } + + /// Retourne la stateType "principale" à afficher sur la card + NymeaStateType? get primaryStateType { + // Priorité : power → energy → temperature → connected → on/off → premier + const priority = [ + 'currentPower', 'power', 'totalEnergyConsumed', 'totalEnergyProduced', + 'temperature', 'batterylevel', 'soc', 'connected', 'on', + ]; + for (final p in priority) { + final found = stateTypes.where( + (s) => s.name.toLowerCase() == p.toLowerCase() + ); + if (found.isNotEmpty) return found.first; + } + return stateTypes.isNotEmpty ? stateTypes.first : null; + } +} + +// ── Favorite Widgets ────────────────────────────────────────────────────────── + +enum FavoriteType { + pvPower, + homePower, + batterySOC, + gridPower, + rates, + evCharger, + thingState, + chart, +} + +class FavoriteWidget { + final String id; + final FavoriteType type; + final String title; + final String? thingId; + final String? stateTypeId; + final Map extra; + + const FavoriteWidget({ + required this.id, + required this.type, + required this.title, + this.thingId, + this.stateTypeId, + this.extra = const {}, + }); +} diff --git a/lib/models/thing_category.dart b/lib/models/thing_category.dart new file mode 100644 index 0000000..f7390c7 --- /dev/null +++ b/lib/models/thing_category.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; +import 'nymea_models.dart'; + +// ───────────────────────────────────────────────────────────────────────────── +// Catégories affichées dans le Things screen +// ───────────────────────────────────────────────────────────────────────────── + +enum ThingCategory { + energy, // smartmeter, energymeter, powersocket + solar, // solarinverter, solarpanel + battery, // battery, energystorage + evCharger, // evcharger + hvac, // thermostat, heatingzone, ac, pump + lighting, // light, dimmablelight, colorlight + sensors, // sensor, temperaturesensor, humiditysensor, motionsensor + network, // networkdevice, gateway + notifications, // pushnotification, email, sms + media, // mediadevice, mediaplayer + weather, // weather + other, // tout le reste +} + +class ThingCategoryInfo { + final ThingCategory category; + final String label; + final IconData icon; + final Color color; + + const ThingCategoryInfo({ + required this.category, + required this.label, + required this.icon, + required this.color, + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Mapping interface nymea → catégorie +// ───────────────────────────────────────────────────────────────────────────── + +const Map interfaceToCategoryMap = { + 'smartmeter': ThingCategory.energy, + 'electricmeter': ThingCategory.energy, + 'energymeter': ThingCategory.energy, + 'smartenergymeter': ThingCategory.energy, + 'extendedsmartmeter': ThingCategory.energy, + 'powersocket': ThingCategory.energy, + 'smartplug': ThingCategory.energy, + 'solarinverter': ThingCategory.solar, + 'solarpanel': ThingCategory.solar, + 'inverter': ThingCategory.solar, + 'battery': ThingCategory.battery, + 'energystorage': ThingCategory.battery, + 'batterymonitor': ThingCategory.battery, + 'evcharger': ThingCategory.evCharger, + 'wallbox': ThingCategory.evCharger, + 'thermostat': ThingCategory.hvac, + 'heatingzone': ThingCategory.hvac, + 'ac': ThingCategory.hvac, + 'airconditioner': ThingCategory.hvac, + 'pump': ThingCategory.hvac, + 'heatpump': ThingCategory.hvac, + 'ventilation': ThingCategory.hvac, + 'boiler': ThingCategory.hvac, + 'light': ThingCategory.lighting, + 'dimmablelight': ThingCategory.lighting, + 'colorlight': ThingCategory.lighting, + 'colortemperaturelight': ThingCategory.lighting, + 'sensor': ThingCategory.sensors, + 'temperaturesensor': ThingCategory.sensors, + 'humiditysensor': ThingCategory.sensors, + 'pressuresensor': ThingCategory.sensors, + 'motionsensor': ThingCategory.sensors, + 'doorsensor': ThingCategory.sensors, + 'windowsensor': ThingCategory.sensors, + 'smokesensor': ThingCategory.sensors, + 'co2sensor': ThingCategory.sensors, + 'lightsensor': ThingCategory.sensors, + 'networkdevice': ThingCategory.network, + 'gateway': ThingCategory.network, + 'router': ThingCategory.network, + 'pushnotification': ThingCategory.notifications, + 'notificationservice': ThingCategory.notifications, + 'notification': ThingCategory.notifications, + 'email': ThingCategory.notifications, + 'sms': ThingCategory.notifications, + 'weather': ThingCategory.weather, + 'weatherstation': ThingCategory.weather, + 'mediadevice': ThingCategory.media, + 'mediaplayer': ThingCategory.media, +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Infos visuelles par catégorie +// ───────────────────────────────────────────────────────────────────────────── + +const Map categoryInfoMap = { + ThingCategory.energy: ThingCategoryInfo( + category: ThingCategory.energy, + label: 'Compteurs & Prises', + icon: Icons.electrical_services_rounded, + color: AppTheme.gridGray, + ), + ThingCategory.solar: ThingCategoryInfo( + category: ThingCategory.solar, + label: 'Solaire', + icon: Icons.solar_power_rounded, + color: AppTheme.solarYellow, + ), + ThingCategory.battery: ThingCategoryInfo( + category: ThingCategory.battery, + label: 'Stockage', + icon: Icons.battery_charging_full_rounded, + color: AppTheme.batteryGreen, + ), + ThingCategory.evCharger: ThingCategoryInfo( + category: ThingCategory.evCharger, + label: 'Chargeurs EV', + icon: Icons.electric_car_rounded, + color: AppTheme.minPvBlue, + ), + ThingCategory.hvac: ThingCategoryInfo( + category: ThingCategory.hvac, + label: 'Chauffage & Climatisation', + icon: Icons.thermostat_rounded, + color: Color(0xFFFF7043), + ), + ThingCategory.lighting: ThingCategoryInfo( + category: ThingCategory.lighting, + label: 'Éclairage', + icon: Icons.lightbulb_rounded, + color: Color(0xFFFFCA28), + ), + ThingCategory.sensors: ThingCategoryInfo( + category: ThingCategory.sensors, + label: 'Capteurs', + icon: Icons.sensors_rounded, + color: Color(0xFF26C6DA), + ), + ThingCategory.network: ThingCategoryInfo( + category: ThingCategory.network, + label: 'Réseau & Passerelles', + icon: Icons.router_rounded, + color: Color(0xFF7C4DFF), + ), + ThingCategory.notifications: ThingCategoryInfo( + category: ThingCategory.notifications, + label: 'Services de notification', + icon: Icons.notifications_rounded, + color: Color(0xFFEC407A), + ), + ThingCategory.weather: ThingCategoryInfo( + category: ThingCategory.weather, + label: 'Météo', + icon: Icons.wb_cloudy_rounded, + color: Color(0xFF42A5F5), + ), + ThingCategory.media: ThingCategoryInfo( + category: ThingCategory.media, + label: 'Multimédia', + icon: Icons.speaker_rounded, + color: Color(0xFFAB47BC), + ), + ThingCategory.other: ThingCategoryInfo( + category: ThingCategory.other, + label: 'Autres', + icon: Icons.device_hub_rounded, + color: Color(0xFF78909C), + ), +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Extension utilitaire sur NymeaThingClass +// ───────────────────────────────────────────────────────────────────────────── + +extension ThingClassCategoryExt on NymeaThingClass { + ThingCategory get category { + for (final iface in interfaces) { + final cat = interfaceToCategoryMap[iface.toLowerCase()]; + if (cat != null) return cat; + } + return ThingCategory.other; + } + + ThingCategoryInfo get categoryDetails => categoryInfoMap[category]!; +} \ No newline at end of file diff --git a/lib/screens/ac_screen.dart b/lib/screens/ac_screen.dart new file mode 100644 index 0000000..2e691c5 --- /dev/null +++ b/lib/screens/ac_screen.dart @@ -0,0 +1,333 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; + +class ACScreen extends StatefulWidget { + const ACScreen({super.key}); + + @override + State createState() => _ACScreenState(); +} + +class _ACScreenState extends State { + final List<_ACZone> _zones = [ + _ACZone(name: 'Salon', currentTemp: 19.5, targetTemp: 21, mode: ACMode.heat, isOn: true), + _ACZone(name: 'Chambre', currentTemp: 18.0, targetTemp: 19, mode: ACMode.cool, isOn: false), + _ACZone(name: 'Bureau', currentTemp: 20.0, targetTemp: 22, mode: ACMode.heat, isOn: true), + _ACZone(name: 'Cuisine', currentTemp: 21.5, targetTemp: 21, mode: ACMode.auto, isOn: false), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppTheme.backgroundGray, + appBar: AppBar( + backgroundColor: AppTheme.backgroundGray, + elevation: 0, + title: const Text( + 'Climatisation / Chauffage', + style: TextStyle(fontWeight: FontWeight.bold, color: AppTheme.textDark), + ), + actions: [ + IconButton( + icon: const Icon(Icons.schedule, color: AppTheme.textDark), + onPressed: () {}, + ), + ], + ), + body: ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: _zones.length, + separatorBuilder: (_, _) => const SizedBox(height: 12), + itemBuilder: (context, index) { + return _ZoneCard( + zone: _zones[index], + onChanged: (updated) { + setState(() => _zones[index] = updated); + }, + ); + }, + ), + ); + } +} + +enum ACMode { heat, cool, auto, fan } + +class _ACZone { + final String name; + final double currentTemp; + final double targetTemp; + final ACMode mode; + final bool isOn; + + const _ACZone({ + required this.name, + required this.currentTemp, + required this.targetTemp, + required this.mode, + required this.isOn, + }); + + _ACZone copyWith({ + double? targetTemp, + ACMode? mode, + bool? isOn, + }) => + _ACZone( + name: name, + currentTemp: currentTemp, + targetTemp: targetTemp ?? this.targetTemp, + mode: mode ?? this.mode, + isOn: isOn ?? this.isOn, + ); +} + +class _ZoneCard extends StatelessWidget { + final _ACZone zone; + final ValueChanged<_ACZone> onChanged; + + const _ZoneCard({required this.zone, required this.onChanged}); + + Color get _modeColor { + switch (zone.mode) { + case ACMode.heat: + return Colors.orange; + case ACMode.cool: + return Colors.cyan; + case ACMode.auto: + return AppTheme.primaryGreen; + case ACMode.fan: + return Colors.blueGrey; + } + } + + IconData get _modeIcon { + switch (zone.mode) { + case ACMode.heat: + return Icons.whatshot_rounded; + case ACMode.cool: + return Icons.ac_unit_rounded; + case ACMode.auto: + return Icons.autorenew_rounded; + case ACMode.fan: + return Icons.air_rounded; + } + } + + String get _modeLabel { + switch (zone.mode) { + case ACMode.heat: + return 'Chauffage'; + case ACMode.cool: + return 'Clim'; + case ACMode.auto: + return 'Auto'; + case ACMode.fan: + return 'Ventilation'; + } + } + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + // Header row + Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: zone.isOn + ? _modeColor.withValues(alpha:0.15) + : Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + _modeIcon, + color: zone.isOn ? _modeColor : Colors.grey, + size: 26, + ), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(zone.name, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + fontSize: 16)), + Text( + zone.isOn ? _modeLabel : 'Éteint', + style: TextStyle( + fontSize: 12, + color: + zone.isOn ? _modeColor : AppTheme.textLight), + ), + ], + ), + ), + Switch( + value: zone.isOn, + onChanged: (v) => onChanged(zone.copyWith(isOn: v)), + activeThumbColor: _modeColor, + ), + ], + ), + + if (zone.isOn) ...[ + const Divider(height: 20), + + // Temperature + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + const Text('Actuelle', + style: TextStyle( + fontSize: 12, color: AppTheme.textLight)), + Text( + '${zone.currentTemp.toStringAsFixed(1)}°C', + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + ), + ), + ], + ), + Icon(Icons.arrow_forward_rounded, + color: Colors.grey.shade400), + Column( + children: [ + const Text('Cible', + style: TextStyle( + fontSize: 12, color: AppTheme.textLight)), + Row( + children: [ + IconButton( + icon: Icon(Icons.remove_circle_outline, + color: _modeColor), + onPressed: () => onChanged(zone.copyWith( + targetTemp: zone.targetTemp - 0.5)), + ), + Text( + '${zone.targetTemp.toStringAsFixed(1)}°C', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: _modeColor, + ), + ), + IconButton( + icon: Icon(Icons.add_circle_outline, + color: _modeColor), + onPressed: () => onChanged(zone.copyWith( + targetTemp: zone.targetTemp + 0.5)), + ), + ], + ), + ], + ), + ], + ), + + // Mode selector + const SizedBox(height: 8), + Row( + children: ACMode.values.map((m) { + final selected = zone.mode == m; + final color = _modeColorFor(m); + final icon = _modeIconFor(m); + final label = _modeLabelFor(m); + return Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: GestureDetector( + onTap: () => onChanged(zone.copyWith(mode: m)), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: selected + ? color.withValues(alpha:0.15) + : Colors.grey.shade100, + borderRadius: BorderRadius.circular(10), + border: selected + ? Border.all(color: color, width: 1.5) + : null, + ), + child: Column( + children: [ + Icon(icon, + size: 18, + color: selected ? color : Colors.grey), + const SizedBox(height: 2), + Text(label, + style: TextStyle( + fontSize: 10, + color: + selected ? color : AppTheme.textLight, + fontWeight: selected + ? FontWeight.bold + : FontWeight.normal)), + ], + ), + ), + ), + ), + ); + }).toList(), + ), + ], + ], + ), + ), + ); + } + + Color _modeColorFor(ACMode m) { + switch (m) { + case ACMode.heat: + return Colors.orange; + case ACMode.cool: + return Colors.cyan; + case ACMode.auto: + return AppTheme.primaryGreen; + case ACMode.fan: + return Colors.blueGrey; + } + } + + IconData _modeIconFor(ACMode m) { + switch (m) { + case ACMode.heat: + return Icons.whatshot_rounded; + case ACMode.cool: + return Icons.ac_unit_rounded; + case ACMode.auto: + return Icons.autorenew_rounded; + case ACMode.fan: + return Icons.air_rounded; + } + } + + String _modeLabelFor(ACMode m) { + switch (m) { + case ACMode.heat: + return 'Chauf.'; + case ACMode.cool: + return 'Clim'; + case ACMode.auto: + return 'Auto'; + case ACMode.fan: + return 'Vent.'; + } + } +} \ No newline at end of file diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..e7d9e77 --- /dev/null +++ b/lib/screens/dashboard_screen.dart @@ -0,0 +1,556 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../services/nymea_service.dart'; +import '../theme/app_theme.dart'; +import '../widgets/energy_flow_widget.dart'; +import '../widgets/production_card.dart'; +import '../widgets/ev_charging_card.dart'; +import '../widgets/gains_card.dart'; + +class DashboardScreen extends StatefulWidget { + const DashboardScreen({super.key}); + + @override + State createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + @override + void initState() { + super.initState(); + // Start simulation on first load + WidgetsBinding.instance.addPostFrameCallback((_) { + final service = context.read(); + if (!service.connected) { + service.startSimulation(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, service, _) { + final data = service.energyData; + + return Scaffold( + backgroundColor: AppTheme.backgroundGray, + body: RefreshIndicator( + onRefresh: () async { + service.startSimulation(); + }, + color: AppTheme.primaryGreen, + child: CustomScrollView( + slivers: [ + // App bar + SliverAppBar( + floating: true, + backgroundColor: AppTheme.backgroundGray, + elevation: 0, + title: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: AppTheme.primaryGreen, + borderRadius: BorderRadius.circular(10), + ), + child: const Icon(Icons.bolt, color: Colors.white, size: 22), + ), + const SizedBox(width: 10), + const Text( + 'Nymea Energy', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + fontSize: 18, + ), + ), + ], + ), + actions: [ + // Connection status + Padding( + padding: const EdgeInsets.only(right: 8), + child: IconButton( + icon: Icon( + service.connected + ? Icons.wifi_rounded + : Icons.wifi_off_rounded, + color: service.connected + ? AppTheme.primaryGreen + : Colors.red, + ), + onPressed: () => _showConnectionDialog(context, service), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 12), + child: IconButton( + icon: const Icon(Icons.notifications_outlined, + color: AppTheme.textDark), + onPressed: () {}, + ), + ), + ], + ), + + SliverPadding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + sliver: SliverList( + delegate: SliverChildListDelegate([ + // Energy flow + EnergyFlowWidget(data: data), + const SizedBox(height: 12), + + // Self-consumption rates + _RatesRow(data: data), + const SizedBox(height: 12), + + // Production card + ProductionCard(data: data), + const SizedBox(height: 12), + + // Gains + GainsCard(data: data), + const SizedBox(height: 12), + + // EV Charging + EVChargingCard(data: data, service: service), + const SizedBox(height: 16), + ]), + ), + ), + ], + ), + ), + ); + }, + ); + } + + void _showConnectionDialog(BuildContext context, NymeaService service) { + showDialog( + context: context, + builder: (ctx) => _ConnectionDialog(service: service), + ); + } +} + +class _RatesRow extends StatelessWidget { + final dynamic data; + + const _RatesRow({required this.data}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _RateCard( + label: 'Autoconsommation', + value: data.selfConsumptionRate, + icon: Icons.wb_sunny_rounded, + color: AppTheme.solarYellow, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _RateCard( + label: 'Autonomie', + value: data.autonomyRate, + icon: Icons.home_rounded, + color: AppTheme.homeBlue, + ), + ), + ], + ); + } +} + +class _RateCard extends StatelessWidget { + final String label; + final double value; + final IconData icon; + final Color color; + + const _RateCard({ + required this.label, + required this.value, + required this.icon, + required this.color, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, color: color, size: 18), + const SizedBox(width: 6), + Expanded( + child: Text( + label, + style: const TextStyle( + fontSize: 12, color: AppTheme.textLight), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + '${value.toStringAsFixed(1)}%', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: color, + ), + ), + const SizedBox(height: 6), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: (value / 100).clamp(0, 1), + backgroundColor: color.withValues(alpha:0.15), + valueColor: AlwaysStoppedAnimation(color), + minHeight: 6, + ), + ), + ], + ), + ), + ); + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Dialog de connexion avec sélection du protocole TCP / WebSocket +// ═══════════════════════════════════════════════════════════════════════════ + +class _ConnectionDialog extends StatefulWidget { + final NymeaService service; + const _ConnectionDialog({required this.service}); + + @override + State<_ConnectionDialog> createState() => _ConnectionDialogState(); +} + +class _ConnectionDialogState extends State<_ConnectionDialog> { + late TextEditingController _hostCtrl; + late TextEditingController _portCtrl; + late TextEditingController _userCtrl; + late TextEditingController _passCtrl; + late NymeaProtocol _protocol; + bool _connecting = false; + bool _verbose = false; + + // Ports par défaut selon protocole + static const _defaultPorts = { + NymeaProtocol.tcpRaw: 2222, + NymeaProtocol.webSocket: 4444, + }; + + @override + void initState() { + super.initState(); + _protocol = widget.service.protocol; + _hostCtrl = TextEditingController(text: widget.service.host); + _portCtrl = TextEditingController(text: widget.service.port.toString()); + _userCtrl = TextEditingController(text: widget.service.username); + _passCtrl = TextEditingController(text: widget.service.password); + _verbose = widget.service.verboseLog; + } + + @override + void dispose() { + _hostCtrl.dispose(); + _portCtrl.dispose(); + _userCtrl.dispose(); + _passCtrl.dispose(); + super.dispose(); + } + + void _onProtocolChanged(NymeaProtocol p) { + setState(() { + _protocol = p; + // Mettre à jour le port par défaut si l'utilisateur n'a pas changé + final currentPort = int.tryParse(_portCtrl.text) ?? 0; + final otherDefault = _defaultPorts[_protocol == NymeaProtocol.tcpRaw + ? NymeaProtocol.webSocket + : NymeaProtocol.tcpRaw]!; + if (currentPort == otherDefault || currentPort == 0) { + _portCtrl.text = _defaultPorts[p]!.toString(); + } + }); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Row( + children: [ + Container( + width: 36, height: 36, + decoration: BoxDecoration( + color: AppTheme.primaryGreen.withValues(alpha:0.12), + borderRadius: BorderRadius.circular(10), + ), + child: const Icon(Icons.router_rounded, + color: AppTheme.primaryGreen, size: 20), + ), + const SizedBox(width: 10), + const Text('Connexion Nymea'), + ], + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ── Sélecteur de protocole ───────────────────────────────────── + const Text('Protocole', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppTheme.textLight)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: [ + _ProtoTab( + label: 'TCP brut', + subtitle: 'port 2222', + icon: Icons.cable_rounded, + selected: _protocol == NymeaProtocol.tcpRaw, + onTap: () => _onProtocolChanged(NymeaProtocol.tcpRaw), + ), + _ProtoTab( + label: 'WebSocket', + subtitle: 'port 4444', + icon: Icons.wifi_rounded, + selected: _protocol == NymeaProtocol.webSocket, + onTap: () => _onProtocolChanged(NymeaProtocol.webSocket), + ), + ], + ), + ), + const SizedBox(height: 6), + // Info protocole + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppTheme.primaryGreen.withValues(alpha:0.07), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _protocol == NymeaProtocol.tcpRaw + ? '💡 TCP brut : nymea envoie un message de bienvenue dès la connexion. Utilisé par l\'app nymea officielle.' + : '💡 WebSocket : le client envoie JSONRPC.Hello en premier. Activez-le dans nymea → Paramètres → Serveur WebSocket.', + style: const TextStyle(fontSize: 11, color: AppTheme.textLight), + ), + ), + const SizedBox(height: 14), + + // ── Hôte ────────────────────────────────────────────────────── + TextField( + controller: _hostCtrl, + decoration: const InputDecoration( + labelText: 'Adresse IP / Hôte', + prefixIcon: Icon(Icons.dns_outlined), + border: OutlineInputBorder(), + isDense: true, + ), + ), + const SizedBox(height: 10), + + // ── Port ────────────────────────────────────────────────────── + TextField( + controller: _portCtrl, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: 'Port', + prefixIcon: Icon(Icons.settings_ethernet), + border: OutlineInputBorder(), + isDense: true, + ), + ), + const SizedBox(height: 10), + + // ── Identifiants ────────────────────────────────────────── + TextField( + controller: _userCtrl, + decoration: const InputDecoration( + labelText: 'Utilisateur', + prefixIcon: Icon(Icons.person_outline), + border: OutlineInputBorder(), + isDense: true, + ), + ), + const SizedBox(height: 10), + TextField( + controller: _passCtrl, + obscureText: true, + decoration: const InputDecoration( + labelText: 'Mot de passe', + prefixIcon: Icon(Icons.lock_outline), + border: OutlineInputBorder(), + isDense: true, + ), + ), + + // ── Debug verbose ───────────────────────────────────────── + const SizedBox(height: 8), + Row( + children: [ + const Icon(Icons.terminal_rounded, size: 16, + color: AppTheme.textLight), + const SizedBox(width: 8), + const Expanded( + child: Text('Logs JSON verbose', + style: TextStyle(fontSize: 13, color: AppTheme.textDark)), + ), + Switch( + value: _verbose, + activeThumbColor: AppTheme.primaryGreen, + onChanged: (v) { + setState(() => _verbose = v); + widget.service.verboseLog = v; + }, + ), + ], + ), + + // ── Erreur ──────────────────────────────────────────────────── + if (widget.service.connectionError != null) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.red.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + const Icon(Icons.error_outline, color: Colors.red, size: 16), + const SizedBox(width: 6), + Expanded( + child: Text( + widget.service.connectionError!, + style: const TextStyle(color: Colors.red, fontSize: 11), + ), + ), + ], + ), + ), + ], + ], + ), + actions: [ + // Mode démo + TextButton.icon( + icon: const Icon(Icons.science_outlined, size: 16), + label: const Text('Mode démo'), + onPressed: () { + widget.service.startSimulation(); + Navigator.pop(context); + }, + ), + // Connecter + ElevatedButton.icon( + icon: _connecting + ? const SizedBox( + width: 14, height: 14, + child: CircularProgressIndicator( + strokeWidth: 2, color: Colors.white)) + : const Icon(Icons.link_rounded, size: 16), + label: const Text('Connecter'), + onPressed: _connecting + ? null + : () async { + setState(() => _connecting = true); + final ok = await widget.service.connect( + _hostCtrl.text.trim(), + int.tryParse(_portCtrl.text.trim()) ?? + _defaultPorts[_protocol]!, + protocol: _protocol, + username: _userCtrl.text.trim(), + password: _passCtrl.text, + ); + if (ok && context.mounted) Navigator.pop(context); + if (mounted) setState(() => _connecting = false); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryGreen, + foregroundColor: Colors.white, + ), + ), + ], + ); + } +} + +class _ProtoTab extends StatelessWidget { + final String label; + final String subtitle; + final IconData icon; + final bool selected; + final VoidCallback onTap; + + const _ProtoTab({ + required this.label, + required this.subtitle, + required this.icon, + required this.selected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + margin: const EdgeInsets.all(4), + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: selected ? Colors.white : Colors.transparent, + borderRadius: BorderRadius.circular(8), + boxShadow: selected + ? [BoxShadow( + color: Colors.black.withValues(alpha:0.08), + blurRadius: 4, offset: const Offset(0, 1))] + : null, + ), + child: Column( + children: [ + Icon(icon, + size: 20, + color: selected ? AppTheme.primaryGreen : AppTheme.textLight), + const SizedBox(height: 4), + Text(label, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: selected ? AppTheme.primaryGreen : AppTheme.textLight)), + Text(subtitle, + style: const TextStyle( + fontSize: 10, color: AppTheme.textLight)), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/energy_screen.dart b/lib/screens/energy_screen.dart new file mode 100644 index 0000000..01e39fa --- /dev/null +++ b/lib/screens/energy_screen.dart @@ -0,0 +1,403 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:provider/provider.dart'; +import '../services/nymea_service.dart'; +import '../theme/app_theme.dart'; + +class EnergyScreen extends StatefulWidget { + const EnergyScreen({super.key}); + + @override + State createState() => _EnergyScreenState(); +} + +class _EnergyScreenState extends State { + String _period = 'Jour'; + final List _periods = ['Jour', 'Semaine', 'Mois', 'Année']; + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, service, _) { + final points = service.historyPoints; + + return Scaffold( + backgroundColor: AppTheme.backgroundGray, + appBar: AppBar( + backgroundColor: AppTheme.backgroundGray, + elevation: 0, + title: const Text( + 'Énergie', + style: TextStyle( + fontWeight: FontWeight.bold, color: AppTheme.textDark), + ), + actions: [ + IconButton( + icon: const Icon(Icons.file_download_outlined, + color: AppTheme.textDark), + onPressed: () {}, + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + // Period selector + Card( + child: Padding( + padding: const EdgeInsets.all(4), + child: Row( + children: _periods.map((p) { + final selected = _period == p; + return Expanded( + child: GestureDetector( + onTap: () => setState(() => _period = p), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: selected + ? AppTheme.primaryGreen + : Colors.transparent, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + p, + textAlign: TextAlign.center, + style: TextStyle( + color: selected + ? Colors.white + : AppTheme.textLight, + fontWeight: selected + ? FontWeight.bold + : FontWeight.normal, + fontSize: 13, + ), + ), + ), + ), + ); + }).toList(), + ), + ), + ), + const SizedBox(height: 12), + + // Summary cards + _SummaryRow(service: service), + const SizedBox(height: 12), + + // Production chart + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Production & Consommation', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + fontSize: 15)), + const SizedBox(height: 4), + const _Legend(), + const SizedBox(height: 16), + SizedBox( + height: 200, + child: points.isEmpty + ? const Center(child: CircularProgressIndicator()) + : BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: 5000, + barTouchData: BarTouchData(enabled: true), + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 24, + getTitlesWidget: (value, meta) { + final idx = value.toInt(); + if (idx % 3 != 0 || + idx >= points.length) { + return const SizedBox(); + } + return Text( + '${points[idx].time.hour}h', + style: const TextStyle( + fontSize: 10, + color: AppTheme.textLight), + ); + }, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (value, meta) => Text( + '${(value / 1000).toStringAsFixed(1)}k', + style: const TextStyle( + fontSize: 9, + color: AppTheme.textLight), + ), + ), + ), + rightTitles: const AxisTitles( + sideTitles: + SideTitles(showTitles: false)), + topTitles: const AxisTitles( + sideTitles: + SideTitles(showTitles: false)), + ), + borderData: FlBorderData(show: false), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + getDrawingHorizontalLine: (value) => + FlLine( + color: Colors.grey.shade100, + strokeWidth: 1, + ), + ), + barGroups: points + .asMap() + .entries + .map( + (e) => BarChartGroupData( + x: e.key, + barsSpace: 2, + barRods: [ + BarChartRodData( + toY: e.value.pvWh, + color: AppTheme.solarYellow, + width: 5, + borderRadius: + const BorderRadius.vertical( + top: Radius.circular(3)), + ), + BarChartRodData( + toY: e.value.homeWh, + color: AppTheme.homeBlue, + width: 5, + borderRadius: + const BorderRadius.vertical( + top: Radius.circular(3)), + ), + ], + ), + ) + .toList(), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 12), + + // Grid line chart + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Import/Export réseau', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + fontSize: 15)), + const SizedBox(height: 16), + SizedBox( + height: 160, + child: points.isEmpty + ? const Center(child: CircularProgressIndicator()) + : LineChart( + LineChartData( + minY: -2000, + maxY: 3000, + lineTouchData: + LineTouchData(enabled: true), + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 22, + getTitlesWidget: (value, meta) { + final idx = value.toInt(); + if (idx % 4 != 0 || + idx >= points.length) { + return const SizedBox(); + } + return Text( + '${points[idx].time.hour}h', + style: const TextStyle( + fontSize: 10, + color: AppTheme.textLight), + ); + }, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 42, + getTitlesWidget: (v, _) => Text( + '${v.toStringAsFixed(0)} W', + style: const TextStyle( + fontSize: 9, + color: AppTheme.textLight), + ), + ), + ), + rightTitles: const AxisTitles( + sideTitles: + SideTitles(showTitles: false)), + topTitles: const AxisTitles( + sideTitles: + SideTitles(showTitles: false)), + ), + borderData: FlBorderData(show: false), + gridData: FlGridData( + drawVerticalLine: false, + getDrawingHorizontalLine: (v) => FlLine( + color: v == 0 + ? Colors.grey.shade400 + : Colors.grey.shade100, + strokeWidth: v == 0 ? 1.5 : 1), + ), + lineBarsData: [ + LineChartBarData( + spots: points + .asMap() + .entries + .map((e) => FlSpot( + e.key.toDouble(), + e.value.gridWh)) + .toList(), + isCurved: true, + color: Colors.orange, + barWidth: 2, + belowBarData: BarAreaData( + show: true, + color: Colors.orange.withValues(alpha:0.1), + ), + dotData: const FlDotData(show: false), + ), + ], + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 16), + ], + ), + ), + ); + }, + ); + } +} + +class _Legend extends StatelessWidget { + const _Legend(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + _dot(AppTheme.solarYellow), + const SizedBox(width: 4), + const Text('Production', style: TextStyle(fontSize: 12, color: AppTheme.textLight)), + const SizedBox(width: 16), + _dot(AppTheme.homeBlue), + const SizedBox(width: 4), + const Text('Consommation', style: TextStyle(fontSize: 12, color: AppTheme.textLight)), + ], + ); + } + + Widget _dot(Color c) => Container( + width: 10, + height: 10, + decoration: BoxDecoration(color: c, shape: BoxShape.circle), + ); +} + +class _SummaryRow extends StatelessWidget { + final NymeaService service; + + const _SummaryRow({required this.service}); + + @override + Widget build(BuildContext context) { + final d = service.energyData; + return Row( + children: [ + _SummaryCard( + label: 'Production', + value: '${(d.dayProductionWh / 1000).toStringAsFixed(2)} kWh', + color: AppTheme.solarYellow, + icon: Icons.wb_sunny_rounded), + const SizedBox(width: 8), + _SummaryCard( + label: 'Consommation', + value: '${(d.daySelfConsumptionWh / 1000).toStringAsFixed(2)} kWh', + color: AppTheme.homeBlue, + icon: Icons.home_rounded), + const SizedBox(width: 8), + _SummaryCard( + label: 'Gains', + value: '${d.dayGains.toStringAsFixed(2)} €', + color: Colors.amber, + icon: Icons.euro_rounded), + ], + ); + } +} + +class _SummaryCard extends StatelessWidget { + final String label; + final String value; + final Color color; + final IconData icon; + + const _SummaryCard({ + required this.label, + required this.value, + required this.color, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Card( + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + children: [ + Icon(icon, color: color, size: 20), + const SizedBox(height: 6), + Text(value, + style: TextStyle( + fontWeight: FontWeight.bold, + color: color, + fontSize: 13)), + const SizedBox(height: 2), + Text(label, + style: const TextStyle( + fontSize: 10, color: AppTheme.textLight)), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/favorites_screen.dart b/lib/screens/favorites_screen.dart new file mode 100644 index 0000000..d86a8d3 --- /dev/null +++ b/lib/screens/favorites_screen.dart @@ -0,0 +1,706 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../models/energy_data.dart'; +import '../models/nymea_models.dart'; +import '../services/nymea_service.dart'; +import '../theme/app_theme.dart'; + +class FavoritesScreen extends StatelessWidget { + const FavoritesScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, service, _) { + final favs = service.favoriteWidgets; + + return Scaffold( + backgroundColor: AppTheme.backgroundGray, + appBar: AppBar( + backgroundColor: AppTheme.backgroundGray, + elevation: 0, + title: const Text('Favoris', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark)), + actions: [ + IconButton( + icon: const Icon(Icons.add_circle_outline, + color: AppTheme.primaryGreen), + onPressed: () => _showAddFavorite(context, service), + ), + ], + ), + body: favs.isEmpty + ? _EmptyState(onAdd: () => _showAddFavorite(context, service)) + : ReorderableListView.builder( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + itemCount: favs.length, + onReorder: service.reorderFavorites, + proxyDecorator: (child, index, animation) => Material( + elevation: 6, + borderRadius: BorderRadius.circular(16), + child: child, + ), + itemBuilder: (context, index) { + final fav = favs[index]; + return Padding( + key: ValueKey(fav.id), + padding: const EdgeInsets.only(bottom: 12), + child: _FavoriteCard( + fav: fav, + service: service, + onRemove: () => service.removeFavorite(fav.id), + ), + ); + }, + ), + ); + }, + ); + } + + void _showAddFavorite(BuildContext context, NymeaService service) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20))), + builder: (_) => _AddFavoriteSheet(service: service), + ); + } +} + +// ── Favorite Card ───────────────────────────────────────────────────────────── + +class _FavoriteCard extends StatelessWidget { + final FavoriteWidget fav; + final NymeaService service; + final VoidCallback onRemove; + + const _FavoriteCard({ + required this.fav, + required this.service, + required this.onRemove, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header with drag handle + title + remove + Row( + children: [ + const Icon(Icons.drag_handle, color: AppTheme.textLight), + const SizedBox(width: 8), + Expanded( + child: Text(fav.title, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + fontSize: 15)), + ), + IconButton( + icon: const Icon(Icons.close, size: 18, color: AppTheme.textLight), + onPressed: onRemove, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ], + ), + const SizedBox(height: 10), + // Content based on type + _buildContent(context), + ], + ), + ), + ); + } + + Widget _buildContent(BuildContext context) { + final d = service.energyData; + switch (fav.type) { + case FavoriteType.pvPower: + return _BigMetric( + value: _fmt(d.pvPower), + unit: d.pvPower >= 1000 ? 'kW' : 'W', + label: 'Production solaire', + icon: Icons.wb_sunny_rounded, + color: AppTheme.solarYellow, + trend: d.pvPower > 100 ? '↑ En production' : 'Pas de production', + ); + + case FavoriteType.homePower: + return _BigMetric( + value: _fmt(d.homePower), + unit: d.homePower >= 1000 ? 'kW' : 'W', + label: 'Consommation maison', + icon: Icons.home_rounded, + color: AppTheme.homeBlue, + trend: 'Dont ${d.selfConsumptionRate.toStringAsFixed(0)}% solaire', + ); + + case FavoriteType.batterySOC: + return _BatteryWidget(data: d); + + case FavoriteType.gridPower: + return _BigMetric( + value: _fmt(d.gridPower.abs()), + unit: d.gridPower.abs() >= 1000 ? 'kW' : 'W', + label: d.gridPower >= 0 ? 'Import réseau' : 'Export réseau', + icon: Icons.electrical_services_rounded, + color: d.gridPower >= 0 ? Colors.orange : AppTheme.primaryGreen, + trend: d.gridPower >= 0 ? '↓ Achat réseau' : '↑ Vente réseau', + ); + + case FavoriteType.rates: + return _RatesWidget(data: d); + + case FavoriteType.evCharger: + return _EVWidget(data: d, service: service); + + case FavoriteType.thingState: + return _ThingStateWidget(fav: fav, service: service); + + case FavoriteType.chart: + return _MiniChart(historyPoints: service.historyPoints); + } + } + + String _fmt(double w) { + if (w >= 1000) return (w / 1000).toStringAsFixed(1); + return w.toStringAsFixed(0); + } +} + +// ── Sub-widgets ─────────────────────────────────────────────────────────────── + +class _BigMetric extends StatelessWidget { + final String value; + final String unit; + final String label; + final IconData icon; + final Color color; + final String trend; + + const _BigMetric({ + required this.value, + required this.unit, + required this.label, + required this.icon, + required this.color, + required this.trend, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: color.withValues(alpha:0.12), + borderRadius: BorderRadius.circular(14), + ), + child: Icon(icon, color: color, size: 30), + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(value, + style: TextStyle( + fontSize: 34, + fontWeight: FontWeight.bold, + color: color)), + const SizedBox(width: 4), + Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Text(unit, + style: const TextStyle( + fontSize: 16, color: AppTheme.textLight)), + ), + ], + ), + Text(label, + style: const TextStyle( + fontSize: 12, color: AppTheme.textLight)), + const SizedBox(height: 2), + Text(trend, + style: TextStyle( + fontSize: 11, + color: color, + fontWeight: FontWeight.w500)), + ], + ), + ], + ); + } +} + +class _BatteryWidget extends StatelessWidget { + final EnergyData data; + const _BatteryWidget({required this.data}); + + @override + Widget build(BuildContext context) { + final soc = data.batterySOC; + final isCharging = data.batteryPower > 0; + final color = soc > 50 + ? AppTheme.batteryGreen + : soc > 20 + ? Colors.orange + : Colors.red; + + return Row( + children: [ + Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: 80, + height: 80, + child: CircularProgressIndicator( + value: soc / 100, + strokeWidth: 8, + backgroundColor: color.withValues(alpha:0.15), + valueColor: AlwaysStoppedAnimation(color), + ), + ), + Column( + children: [ + Text('${soc.toStringAsFixed(0)}%', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: color)), + Icon( + isCharging + ? Icons.bolt + : Icons.battery_std_rounded, + size: 14, + color: color), + ], + ), + ], + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('État de charge', + style: const TextStyle( + fontSize: 13, color: AppTheme.textLight)), + const SizedBox(height: 4), + Text( + isCharging + ? '⚡ En charge ${_fmt(data.batteryPower)} W' + : data.batteryPower < -10 + ? '↓ Décharge ${_fmt(data.batteryPower.abs())} W' + : '○ Standby', + style: TextStyle( + fontSize: 13, + color: color, + fontWeight: FontWeight.w600), + ), + ], + ), + ], + ); + } + + String _fmt(double w) => w >= 1000 + ? '${(w / 1000).toStringAsFixed(1)} kW' + : '${w.toStringAsFixed(0)} W'; +} + +class _RatesWidget extends StatelessWidget { + final EnergyData data; + const _RatesWidget({required this.data}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _RateItem( + label: 'Autoconsommation', + value: data.selfConsumptionRate, + color: AppTheme.solarYellow)), + const SizedBox(width: 16), + Expanded( + child: _RateItem( + label: 'Autonomie', + value: data.autonomyRate, + color: AppTheme.homeBlue)), + ], + ); + } +} + +class _RateItem extends StatelessWidget { + final String label; + final double value; + final Color color; + const _RateItem( + {required this.label, required this.value, required this.color}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text('${value.toStringAsFixed(1)}%', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: color)), + const SizedBox(height: 4), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: (value / 100).clamp(0, 1), + backgroundColor: color.withValues(alpha:0.15), + valueColor: AlwaysStoppedAnimation(color), + minHeight: 7, + ), + ), + const SizedBox(height: 4), + Text(label, + style: const TextStyle(fontSize: 11, color: AppTheme.textLight), + textAlign: TextAlign.center), + ], + ); + } +} + +class _EVWidget extends StatelessWidget { + final EnergyData data; + final NymeaService service; + const _EVWidget({required this.data, required this.service}); + + @override + Widget build(BuildContext context) { + final modeColors = { + ChargingMode.pv: AppTheme.pvGreen, + ChargingMode.minPv: AppTheme.minPvBlue, + ChargingMode.boost: AppTheme.boostRed, + }; + final modeLabels = { + ChargingMode.pv: 'PV', + ChargingMode.minPv: 'Min+PV', + ChargingMode.boost: 'Boost', + }; + final color = modeColors[data.chargingMode]!; + + return Row( + children: [ + Container( + width: 52, + height: 52, + decoration: BoxDecoration( + color: color.withValues(alpha:0.12), + borderRadius: BorderRadius.circular(14)), + child: Icon(Icons.electric_car_rounded, color: color, size: 28), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('${data.chargingPower.toStringAsFixed(1)} kW', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: color)), + Text('Mode : ${modeLabels[data.chargingMode]}', + style: TextStyle(fontSize: 12, color: color)), + Text( + '${data.solarSourcePercent.toStringAsFixed(0)}% solaire · Réseau ${data.gridLimitOk ? "OK" : "!"}', + style: const TextStyle( + fontSize: 11, color: AppTheme.textLight)), + ], + ), + ), + // Quick mode toggle + Column( + children: ChargingMode.values.map((m) { + final c = modeColors[m]!; + return GestureDetector( + onTap: () => service.setChargingMode(m), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + width: 8, + height: 8, + margin: const EdgeInsets.symmetric(vertical: 3), + decoration: BoxDecoration( + color: data.chargingMode == m ? c : c.withValues(alpha:0.3), + shape: BoxShape.circle, + ), + ), + ); + }).toList(), + ), + ], + ); + } +} + +class _ThingStateWidget extends StatelessWidget { + final FavoriteWidget fav; + final NymeaService service; + const _ThingStateWidget( + {required this.fav, required this.service}); + + @override + Widget build(BuildContext context) { + final thing = service.things.firstWhere( + (t) => t.id == fav.thingId, + orElse: () => NymeaThing( + id: '', name: 'Introuvable', thingClassId: '', + setupStatus: '', paramValues: [])); + + final value = fav.stateTypeId != null + ? thing.stateValue(fav.stateTypeId!) + : null; + + return Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: AppTheme.primaryGreen.withValues(alpha:0.12), + borderRadius: BorderRadius.circular(12)), + child: const Icon(Icons.device_hub_rounded, + color: AppTheme.primaryGreen, size: 26), + ), + const SizedBox(width: 14), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(thing.name, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + fontSize: 14)), + const SizedBox(height: 4), + Text(value?.toString() ?? '—', + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: AppTheme.primaryGreen)), + ], + ), + ], + ); + } +} + +class _MiniChart extends StatelessWidget { + final List historyPoints; + const _MiniChart({required this.historyPoints}); + + @override + Widget build(BuildContext context) { + if (historyPoints.isEmpty) { + return const SizedBox( + height: 60, + child: Center(child: Text('Pas de données', style: TextStyle(color: AppTheme.textLight)))); + } + final max = historyPoints.map((p) => p.pvWh).reduce((a, b) => a > b ? a : b); + + return SizedBox( + height: 60, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: historyPoints.map((p) { + final h = max > 0 ? (p.pvWh / max) * 50 : 0.0; + return Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 1), + child: Container( + height: h.clamp(2, 50), + decoration: BoxDecoration( + color: AppTheme.solarYellow.withValues(alpha:0.7), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(3)), + ), + ), + ), + ); + }).toList(), + ), + ); + } +} + +// ── Empty state ─────────────────────────────────────────────────────────────── + +class _EmptyState extends StatelessWidget { + final VoidCallback onAdd; + const _EmptyState({required this.onAdd}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 90, + height: 90, + decoration: BoxDecoration( + color: AppTheme.primaryGreen.withValues(alpha:0.1), + shape: BoxShape.circle, + ), + child: const Icon(Icons.star_outline_rounded, + color: AppTheme.primaryGreen, size: 48), + ), + const SizedBox(height: 16), + const Text('Aucun favori', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: AppTheme.textDark)), + const SizedBox(height: 8), + const Text('Ajoutez des widgets pour un accès rapide', + style: TextStyle(color: AppTheme.textLight)), + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: onAdd, + icon: const Icon(Icons.add), + label: const Text('Ajouter un widget'), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryGreen, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + padding: + const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + ), + ), + ], + ), + ); + } +} + +// ── Add favorite sheet ──────────────────────────────────────────────────────── + +class _AddFavoriteSheet extends StatelessWidget { + final NymeaService service; + const _AddFavoriteSheet({required this.service}); + + @override + Widget build(BuildContext context) { + final options = [ + _FavOption(FavoriteType.pvPower, '☀️ Production PV', + 'Puissance solaire instantanée', Icons.wb_sunny_rounded, + AppTheme.solarYellow), + _FavOption(FavoriteType.homePower, '🏠 Consommation', + 'Puissance consommée', Icons.home_rounded, AppTheme.homeBlue), + _FavOption(FavoriteType.batterySOC, '🔋 Batterie', + 'État de charge', Icons.battery_charging_full_rounded, + AppTheme.batteryGreen), + _FavOption(FavoriteType.gridPower, '⚡ Réseau', + 'Import / Export réseau', Icons.electrical_services_rounded, + AppTheme.gridGray), + _FavOption(FavoriteType.rates, '📊 Taux', + 'Autoconsommation & autonomie', Icons.pie_chart_rounded, + Colors.deepPurple), + _FavOption(FavoriteType.evCharger, '🚗 Borne EV', + 'Recharge véhicule', Icons.electric_car_rounded, + AppTheme.minPvBlue), + _FavOption(FavoriteType.chart, '📈 Graphique', + 'Production journalière', Icons.bar_chart_rounded, + Colors.teal), + ]; + + return DraggableScrollableSheet( + initialChildSize: 0.6, + maxChildSize: 0.9, + minChildSize: 0.4, + expand: false, + builder: (_, controller) => Column( + children: [ + Container( + margin: const EdgeInsets.only(top: 8, bottom: 16), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(2)), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 20), + child: Text('Ajouter un widget', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), + ), + const SizedBox(height: 12), + Expanded( + child: ListView( + controller: controller, + padding: const EdgeInsets.symmetric(horizontal: 16), + children: options.map((opt) { + final alreadyAdded = service.favoriteWidgets + .any((f) => f.type == opt.type); + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: ListTile( + leading: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: opt.color.withValues(alpha:0.12), + borderRadius: BorderRadius.circular(12)), + child: Icon(opt.icon, color: opt.color), + ), + title: Text(opt.title, + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text(opt.subtitle, + style: const TextStyle(fontSize: 12)), + trailing: alreadyAdded + ? const Icon(Icons.check_circle, + color: AppTheme.primaryGreen) + : const Icon(Icons.add_circle_outline, + color: AppTheme.primaryGreen), + onTap: alreadyAdded + ? null + : () { + service.addFavorite(FavoriteWidget( + id: 'fw_${opt.type.name}_${DateTime.now().millisecondsSinceEpoch}', + type: opt.type, + title: opt.title.split(' ').skip(1).join(' '), + )); + Navigator.pop(context); + }, + ), + ); + }).toList(), + ), + ), + ], + ), + ); + } +} + +class _FavOption { + final FavoriteType type; + final String title; + final String subtitle; + final IconData icon; + final Color color; + const _FavOption( + this.type, this.title, this.subtitle, this.icon, this.color); +} diff --git a/lib/screens/thing_detail_screen.dart b/lib/screens/thing_detail_screen.dart new file mode 100644 index 0000000..cae969e --- /dev/null +++ b/lib/screens/thing_detail_screen.dart @@ -0,0 +1,655 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../models/nymea_models.dart'; +import '../models/thing_category.dart'; +import '../services/nymea_service.dart'; +import '../theme/app_theme.dart'; + +// ───────────────────────────────────────────────────────────────────────────── +// ThingDetailScreen — fiche complète d'un appareil +// ───────────────────────────────────────────────────────────────────────────── + +class ThingDetailScreen extends StatelessWidget { + final NymeaThing thing; + + const ThingDetailScreen({super.key, required this.thing}); + + @override + Widget build(BuildContext context) { + final service = context.watch(); + + // Récupérer le thing à jour (les états sont mis à jour en temps réel) + final liveThing = service.things.firstWhere( + (t) => t.id == thing.id, + orElse: () => thing, + ); + + NymeaThingClass? cls; + try { + cls = service.thingClasses.firstWhere((c) => c.id == thing.thingClassId); + } catch (_) {} + + final catInfo = cls != null + ? categoryInfoMap[cls.category] ?? categoryInfoMap[ThingCategory.other]! + : categoryInfoMap[ThingCategory.other]!; + + // Grouper les états par thème + final stateGroups = _groupStates(cls?.stateTypes ?? [], liveThing); + + return Scaffold( + backgroundColor: AppTheme.backgroundGray, + body: CustomScrollView( + slivers: [ + // ── SliverAppBar avec header coloré ───────────────────────────── + SliverAppBar( + expandedHeight: 160, + pinned: true, + backgroundColor: catInfo.color, + foregroundColor: Colors.white, + actions: [ + if (!service.isSimulation) + IconButton( + icon: const Icon(Icons.edit_outlined, color: Colors.white), + onPressed: () => + _showRenameDialog(context, service, liveThing), + ), + ], + flexibleSpace: FlexibleSpaceBar( + background: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + catInfo.color, + catInfo.color.withValues(alpha: 0.7), + ], + ), + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 56, 20, 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + width: 56, height: 56, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(16), + ), + child: Icon(catInfo.icon, + color: Colors.white, size: 30), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(liveThing.name, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis), + const SizedBox(height: 2), + Text( + cls?.displayName ?? catInfo.label, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.8), + fontSize: 13), + ), + ], + ), + ), + _OnlineBadge(thing: liveThing, thingClass: cls), + ], + ), + ), + ), + ), + ), + ), + + // ── Contenu ────────────────────────────────────────────────────── + SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildListDelegate([ + // Actions rapides (si la classe en a) + if (cls != null && cls.actionTypes.isNotEmpty) ...[ + _ActionsSection(thing: liveThing, thingClass: cls), + const SizedBox(height: 16), + ], + + // États groupés + ...stateGroups.entries.map((entry) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _SectionHeader(title: entry.key), + const SizedBox(height: 8), + Card( + child: Column( + children: entry.value.asMap().entries.map((e) { + final isLast = e.key == entry.value.length - 1; + return _StateRow( + stateType: e.value, + value: liveThing.stateValue(e.value.id), + isLast: isLast, + color: catInfo.color, + ); + }).toList(), + ), + ), + const SizedBox(height: 16), + ], + )), + + // Interfaces nymea + if (cls != null && cls.interfaces.isNotEmpty) ...[ + _SectionHeader(title: 'Interfaces'), + const SizedBox(height: 8), + Wrap( + spacing: 8, runSpacing: 6, + children: cls.interfaces.map((iface) => Chip( + label: Text(iface, + style: const TextStyle(fontSize: 12)), + backgroundColor: catInfo.color.withValues(alpha: 0.1), + side: BorderSide(color: catInfo.color.withValues(alpha: 0.3)), + padding: const EdgeInsets.symmetric(horizontal: 4), + )).toList(), + ), + const SizedBox(height: 16), + ], + + // Section Réglages + if (cls != null && cls.settingsTypes.isNotEmpty) ...[ + const _SectionHeader(title: 'Réglages'), + const SizedBox(height: 8), + Card( + child: Column( + children: cls.settingsTypes.asMap().entries.map((e) { + final isLast = e.key == cls!.settingsTypes.length - 1; + final current = liveThing.settingValue(e.value.id); + return _SettingRow( + settingType: e.value, + value: current, + isLast: isLast, + onEdit: (newVal) => service.setThingSettings( + liveThing.id, e.value.id, newVal), + ); + }).toList(), + ), + ), + const SizedBox(height: 16), + ], + + // Info technique + _SectionHeader(title: 'Informations'), + const SizedBox(height: 8), + Card( + child: Column(children: [ + _InfoRow('ID', liveThing.id, isLast: false), + _InfoRow('Classe', liveThing.thingClassId, isLast: false), + _InfoRow('Statut setup', liveThing.setupStatus + .replaceAll('ThingSetupStatus', ''), isLast: true), + ]), + ), + const SizedBox(height: 16), + + // Section Gestion + if (!service.isSimulation) ...[ + const _SectionHeader(title: 'Gestion'), + const SizedBox(height: 8), + Card( + child: Column(children: [ + ListTile( + leading: const Icon(Icons.edit_rounded, + color: AppTheme.primaryGreen), + title: const Text('Renommer'), + onTap: () => + _showRenameDialog(context, service, liveThing), + ), + const Divider(height: 1, indent: 16, endIndent: 16), + ListTile( + leading: const Icon(Icons.delete_outline_rounded, + color: Colors.red), + title: const Text('Supprimer', + style: TextStyle(color: Colors.red)), + onTap: () => + _showRemoveDialog(context, service, liveThing), + ), + ]), + ), + const SizedBox(height: 16), + ], + + const SizedBox(height: 60), + ]), + ), + ), + ], + ), + ); + } + + // ── Groupement des états par thème ──────────────────────────────────────── + + Map> _groupStates( + List stateTypes, + NymeaThing thing, + ) { + final Map> groups = {}; + + for (final st in stateTypes) { + final group = _groupName(st.name); + groups.putIfAbsent(group, () => []).add(st); + } + + // Trier : Puissance en premier, puis alphabétique + final ordered = >{}; + const firstGroups = ['Puissance', 'Énergie', 'Mesures électriques', 'État']; + for (final g in firstGroups) { + if (groups.containsKey(g)) ordered[g] = groups[g]!; + } + for (final entry in groups.entries) { + if (!ordered.containsKey(entry.key)) ordered[entry.key] = entry.value; + } + + return ordered; + } + + String _groupName(String stateName) { + final n = stateName.toLowerCase(); + if (n.contains('power') || n.contains('currentpower')) { return 'Puissance'; } + if (n.contains('energy') || n.contains('total')) { return 'Énergie'; } + if (n.contains('voltage') || n.contains('current') || n.contains('frequency') + || n.contains('ampere') || n.contains('volt') || n.contains('hertz') + || n.contains('powerfactor')) { return 'Mesures électriques'; } + if (n.contains('temperature') || n.contains('humidity') || n.contains('pressure') + || n.contains('co2') || n.contains('lux')) { return 'Environnement'; } + if (n.contains('connected') || n.contains('reachable') || n.contains('status') + || n.contains('mode') || n.contains('on')) { return 'État'; } + if (n.contains('soc') || n.contains('battery') || n.contains('charging')) { return 'Batterie'; } + return 'Divers'; + } + + void _showRenameDialog( + BuildContext ctx, NymeaService svc, NymeaThing t) { + final ctrl = TextEditingController(text: t.name); + showDialog( + context: ctx, + builder: (c) => AlertDialog( + title: const Text('Renommer'), + content: TextField( + controller: ctrl, + autofocus: true, + decoration: const InputDecoration(border: OutlineInputBorder()), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(c), + child: const Text('Annuler')), + ElevatedButton( + onPressed: () async { + final name = ctrl.text.trim(); + if (name.isNotEmpty) await svc.renameThing(t.id, name); + if (c.mounted) Navigator.pop(c); + }, + child: const Text('Renommer'), + ), + ], + ), + ); + } + + void _showRemoveDialog( + BuildContext ctx, NymeaService svc, NymeaThing t) { + showDialog( + context: ctx, + builder: (c) => AlertDialog( + title: const Text('Supprimer'), + content: Text('Supprimer "${t.name}" définitivement ?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(c), + child: const Text('Annuler')), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, foregroundColor: Colors.white), + onPressed: () async { + await svc.removeThing(t.id); + if (c.mounted) { + Navigator.pop(c); + Navigator.pop(ctx); + } + }, + child: const Text('Supprimer'), + ), + ], + ), + ); + } +} + +// ─── Section actions ────────────────────────────────────────────────────────── + +class _ActionsSection extends StatelessWidget { + final NymeaThing thing; + final NymeaThingClass thingClass; + const _ActionsSection({required this.thing, required this.thingClass}); + + @override + Widget build(BuildContext context) { + final service = context.read(); + + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + const _SectionHeader(title: 'Actions'), + const SizedBox(height: 8), + Wrap( + spacing: 10, runSpacing: 8, + children: thingClass.actionTypes.map((action) => ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryGreen, + foregroundColor: Colors.white, + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + ), + icon: const Icon(Icons.play_arrow_rounded, size: 18), + label: Text(action.displayName, + style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)), + onPressed: () => _executeAction(context, service, action), + )).toList(), + ), + ]); + } + + void _executeAction(BuildContext context, NymeaService service, + NymeaActionType action) async { + if (action.paramTypes.isEmpty) { + // Action sans paramètre → exécuter directement + await service.executeAction( + thingId: thing.id, + actionTypeId: action.id, + params: {}, + ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Action "${action.displayName}" exécutée'), + backgroundColor: AppTheme.primaryGreen, + duration: const Duration(seconds: 2), + )); + } + } else { + // Action avec paramètres → dialog + _showActionParamDialog(context, service, action); + } + } + + void _showActionParamDialog(BuildContext context, NymeaService service, + NymeaActionType action) { + // Simple pour l'instant : juste afficher les paramètres requis + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text(action.displayName), + content: Column( + mainAxisSize: MainAxisSize.min, + children: action.paramTypes.map((p) => ListTile( + dense: true, + title: Text(p['displayName']?.toString() ?? p['name']?.toString() ?? ''), + subtitle: Text('Type: ${p['type']}'), + )).toList(), + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx), + child: const Text('Fermer')), + ], + ), + ); + } +} + +// ─── Widgets utilitaires ────────────────────────────────────────────────────── + +class _SectionHeader extends StatelessWidget { + final String title; + const _SectionHeader({required this.title}); + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.only(left: 4), + child: Text(title, + style: const TextStyle( + fontSize: 13, fontWeight: FontWeight.w700, + color: AppTheme.textLight, letterSpacing: 0.5)), + ); +} + +class _StateRow extends StatelessWidget { + final NymeaStateType stateType; + final dynamic value; + final bool isLast; + final Color color; + + const _StateRow({ + required this.stateType, required this.value, + required this.isLast, required this.color, + }); + + @override + Widget build(BuildContext context) { + final formatted = stateType.formatValue(value); + final isBool = stateType.type == 'Bool'; + + return Column(children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), + child: Row(children: [ + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(stateType.displayName, + style: const TextStyle( + fontSize: 14, color: AppTheme.textDark, + fontWeight: FontWeight.w500)), + if (stateType.name != stateType.displayName) + Text(stateType.name, + style: const TextStyle( + fontSize: 10, color: AppTheme.textLight)), + ]), + ), + if (isBool) + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3), + decoration: BoxDecoration( + color: (value == true || value == 'true') + ? AppTheme.primaryGreen.withValues(alpha: 0.15) + : Colors.grey.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(20), + ), + child: Text(formatted, + style: TextStyle( + fontSize: 12, fontWeight: FontWeight.bold, + color: (value == true || value == 'true') + ? AppTheme.primaryGreen : AppTheme.textLight)), + ) + else + Text(formatted, + style: TextStyle( + fontSize: 15, fontWeight: FontWeight.bold, + color: color)), + ]), + ), + if (!isLast) + const Divider(height: 1, indent: 16, endIndent: 16, + color: Color(0xFFEEEEEE)), + ]); + } +} + +class _InfoRow extends StatelessWidget { + final String label; + final String value; + final bool isLast; + const _InfoRow(this.label, this.value, {required this.isLast}); + + @override + Widget build(BuildContext context) => Column(children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row(children: [ + Text(label, + style: const TextStyle(fontSize: 13, color: AppTheme.textLight)), + const SizedBox(width: 12), + Expanded( + child: SelectableText(value, + textAlign: TextAlign.end, + style: const TextStyle( + fontSize: 12, fontWeight: FontWeight.w500, + color: AppTheme.textDark)), + ), + ]), + ), + if (!isLast) const Divider(height: 1, indent: 16, endIndent: 16, + color: Color(0xFFEEEEEE)), + ]); +} + +class _SettingRow extends StatelessWidget { + final NymeaStateType settingType; + final dynamic value; + final bool isLast; + final Future Function(dynamic) onEdit; + + const _SettingRow({ + required this.settingType, + required this.value, + required this.isLast, + required this.onEdit, + }); + + @override + Widget build(BuildContext context) { + final formatted = settingType.formatValue(value); + return Column(children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: Row(children: [ + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(settingType.displayName, + style: const TextStyle( + fontSize: 14, color: AppTheme.textDark, + fontWeight: FontWeight.w500)), + if (settingType.name != settingType.displayName) + Text(settingType.name, + style: const TextStyle( + fontSize: 10, color: AppTheme.textLight)), + ]), + ), + Text(formatted, + style: const TextStyle( + fontSize: 14, color: AppTheme.textLight)), + const SizedBox(width: 8), + IconButton( + icon: const Icon(Icons.edit_outlined, size: 18, + color: AppTheme.textLight), + onPressed: () => _showEditDialog(context), + ), + ]), + ), + if (!isLast) + const Divider(height: 1, indent: 16, endIndent: 16, + color: Color(0xFFEEEEEE)), + ]); + } + + void _showEditDialog(BuildContext context) { + if (settingType.type == 'Bool') { + final current = value == true || value == 'true'; + onEdit(!current); + return; + } + final ctrl = TextEditingController(text: value?.toString() ?? ''); + showDialog( + context: context, + builder: (c) => AlertDialog( + title: Text(settingType.displayName), + content: TextField( + controller: ctrl, + autofocus: true, + keyboardType: (settingType.type == 'Int' || settingType.type == 'Double') + ? const TextInputType.numberWithOptions(decimal: true) + : TextInputType.text, + decoration: InputDecoration( + border: const OutlineInputBorder(), + suffixText: settingType.unit, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(c), + child: const Text('Annuler')), + ElevatedButton( + onPressed: () { + dynamic newVal = ctrl.text.trim(); + if (settingType.type == 'Int') { + newVal = int.tryParse(ctrl.text.trim()) ?? value; + } else if (settingType.type == 'Double') { + newVal = double.tryParse(ctrl.text.trim()) ?? value; + } + onEdit(newVal); + Navigator.pop(c); + }, + child: const Text('Enregistrer'), + ), + ], + ), + ); + } +} + +class _OnlineBadge extends StatelessWidget { + final NymeaThing thing; + final NymeaThingClass? thingClass; + const _OnlineBadge({required this.thing, required this.thingClass}); + + bool _isOnline() { + final connType = thingClass?.stateTypes + .where((s) => s.name.toLowerCase() == 'connected'); + if (connType != null && connType.isNotEmpty) { + final val = thing.stateValue(connType.first.id); + return val == true || val == 'true'; + } + return thing.isSetupComplete; + } + + @override + Widget build(BuildContext context) { + final online = _isOnline(); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Container( + width: 8, height: 8, + decoration: BoxDecoration( + color: online ? Colors.greenAccent : Colors.grey[300], + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 6), + Text(online ? 'En ligne' : 'Hors ligne', + style: const TextStyle( + color: Colors.white, fontSize: 12, + fontWeight: FontWeight.w600)), + ]), + ); + } +} \ No newline at end of file diff --git a/lib/screens/things_screen.dart b/lib/screens/things_screen.dart new file mode 100644 index 0000000..9385d55 --- /dev/null +++ b/lib/screens/things_screen.dart @@ -0,0 +1,596 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../models/nymea_models.dart'; +import '../models/thing_category.dart'; +import '../services/nymea_service.dart'; +import '../theme/app_theme.dart'; +import 'thing_detail_screen.dart'; + +class ThingsScreen extends StatefulWidget { + const ThingsScreen({super.key}); + @override + State createState() => _ThingsScreenState(); +} + +class _ThingsScreenState extends State { + final Set _collapsed = {}; + String _searchQuery = ''; + bool _showSearch = false; + + @override + Widget build(BuildContext context) { + final service = context.watch(); + // Garder tous les things avec un id valide (le filtre setupStatus était trop strict) + final things = service.things.where((t) => t.id.isNotEmpty).toList(); + final thingClasses = service.thingClasses; + + final filtered = _searchQuery.isEmpty + ? things + : things.where((t) => + t.name.toLowerCase().contains(_searchQuery.toLowerCase())).toList(); + + final Map> grouped = {}; + for (final thing in filtered) { + final cls = _classFor(thing, thingClasses); + final cat = cls?.category ?? ThingCategory.other; + grouped.putIfAbsent(cat, () => []).add(thing); + } + + const orderedCats = [ + ThingCategory.energy, ThingCategory.solar, ThingCategory.battery, + ThingCategory.evCharger, ThingCategory.hvac, ThingCategory.lighting, + ThingCategory.sensors, ThingCategory.network, ThingCategory.notifications, + ThingCategory.weather, ThingCategory.media, ThingCategory.other, + ]; + final cats = orderedCats.where((c) => grouped.containsKey(c)).toList(); + + return Scaffold( + backgroundColor: AppTheme.backgroundGray, + appBar: _buildAppBar(service, things.length), + body: !service.isConnected + ? _buildDisconnected() + : (things.isEmpty && !service.thingsLoaded) + ? _buildLoading() + : things.isEmpty + ? _buildEmpty() + : cats.isEmpty + // Classes pas encore chargées — afficher things en mode simple + ? _buildThingsSimple(things, service) + : ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 100), + itemCount: cats.length, + itemBuilder: (context, i) { + final cat = cats[i]; + return _CategorySection( + info: categoryInfoMap[cat]!, + things: grouped[cat]!, + thingClasses: thingClasses, + collapsed: _collapsed.contains(cat), + onToggle: () => setState(() { + if (_collapsed.contains(cat)) { + _collapsed.remove(cat); + } else { + _collapsed.add(cat); + } + }), + onTap: (t) => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ThingDetailScreen(thing: t)), + ), + ); + }, + ), + ); + } + + AppBar _buildAppBar(NymeaService service, int count) { + return AppBar( + backgroundColor: AppTheme.backgroundGray, + elevation: 0, + title: _showSearch + ? TextField( + autofocus: true, + decoration: const InputDecoration( + hintText: 'Rechercher un appareil...', + border: InputBorder.none, + hintStyle: TextStyle(color: AppTheme.textLight), + ), + style: const TextStyle(color: AppTheme.textDark, fontSize: 18), + onChanged: (v) => setState(() => _searchQuery = v), + ) + : Row(children: [ + const Text('Things', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + fontSize: 20)), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: AppTheme.primaryGreen.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + ), + child: Text('$count', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: AppTheme.primaryGreen)), + ), + ]), + actions: [ + IconButton( + icon: Icon(_showSearch ? Icons.close : Icons.search, + color: AppTheme.textDark), + onPressed: () => setState(() { + _showSearch = !_showSearch; + _searchQuery = ''; + }), + ), + IconButton( + icon: const Icon(Icons.refresh_rounded, color: AppTheme.textDark), + onPressed: () => service.refresh(), + ), + ], + ); + } + + Widget _buildLoading() => const Center( + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + CircularProgressIndicator(color: AppTheme.primaryGreen), + SizedBox(height: 16), + Text('Chargement des appareils...', + style: TextStyle(color: AppTheme.textLight)), + ]), + ); + + Widget _buildDisconnected() => Center( + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.cloud_off_rounded, size: 64, color: Colors.grey[400]), + const SizedBox(height: 12), + const Text('Non connecté à nymea', + style: TextStyle(color: AppTheme.textLight, fontSize: 16)), + ]), + ); + + /// Affichage simple quand les ThingClasses ne sont pas encore chargées + Widget _buildThingsSimple(List things, NymeaService service) { + return ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 100), + itemCount: things.length, + itemBuilder: (context, i) { + final t = things[i]; + return Card( + margin: const EdgeInsets.only(bottom: 8), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: ListTile( + leading: Container( + width: 40, height: 40, + decoration: BoxDecoration( + color: AppTheme.primaryGreen.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + ), + child: const Icon(Icons.device_hub_rounded, + color: AppTheme.primaryGreen, size: 22), + ), + title: Text(t.name, + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)), + subtitle: Text(t.setupStatus.replaceAll('ThingSetupStatus', ''), + style: const TextStyle(fontSize: 12, color: AppTheme.textLight)), + trailing: t.setupStatus == 'ThingSetupStatusComplete' + ? Container( + width: 8, height: 8, + decoration: const BoxDecoration( + color: AppTheme.primaryGreen, + shape: BoxShape.circle, + ), + ) + : null, + ), + ); + }, + ); + } + + Widget _buildEmpty() => Center( + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.search_off_rounded, size: 64, color: Colors.grey[400]), + const SizedBox(height: 12), + Text( + _searchQuery.isEmpty ? 'Aucun appareil' : 'Aucun résultat pour "$_searchQuery"', + style: const TextStyle(color: AppTheme.textLight, fontSize: 16), + ), + ]), + ); + + NymeaThingClass? _classFor(NymeaThing thing, List classes) { + try { return classes.firstWhere((c) => c.id == thing.thingClassId); } + catch (_) { return null; } + } +} + +// ─── Section catégorie ──────────────────────────────────────────────────────── + +class _CategorySection extends StatelessWidget { + final ThingCategoryInfo info; + final List things; + final List thingClasses; + final bool collapsed; + final VoidCallback onToggle; + final void Function(NymeaThing) onTap; + + const _CategorySection({ + required this.info, required this.things, required this.thingClasses, + required this.collapsed, required this.onToggle, required this.onTap, + }); + + NymeaThingClass? _cls(NymeaThing t) { + try { return thingClasses.firstWhere((c) => c.id == t.thingClassId); } + catch (_) { return null; } + } + + @override + Widget build(BuildContext context) { + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + // ── Header collapsable ───────────────────────────────────────────────── + InkWell( + onTap: onToggle, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 4), + child: Row(children: [ + Container( + width: 34, height: 34, + decoration: BoxDecoration( + color: info.color.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(info.icon, color: info.color, size: 19), + ), + const SizedBox(width: 10), + Expanded( + child: Text(info.label, + style: const TextStyle( + fontWeight: FontWeight.w700, fontSize: 13, + color: AppTheme.textDark, letterSpacing: 0.2)), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: info.color.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + ), + child: Text('${things.length}', + style: TextStyle( + fontSize: 11, fontWeight: FontWeight.bold, + color: info.color)), + ), + const SizedBox(width: 4), + AnimatedRotation( + turns: collapsed ? -0.25 : 0, + duration: const Duration(milliseconds: 200), + child: const Icon(Icons.expand_more, + color: AppTheme.textLight, size: 20), + ), + ]), + ), + ), + + // ── Things ──────────────────────────────────────────────────────────── + AnimatedCrossFade( + duration: const Duration(milliseconds: 220), + crossFadeState: collapsed + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + firstChild: Column( + children: [ + ...things.map((t) => Padding( + padding: const EdgeInsets.only(bottom: 10), + child: ThingCard( + thing: t, + thingClass: _cls(t), + categoryInfo: info, + onTap: () => onTap(t), + ), + )), + const SizedBox(height: 4), + ], + ), + secondChild: const SizedBox.shrink(), + ), + ]); + } +} + +// ─── ThingCard ──────────────────────────────────────────────────────────────── + +class ThingCard extends StatelessWidget { + final NymeaThing thing; + final NymeaThingClass? thingClass; + final ThingCategoryInfo categoryInfo; + final VoidCallback onTap; + + const ThingCard({ + super.key, required this.thing, required this.thingClass, + required this.categoryInfo, required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final primaryType = thingClass?.primaryStateType; + final primaryValue = primaryType?.formatValue(thing.stateValue(primaryType.id)); + final secondaryStates = _secondaryStates(); + final isOnline = _isOnline(); + + return Card( + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(14), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Ligne principale + Row(children: [ + Container( + width: 48, height: 48, + decoration: BoxDecoration( + color: categoryInfo.color.withValues(alpha: isOnline ? 0.15 : 0.07), + borderRadius: BorderRadius.circular(13), + ), + child: Icon(categoryInfo.icon, + color: isOnline + ? categoryInfo.color + : categoryInfo.color.withValues(alpha: 0.4), + size: 26), + ), + const SizedBox(width: 12), + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(thing.name, + style: const TextStyle( + fontWeight: FontWeight.w600, fontSize: 15, + color: AppTheme.textDark), + maxLines: 1, overflow: TextOverflow.ellipsis), + const SizedBox(height: 2), + Text(thingClass?.displayName ?? 'Appareil', + style: const TextStyle(fontSize: 12, color: AppTheme.textLight), + maxLines: 1, overflow: TextOverflow.ellipsis), + ]), + ), + Column(crossAxisAlignment: CrossAxisAlignment.end, children: [ + if (primaryValue != null) + Text(primaryValue, + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 16, + color: isOnline + ? categoryInfo.color + : AppTheme.textLight)), + const SizedBox(height: 4), + _StatusDot(isOnline: isOnline), + ]), + const SizedBox(width: 2), + _ThingMenu(thing: thing, onDetail: onTap), + ]), + + // Chips états secondaires + if (secondaryStates.isNotEmpty) ...[ + const SizedBox(height: 10), + const Divider(height: 1, color: Color(0xFFEEEEEE)), + const SizedBox(height: 10), + Wrap( + spacing: 7, runSpacing: 6, + children: secondaryStates.map((s) => _StateChip( + label: s.displayName, + value: s.formatValue(thing.stateValue(s.id)), + color: categoryInfo.color, + )).toList(), + ), + ], + ]), + ), + ), + ); + } + + bool _isOnline() { + final connType = thingClass?.stateTypes + .where((s) => s.name.toLowerCase() == 'connected'); + if (connType != null && connType.isNotEmpty) { + final val = thing.stateValue(connType.first.id); + return val == true || val == 'true'; + } + return thing.isSetupComplete; + } + + List _secondaryStates() { + if (thingClass == null) return []; + final primary = thingClass!.primaryStateType; + const interesting = [ + 'voltage', 'current', 'frequency', 'power', + 'totalenergyconsumed', 'totalenergyproduced', + 'temperature', 'humidity', 'soc', + 'chargingmode', 'mode', 'maxchargingcurrent', 'status', + ]; + return thingClass!.stateTypes + .where((s) => + s != primary && + s.name.toLowerCase() != 'connected' && + interesting.any((i) => s.name.toLowerCase().contains(i))) + .take(3) + .toList(); + } +} + +// ─── Widgets utilitaires ────────────────────────────────────────────────────── + +class _StatusDot extends StatelessWidget { + final bool isOnline; + const _StatusDot({required this.isOnline}); + + @override + Widget build(BuildContext context) => Row(mainAxisSize: MainAxisSize.min, children: [ + Container( + width: 7, height: 7, + decoration: BoxDecoration( + color: isOnline ? AppTheme.primaryGreen : Colors.grey[400], + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 4), + Text( + isOnline ? 'En ligne' : 'Hors ligne', + style: TextStyle( + fontSize: 10, + color: isOnline ? AppTheme.primaryGreen : AppTheme.textLight), + ), + ]); +} + +class _StateChip extends StatelessWidget { + final String label; + final String value; + final Color color; + const _StateChip({required this.label, required this.value, required this.color}); + + @override + Widget build(BuildContext context) => Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color.withValues(alpha: 0.2)), + ), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Flexible( + child: Text(label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 11, color: color.withValues(alpha: 0.8), + fontWeight: FontWeight.w500)), + ), + const SizedBox(width: 5), + Flexible( + child: Text(value, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 11, color: color, fontWeight: FontWeight.bold)), + ), + ]), + ); +} + +// ─── Menu contextuel ────────────────────────────────────────────────────────── + +class _ThingMenu extends StatelessWidget { + final NymeaThing thing; + final VoidCallback onDetail; + const _ThingMenu({required this.thing, required this.onDetail}); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: const Icon(Icons.more_vert, color: AppTheme.textLight, size: 20), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + itemBuilder: (_) => [ + const PopupMenuItem(value: 'detail', + child: Row(children: [ + Icon(Icons.info_outline, size: 18, color: AppTheme.textDark), + SizedBox(width: 10), Text('Détails & États'), + ])), + const PopupMenuItem(value: 'params', + child: Row(children: [ + Icon(Icons.tune, size: 18, color: AppTheme.textDark), + SizedBox(width: 10), Text('Paramètres'), + ])), + const PopupMenuItem(value: 'rename', + child: Row(children: [ + Icon(Icons.edit_outlined, size: 18, color: AppTheme.textDark), + SizedBox(width: 10), Text('Renommer'), + ])), + const PopupMenuItem(value: 'favorite', + child: Row(children: [ + Icon(Icons.star_outline, size: 18, color: AppTheme.textDark), + SizedBox(width: 10), Text('Ajouter aux favoris'), + ])), + ], + onSelected: (value) { + switch (value) { + case 'detail': onDetail(); break; + case 'params': _showParamsSheet(context); break; + case 'rename': _showRenameDialog(context); break; + case 'favorite': + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('${thing.name} ajouté aux favoris'), + backgroundColor: AppTheme.primaryGreen, + duration: const Duration(seconds: 2), + )); + } + }, + ); + } + + void _showParamsSheet(BuildContext context) { + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20))), + builder: (_) => Padding( + padding: const EdgeInsets.all(20), + child: Column(mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row(children: [ + const Icon(Icons.tune, color: AppTheme.primaryGreen), + const SizedBox(width: 8), + Expanded(child: Text('Paramètres — ${thing.name}', + style: const TextStyle(fontWeight: FontWeight.bold, + fontSize: 16, color: AppTheme.textDark))), + ]), + const SizedBox(height: 4), + SelectableText('ID: ${thing.id}', + style: const TextStyle(fontSize: 10, color: AppTheme.textLight)), + const Divider(height: 24), + if (thing.paramValues.isEmpty) + const Text('Aucun paramètre configuré.', + style: TextStyle(color: AppTheme.textLight)) + else + ...thing.paramValues.map((p) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row(children: [ + Expanded(child: Text(p['paramTypeId']?.toString() ?? '?', + style: const TextStyle(color: AppTheme.textLight))), + Text(p['value']?.toString() ?? '—', + style: const TextStyle( + fontWeight: FontWeight.w600, color: AppTheme.textDark)), + ]), + )), + const SizedBox(height: 20), + ]), + ), + ); + } + + void _showRenameDialog(BuildContext context) { + final ctrl = TextEditingController(text: thing.name); + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Renommer'), + content: TextField( + controller: ctrl, autofocus: true, + decoration: const InputDecoration(labelText: 'Nouveau nom'), + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx), + child: const Text('Annuler')), + FilledButton( + onPressed: () { + context.read().renameThing(thing.id, ctrl.text.trim()); + Navigator.pop(ctx); + }, + child: const Text('Renommer'), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/services/nymea_service.dart b/lib/services/nymea_service.dart new file mode 100644 index 0000000..21f04d3 --- /dev/null +++ b/lib/services/nymea_service.dart @@ -0,0 +1,946 @@ +import 'dart:async'; +import 'dart:developer' as dev; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; +import '../models/energy_data.dart'; +import '../models/nymea_models.dart'; + +// ── Protocole de connexion ───────────────────────────────────────────────────── +enum NymeaProtocol { + /// TCP brut port 2222 — nymea parle en premier (\n-delimited JSON) + tcpRaw, + /// WebSocket port 4444 — client envoie JSONRPC.Hello en premier + webSocket, +} + +/// Service de communication nymea JSON-RPC 2.0 +/// Supporte TCP brut (port 2222) et WebSocket (port 4444) +class NymeaService extends ChangeNotifier { + // ── Transport ──────────────────────────────────────────────────────────────── + Socket? _socket; // TCP raw + WebSocketChannel? _wsChannel; // WebSocket + StreamSubscription? _wsSub; + NymeaProtocol _protocol = NymeaProtocol.tcpRaw; + + int _requestId = 1; + final Map>> _pendingRequests = {}; + final StringBuffer _buffer = StringBuffer(); // TCP fragment buffer + + // ── State ──────────────────────────────────────────────────────────────────── + bool _connected = false; + bool _connecting = false; + String _host = '192.168.1.106'; + int _port = 2222; + String? _connectionError; + String? _nymeaVersion; + Timer? _heartbeatTimer; + Timer? _simulationTimer; + bool _isSimulation = false; + + // ── Auth ───────────────────────────────────────────────────────────────────── + String _token = ''; + String _username = ''; + String _password = ''; + + // ── Debug verbeux ───────────────────────────────────────────────────────── + bool _verboseLog = false; // activé depuis l'UI ou à la connexion + bool get verboseLog => _verboseLog; + set verboseLog(bool v) { + _verboseLog = v; + _log('🔧 Verbose logging ${v ? "ON" : "OFF"}', force: true); + } + + void _log(String msg, {bool force = false, String? json}) { + if (!force && !_verboseLog) return; + dev.log(msg, name: 'nymea'); + if (json != null && _verboseLog) { + // Formatter le JSON pour lisibilité + try { + final parsed = jsonDecode(json); + final pretty = const JsonEncoder.withIndent(' ').convert(parsed); + dev.log(pretty, name: 'nymea.json'); + } catch (_) { + dev.log(json, name: 'nymea.json'); + } + } + } + + EnergyData _energyData = const EnergyData(); + List _historyPoints = []; + List _things = []; + List _thingClasses = []; + List _favoriteWidgets = []; + + // ── Getters ────────────────────────────────────────────────────────────────── + bool get connected => _connected; + bool get isConnected => _connected; // alias pour les screens + bool get connecting => _connecting; + bool get isSimulation => _isSimulation; + NymeaProtocol get protocol => _protocol; + String get host => _host; + int get port => _port; + String? get connectionError => _connectionError; + String? get nymeaVersion => _nymeaVersion; + String get username => _username; + String get password => _password; + EnergyData get energyData => _energyData; + List get historyPoints => _historyPoints; + List get things => _things; + List get thingClasses => _thingClasses; + List get favoriteWidgets => _favoriteWidgets; + + // ═══════════════════════════════════════════════════════════════════════════ + // CONNEXION — entrée publique + // ═══════════════════════════════════════════════════════════════════════════ + + Future connect( + String host, + int port, { + NymeaProtocol protocol = NymeaProtocol.tcpRaw, + String username = '', + String password = '', + }) async { + if (_connecting) return false; + _host = host; + _port = port; + _protocol = protocol; + _username = username; + _password = password; + _connectionError = null; + _connecting = true; + _isSimulation = false; + _simulationTimer?.cancel(); + _buffer.clear(); + // Charger le token sauvegardé pour cet hôte + final prefs = await SharedPreferences.getInstance(); + _token = prefs.getString('nymea_token_$host') ?? ''; + if (_token.isNotEmpty) { + _log('🔑 Token restauré depuis SharedPreferences', force: true); + } + notifyListeners(); + + try { + if (protocol == NymeaProtocol.tcpRaw) { + return await _connectTcp(host, port); + } else { + return await _connectWs(host, port); + } + } catch (e) { + _connectionError = e.toString(); + _connected = false; + _connecting = false; + notifyListeners(); + return false; + } + } + + // ── TCP brut : nymea parle en PREMIER ──────────────────────────────────────── + Future _connectTcp(String host, int port) async { + _log('TCP → $host:$port'); + _socket = await Socket.connect(host, port, + timeout: const Duration(seconds: 8)); + + final welcomeCompleter = Completer>(); + bool welcomeReceived = false; + + _socket!.cast>().transform(utf8.decoder).listen( + (chunk) { + _buffer.write(chunk); + final raw = _buffer.toString(); + int start = 0; + for (int i = 0; i < raw.length; i++) { + if (raw[i] == '\n') { + final line = raw.substring(start, i).trim(); + if (line.isNotEmpty) { + try { + final data = jsonDecode(line) as Map; + if (!welcomeReceived) { + welcomeReceived = true; + _log('← raw TCP line (${line.length}): ${line.substring(0, line.length.clamp(0, 120))}…', force: _verboseLog); + if (!welcomeCompleter.isCompleted) { + welcomeCompleter.complete(data); + } + } else { + _processMessage(data); + } + } catch (e) { + _log('TCP parse error: $e', force: true); + } + } + start = i + 1; + } + } + _buffer.clear(); + if (start < raw.length) _buffer.write(raw.substring(start)); + }, + onError: (e) { + if (!welcomeCompleter.isCompleted) welcomeCompleter.completeError(e); + _onError(e); + }, + onDone: () { + if (!welcomeCompleter.isCompleted) { + welcomeCompleter.completeError( + Exception('Socket closed before welcome')); + } + _onDone(); + }, + cancelOnError: false, + ); + + // Attendre le message de bienvenue de nymea + final welcome = await welcomeCompleter.future + .timeout(const Duration(seconds: 8)); + + return _finishConnect(welcome); + } + + // ── WebSocket : client parle en PREMIER (JSONRPC.Hello) ────────────────────── + Future _connectWs(String host, int port) async { + _log('WebSocket → ws://$host:$port'); + final uri = Uri.parse('ws://$host:$port'); + _wsChannel = WebSocketChannel.connect(uri); + + // Attendre que la connexion soit établie + try { + await _wsChannel!.ready.timeout(const Duration(seconds: 8)); + } catch (_) { + // some versions don't have .ready — ignore + } + + _wsSub = _wsChannel!.stream.listen( + (raw) { + try { + // nymea peut envoyer String ou binary (Uint8List) + String str; + if (raw is String) { + str = raw; + } else if (raw is List) { + str = utf8.decode(raw); + } else { + _log('⚠️ WS frame type inattendu: ${raw.runtimeType}', force: true); + return; + } + _log('← WS raw (${str.length} chars): ${str.substring(0, str.length.clamp(0, 80))}…'); + final data = jsonDecode(str) as Map; + _processMessage(data); + } catch (e) { + _log('❌ WS parse error: $e raw type: ${raw.runtimeType}', force: true); + } + }, + onError: (e) { + _log('❌ WS stream error: $e', force: true); + _onError(e); + }, + onDone: _onDone, + cancelOnError: false, + ); + + _log('📡 WS listener actif, envoi Hello...', force: true); + + // Sur WebSocket, c'est le CLIENT qui envoie Hello en premier + final hello = await _sendRequest('JSONRPC.Hello', {}) + .timeout(const Duration(seconds: 8)); + + return _finishConnect(hello); + } + + // ── Finalisation commune ───────────────────────────────────────────────────── + Future _finishConnect(Map welcome) async { + final p = welcome['params'] as Map? ?? welcome; + _nymeaVersion = p['version'] as String? + ?? p['serverVersion'] as String? + ?? 'nymea'; + + _log('✅ Connected to nymea $_nymeaVersion' + ' via ${_protocol == NymeaProtocol.tcpRaw ? "TCP" : "WebSocket"}'); + + // Flux auth : si requis ET pas encore authentifié avec token valide + if (p['authenticationRequired'] == true && p['authenticated'] != true) { + if (_token.isNotEmpty) { + _log('🔑 Token existant présent, tentative d\'utilisation...', force: true); + // Le token sera inclus dans les requêtes — on verra si ça marche + } else if (_username.isNotEmpty) { + try { + await _doAuthenticate(); + } catch (e) { + _connectionError = e.toString(); + _connected = false; + _connecting = false; + notifyListeners(); + return false; + } + } + } + + await _setNotificationsEnabled(); + + _connected = true; + _connecting = false; + notifyListeners(); + + await _initialLoad(); + _startHeartbeat(); + return true; + } + + Future _doAuthenticate() async { + _log('🔐 JSONRPC.Authenticate → user=$_username', force: true); + final r = await _sendRequest('JSONRPC.Authenticate', { + 'username': _username, + 'password': _password, + 'deviceName': 'etm-powersync-app', + }); + _token = r['params']?['token'] as String? ?? ''; + if (_token.isNotEmpty) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('nymea_token_$_host', _token); + _log('✅ Token obtenu et sauvegardé', force: true); + } else { + _log('❌ Authenticate échoué: ${r["params"]}', force: true); + throw Exception('Authentication failed: ${r["params"]}'); + } + } + + Future _setNotificationsEnabled() async { + try { + await _sendRequest('JSONRPC.SetNotificationStatus', {'enabled': true}); + } catch (e) { + _log('SetNotificationStatus: $e'); + } + } + + // ── Déconnexion ────────────────────────────────────────────────────────────── + void disconnect() { + _heartbeatTimer?.cancel(); + _simulationTimer?.cancel(); + _socket?.destroy(); + _socket = null; + _wsSub?.cancel(); + _wsChannel?.sink.close(); + _wsChannel = null; + _connected = false; + _connecting = false; + _isSimulation = false; + _buffer.clear(); + for (final c in _pendingRequests.values) { + c.completeError(Exception('Disconnected')); + } + _pendingRequests.clear(); + notifyListeners(); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // JSON-RPC SEND — \n pour TCP, frame WebSocket pour WS + // ═══════════════════════════════════════════════════════════════════════════ + + Future> _sendRequest( + String method, Map params) async { + if (_socket == null && _wsChannel == null) { + throw Exception('Not connected'); + } + final id = _requestId++; + final completer = Completer>(); + _pendingRequests[id] = completer; + + final msg = jsonEncode({ + 'id': id, 'method': method, 'params': params, + 'token': _token, + }); + _log('→ [$id] $method', force: true, json: msg); + + if (_protocol == NymeaProtocol.tcpRaw) { + _socket!.write('$msg\n'); + } else { + _wsChannel!.sink.add(msg); + } + + // Timeout 15s — évite le blocage si nymea ne répond pas + return completer.future.timeout( + const Duration(seconds: 15), + onTimeout: () { + _pendingRequests.remove(id); + throw TimeoutException('Request $method timed out', const Duration(seconds: 15)); + }, + ); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // MESSAGE PROCESSING (commun TCP + WS) + // ═══════════════════════════════════════════════════════════════════════════ + + void _processMessage(Map data) { + // Réponse à une requête + if (data.containsKey('id')) { + final id = data['id']; + // JSON decode peut retourner num au lieu de int — on normalise + final intId = (id is num) ? id.toInt() : null; + if (intId != null && _pendingRequests.containsKey(intId)) { + _log('← [$intId] réponse status=${data['status'] ?? '?'}', + force: true, json: jsonEncode(data)); + _pendingRequests[intId]!.complete(data); + _pendingRequests.remove(intId); + } else if (intId != null) { + _log('← [$intId] réponse non attendue (déjà timeout?)', force: true); + } + } + // Notification push + if (data.containsKey('notification')) { + final notification = data['notification'] as String; + _log('← push: $notification', force: true, + json: _verboseLog ? jsonEncode(data) : null); + _handleNotification( + notification, + data['params'] as Map? ?? {}, + ); + } + } + + void _handleNotification( + String notification, Map params) { + _log('← push: $notification'); + switch (notification) { + case 'Energy.PowerBalanceChanged': + _parsePowerBalance(params); + break; + case 'Energy.RootMeterChanged': + _parseRootMeter(params); + break; + case 'Integrations.StateChanged': + _handleStateChanged(params); + break; + case 'Integrations.ThingAdded': + final t = params['thing']; + if (t != null) { + _things.add(NymeaThing.fromJson(t as Map)); + notifyListeners(); + } + break; + case 'Integrations.ThingRemoved': + _things.removeWhere((t) => t.id == params['thingId']); + notifyListeners(); + break; + } + } + + void _handleStateChanged(Map params) { + final thingId = params['thingId'] as String?; + final stateTypeId = params['stateTypeId'] as String?; + final value = params['value']; + if (thingId == null || stateTypeId == null) return; + final idx = _things.indexWhere((t) => t.id == thingId); + if (idx >= 0) { + final states = List.from(_things[idx].states); + final si = states.indexWhere((s) => s.stateTypeId == stateTypeId); + if (si >= 0) { + states[si] = NymeaStateValue(stateTypeId: stateTypeId, value: value); + } else { + states.add(NymeaStateValue(stateTypeId: stateTypeId, value: value)); + } + _things[idx] = _things[idx].copyWith(states: states); + notifyListeners(); + } + } + + void _onError(dynamic error) { + _log('❌ Connection error: $error', force: true); + _connectionError = error.toString(); + _connected = false; + for (final c in _pendingRequests.values) { + c.completeError(error); + } + _pendingRequests.clear(); + notifyListeners(); + } + + void _onDone() { + _log('🔌 Connection closed', force: true); + _connected = false; + notifyListeners(); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // HEARTBEAT & INITIAL LOAD + // ═══════════════════════════════════════════════════════════════════════════ + + Future _initialLoad() async { + await Future.wait([ + _loadThings(), + _loadEnergyStatus(), + _loadEnergyHistory(), + ]); + } + + void _startHeartbeat() { + _heartbeatTimer?.cancel(); + // Polling toutes les 5 secondes pour rafraîchir les données énergie + _heartbeatTimer = Timer.periodic(const Duration(seconds: 5), (_) async { + if (!_connected || _isSimulation) return; + try { + await _loadEnergyStatus(); + } catch (_) { + _connected = false; + notifyListeners(); + } + }); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // ENERGY MANAGER + // ═══════════════════════════════════════════════════════════════════════════ + + Future _loadEnergyStatus() async { + try { + final r = await _sendRequest('Energy.GetPowerBalance', {}); + _log('📊 PowerBalance: $r'); + _parsePowerBalance(r); + } catch (e) { _log('GetPowerBalance error: $e'); } + } + + // ── nymea 1.14 Energy.PowerBalanceChanged / Energy.GetPowerBalance ─────────── + // Champs réels confirmés depuis les logs : + // currentPowerProduction : W solaire (NÉGATIF! ex: -1190 = 1190W PV) + // currentPowerConsumption : W maison (positif) + // currentPowerAcquisition : W réseau (positif = import, 0 = export) + // currentPowerStorage : W batterie (+ charge, - décharge) + // totalProduction : kWh PV cumulé + // totalConsumption : kWh maison cumulé + // totalAcquisition : kWh réseau importé cumulé + // totalReturn : kWh réseau exporté cumulé + void _parsePowerBalance(Map response) { + final p = response['params'] as Map? ?? response; + _log('📊 Energy fields: ${p.keys.toList()}'); + + double? n(String k) => (p[k] as num?)?.toDouble(); + + // Production PV est négative dans nymea (convention producteur = négatif) + final rawProd = n('currentPowerProduction'); + final pv = rawProd != null ? rawProd.abs() : 0.0; + + final home = n('currentPowerConsumption') ?? 0.0; + final grid = n('currentPowerAcquisition') ?? 0.0; // + import, 0 si export + final bat = n('currentPowerStorage') ?? 0.0; + + // Totaux cumulés en kWh → convertir en Wh pour l'affichage + final totalProdKwh = n('totalProduction') ?? 0.0; + final totalRetKwh = n('totalReturn') ?? 0.0; + final totalConsoKwh = n('totalConsumption') ?? 0.0; + final totalAcqKwh = n('totalAcquisition') ?? 0.0; + + // Autoconsommation = production - injection réseau + final selfConsoKwh = totalProdKwh - totalRetKwh; + // Taux autoconsommation = autoconso / production * 100 + final selfRate = totalProdKwh > 0 ? (selfConsoKwh / totalProdKwh * 100).clamp(0.0, 100.0) : 0.0; + // Taux autonomie = (production + batterie utilisée) / conso * 100 + final autoRate = totalConsoKwh > 0 ? ((totalConsoKwh - totalAcqKwh) / totalConsoKwh * 100).clamp(0.0, 100.0) : 0.0; + + _energyData = _energyData.copyWith( + pvPower: pv, + homePower: home, + gridPower: grid, + batteryPower: bat, + dayProductionWh: totalProdKwh * 1000, + dayGridInjectionWh: totalRetKwh * 1000, + daySelfConsumptionWh: selfConsoKwh * 1000, + selfConsumptionRate: selfRate, + autonomyRate: autoRate, + dayGains: totalRetKwh * 0.13 + selfConsoKwh * 0.22, // estimation tarifaire + ); + notifyListeners(); + } + + void _parseRootMeter(Map response) { + // Dans nymea 1.14, tout est dans PowerBalance — méthode conservée pour compat + } + + Future refreshEnergy() => _loadEnergyStatus(); + + /// Rafraîchit things + classes + énergie + Future refresh() async { + await Future.wait([_loadThings(), _loadEnergyStatus()]); + } + + Future _loadEnergyHistory() async { + try { + final now = DateTime.now(); + final from = DateTime(now.year, now.month, now.day) + .millisecondsSinceEpoch ~/ 1000; + final to = now.millisecondsSinceEpoch ~/ 1000; + final r = await _sendRequest('Energy.GetEnergyLogs', { + 'sampleRate': 'SampleRateHour', + 'from': from, + 'to': to, + }); + final logs = r['params']?['logs'] as List? ?? []; + _historyPoints = logs.map((l) { + final lm = l as Map; + return HistoryPoint( + time: DateTime.fromMillisecondsSinceEpoch( + (lm['timestamp'] as int) * 1000), + pvWh: (lm['totalEnergyProduced'] as num?)?.toDouble() ?? 0, + homeWh: (lm['totalEnergyConsumed'] as num?)?.toDouble() ?? 0, + gridWh: (lm['totalEnergyReturned'] as num?)?.toDouble() ?? 0, + ); + }).toList(); + notifyListeners(); + } catch (e) { _log('GetEnergyLogs: $e'); } + } + + Future setChargingMode(ChargingMode mode) async { + final modeStr = { + ChargingMode.pv: 'pv', + ChargingMode.minPv: 'minpv', + ChargingMode.boost: 'boost', + }[mode]!; + if (_connected && !_isSimulation) { + try { + await _sendRequest('Energy.SetChargingMode', {'mode': modeStr}); + } catch (e) { _log('SetChargingMode: $e'); } + } + _energyData = _energyData.copyWith(chargingMode: mode); + notifyListeners(); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // THINGS / INTEGRATIONS + // ═══════════════════════════════════════════════════════════════════════════ + + bool _thingsLoaded = false; + bool get thingsLoaded => _thingsLoaded; + + Future _loadThings() async { + // ── Étape 1 : charger les things configurés ────────────────────────────── + try { + _log('📦 GetThings → envoi...', force: true); + final r = await _sendRequest('Integrations.GetThings', {}); + _log('📦 GetThings ← status=${r["status"]} keys=${r.keys.toList()}', force: true); + + final thingsRaw = r['params']?['things'] + ?? r['result']?['things'] + ?? r['things']; + + if (thingsRaw is List) { + _things = thingsRaw + .map((t) => NymeaThing.fromJson(t as Map)) + .where((t) => t.id.isNotEmpty) // exclure entrées invalides + .toList(); + _log('📦 ✅ ${_things.length} things configurés:', force: true); + for (final t in _things) { + _log(' 📱 "${t.name}" | classId=${t.thingClassId.substring(0, 8)}… | ${t.setupStatus}', force: true); + } + } else { + _log('📦 ⚠️ GetThings: clé "things" absente — keys=${r.keys.toList()}', force: true); + } + } catch (e, st) { + _log('📦 ❌ GetThings erreur: $e\n$st', force: true); + } + + // ── Étape 2 : charger UNIQUEMENT les classes des things présents ────────── + // On filtre sur les thingClassIds réellement utilisés → évite les 80+ plugins + if (_things.isNotEmpty) { + try { + final neededClassIds = _things + .map((t) => t.thingClassId) + .where((id) => id.isNotEmpty) + .toSet() + .toList(); + + _log('📦 GetThingClasses pour ${neededClassIds.length} classes...', force: true); + + // nymea supporte un filtre optionnel thingClassIds + final r = await _sendRequest('Integrations.GetThingClasses', { + 'thingClassIds': neededClassIds, + }); + + final classesRaw = r['params']?['thingClasses'] + ?? r['result']?['thingClasses'] + ?? r['thingClasses']; + + if (classesRaw is List && classesRaw.isNotEmpty) { + _thingClasses = classesRaw + .map((c) => NymeaThingClass.fromJson(c as Map)) + .toList(); + _log('📦 ✅ ThingClasses: ${_thingClasses.length}', force: true); + for (final c in _thingClasses) { + _log(' 📋 "${c.name}" | id=${c.id.substring(0, 8)}… | interfaces=${c.interfaces}', force: true); + } + } else { + // Fallback : nymea < 1.6 ne supporte peut-être pas le filtre → tout charger + _log('📦 Filtre non supporté, fallback GetThingClasses sans filtre...', force: true); + await _loadAllThingClassesFiltered(neededClassIds); + } + } catch (e) { + _log('📦 ❌ GetThingClasses erreur: $e', force: true); + // Fallback si le filtre cause une erreur + await _loadAllThingClassesFiltered( + _things.map((t) => t.thingClassId).toSet().toList() + ); + } + } + + _thingsLoaded = true; + notifyListeners(); + } + + /// Fallback : charge toutes les classes puis filtre côté client + Future _loadAllThingClassesFiltered(List neededIds) async { + try { + final r = await _sendRequest('Integrations.GetThingClasses', {}); + final classesRaw = r['params']?['thingClasses'] + ?? r['result']?['thingClasses'] + ?? r['thingClasses']; + + if (classesRaw is List) { + final all = classesRaw + .map((c) => NymeaThingClass.fromJson(c as Map)) + .toList(); + // Garder seulement les classes dont on a besoin + _thingClasses = all + .where((c) => neededIds.contains(c.id)) + .toList(); + _log('📦 Fallback: ${all.length} classes totales → ${_thingClasses.length} utilisées', force: true); + for (final c in _thingClasses) { + _log(' 📋 "${c.name}" interfaces=${c.interfaces}', force: true); + } + } + } catch (e) { + _log('📦 ❌ Fallback GetThingClasses erreur: $e', force: true); + } + } + Future refreshThings() => _loadThings(); + + Future>> discoverThings(String thingClassId) async { + if (_isSimulation) return []; + try { + final r = await _sendRequest( + 'Integrations.DiscoverThings', {'thingClassId': thingClassId}); + return List>.from( + r['params']?['thingDescriptors'] ?? []); + } catch (_) { return []; } + } + + Future addThing({ + required String thingClassId, + required String thingDescriptorId, + required String name, + }) async { + if (_isSimulation) return false; + try { + final r = await _sendRequest('Integrations.AddThing', { + 'thingClassId': thingClassId, + 'name': name, + 'thingDescriptorId': thingDescriptorId, + }); + if (r['status'] == 'success') { await _loadThings(); return true; } + return false; + } catch (_) { return false; } + } + + Future removeThing(String thingId) async { + if (_isSimulation) return false; + try { + final r = await _sendRequest( + 'Integrations.RemoveThing', {'thingId': thingId}); + if (r['status'] == 'success') { + _things.removeWhere((t) => t.id == thingId); + notifyListeners(); + return true; + } + return false; + } catch (_) { return false; } + } + + Future renameThing(String thingId, String newName) async { + if (_isSimulation) return false; + try { + final r = await _sendRequest( + 'Integrations.EditThing', {'thingId': thingId, 'name': newName}); + if (r['status'] == 'success') { + final idx = _things.indexWhere((t) => t.id == thingId); + if (idx >= 0) { + _things[idx] = _things[idx].copyWith(name: newName); + notifyListeners(); + } + return true; + } + return false; + } catch (_) { return false; } + } + + Future setThingSettings( + String thingId, String settingTypeId, dynamic value) async { + if (_isSimulation) return true; + try { + final r = await _sendRequest('Integrations.SetThingSettings', { + 'thingId': thingId, + 'settings': [{'paramTypeId': settingTypeId, 'value': value}], + }); + return r['params']?['thingError'] == 'ThingErrorNoError'; + } catch (e) { + _log('SetThingSettings: $e'); + return false; + } + } + + Future executeAction({ + required String thingId, + required String actionTypeId, + Map params = const {}, + }) async { + if (_isSimulation) return true; + try { + final r = await _sendRequest('Integrations.ExecuteAction', { + 'thingId': thingId, + 'actionTypeId': actionTypeId, + 'params': params.entries + .map((e) => {'paramTypeId': e.key, 'value': e.value}) + .toList(), + }); + return r['status'] == 'success'; + } catch (e) { _log('ExecuteAction: $e'); return false; } + } + + Future getStateValue(String thingId, String stateTypeId) async { + if (_isSimulation) return null; + try { + final r = await _sendRequest('Integrations.GetStateValue', { + 'thingId': thingId, 'stateTypeId': stateTypeId, + }); + return r['params']?['value']; + } catch (_) { return null; } + } + + Future>> getRules() async { + if (_isSimulation) return []; + try { + final r = await _sendRequest('Rules.GetRules', {}); + return List>.from(r['params']?['rules'] ?? []); + } catch (_) { return []; } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // FAVORITES + // ═══════════════════════════════════════════════════════════════════════════ + + void addFavorite(FavoriteWidget widget) { + if (!_favoriteWidgets.any((f) => f.id == widget.id)) { + _favoriteWidgets.add(widget); + notifyListeners(); + } + } + + void removeFavorite(String id) { + _favoriteWidgets.removeWhere((f) => f.id == id); + notifyListeners(); + } + + void reorderFavorites(int oldIndex, int newIndex) { + if (newIndex > oldIndex) newIndex--; + final item = _favoriteWidgets.removeAt(oldIndex); + _favoriteWidgets.insert(newIndex, item); + notifyListeners(); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // SIMULATION + // ═══════════════════════════════════════════════════════════════════════════ + + void startSimulation() { + disconnect(); + _connected = true; + _isSimulation = true; + _nymeaVersion = 'demo 1.8.2'; + _generateSimulatedThings(); + _generateHistory(); + if (_favoriteWidgets.isEmpty) { + _favoriteWidgets = [ + FavoriteWidget(id: 'fw_pv', type: FavoriteType.pvPower, title: 'Production PV'), + FavoriteWidget(id: 'fw_home', type: FavoriteType.homePower, title: 'Consommation'), + FavoriteWidget(id: 'fw_soc', type: FavoriteType.batterySOC, title: 'Batterie'), + FavoriteWidget(id: 'fw_rates', type: FavoriteType.rates, title: 'Taux'), + FavoriteWidget(id: 'fw_ev', type: FavoriteType.evCharger, title: 'Borne EV'), + ]; + } + _simulationTimer?.cancel(); + _simulationTimer = Timer.periodic( + const Duration(seconds: 2), (_) => _simulateData()); + _simulateData(); + notifyListeners(); + } + + void _simulateData() { + final now = DateTime.now(); + final hour = now.hour + now.minute / 60.0; + final rng = Random(); + double pv = 0; + if (hour >= 7 && hour <= 20) { + pv = (3800 * sin((hour - 7) * pi / 13) + rng.nextDouble() * 80 - 40) + .clamp(0, 4500); + } + final home = 200 + rng.nextDouble() * 600; + final battery = pv > home + 200 ? (pv - home - 200).clamp(0.0, 2000.0) : 0.0; + final batDischarge = home > pv + 100 && _energyData.batterySOC > 10 + ? min(home - pv, 1500.0) : 0.0; + final grid = home - pv - battery + batDischarge; + final soc = (_energyData.batterySOC + + (battery > 0 ? 0.1 : batDischarge > 0 ? -0.05 : 0)).clamp(5.0, 100.0); + _energyData = _energyData.copyWith( + pvPower: pv, homePower: home, + batteryPower: battery > 0 ? battery : (batDischarge > 0 ? -batDischarge : 0), + gridPower: grid, batterySOC: soc, temperature: 7, + dayProductionWh: pv * 0.5, daySelfConsumptionWh: home * 0.4, + dayGridInjectionWh: grid < 0 ? -grid * 0.3 : 0, + selfConsumptionRate: pv > 0 ? (min(pv, home) / pv * 100).clamp(0.0, 100.0) : 0.0, + autonomyRate: home > 0 ? ((pv + batDischarge) / home * 100).clamp(0.0, 100.0) : 0.0, + dayGains: (pv * 0.5 / 1000) * 0.13, + ); + notifyListeners(); + } + + void _generateHistory() { + final now = DateTime.now(); + _historyPoints = List.generate(24, (i) { + final h = i.toDouble(); + final pv = (h >= 7 && h <= 20) + ? (3000 * sin((h - 7) * pi / 13)).clamp(0.0, 4500.0) : 0.0; + return HistoryPoint( + time: DateTime(now.year, now.month, now.day, i), + pvWh: pv, homeWh: 200 + Random().nextDouble() * 400, + gridWh: (200 - pv * 0.3).clamp(0, 500), + ); + }); + } + + void _generateSimulatedThings() { + _things = [ + NymeaThing(id: 'sim-inverter', name: 'Onduleur SolarEdge', thingClassId: 'solaredge', + setupStatus: 'ThingSetupStatusComplete', paramValues: [], + states: [NymeaStateValue(stateTypeId: 'power', value: 4200.0)]), + NymeaThing(id: 'sim-battery', name: 'Batterie BYD', thingClassId: 'byd', + setupStatus: 'ThingSetupStatusComplete', paramValues: [], + states: [NymeaStateValue(stateTypeId: 'soc', value: 42.0)]), + NymeaThing(id: 'sim-ev', name: 'Borne Wallbox', thingClassId: 'wallbox', + setupStatus: 'ThingSetupStatusComplete', paramValues: [], + states: [NymeaStateValue(stateTypeId: 'power', value: 3600.0)]), + NymeaThing(id: 'sim-meter', name: 'Compteur Linky', thingClassId: 'linky', + setupStatus: 'ThingSetupStatusComplete', paramValues: [], + states: [NymeaStateValue(stateTypeId: 'power', value: 180.0)]), + NymeaThing(id: 'sim-ac', name: 'Climatiseur Salon', thingClassId: 'mitsubishi', + setupStatus: 'ThingSetupStatusComplete', paramValues: [], + states: [NymeaStateValue(stateTypeId: 'power', value: false)]), + ]; + } + + @override + void dispose() { + _heartbeatTimer?.cancel(); + _simulationTimer?.cancel(); + _socket?.destroy(); + _wsSub?.cancel(); + _wsChannel?.sink.close(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart new file mode 100644 index 0000000..2ee3ac4 --- /dev/null +++ b/lib/theme/app_theme.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + // Colors + static const Color primaryGreen = Color(0xFF4CAF50); + static const Color solarYellow = Color(0xFFF5C842); + static const Color gridGray = Color(0xFF5A6473); + static const Color homeBlue = Color(0xFF5BA4CF); + static const Color batteryGreen = Color(0xFF4CAF50); + static const Color accentOrange = Color(0xFFFF7043); + static const Color backgroundGray = Color(0xFFF0F2F5); + static const Color cardWhite = Color(0xFFFFFFFF); + static const Color textDark = Color(0xFF1A1A2E); + static const Color textLight = Color(0xFF6B7280); + static const Color boostRed = Color(0xFFEF5350); + static const Color pvGreen = Color(0xFF43A047); + static const Color minPvBlue = Color(0xFF42A5F5); + + static ThemeData get theme => ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: primaryGreen, + surface: backgroundGray, + ), + scaffoldBackgroundColor: backgroundGray, + cardTheme: CardThemeData( + color: cardWhite, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + margin: EdgeInsets.zero, + ), + fontFamily: 'Roboto', + useMaterial3: true, + ); +} diff --git a/lib/widgets/energy_flow_widget.dart b/lib/widgets/energy_flow_widget.dart new file mode 100644 index 0000000..7b70dbd --- /dev/null +++ b/lib/widgets/energy_flow_widget.dart @@ -0,0 +1,272 @@ +import 'package:flutter/material.dart'; +import '../models/energy_data.dart'; +import '../theme/app_theme.dart'; + +class EnergyFlowWidget extends StatelessWidget { + final EnergyData data; + + const EnergyFlowWidget({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Données en temps réel', + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + ), + ), + Row( + children: [ + Text( + '${data.temperature.toStringAsFixed(0)}°C', + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: AppTheme.textDark, + ), + ), + const SizedBox(width: 4), + const Icon(Icons.cloud, color: Colors.blueGrey, size: 22), + ], + ), + ], + ), + const SizedBox(height: 20), + + // Energy nodes + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _EnergyNode( + icon: Icons.wb_sunny_rounded, + color: AppTheme.solarYellow, + power: data.pvPower, + label: 'Solaire', + ), + _EnergyNode( + icon: Icons.home_rounded, + color: AppTheme.homeBlue, + power: data.homePower, + label: 'Maison', + ), + _EnergyNode( + icon: Icons.battery_charging_full_rounded, + color: AppTheme.batteryGreen, + power: data.batteryPower.abs(), + label: 'Batterie', + badge: '${data.batterySOC.toStringAsFixed(0)}%', + ), + _EnergyNode( + icon: Icons.electrical_services_rounded, + color: AppTheme.gridGray, + power: data.gridPower.abs(), + label: data.gridPower >= 0 ? 'Réseau ↓' : 'Réseau ↑', + ), + ], + ), + const SizedBox(height: 16), + + // Flow diagram + SizedBox( + height: 80, + child: CustomPaint( + painter: _FlowPainter(data: data), + size: Size.infinite, + ), + ), + ], + ), + ), + ); + } +} + +class _EnergyNode extends StatelessWidget { + final IconData icon; + final Color color; + final double power; + final String label; + final String? badge; + + const _EnergyNode({ + required this.icon, + required this.color, + required this.power, + required this.label, + this.badge, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + child: Icon(icon, color: Colors.white, size: 30), + ), + if (badge != null) + Positioned( + bottom: -4, + left: 0, + right: 0, + child: Center( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color, width: 1.5), + ), + child: Text( + badge!, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: color, + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 10), + Text( + _formatPower(power), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + ), + ), + Text( + _powerUnit(power), + style: const TextStyle( + fontSize: 12, + color: AppTheme.textLight, + ), + ), + Text( + label, + style: const TextStyle( + fontSize: 11, + color: AppTheme.textLight, + ), + ), + ], + ); + } + + String _formatPower(double w) { + if (w >= 1000) return (w / 1000).toStringAsFixed(1); + return w.toStringAsFixed(0); + } + + String _powerUnit(double w) => w >= 1000 ? 'kW' : 'W'; +} + +class _FlowPainter extends CustomPainter { + final EnergyData data; + + _FlowPainter({required this.data}); + + @override + void paint(Canvas canvas, Size size) { + // Draw animated flow lines between nodes + final centerY = size.height * 0.4; + final nodePositions = [ + size.width * 0.125, // PV + size.width * 0.375, // Home + size.width * 0.625, // Battery + size.width * 0.875, // Grid + ]; + + final linePaint = Paint() + ..strokeWidth = 2 + ..style = PaintingStyle.stroke; + + // PV -> Home (always if PV > 0) + if (data.pvPower > 0) { + _drawArrowLine(canvas, Offset(nodePositions[0], centerY), + Offset(nodePositions[1], centerY), AppTheme.solarYellow, linePaint); + } + + // Home -> Battery (if battery charging) + if (data.batteryPower > 0) { + _drawArrowLine(canvas, Offset(nodePositions[1], centerY), + Offset(nodePositions[2], centerY), AppTheme.batteryGreen, linePaint); + } + + // Grid -> Home (if importing) + if (data.gridPower > 0) { + _drawArrowLine(canvas, Offset(nodePositions[3], centerY), + Offset(nodePositions[1], centerY), Colors.orange, linePaint); + } else if (data.gridPower < 0) { + // Exporting + _drawArrowLine(canvas, Offset(nodePositions[2], centerY), + Offset(nodePositions[3], centerY), AppTheme.solarYellow, linePaint); + } + + // Draw node dots + final dotPaint = Paint()..style = PaintingStyle.fill; + for (int i = 0; i < nodePositions.length; i++) { + final colors = [ + AppTheme.solarYellow, + AppTheme.homeBlue, + AppTheme.batteryGreen, + AppTheme.gridGray, + ]; + dotPaint.color = colors[i]; + canvas.drawCircle(Offset(nodePositions[i], centerY), 6, dotPaint); + } + } + + void _drawArrowLine(Canvas canvas, Offset start, Offset end, Color color, + Paint paint) { + paint.color = color.withValues(alpha:0.7); + + final path = Path() + ..moveTo(start.dx, start.dy) + ..lineTo(end.dx, end.dy); + canvas.drawPath(path, paint); + + // Arrow head + final arrowPaint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + final dx = end.dx - start.dx; + final arrowSign = dx > 0 ? 1 : -1; + final arrowTip = Offset(end.dx - arrowSign * 8, end.dy); + + final arrowPath = Path() + ..moveTo(arrowTip.dx + arrowSign * 8, arrowTip.dy) + ..lineTo(arrowTip.dx, arrowTip.dy - 5) + ..lineTo(arrowTip.dx, arrowTip.dy + 5) + ..close(); + canvas.drawPath(arrowPath, arrowPaint); + } + + @override + bool shouldRepaint(_FlowPainter oldDelegate) => true; +} diff --git a/lib/widgets/ev_charging_card.dart b/lib/widgets/ev_charging_card.dart new file mode 100644 index 0000000..36e7426 --- /dev/null +++ b/lib/widgets/ev_charging_card.dart @@ -0,0 +1,425 @@ +import 'package:flutter/material.dart'; +import '../models/energy_data.dart'; +import '../services/nymea_service.dart'; +import '../theme/app_theme.dart'; + +class EVChargingCard extends StatelessWidget { + final EnergyData data; + final NymeaService service; + + const EVChargingCard({ + super.key, + required this.data, + required this.service, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Recharge du véhicule', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + ), + ), + Text( + _statusLabel(data.chargingMode), + style: const TextStyle( + fontSize: 13, + color: AppTheme.textLight, + ), + ), + ], + ), + IconButton( + icon: const Icon(Icons.settings_outlined), + onPressed: () => _showSettings(context), + color: AppTheme.textLight, + ), + ], + ), + const SizedBox(height: 16), + + // Mode buttons + Row( + children: [ + _ModeButton( + label: 'PV', + icon: Icons.wb_sunny_rounded, + color: AppTheme.pvGreen, + isSelected: data.chargingMode == ChargingMode.pv, + onTap: () => service.setChargingMode(ChargingMode.pv), + ), + const SizedBox(width: 8), + _ModeButton( + label: 'Min + PV', + icon: Icons.bolt, + color: AppTheme.minPvBlue, + isSelected: data.chargingMode == ChargingMode.minPv, + onTap: () => service.setChargingMode(ChargingMode.minPv), + ), + const SizedBox(width: 8), + _ModeButton( + label: 'Boost', + icon: Icons.rocket_launch_rounded, + color: AppTheme.boostRed, + isSelected: data.chargingMode == ChargingMode.boost, + onTap: () => service.setChargingMode(ChargingMode.boost), + ), + ], + ), + const SizedBox(height: 16), + + // Stats + _InfoRow( + label: 'Puissance borne', + value: '${data.chargingPower.toStringAsFixed(1)} kW', + ), + const SizedBox(height: 6), + _InfoRow( + label: 'Source', + value: '${data.solarSourcePercent.toStringAsFixed(0)}% solaire', + valueColor: AppTheme.pvGreen, + ), + const SizedBox(height: 6), + _InfoRow( + label: 'Limite réseau', + value: data.gridLimitOk ? 'OK ✓' : 'Dépassée !', + valueColor: data.gridLimitOk ? AppTheme.primaryGreen : AppTheme.boostRed, + ), + + // Charging indicator + const SizedBox(height: 12), + if (data.chargingMode != ChargingMode.pv || data.pvPower > 0) + _ChargingIndicator( + mode: data.chargingMode, + pvPower: data.pvPower, + chargingPower: data.chargingPower, + ), + ], + ), + ), + ); + } + + String _statusLabel(ChargingMode mode) { + switch (mode) { + case ChargingMode.pv: + return 'Surplus PV disponible'; + case ChargingMode.minPv: + return 'Minimum + surplus PV'; + case ChargingMode.boost: + return 'Charge rapide (réseau)'; + } + } + + void _showSettings(BuildContext context) { + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (_) => const _ChargingSettingsSheet(), + ); + } +} + +class _ModeButton extends StatelessWidget { + final String label; + final IconData icon; + final Color color; + final bool isSelected; + final VoidCallback onTap; + + const _ModeButton({ + required this.label, + required this.icon, + required this.color, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: isSelected ? color : color.withValues(alpha:0.1), + borderRadius: BorderRadius.circular(30), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, + color: isSelected ? Colors.white : color, size: 16), + const SizedBox(width: 4), + Text( + label, + style: TextStyle( + color: isSelected ? Colors.white : color, + fontWeight: FontWeight.bold, + fontSize: 13, + ), + ), + ], + ), + ), + ), + ); + } +} + +class _InfoRow extends StatelessWidget { + final String label; + final String value; + final Color? valueColor; + + const _InfoRow({required this.label, required this.value, this.valueColor}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text(label, + style: const TextStyle(fontSize: 13, color: AppTheme.textLight)), + const Text(' : ', style: TextStyle(color: AppTheme.textLight)), + Text( + value, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: valueColor ?? AppTheme.textDark, + ), + ), + ], + ); + } +} + +class _ChargingIndicator extends StatelessWidget { + final ChargingMode mode; + final double pvPower; + final double chargingPower; + + const _ChargingIndicator({ + required this.mode, + required this.pvPower, + required this.chargingPower, + }); + + @override + Widget build(BuildContext context) { + final pvPercent = (pvPower / (chargingPower * 1000)).clamp(0.0, 1.0); + final gridPercent = mode == ChargingMode.boost ? 1.0 : (1 - pvPercent).clamp(0.0, 1.0); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Répartition charge', + style: TextStyle(fontSize: 12, color: AppTheme.textLight)), + const SizedBox(height: 6), + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: SizedBox( + height: 10, + child: Row( + children: [ + Flexible( + flex: (pvPercent * 100).toInt(), + child: Container(color: AppTheme.solarYellow), + ), + Flexible( + flex: (gridPercent * 100).toInt(), + child: Container(color: Colors.orange.shade300), + ), + ], + ), + ), + ), + const SizedBox(height: 4), + Row( + children: [ + _legend(AppTheme.solarYellow, 'Solaire'), + const SizedBox(width: 12), + _legend(Colors.orange.shade300, 'Réseau'), + ], + ), + ], + ); + } + + Widget _legend(Color color, String label) => Row( + children: [ + Container(width: 10, height: 10, color: color), + const SizedBox(width: 4), + Text(label, style: const TextStyle(fontSize: 11, color: AppTheme.textLight)), + ], + ); +} + +class _ChargingSettingsSheet extends StatefulWidget { + const _ChargingSettingsSheet(); + + @override + State<_ChargingSettingsSheet> createState() => _ChargingSettingsSheetState(); +} + +class _ChargingSettingsSheetState extends State<_ChargingSettingsSheet> { + double _minPower = 6; + double _maxPower = 16; + bool _scheduleEnabled = false; + TimeOfDay _startTime = const TimeOfDay(hour: 22, minute: 0); + TimeOfDay _endTime = const TimeOfDay(hour: 6, minute: 0); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Paramètres de charge', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 20), + const Text('Courant minimum (A)', + style: TextStyle(fontWeight: FontWeight.w500)), + Slider( + value: _minPower, + min: 6, + max: 16, + divisions: 10, + label: '${_minPower.toStringAsFixed(0)} A', + onChanged: (v) => setState(() => _minPower = v), + activeColor: AppTheme.primaryGreen, + ), + const Text('Courant maximum (A)', + style: TextStyle(fontWeight: FontWeight.w500)), + Slider( + value: _maxPower, + min: 6, + max: 32, + divisions: 26, + label: '${_maxPower.toStringAsFixed(0)} A', + onChanged: (v) => setState(() => _maxPower = v), + activeColor: AppTheme.primaryGreen, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Programmation horaire', + style: TextStyle(fontWeight: FontWeight.w500)), + Switch( + value: _scheduleEnabled, + onChanged: (v) => setState(() => _scheduleEnabled = v), + activeThumbColor: AppTheme.primaryGreen, + ), + ], + ), + if (_scheduleEnabled) ...[ + Row( + children: [ + Expanded( + child: _TimePicker( + label: 'Début', + time: _startTime, + onChanged: (t) => setState(() => _startTime = t), + ), + ), + const SizedBox(width: 16), + Expanded( + child: _TimePicker( + label: 'Fin', + time: _endTime, + onChanged: (t) => setState(() => _endTime = t), + ), + ), + ], + ), + ], + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryGreen, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + ), + child: const Text('Enregistrer'), + ), + ), + ], + ), + ); + } +} + +class _TimePicker extends StatelessWidget { + final String label; + final TimeOfDay time; + final ValueChanged onChanged; + + const _TimePicker({ + required this.label, + required this.time, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () async { + final picked = await showTimePicker(context: context, initialTime: time); + if (picked != null) onChanged(picked); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + const Icon(Icons.access_time, size: 16, color: AppTheme.textLight), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, + style: const TextStyle( + fontSize: 11, color: AppTheme.textLight)), + Text( + '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}', + style: const TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.textDark), + ), + ], + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/gains_card.dart b/lib/widgets/gains_card.dart new file mode 100644 index 0000000..a35b38a --- /dev/null +++ b/lib/widgets/gains_card.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import '../models/energy_data.dart'; +import '../theme/app_theme.dart'; + +class GainsCard extends StatelessWidget { + final EnergyData data; + + const GainsCard({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return Card( + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () {}, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.amber.shade50, + shape: BoxShape.circle, + ), + child: const Icon(Icons.euro_rounded, + color: Colors.amber, size: 22), + ), + const SizedBox(width: 12), + const Text( + 'Gains', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppTheme.textDark, + ), + ), + ], + ), + Row( + children: [ + Text( + '${data.dayGains.toStringAsFixed(2)} €', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + ), + ), + const Icon(Icons.chevron_right, color: AppTheme.textLight), + ], + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/production_card.dart b/lib/widgets/production_card.dart new file mode 100644 index 0000000..af5f1bc --- /dev/null +++ b/lib/widgets/production_card.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:percent_indicator/percent_indicator.dart'; +import '../models/energy_data.dart'; +import '../theme/app_theme.dart'; + +class ProductionCard extends StatelessWidget { + final EnergyData data; + + const ProductionCard({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Production du jour', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + ), + ), + Text( + '${_formatEnergy(data.dayProductionWh)} Wh', + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: AppTheme.textDark, + ), + ), + ], + ), + CircularPercentIndicator( + radius: 35, + lineWidth: 6, + percent: (data.selfConsumptionRate / 100).clamp(0, 1), + center: Text( + '${data.selfConsumptionRate.toStringAsFixed(0)}%', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 13, + ), + ), + progressColor: AppTheme.primaryGreen, + backgroundColor: Colors.grey.shade200, + ), + ], + ), + const Divider(height: 24), + + // Self-consumption + _StatRow( + icon: Icons.home_rounded, + iconColor: AppTheme.solarYellow, + label: 'Usage personnel', + value: '${_formatEnergy(data.daySelfConsumptionWh)} Wh', + subLabel: 'Autoconsommation', + percent: data.selfConsumptionRate, + color: AppTheme.solarYellow, + ), + const SizedBox(height: 12), + + // Grid injection + _StatRow( + icon: Icons.electrical_services_rounded, + iconColor: AppTheme.gridGray, + label: 'Vers réseau', + value: '${_formatEnergy(data.dayGridInjectionWh)} Wh', + subLabel: 'Autonomie', + percent: data.autonomyRate, + color: AppTheme.homeBlue, + ), + ], + ), + ), + ); + } + + String _formatEnergy(double wh) { + if (wh >= 1000) return '${(wh / 1000).toStringAsFixed(2)} k'; + return wh.toStringAsFixed(0); + } +} + +class _StatRow extends StatelessWidget { + final IconData icon; + final Color iconColor; + final String label; + final String value; + final String subLabel; + final double percent; + final Color color; + + const _StatRow({ + required this.icon, + required this.iconColor, + required this.label, + required this.value, + required this.subLabel, + required this.percent, + required this.color, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: iconColor.withValues(alpha:0.15), + shape: BoxShape.circle, + ), + child: Icon(icon, color: iconColor, size: 20), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, + style: const TextStyle( + fontSize: 14, color: AppTheme.textLight)), + Text(value, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: AppTheme.textDark)), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Text( + '$subLabel : ${percent.toStringAsFixed(0)}%', + style: TextStyle(fontSize: 11, color: color), + ), + const SizedBox(width: 8), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: (percent / 100).clamp(0, 1), + backgroundColor: Colors.grey.shade200, + valueColor: AlwaysStoppedAnimation(color), + minHeight: 5, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ], + ); + } +} \ No newline at end of file diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..c416dcc --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "etm_powersync_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.etm_powersync_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..60c370d --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "etm_powersync_app"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "etm_powersync_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..724bb2a --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e455769 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* etm_powersync_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "etm_powersync_app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* etm_powersync_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* etm_powersync_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/etm_powersync_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/etm_powersync_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/etm_powersync_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/etm_powersync_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/etm_powersync_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/etm_powersync_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..ecf8b87 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 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 literal 0 HcmV?d00001 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..c158e80 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = etm_powersync_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.etmPowersyncApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..3d1be9a --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,418 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237" + url: "https://pub.dev" + source: hosted + version: "0.70.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + url: "https://pub.dev" + source: hosted + version: "0.12.18" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + percent_indicator: + dependency: "direct main" + description: + name: percent_indicator + sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f + url: "https://pub.dev" + source: hosted + version: "2.4.20" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + url: "https://pub.dev" + source: hosted + version: "0.7.9" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: "direct main" + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..06a32ed --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,94 @@ +name: etm_powersync_app +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.11.0 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + provider: ^6.1.2 + web_socket_channel: ^3.0.1 + fl_chart: ^0.70.2 + percent_indicator: ^4.2.3 + shared_preferences: ^2.3.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..3b87a07 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:etm_powersync_app/main.dart'; +import 'package:etm_powersync_app/services/nymea_service.dart'; + +void main() { + testWidgets('App smoke test', (WidgetTester tester) async { + await tester.pumpWidget( + ChangeNotifierProvider( + create: (_) => NymeaService(), + child: const NymeaEnergyApp(), + ), + ); + expect(find.byType(NymeaEnergyApp), findsOneWidget); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..28d3702 --- /dev/null +++ b/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + etm_powersync_app + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..3ed15cf --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "etm_powersync_app", + "short_name": "etm_powersync_app", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..b36e2a6 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(etm_powersync_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "etm_powersync_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..2eedaaf --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "etm_powersync_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "etm_powersync_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "etm_powersync_app.exe" "\0" + VALUE "ProductName", "etm_powersync_app" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..572e5bd --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"etm_powersync_app", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_