{"id":20260202,"url":"https://github.com/influxdata/iot-center-flutter","last_synced_at":"2025-04-11T01:32:55.851Z","repository":{"id":38402089,"uuid":"337857978","full_name":"influxdata/iot-center-flutter","owner":"influxdata","description":"InlfuxDB 2.0 dart client flutter demo","archived":false,"fork":false,"pushed_at":"2022-07-12T08:16:18.000Z","size":6707,"stargazers_count":35,"open_issues_count":0,"forks_count":6,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-24T22:42:07.835Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/influxdata.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-02-10T21:24:53.000Z","updated_at":"2025-03-04T01:40:08.000Z","dependencies_parsed_at":"2022-08-18T13:21:33.264Z","dependency_job_id":null,"html_url":"https://github.com/influxdata/iot-center-flutter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/influxdata%2Fiot-center-flutter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/influxdata%2Fiot-center-flutter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/influxdata%2Fiot-center-flutter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/influxdata%2Fiot-center-flutter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/influxdata","download_url":"https://codeload.github.com/influxdata/iot-center-flutter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248325348,"owners_count":21084908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T11:18:22.996Z","updated_at":"2025-04-11T01:32:55.831Z","avatar_url":"https://github.com/influxdata.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv style=\"max-width: 1200px; min-width: 600px; font-size: 18px; margin: auto; padding: 50px;\"\u003e\n\n\n\u003ch1\u003eIoT Center \u003ca style=\"color: #d30971 !important;\"\u003e Demo v2.\u003c/a\u003e Build on \u003ca style=\"color: #d30971 !important;\"\u003e InfluxDB.\u003c/a\u003e\u003c/h1\u003e\n\nThis demo was designed to display data from Devices connected to IoT Center. Application is using InfluxDB v2 to store \nthe data, Telegraf and MQTT.\n\n\u003cimg align=\"right\" src=\"assets/images/device-dashboard.png\" alt=\"drawing\" width=\"35%\" style=\"margin-left: 30px;margin-top: 30px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n## Features\n\n### Devices\n- device registration in InfluxDB\n- two types of device - virtual and mobile\n- remove device from InfluxDB\n- remove associated device data\n- displaying device info and measurements\n- data visualizations in gauge or simple chart\n- can use different dashboards for one device\n- write testing data to InfluxDB for virtual device\n- write sensor data to InfluxDB for mobile device\n\n### Dashboards\n- dashboard registration in InfluxDB\n- two types of dashboards - virtual or mobile\n- pair dashboard with device\n- editable charts parameters\n- customizable - adding and deleting charts\n\n### Sensors\n- event based sensor listening\n- requesting permission from user\n- normalized object for simple write \n- detect available sensors\n\n## Getting Started\n\n### Prerequisites\n- Flutter - [Install Flutter](https://docs.flutter.dev/get-started/install), \n[online documentation](https://flutter.dev/docs)\n- Docker - [Get started with docker](https://www.docker.com/get-started)\n- **InfluxDB** with mosquitto and telegraf \n  - you need following ports: \n      - **1883** (mqtt broker)\n      - **8086** (influxdb 2.0 OSS)\n  - for start use docker-compose.yaml:\n    ```bash\n    docker-compose up\n    ```\n\n## Run Application\n\n\u003cimg align=\"right\" src=\"assets/images/home-page.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n### Home page\n\nHome page contains device `ListView`.\n\n#### AppBar\nApp bar on this screen contains basic functions:\n\n- ![Lock](assets/images/icons/add_white_24dp.svg#gh-dark-mode-only)\n  ![Lock](assets/images/icons/add_dark_24dp.svg#gh-light-mode-only)\n  add new device\n- ![Auto-renew](assets/images/icons/autorenew_white_24dp.svg#gh-dark-mode-only)\n  ![Auto-renew](assets/images/icons/autorenew_dark_24dp.svg#gh-light-mode-only) \nrefresh devices\n- ![Settings](assets/images/icons/settings_white_24dp.svg#gh-dark-mode-only)\n ![Lock](assets/images/icons/settings_dark_24dp.svg#gh-light-mode-only) settings page \n\n#### Device ListView\nEach device tile contains DeviceId and following actions:\n\n- ![Settings](assets/images/icons/delete_white_24dp.svg#gh-dark-mode-only)![Lock](assets/images/icons/delete_dark_24dp.svg#gh-light-mode-only) for deleting device\n- ![Lock](assets/images/icons/arrow_forward.svg#gh-dark-mode-only)\n    ![Lock](assets/images/icons/arrow_forward_dark.svg#gh-light-mode-only)\n    go to device page\n\n\n\u003cimg align=\"right\" src=\"assets/images/new-device.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n#### Add Device\n\nTo `TextBox` enter device id, in `DropDownList` select type of device and click to Save for create. Device is automatically \nregistered in InfluxDB - it's write as point via `WriteService` with its authorization.\n\nExample of creating device point in InfluxDB - [createDevice](/lib/src/app/model/influx_model.dart#L524):\n```dart\nvar writeApi = _influxDBClient.getWriteService();\nvar point = Point('deviceauth')\n          .addTag('deviceId', deviceId)\n          .addField('key', authorization.id)\n          .addField('token', authorization.token);\nwriteApi.write(point);\n```\n\nCreating device IoT authorization via `AuthorizationsApi` - [_createIoTAuthorization](/lib/src/app/model/influx_model.dart#L592):\n```dart\nvar authorizationApi = _influxDBClient.getAuthorizationsApi();\nvar permissions = [\n  Permission(\n          action: PermissionActionEnum.read,\n          resource: Resource(\n                  type: ResourceTypeEnum.buckets,\n                  id: bucketId,\n                  orgID: orgId,\n                  org: org)),\n  Permission(\n          action: PermissionActionEnum.write,\n          resource: Resource(\n                  type: ResourceTypeEnum.buckets,\n                  id: bucketId,\n                  orgID: orgId,\n                  org: org)),\n];\nAuthorizationPostRequest request = AuthorizationPostRequest(\n        orgID: orgId,\n        description: 'IoTCenterDevice: ' + deviceId,\n        permissions: permissions);\n\nauthorizationApi.postAuthorizations(request);\n```\n\n\n#### Refresh Devices\n\nReload active devices (`_value` field cannot be empty) from InfluxDB using `QueryService` with following query - \n[fetchDevices](/lib/src/app/model/influx_model.dart#L94):\n```sql\nfrom(bucket: \"${_influxDBClient.bucket}\")\n    |\u003e range(start: -30d)\n    |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"deviceauth\"\n                     and r[\"_field\"] == \"key\")\n    |\u003e last()\n    |\u003e filter(fn: (r) =\u003e r[\"_value\"] != \"\")\n```\n\n\u003cimg align=\"right\" src=\"assets/images/delete-device.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n#### Delete Device\n\nOn each tile of device is ![Settings](assets/images/icons/delete_white_24dp.svg#gh-dark-mode-only)![Lock](assets/images/icons/delete_dark_24dp.svg#gh-light-mode-only) \nfor deleting device. After clicking on it, there is confirmation dialog with `CheckBox` for choose deleting \ndevice with associated data - if it's checked, data are deleted too.\n\nExample of deleting data via `DeleteService` - [deleteDevice](/lib/src/app/model/influx_model.dart#L643):\n```dart\nvar deleteApi = _influxDBClient.getDeleteService();\nif (deleteWithData) {\n    await deleteApi.delete(\n          predicate: 'clientId=\"$deviceId\"',\n          start: DateTime(1970).toUtc(),\n          stop: DateTime.now().toUtc(),\n          bucket: _influxDBClient.bucket,\n          org: _influxDBClient.org);\n}\n```\n\nAfter deleting device **isn't** remove from InfluxDB - in this case **deleting** is meaning **removing of device\nauthorization** and **IoT Authorization**.\n\nRemoved device has in InfluxDB empty fields `key` and `token`, it means, that device authorization was removed via \n`WriteService`- [_removeDeviceAuthorization](/lib/src/app/model/influx_model.dart#L673):\n```dart\nvar writeApi = _influxDBClient.getWriteService();\nvar point = Point('deviceauth')\n          .addTag('deviceId', deviceId)\n          .addField('key', '')\n          .addField('token', '');\nwriteApi.write(point);\n```\n\u003cimg align=\"right\" src=\"assets/images/settings-dashboards.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\nIoT Authorization is also removed, in this case `AuthorizationsApi` is used - [_deleteIoTAuthorization](/lib/src/app/model/influx_model.dart#L714):\n```dart\n var authorizationApi = _influxDBClient.getAuthorizationsApi();\n authorizationApi.deleteAuthorizationsID(key);\n```\n### Settings Page\n\n#### Dashboards\n\nDashboard tab contains `ListView` of all Dashboards stored in InfluxDB, with associated devices. \nIt uses `QueryService` to load data - [fetchDashboards](/lib/src/app/model/influx_model.dart#L202):\n\n```sql\nfrom(bucket: \"${_influxDBClient.bucket}\")\n    |\u003e range(start: -30d)\n    |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"$measurementDashboardFlutter\")\n    |\u003e last()\n```\n\nFor loading associated devices is use `QueryService` for each dashboard (identify by `dashboardKey`) - \n[fetchDashboardDevices](/lib/src/app/model/influx_model.dart#L314):\n\n\u003cimg align=\"right\" src=\"assets/images/settings-dashboards-add.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n```sql\nfrom(bucket: \"${_influxDBClient.bucket}\") \n    |\u003e range(start: 0) \n    |\u003e filter(fn: (r) =\u003e r._measurement == \"deviceauth\")\n    |\u003e filter(fn: (r) =\u003e r._field == \"dashboardKey\")\n    |\u003e last()\n    |\u003e filter(fn: (r) =\u003e r._value == \"$dashboardKey\")\n```\n\nApp bar on this tab has this functions:\n\n- ![Lock](assets/images/icons/add_white_24dp.svg#gh-dark-mode-only)\n  ![Lock](assets/images/icons/add_dark_24dp.svg#gh-light-mode-only)\n  add new dashboard\n- ![Auto-renew](assets/images/icons/autorenew_white_24dp.svg#gh-dark-mode-only)\n  ![Auto-renew](assets/images/icons/autorenew_dark_24dp.svg#gh-light-mode-only)\n  refresh dashboards\n\nAfter clicking **add new device** button dialog is shown - to `TextBox` enter dashboard id, in `DropDownList` select \ntype of dashboard and click to Save for create. Dashboard is automatically registered in InfluxDB - it's write as \npoint via `WriteService`.\n\nExample of creating dashboard point in InfluxDB - [createDashboard](/lib/src/app/model/influx_model.dart#L346):\n```dart\nvar writeApi = _influxDBClient.getWriteService();\nvar point = Point(measurementDashboardFlutter)\n        .addTag(\"key\", key)\n        .addTag(\"deviceType\", deviceType)\n        .addField(\"data\", dashboardData);\nwriteApi.write(point);\n```\nOn each dashboard tile is ![Settings](assets/images/icons/delete_white_24dp.svg#gh-dark-mode-only)![Lock](assets/images/icons/delete_dark_24dp.svg#gh-light-mode-only)\nfor deleting - after clicking on it and after confirmation by `Delete` in dialog, dashboard is deleted from InfluxDB \nwith all associations to devices via `DeleteService` - [deleteDashboard](/lib/src/app/model/influx_model.dart#L411):\n```dart\nvar deleteApi = _influxDBClient.getDeleteService();\ndeleteApi.delete(\n  predicate: 'dashboardKey=\"$dashboardKey\"',\n  start: DateTime(1970).toUtc(),\n  stop: DateTime.now().toUtc(),\n  bucket: _influxDBClient.bucket,\n  org: _influxDBClient.org);\n```\n\n\u003cimg align=\"right\" src=\"assets/images/settings-sensors.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n#### Sensors\n\nEach sensor is enabled individualy by pressing it's corresponding switch. Sensor sends all data immediately after switch is enabled. \nRunning sensor also show current value underneath its name.\n\nUser is asked to allow permission when enables switch if needed. Sensors written in gray are not availeble.\n\nAll sensors sends data via events so we have informations when sensor values change.\n\nSensors are iplemented using dart/flutter libraries:\n  - sensors_plus\n  - battery_plus\n  - environment_sensors\n  - geolocator\n\nMost of these libraries uses event's to comunicate state changes, some has only getter. Libraries with getters uses *Stream.periodic* to \"convert\" getter into events.\n\nAll sensors sends events with normalized data:\n```dart\ntypedef SensorMeasurement = Map\u003cString, double\u003e;\n```\nFrom this map we can simply create object that we can sent into influxdb:\n```dart\nfinal measurements = metrics.map((key, value) {\n  final field = sensor.name + (key != \"\" ? \"_$key\" : \"\");\n  return MapEntry(field, value);\n});\n```\n*some sensors sends only one value so it's map has only \"\" key*\n\nNow we have everything we need for inflxudb write.\n```dart\nvar writeApi = _influxDBClient.getWriteService();\nfinal point = Point(\"environment\");\npoint.addTag(\"clientId\", clientID)\n  .addTag(\"device\", device)\n  .time(DateTime.now().toUtc());\nfor (var m in measurements.entries) {\n  point.addField(m.key, m.value);\n}\nwriteApi.write(point);\n```\n\n\n\u003cimg align=\"right\" src=\"assets/images/settings-influx.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n#### Influx settings\n\nThis tab stored credentials for InfluxDB connection. Those credentials are saved locally in `SharedPreferences`.\n\nApp bar on this tab has this functions:\n\n- ![Lock](assets/images/icons/lock_white_24dp.svg#gh-dark-mode-only)\n  ![Lock](assets/images/icons/lock_dark_24dp.svg#gh-light-mode-only)/\n  ![Lock open](assets/images/icons/lock_open_white_24dp.svg#gh-dark-mode-only)\n  ![Lock open](assets/images/icons/lock_open_dark_24dp.svg#gh-light-mode-only)\n  enable/disable editing \n\nLoading is in method [loadInfluxClient](/lib/src/app/model/influx_client.dart#L27):\n```dart\nvar prefs = await SharedPreferences.getInstance();\nif (prefs.containsKey(\"influxClient\")) {\n  _fromJson(json.decode(prefs.getString(\"influxClient\")!));\n}\n```\n\nSave credentials is allowed only in editable mode. Method for save is [saveInfluxClient](/lib/src/app/model/influx_client.dart#L40):\n```dart\nSharedPreferences prefs = await SharedPreferences.getInstance();\nprefs.setString(\"influxClient\", jsonEncode(this));\n```\n\n\n\u003cimg align=\"right\" src=\"assets/images/device-dashboard.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n### Device Detail Page\n\n#### Dashboard\n\nDashboard tab contains chart ListView - it's scrollable and contains two different types of charts - gauge and simple.\nBoth charts use `QueryService` to get data - [fetchDeviceDataField](/lib/src/app/model/influx_model.dart#L732)\n- Gauge chart display average of all values in selected time range\n```sql\nimport \"influxdata/influxdb/v1\"\n    from(bucket: \"${_client.bucket}\")\n        |\u003e range(start: $maxPastTime)\n        |\u003e filter(fn: (r) =\u003e r.clientId == \"${_config.id}\" \n                    and r._measurement == \"environment\" \n                    and r[\"_field\"] == \"$field\")\n        |\u003e mean()\n```\n- Simple chart display average data for aggregate window\n```sql\nimport \"influxdata/influxdb/v1\"\n    from(bucket: \"${_client.bucket}\")\n        |\u003e range(start: $maxPastTime)\n        |\u003e filter(fn: (r) =\u003e r.clientId == \"${_config.id}\" \n                    and r._measurement == \"environment\" \n                    and r[\"_field\"] == \"$field\")\n        |\u003e keep(columns: [\"_value\", \"_time\"])\n        |\u003e aggregateWindow(column: \"_value\", every: $aggregate, fn: mean)\n```\n\n\u003cimg align=\"right\" src=\"assets/images/device-dashboard-editable.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\nApp bar on this tab (in non-editable mode) contains:\n\n- \"-1h\" `TextButton` with selected time range\n- ![Auto-renew](assets/images/icons/autorenew_white_24dp.svg#gh-dark-mode-only)\n  ![Auto-renew](assets/images/icons/autorenew_dark_24dp.svg#gh-light-mode-only)\n  refresh charts\n- ![Auto-renew](assets/images/icons/edit_icon.svg#gh-dark-mode-only)\n  ![Auto-renew](assets/images/icons/edit_icon_dark.svg#gh-light-mode-only) turn on editable mode - display/hide\nbuttons for editing on chart tiles and `FloatingActionButton` button for add new chart.\n\nApp bar on this tab (in editable mode) contains:\n\n- ![Auto-renew](assets/images/icons/dashboard_customize_icon.svg#gh-dark-mode-only)\n  ![Auto-renew](assets/images/icons/dashboard_customize_icon_dark.svg#gh-light-mode-only) change dashboard\n- ![Auto-renew](assets/images/icons/done_icon.svg#gh-dark-mode-only)\n![Auto-renew](assets/images/icons/done_icon_dark.svg#gh-light-mode-only) turn off editable mode, save changes to \ndashboard in InfluxDB\n\nButton 'change dashboard' open dialog with `DropDownMenu` - for getting dashboards is use `QueryService`, where are \ndashboards filtered by device type - [fetchDashboardsByType](/lib/src/app/model/influx_model.dart#L230):\n```sql\nfrom(bucket: \"${_influxDBClient.bucket}\")\n    |\u003e range(start: -30d)\n    |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"$measurementDashboardFlutter\")\n    |\u003e filter(fn: (r) =\u003e r[\"deviceType\"] == \"$deviceType\")\n    |\u003e last()\n```\n\u003cimg align=\"right\" src=\"assets/images/device-dashboard-change-dashboard.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\nAfter selecting dashboard and clicking 'Save' is dashboard associated to device. New point is created and write to\nInfluxDB by `WriteService` - [pairDeviceDashboard](/lib/src/app/model/influx_model.dart#L384):\n```dart\nvar writeApi = _influxDBClient.getWriteService();\nvar point = Point('deviceauth')\n        .addTag('deviceId', deviceId)\n        .addField('dashboardKey', dashboardKey);\n        .addTag('dashboardKey', dashboardKey);\nwriteApi.write(point);\n```\nWith InfluxDB 2.2, delete predicates can use any column or tag except _time, _field,or _value, so because of future\npossibility of deleting point is also added tag 'dashboardKey'.\n\nIn 'change dashboard' dialog new dashboard can be created - after clicking 'New' is open another dialog with `TextBox`\nfor DashboardKey. The type of dashboard created in Device Detail page is the same as current device. Empty dashboard is saved to\nInfluxDB by `WriteService` - [createDashboard](/lib/src/app/model/influx_model.dart#L346):\n\n\u003cimg align=\"right\" src=\"assets/images/device-dashboard-new-dashboard.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n```dart\nvar writeApi = _influxDBClient.getWriteService();\nvar point = Point(measurementDashboardFlutter)\n        .addTag(\"key\", key)\n        .addTag(\"deviceType\", deviceType)\n        .addField(\"data\", dashboardData);\nwriteApi.write(point);\n```\nAfter creating new dashboard, dialog for changing dashboard is showing again and in `DropDownList` is preselected \nnewly created dashboard. Association to device is created after clicking 'Save'.\n\n**Each dashboard can be customized** (in editable mode):\n- adding new charts - by clicking on `FloatingActionButton` is 'New Chart Page' open\n- editing charts - on each chart tile is ![Settings](assets/images/icons/settings_white_24dp.svg#gh-dark-mode-only)\n  ![Lock](assets/images/icons/settings_dark_24dp.svg#gh-light-mode-only) button, which open 'Edit Chart Page'\n- delete chart - chart can be deleted on 'Edit Chart Page'\n\nChanges on dashboard are saved to InfluxDB after clicking ![Auto-renew](assets/images/icons/done_icon.svg#gh-dark-mode-only)\n![Auto-renew](assets/images/icons/done_icon_dark.svg#gh-light-mode-only) button and editable mode is turned off.\n\n\u003cimg align=\"right\" src=\"assets/images/device-detail.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n#### Device Detail\n\nThis page contains basic device information. For getting them is use method \n[fetchDeviceWithDashboard](/lib/src/app/model/influx_model.dart#L193) - this method has two parts:\n- get device info by `QueryService` - [fetchDevice](/lib/src/app/model/influx_model.dart#L129)\n```sql\nfrom(bucket: \"${_influxDBClient.bucket}\")\n    |\u003e range(start: -30d)\n    |\u003e filter(fn: (r) =\u003e r[\"_measurement\"] == \"deviceauth\" \n                     and r.deviceId == \"$deviceId\")\n    |\u003e last()\n```\n- get dashboard by `QueryService` - [fetchDashboard](/lib/src/app/model/influx_model.dart#L261)\n```sql\nfrom(bucket: \"${_influxDBClient.bucket}\") \n    |\u003e range(start: 0) \n    |\u003e filter(fn: (r) =\u003e r._measurement == \"$measurementDashboardFlutter\")\n    |\u003e filter(fn: (r) =\u003e r.key == \"$dashboardKey\")\n    |\u003e filter(fn: (r) =\u003e r._field == \"data\")\n    |\u003e last()\n```\n\nApp bar on this tab has this functions:\n\n- ![Lock](assets/images/icons/write_data.svg#gh-dark-mode-only)\n  ![Lock](assets/images/icons/write_data_dark.svg#gh-light-mode-only)\n  write emulated data to InfluxDB by `WriteService` - [writeEmulatedData](/lib/src/app/model/influx_model.dart#L793):\n```dart\nwhile (lastTime \u003c toTime) {\n  lastTime += 60000;\n  var point = Point('environment');\n  point\n    .addTag('clientId', deviceId)\n    .addField('Temperature', _generate(period: 30, min: 0, max: 40, time: lastTime))\n    .addField('Humidity', _generate(period: 90, min: 0, max: 99, time: lastTime))\n    .addField('Pressure', _generate(period: 20, min: 970, max: 1050, time: lastTime))\n    .addField('CO2', _generate(period: 1, min: 400, max: 3000, time: lastTime).toInt())\n    .addField('TVOC', _generate(period: 1, min: 250, max: 2000, time: lastTime).toInt())\n    .addTag('TemperatureSensor', 'virtual_TemperatureSensor')\n    .addTag('HumiditySensor', 'virtual_HumiditySensor')\n    .addTag('PressureSensor', 'virtual_PressureSensor')\n    .addTag('CO2Sensor', 'virtual_CO2Sensor')\n    .addTag('TVOCSensor', 'virtual_TVOCSensor')\n    .addTag('GPSSensor', 'virtual_GPSSensor')\n    .time(lastTime);\n  writeApi.batchWrite(point);\n}\n```\n\n\u003cimg align=\"right\" src=\"assets/images/device-measurements.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n#### Measurements\n\nMeasurements tab contains `ListView` of basic metrics for each field of selected Device for last 30 days. It's use following\n`QueryService` - [fetchMeasurements](/lib/src/app/model/influx_model.dart#L469):\n```sql\ndeviceData = from(bucket: \"${_influxDBClient.bucket}\")\n    |\u003e range(start: -30d)\n    |\u003e filter(fn: (r) =\u003e r._measurement == \"environment\"\n            and r.clientId == \"$deviceId\")\nmeasurements = deviceData\n    |\u003e keep(columns: [\"_field\", \"_value\", \"_time\"])\n    |\u003e group(columns: [\"_field\"])\ncounts  =   measurements |\u003e count()                \n                         |\u003e keep(columns: [\"_field\", \"_value\"]) \n                         |\u003e rename(columns: {_value: \"count\"   })\nmaxValues = measurements |\u003e max  ()  \n                         |\u003e toFloat()  \n                         |\u003e keep(columns: [\"_field\", \"_value\"]) \n                         |\u003e rename(columns: {_value: \"maxValue\"})\nminValues = measurements |\u003e min  ()  \n                         |\u003e toFloat()  \n                         |\u003e keep(columns: [\"_field\", \"_value\"]) \n                         |\u003e rename(columns: {_value: \"minValue\"})\nmaxTimes  = measurements |\u003e max  (column: \"_time\") \n                         |\u003e keep(columns: [\"_field\", \"_time\" ]) \n                         |\u003e rename(columns: {_time : \"maxTime\" })\n\nj = (tables=\u003c-, t) =\u003e join(tables: {tables, t}, on:[\"_field\"])\n\ncounts |\u003e j(t: maxValues)\n       |\u003e j(t: minValues)\n       |\u003e j(t: maxTimes)\n       |\u003e yield(name: \"measurements\")\n```\n\nApp bar on this tab contains:\n- ![Auto-renew](assets/images/icons/autorenew_white_24dp.svg#gh-dark-mode-only)\n  ![Auto-renew](assets/images/icons/autorenew_dark_24dp.svg#gh-light-mode-only)\n  refresh Measurements\n\n\u003cimg align=\"right\" src=\"assets/images/edit-chart-page.png\" alt=\"drawing\" width=\"25%\" style=\"margin-left: 15px; margin-bottom: 15px; border-radius: 10px; filter: drop-shadow(1px 5px 5px black);\"\u003e\n\n### Edit Chart \u0026 New Chart Page\n\nEach chart in Charts ListView contains ![Settings](assets/images/icons/settings_white_24dp.svg#gh-dark-mode-only)\n![Lock](assets/images/icons/settings_dark_24dp.svg#gh-light-mode-only) button (after unlock editing in AppBar) at \nDevice Detail Page in tab Dashboard. By clicking it, Edit Chart page is displayed. \nNew chart page is showed after clicking `floatingActionButton` on Device Detail Page in tab Dashboard.\n\nOn Edit chart page, chart can be deleted by clicking on ![Settings](assets/images/icons/delete_white_24dp.svg#gh-dark-mode-only)\n\n![Lock](assets/images/icons/delete_dark_24dp.svg#gh-light-mode-only) in AppBar and after confirmation dialog.\n\nDropDown list `Field:` gets values from InfluxDB by `QueryService` - [fetchFieldNames](/lib/src/app/model/influx_model.dart#L437):\n```sql\nimport \"influxdata/influxdb/schema\"\n    schema.fieldKeys(\n        bucket: \"${_client.bucket}\",\n        predicate: (r) =\u003e r[\"_measurement\"] == \"environment\" \n                      and r[\"clientId\"] == \"${_config.id}\")\n\n```\nAfter updating/creating chart are data automatically refreshed (only for updated/created chart, other charts \naren't affected).\n\n\u003cbr clear=\"right\"/\u003e\n\u003c/div\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfluxdata%2Fiot-center-flutter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finfluxdata%2Fiot-center-flutter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfluxdata%2Fiot-center-flutter/lists"}