diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..79d935a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# Use an official Python runtime as a parent image +FROM python:3.10.8-slim + +# Set the working directory in the container +WORKDIR /app + +# Copy the current directory contents into the container at /usr/src/app +COPY sense-collector.py . +COPY storage.py . +COPY requirements.txt . + +# Install any needed packages specified in requirements.txt + +RUN apt-get update && \ + apt-get install -y gcc python3-dev python3-pip && \ + rm -rf /var/lib/apt/lists/* + +RUN python3 -m pip install --no-cache-dir --upgrade pip \ +&& python3 -m pip install --no-cache-dir -r /app/requirements.txt + +# Run script.py when the container launches +CMD ["python3", "./sense-collector.py"] diff --git a/README.md b/README.md index 4e4521d..393f015 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ + ## About The Project
-**Sense Collector** is a set of scripts deployed with Docker that provide a way of collecting data from the [Sense](https://sense.com/) energy monitoring system. Once deployed, this collection of Grafana dashboards will help you start visualizing that data. If you're just getting started with Grafana, InfluxDB, and Sense - you may want to check out my [Sense Dashboards AIO](https://github.com/lux4rd0/sense-dashboards-aio) (All In One) project. +**Sense Collector** is a set of scripts deployed with Docker that provide a way of collecting data from the [Sense](https://sense.com/) energy monitoring system. Once deployed, this collection of Grafana dashboards will help you start visualizing that data. If you're just getting started with Grafana, InfluxDB, and Sense - you may want to check out my [Sense Dashboards AIO](https://github.com/lux4rd0/sense-dashboards-aio) (All In One) project. (Still being updated... It currently uses the older V1 of my collector...) A live set of dashboards using this Collector [are available](https://labs.lux4rd0.com/sense-collector/) for you to try out. @@ -15,71 +16,74 @@ The project builds a pre-configured Docker container that takes different config - [Docker](https://docs.docker.com/install) - [Docker Compose](https://docs.docker.com/compose/install) -- [InfluxDB 1.8](https://docs.influxdata.com/influxdb/v1.8/) or [Grafana Loki 2.2](https://grafana.com/oss/loki/) -- [Grafana 8.0.6](https://grafana.com/oss/grafana/) +- [InfluxDB 2.x](https://docs.influxdata.com/influxdb/v2/) +- [Grafana 11.0.0](https://grafana.com/oss/grafana/) ## Deploying the Sense Collector Use the following [Docker container](https://hub.docker.com/r/lux4rd0/sense-collector): - lux4rd0/sense-collector:1.0.1 + lux4rd0/sense-collector:2.0.03 lux4rd0/sense-collector:latest -Correct environmental variables are required for the container to function. It mainly includes a **Monitor ID** and a corresponding **authentication token**. If you don't have those, the following script may be used: - - generate_docker-compose.sh +Correct environmental variables are required for the container to function. -The script takes the following details about your InfluxDB and your Sense credentials as environmental variables: - - SENSE_COLLECTOR_INFLUXDB_PASSWORD - SENSE_COLLECTOR_INFLUXDB_URL - SENSE_COLLECTOR_INFLUXDB_USERNAME - SENSE_COLLECTOR_PASSWORD - SENSE_COLLECTOR_USERNAME + SENSE_COLLECTOR_API_PASSWORD + SENSE_COLLECTOR_API_USERNAME + SENSE_COLLECTOR_INFLUXDB_BUCKET + SENSE_COLLECTOR_INFLUXDB_ORG + SENSE_COLLECTOR_INFLUXDB_TOKEN + SENSE_COLLECTOR_INFLUXDB_URL The username and password are the same that you use in your Sense mobile app or the Sense Web app. -An example command line would be (be sure to use your own personal access token): - - SENSE_COLLECTOR_INFLUXDB_PASSWORD="5Q7c7hwQtsZtCrXW" \ - SENSE_COLLECTOR_INFLUXDB_URL="http://influxdb01.com:8086/write?db=sense" \ - SENSE_COLLECTOR_INFLUXDB_USERNAME="senseuser" \ - SENSE_COLLECTOR_PASSWORD="twFQ8P3XBj55DvMw" \ - SENSE_COLLECTOR_USERNAME="lux4rd0@domain.com" \ - bash ./generate_docker-compose.sh +An example command line would be (be sure to change all of the variables): -The following file will be generated for you: - -#### `docker-compose.yml` - -An example of this docker-compose.yml file is included in this repository. +To start the docker container, simply update this example compose.yaml file: + name: sense-collector-53997 services: - sense-collector: - container_name: sense-collector-72535 + sense-collector-53997: + container_name: sense-collector-53997 environment: + SENSE_COLLECTOR_API_PASSWORD: CHANGEME + SENSE_COLLECTOR_API_USERNAME: dave@pulpfree.org SENSE_COLLECTOR_HOST_HOSTNAME: sense-collector.lux4rd0.com - SENSE_COLLECTOR_INFLUXDB_PASSWORD: none - SENSE_COLLECTOR_INFLUXDB_URL: http://influxdb01.lux4rd0.com:8086/write?db=sense - SENSE_COLLECTOR_INFLUXDB_USERNAME: none - SENSE_COLLECTOR_MONITOR_ID: 72535 - SENSE_COLLECTOR_TOKEN: t1.1476.1474.8e6dc77daf22e1fb471d7b942w97e477d1es53bcf2d72 + SENSE_COLLECTOR_INFLUXDB_BUCKET: sense + SENSE_COLLECTOR_INFLUXDB_ORG: Tylephony + SENSE_COLLECTOR_INFLUXDB_TOKEN: TOKEN + SENSE_COLLECTOR_INFLUXDB_URL: http://sense-collector.lux4rd0.com:8086 + SENSE_COLLECTOR_LOG_LEVEL_API: CRITICAL + SENSE_COLLECTOR_LOG_LEVEL_GENERAL: INFO + SENSE_COLLECTOR_LOG_LEVEL_STORAGE: CRITICAL + SENSE_COLLECTOR_SENSE_API_RECEIVE_DATA_OUTPUT: "False" TZ: America/Chicago image: lux4rd0/sense-collector:latest restart: always - version: '3.3' + volumes: + - type: bind + source: /mnt/docker/sense-collector/export + target: /app/export + bind: + create_host_path: true If you don't want to use docker-compose, an example docker run command will be displayed on the screen. docker run --rm \ - --name=sense-collector-72535 \ - -e SENSE_COLLECTOR_HOST_HOSTNAME=app02.tylephony.com \ - -e SENSE_COLLECTOR_INFLUXDB_PASSWORD=5Q7c7hwQtsZtCrXW \ - -e SENSE_COLLECTOR_INFLUXDB_URL=http://influxdb01.lux4rd0.com:8086/write?db=sense \ - -e SENSE_COLLECTOR_INFLUXDB_USERNAME=senseuser \ - -e SENSE_COLLECTOR_MONITOR_ID=72535 \ - -e SENSE_COLLECTOR_TOKEN=t1.1476.1474.8e6dc77daf22e1fb471d7b942w97e477d1es53bcf2d72 \ + --name=sense-collector-53997 \ + -e SENSE_COLLECTOR_API_PASSWORD=CHANGEME \ + -e SENSE_COLLECTOR_API_USERNAME=dave@pulpfree.org \ + -e SENSE_COLLECTOR_HOST_HOSTNAME=sense-collector.lux4rd0.com \ + -e SENSE_COLLECTOR_INFLUXDB_BUCKET=sense \ + -e SENSE_COLLECTOR_INFLUXDB_ORG=Tylephony \ + -e SENSE_COLLECTOR_INFLUXDB_TOKEN=TOKEN \ + -e SENSE_COLLECTOR_INFLUXDB_URL=http://sense-collector.lux4rd0.com:8086 \ + -e SENSE_COLLECTOR_LOG_LEVEL_API=CRITICAL \ + -e SENSE_COLLECTOR_LOG_LEVEL_GENERAL=INFO \ + -e SENSE_COLLECTOR_LOG_LEVEL_STORAGE=CRITICAL \ + -e SENSE_COLLECTOR_SENSE_API_RECEIVE_DATA_OUTPUT="False" \ -e TZ=America/Chicago \ + -v /mnt/docker/sense-collector/export:/app/export \ --restart always \ lux4rd0/sense-collector:latest @@ -89,118 +93,78 @@ Running `docker-compose up -d' or the `docker-run` command will download and sta The Docker contain can be configured with additional environment flags to control collector behaviors. They are descript below: -`SENSE_COLLECTOR_DEBUG` - OPTIONAL - -Outputs additional logging. Defaults to false. - -- false -- true - -`SENSE_COLLECTOR_DEBUG_CURL` - OPTIONAL - -Outputs additional logging specific to the curl commands to collect data from Sense and persist data to InfluxDB. Defaults to false. - -- true -- false - -`SENSE_COLLECTOR_DEBUG_IF` - OPTIONAL - -Outputs additional logging specific to script validation. Defaults to false. - -- true -- false - -`SENSE_COLLECTOR_DEBUG_SLEEPING` - OPTIONAL - -Outputs additional logging specific to when the Collector is sleeping between polling. Sometimes you want to see if it's doing anything. Defaults to false. - -- true -- false - -`SENSE_COLLECTOR_DISABLE_DEVICE_DETAILS` - OPTIONAL - -Disables or enables the Device Details process. Defaults to false if not set. - -- true -- false - -`SENSE_COLLECTOR_DISABLE_HEALTH_CHECK` - OPTIONAL - -Disables or enables the Health Check process. Defaults to false if not set. - -- true -- false - -`SENSE_COLLECTOR_DISABLE_HOST_PERFORMANCE` - OPTIONAL - -Disables or enables the Host Performance process. Defaults to false if not set. - -- true -- false - -`SENSE_COLLECTOR_DISABLE_MONITOR_STATUS` - OPTIONAL - -Disables or enables the Monitor Status process. Defaults to false if not set. - -- true -- false - -`SENSE_COLLECTOR_DISABLE_SENSE_COLLECTOR` - OPTIONAL - -Disables or enables the Sense Collector process. Defaults to false if not set. +# Sense Collector Environmental Variables + +This document provides a detailed explanation of the environmental variables used to configure the Sense Collector Docker container. Each variable is classified as either required or optional, along with its default value and possible options. + +## Environmental Variables + +### SENSE_COLLECTOR_API_PASSWORD +- **Description**: The password for authenticating with the Sense API. +- **Required**: Yes +- **Default**: None +- **Options**: User-provided password + +### SENSE_COLLECTOR_API_USERNAME +- **Description**: The username (email address) for authenticating with the Sense API. +- **Required**: Yes +- **Default**: None +- **Options**: User-provided email address + +### SENSE_COLLECTOR_HOST_HOSTNAME +- **Description**: The hostname that is running the Docker container. Used in the Collector Info dashboard to know where the Collector is running. +- **Required**: No +- **Default**: None +- **Options**: User-provided hostname + +### SENSE_COLLECTOR_INFLUXDB_BUCKET +- **Description**: The bucket name in InfluxDB where data will be stored. +- **Required**: Yes +- **Default**: None +- **Options**: User-provided bucket name + +### SENSE_COLLECTOR_INFLUXDB_ORG +- **Description**: The organization name in InfluxDB. +- **Required**: Yes +- **Default**: None +- **Options**: User-provided organization name + +### SENSE_COLLECTOR_INFLUXDB_TOKEN +- **Description**: The authentication token for InfluxDB. +- **Required**: Yes +- **Default**: None +- **Options**: User-provided token + +### SENSE_COLLECTOR_INFLUXDB_URL +- **Description**: The URL of the InfluxDB instance where data will be written. +- **Required**: Yes +- **Default**: None +- **Options**: User-provided URL + +### SENSE_COLLECTOR_LOG_LEVEL_API +- **Description**: Sets the logging level for the API interactions. +- **Required**: No +- **Default**: INFO +- **Options**: DEBUG, INFO, WARNING, ERROR, CRITICAL + +### SENSE_COLLECTOR_LOG_LEVEL_GENERAL +- **Description**: Sets the general logging level for the application. +- **Required**: No +- **Default**: INFO +- **Options**: DEBUG, INFO, WARNING, ERROR, CRITICAL + +### SENSE_COLLECTOR_LOG_LEVEL_STORAGE +- **Description**: Sets the logging level for storage operations. +- **Required**: No +- **Default**: INFO +- **Options**: DEBUG, INFO, WARNING, ERROR, CRITICAL + +### SENSE_COLLECTOR_SENSE_API_RECEIVE_DATA_OUTPUT +- **Description**: Enables or disables the output of received Sense API data to a file. +- **Required**: No +- **Default**: false +- **Options**: true, false -- true -- false - -`SENSE_COLLECTOR_HOST_HOSTNAME` - OPTIONAL - -This value represents the hostname that is running the Docker container. Docker creates a unique hostname each time a docker container is recycled. This entry is used in the Collector Info dashboard to know where the Collector is running. This value is populated when the `generate_docker-compose.sh` script generates the `docker-compose.yml` file. - -`SENSE_COLLECTOR_HOST_PERFORMANCE_POLL_INTERVAL` - OPTIONAL - -Time in seconds that the Host Performance process polls the host for performance details. Defaults to 60 (seconds). - -`SENSE_COLLECTOR_INFLUXDB_PASSWORD` - REQUIRED - -The password to your InfluxDB database instance. If you use the `generate_docker-compose.sh` script, it defaults to "password". It is a **required** environment variable. - -`SENSE_COLLECTOR_INFLUXDB_URL` - REQUIRED - -The URL required to persist data to InfluxDB. If you use the `generate_docker-compose.sh` script, it defaults to "http://influxdb:8086/write?db=sense". It is a **required** environment variable. - -`SENSE_COLLECTOR_INFLUXDB_USERNAME` - REQUIRED - -The username to your InfluxDB database instance. If you use the `generate_docker-compose.sh` script, it defaults to "influxdb". It is a **required** environment variable. - -`SENSE_COLLECTOR_MONITOR_ID` - REQUIRED - -The ID of your Sense monitor. If you use the generate_docker-compose.sh script, it will be automatically added to your docker-compose.yml or docker-compose run command for you based on your Sense username and password. There's no way to know this ID from the Sense mobile or Web app. It is a **required** environment variable. - -`SENSE_COLLECTOR_MONITOR_STATUS_POLL_INTERVAL` - OPTIONAL - -Time in seconds that the Monitor Status process polls the Sense monitor for details. Defaults to 60 (seconds). - -`SENSE_COLLECTOR_SENSE_COLLECTOR_POLL_INTERVAL` - OPTIONAL - -Determines how often the core Sense Collector polls mains and device information. There are two types of collections possible: - - - stream - -Allows for ingesting all of the stream data from the Sense socket API. The data is generally updated about two times a second and provides a very high granularity of metric data on all of your mains and devices. However, depending on the performance of the running Sense Collector host, it may fall behind on processing data as well as possibly higher CPU utilization. Take a look at the Collector Info dashboard for more understanding of the Sense Collector utilization. - -or - - - 5, 10, 15, 30, or 60 - -If streaming data is not an option or a desire to reduce the amount of CPU being consumed, use one of these settings (measured in seconds). When these settings are provided, the data stream is still passed through the Sense Collector to listen for timeline events but will only process mains and device details on the polling interval provided. - -`SENSE_COLLECTOR_THREADS` - OPTIONAL - -The number of threads used for processing device details. Defaults to 4. Threads should be set to something close to the number of CPUs on your host. For slower processing hosts, lowering this and using a polling interval may be helpful. - -`SENSE_COLLECTOR_TOKEN` - REQUIRED - -The authentication token for your Sense monitor. If you use the generate_docker-compose.sh script, it will be automatically added to your docker-compose.yml or docker-compose run command for you based on your Sense username and password. There's no way to obtain this token from the Sense mobile or Web app. It is a **required** environment variable. ## Collector Details @@ -218,18 +182,10 @@ Device Details polls the Sense API to gather details on each of your devices. Th avg_duration, avg_monthly_KWH, avg_monthly_cost, avg_monthly_cost, avg_monthly_pct, avg_monthly_runs, avg_watts, current_ao_wattage, current_month_KWH, current_month_cost, current_month_runs, icon, last_state, last_state_time, name, yearly_KWH, yearly_cost, yearly_text -#### host-performance - -Host Performance is a process that gathers CPU, process details, netstat, process counts, and memory utilization. These details are viewable in the Collector Info Grafana dashboard. - #### monitor-status Monitor Status gathers details about the Sense monitor and detection status for both in progress and found devices. Monitor details include: emac, ethernet, ip_address, mac, ndt_enabled, online, progress, serial, signal, ssid, status, test_result, version, wifi_strength -#### health-check - -Health Check is a function that runs every 60 seconds to validate the health of the running processes. If no data has been collected or persisted to InfluxDB and this parameter is set to true, the docker container will be marked as Unhealthy and terminate. Setting this to false will always return a healthy response to the Docker health check. The health check is included as there may be times when the socket connection goes silent, and recycling the container is the only way to get it listening again. - ## Grafana Dashboards Collecting data is only half the fun. Now it's time to provision some Grafana Dashboards to visualize all of your essential Sense data. You'll find a [folder of dashboards](https://github.com/lux4rd0/sense-collector/dashboards) with collectors and backends split out. You can also use the links/numbers next to each dashboard title to load the dashboards in [directly from Grafana](https://grafana.com/grafana/dashboards?search=sense%20collector). @@ -282,7 +238,7 @@ Each dashboard has dropdowns at the top that provide for filtering of measuremen **Always On Devices**: Shows which devices that the Sense monitor has detected to have an Always On wattage component. This may be different than actual wattage and tends to update less frequently. -> **Notice**: This Grafana dashboard uses the community visualization [Bubble Chart](https://grafana.com/grafana/plugins/digrich-bubblechart-panel/) panel plugin. It hasn't been updated for quite some time and won't work out of the box with current versions of Grafana due to plugin signing requirements. [Configuration changes](https://grafana.com/docs/grafana/latest/administration/configuration/#allow_loading_unsigned_plugins) on your Grafana instance will be needed to load this plugin. +> **Notice**: This Grafana dashboard uses the community visualization [Bubble Chart](https://grafana.com/grafana/plugins/digrich-bubblechart-panel/) panel plugin. ### Mains Overview - [14736](https://grafana.com/grafana/dashboards/14736) @@ -325,3 +281,4 @@ Project Link: https://github.com/lux4rd0/sense-collector - Grafana Labs - [https://grafana.com/](https://grafana.com/) - Grafana - [https://grafana.com/oss/grafana/](https://grafana.com/oss/grafana/) - Grafana Dashboard Community - [https://grafana.com/grafana/dashboards/](https://grafana.com/grafana/dashboards/) + diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..b33a1f5 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,25 @@ +name: sense-collector +services: + sense-collector: + container_name: sense-collector + environment: + SENSE_COLLECTOR_API_PASSWORD: adkajsdlnaisdjasidamlisdmald + SENSE_COLLECTOR_API_USERNAME: dave@lux4rd0.com + SENSE_COLLECTOR_HOST_HOSTNAME: app20.tylephony.com + SENSE_COLLECTOR_INFLUXDB_BUCKET: sense + SENSE_COLLECTOR_INFLUXDB_ORG: Tylephony + SENSE_COLLECTOR_INFLUXDB_TOKEN: lkjijdalisdjoanisdnalisdjlamisdjm== + SENSE_COLLECTOR_INFLUXDB_URL: http://influxdb.lux4rd0.com:8086 + SENSE_COLLECTOR_LOG_LEVEL_API: CRITICAL + SENSE_COLLECTOR_LOG_LEVEL_GENERAL: INFO + SENSE_COLLECTOR_LOG_LEVEL_STORAGE: CRITICAL + SENSE_COLLECTOR_SENSE_API_RECEIVE_DATA_OUTPUT: "False" + TZ: America/Chicago + image: lux4rd0/sense-collector:latest + restart: always + volumes: + - type: bind + source: /mnt/docker/sense-collector/export + target: /app/export + bind: + create_host_path: true diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e44a99a..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - sense-collector: - container_name: sense-collector-72535 - environment: - SENSE_COLLECTOR_HOST_HOSTNAME: sense-collector.lux4rd0.com - SENSE_COLLECTOR_INFLUXDB_PASSWORD: none - SENSE_COLLECTOR_INFLUXDB_URL: http://influxdb01.lux4rd0.com:8086/write?db=sense - SENSE_COLLECTOR_INFLUXDB_USERNAME: none - SENSE_COLLECTOR_MONITOR_ID: 72535 - SENSE_COLLECTOR_TOKEN: t1.1476.1474.8e6dc77daf22e1fb471d7b942w97e477d1es53bcf2d72 - TZ: America/Chicago - image: lux4rd0/sense-collector:latest - restart: always -version: '3.3' diff --git a/generate_docker-compose.sh b/generate_docker-compose.sh deleted file mode 100644 index 98f1981..0000000 --- a/generate_docker-compose.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - generate_docker-compose.sh -## - -## -## Set Specific Variables -## - -collector_type="sense-collector" - -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -sense_password=$SENSE_COLLECTOR_PASSWORD -sense_username=$SENSE_COLLECTOR_USERNAME - -echo_bold=$(tput -T xterm bold) -echo_color_sense=$(echo -e "\e[3$(( RANDOM * 6 / 32767 + 1 ))m") -echo_color_collector=$(echo -e "\e[3$(( RANDOM * 6 / 32767 + 1 ))m") -echo_normal=$(tput -T xterm sgr0) - -echo " - - ███████╗███████╗███╗ ██╗███████╗███████╗ - ██╔════╝██╔════╝████╗ ██║██╔════╝██╔════╝ - ███████╗█████╗ ██╔██╗ ██║███████╗█████╗ - ╚════██║██╔══╝ ██║╚██╗██║╚════██║██╔══╝ - ███████║███████╗██║ ╚████║███████║███████╗ - ╚══════╝╚══════╝╚═╝ ╚═══╝╚══════╝╚══════╝ - - ██████╗ ██████╗ ██╗ ██╗ ███████╗ ██████╗████████╗ ██████╗ ██████╗ -██╔════╝██╔═══██╗██║ ██║ ██╔════╝██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗ -██║ ██║ ██║██║ ██║ █████╗ ██║ ██║ ██║ ██║██████╔╝ -██║ ██║ ██║██║ ██║ ██╔══╝ ██║ ██║ ██║ ██║██╔══██╗ -╚██████╗╚██████╔╝███████╗███████╗███████╗╚██████╗ ██║ ╚██████╔╝██║ ██║ - ╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ - -" - -echo "${echo_bold}Sense Collector${echo_normal} (generate_docker-compose.sh) - https://github.com/lux4rd0/sense-collector - -${echo_bold}influxdb_password${echo_normal}=${influxdb_password} -${echo_bold}influxdb_url${echo_normal}=${influxdb_url} -${echo_bold}influxdb_username${echo_normal}=${influxdb_username} -${echo_bold}sense_password${echo_normal}=${sense_password} -${echo_bold}sense_username${echo_normal}=${sense_username} -" - -if [ -z "${influxdb_password}" ]; then echo "${echo_bold}${echo_color_sense}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_INFLUXDB_PASSWORD${echo_normal} was not set. Setting defaults: ${echo_bold}password${echo_normal}"; influxdb_password="password"; fi - -if [ -z "${influxdb_url}" ]; then echo "${echo_bold}${echo_color_sense}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_INFLUXDB_URL${echo_normal} was not set. Setting defaults: ${echo_bold}http://influxdb:8086/write?db=sense${echo_normal}"; influxdb_url="http://influxdb:8086/write?db=sense" ; fi - -if [ -z "${influxdb_username}" ]; then echo "${echo_bold}${echo_color_sense}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_INFLUXDB_USERNAME${echo_normal} was not set. Setting defaults: ${echo_bold}influxdb${echo_normal}"; influxdb_username="influxdb"; fi - -if [ -z "${sense_password}" ]; then echo "${echo_bold}${echo_color_sense}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_PASSWORD${echo_normal} was not set. Missing Sense password. Please provide your password as an environmental variable."; fi - -if [ -z "${sense_username}" ]; then echo "${echo_bold}${echo_color_sense}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_USERNAME${echo_normal} was not set. Missing Sense user name. Please provide your user name as an environmental variable." - -else - -url_sense_authenticate="https://api.sense.com/apiservice/api/v1/authenticate" - -response_url_sense=$(curl --silent --show-error --fail -k --data "email=${sense_username}" --data "password=${sense_password}" -H "Sense-Collector-Client-Version: 1.0.0" -H "X-Sense-Protocol: 3" -H "User-Agent: okhttp/3.8.0" "${url_sense_authenticate}") - -#echo "${response_url_sense}" - -token=$(echo "${response_url_sense}" | jq -r .access_token) -monitor_id=$(echo "${response_url_sense}" | jq -r .monitors[].id) -time_zone=$(echo "${response_url_sense}" | jq -r .monitors[].time_zone) - -echo "${echo_bold}token${echo_normal}=${token} -${echo_bold}monitor_id${echo_normal}=${monitor_id} -${echo_bold}time_zone${echo_normal}=${time_zone} -" - -FILE_DC="${PWD}/docker-compose.yml" - -if test -f "${FILE_DC}"; then -existing_file_timestamp_dc=$(date -r "${FILE_DC}" "+%Y%m%d-%H%M%S") -echo "${echo_bold}${echo_color_sense}sense-collector:${echo_normal} Existing ${echo_bold}${FILE_DC}${echo_normal} with a timestamp of ${echo_bold}${existing_file_timestamp_dc}${echo_normal} file found. Backup up file to ${echo_bold}${FILE_DC}.${existing_file_timestamp_dc}.old${echo_normal} -" -mv "${FILE_DC}" "${FILE_DC}"."${existing_file_timestamp_dc}.old" -fi - -## -## ┌┬┐┌─┐┌─┐┬┌─┌─┐┬─┐ ┌─┐┌─┐┌┬┐┌─┐┌─┐┌─┐┌─┐┬ ┬┌┬┐┬ -## │││ ││ ├┴┐├┤ ├┬┘───│ │ ││││├─┘│ │└─┐├┤ └┬┘││││ -## ─┴┘└─┘└─┘┴ ┴└─┘┴└─ └─┘└─┘┴ ┴┴ └─┘└─┘└─┘o┴ ┴ ┴┴─┘ -## - -echo "services: - sense-collector-${monitor_id}: - container_name: sense-collector-${monitor_id} - environment: - TZ: ${time_zone} - SENSE_COLLECTOR_HOST_HOSTNAME: $(hostname) - SENSE_COLLECTOR_INFLUXDB_PASSWORD: ${influxdb_password} - SENSE_COLLECTOR_INFLUXDB_URL: ${influxdb_url} - SENSE_COLLECTOR_INFLUXDB_USERNAME: ${influxdb_username}" > docker-compose.yml - -if [ -n "$loki_client_url" ] - -then - -echo " SENSE_COLLECTOR_LOKI_CLIENT_URL: ${loki_client_url}" >> docker-compose.yml - -fi - -echo " SENSE_COLLECTOR_TOKEN: ${token} - SENSE_COLLECTOR_MONITOR_ID: ${monitor_id} - image: lux4rd0/sense-collector:latest - restart: always -version: '3.3'" >> docker-compose.yml - -echo "${echo_bold}${echo_color_sense}sense-collector:${echo_normal} ${echo_bold}${FILE_DC}${echo_normal} file created" - -fi - -echo " -You may also use this docker run command: -" - -echo "docker run --rm \\ - --name=sense-collector-${monitor_id} \\ - -e SENSE_COLLECTOR_HOST_HOSTNAME=$(hostname) \\ - -e SENSE_COLLECTOR_INFLUXDB_PASSWORD=${influxdb_password} \\ - -e SENSE_COLLECTOR_INFLUXDB_URL=${influxdb_url} \\ - -e SENSE_COLLECTOR_INFLUXDB_USERNAME=${influxdb_username} \\ - -e SENSE_COLLECTOR_MONITOR_ID=${monitor_id} \\ - -e SENSE_COLLECTOR_TOKEN=${token} \\ - -e TZ=America/Chicago \\ - --restart always \\ - lux4rd0/sense-collector:latest" - - - diff --git a/grafana/shared/sense_collector-collector_info.json b/grafana/shared/sense_collector-collector_info.json deleted file mode 100644 index aa045db..0000000 --- a/grafana/shared/sense_collector-collector_info.json +++ /dev/null @@ -1,1991 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_INFLUXDB_- SENSE", - "label": "InfluxDB - sense", - "description": "", - "type": "datasource", - "pluginId": "influxdb", - "pluginName": "InfluxDB" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "8.0.6" - }, - { - "type": "datasource", - "id": "influxdb", - "name": "InfluxDB", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "description": "", - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "iteration": 1626665920803, - "links": [ - { - "asDropdown": true, - "icon": "external link", - "includeVars": true, - "keepTime": true, - "tags": [ - "sense-collector" - ], - "targetBlank": false, - "title": "Sense Collector - Dashboards", - "tooltip": "", - "type": "dashboards", - "url": "" - } - ], - "panels": [ - { - "collapsed": false, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 30, - "panels": [], - "title": "Collector Duration Stats", - "type": "row" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 20, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "ns" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 0, - "y": 1 - }, - "id": 103, - "interval": "$interval", - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "bottom" - }, - "tooltip": { - "mode": "multi" - } - }, - "targets": [ - { - "alias": "$tag_source - $tag_function", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "source" - ], - "type": "tag" - }, - { - "params": [ - "function" - ], - "type": "tag" - }, - { - "params": [ - "0" - ], - "type": "fill" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "duration" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - } - ] - ], - "tags": [ - { - "key": "source", - "operator": "=~", - "value": "/^$source$/" - }, - { - "condition": "AND", - "key": "function", - "operator": "=~", - "value": "/^$function$/" - } - ] - } - ], - "title": "Function Timer", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 12, - "y": 1 - }, - "id": 105, - "interval": "$interval", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "single" - } - }, - "targets": [ - { - "alias": "$tag_source - $tag_function", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "function" - ], - "type": "tag" - }, - { - "params": [ - "source" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "difference_epoch" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - } - ] - ], - "tags": [ - { - "key": "source", - "operator": "=~", - "value": "/^$source$/" - }, - { - "condition": "AND", - "key": "function", - "operator": "=~", - "value": "/^$function$/" - } - ] - } - ], - "timeFrom": null, - "title": "Epoch Time Difference", - "type": "timeseries" - }, - { - "collapsed": false, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 12 - }, - "id": 24, - "panels": [], - "repeat": "collector_key", - "title": "Host Observability - $host_hostname", - "type": "row" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMax": 0, - "axisSoftMin": 0, - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 20, - "gradientMode": "opacity", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 13 - }, - "id": 43, - "interval": "$interval", - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "alias": "$col", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "host_hostname" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"cpu_idle\") AS \"Idle\", mean(\"cpu_iowait\"), mean(\"cpu_sys\"), mean(\"cpu_usr\") FROM \"sense_o11y\" WHERE (\"host_hostname\" = 'docker02.tylephony.com') AND $timeFilter GROUP BY time($__interval), \"host_hostname\" fill(previous)", - "rawQuery": false, - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "cpu_iowait" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "IO Wait" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "cpu_sys" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "System" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "cpu_usr" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "User" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "host_hostname", - "operator": "=~", - "value": "/^$host_hostname$/" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "CPU", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 20, - "gradientMode": "opacity", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 8, - "y": 13 - }, - "id": 52, - "interval": "$interval", - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "alias": "$col minute", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "host_hostname" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"cpu_idle\") AS \"Idle\", mean(\"cpu_iowait\"), mean(\"cpu_sys\"), mean(\"cpu_usr\") FROM \"sense_o11y\" WHERE (\"host_hostname\" = 'docker02.tylephony.com') AND $timeFilter GROUP BY time($__interval), \"host_hostname\" fill(previous)", - "rawQuery": false, - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "loadavg_one" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "1" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "loadavg_five" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "5" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "loadavg_fifteen" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "15" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "host_hostname", - "operator": "=~", - "value": "/^$host_hostname$/" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Load Average", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 20, - "gradientMode": "opacity", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 16, - "y": 13 - }, - "id": 71, - "interval": "$interval", - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "right" - }, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "7.5.3", - "targets": [ - { - "alias": "$tag_sysperf_command", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "sysperf_command" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "total_time" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [], - "type": "non_negative_difference" - }, - { - "params": [ - " / 100" - ], - "type": "math" - } - ] - ], - "tags": [ - { - "key": "sysperf_type", - "operator": "=", - "value": "process_utilization" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Per Process CPU Usage (Seconds)", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 20, - "gradientMode": "opacity", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 19 - }, - "id": 101, - "interval": "$interval", - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right" - }, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "alias": "$tag_netstat_app - $col", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "host_hostname" - ], - "type": "tag" - }, - { - "params": [ - "netstat_app" - ], - "type": "tag" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"cpu_idle\") AS \"Idle\", mean(\"cpu_iowait\"), mean(\"cpu_sys\"), mean(\"cpu_usr\") FROM \"sense_o11y\" WHERE (\"host_hostname\" = 'docker02.tylephony.com') AND $timeFilter GROUP BY time($__interval), \"host_hostname\" fill(previous)", - "rawQuery": false, - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "netstat_closewait" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Close Wait" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "netstat_established" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Established" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "netstat_finwait2" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Finwait2" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "netstat_listen" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Listen" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "netstat_timewait" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Timewait" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "host_hostname", - "operator": "=~", - "value": "/^$host_hostname$/" - }, - { - "condition": "AND", - "key": "sysperf_type", - "operator": "=", - "value": "netstat" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Netstat (Container)", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 20, - "gradientMode": "opacity", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 8, - "y": 19 - }, - "id": 69, - "interval": "$interval", - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "alias": "$col", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "host_hostname" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"cpu_idle\") AS \"Idle\", mean(\"cpu_iowait\"), mean(\"cpu_sys\"), mean(\"cpu_usr\") FROM \"sense_o11y\" WHERE (\"host_hostname\" = 'docker02.tylephony.com') AND $timeFilter GROUP BY time($__interval), \"host_hostname\" fill(previous)", - "rawQuery": false, - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "processes_running" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Running" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "processes_sleeping" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Sleeping" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "processes_stopped" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Stopped" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "processes_zombie" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Zombie" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "host_hostname", - "operator": "=~", - "value": "/^$host_hostname$/" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Processes (Container)", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "deckbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 16, - "y": 19 - }, - "id": 53, - "interval": "$interval", - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "right" - }, - "tooltip": { - "mode": "multi" - } - }, - "pluginVersion": "7.5.5", - "targets": [ - { - "alias": "$col", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "host_hostname" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"cpu_idle\") AS \"Idle\", mean(\"cpu_iowait\"), mean(\"cpu_sys\"), mean(\"cpu_usr\") FROM \"sense_o11y\" WHERE (\"host_hostname\" = 'docker02.tylephony.com') AND $timeFilter GROUP BY time($__interval), \"host_hostname\" fill(previous)", - "rawQuery": false, - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "mem_used" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Used" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "mem_free" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Free" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "mem_available" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Available" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "mem_buffers" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Buffers" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "mem_cache" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Cache" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "mem_shared" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Shared" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "mem_total" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - }, - { - "params": [ - "Total" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "host_hostname", - "operator": "=~", - "value": "/^$host_hostname$/" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Memory Utilization", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "dateTimeFromNow" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 5, - "x": 0, - "y": 25 - }, - "id": 107, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "targets": [ - { - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - } - ], - "measurement": "sense_event", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "time_epoch" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], - "tags": [ - { - "key": "event_type", - "operator": "=", - "value": "hello" - } - ] - } - ], - "title": "Collector Socket Refresh Connection", - "type": "stat" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "dark-green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 7, - "x": 5, - "y": 25 - }, - "id": 9, - "interval": "$interval", - "options": { - "colorMode": "background", - "graphMode": "area", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.0.6", - "repeat": null, - "targets": [ - { - "alias": "$tag_source", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "source" - ], - "type": "tag" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "time_epoch" - ], - "type": "field" - }, - { - "params": [], - "type": "count" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "event" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Collector Starts", - "transformations": [], - "type": "stat" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "center", - "displayMode": "auto", - "filterable": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "dateTimeAsUS" - }, - "overrides": [ - { - "matcher": { - "id": "byType", - "options": "number" - }, - "properties": [ - { - "id": "unit", - "value": "dateTimeFromNow" - } - ] - } - ] - }, - "gridPos": { - "h": 4, - "w": 12, - "x": 12, - "y": 25 - }, - "id": 8, - "options": { - "frameIndex": 3, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "Time" - } - ] - }, - "pluginVersion": "8.0.6", - "repeat": null, - "repeatDirection": "v", - "targets": [ - { - "alias": "$tag_source", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "source" - ], - "type": "tag" - } - ], - "measurement": "sense_o11y", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "time_epoch" - ], - "type": "field" - }, - { - "params": [], - "type": "distinct" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "event" - }, - { - "condition": "AND", - "key": "type", - "operator": "=", - "value": "event" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Collector Start Time", - "transformations": [ - { - "id": "seriesToColumns", - "options": {} - } - ], - "type": "table" - } - ], - "refresh": "1m", - "schemaVersion": 30, - "style": "dark", - "tags": [ - "sense-collector", - "influxdb" - ], - "templating": { - "list": [ - { - "allValue": null, - "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", - "definition": "select DISTINCT(\"host_hostname\") from (select \"host_hostname\",\"cpu_soft\" from \"sense_o11y\" WHERE $timeFilter)", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Host", - "multi": true, - "name": "host_hostname", - "options": [], - "query": "select DISTINCT(\"host_hostname\") from (select \"host_hostname\",\"cpu_soft\" from \"sense_o11y\" WHERE $timeFilter)", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 5, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", - "definition": "SHOW TAG VALUES FROM \"sense_o11y\" WITH KEY = \"source\"", - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Source", - "multi": false, - "name": "source", - "options": [], - "query": "SHOW TAG VALUES FROM \"sense_o11y\" WITH KEY = \"source\"", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 5, - "type": "query" - }, - { - "allValue": null, - "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", - "definition": "SHOW TAG VALUES FROM \"sense_o11y\" WITH KEY = \"function\" WHERE \"source\" =~ /^$source$/", - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Function", - "multi": true, - "name": "function", - "options": [], - "query": "SHOW TAG VALUES FROM \"sense_o11y\" WITH KEY = \"function\" WHERE \"source\" =~ /^$source$/", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 5, - "type": "query" - }, - { - "allValue": null, - "current": { - "selected": false, - "text": "1m", - "value": "1m" - }, - "description": null, - "error": null, - "hide": 0, - "includeAll": false, - "label": "Interval", - "multi": false, - "name": "interval", - "options": [ - { - "selected": false, - "text": "1s", - "value": "1s" - }, - { - "selected": false, - "text": "5s", - "value": "5s" - }, - { - "selected": false, - "text": "10s", - "value": "10s" - }, - { - "selected": false, - "text": "15s", - "value": "15s" - }, - { - "selected": false, - "text": "30s", - "value": "30s" - }, - { - "selected": true, - "text": "1m", - "value": "1m" - }, - { - "selected": false, - "text": "5m", - "value": "5m" - }, - { - "selected": false, - "text": "10m", - "value": "10m" - }, - { - "selected": false, - "text": "15m", - "value": "15m" - }, - { - "selected": false, - "text": "1h", - "value": "1h" - }, - { - "selected": false, - "text": "3h", - "value": "3h" - } - ], - "query": "1s,5s,10s,15s,30s,1m,5m,10m,15m,1h,3h", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - } - ] - }, - "time": { - "from": "now/d", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Sense Collector - Collector Info", - "uid": "lux4rd0labs_sense_01", - "version": 1, - "description": "Sense Collector provides a way of collecting real-time data from the Sense Energy Monitor. These Grafana dashboards offer visualizations for detected devices and smart plugs and their wattage, voltage, and amp utilization." -} \ No newline at end of file diff --git a/grafana/shared/sense_collector-device_overview.json b/grafana/shared/sense_collector-device_overview.json index 7802add..d633599 100644 --- a/grafana/shared/sense_collector-device_overview.json +++ b/grafana/shared/sense_collector-device_overview.json @@ -1,26 +1,27 @@ { "__inputs": [ { - "name": "DS_INFLUXDB_- SENSE", - "label": "InfluxDB - sense", + "name": "DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE", + "label": "InfluxDB - influxdb02 - Tylephony - sense", "description": "", "type": "datasource", "pluginId": "influxdb", "pluginName": "InfluxDB" } ], + "__elements": {}, "__requires": [ { "type": "panel", "id": "digrich-bubblechart-panel", "name": "Bubble Chart", - "version": "1.2.0" + "version": "2.0.1" }, { "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "8.0.6" + "version": "11.0.0" }, { "type": "datasource", @@ -51,7 +52,10 @@ "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -65,49 +69,85 @@ "type": "dashboard" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "enable": false, "iconColor": "#e35431", "name": "Device Status - On", "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"event_type\" = 'new_timeline' AND \"name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceOn'", "tagsColumn": "device_state", + "target": { + "fromAnnotations": true, + "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"device_name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceOn'", + "rawQuery": true, + "refId": "Anno", + "tagsColumn": "device_state", + "textColumn": "body", + "textEditor": true, + "timeEndColumn": "" + }, "textColumn": "body" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "enable": false, "iconColor": "#707070", "name": "Device Status - Off", "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"event_type\" = 'new_timeline' AND \"name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceOff'", "tagsColumn": "device_state", + "target": { + "fromAnnotations": true, + "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"device_name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceOff'", + "rawQuery": true, + "refId": "Anno", + "tagsColumn": "device_state", + "textColumn": "body", + "textEditor": true, + "timeEndColumn": "" + }, "textColumn": "body" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "enable": false, "iconColor": "#77d38e", "name": "Device Status - Idle", "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"event_type\" = 'new_timeline' AND \"name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceIdle'", "tagsColumn": "device_state", + "target": { + "fromAnnotations": true, + "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"device_name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceIdle'", + "rawQuery": true, + "refId": "Anno", + "tagsColumn": "device_state", + "textColumn": "body", + "textEditor": true, + "timeEndColumn": "" + }, "textColumn": "body" } ] }, - "description": "Sense Collector provides a way of collecting real-time data from the Sense Energy Monitor. These Grafana dashboards offer visualizations for detected devices and smart plugs and their wattage, voltage, and amp utilization.", "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, - "iteration": 1626667131144, "links": [ { "asDropdown": true, "icon": "external link", - "includeVars": true, - "keepTime": true, + "includeVars": false, + "keepTime": false, "tags": [ - "sense-collector", - "influxdb" + "sense-collector" ], "targetBlank": false, "title": "Sense Collector - Dashboards", @@ -116,43 +156,66 @@ "url": "" } ], + "liveNow": false, "panels": [ { - "bgColor": null, - "colorScheme": "Gradient", - "datasource": "${DS_INFLUXDB_- SENSE}", - "decimal": 2, - "displayLabel": true, - "format": "watt", - "gradientColors": [ - "#468860", - "#e35431" - ], - "gradientThresholds": "40,80", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "gridPos": { - "h": 15, + "h": 16, "w": 6, "x": 0, "y": 0 }, - "groupDepthColors": [ - "#E0B400", - "#5794F2" - ], - "groupSeperator": ",", - "height": 400, "hideTimeOverride": true, "id": 12, "interval": "$interval", - "maxDataPoints": 1, - "mode": "time", - "nullPointMode": "connected", - "pluginVersion": "8.0.4", - "svgBubbleId": "svg_12", - "svgContainer": {}, + "options": { + "bgColor": "", + "colorSchemeParams": { + "colorScheme": "Gradient", + "gradientColors": [ + "#468860", + "#e35431" + ], + "gradientThresholds": "40,80", + "groupDepthColors": [ + "#E0B400", + "#5794F2" + ], + "thresholdColors": [ + "#C4162A", + "#FADE2A", + "#1F60C4" + ], + "thresholds": "33,66" + }, + "decimals": 2, + "displayLabel": false, + "displayLabels": [ + "name" + ], + "groupBy": "Name", + "groupLabels": [], + "groupSeparator": ",", + "showSeriesCount": false, + "stat": "current", + "text": "", + "textanchor": "", + "textfont": "", + "textshadow": "", + "unit": "watt" + }, + "pluginVersion": "2.0.1", "targets": [ { - "alias": "$tag_name", + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -162,7 +225,7 @@ }, { "params": [ - "name" + "device_name::tag" ], "type": "tag" } @@ -178,7 +241,7 @@ [ { "params": [ - "current_watts" + "watts" ], "type": "field" }, @@ -190,34 +253,31 @@ ], "tags": [ { - "key": "name", + "key": "device_name::tag", "operator": "=~", "value": "/^$devices$/" } ] } ], - "thresholdColors": [ - "#C4162A", - "#FADE2A", - "#1F60C4" - ], - "thresholds": "33,66", - "timeFrom": "1m", - "timeShift": null, "title": "Current Wattage", "transparent": true, - "type": "digrich-bubblechart-panel", - "valueName": "current" + "type": "digrich-bubblechart-panel" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -229,6 +289,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, @@ -236,7 +297,7 @@ "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "spanNulls": false, "stacking": { "group": "A", "mode": "normal" @@ -256,7 +317,7 @@ } ] }, - "unit": "watth" + "unit": "watt" }, "overrides": [] }, @@ -272,16 +333,23 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { - "mode": "multi" + "maxHeight": 600, + "mode": "multi", + "sort": "none" } }, "pluginVersion": "8.0.4", "targets": [ { - "alias": "$tag_name", + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -291,7 +359,7 @@ }, { "params": [ - "name" + "device_name::tag" ], "type": "tag" } @@ -307,7 +375,7 @@ [ { "params": [ - "current_watts" + "watts" ], "type": "field" }, @@ -319,20 +387,21 @@ ], "tags": [ { - "key": "name", + "key": "device_name::tag", "operator": "=~", "value": "/^$devices$/" } ] } ], - "timeFrom": null, - "timeShift": null, "title": "Wattage By Device (Stacked)", "type": "timeseries" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { @@ -340,7 +409,14 @@ }, "custom": { "fillOpacity": 100, - "lineWidth": 0 + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineWidth": 0, + "spanNulls": false }, "mappings": [ { @@ -388,19 +464,70 @@ "alignValue": "center", "legend": { "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "mergeValues": true, "rowHeight": 0.72, "showValue": "auto", "tooltip": { - "mode": "single" + "maxHeight": 600, + "mode": "single", + "sort": "none" } }, "pluginVersion": "8.0.4", "targets": [ + { + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "device_name::tag" + ], + "type": "tag" + } + ], + "hide": true, + "measurement": "sense_event", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT last(\"watts\") FROM \"sense_devices\" WHERE $timeFilter GROUP BY time($__interval), \"name\" fill(null)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "device_state" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + }, { "alias": "$tag_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -421,12 +548,119 @@ "type": "fill" } ], + "hide": true, "measurement": "sense_event", "orderByTime": "ASC", "policy": "default", - "query": "SELECT last(\"watts\") FROM \"sense_devices\" WHERE $timeFilter GROUP BY time($__interval), \"name\" fill(null)", - "rawQuery": false, - "refId": "A", + "query": "SELECT last(\"device_state\") FROM \"sense_event\" WHERE (\"event_type\" = 'new_timeline' AND \"name\" =~ /^$devices$/) AND (time >= ${__from}ms - $expand_from_events AND time <= ${__to}ms) GROUP BY time($__interval), \"name\" fill(previous)", + "rawQuery": true, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "device_state" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "event_type", + "operator": "=", + "value": "new_timeline" + }, + { + "condition": "AND", + "key": "name", + "operator": "=~", + "value": "/^$devices$/" + } + ] + }, + { + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "device_name::tag" + ], + "type": "tag" + } + ], + "hide": true, + "measurement": "sense_event", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT last(\"device_state\") FROM \"sense_event\" WHERE $timeFilter GROUP BY time($__interval), \"device_name\"::tag", + "rawQuery": true, + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "device_state" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + }, + { + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "name" + ], + "type": "tag" + }, + { + "params": [ + "previous" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "sense_event", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT last(\"device_state\") FROM \"sense_event\" WHERE ( \"device_name\" =~ /^$devices$/) AND (time >= ${__from}ms - $expand_from_events AND time <= ${__to}ms) GROUP BY time($__interval), \"device_name\"::tag fill(previous)", + "rawQuery": true, + "refId": "D", "resultFormat": "time_series", "select": [ [ @@ -458,11 +692,13 @@ } ], "title": "Device Status", - "transformations": [], "type": "state-timeline" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { @@ -470,8 +706,11 @@ }, "custom": { "align": "auto", - "displayMode": "auto", - "filterable": false + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false }, "mappings": [], "thresholds": { @@ -497,7 +736,7 @@ }, { "id": "custom.width", - "value": 75 + "value": 82 } ] }, @@ -517,7 +756,7 @@ }, { "id": "custom.width", - "value": 128 + "value": 129 }, { "id": "thresholds", @@ -568,8 +807,11 @@ } }, { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } } ] }, @@ -585,7 +827,7 @@ }, { "id": "custom.width", - "value": 92 + "value": 90 }, { "id": "decimals", @@ -600,8 +842,10 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "image" + "id": "custom.cellOptions", + "value": { + "type": "image" + } }, { "id": "mappings", @@ -691,7 +935,7 @@ }, { "id": "custom.width", - "value": 55 + "value": 34 } ] }, @@ -758,8 +1002,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } }, { "id": "mappings", @@ -799,7 +1046,7 @@ }, { "id": "custom.width", - "value": 55 + "value": 37 } ] }, @@ -811,23 +1058,39 @@ "properties": [ { "id": "custom.width", - "value": 104 + "value": 111 } ] }, { "matcher": { "id": "byName", - "options": "Watts (AO)" + "options": "Last State Time" }, "properties": [ { "id": "unit", - "value": "watt" + "value": "dateTimeAsUSNoDateIfToday" + }, + { + "id": "custom.align", + "value": "center" }, { "id": "custom.width", - "value": 90 + "value": 172 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Device Name" + }, + "properties": [ + { + "id": "custom.width", + "value": 232 } ] } @@ -843,25 +1106,37 @@ "id": 5, "maxDataPoints": 1, "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, "showHeader": true, "sortBy": [ { "desc": true, - "displayName": "Watts (In Use)" + "displayName": "Last State Time" } ] }, - "pluginVersion": "8.0.6", + "pluginVersion": "11.0.0", "targets": [ { - "query": "SELECT last(*) FROM \"sense_devices\" WHERE (\"name\" =~ /^$devices$/) and $timeFilter GROUP BY time(1d), \"name\" fill(null)", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "query": "SELECT last(*) FROM \"sense_devices\" WHERE $timeFilter GROUP BY time(30d), \"device_name\"::tag", "rawQuery": true, "refId": "A", "resultFormat": "table" } ], - "timeFrom": "5m", - "timeShift": null, + "timeFrom": "30d", "title": "Device Details - Average", "transformations": [ { @@ -869,6 +1144,8 @@ "options": { "excludeByName": { "Time": true, + "last_ao_st": true, + "last_ao_w": true, "last_avg_monthly_cost": true, "last_current_ao_wattage": true, "last_current_month_KWH": true, @@ -877,40 +1154,50 @@ "last_e": true, "last_i": true, "last_icon": false, + "last_info": true, "last_last_state": false, + "last_last_state_time": false, "last_last_state_time_epoch": false, + "last_sd_current": true, + "last_sd_energy": true, + "last_sd_voltage": true, + "last_sd_watts": true, "last_v": true, - "last_watts": false, + "last_watts": true, "last_yearly_KWH": true, "last_yearly_cost": true, "last_yearly_text": true }, "indexByName": { "Time": 0, - "last_ao_w": 7, - "last_avg_duration": 8, - "last_avg_monthly_KWH": 9, - "last_avg_monthly_cost": 10, - "last_avg_monthly_pct": 11, - "last_avg_monthly_runs": 12, - "last_avg_watts": 6, - "last_current_ao_wattage": 13, - "last_current_month_KWH": 14, - "last_current_month_cost": 15, - "last_current_month_runs": 16, - "last_current_watts": 5, - "last_e": 20, - "last_i": 21, + "device_name": 1, + "last_ao_st": 17, + "last_ao_w": 5, + "last_avg_duration": 6, + "last_avg_monthly_KWH": 7, + "last_avg_monthly_cost": 8, + "last_avg_monthly_pct": 9, + "last_avg_monthly_runs": 10, + "last_avg_watts": 4, + "last_current_month_KWH": 11, + "last_current_month_cost": 12, + "last_current_month_runs": 13, "last_icon": 2, + "last_info": 18, "last_last_state": 3, - "last_last_state_time_epoch": 4, - "last_v": 22, - "last_yearly_KWH": 17, - "last_yearly_cost": 18, - "last_yearly_text": 19, - "name": 1 + "last_last_state_time": 19, + "last_sd_current": 20, + "last_sd_energy": 21, + "last_sd_voltage": 22, + "last_sd_watts": 23, + "last_watts": 24, + "last_yearly_KWH": 14, + "last_yearly_cost": 15, + "last_yearly_text": 16 }, "renameByName": { + "device_name": "Device Name", + "last_ao_st": "", "last_ao_w": "Watts (AO)", "last_avg_duration": "Duration", "last_avg_monthly_KWH": "Monthly kWh", @@ -936,12 +1223,67 @@ "name": "Name" } } + }, + { + "id": "calculateField", + "options": { + "alias": "Last State Time", + "binary": { + "left": "last_last_state_time", + "operator": "*", + "right": "1000" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "last_last_state_time": true + }, + "indexByName": { + "Device Name": 0, + "Duration": 5, + "Icon": 1, + "Last State Time": 2, + "Monthly Percent": 7, + "Monthly Runs": 8, + "Monthly kWh": 6, + "State": 3, + "Watts (Avg)": 4, + "last_last_state_time": 9 + }, + "renameByName": {} + } + }, + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "isNull", + "options": {} + }, + "fieldName": "State" + } + ], + "match": "any", + "type": "exclude" + } } ], "type": "table" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { @@ -949,8 +1291,11 @@ }, "custom": { "align": "auto", - "displayMode": "auto", - "filterable": false + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false }, "mappings": [], "thresholds": { @@ -967,19 +1312,23 @@ { "matcher": { "id": "byName", - "options": "Duration (Average)" + "options": "Duration" }, "properties": [ { "id": "unit", "value": "s" + }, + { + "id": "custom.width", + "value": 89 } ] }, { "matcher": { "id": "byName", - "options": "Monthly Percent (Average)" + "options": "Monthly Percent" }, "properties": [ { @@ -989,66 +1338,85 @@ { "id": "decimals", "value": 1 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Watts (Average)" - }, - "properties": [ + }, { - "id": "unit", - "value": "watt" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "KWH (Yearly)" - }, - "properties": [ + "id": "custom.width", + "value": 130 + }, { - "id": "unit", - "value": "kwatth" + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(55, 135, 45, 0)", + "value": null + }, + { + "color": "rgba(69, 135, 95, 0.1)", + "value": 10 + }, + { + "color": "rgba(69, 135, 95, 0.2)", + "value": 20 + }, + { + "color": "rgba(69, 135, 95, 0.3)", + "value": 30 + }, + { + "color": "rgba(69, 135, 95, 0.4)", + "value": 40 + }, + { + "color": "rgba(69, 135, 95, 0.5)", + "value": 50 + }, + { + "color": "rgba(69, 135, 95, 0.6)", + "value": 60 + }, + { + "color": "rgba(69, 135, 95, 0.7)", + "value": 70 + }, + { + "color": "rgba(69, 135, 95, 0.8)", + "value": 80 + }, + { + "color": "rgba(69, 135, 95, 0.9)", + "value": 90 + } + ] + } }, { - "id": "decimals", - "value": 2 + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } } ] }, { "matcher": { "id": "byName", - "options": "Cost" + "options": "Watts (Avg)" }, "properties": [ { "id": "unit", - "value": "currencyUSD" + "value": "watt" }, { "id": "custom.width", - "value": 59 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Cost (Yearly)" - }, - "properties": [ - { - "id": "unit", - "value": "currencyUSD" + "value": 109 }, { "id": "decimals", - "value": 2 + "value": 1 } ] }, @@ -1059,8 +1427,10 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "image" + "id": "custom.cellOptions", + "value": { + "type": "image" + } }, { "id": "mappings", @@ -1150,26 +1520,34 @@ }, { "id": "custom.width", - "value": 55 + "value": 41 } ] }, { "matcher": { "id": "byName", - "options": "Last State Time" + "options": "State Time" }, "properties": [ { "id": "unit", "value": "dateTimeAsUS" + }, + { + "id": "custom.align", + "value": "center" + }, + { + "id": "custom.width", + "value": 183 } ] }, { "matcher": { "id": "byName", - "options": "KWH" + "options": "Monthly kWh" }, "properties": [ { @@ -1182,31 +1560,38 @@ }, { "id": "custom.width", - "value": 70 + "value": 113 } ] }, { "matcher": { "id": "byName", - "options": "Watts (Current)" + "options": "Watts (In Use)" }, "properties": [ { "id": "unit", "value": "watt" + }, + { + "id": "custom.width", + "value": 120 } ] }, { "matcher": { "id": "byName", - "options": "Last State" + "options": "State" }, "properties": [ { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } }, { "id": "mappings", @@ -1214,15 +1599,15 @@ { "options": { "Idle": { - "color": "light-blue", + "color": "#77d38e", "index": 2 }, "Off": { - "color": "dark-blue", + "color": "#707070", "index": 1 }, "On": { - "color": "dark-red", + "color": "#e35431", "index": 0 } }, @@ -1243,6 +1628,54 @@ { "id": "custom.align", "value": "center" + }, + { + "id": "custom.width", + "value": 55 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Monthly Runs" + }, + "properties": [ + { + "id": "custom.width", + "value": 119 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Last State Time" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeAsUS" + }, + { + "id": "custom.align", + "value": "center" + }, + { + "id": "custom.width", + "value": 229 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Device Name" + }, + "properties": [ + { + "id": "custom.width", + "value": 231 } ] }, @@ -1254,7 +1687,19 @@ "properties": [ { "id": "custom.width", - "value": 66 + "value": 59 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Name" + }, + "properties": [ + { + "id": "custom.width", + "value": 145 } ] } @@ -1267,9 +1712,18 @@ "y": 16 }, "hideTimeOverride": true, - "id": 13, + "id": 25, "maxDataPoints": 1, "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, "showHeader": true, "sortBy": [ { @@ -1278,16 +1732,20 @@ } ] }, - "pluginVersion": "8.0.6", + "pluginVersion": "11.0.0", "targets": [ { - "query": "SELECT last(*) FROM \"sense_devices\" WHERE (\"name\" =~ /^$devices$/) and $timeFilter GROUP BY time(1d), \"name\" fill(null)", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "query": "SELECT last(*) FROM \"sense_devices\" WHERE $timeFilter GROUP BY time(30d), \"device_name\"::tag fill(null)", "rawQuery": true, "refId": "A", "resultFormat": "table" } ], - "timeFrom": "1m", + "timeFrom": "30d", "title": "Device Details - Current Month", "transformations": [ { @@ -1295,82 +1753,89 @@ "options": { "excludeByName": { "Time": true, + "last_ao_st": true, "last_ao_w": true, "last_avg_duration": true, "last_avg_monthly_KWH": true, "last_avg_monthly_cost": true, "last_avg_monthly_pct": true, - "last_avg_monthly_runs": true, "last_avg_watts": true, - "last_current_ao_wattage": true, "last_current_month_KWH": true, "last_current_month_cost": true, - "last_current_month_runs": false, - "last_current_watts": true, - "last_e": true, - "last_i": true, - "last_icon": false, + "last_current_month_runs": true, + "last_info": true, "last_last_state": true, - "last_last_state_time_epoch": true, - "last_v": true, - "last_watts": false, + "last_last_state_time": true, + "last_sd_current": true, + "last_sd_energy": true, + "last_sd_voltage": true, + "last_sd_watts": true, + "last_watts": true, "last_yearly_KWH": true, "last_yearly_cost": true, "last_yearly_text": true }, "indexByName": { "Time": 0, - "last_avg_duration": 4, - "last_avg_monthly_KWH": 5, - "last_avg_monthly_cost": 6, - "last_avg_monthly_pct": 7, - "last_avg_monthly_runs": 8, - "last_avg_watts": 9, - "last_current_ao_wattage": 10, + "device_name": 1, + "last_ao_st": 4, + "last_ao_w": 5, + "last_avg_duration": 6, + "last_avg_monthly_KWH": 7, + "last_avg_monthly_cost": 8, + "last_avg_monthly_pct": 9, + "last_avg_monthly_runs": 3, + "last_avg_watts": 10, "last_current_month_KWH": 11, "last_current_month_cost": 12, "last_current_month_runs": 13, - "last_current_watts": 3, "last_icon": 2, - "last_last_state": 14, - "last_last_state_time_epoch": 15, - "last_yearly_KWH": 16, - "last_yearly_cost": 17, - "last_yearly_text": 18, - "name": 1 + "last_info": 14, + "last_last_state": 15, + "last_last_state_time": 16, + "last_sd_current": 17, + "last_sd_energy": 18, + "last_sd_voltage": 19, + "last_sd_watts": 20, + "last_watts": 21, + "last_yearly_KWH": 22, + "last_yearly_cost": 23, + "last_yearly_text": 24 }, "renameByName": { - "last_ao_w": "Watts (AO)", - "last_avg_duration": "Duration (Average)", - "last_avg_monthly_KWH": "Monthly KWH (Average)", - "last_avg_monthly_cost": "Monthly Cost (Average)", - "last_avg_monthly_pct": "Monthly Percent (Average)", - "last_avg_monthly_runs": "Monthly Runs (Average)", - "last_avg_watts": "Watts (Average)", - "last_current_ao_wattage": "Always On Wattage (Average)", - "last_current_month_KWH": "KWH", - "last_current_month_cost": "Cost", - "last_current_month_runs": "Runs", - "last_current_watts": "Watts (Current)", - "last_e": "Who Knows", - "last_i": "Amps", - "last_icon": "Icon", - "last_last_state": "Last State", - "last_last_state_time_epoch": "Last State Time", - "last_v": "Volts", - "last_watts": "Watts", - "last_yearly_KWH": "KWH (Yearly)", - "last_yearly_cost": "Cost (Yearly)", - "last_yearly_text": "Text (Yearly)", - "name": "Name" + "device_name": "Name", + "last_ao_st": "", + "last_avg_monthly_runs": "Runs", + "last_avg_watts": "", + "last_current_month_runs": "", + "last_icon": "Icon" } } + }, + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "isNull", + "options": {} + }, + "fieldName": "Runs" + } + ], + "match": "all", + "type": "exclude" + } } ], "type": "table" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { @@ -1378,8 +1843,11 @@ }, "custom": { "align": "auto", - "displayMode": "auto", - "filterable": false + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false }, "mappings": [], "thresholds": { @@ -1421,18 +1889,6 @@ } ] }, - { - "matcher": { - "id": "byName", - "options": "Watts (Average)" - }, - "properties": [ - { - "id": "unit", - "value": "watt" - } - ] - }, { "matcher": { "id": "byName", @@ -1449,7 +1905,7 @@ }, { "id": "custom.width", - "value": 94 + "value": 101 } ] }, @@ -1492,8 +1948,10 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "image" + "id": "custom.cellOptions", + "value": { + "type": "image" + } }, { "id": "mappings", @@ -1583,7 +2041,7 @@ }, { "id": "custom.width", - "value": 55 + "value": 32 } ] }, @@ -1634,8 +2092,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } }, { "id": "mappings", @@ -1674,6 +2135,18 @@ "value": "center" } ] + }, + { + "matcher": { + "id": "byName", + "options": "Name" + }, + "properties": [ + { + "id": "custom.width", + "value": 177 + } + ] } ] }, @@ -1687,6 +2160,15 @@ "id": 14, "maxDataPoints": 1, "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, "showHeader": true, "sortBy": [ { @@ -1695,16 +2177,20 @@ } ] }, - "pluginVersion": "8.0.6", + "pluginVersion": "11.0.0", "targets": [ { - "query": "SELECT last(*) FROM \"sense_devices\" WHERE (\"name\" =~ /^$devices$/) and $timeFilter GROUP BY time(1d), \"name\" fill(null)", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "query": "SELECT last(*) FROM \"sense_devices\" WHERE $timeFilter GROUP BY time(30d), \"device_name\"::tag fill(null)", "rawQuery": true, "refId": "A", "resultFormat": "table" } ], - "timeFrom": "1m", + "timeFrom": "30d", "title": "Device Details - Yearly", "transformations": [ { @@ -1712,6 +2198,7 @@ "options": { "excludeByName": { "Time": true, + "last_ao_st": true, "last_ao_w": true, "last_avg_duration": true, "last_avg_monthly_KWH": true, @@ -1719,76 +2206,57 @@ "last_avg_monthly_pct": true, "last_avg_monthly_runs": true, "last_avg_watts": true, - "last_current_ao_wattage": true, "last_current_month_KWH": true, "last_current_month_cost": true, "last_current_month_runs": true, - "last_current_watts": true, - "last_e": true, - "last_i": true, - "last_icon": false, + "last_info": true, "last_last_state": true, - "last_last_state_time_epoch": true, - "last_v": true, - "last_watts": false, - "last_yearly_KWH": false, - "last_yearly_cost": false, - "last_yearly_text": false - }, - "indexByName": { - "Time": 0, - "last_avg_duration": 4, - "last_avg_monthly_KWH": 5, - "last_avg_monthly_cost": 6, - "last_avg_monthly_pct": 7, - "last_avg_monthly_runs": 8, - "last_avg_watts": 9, - "last_current_ao_wattage": 10, - "last_current_month_KWH": 11, - "last_current_month_cost": 12, - "last_current_month_runs": 13, - "last_current_watts": 3, - "last_icon": 2, - "last_last_state": 14, - "last_last_state_time_epoch": 15, - "last_yearly_KWH": 16, - "last_yearly_cost": 17, - "last_yearly_text": 18, - "name": 1 + "last_last_state_time": true, + "last_sd_current": true, + "last_sd_energy": true, + "last_sd_voltage": true, + "last_sd_watts": true, + "last_watts": true }, + "indexByName": {}, "renameByName": { - "last_ao_w": "Watts (AO)", - "last_avg_duration": "Duration (Average)", - "last_avg_monthly_KWH": "Monthly KWH (Average)", - "last_avg_monthly_cost": "Monthly Cost (Average)", - "last_avg_monthly_pct": "Monthly Percent (Average)", - "last_avg_monthly_runs": "Monthly Runs (Average)", - "last_avg_watts": "Watts (Average)", - "last_current_ao_wattage": "Always On Wattage (Average)", - "last_current_month_KWH": "KWH (Current Month)", - "last_current_month_cost": "Cost (Current Month)", - "last_current_month_runs": "Runs (Current Month)", - "last_current_watts": "Watts (Current)", - "last_e": "Who Knows", - "last_i": "Amps", + "device_name": "Name", + "last_ao_st": "", "last_icon": "Icon", - "last_last_state": "Last State", - "last_last_state_time_epoch": "Last State Time", - "last_v": "Volts", - "last_watts": "Watts", + "last_info": "Info", + "last_last_state": "", + "last_watts": "", "last_yearly_KWH": "kWh", "last_yearly_cost": "Cost", - "last_yearly_text": "Text", - "name": "Name" + "last_yearly_text": "Text" } } + }, + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "isNull", + "options": {} + }, + "fieldName": "Cost" + } + ], + "match": "any", + "type": "exclude" + } } ], "type": "table" }, { - "collapsed": true, - "datasource": null, + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" + }, "gridPos": { "h": 1, "w": 24, @@ -1796,395 +2264,446 @@ "y": 28 }, "id": 17, - "panels": [ + "panels": [], + "targets": [ { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 2, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "links": [], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "volt" - }, - "overrides": [] + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" }, - "gridPos": { - "h": 15, - "w": 8, - "x": 0, - "y": 29 + "refId": "A" + } + ], + "title": "Plug Details", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" }, - "id": 15, - "interval": "$interval", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false }, - "tooltip": { - "mode": "multi" + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } }, - "pluginVersion": "8.0.4", - "repeat": null, - "targets": [ + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "volt" + }, + "overrides": [] + }, + "gridPos": { + "h": 15, + "w": 8, + "x": 0, + "y": 29 + }, + "id": 15, + "interval": "$interval", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.4", + "targets": [ + { + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "groupBy": [ { - "alias": "$tag_name", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "name" - ], - "type": "tag" - } + "params": [ + "$__interval" ], - "measurement": "sense_devices", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"value\") FROM \"devices\" WHERE (\"name\" =~ /^$Device$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", - "rawQuery": false, - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "v" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - } - ] + "type": "time" + }, + { + "params": [ + "device_name::tag" ], - "tags": [ - { - "key": "name", - "operator": "=~", - "value": "/^$plugs$/" - } - ] + "type": "tag" } ], - "timeFrom": null, - "timeShift": null, - "title": "Volts By Plugs", - "type": "timeseries" - }, - { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 2, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "links": [], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] + "measurement": "sense_devices", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"value\") FROM \"devices\" WHERE (\"name\" =~ /^$Device$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", + "rawQuery": false, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "sd_voltage" + ], + "type": "field" }, - "unit": "amp" + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "device_name::tag", + "operator": "=~", + "value": "/^$plugs$/" + } + ] + } + ], + "title": "Volts By Plugs", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false }, - "overrides": [] + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, - "gridPos": { - "h": 15, - "w": 8, - "x": 8, - "y": 29 + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] }, - "id": 18, - "interval": "$interval", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" + "unit": "watt" + }, + "overrides": [] + }, + "gridPos": { + "h": 15, + "w": 8, + "x": 8, + "y": 29 + }, + "id": 26, + "interval": "$interval", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.4", + "targets": [ + { + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "device_name::tag" + ], + "type": "tag" + } + ], + "measurement": "sense_devices", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"value\") FROM \"devices\" WHERE (\"name\" =~ /^$Device$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", + "rawQuery": false, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "sd_watts" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "device_name::tag", + "operator": "=~", + "value": "/^$plugs$/" + } + ] + } + ], + "title": "Watts By Plugs", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" }, - "tooltip": { - "mode": "multi" + "thresholdsStyle": { + "mode": "off" } }, - "pluginVersion": "8.0.4", - "targets": [ - { - "alias": "$tag_name", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "name" - ], - "type": "tag" - } - ], - "measurement": "sense_devices", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"value\") FROM \"devices\" WHERE (\"name\" =~ /^$Device$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", - "rawQuery": false, - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "i" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - } - ] - ], - "tags": [ - { - "key": "name", - "operator": "=~", - "value": "/^$plugs$/" - } - ] - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Amps By Plugs", - "type": "timeseries" + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "voltamp" + }, + "overrides": [] + }, + "gridPos": { + "h": 15, + "w": 8, + "x": 16, + "y": 29 + }, + "id": 27, + "interval": "$interval", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.4", + "targets": [ { - "datasource": "${DS_INFLUXDB_- SENSE}", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 2, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "links": [], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 15, - "w": 8, - "x": 16, - "y": 29 - }, - "id": 19, - "interval": "$interval", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom" - }, - "tooltip": { - "mode": "multi" - } + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" }, - "pluginVersion": "8.0.4", - "targets": [ + "groupBy": [ { - "alias": "$tag_name", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "name" - ], - "type": "tag" - } + "params": [ + "$__interval" ], - "measurement": "sense_devices", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT mean(\"value\") FROM \"devices\" WHERE (\"name\" =~ /^$Device$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", - "rawQuery": false, - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "e" - ], - "type": "field" - }, - { - "params": [], - "type": "mean" - } - ] + "type": "time" + }, + { + "params": [ + "device_name::tag" ], - "tags": [ - { - "key": "name", - "operator": "=~", - "value": "/^$plugs$/" - } - ] + "type": "tag" } ], - "timeFrom": null, - "timeShift": null, - "title": "Who Knows By Plugs", - "type": "timeseries" + "measurement": "sense_devices", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"value\") FROM \"devices\" WHERE (\"name\" =~ /^$Device$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", + "rawQuery": false, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "sd_current" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "device_name::tag", + "operator": "=~", + "value": "/^$plugs$/" + } + ] } ], - "repeat": null, - "title": "Plug Details", - "type": "row" + "title": "Amps By Plugs", + "type": "timeseries" }, { "collapsed": true, - "datasource": null, + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" + }, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 29 + "y": 44 }, "id": 23, "panels": [ { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -2196,6 +2715,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, @@ -2217,8 +2737,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2234,22 +2753,29 @@ "h": 10, "w": 12, "x": 0, - "y": 30 + "y": 45 }, "id": 21, "options": { "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { - "mode": "multi" + "maxHeight": 600, + "mode": "multi", + "sort": "none" } }, "targets": [ { - "alias": "$tag_name", + "alias": "$tag_device_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -2259,15 +2785,9 @@ }, { "params": [ - "name" + "device_name::tag" ], "type": "tag" - }, - { - "params": [ - "null" - ], - "type": "fill" } ], "measurement": "sense_devices", @@ -2279,7 +2799,7 @@ [ { "params": [ - "ao_w" + "always_on_watts" ], "type": "field" }, @@ -2289,26 +2809,28 @@ } ] ], - "tags": [ - { - "key": "always_on", - "operator": "=", - "value": "true" - } - ] + "tags": [] } ], "title": "Always On Watts (Stacked)", "type": "timeseries" } ], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" + }, + "refId": "A" + } + ], "title": "Always On Devices", "type": "row" } ], - "refresh": "1m", - "schemaVersion": 30, - "style": "dark", + "refresh": "", + "schemaVersion": 39, "tags": [ "influxdb", "sense-collector" @@ -2316,19 +2838,38 @@ "templating": { "list": [ { - "allValue": null, + "current": { + "selected": false, + "text": "InfluxDB - influxdb02 - Tylephony - sense", + "value": "PDE3CD461864125BE" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "data_source", + "options": [], + "query": "influxdb", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", - "definition": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"name\"", - "description": null, - "error": null, + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "definition": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"device_name\"", "hide": 0, "includeAll": true, "label": "Device", "multi": true, "name": "devices", "options": [], - "query": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"name\"", + "query": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"device_name\"", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2339,19 +2880,19 @@ "useTags": false }, { - "allValue": null, "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", - "definition": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"name\" WHERE \"is_plug\" = 'true'", - "description": null, - "error": null, + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "definition": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"device_name\" WHERE \"is_plug\" = 'true'", "hide": 0, "includeAll": true, "label": "Plugs", "multi": true, "name": "plugs", "options": [], - "query": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"name\" WHERE \"is_plug\" = 'true'", + "query": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"device_name\" WHERE \"is_plug\" = 'true'", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -2361,6 +2902,71 @@ "type": "query", "useTags": false }, + { + "auto": false, + "auto_count": 30, + "auto_min": "10s", + "current": { + "selected": false, + "text": "2d", + "value": "2d" + }, + "hide": 0, + "label": "Expand Events", + "name": "expand_from_events", + "options": [ + { + "selected": false, + "text": "5m", + "value": "5m" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": true, + "text": "2d", + "value": "2d" + }, + { + "selected": false, + "text": "3d", + "value": "3d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "5m,1h,12h,1d,2d,3d,7d,14d,30d", + "queryValue": "", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + }, { "auto": true, "auto_count": 4, @@ -2370,8 +2976,6 @@ "text": "1m", "value": "1m" }, - "description": null, - "error": null, "hide": 0, "label": "Interval", "name": "interval", @@ -2446,9 +3050,10 @@ ] }, "time": { - "from": "now/d", + "from": "now-3h", "to": "now" }, + "timeRangeUpdatedDuringEditOrView": false, "timepicker": { "refresh_intervals": [ "5s", @@ -2466,6 +3071,6 @@ "timezone": "", "title": "Sense Collector - Device Overview", "uid": "lux4rd0labs_sense_02", - "version": 1, - "description": "Sense Collector provides a way of collecting real-time data from the Sense Energy Monitor. These Grafana dashboards offer visualizations for detected devices and smart plugs and their wattage, voltage, and amp utilization." + "version": 64, + "weekStart": "" } \ No newline at end of file diff --git a/grafana/shared/sense_collector-mains_overview.json b/grafana/shared/sense_collector-mains_overview.json index 9967bf5..80d32b2 100644 --- a/grafana/shared/sense_collector-mains_overview.json +++ b/grafana/shared/sense_collector-mains_overview.json @@ -1,20 +1,29 @@ { "__inputs": [ { - "name": "DS_INFLUXDB_- SENSE", - "label": "InfluxDB - sense", + "name": "DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE", + "label": "InfluxDB - influxdb02 - Tylephony - sense", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + }, + { + "name": "DS_INFLUXDB_- BUILD01 - TYLE - SENSE-COLLECTOR", + "label": "InfluxDB - build01 - Tyle - sense-collector", "description": "", "type": "datasource", "pluginId": "influxdb", "pluginName": "InfluxDB" } ], + "__elements": {}, "__requires": [ { "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "8.0.6" + "version": "11.0.0" }, { "type": "datasource", @@ -22,6 +31,12 @@ "name": "InfluxDB", "version": "1.0.0" }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, { "type": "panel", "id": "timeseries", @@ -33,7 +48,10 @@ "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -47,45 +65,88 @@ "type": "dashboard" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- BUILD01 - TYLE - SENSE-COLLECTOR}" + }, "enable": false, "iconColor": "#e35431", "name": "Device Status - On", "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"event_type\" = 'new_timeline') and $timeFilter and device_state='DeviceOn'", "tagsColumn": "device_state", + "target": { + "fromAnnotations": true, + "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"device_name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceOn'", + "rawQuery": true, + "refId": "Anno", + "tagsColumn": "device_state", + "textColumn": "body", + "textEditor": true + }, "textColumn": "body" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- BUILD01 - TYLE - SENSE-COLLECTOR}" + }, "enable": false, "iconColor": "#b7b7b7", "name": "Device Status - Off", "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"event_type\" = 'new_timeline') and $timeFilter and device_state='DeviceOff'", "tagsColumn": "device_state", + "target": { + "fromAnnotations": true, + "name": "Device Status - Off", + "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"device_name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceOff'", + "queryType": "tags", + "rawQuery": true, + "refId": "", + "tagsColumn": "device_state", + "textColumn": "body", + "textEditor": true, + "timeEndColumn": "", + "titleColumn": "" + }, "textColumn": "body" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- BUILD01 - TYLE - SENSE-COLLECTOR}" + }, "enable": false, "iconColor": "#77d38e", "name": "Device Status - Idle", "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"event_type\" = 'new_timeline') and $timeFilter and device_state='DeviceIdle'", "tagsColumn": "device_state", + "target": { + "fromAnnotations": true, + "name": "Device Status - Idle", + "query": "SELECT body,device_state FROM \"sense_event\" WHERE (\"device_name\" =~ /^$devices$/) and $timeFilter and device_state='DeviceIdle'", + "queryType": "tags", + "rawQuery": true, + "refId": "", + "tagsColumn": "device_state", + "textColumn": "body", + "textEditor": true, + "timeEndColumn": "", + "titleColumn": "" + }, "textColumn": "body" } ] }, "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, - "iteration": 1626665955972, "links": [ { "asDropdown": true, "icon": "external link", - "includeVars": true, - "keepTime": true, + "includeVars": false, + "keepTime": false, "tags": [ "sense-collector" ], @@ -96,43 +157,22 @@ "url": "" } ], + "liveNow": false, "panels": [ { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, "custom": { - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 20, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "smooth", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "normal" + "align": "auto", + "cellOptions": { + "type": "auto" }, - "thresholdsStyle": { - "mode": "off" - } + "inspect": false }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", @@ -140,159 +180,379 @@ { "color": "green", "value": null + }, + { + "color": "red", + "value": 80 } ] - }, - "unit": "watt" + } }, "overrides": [ { "matcher": { "id": "byName", - "options": "L1" + "options": "Time" }, "properties": [ { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } + "id": "unit", + "value": "dateTimeAsUSNoDateIfToday" + }, + { + "id": "custom.width", + "value": 96 } ] }, { "matcher": { "id": "byName", - "options": "L2" + "options": "Icon" }, "properties": [ { - "id": "color", + "id": "mappings", + "value": [] + }, + { + "id": "custom.cellOptions", "value": { - "fixedColor": "red", - "mode": "fixed" + "type": "image" } + }, + { + "id": "mappings", + "value": [ + { + "options": { + "ac": { + "index": 0, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04NC43LDUzLjdsLTE0LjgsNGwtNC4yLTIuNGMwLjYtMS43LDAuOS0zLjQsMC45LTUuMnMtMC40LTMuNi0wLjktNS4ybDQuMi0yLjRsMTQuOCw0bDEuNy02LjRsLTguMy0yLjIKCWw4LjItNC43TDgzLDI3LjFsLTguMiw0LjdsMi4yLTguM2wtNi40LTEuN2wtNCwxNC44TDYyLjQsMzljLTIuMy0yLjYtNS41LTQuNi05LjEtNS4zdi00LjhMNjQuMSwxOGwtNC43LTQuN2wtNi4xLDYuMVYxMGgtNi43djkuNAoJbC02LjEtNi4xTDM1LjksMThsMTAuOCwxMC44djQuOGMtMy42LDAuNy02LjcsMi43LTkuMSw1LjNsLTQuMi0yLjRsLTQtMTQuOEwyMywyMy41bDIuMiw4LjNMMTcsMjcuMWwtMy4zLDUuOGw4LjIsNC43bC04LjMsMi4yCglsMS43LDYuNGwxNC44LTRsNC4yLDIuNGMtMC42LDEuNy0wLjksMy40LTAuOSw1LjJzMC40LDMuNiwwLjksNS4yTDMwLDU3LjdsLTE0LjgtNGwtMS43LDYuNGw4LjMsMi4ybC04LjIsNC43bDMuMyw1LjhsOC4yLTQuNwoJTDIzLDc2LjVsNi40LDEuN2w0LTE0LjhsNC4yLTIuNGMyLjMsMi42LDUuNSw0LjYsOS4xLDUuM3Y0LjhMMzUuOSw4Mmw0LjcsNC43bDYuMS02LjFWOTBoNi43di05LjRsNi4xLDYuMWw0LjctNC43TDUzLjMsNzEuMXYtNC44CgljMy42LTAuNyw2LjctMi43LDkuMS01LjNsNC4yLDIuNGw0LDE0LjhsNi40LTEuN2wtMi4yLTguM2w4LjIsNC43bDMuMy01LjhsLTguMi00LjdsOC4zLTIuMkw4NC43LDUzLjd6Ii8+Cjwvc3ZnPgo=" + }, + "always_on": { + "index": 1, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04My4zLDEwSDE2LjdDMTMsMTAsMTAsMTMsMTAsMTYuN3Y2Ni43YzAsMy43LDMsNi43LDYuNyw2LjdoNjYuN2MzLjcsMCw2LjctMyw2LjctNi43VjE2LjcKCUM5MCwxMyw4NywxMCw4My4zLDEweiBNNzguMyw4MGgtNi43di05LjJDNjYuMiw3Ni40LDU4LjUsODAsNTAsODBjLTE2LjYsMC0zMC0xMy40LTMwLTMwaDYuN2MwLDEyLjksMTAuNCwyMy4zLDIzLjMsMjMuMwoJYzYuNiwwLDEyLjYtMi44LDE2LjktNy4yaC05LjF2LTYuN2wxNy4yLDB2MGMxLjgsMCwzLjMsMS41LDMuMywzLjNWODB6IE03My4zLDUwYzAtMTIuOS0xMC40LTIzLjMtMjMuMy0yMy4zCgljLTYuNiwwLTEyLjYsMi44LTE2LjksNy4yaDkuMnY2LjdsLTE3LjIsMGwwLDBjLTEuOCwwLTMuMy0xLjUtMy4zLTMuM1YyMGg2LjdsMCw5LjJDMzMuOCwyMy42LDQxLjUsMjAsNTAsMjBjMTYuNiwwLDMwLDEzLjQsMzAsMzAKCUg3My4zeiIvPgo8L3N2Zz4K" + }, + "car": { + "index": 2, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cgkuc3Qxe2ZpbGw6IzQ2ODg2MDt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04My4zLDU2LjdsLTUuNi0xNi44Yy0wLjYtMS43LTIuNi0zLjItNC40LTMuMkg0Ni43bC0xMy4zLDIwbC0yMC4zLDguN0MxMS40LDY2LjEsMTAsNjguMiwxMCw3MHYxMy4zaDEwLjYKCWMxLjQsMy45LDUsNi43LDkuNCw2LjdzOC0yLjgsOS40LTYuN2gyNC42YzEuNCwzLjksNSw2LjcsOS40LDYuN2M0LjMsMCw4LTIuOCw5LjQtNi43SDkwdi0yMEM5MCw1OS43LDg3LDU2LjcsODMuMyw1Ni43eiBNMzAsODMuMwoJYy0xLjgsMC0zLjMtMS41LTMuMy0zLjNzMS41LTMuMywzLjMtMy4zczMuMywxLjUsMy4zLDMuM1MzMS44LDgzLjMsMzAsODMuM3ogTTU2LjcsNTYuN0g0MS4zbDguOS0xMy4zaDYuNEw1Ni43LDU2LjdMNTYuNyw1Ni43egoJIE02My4zLDU2LjdWNDMuM2g4LjVsNC40LDEzLjNINjMuM3ogTTczLjMsODMuM2MtMS44LDAtMy4zLTEuNS0zLjMtMy4zczEuNS0zLjMsMy4zLTMuM2MxLjgsMCwzLjMsMS41LDMuMywzLjNTNzUuMiw4My4zLDczLjMsODMuMwoJeiIvPgo8cGF0aCBjbGFzcz0ic3QxIiBkPSJNODMuMywxMHY4LjNjMCwxLjgtMS41LDMuMy0zLjMsMy4zSDM2Ljd2LTVjMC0zLjctMy02LjctNi43LTYuN0gyMHY1LjhoLTYuN2MtMS44LDAtMy4zLDEuNS0zLjMsMy4zCgljMCwxLjgsMS41LDMuMywzLjMsMy4zSDIwdjVoLTYuN2MtMS44LDAtMy4zLDEuNS0zLjMsMy4zYzAsMS44LDEuNSwzLjMsMy4zLDMuM0gyMFY0MGgxMGMzLjcsMCw2LjctMyw2LjctNi43di01SDgwCgljNS41LDAsMTAtNC41LDEwLTEwVjEwSDgzLjN6Ii8+Cjwvc3ZnPgo=" + }, + "computer": { + "index": 3, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qxe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0yMy4zLDgzLjNWOTBoMjBWMjMuM0gxNi43Yy0zLjcsMC02LjcsMy02LjcsNi43djQwYzAsMy43LDMsNi43LDYuNyw2LjdoMjB2Ni43SDIzLjN6Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik04My4zLDEwSDU2LjdDNTMsMTAsNTAsMTMsNTAsMTYuN3Y2Ni43YzAsMy43LDMsNi43LDYuNyw2LjdoMjYuN2MzLjcsMCw2LjctMyw2LjctNi43VjE2LjcKCUM5MCwxMyw4NywxMCw4My4zLDEweiBNNzAsODMuM2MtMS44LDAtMy4zLTEuNS0zLjMtMy4zczEuNS0zLjMsMy4zLTMuM3MzLjMsMS41LDMuMywzLjNTNzEuOCw4My4zLDcwLDgzLjN6IE04My4zLDQzLjNINTYuN3YtNi43CgloMjYuN1Y0My4zeiBNODMuMywzMEg1Ni43di02LjdoMjYuN1YzMHoiLz4KPC9zdmc+Cg==" + }, + "dishes": { + "index": 4, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qxe2ZpbGw6I0UzNTQzMTt9Cgkuc3Qye2ZpbGw6IzcwNzA3MDt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMCwzMHY1My4zYzAsMy43LDMsNi43LDYuNyw2LjdoNjYuN2MzLjcsMCw2LjctMyw2LjctNi43VjMwSDEweiBNNTAsODMuM2MtMTIuOSwwLTIzLjMtMTAuNC0yMy4zLTIzLjMKCVMzNy4xLDM2LjcsNTAsMzYuN2MxMi45LDAsMjMuMywxMC40LDIzLjMsMjMuM1M2Mi45LDgzLjMsNTAsODMuM3oiLz4KPHBhdGggY2xhc3M9InN0MSIgZD0iTTUwLDQzLjNjLTkuMiwwLTE2LjcsNy41LTE2LjcsMTYuN2MwLDkuMiw3LjUsMTYuNywxNi43LDE2LjdjOS4yLDAsMTYuNy03LjUsMTYuNy0xNi43CglDNjYuNyw1MC44LDU5LjIsNDMuMyw1MCw0My4zeiBNNTAsNzBjLTUuNSwwLTEwLTQuNS0xMC0xMGMwLTUuNSw0LjUtMTAsMTAtMTBzMTAsNC41LDEwLDEwQzYwLDY1LjUsNTUuNSw3MCw1MCw3MHoiLz4KPHBhdGggY2xhc3M9InN0MiIgZD0iTTgzLjMsMTBIMTYuN0MxMywxMCwxMCwxMywxMCwxNi43djYuN2gxMHYtNi43aDIwdjYuN2gyMHYtNi43aDYuN3Y2LjdoNi43di02LjdIODB2Ni43aDEwdi02LjcKCUM5MCwxMyw4NywxMCw4My4zLDEweiIvPgo8L3N2Zz4K" + }, + "fridge": { + "index": 5, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik03My4zLDQzLjN2MjBoLTYuN3YtMjBIMjBWODBjMCw1LjUsNC41LDEwLDEwLDEwaDQwYzUuNSwwLDEwLTQuNSwxMC0xMFY0My4zSDczLjN6Ii8+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik02Ni43LDM2LjdWMjMuM2g2Ljd2MTMuM0g4MFYyMGMwLTUuNS00LjUtMTAtMTAtMTBIMzBjLTUuNSwwLTEwLDQuNS0xMCwxMHYxNi43SDY2Ljd6Ii8+Cjwvc3ZnPgo=" + }, + "garage": { + "index": 6, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cgkuc3Qxe2ZpbGw6IzcwNzA3MDt9Cgkuc3Qye2ZpbGw6Izc3RDM4RTt9Cjwvc3R5bGU+Cjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iMTAsMzYuNyAxMCw5MCAyMCw5MCAyMCw0Ni43IDgwLDQ2LjcgODAsOTAgOTAsOTAgOTAsMzYuNyAiLz4KPHBvbHlnb24gY2xhc3M9InN0MSIgcG9pbnRzPSIyNi43LDUzLjMgMjYuNyw3MCA0Ni43LDcwIDQ2LjcsNjMuMyA1My4zLDYzLjMgNTMuMyw3MCA3My4zLDcwIDczLjMsNTMuMyAiLz4KPHBvbHlnb24gY2xhc3M9InN0MiIgcG9pbnRzPSI1MCwxMCAxMCwzMCA5MCwzMCAiLz4KPC9zdmc+Cg==" + }, + "heat": { + "index": 7, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qxe2ZpbGw6Izc3RDM4RTt9Cgkuc3Qye2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik01MCw3NWMtMTIsMC0yNC4yLTIuNS0zMS41LTcuNEMyMy4xLDgwLjYsMzUuNCw5MCw1MCw5MHMyNi45LTkuNCwzMS41LTIyLjRDNzQuMiw3Mi41LDYyLDc1LDUwLDc1eiIvPgo8cGF0aCBjbGFzcz0ic3QxIiBkPSJNNzYuMSw0OC40Yy0xLDQuNC0zLjIsOC41LTYuNCwxMS44bC0xLjUsMS41bC0zNi40LDBsLTEuNS0xLjVjLTMuMy0zLjMtNS41LTcuNC02LjQtMTEuOAoJYy00LjUsMi4zLTcuMiw1LjEtNy4yLDguM0MxNi43LDY0LDMxLjYsNzAsNTAsNzBzMzMuMy02LDMzLjMtMTMuM0M4My4zLDUzLjUsODAuNiw1MC43LDc2LjEsNDguNHoiLz4KPHBhdGggY2xhc3M9InN0MiIgZD0iTTMzLjksNTYuN2MtNy40LTcuNC03LjQtMTkuMywwLTI2LjdjMCw1LjUsNC41LDEwLDEwLDEwdi01LjZjMC05LjUsMy45LTE4LjEsMTAuMS0yNC40YzAsNC44LDEuOCw5LjYsNS41LDEzLjMKCWw2LjcsNi43YzcuNCw3LjQsNy40LDE5LjMsMCwyNi43TDMzLjksNTYuN3oiLz4KPC9zdmc+Cg==" + }, + "modem": { + "index": 8, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qxe2ZpbGw6Izc3RDM4RTt9Cgkuc3Qye2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMyw0MC42Yy0yLTQuNy0zLTkuOS0zLTE1LjNjMC01LjQsMS4xLTEwLjYsMy0xNS4zbDYuMiwyLjZjLTEuNiwzLjktMi41LDguMi0yLjUsMTIuOGMwLDQuNSwwLjksOC44LDIuNSwxMi44CglMMTMsNDAuNnoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTgwLjgsMTIuNmMxLjYsMy45LDIuNSw4LjIsMi41LDEyLjhjMCw0LjUtMC45LDguOC0yLjUsMTIuOGw2LjIsMi42YzItNC43LDMtOS45LDMtMTUuM2MwLTUuNC0xLjEtMTAuNi0zLTE1LjMKCUw4MC44LDEyLjZ6Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik02NS40LDE4LjljMC44LDIsMS4zLDQuMSwxLjMsNi40YzAsMi4zLTAuNSw0LjQtMS4zLDYuNGw2LjIsMi42YzEuMS0yLjgsMS44LTUuOCwxLjgtOC45cy0wLjYtNi4yLTEuOC04LjkKCUw2NS40LDE4Ljl6Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0zNC42LDMxLjdjLTAuOC0yLTEuMy00LjEtMS4zLTYuNGMwLTIuMywwLjUtNC40LDEuMy02LjRsLTYuMi0yLjZjLTEuMSwyLjgtMS44LDUuOC0xLjgsOC45czAuNiw2LjIsMS44LDguOQoJTDM0LjYsMzEuN3oiLz4KPHBhdGggY2xhc3M9InN0MiIgZD0iTTgzLjMsNTBoLTMwVjM0LjdjMy45LTEuNCw2LjctNSw2LjctOS40YzAtNS41LTQuNS0xMC0xMC0xMGMtNS41LDAtMTAsNC41LTEwLDEwYzAsNC40LDIuOCw4LDYuNyw5LjRWNTBoLTMwCglDMTMsNTAsMTAsNTMsMTAsNTYuN3YyMGMwLDMuNywzLDYuNyw2LjcsNi43VjkwaDYuN3YtNi43aDIwdi0yMEg1MHYyMGg2Ljd2LTIwaDYuN3YyMEg3MHYtMjBoNi43djIwVjkwaDYuN3YtNi43CgljMy43LDAsNi43LTMsNi43LTYuN3YtMjBDOTAsNTMsODcsNTAsODMuMyw1MHogTTM2LjcsNzBIMjMuM3YtNi43aDEzLjNWNzB6Ii8+Cjwvc3ZnPgo=" + }, + "outlet": { + "index": 9, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik03Ny42LDIwbC01NS4yLDBjLTE2LjYsMTYuNi0xNi42LDQzLjQsMCw2MGw1NS4yLDBDOTQuMSw2My40LDk0LjEsMzYuNiw3Ny42LDIweiBNMzYuNyw1My4zSDMwdi0yMGg2LjdWNTMuM3oKCSBNNTYuNyw2Ni43SDQzLjNWNjBoMTMuM1Y2Ni43eiBNNzAsNTMuM2gtNi43di0yMEg3MFY1My4zeiIvPgo8L3N2Zz4K" + }, + "settings": { + "index": 10, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04OS44LDUzLjNjMC4xLTEuMSwwLjItMi4yLDAuMi0zLjNjMC0xLjEtMC4xLTIuMi0wLjItMy4zaC02LjdjLTAuMi0yLjItMC42LTQuMy0xLjItNi4zbDYuMi0yLjYKCWMtMC43LTIuMS0xLjYtNC4yLTIuNi02LjFsLTYuMiwyLjZjLTEtMS45LTIuMi0zLjctMy42LTUuM2w0LjctNC43Yy0xLjQtMS43LTMtMy4zLTQuNy00LjdsLTQuNyw0LjdjLTEuNi0xLjQtMy40LTIuNS01LjMtMy42CglsMi42LTYuMmMtMi0xLTQtMS45LTYuMS0yLjZsLTIuNiw2LjJjLTItMC42LTQuMS0xLTYuMy0xLjJ2LTYuN0M1Mi4yLDEwLjEsNTEuMSwxMCw1MCwxMGMtMS4xLDAtMi4yLDAuMS0zLjMsMC4ydjYuNwoJYy0yLjIsMC4yLTQuMywwLjYtNi4zLDEuMmwtMi42LTYuMmMtMi4xLDAuNy00LjIsMS42LTYuMSwyLjZsMi42LDYuMmMtMS45LDEtMy43LDIuMi01LjMsMy42bC00LjctNC43Yy0xLjcsMS40LTMuMywzLTQuNyw0LjcKCWw0LjcsNC43Yy0xLjMsMS43LTIuNSwzLjQtMy42LDUuM2wtNi4yLTIuNmMtMSwyLTEuOSw0LTIuNiw2LjFsNi4yLDIuNmMtMC42LDItMSw0LjEtMS4yLDYuM2gtNi43QzEwLjEsNDcuOCwxMCw0OC45LDEwLDUwCgljMCwxLjEsMC4xLDIuMiwwLjIsMy4zaDYuN2MwLjIsMi4yLDAuNiw0LjMsMS4yLDYuM2wtNi4yLDIuNmMwLjcsMi4xLDEuNiw0LjIsMi42LDYuMWw2LjItMi42aDBjMSwxLjksMi4yLDMuNywzLjYsNS4zbDAsMAoJbC00LjcsNC43YzEuNCwxLjcsMywzLjMsNC43LDQuN2w0LjctNC43YzEuNiwxLjQsMy40LDIuNSw1LjMsMy42djBsLTIuNiw2LjJjMiwxLDQsMS45LDYuMSwyLjZsMi42LTYuMnYwYzIsMC42LDQuMSwxLDYuMywxLjN2Ni43CgljMS4xLDAuMSwyLjIsMC4yLDMuMywwLjJjMS4xLDAsMi4yLTAuMSwzLjMtMC4ydi02LjdjMi4yLTAuMiw0LjMtMC42LDYuMy0xLjNsMi42LDYuMmMyLjEtMC43LDQuMi0xLjYsNi4yLTIuNmwtMi42LTYuMgoJYzEuOS0xLDMuNy0yLjIsNS4zLTMuNmw0LjcsNC43YzEuNy0xLjQsMy4zLTMsNC43LTQuN2wtNC43LTQuN2MxLjQtMS42LDIuNS0zLjQsMy42LTUuM2gwbDYuMiwyLjZjMS0yLDEuOS00LDIuNi02LjFsLTYuMi0yLjYKCWMwLjYtMiwxLTQuMSwxLjMtNi4zTDg5LjgsNTMuM0w4OS44LDUzLjN6IE0zMCw1MGMwLTEwLjYsOC4zLTE5LjMsMTguNy0xOS45TDQwLDUzLjNoMTAuNGwtNS45LDE1LjlDMzYuMSw2Ni44LDMwLDU5LjEsMzAsNTB6CgkgTTUxLjMsNjkuOUw2MCw0Ni43SDQ5LjZsNi0xNS45QzYzLjksMzMuMiw3MCw0MC45LDcwLDUwQzcwLDYwLjYsNjEuNyw2OS4zLDUxLjMsNjkuOXoiLz4KPC9zdmc+Cg==" + }, + "socket": { + "index": 11, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qxe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+Cjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNTguMywyNi43IDU4LjMsMTAgNTEuNywxMCA1MS43LDI2LjcgMzUsMjYuNyAzNSwxMCAyOC4zLDEwIDI4LjMsMjYuNyAxNi43LDI2LjcgMTYuNyw0MCA3MCw0MCAKCTcwLDI2LjcgIi8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik01My4zLDgzLjNjLTMuNywwLTYuNy0zLTYuNy02LjdoMTBsNi43LTMwaC00MGw2LjcsMzBoMTBDNDAsODQsNDYsOTAsNTMuMyw5MGgzMHYtNi43SDUzLjN6Ii8+Cjwvc3ZnPgo=" + }, + "toaster_oven": { + "index": 12, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qxe2ZpbGw6I0UzNTQzMTt9Cgkuc3Qye2ZpbGw6I0I3QjdCNzt9Cgkuc3Qze2ZpbGw6IzcwNzA3MDt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0yMy4zLDQwVjI2LjdjMC0xLjgsMS41LTMuMywzLjMtMy4zaDQwYzEuOCwwLDMuMywxLjUsMy4zLDMuM1Y0MEgyMy4zeiIvPgo8cGF0aCBjbGFzcz0ic3QxIiBkPSJNODMuMyw1NXYtMy4zYzAtMy43LTMtNi43LTYuNy02LjdoLTYwQzEzLDQ1LDEwLDQ4LDEwLDUxLjd2MzEuN2g2LjdWOTBoNi43di02LjdINzBWOTBoNi43di02LjdoNi43di0yMEw5MCw2MAoJdi01SDgzLjN6IE01Ni43LDczLjNjLTEuOCwwLTMuMy0xLjUtMy4zLTMuM2MwLTEuOCwxLjUtMy4zLDMuMy0zLjNjMS44LDAsMy4zLDEuNSwzLjMsMy4zQzYwLDcxLjgsNTguNSw3My4zLDU2LjcsNzMuM3ogTTcwLDczLjMKCWMtMS44LDAtMy4zLTEuNS0zLjMtMy4zYzAtMS44LDEuNS0zLjMsMy4zLTMuM3MzLjMsMS41LDMuMywzLjNDNzMuMyw3MS44LDcxLjgsNzMuMyw3MCw3My4zeiIvPgo8cmVjdCB4PSI0My4zIiB5PSIxMCIgY2xhc3M9InN0MiIgd2lkdGg9IjYuNyIgaGVpZ2h0PSI4LjMiLz4KPHJlY3QgeD0iMzAiIHk9IjEwIiBjbGFzcz0ic3QzIiB3aWR0aD0iNi43IiBoZWlnaHQ9IjguMyIvPgo8cmVjdCB4PSI1Ni43IiB5PSIxMCIgY2xhc3M9InN0MyIgd2lkdGg9IjYuNyIgaGVpZ2h0PSI4LjMiLz4KPC9zdmc+Cg==" + }, + "trash": { + "index": 13, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzcwNzA3MDt9Cgkuc3Qxe2ZpbGw6I0UzNTQzMTt9Cgkuc3Qye2ZpbGw6I0I3QjdCNzt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik05MCwzNi43VjMwYzAtMy43LTMtNi43LTYuNy02LjdIMTYuN2MtMy43LDAtNi43LDMtNi43LDYuN3Y2LjdIOTB6Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNi43LDQzLjN2NDBjMCwzLjcsMyw2LjcsNi43LDYuN2g1My4zYzMuNywwLDYuNy0zLDYuNy02Ljd2LTQwSDE2Ljd6IE0zNi43LDgwSDMwVjUzLjNoNi43VjgweiBNNTMuMyw4MGgtNi43CglWNTMuM2g2LjdWODB6IE03MCw4MGgtNi43VjUzLjNINzBWODB6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02MCwxMEg0MGMtMy43LDAtNi43LDMtNi43LDYuN2gzMy4zQzY2LjcsMTMsNjMuNywxMCw2MCwxMHoiLz4KPC9zdmc+Cg==" + }, + "tv": { + "index": 14, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6Izc3RDM4RTt9Cgkuc3Qxe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qye2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik03MCw3OC4zVjkwaDEzLjNjMy43LDAsNi43LTMsNi43LTYuN3YtNUg3MHoiLz4KPHBhdGggY2xhc3M9InN0MSIgZD0iTTgzLjMsMzYuN0g3MHYzNi43aDIwdi0zMEM5MCwzOS43LDg3LDM2LjcsODMuMywzNi43eiBNODAsNjMuM2MtMS44LDAtMy4zLTEuNS0zLjMtMy4zczEuNS0zLjMsMy4zLTMuMwoJYzEuOCwwLDMuMywxLjUsMy4zLDMuM1M4MS44LDYzLjMsODAsNjMuM3ogTTgwLDUzLjNjLTEuOCwwLTMuMy0xLjUtMy4zLTMuM3MxLjUtMy4zLDMuMy0zLjNjMS44LDAsMy4zLDEuNSwzLjMsMy4zCglTODEuOCw1My4zLDgwLDUzLjN6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02OCwxNC43TDYzLjMsMTBMNDAsMzMuM0wxNi43LDEwTDEyLDE0LjdsMjEuOSwyMkgxNi43Yy0zLjcsMC02LjcsMy02LjcsNi43djQwYzAsMy43LDMsNi43LDYuNyw2LjdINjVWMzYuNwoJSDQ2LjFMNjgsMTQuN3oiLz4KPC9zdmc+Cg==" + }, + "voice_assistant": { + "index": 15, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik01MCwxMEwyMCwyNi43djQ2LjdMNTAsOTBsMzAtMTYuN1YyNi43TDUwLDEweiBNNDYuNyw4MC41bC0yMC0xMS4xdi00LjdsMjAsMTEuMVY4MC41eiBNNDYuNyw2OC4ybC0yMC0xMS4xCgl2LTcuNmwyMCwxMS4xVjY4LjJ6IE00Ni43LDUyLjlsLTIwLTExLjF2LTcuNmwyMCwxMS4xVjUyLjl6IE01MCwzNC44Yy01LjUsMC0xMC0yLjgtMTAtNi4yYzAtMy41LDQuNS02LjIsMTAtNi4yCgljNS41LDAsMTAsMi44LDEwLDYuMkM2MCwzMiw1NS41LDM0LjgsNTAsMzQuOHogTTY2LjcsNTMuMUw2MCw1Ni44di03LjZsNi43LTMuN1Y1My4xeiIvPgo8L3N2Zz4K" + }, + "washer": { + "index": 16, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzQ2ODg2MDt9Cgkuc3Qxe2ZpbGw6I0UzNTQzMTt9Cgkuc3Qye2ZpbGw6IzcwNzA3MDt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMCwzMHY1My4zYzAsMy43LDMsNi43LDYuNyw2LjdoNjYuN2MzLjcsMCw2LjctMyw2LjctNi43VjMwSDEweiBNNTAsODMuM2MtMTIuOSwwLTIzLjMtMTAuNC0yMy4zLTIzLjMKCVMzNy4xLDM2LjcsNTAsMzYuN2MxMi45LDAsMjMuMywxMC40LDIzLjMsMjMuM1M2Mi45LDgzLjMsNTAsODMuM3oiLz4KPHBhdGggY2xhc3M9InN0MSIgZD0iTTUwLDQzLjNjLTkuMiwwLTE2LjcsNy41LTE2LjcsMTYuN2MwLDkuMiw3LjUsMTYuNywxNi43LDE2LjdjOS4yLDAsMTYuNy03LjUsMTYuNy0xNi43CglDNjYuNyw1MC44LDU5LjIsNDMuMyw1MCw0My4zeiBNNTAsNzBjLTUuNSwwLTEwLTQuNS0xMC0xMGMwLTUuNSw0LjUtMTAsMTAtMTBzMTAsNC41LDEwLDEwQzYwLDY1LjUsNTUuNSw3MCw1MCw3MHoiLz4KPHBhdGggY2xhc3M9InN0MiIgZD0iTTgzLjMsMTBIMTYuN0MxMywxMCwxMCwxMywxMCwxNi43djYuN2gxMHYtNi43aDIwdjYuN2gyMHYtNi43aDYuN3Y2LjdoNi43di02LjdIODB2Ni43aDEwdi02LjcKCUM5MCwxMyw4NywxMCw4My4zLDEweiIvPgo8L3N2Zz4K" + }, + "water_heater": { + "index": 17, + "text": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjMuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxMDAgMTAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzcwNzA3MDt9Cgkuc3Qxe2ZpbGw6I0I3QjdCNzt9Cgkuc3Qye2ZpbGw6Izc3RDM4RTt9Cgkuc3Qze2ZpbGw6IzQ2ODg2MDt9Cgkuc3Q0e2ZpbGw6I0UzNTQzMTt9Cjwvc3R5bGU+CjxyZWN0IHg9IjI2LjciIHk9IjcwIiBjbGFzcz0ic3QwIiB3aWR0aD0iNi43IiBoZWlnaHQ9IjIwIi8+CjxyZWN0IHg9IjI2LjciIHk9IjUwIiBjbGFzcz0ic3QxIiB3aWR0aD0iNi43IiBoZWlnaHQ9IjEzLjMiLz4KPHJlY3QgeD0iNDAiIHk9IjUwIiBjbGFzcz0ic3QyIiB3aWR0aD0iNi43IiBoZWlnaHQ9IjIwIi8+CjxyZWN0IHg9IjQwIiB5PSI3Ni43IiBjbGFzcz0ic3QxIiB3aWR0aD0iNi43IiBoZWlnaHQ9IjYuNyIvPgo8cmVjdCB4PSI1My4zIiB5PSI1MCIgY2xhc3M9InN0MCIgd2lkdGg9IjYuNyIgaGVpZ2h0PSI2LjciLz4KPHJlY3QgeD0iNTMuMyIgeT0iNjMuMyIgY2xhc3M9InN0MSIgd2lkdGg9IjYuNyIgaGVpZ2h0PSIxMy4zIi8+CjxyZWN0IHg9IjUzLjMiIHk9IjgzLjMiIGNsYXNzPSJzdDIiIHdpZHRoPSI2LjciIGhlaWdodD0iNi43Ii8+CjxyZWN0IHg9IjY2LjciIHk9IjUwIiBjbGFzcz0ic3QzIiB3aWR0aD0iNi43IiBoZWlnaHQ9IjEzLjMiLz4KPHJlY3QgeD0iNjYuNyIgeT0iNzAiIGNsYXNzPSJzdDEiIHdpZHRoPSI2LjciIGhlaWdodD0iMjAiLz4KPHBhdGggY2xhc3M9InN0NCIgZD0iTTUzLjMsMjAuM1YxMGgtNi43djEwLjNjLTExLjMsMS42LTIwLDExLjMtMjAsMjMuMWg0Ni43QzczLjMsMzEuNiw2NC42LDIxLjksNTMuMywyMC4zeiIvPgo8L3N2Zz4K" + } + }, + "type": "value" + } + ] + }, + { + "id": "custom.width", + "value": 33 + }, + { + "id": "custom.align", + "value": "center" } ] }, { "matcher": { "id": "byName", - "options": "Total" + "options": "State" }, "properties": [ { - "id": "color", + "id": "mappings", + "value": [ + { + "options": { + "DeviceIdle": { + "color": "#77d38e", + "index": 2, + "text": "Idle" + }, + "DeviceOff": { + "color": "#707070", + "index": 1, + "text": "Off" + }, + "DeviceOn": { + "color": "#e35431", + "index": 0, + "text": "On" + } + }, + "type": "value" + } + ] + }, + { + "id": "custom.cellOptions", "value": { - "fixedColor": "rgba(145, 145, 145, 0.81)", - "mode": "fixed" + "type": "color-background" } + }, + { + "id": "custom.align", + "value": "center" + }, + { + "id": "custom.width", + "value": 36 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Device Name" + }, + "properties": [ + { + "id": "custom.width", + "value": 185 } ] } ] }, "gridPos": { - "h": 10, - "w": 24, + "h": 30, + "w": 6, "x": 0, "y": 0 }, - "id": 4, + "id": 10, "interval": "$interval", "options": { - "legend": { - "calcs": [ - "mean", - "lastNotNull", - "max" + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" ], - "displayMode": "list", - "placement": "bottom" + "show": false }, - "tooltip": { - "mode": "multi" - } + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Time" + } + ] }, - "pluginVersion": "8.0.4", + "pluginVersion": "11.0.0", "targets": [ { - "alias": "$tag_leg", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ - "$__interval" + "1s" ], "type": "time" }, { "params": [ - "leg" + "device_name::tag" ], "type": "tag" }, { "params": [ - "previous" + "null" ], "type": "fill" } ], - "measurement": "sense_mains", - "orderByTime": "ASC", + "measurement": "sense_event", + "orderByTime": "DESC", "policy": "default", - "query": "SELECT mean(\"value\") FROM \"mains\" WHERE (\"type\" = 'watts' AND \"name\" =~ /^$Mains$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", - "rawQuery": false, "refId": "A", - "resultFormat": "time_series", + "resultFormat": "table", "select": [ [ { "params": [ - "watts" + "device_state" ], "type": "field" }, { "params": [], - "type": "mean" + "type": "last" + } + ], + [ + { + "params": [ + "body" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ], + [ + { + "params": [ + "icon" + ], + "type": "field" + }, + { + "params": [], + "type": "last" } ] ], - "tags": [ - { - "key": "leg", - "operator": "=~", - "value": "/^$mains$/" + "tags": [] + } + ], + "title": "Event Timeline", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "device_name": false, + "last": false, + "last_1": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": { + "Time": "Time", + "device_name": "Device Name", + "last": "State", + "last_1": "Text", + "last_2": "Icon" } - ] + } + }, + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "isNotNull", + "options": {} + }, + "fieldName": "State" + } + ], + "match": "any", + "type": "include" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "includeByName": {}, + "indexByName": { + "Device Name": 2, + "Icon": 1, + "State": 3, + "Time": 0 + }, + "renameByName": {} + } } ], - "timeFrom": null, - "timeShift": null, - "title": "Wattage (Stacked)", - "type": "timeseries" + "type": "table" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 30, + "fillOpacity": 20, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 5, @@ -303,15 +563,15 @@ "spanNulls": true, "stacking": { "group": "A", - "mode": "none" + "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, - "decimals": 2, "links": [], "mappings": [], + "min": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -321,7 +581,7 @@ } ] }, - "unit": "volt" + "unit": "watt" }, "overrides": [ { @@ -333,7 +593,7 @@ { "id": "color", "value": { - "fixedColor": "dark-blue", + "fixedColor": "blue", "mode": "fixed" } } @@ -353,35 +613,57 @@ } } ] + }, + { + "matcher": { + "id": "byName", + "options": "Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(145, 145, 145, 0.81)", + "mode": "fixed" + } + } + ] } ] }, "gridPos": { "h": 10, - "w": 24, - "x": 0, - "y": 10 + "w": 18, + "x": 6, + "y": 0 }, - "id": 9, + "id": 4, "interval": "$interval", "options": { "legend": { "calcs": [ + "mean", "lastNotNull", - "max", - "min" + "max" ], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { - "mode": "single" + "maxHeight": 600, + "mode": "multi", + "sort": "none" } }, "pluginVersion": "8.0.4", "targets": [ { "alias": "$tag_leg", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -391,7 +673,7 @@ }, { "params": [ - "leg" + "leg::tag" ], "type": "tag" }, @@ -405,7 +687,7 @@ "measurement": "sense_mains", "orderByTime": "ASC", "policy": "default", - "query": "SELECT mean(\"value\") FROM \"mains\" WHERE (\"type\" = 'volts' AND \"name\" =~ /^$Mains$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", + "query": "SELECT mean(\"value\") FROM \"mains\" WHERE (\"type\" = 'watts' AND \"name\" =~ /^$Mains$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", "rawQuery": false, "refId": "A", "resultFormat": "time_series", @@ -413,7 +695,7 @@ [ { "params": [ - "voltage" + "watts" ], "type": "field" }, @@ -425,26 +707,30 @@ ], "tags": [ { - "key": "leg", + "key": "leg::tag", "operator": "=~", "value": "/^$mains$/" } ] } ], - "timeFrom": null, - "timeShift": null, - "title": "Voltages", + "title": "Wattage (Stacked)", "type": "timeseries" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -456,6 +742,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 0, "pointSize": 5, @@ -537,9 +824,9 @@ }, "gridPos": { "h": 10, - "w": 24, - "x": 0, - "y": 20 + "w": 18, + "x": 6, + "y": 10 }, "id": 2, "interval": "$interval", @@ -551,17 +838,24 @@ "max", "min" ], - "displayMode": "hidden", - "placement": "bottom" + "displayMode": "list", + "placement": "bottom", + "showLegend": false }, "tooltip": { - "mode": "single" + "maxHeight": 600, + "mode": "single", + "sort": "none" } }, "pluginVersion": "8.0.4", "targets": [ { "alias": "Hertz", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -587,7 +881,7 @@ [ { "params": [ - "hz" + "hertz" ], "type": "field" }, @@ -601,6 +895,10 @@ }, { "alias": "Hertz2", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -626,7 +924,7 @@ [ { "params": [ - "hz" + "hertz" ], "type": "field" }, @@ -639,15 +937,187 @@ "tags": [] } ], - "timeFrom": null, - "timeShift": null, "title": "Frequency", "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "volt" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "L1" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "L2" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 18, + "x": 6, + "y": 20 + }, + "id": 9, + "interval": "$interval", + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "max", + "min" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.0.4", + "targets": [ + { + "alias": "$tag_leg", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "leg" + ], + "type": "tag" + }, + { + "params": [ + "previous" + ], + "type": "fill" + } + ], + "measurement": "sense_mains", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"value\") FROM \"mains\" WHERE (\"type\" = 'volts' AND \"name\" =~ /^$Mains$/) AND $timeFilter GROUP BY time(3s), \"name\" fill(previous)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "voltage" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "leg", + "operator": "=~", + "value": "/^$mains$/" + } + ] + } + ], + "title": "Voltages", + "type": "timeseries" } ], "refresh": "1m", - "schemaVersion": 30, - "style": "dark", + "schemaVersion": 39, "tags": [ "influxdb", "sense-collector" @@ -655,12 +1125,31 @@ "templating": { "list": [ { - "allValue": null, + "current": { + "selected": false, + "text": "InfluxDB - influxdb02 - Tylephony - sense", + "value": "PDE3CD461864125BE" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "data_source", + "options": [], + "query": "influxdb", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "definition": "SHOW TAG VALUES FROM \"sense_devices\" WITH KEY = \"name\"", - "description": null, - "error": null, "hide": 2, "includeAll": true, "label": "Device", @@ -680,10 +1169,11 @@ { "allValue": "", "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "definition": "SHOW TAG VALUES FROM \"sense_mains\" WITH KEY = \"leg\"", - "description": null, - "error": null, "hide": 0, "includeAll": true, "label": "Legs", @@ -702,12 +1192,10 @@ "auto_count": 4, "auto_min": "5s", "current": { - "selected": true, + "selected": false, "text": "1m", "value": "1m" }, - "description": null, - "error": null, "hide": 0, "label": "Interval", "name": "interval", @@ -785,6 +1273,7 @@ "from": "now/d", "to": "now" }, + "timeRangeUpdatedDuringEditOrView": false, "timepicker": { "refresh_intervals": [ "5s", @@ -802,6 +1291,6 @@ "timezone": "", "title": "Sense Collector - Mains Overview", "uid": "lux4rd0labs_sense_03", - "version": 1, - "description": "Sense Collector provides a way of collecting real-time data from the Sense Energy Monitor. These Grafana dashboards offer visualizations for detected devices and smart plugs and their wattage, voltage, and amp utilization." + "version": 33, + "weekStart": "" } \ No newline at end of file diff --git a/grafana/shared/sense_collector-monitor_and_detection.json b/grafana/shared/sense_collector-monitor_and_detection.json index 0deda35..abf5314 100644 --- a/grafana/shared/sense_collector-monitor_and_detection.json +++ b/grafana/shared/sense_collector-monitor_and_detection.json @@ -1,20 +1,21 @@ { "__inputs": [ { - "name": "DS_INFLUXDB_- SENSE", - "label": "InfluxDB - sense", + "name": "DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE", + "label": "InfluxDB - influxdb02 - Tylephony - sense", "description": "", "type": "datasource", "pluginId": "influxdb", "pluginName": "InfluxDB" } ], + "__elements": {}, "__requires": [ { "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "8.0.6" + "version": "11.0.0" }, { "type": "datasource", @@ -39,7 +40,10 @@ "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -49,18 +53,18 @@ ] }, "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, - "iteration": 1626665965748, "links": [ { "asDropdown": true, "icon": "external link", - "includeVars": true, - "keepTime": true, + "includeVars": false, + "keepTime": false, "tags": [ - "sense-collector" + "sense-collector", + "influxdb" ], "targetBlank": false, "title": "Sense Collector - Dashboards", @@ -69,9 +73,13 @@ "url": "" } ], + "liveNow": false, "panels": [ { - "datasource": null, + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" + }, "gridPos": { "h": 1, "w": 24, @@ -79,17 +87,32 @@ "y": 0 }, "id": 11, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" + }, + "refId": "A" + } + ], "title": "Device Detection Status", "type": "row" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -101,6 +124,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineStyle": { "fill": "solid" @@ -141,7 +165,7 @@ "overrides": [] }, "gridPos": { - "h": 11, + "h": 9, "w": 12, "x": 0, "y": 1 @@ -150,17 +174,26 @@ "interval": "$interval", "options": { "legend": { - "calcs": [], + "calcs": [ + "lastNotNull" + ], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { - "mode": "multi" + "maxHeight": 600, + "mode": "multi", + "sort": "none" } }, "targets": [ { "alias": "$tag_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -170,12 +203,12 @@ }, { "params": [ - "name" + "name::field" ], "type": "tag" } ], - "measurement": "sense_monitor_device_detection", + "measurement": "sense_device_detection", "orderByTime": "ASC", "policy": "default", "refId": "A", @@ -196,25 +229,36 @@ ], "tags": [ { - "key": "detection_type", + "key": "status::tag", "operator": "=", "value": "found" + }, + { + "condition": "AND", + "key": "monitor_id::tag", + "operator": "=~", + "value": "/^$monitor_id$/" } ] } ], - "timeFrom": null, "title": "Found Devices", "type": "timeseries" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -226,6 +270,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 20, @@ -263,7 +308,7 @@ "overrides": [] }, "gridPos": { - "h": 11, + "h": 9, "w": 12, "x": 12, "y": 1 @@ -272,17 +317,26 @@ "interval": "$interval", "options": { "legend": { - "calcs": [], + "calcs": [ + "lastNotNull" + ], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { - "mode": "multi" + "maxHeight": 600, + "mode": "multi", + "sort": "none" } }, "targets": [ { "alias": "$tag_name", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -292,12 +346,12 @@ }, { "params": [ - "name" + "name::field" ], "type": "tag" } ], - "measurement": "sense_monitor_device_detection", + "measurement": "sense_device_detection", "orderByTime": "ASC", "policy": "default", "refId": "A", @@ -318,9 +372,15 @@ ], "tags": [ { - "key": "detection_type", + "key": "status::tag", "operator": "=", "value": "in_progress" + }, + { + "condition": "AND", + "key": "monitor_id::tag", + "operator": "=~", + "value": "/^$monitor_id$/" } ] } @@ -330,20 +390,35 @@ }, { "collapsed": false, - "datasource": null, + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" + }, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 12 + "y": 10 }, "id": 9, "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "uDxwFcOGz" + }, + "refId": "A" + } + ], "title": "Monitor Details", "type": "row" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { @@ -351,6 +426,9 @@ "mode": "fixed" }, "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -362,6 +440,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 1, "pointSize": 20, @@ -379,7 +458,6 @@ } }, "mappings": [], - "max": 0, "thresholds": { "mode": "absolute", "steps": [ @@ -397,7 +475,7 @@ "h": 10, "w": 24, "x": 0, - "y": 13 + "y": 11 }, "id": 4, "interval": "$interval", @@ -405,15 +483,22 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { - "mode": "multi" + "maxHeight": 600, + "mode": "multi", + "sort": "none" } }, "targets": [ { - "alias": "$tag_serial", + "alias": "$tag_monitor_id", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -423,7 +508,7 @@ }, { "params": [ - "serial" + "monitor_id::tag" ], "type": "tag" } @@ -447,15 +532,23 @@ } ] ], - "tags": [] + "tags": [ + { + "key": "monitor_id::tag", + "operator": "=~", + "value": "/^$monitor_id$/" + } + ] } ], - "timeFrom": null, "title": "Wifi Signal Strength - RSSI", "type": "timeseries" }, { - "datasource": "${DS_INFLUXDB_- SENSE}", + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "fieldConfig": { "defaults": { "color": { @@ -463,7 +556,10 @@ }, "custom": { "align": "center", - "displayMode": "auto" + "cellOptions": { + "type": "auto" + }, + "inspect": false }, "mappings": [ { @@ -505,8 +601,15 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "custom.width", + "value": 105 } ] }, @@ -517,8 +620,15 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "custom.width", + "value": 123 } ] }, @@ -529,8 +639,15 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "custom.width", + "value": 95 } ] }, @@ -550,35 +667,58 @@ "mode": "absolute", "steps": [ { - "color": "dark-red", + "color": "#00ff00", "value": null }, { - "color": "semi-dark-red", - "value": -90 + "color": "#00ff00", + "value": 50 + }, + { + "color": "#40ff00", + "value": 55 + }, + { + "color": "#80ff00", + "value": 60 + }, + { + "color": "#bfff00", + "value": 65 + }, + { + "color": "#ffff00", + "value": 70 }, { - "color": "dark-orange", - "value": -80 + "color": "#ffbf00", + "value": 75 }, { - "color": "light-orange", - "value": -70 + "color": "#ff8000", + "value": 80 }, { - "color": "yellow", - "value": -60 + "color": "#ff4000", + "value": 85 }, { - "color": "dark-green", - "value": -50 + "color": "#ff0000", + "value": 90 } ] } }, { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "custom.width", + "value": 125 } ] }, @@ -589,8 +729,15 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "color-background" + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" + } + }, + { + "id": "custom.width", + "value": 119 } ] }, @@ -603,27 +750,56 @@ { "id": "unit", "value": "percent" + }, + { + "id": "custom.width", + "value": 152 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "MAC Address" + }, + "properties": [ + { + "id": "custom.width", + "value": 164 } ] } ] }, "gridPos": { - "h": 3, + "h": 4, "w": 24, "x": 0, - "y": 23 + "y": 21 }, "hideTimeOverride": true, "id": 2, - "interval": null, "maxDataPoints": 1, "options": { - "showHeader": true + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [] }, - "pluginVersion": "8.0.6", + "pluginVersion": "11.0.0", "targets": [ { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, "groupBy": [ { "params": [ @@ -821,9 +997,9 @@ ], "tags": [ { - "key": "serial", - "operator": "=", - "value": "M638000670" + "key": "monitor_id::tag", + "operator": "=~", + "value": "/^$monitor_id$/" } ] } @@ -870,8 +1046,7 @@ } ], "refresh": "1m", - "schemaVersion": 30, - "style": "dark", + "schemaVersion": 39, "tags": [ "sense-collector", "influxdb" @@ -879,19 +1054,38 @@ "templating": { "list": [ { - "allValue": null, + "current": { + "selected": false, + "text": "InfluxDB - influxdb02 - Tylephony - sense", + "value": "PDE3CD461864125BE" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "data_source", + "options": [], + "query": "influxdb", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { "current": {}, - "datasource": "${DS_INFLUXDB_- SENSE}", - "definition": "SHOW TAG VALUES WITH KEY = \"serial\"", - "description": null, - "error": null, + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB_- INFLUXDB02 - TYLEPHONY - SENSE}" + }, + "definition": "SHOW TAG VALUES WITH KEY = \"monitor_id\"", "hide": 0, "includeAll": false, "label": "Monitor", "multi": false, - "name": "serial", + "name": "monitor_id", "options": [], - "query": "SHOW TAG VALUES WITH KEY = \"serial\"", + "query": "SHOW TAG VALUES WITH KEY = \"monitor_id\"", "refresh": 1, "regex": "", "skipUrlSync": false, @@ -899,20 +1093,23 @@ "type": "query" }, { - "auto": false, + "auto": true, "auto_count": 30, "auto_min": "10s", "current": { - "selected": true, + "selected": false, "text": "1m", "value": "1m" }, - "description": null, - "error": null, "hide": 0, "label": "Interval", "name": "interval", "options": [ + { + "selected": false, + "text": "auto", + "value": "$__auto_interval_interval" + }, { "selected": true, "text": "1m", @@ -963,13 +1160,14 @@ ] }, "time": { - "from": "now/d", + "from": "now-7d", "to": "now" }, + "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "", - "title": "Sense Collector - Monitor and Detection", - "uid": "lux4rd0labs_sense_03", - "version": 1, - "description": "Sense Collector provides a way of collecting real-time data from the Sense Energy Monitor. These Grafana dashboards offer visualizations for detected devices and smart plugs and their wattage, voltage, and amp utilization." + "title": "Sense Collector - Monitor & Detection", + "uid": "lux4rd0labs_sense_04", + "version": 25, + "weekStart": "" } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6259cc4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +aiohttp==3.9.1 +influxdb_client==1.39.0 +psutil==5.9.5 +python_dateutil==2.8.2 +pytz==2024.1 +Requests==2.32.3 +websocket_client==1.3.3 diff --git a/sense-collector.py b/sense-collector.py new file mode 100644 index 0000000..3304619 --- /dev/null +++ b/sense-collector.py @@ -0,0 +1,606 @@ +import asyncio +import json +import os +import sys +import time +from datetime import datetime, timezone, timedelta +import aiohttp +from storage import InfluxDBStorage +import logging + +# Configure logging based on environment variables +log_level_api = os.getenv("SENSE_COLLECTOR_LOG_LEVEL_API", "INFO").upper() +log_level_storage = os.getenv("SENSE_COLLECTOR_LOG_LEVEL_STORAGE", "INFO").upper() +log_level_general = os.getenv("SENSE_COLLECTOR_LOG_LEVEL_GENERAL", "INFO").upper() +sense_api_receive_data_output = ( + os.getenv("SENSE_COLLECTOR_SENSE_API_RECEIVE_DATA_OUTPUT", "false").lower() + == "true" +) + + +logging.basicConfig( + format="%(asctime)s %(levelname)s:%(name)s:%(message)s", + handlers=[logging.StreamHandler()], +) + +# Create loggers +logger = logging.getLogger("general") +logger.setLevel(log_level_general) + +api_logger = logging.getLogger("api") +api_logger.setLevel(log_level_api) + +storage_logger = logging.getLogger("storage") +storage_logger.setLevel(log_level_storage) + +# Retrieve the export folder path from the environment variable +export_folder = os.getenv("SENSE_COLLECTOR_EXPORT_FOLDER", "export") + +# Ensure the export folder exists and is writable +os.makedirs(export_folder, exist_ok=True) +if not os.access(export_folder, os.W_OK): + raise PermissionError(f"Export folder {export_folder} is not writable") + + +class SenseAPIEndpoints: + BASE_URL = "https://api.sense.com/apiservice/api/v1" + AUTHENTICATE = f"{BASE_URL}/authenticate" + TIMELINE = f"{BASE_URL}/users/{{user_id}}/timeline" + DEVICE_DATA = f"{BASE_URL}/app/monitors/{{monitor_id}}/devices/{{device_id}}" + MONITOR_STATUS = f"{BASE_URL}/app/monitors/{{monitor_id}}/status" + WS_BASE_URL = "wss://clientrt.sense.com" + REALTIME_FEED = f"{WS_BASE_URL}/monitors/{{monitor_id}}/realtimefeed" + + +async def authenticate_with_sense(username, password): + url = SenseAPIEndpoints.AUTHENTICATE + headers = {"Content-Type": "application/x-www-form-urlencoded"} + data = {"email": username, "password": password} + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, data=data) as response: + response.raise_for_status() + return await response.json() + + +class SenseCollector: + def __init__( + self, + monitor_id, + token, + influxdb_storage, + user_id, + cache_expiry_seconds=120, + max_concurrent_lookups=4, + lookup_delay_seconds=1, + ): + self.monitor_id = monitor_id + self.token = token + self.user_id = user_id + self.ws_url = ( + f"wss://clientrt.sense.com/monitors/{self.monitor_id}/realtimefeed" + ) + self.headers = { + "Authorization": f"bearer {self.token}", + "Sense-Collector-Client-Version": "2.0.0", + "X-Sense-Protocol": "3", + "User-Agent": "okhttp/3.8.0", + } + self.influxdb_storage = influxdb_storage + self.api_call_queue = asyncio.Queue() + self.device_cache = {} + self.cache_expiry_seconds = cache_expiry_seconds + self.max_concurrent_lookups = max_concurrent_lookups + self.lookup_delay_seconds = lookup_delay_seconds + self.semaphore = asyncio.Semaphore(max_concurrent_lookups) + self.session = None + + async def start_session(self): + self.session = aiohttp.ClientSession() + + async def close_session(self): + if self.session: + await self.session.close() + + async def api_worker(self): + while True: + queue_item = await self.api_call_queue.get() + device_id = queue_item.get("device_id") + + try: + device_data = await self.lookup_device_data(device_id) + if device_data: + await self.influxdb_storage.persist_device_data(device_data) + except Exception as e: + api_logger.error(f"Error processing device {device_id}: {e}") + finally: + self.api_call_queue.task_done() + + async def create_connection(self, max_retries=3, backoff_factor=1): + ws_url = SenseAPIEndpoints.REALTIME_FEED.format(monitor_id=self.monitor_id) + for attempt in range(max_retries): + try: + self.ws = await self.session.ws_connect(ws_url, headers=self.headers) + api_logger.info("Created WebSocket connection") + return + except ( + aiohttp.ClientError, + asyncio.TimeoutError, + ) as e: + api_logger.warning(f"Retry {attempt + 1} for WebSocket connection: {e}") + if attempt < max_retries - 1: + sleep_time = backoff_factor * (2**attempt) + await asyncio.sleep(sleep_time) + else: + raise # Re-raise the last exception if all retries fail + + async def close_connection(self): + if self.ws: + await self.ws.close() + api_logger.info("Closed WebSocket connection") + + async def receive_data(self): + api_logger.info("Starting data reception") + + while True: + try: + await self.create_connection() + async for msg in self.ws: + if msg.type == aiohttp.WSMsgType.TEXT: + data = json.loads(msg.data) + if sense_api_receive_data_output: + export_file_path = os.path.join( + export_folder, "received_data.json" + ) + with open(export_file_path, "a") as f: + f.write(json.dumps(data) + "\n") + await self.process_and_send_data(data) + except Exception as e: + api_logger.error(f"Error in data reception: {e}") + finally: + await self.close_connection() + api_logger.info("Stopped data reception") + + # Reconnect after a brief delay + api_logger.info("Attempting to reconnect in 5 seconds...") + await asyncio.sleep(5) + + async def process_and_send_data(self, data): + api_logger.info("Starting to process and send data") + # api_logger.debug(f"Received data: {data}") + + try: + data_type = data.get("type") + api_logger.debug(f"Data type: {data_type}") + + if data_type == "realtime_update": + api_logger.info("Processing realtime update") + await self.handle_realtime_update(data["payload"]) + elif data_type == "new_timeline_event": + api_logger.info("Processing new timeline event") + await self.handle_new_timeline_event(data["payload"]) + elif data_type == "hello": + api_logger.info("Processing hello event") + await self.handle_hello_event(data["payload"]) + elif data_type == "data_change": + api_logger.info("Processing data change event") + await self.handle_data_change_event(data["payload"]) + elif data_type == "device_states": + api_logger.info("Processing device states event") + await self.handle_device_states_event(data["payload"]) + else: + api_logger.warning(f"Unknown data type received: {data_type}") + + except Exception as e: + api_logger.error(f"Error processing data: {e}") + finally: + api_logger.info("Finished processing and sending data") + + async def handle_realtime_update(self, payload): + api_logger.info("Starting to handle realtime update") + + try: + hertz = float(payload["hz"]) + total_current = float(payload["c"]) + total_watts = float(payload["w"]) + epoch = int(payload["epoch"]) # Use epoch time in seconds directly + + voltage = payload.get("voltage", []) + devices = payload.get("devices", []) + channels = payload.get("channels", []) + + await self.influxdb_storage.persist_realtime_data( + self.monitor_id, + hertz, + total_current, + total_watts, + epoch, + voltage, + devices, + channels, + ) + + api_logger.info( + "Data processed and sent to InfluxDB in 'handle_realtime_update' method" + ) + + except Exception as e: + api_logger.error(f"Error in handle_realtime_update: {e}") + + async def handle_new_timeline_event(self, payload): + items_added = payload.get("items_added", []) + for item in items_added: + device_id = item.get("device_id") + if device_id: + await self.process_timeline_item(item) + queue_item = { + "device_id": device_id, + } + await self.api_call_queue.put(queue_item) + else: + api_logger.warning(f"Missing device_id in item: {item}") + + async def process_timeline_item(self, item): + api_logger.info("Starting to process timeline item") + api_logger.debug(f"Timeline item: {item}") + + try: + time = item.get("time") + event_type = item.get("type") + icon = item.get("icon") + body = item.get("body") + device_id = item.get("device_id") + device_state = item.get("device_state") + user_device_type = item.get("user_device_type") + device_transition_from_state = item.get("device_transition_from_state") + + api_logger.debug(f"Fetching device data for device_id: {device_id}") + device_data = await self.lookup_device_data(device_id) + api_logger.debug(f"Device data: {device_data}") + + if device_data and "device" in device_data: + device_name = device_data["device"]["name"] + api_logger.debug(f"Device name: {device_name}") + else: + device_name = "Unknown" + api_logger.warning( + f"Device data for device_id {device_id} is missing 'device' key or is None" + ) + + await self.influxdb_storage.persist_timeline_data( + device_id, + device_name, + time, + event_type, + icon, + body, + device_state, + user_device_type, + device_transition_from_state, + ) + + api_logger.info("Timeline item processed and sent to InfluxDB") + + except KeyError as e: + api_logger.error(f"KeyError in process_timeline_item: {e}") + except Exception as e: + api_logger.error(f"Error in process_timeline_item: {e}") + + async def handle_hello_event(self, payload): + online_status = payload.get("online", False) + influxdb_timestamp = int( + datetime.now(timezone.utc).timestamp() + ) # Convert to epoch seconds + await self.influxdb_storage.persist_hello_event( + self.monitor_id, online_status, influxdb_timestamp + ) + + async def handle_data_change_event(self, payload): + influxdb_timestamp = int( + datetime.now(timezone.utc).timestamp() + ) # Convert to epoch seconds + user_version = payload.get("user_version") + partner_checksum = payload.get("partner_checksum") + monitor_overview_checksum = payload.get("monitor_overview_checksum") + device_data_checksum = payload.get("device_data_checksum") + settings_version = payload.get("settings_version") + + new_device_events = payload.get("pending_events", {}).get( + "new_device_found", [] + ) + if not isinstance(new_device_events, list): + new_device_events = [new_device_events] + + for event in new_device_events: + device_id = event.get("device_id") + guid = event.get("guid") + json_timestamp = event.get("timestamp") + epoch_timestamp = ( + self.convert_to_epoch(json_timestamp) if json_timestamp else None + ) + + await self.influxdb_storage.persist_data_change_event( + self.monitor_id, + device_id, + user_version, + guid, + epoch_timestamp, + influxdb_timestamp, + ) + + api_logger.info( + f"Data change event data for device {device_id} sent to InfluxDB" + ) + + async def handle_device_states_event(self, payload): + states = payload.get("states", []) + for state in states: + device_id = state.get("device_id") + mode = state.get("mode") + device_state = state.get("state") + influxdb_timestamp = int( + datetime.now(timezone.utc).timestamp() + ) # Convert to epoch seconds + + queue_item = { + "device_id": device_id, + } + await self.api_call_queue.put(queue_item) + + await self.influxdb_storage.persist_device_state( + self.monitor_id, device_id, mode, device_state, influxdb_timestamp + ) + + async def lookup_device_data(self, device_id): + async with self.semaphore: + current_time = time.time() + # Check if the device data is in cache and not expired + if device_id in self.device_cache: + cached_data, timestamp = self.device_cache[device_id] + time_since_cached = current_time - timestamp + time_until_expiry = self.cache_expiry_seconds - time_since_cached + if time_until_expiry > 0: + api_logger.debug( + f"Cache hit for device_id: {device_id}, time until expiry: {time_until_expiry:.2f} seconds" + ) + return cached_data + else: + api_logger.debug( + f"Cache expired for device_id: {device_id}, fetching new data" + ) + + url = SenseAPIEndpoints.DEVICE_DATA.format( + monitor_id=self.monitor_id, device_id=device_id + ) + api_logger.info( + f"Sending request to fetch device data for device_id: {device_id}" + ) + try: + async with self.session.get(url, headers=self.headers) as response: + if response.status == 429: # Handle rate limiting + retry_after = int(response.headers.get("Retry-After", 1)) + api_logger.warning( + f"Rate limited. Retrying after {retry_after} seconds" + ) + await asyncio.sleep(retry_after) + return await self.lookup_device_data( + device_id + ) # Retry after delay + response.raise_for_status() + device_data = await response.json() + + # Cache the fetched data + self.device_cache[device_id] = (device_data, current_time) + api_logger.debug( + f"Fetched and cached data for device_id: {device_id}" + ) + return device_data + except aiohttp.ClientError as e: + api_logger.error(f"Error fetching device data for {device_id}: {e}") + return None + finally: + api_logger.info( + f"Completed request for device_id: {device_id}, waiting for {self.lookup_delay_seconds} seconds before next request" + ) + await asyncio.sleep(self.lookup_delay_seconds) + + async def fetch_monitor_status(self): + url = SenseAPIEndpoints.MONITOR_STATUS.format(monitor_id=self.monitor_id) + while True: + start_time = time.time() + try: + async with self.session.get(url, headers=self.headers) as response: + response.raise_for_status() + monitor_status = await response.json() + await self.influxdb_storage.persist_monitor_status( + self.monitor_id, monitor_status + ) + api_logger.info("Successfully fetched and persisted monitor status") + except aiohttp.ClientError as e: + api_logger.error(f"Failed to fetch monitor status: {e}") + + elapsed_time = time.time() - start_time + sleep_time = max(60 - elapsed_time, 0) + await asyncio.sleep(sleep_time) + + async def poll_timeline(self): + url = SenseAPIEndpoints.TIMELINE.format(user_id=self.user_id) + while True: + start_time = time.time() + human_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + try: + async with self.session.get(url, headers=self.headers) as response: + response.raise_for_status() + timeline_data = await response.json() + await self.process_timeline_data(timeline_data) + api_logger.info( + f"Successfully fetched and processed timeline data at {human_start_time}" + ) + logger.debug( + f"Timeline response: {json.dumps(timeline_data, indent=2)}" + ) + except aiohttp.ClientError as e: + api_logger.error( + f"Failed to fetch timeline data at {human_start_time}: {e}" + ) + + elapsed_time = time.time() - start_time + sleep_time = max(60 - elapsed_time, 0) + human_sleep_time = datetime.now() + timedelta(seconds=sleep_time) + human_sleep_time = human_sleep_time.strftime("%Y-%m-%d %H:%M:%S") + api_logger.info( + f"Timeline polling completed at {human_start_time}. Next run at {human_sleep_time} after sleeping for {sleep_time:.2f} seconds" + ) + await asyncio.sleep(sleep_time) + + async def process_timeline_data(self, data): + for item in data.get("items", []): + await self.process_timeline_item(item) + + async def fetch_devices(self): + url = SenseAPIEndpoints.BASE_URL + f"/app/monitors/{self.monitor_id}/devices" + while True: + start_time = time.time() + try: + async with self.session.get(url, headers=self.headers) as response: + api_logger.debug(f"HTTP GET {url} status: {response.status}") + response.raise_for_status() + devices_response = await response.json() + + # Output the JSON response for debugging + api_logger.debug( + f"Fetched devices response: {json.dumps(devices_response, indent=4)}" + ) + + if isinstance(devices_response, list): + for device in devices_response: + device_id = device.get("id") + if device_id: + api_logger.debug( + f"Queueing device for processing: {device_id}" + ) + await self.api_call_queue.put({"device_id": device_id}) + else: + api_logger.warning( + f"Device without ID found: {json.dumps(device, indent=4)}" + ) + api_logger.info("Successfully fetched and queued devices") + else: + api_logger.warning( + f"Unexpected response format: {devices_response}" + ) + + except aiohttp.ClientResponseError as e: + api_logger.error(f"Client response error: {e.status} {e.message}") + except aiohttp.ClientConnectionError as e: + api_logger.error(f"Client connection error: {e}") + except aiohttp.ClientError as e: + api_logger.error(f"Client error: {e}") + except Exception as e: + api_logger.error(f"Unexpected error: {e}") + + elapsed_time = time.time() - start_time + human_elapsed_time = datetime.now() + timedelta(seconds=elapsed_time) + human_sleep_time = datetime.now() + timedelta( + seconds=max(3600 - elapsed_time, 0) + ) + api_logger.info( + f"Fetch devices ran at: {human_elapsed_time}. " + f"Next fetch will run at: {human_sleep_time}. " + f"Sleeping for {max(3600 - elapsed_time, 0)} seconds." + ) + await asyncio.sleep(max(3600 - elapsed_time, 0)) + + def convert_to_epoch(self, timestamp_str): + timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ" + try: + datetime_obj = datetime.strptime(timestamp_str, timestamp_format) + return int(datetime_obj.timestamp()) + except ValueError as e: + api_logger.error(f"Error converting timestamp: {e}") + return None + + +def obfuscate_sensitive_data(data, visible_chars=4): + if len(data) <= visible_chars: + return "*" * len(data) + return data[:visible_chars] + "*" * (len(data) - visible_chars) + + +async def main(): + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + logger.info(f"Welcome to Sense Collector! Current time: {current_time}") + + env_vars_defaults = { + "SENSE_COLLECTOR_API_USERNAME": None, + "SENSE_COLLECTOR_API_PASSWORD": None, + "SENSE_COLLECTOR_INFLUXDB_URL": None, + "SENSE_COLLECTOR_INFLUXDB_TOKEN": None, + "SENSE_COLLECTOR_INFLUXDB_ORG": None, + "SENSE_COLLECTOR_INFLUXDB_BUCKET": None, + "SENSE_COLLECTOR_LOG_LEVEL_API": "INFO", + "SENSE_COLLECTOR_LOG_LEVEL_STORAGE": "INFO", + "SENSE_COLLECTOR_LOG_LEVEL_GENERAL": "INFO", + } + + def obscure_value(value): + if value and len(value) > 4: + return value[:2] + "*" * (len(value) - 4) + value[-2:] + return value + + missing_env_vars = [] + logger.info("Environment Variable Settings:") + for var, default in env_vars_defaults.items(): + value = os.environ.get(var, default) + is_default = value == default + default_indicator = "(default)" if is_default else "(custom)" + if "PASSWORD" in var or "TOKEN" in var: + value_to_print = obscure_value(value) + else: + value_to_print = value + logger.info(f"{var}: {value_to_print} {default_indicator}") + if value is None: + missing_env_vars.append(var) + + if missing_env_vars: + logger.error( + f"Missing required environment variables: {', '.join(missing_env_vars)}" + ) + sys.exit(1) + + try: + auth_response = await authenticate_with_sense( + os.environ["SENSE_COLLECTOR_API_USERNAME"], + os.environ["SENSE_COLLECTOR_API_PASSWORD"], + ) + monitor_id = str(auth_response["monitors"][0]["id"]) + token = auth_response["access_token"] + user_id = auth_response["user_id"] + except Exception as e: + logger.error(f"Authentication failed: {e}") + sys.exit(1) + + influxdb_params = { + "url": os.environ.get("SENSE_COLLECTOR_INFLUXDB_URL"), + "token": os.environ.get("SENSE_COLLECTOR_INFLUXDB_TOKEN"), + "org": os.environ.get("SENSE_COLLECTOR_INFLUXDB_ORG"), + "bucket": os.environ.get("SENSE_COLLECTOR_INFLUXDB_BUCKET"), + } + + influxdb_storage = InfluxDBStorage(influxdb_params) + + collector = SenseCollector(monitor_id, token, influxdb_storage, user_id) + await collector.start_session() + + try: + await asyncio.gather( + collector.api_worker(), + collector.receive_data(), + collector.fetch_monitor_status(), + collector.fetch_devices(), + ) + finally: + await collector.close_session() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sense-collector/Dockerfile b/sense-collector/Dockerfile deleted file mode 100644 index ca41e3c..0000000 --- a/sense-collector/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM grafana/promtail:2.2.1 - -RUN apt-get update && apt-get install -y bc curl dumb-init bash python procps coreutils sysstat vim net-tools - -RUN mkdir /sense-collector - -COPY exec-device-details.sh \ -exec-host-performance.sh \ -exec-monitor-status.sh \ -exec-sense-collector.sh \ -sense-collector-details.sh \ -start-device-details.sh \ -start-host-performance.sh \ -start-monitor-status.sh \ -start-sense-collector.sh \ -sense-collector-init.sh \ -websocat_amd64-linux-static \ -/sense-collector/ - -COPY jq /usr/bin/ - -WORKDIR /sense-collector - -RUN chmod a+x *.sh - -RUN chmod +x websocat_amd64-linux-static - -RUN chmod +x /usr/bin/jq - -ENTRYPOINT ["/usr/bin/dumb-init", "--"] - -CMD ["/sense-collector/sense-collector-init.sh"] diff --git a/sense-collector/build.sh b/sense-collector/build.sh deleted file mode 100644 index 81f87d8..0000000 --- a/sense-collector/build.sh +++ /dev/null @@ -1,5 +0,0 @@ -docker build . -t lux4rd0/sense-collector:latest -t lux4rd0/sense-collector:$1 -t docker01.tylephony.com:5000/lux4rd0/sense-collector:latest -t docker01.tylephony.com:5000/lux4rd0/sense-collector:$1 -docker push docker01.tylephony.com:5000/lux4rd0/sense-collector:latest -docker push docker01.tylephony.com:5000/lux4rd0/sense-collector:$1 -docker push lux4rd0/sense-collector:latest -docker push lux4rd0/sense-collector:$1 diff --git a/sense-collector/exec-device-details.sh b/sense-collector/exec-device-details.sh deleted file mode 100644 index d32ad32..0000000 --- a/sense-collector/exec-device-details.sh +++ /dev/null @@ -1,261 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - exec-sense-device-details.sh -## - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Set Specific Variables -## - -collector_type="device-details" - -## -## Start Observations Timer -## - -observations_start=$(date +%s%N) - -## -## Sense-Collector Details -## - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -debug_if=$SENSE_COLLECTOR_DEBUG_IF -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -sense_monitor_id=$SENSE_COLLECTOR_MONITOR_ID -sense_token=$SENSE_COLLECTOR_TOKEN -threads=$SENSE_COLLECTOR_THREADS - -## -## Set Threads -## - -if [ -z "${threads}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_THREADS${echo_normal} environmental variable not set. Defaulting to ${echo_bold}4${echo_normal} threads."; threads="4"; export SENSE_COLLECTOR_THREADS="4"; fi - -if [ "$debug" == "true" ]; then - -echo "debug=${debug} -debug_curl=${debug_curl} -debug_if=${debug_if} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -sense_monitor_id=${sense_monitor_id} -sense_token=${sense_token} -threads=${threads}" - -fi - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -url_sense_get_devices="https://api.sense.com/apiservice/api/v1/app/monitors/${sense_monitor_id}/devices" - -response_url_sense_get_devices=$(curl "${curl[@]}" -k -H "Sense-Collector-Client-Version: 1.0.0" -H "X-Sense-Protocol: 3" -H "User-Agent: okhttp/3.8.0" -H "Authorization: bearer ${sense_token}" "${url_sense_get_devices}") - -#echo "${response_url_sense_get_devices}" - -devices=($(echo "${response_url_sense_get_devices}" | jq -r '.[].id | @sh' | tr -d \')) - -number_of_devices=$(echo "${response_url_sense_get_devices}" | jq '. | length') - -number_of_devices_minus_one=$((number_of_devices-1)) - -#echo "number_of_devices=${number_of_devices}" -#echo "devices=${devices[@]}" - -## -## Start "threading" -## - -for device_number in $(seq 0 $number_of_devices_minus_one) ; do - -#( - -url_sense_device="https://api.sense.com/apiservice/api/v1/app/monitors/${sense_monitor_id}/devices/${devices[device_number]}" - -#echo "url_sense_device=${url_sense_device}" -#echo "device_id=${devices[device_number]}" - -if [ "${devices[device_number]}" == "unknown" ] || [ "${devices[device_number]}" == "always_on" ] || [ "${devices[device_number]}" == "Other" ]; then continue; fi - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -response_url_sense_device=$(curl "${curl[@]}" -k -H "Sense-Collector-Client-Version: 1.0.0" -H "X-Sense-Protocol: 3" -H "User-Agent: okhttp/3.8.0" -H "Authorization: bearer ${sense_token}" "${url_sense_device}") - -#name=$(echo "${response_url_sense_device}" | jq -r .device.name) - -eval "$(echo "${response_url_sense_device}" | jq -r '.usage | {"current_month_runs", "current_month_KWH", "avg_monthly_runs", "avg_monthly_KWH", "avg_monthly_pct", "avg_watts", "avg_duration", "yearly_KWH", "yearly_text", "yearly_cost", "current_month_cost", "avg_monthly_cost"} | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -if [ "$debug" == "true" ]; then - -echo " -current_month_runs=${current_month_runs} -current_month_KWH=${current_month_KWH} -avg_monthly_runs=${avg_monthly_runs} -avg_monthly_KWH=${avg_monthly_KWH} -avg_monthly_pct=${avg_monthly_pct} -avg_watts=${avg_watts} -avg_duration=${avg_duration} -yearly_KWH=${yearly_KWH} -yearly_text=${yearly_text} -yearly_cost=${yearly_cost} -current_month_cost=${current_month_cost} -avg_monthly_cost=${avg_monthly_cost}" - -fi - -eval "$(echo "${response_url_sense_device}" | jq -r '.device | {"name", "icon", "last_state", "last_state_time"}' | jq -r '. | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -if [ "${last_state_time}" != "null" ]; then last_state_time_epoch=$(date -d"${last_state_time}" +%s%3N ); else if [ "$debug_if" == "true" ]; then echo "last_state_time is null"; fi; fi -if [ "${last_state_time}" != "null" ]; then last_state_time_ns=$(date -d"${last_state_time}" +%s%N ); else if [ "$debug_if" == "true" ]; then echo "last_state_time is null"; fi; fi - -if [ "$debug" == "true" ]; then - -echo " -name=${name} -icon=${icon} -last_state=${last_state} -last_state_time=${last_state_time} -last_state_time_epoch=${last_state_time_epoch} -last_state_time_ns=${last_state_time_ns} -" - -fi - -if [ "$debug" == "true" ]; then echo "loop=${device_number} - name=${name} - device_id=${devices[$device_number]}"; fi - -## -## Escape Names (Function) -## - -escape_names - -curl_message="sense_devices,device_id=${devices[device_number]},name=${name_escaped} " - -if [ "${current_month_runs}" != "null" ]; then curl_message="${curl_message}current_month_runs=${current_month_runs},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} current_month_runs is null"; fi; fi -if [ "${current_month_KWH}" != "null" ]; then curl_message="${curl_message}current_month_KWH=${current_month_KWH},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} current_month_KWH is null"; fi; fi -if [ "${avg_monthly_runs}" != "null" ]; then curl_message="${curl_message}avg_monthly_runs=${avg_monthly_runs},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} avg_monthly_runs is null"; fi; fi -if [ "${avg_monthly_KWH}" != "null" ]; then curl_message="${curl_message}avg_monthly_KWH=${avg_monthly_KWH},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} avg_monthly_KWH is null"; fi; fi -if [ "${avg_monthly_pct}" != "null" ]; then curl_message="${curl_message}avg_monthly_pct=${avg_monthly_pct},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} avg_monthly_pct is null"; fi; fi -if [ "${avg_watts}" != "null" ]; then curl_message="${curl_message}avg_watts=${avg_watts},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} avg_watts is null"; fi; fi -if [ "${avg_duration}" != "null" ]; then curl_message="${curl_message}avg_duration=${avg_duration},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} avg_duration is null"; fi; fi -if [ "${yearly_KWH}" != "null" ]; then curl_message="${curl_message}yearly_KWH=${yearly_KWH},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} yearly_KWH is null"; fi; fi -if [ "${yearly_text}" != "null" ]; then curl_message="${curl_message}yearly_text=\"${yearly_text}\","; else if [ "$debug_if" == "true" ]; then echo "name=${name} yearly_text is null"; fi; fi -if [ "${yearly_cost}" != "null" ]; then yearly_cost_dollars=$(echo "scale=2; ${yearly_cost}/100" | bc); curl_message="${curl_message}yearly_cost=${yearly_cost_dollars},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} yearly_cost is null"; fi; fi -if [ "${current_month_cost}" != "null" ]; then current_month_cost_dollars=$(echo "scale=2; ${current_month_cost}/100" | bc); curl_message="${curl_message}current_month_cost=${current_month_cost_dollars},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} current_month_cost is null"; fi; fi -if [ "${icon}" != "null" ]; then curl_message="${curl_message}icon=\"${icon}\","; else if [ "$debug_if" == "true" ]; then echo "name=${name} icon is null"; fi; fi -if [ "${last_state}" != "null" ]; then curl_message="${curl_message}last_state=\"${last_state}\","; else if [ "$debug_if" == "true" ]; then echo "name=${name} last_state is ${last_state} - null"; fi; fi -if [ -n "${last_state_time_epoch}" ]; then curl_message="${curl_message}last_state_time_epoch=${last_state_time_epoch},"; else if [ "$debug_if" == "true" ]; then echo "name=${name} last_state_time_epoch is empty"; fi; fi - -## -## Remove a trailing comma in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -curl_message="$(echo "${curl_message}" | sed 's/,$//')" - -if [ "$debug" == "true" ]; then echo "${curl_message}"; fi - -if [ "$curl_debug" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -#) & - -#if [[ $(jobs -r -p | wc -l) -ge $threads ]]; then wait -n; fi - -done - -#wait - -if [ "$debug" == "true" ]; then echo "Device Loop Finished"; fi - -## -## End "threading" -## - -## -## Always On -## - -url_sense_device="https://api.sense.com/apiservice/api/v1/app/monitors/${sense_monitor_id}/devices/always_on" - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -response_url_sense_device=$(curl "${curl[@]}" -k -H "Sense-Collector-Client-Version: 1.0.0" -H "X-Sense-Protocol: 3" -H "User-Agent: okhttp/3.8.0" -H "Authorization: bearer ${sense_token}" "${url_sense_device}") - -eval "$(echo "${response_url_sense_device}" | jq -r '.usage | {"avg_monthly_KWH", "avg_monthly_pct", "avg_watts", "yearly_KWH", "yearly_text", "yearly_cost", "avg_monthly_cost", "current_ao_wattage"} | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -if [ "$debug" == "true" ]; then - -echo " -avg_monthly_KWH=${avg_monthly_KWH} -avg_monthly_pct=${avg_monthly_pct} -avg_watts=${avg_watts} -yearly_KWH=${yearly_KWH} -yearly_text=${yearly_text} -yearly_cost=${yearly_cost} -avg_monthly_cost=${avg_monthly_cost} -current_ao_wattage=${current_ao_wattage} -" -fi - -curl_message="sense_devices,device_id=${devices[device_number]},name=Always\ On " - -if [ "${avg_monthly_KWH}" != "null" ]; then curl_message="${curl_message}avg_monthly_KWH=${avg_monthly_KWH},"; else if [ "$debug_if" == "true" ]; then echo "avg_monthly_KWH is null"; fi; fi -if [ "${avg_monthly_pct}" != "null" ]; then curl_message="${curl_message}avg_monthly_pct=${avg_monthly_pct},"; else if [ "$debug_if" == "true" ]; then echo "avg_monthly_pct is null"; fi; fi -if [ "${avg_watts}" != "null" ]; then curl_message="${curl_message}avg_watts=${avg_watts},"; else if [ "$debug_if" == "true" ]; then echo "avg_watts is null"; fi; fi -if [ "${yearly_KWH}" != "null" ]; then curl_message="${curl_message}yearly_KWH=${yearly_KWH},"; else if [ "$debug_if" == "true" ]; then echo "yearly_KWH is null"; fi; fi -if [ "${yearly_text}" != "null" ]; then curl_message="${curl_message}yearly_text=\"${yearly_text}\","; else if [ "$debug_if" == "true" ]; then echo "yearly_text is null"; fi; fi -if [ "${yearly_cost}" != "null" ]; yearly_cost_dollars=$(echo "scale=2; ${yearly_cost}/100" | bc); then curl_message="${curl_message}yearly_cost=${yearly_cost_dollars},"; else if [ "$debug_if" == "true" ]; then echo "yearly_cost is null"; fi; fi -if [ "${avg_monthly_cost}" != "null" ]; then curl_message="${curl_message}avg_monthly_cost=${avg_monthly_cost},"; else if [ "$debug_if" == "true" ]; then echo "avg_monthly_cost is null"; fi; fi -if [ "${current_ao_wattage}" != "null" ]; then curl_message="${curl_message}current_ao_wattage=${current_ao_wattage},"; else if [ "$debug_if" == "true" ]; then echo "current_ao_wattage is null"; fi; fi - -## -## Add our own Icon for Always On -## - -curl_message="${curl_message}icon=\"always_on\"" - -if [ "$debug" == "true" ]; then echo "${curl_message}"; fi - -if [ "$curl_debug" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -#wait - -## -## End Observations Timer -## - -observations_end=$(date +%s%N) -observations_duration=$((observations_end-observations_start)) - -if [ "$debug" == "true" ]; then echo "$(date) - observations_duration:${observations_duration}"; fi - -## -## Send Observations Metrics To InfluxDB -## - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="device_details",source=${collector_type} duration=${observations_duration}" - -fi \ No newline at end of file diff --git a/sense-collector/exec-host-performance.sh b/sense-collector/exec-host-performance.sh deleted file mode 100644 index 1de31dc..0000000 --- a/sense-collector/exec-host-performance.sh +++ /dev/null @@ -1,281 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - exec-host-performance.sh -## - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Set Specific Variables -## - -collector_type="host-performance" - -## -## Start Observations Timer -## - -observations_start=$(date +%s%N) - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -healthcheck=$SENSE_COLLECTOR_HEALTHCHECK -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME - -if [ "$debug" == "true" ] - -then - -echo "${echo_bold}${echo_color_host_performance}${collector_type}:${echo_normal} $(date) - Starting Sense Collector (exec-host-performance.sh) - https://github.com/lux4rd0/sense-collector - -Debug Environmental Variables - -collector_type=${collector_type} -debug=${debug} -debug_curl=${debug_curl} -healthcheck=${healthcheck} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -sense_collector_version=${sense_collector_version}" - -fi - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -## -## Health Check Function -## - -health_check - -## -## Escape Names (Function) -## - -escape_names - -## -## ╦ ╦┌─┐┌─┐┌┬┐ ╔╦╗┌─┐┌┬┐┬─┐┬┌─┐┌─┐ -## ╠═╣│ │└─┐ │ ║║║├┤ │ ├┬┘││ └─┐ -## ╩ ╩└─┘└─┘ ┴ ╩ ╩└─┘ ┴ ┴└─┴└─┘└─┘ -## - -memory=($(free -w)) - -mem_total=${memory[8]} -mem_used=${memory[9]} -mem_free=${memory[10]} -mem_shared=${memory[11]} -mem_buffers=${memory[12]} -mem_cache=${memory[13]} -mem_available=${memory[14]} -swap_total=${memory[16]} -swap_used=${memory[17]} -swap_free=${memory[18]} - -if [ "$debug" == "true" ] -then - -echo " -mem_total=${mem_total} -mem_used=${mem_used} -mem_free=${mem_free} -mem_shared=${mem_shared} -mem_buffers=${mem_buffers} -mem_cache=${mem_cache} -mem_available=${mem_available} -swap_total=${swap_total} -swap_used=${swap_used} -swap_free=${swap_free}" - -fi - -cpu=($(mpstat 1 1 | tail -1)) - -cpu_usr=${cpu[2]} -cpu_nice=${cpu[3]} -cpu_sys=${cpu[4]} -cpu_iowait=${cpu[5]} -cpu_irq=${cpu[6]} -cpu_soft=${cpu[7]} -cpu_steal=${cpu[8]} -cpu_guest=${cpu[9]} -cpu_gnice=${cpu[10]} -cpu_idle=${cpu[11]} - -if [ "$debug" == "true" ] -then - -echo " -cpu_usr=${cpu_usr} -cpu_nice=${cpu_nice} -cpu_sys=${cpu_sys} -cpu_iowait=${cpu_iowait} -cpu_irq=${cpu_irq} -cpu_soft=${cpu_soft} -cpu_steal=${cpu_steal} -cpu_guest=${cpu_guest} -cpu_gnice=${cpu_gnice} -cpu_idle=${cpu_idle}" - -fi - -loadavg=($(cat /proc/loadavg)) - -loadavg_one=${loadavg[0]} -loadavg_five=${loadavg[1]} -loadavg_fifteen=${loadavg[2]} - -if [ "$debug" == "true" ] -then - -echo " -loadavg_one=${loadavg_one} -loadavg_five=${loadavg_five} -loadavg_fifteen=${loadavg_fifteen}" - -fi - -processes=($(top -bn1 | grep zombie | awk '{print $4" "$6" "$8" "$10}')) - -processes_running=${processes[0]} -processes_sleeping=${processes[1]} -processes_stopped=${processes[2]} -processes_zombie=${processes[3]} - -if [ "$debug" == "true" ] -then - -echo " -processes_running=${processes_running} -processes_sleeping=${processes_sleeping} -processes_stopped=${processes_stopped} -processes_zombie=${processes_zombie}" - -fi - -## -## ╔╗╔┌─┐┌┬┐┌─┐┌┬┐┌─┐┌┬┐ ╦ ┌─┐┬┌─┬ ┬ ╦┌┐┌┌─┐┬ ┬ ┬─┐ ┬╔╦╗╔╗ -## ║║║├┤ │ └─┐ │ ├─┤ │ ─── ║ │ │├┴┐│ ┌┼─ ║│││├┤ │ │ │┌┴┬┘ ║║╠╩╗ -## ╝╚╝└─┘ ┴ └─┘ ┴ ┴ ┴ ┴ ╩═╝└─┘┴ ┴┴ └┘ ╩┘└┘└ ┴─┘└─┘┴ └─═╩╝╚═╝ -## - -influxdb_port=$(echo "$SENSE_COLLECTOR_INFLUXDB_URL" | cut -d ':' -f3 | cut -c1-4) -#loki_port=$(echo "$SENSE_COLLECTOR_LOKI_CLIENT_URL" | cut -d ':' -f3 | cut -c1-4) - -#loki_netstat=$(netstat -ant | grep "${loki_port}") -#loki_established=$(echo "${loki_netstat}" | grep -c ESTABLISHED ) -#loki_finwait2=$(echo "${loki_netstat}" | grep -c FIN_WAIT_2 ) -#loki_closewait=$(echo "${loki_netstat}" | grep -c CLOSE_WAIT ) -#loki_listen=$(echo "${loki_netstat}" | grep -c LISTENING ) -#loki_timewait=$(echo "${loki_netstat}" | grep -c TIME_WAIT ) - -influxdb_netstat=$(netstat -ant | grep "${influxdb_port}") -influxdb_established=$(echo "${influxdb_netstat}" | grep -c ESTABLISHED ) -influxdb_finwait2=$(echo "${influxdb_netstat}" | grep -c FIN_WAIT_2 ) -influxdb_closewait=$(echo "${influxdb_netstat}" | grep -c CLOSE_WAIT ) -influxdb_listen=$(echo "${influxdb_netstat}" | grep -c LISTENING ) -influxdb_timewait=$(echo "${influxdb_netstat}" | grep -c TIME_WAIT ) - -if [ "$debug" == "true" ] -then - -echo "loki_established=${loki_established} loki_finwait2=${loki_finwait2} loki_closewait=${loki_closewait} loki_listen=${loki_listen} loki_timewait=${loki_timewait}" -echo "influxdb_established=${influxdb_established} influxdb_finwait2=${influxdb_finwait2} influxdb_closewait=${influxdb_closewait} influxdb_listen=${influxdb_listen} influxdb_timewait=${influxdb_timewait}" - -fi - -## -## ╔═╗┌─┐┬─┐ ╔═╗┬─┐┌─┐┌─┐┌─┐┌─┐┌─┐ ╔═╗╔═╗╦ ╦ -## ╠═╝├┤ ├┬┘ ╠═╝├┬┘│ ││ ├┤ └─┐└─┐ ║ ╠═╝║ ║ -## ╩ └─┘┴└─ ╩ ┴└─└─┘└─┘└─┘└─┘└─┘ ╚═╝╩ ╚═╝ -## -## Derived from https://github.com/AraKhachatryan/top -## - -pid_array=$(ls /proc | grep -E '^[0-9]+$') -clock_ticks=$(getconf CLK_TCK) - -for pid in $pid_array -do -if [ -r /proc/"$pid"/stat ] -then - -stat_array=( $(sed -E 's/(\([^\s)]+)\s([^)]+\))/\1_\2/g' /proc/"$pid"/stat 2>/dev/null) ) - -## -## Sometimes the PID disappears before we can get some details about it. This exits the loop in that case. -## - -if [ -z "$stat_array" ]; then exit 0; fi - -uptime_array=( $(cat /proc/uptime) ) -comm=( $(grep -Pos '^[^\s\/]+' /proc/"$pid"/comm) ) - -if [ -z "$comm" ]; then exit 0; fi - -uptime=${uptime_array[0]} -ppid=${stat_array[3]} -utime=${stat_array[13]} -stime=${stat_array[14]} -cstime=${stat_array[16]} -starttime=${stat_array[21]} -total_time=$(( utime + stime )) -total_time=$(( total_time + cstime )) -seconds=$( awk 'BEGIN {print ( '"$uptime"' - ('"$starttime"' / '"$clock_ticks"') )}' ) -cpu_usage=$( awk 'BEGIN {print ( 100 * (('$total_time' / '"$clock_ticks"') / '"$seconds"') )}' ) - -## -## Send Per Process CPU Metrics To InfluxDB -## - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},source=${collector_type},sysperf_type=process_utilization,sysperf_command=${comm} pid=${pid},ppid=${ppid},uptime=${uptime},utime=${utime},stime=${stime},cstime=${cstime},starttime=${starttime},total_time=${total_time},seconds=${seconds},cpu_usage=${cpu_usage}" - -fi -done - -## -## Send CPU, Memory, and Netstat Metrics To InfluxDB -## - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},source=${collector_type},sysperf_type=cpu cpu_gnice=${cpu_gnice},cpu_guest=${cpu_guest},cpu_idle=${cpu_idle},cpu_iowait=${cpu_iowait},cpu_irq=${cpu_irq},cpu_nice=${cpu_nice},cpu_soft=${cpu_soft},cpu_steal=${cpu_steal},cpu_sys=${cpu_sys},cpu_usr=${cpu_usr} -sense_o11y,host_hostname=${host_hostname},source=${collector_type},sysperf_type=memory mem_available=${mem_available},mem_buffers=${mem_buffers},mem_cache=${mem_cache},mem_free=${mem_free},mem_shared=${mem_shared},mem_total=${mem_total},mem_used=${mem_used},swap_free=${swap_free},swap_total=${swap_total},swap_used=${swap_used} -sense_o11y,host_hostname=${host_hostname},source=${collector_type},sysperf_type=process_count processes_running=${processes_running},processes_sleeping=${processes_sleeping},processes_stopped=${processes_stopped},processes_zombie=${processes_zombie} -sense_o11y,host_hostname=${host_hostname},source=${collector_type},sysperf_type=loadavg loadavg_fifteen=${loadavg_fifteen},loadavg_five=${loadavg_five},loadavg_one=${loadavg_one} -sense_o11y,host_hostname=${host_hostname},source=${collector_type},sysperf_type=netstat,netstat_app=influxdb netstat_established=${influxdb_established},netstat_finwait2=${influxdb_finwait2},netstat_closewait=${influxdb_closewait},netstat_listen=${influxdb_listen},netstat_timewait=${influxdb_timewait}" - -## -## End Observations Timer -## - -observations_end=$(date +%s%N) -observations_duration=$((observations_end-observations_start)) - -if [ "$debug" == "true" ]; then echo "$(date) - observations_duration:${observations_duration}"; fi - -## -## Send Observations Metrics To InfluxDB -## - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="host_performance",source=${collector_type} duration=${observations_duration}" - -fi \ No newline at end of file diff --git a/sense-collector/exec-monitor-status.sh b/sense-collector/exec-monitor-status.sh deleted file mode 100644 index e7ac054..0000000 --- a/sense-collector/exec-monitor-status.sh +++ /dev/null @@ -1,284 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - exec-monitor-status.sh -## - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Set Specific Variables -## - -collector_type="monitor-status" - -## -## Start Observations Timer -## - -observations_start=$(date +%s%N) - -## -## Sense-Collector Details -## - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -debug_if=$SENSE_COLLECTOR_DEBUG_IF -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -sense_monitor_id=$SENSE_COLLECTOR_MONITOR_ID -sense_token=$SENSE_COLLECTOR_TOKEN - -if [ "$debug" == "true" ]; then - -echo "debug=${debug} -debug_curl=${debug_curl} -debug_if=${debug_if} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -sense_monitor_id=${sense_monitor_id} -sense_token=${sense_token}" - -fi - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -url_sense_get_status="https://api.sense.com/apiservice/api/v1/app/monitors/${sense_monitor_id}/status" - -response_url_sense_get_status=$(curl "${curl[@]}" -k -H "Sense-Collector-Client-Version: 1.0.0" -H "X-Sense-Protocol: 3" -H "User-Agent: okhttp/3.8.0" -H "Authorization: bearer ${sense_token}" "${url_sense_get_status}") - -#echo "${response_url_sense_get_status}" - -eval "$(echo "${response_url_sense_get_status}" | jq -r '.signals | {"progress", "status"}' | \ -jq -r '. | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -eval "$(echo "${response_url_sense_get_status}" | jq -r '.monitor_info | {"ethernet", "test_result", "serial", "emac", "ndt_enabled", "wifi_strength", "online", "ip_address", "version", "ssid", "signal", "mac"}' | \ -jq -r '. | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -if [ "$debug" == "true" ]; then - -echo " -progress=${progress} -status=${status} -ethernet=${ethernet} -test_result=${test_result} -serial=${serial} -emac=${emac} -ndt_enabled=${ndt_enabled} -wifi_strength=${wifi_strength} -online=${online} -ip_address=${ip_address} -version=${version} -ssid=${ssid} -signal=${signal} -mac=${mac} -" - -fi - -curl_message="sense_monitor_status,serial=${serial} " - -if [ "${progress}" != "null" ]; then curl_message="${curl_message}progress=${progress},"; else if [ "$debug_if" == "true" ]; then echo "progress is null"; fi; fi -if [ "${status}" != "null" ]; then curl_message="${curl_message}status=\"${status}\","; else if [ "$debug_if" == "true" ]; then echo "status is null"; fi; fi -if [ "${ethernet}" != "null" ]; then curl_message="${curl_message}ethernet=\"${ethernet}\","; else if [ "$debug_if" == "true" ]; then echo "ethernet is null"; fi; fi -if [ "${test_result}" != "null" ]; then curl_message="${curl_message}test_result=\"${test_result}\","; else if [ "$debug_if" == "true" ]; then echo "test_result is null"; fi; fi -if [ "${emac}" != "null" ]; then curl_message="${curl_message}emac=\"${emac}\","; else if [ "$debug_if" == "true" ]; then echo "emac is null"; fi; fi -if [ "${ndt_enabled}" != "null" ]; then curl_message="${curl_message}ndt_enabled=\"${ndt_enabled}\","; else if [ "$debug_if" == "true" ]; then echo "ndt_enabled is null"; fi; fi -if [ "${wifi_strength}" != "null" ]; then curl_message="${curl_message}wifi_strength=-${wifi_strength},"; else if [ "$debug_if" == "true" ]; then echo "wifi_strength is null"; fi; fi -if [ "${online}" != "null" ]; then curl_message="${curl_message}online=\"${online}\","; else if [ "$debug_if" == "true" ]; then echo "online is null"; fi; fi -if [ "${ip_address}" != "null" ]; then curl_message="${curl_message}ip_address=\"${ip_address}\","; else if [ "$debug_if" == "true" ]; then echo "ip_address is null"; fi; fi -if [ "${version}" != "null" ]; then curl_message="${curl_message}version=\"${version}\","; else if [ "$debug_if" == "true" ]; then echo "version is null"; fi; fi -if [ "${ssid}" != "null" ]; then curl_message="${curl_message}ssid=\"${ssid}\","; else if [ "$debug_if" == "true" ]; then echo "ssid is null"; fi; fi -if [ "${signal}" != "null" ]; then curl_message="${curl_message}signal=\"${signal}\","; else if [ "$debug_if" == "true" ]; then echo "signal is null"; fi; fi -if [ "${mac}" != "null" ]; then curl_message="${curl_message}mac=\"${mac}\","; else if [ "$debug_if" == "true" ]; then echo "mac is null"; fi; fi - -## -## Remove a trailing comma in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -curl_message="$(echo "${curl_message}" | sed 's/,$//')" - -if [ "$debug" == "true" ]; then - -echo "${curl_message}" - -fi - -if [ "$curl_debug" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -## -## Device Detection - In Progress -## - -number_of_devices_in_progress=$(echo "${response_url_sense_get_status}" | jq '.device_detection.in_progress | length') - -number_of_devices_in_progress_minus_one=$((number_of_devices_in_progress-1)) - -if [ "$debug" == "true" ]; then - -echo "number_of_devices_in_progress=${number_of_devices_in_progress}" - -fi - -for device_number in $(seq 0 $number_of_devices_in_progress_minus_one) ; do - -eval "$(echo "${response_url_sense_get_status}" | jq -r '.device_detection.in_progress['"$device_number"'] | {"icon", "name", "progress"}' | \ -jq -r '. | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -if [ "$debug" == "true" ]; then - -echo "Device Detection - In Progress" - -echo " -icon=${icon} -name=${name} -progress=${progress}" - -fi - -## -## Escape Names (Function) -## - -escape_names - -curl_message="sense_monitor_device_detection,detection_type=in_progress,name=${name_escaped},serial=${serial} " - -if [ "${icon}" != "null" ]; then curl_message="${curl_message}icon=\"${icon}\","; else if [ "$debug_if" == "true" ]; then echo "icon is null"; fi; fi -if [ "${progress}" != "null" ]; then curl_message="${curl_message}progress=${progress},"; else if [ "$debug_if" == "true" ]; then echo "progress is null"; fi; fi - -## -## Remove a trailing comma in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -curl_message="$(echo "${curl_message}" | sed 's/,$//')" - -if [ "$debug" == "true" ]; then - -echo "${curl_message}" - -fi - -if [ "$curl_debug" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -done - -## -## Device Detection - Found -## - -number_of_devices_found=$(echo "${response_url_sense_get_status}" | jq '.device_detection.found | length') - -number_of_devices_found_minus_one=$((number_of_devices_found-1)) - -if [ "$debug" == "true" ]; then - -echo "number_of_devices_found=${number_of_devices_found}" - -fi - -for device_number in $(seq 0 $number_of_devices_found_minus_one) ; do - -eval "$(echo "${response_url_sense_get_status}" | jq -r '.device_detection.found['"$device_number"'] | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -if [ "$debug" == "true" ]; then - -echo "Device Detection - Found" - -echo " -icon=${icon} -name=${name} -progress=${progress}" -fi - -## -## Escape Names (Function) -## - -escape_names - -curl_message="sense_monitor_device_detection,detection_type=found,name=${name_escaped},serial=${serial} " - -if [ "${icon}" != "null" ]; then curl_message="${curl_message}icon=\"${icon}\","; else if [ "$debug_if" == "true" ]; then echo "icon is null"; fi; fi -if [ "${progress}" != "null" ]; then curl_message="${curl_message}progress=${progress},"; else if [ "$debug_if" == "true" ]; then echo "progress is null"; fi; fi - -## -## Remove a trailing comma in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -curl_message="$(echo "${curl_message}" | sed 's/,$//')" - -if [ "$debug" == "true" ]; then - -echo "${curl_message}" - -fi - -if [ "$curl_debug" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -done - -## -## Device Detection - Number Detected -## - -num_detected=$(echo "${response_url_sense_get_status}" | jq -r '.device_detection.num_detected') - -if [ "$debug" == "true" ]; then - -echo "Device Detection - Number Detected" -echo "num_detected=${num_detected}" -fi - -curl_message="sense_monitor_device_detection,serial=${serial} num_detected=${num_detected}" - -if [ "$debug" == "true" ]; then - -echo "${curl_message}" - -fi - -if [ "$curl_debug" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -wait - -## -## End Observations Timer -## - -observations_end=$(date +%s%N) -observations_duration=$((observations_end-observations_start)) - -if [ "$debug" == "true" ]; then echo "$(date) - observations_duration:${observations_duration}"; fi - -## -## Send Observations Metrics To InfluxDB -## - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="monitor_status",source=${collector_type} duration=${observations_duration}" - -fi \ No newline at end of file diff --git a/sense-collector/exec-sense-collector.sh b/sense-collector/exec-sense-collector.sh deleted file mode 100644 index cd18f40..0000000 --- a/sense-collector/exec-sense-collector.sh +++ /dev/null @@ -1,576 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - start-sense-collector.sh -## - -## -## Set Specific Variables -## - -collector_type="sense-collector" - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Sense-Collector Details -## - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -debug_if=$SENSE_COLLECTOR_DEBUG_IF -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -poll_interval=$SENSE_COLLECTOR_SENSE_COLLECTOR_POLL_INTERVAL -sense_monitor_id=$SENSE_COLLECTOR_MONITOR_ID -sense_token=$SENSE_COLLECTOR_TOKEN -threads=$SENSE_COLLECTOR_THREADS - -## -## Set Threads -## - -if [ -z "${threads}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_THREADS${echo_normal} environmental variable not set. Defaulting to ${echo_bold}4${echo_normal} threads."; threads="4"; export SENSE_COLLECTOR_THREADS="4"; fi - -if [ "$debug" == "true" ]; then - -echo "debug=${debug} -debug_curl=${debug_curl} -debug_if=${debug_if} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -poll_interval=${poll_interval} -sense_monitor_id=${sense_monitor_id} -sense_token=${sense_token} -threads=${threads}" - -fi - -## -## Set New Line Variable -## - -NL=$'\n' - -## -## Testing -## - -#debug=true -#debug_curl=true - -## -## Read Socket -## - -while read -r line; do - -if [ "$debug" == "true" ] -then - -echo "" -echo "${line}" -echo "" - -fi - -## -## Get Power Mains Metrics -## - -## -## ┬─┐┌─┐┌─┐┬ ┌┬┐┬┌┬┐┌─┐ ┬ ┬┌─┐┌┬┐┌─┐┌┬┐┌─┐ -## ├┬┘├┤ ├─┤│ │ ││││├┤ │ │├─┘ ││├─┤ │ ├┤ -## ┴└─└─┘┴ ┴┴─┘┴ ┴┴ ┴└─┘────└─┘┴ ─┴┘┴ ┴ ┴ └─┘ -## - -if [[ $line == *"realtime_update"* ]]; then - - -## -## Process realtime_update by streaming or on a polling interval -## - -poll_check=$(date +"%S") - -if [ "$debug" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} poll_check=${poll_check}"; fi - -if [ "$poll_interval" == "5" ]; then - -if [ "$poll_check" == "00" ] || [ "$poll_check" == "05" ] || [ "$poll_check" == "10" ] || [ "$poll_check" == "15" ] || [ "$poll_check" == "20" ] || \ -[ "$poll_check" == "25" ] || [ "$poll_check" == "30" ] || [ "$poll_check" == "35" ] || [ "$poll_check" == "40" ] || [ "$poll_check" == "45" ] || \ -[ "$poll_check" == "50" ] || [ "$poll_check" == "55" ]; then run_collector="true"; if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Running Collector Poll - ${echo_bold}5${echo_normal} seconds."; fi; else continue; fi - -fi - -if [ "$poll_interval" == "10" ]; then - -if [ "$poll_check" == "00" ] || [ "$poll_check" == "10" ] || [ "$poll_check" == "20" ] || [ "$poll_check" == "30" ] || [ "$poll_check" == "40" ] || \ -[ "$poll_check" == "50" ]; then run_collector="true"; if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Running Collector Poll - ${echo_bold}10${echo_normal} seconds."; fi; else continue; fi - -fi - -if [ "$poll_interval" == "15" ]; then - -if [ "$poll_check" == "00" ] || [ "$poll_check" == "15" ] || [ "$poll_check" == "30" ] || [ "$poll_check" == "45" ]; then run_collector="true"; if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Running Collector Poll - ${echo_bold}15${echo_normal} seconds."; fi; else continue; fi - -fi - -if [ "$poll_interval" == "30" ]; then - -if [ "$poll_check" == "00" ] || [ "$poll_check" == "30" ]; then run_collector="true"; if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Running Collector Poll - ${echo_bold}30${echo_normal} seconds."; fi; else continue; fi - -fi - -if [ "$poll_interval" == "60" ]; then - -if [ "$poll_check" == "00" ]; then run_collector="true"; if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Running Collector Poll - ${echo_bold}60${echo_normal} seconds."; fi; else continue; fi - -fi - -## -## Start Observations Timer -## - -observations_start=$(date +%s%N) - -## -## ╔╦╗┌─┐┬┌┐┌┌─┐ -## ║║║├─┤││││└─┐ -## ╩ ╩┴ ┴┴┘└┘└─┘ -## - -eval "$(echo "${line}" | jq -r '.payload | {"hz", "w", "c", "d_w", "epoch"} | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -voltage=($(echo "${line}" | jq -r '.payload.voltage | @sh') ) -watts=($(echo "${line}" | jq -r '.payload.channels | @sh') ) - -if [ "$debug" == "true" ]; then - -echo " -voltage=${voltage[0]}, ${voltage[1]} -watts=${watts[0]}, ${watts[1]} -hz=${hz} -w=${w} -c=${c} -epoch=${epoch}" - -echo "epoch top: ${epoch}" -fi - -## -## Clear Curl Message -## - -if [ "${epoch}" != "null" ]; then curl_message=""; else -if [ "$debug_if" == "true" ]; then echo "epoch is null. Skipping realtime_update"; fi; continue; fi - -if [ "${voltage[0]}" != "null" ]; then curl_message="${curl_message}sense_mains,leg=L1 voltage=${voltage[0]} ${epoch}${NL}"; else if [ "$debug_if" == "true" ]; then echo "leg=L1 voltage is ${voltage[0]}."; fi; continue; fi -if [ "${voltage[1]}" != "null" ]; then curl_message="${curl_message}sense_mains,leg=L2 voltage=${voltage[1]} ${epoch}${NL}"; else if [ "$debug_if" == "true" ]; then echo "leg=L2 voltage is ${voltage[1]}."; fi; continue; fi -if [ "${watts[0]}" != "null" ]; then curl_message="${curl_message}sense_mains,leg=L1 watts=${watts[0]} ${epoch}${NL}"; else if [ "$debug_if" == "true" ]; then echo "leg=L1 watts is ${watts[0]}."; fi; continue; fi -if [ "${watts[1]}" != "null" ]; then curl_message="${curl_message}sense_mains,leg=L2 watts=${watts[1]} ${epoch}${NL}"; else if [ "$debug_if" == "true" ]; then echo "leg=L2 watts is ${watts[1]}."; fi; continue; fi -if [ "${hz}" != "null" ]; then curl_message="${curl_message}sense_mains hz=${hz} ${epoch}${NL}"; else if [ "$debug_if" == "true" ]; then echo "hz is ${hz}."; fi; continue; fi -if [ "${c}" != "null" ]; then curl_message="${curl_message}sense_mains c=${c} ${epoch}${NL}"; else if [ "$debug_if" == "true" ]; then echo "c is ${c}."; fi; continue; fi - -## -## Remove a new line in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -curl_message="$(echo "${curl_message}" | sed 's/\r$//')" - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -if [ "$debug" == "true" ]; then echo "${curl_message}"; fi - -## -## Set InfluxDB Precision to Seconds to use the epoch time from Sense -## - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}&precision=s" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -## -## End Observations Timer -## - -observations_end=$(date +%s%N) -observations_duration=$((observations_end-observations_start)) - -if [ "$debug" == "true" ]; then echo "$(date) - observations_duration:${observations_duration}"; fi - -## -## Send Observations Metrics To InfluxDB -## - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="realtime_update_mains",source=${collector_type} duration=${observations_duration}" - -fi - -## Mac -#curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}&precision=s" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -#server_epoch=$(date +%s) -#difference_epoch=$((server_epoch-epoch)) - -#echo "MAINS: server_epoch=${server_epoch} sense_epoch=${epoch} difference_epoch=${difference_epoch}" - -#continue; - -## -## ╔╦╗┌─┐┬ ┬┬┌─┐┌─┐┌─┐ -## ║║├┤ └┐┌┘││ ├┤ └─┐ -## ═╩╝└─┘ └┘ ┴└─┘└─┘└─┘ -## - - -## -## Start Observations Timer -## - -observations_start=$(date +%s%N) - - -## -## Number of devices -## - -num_of_devices=$(echo "${line}" | jq -r '.payload.devices | length') -num_of_devices_minus_one=$((num_of_devices-1)) - -## -## Clear variables from prior loop -## - -curl_message="" -sd="null" -i="null" -v="null" -e="null" - -## -## Start "threading" -## - -for device in $(seq 0 $num_of_devices_minus_one); do - -( - -eval "$(echo "${line}" | jq -r '.payload.devices['"${device}"'] | {"id", "name", "w", "sd", "ao_w"} | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -## -## Check the ${sd} variable to see if here are additional details for discovered plugs. If found - we'll overwrite the ${w} value since it should be the same. -## - -if [ "${sd}" != "null" ]; then if [ "$debug" == "true" ]; then echo "name=${name}, sd=${sd}"; fi; eval "$(echo "${line}" | jq -r '.payload.devices['"${device}"'].sd | {"w", "i", "v", "e"} | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')"; fi - -curl_message="${curl_message}sense_devices," - -if [ "${id}" != "null" ]; then curl_message="${curl_message}id=${id},"; else echo "id is null. Skipping device."; continue; fi - -## -## Escape Names (Function) -## - -if [ "${name}" != "null" ]; then escape_names; curl_message="${curl_message}name=${name_escaped},"; else if [ "$debug_if" == "true" ]; then echo "${name} - id is null. Skipping device."; continue; fi; fi - -if [ "${sd}" != "null" ]; then curl_message="${curl_message}is_plug=true,"; else curl_message="${curl_message}is_plug=false,"; fi -if [ "${ao_w}" != "null" ]; then curl_message="${curl_message}always_on=true,"; else curl_message="${curl_message}always_on=false,"; fi - -## -## Remove a trailing comma and add a space in curl_message -## - -curl_message="$(echo "${curl_message}" | sed 's/,$/ /')" - -if [ "${i}" != "null" ]; then curl_message="${curl_message}i=${i},"; else if [ "$debug_if" == "true" ]; then echo "${name} - i is null."; fi; fi -if [ "${v}" != "null" ]; then curl_message="${curl_message}v=${v},"; else if [ "$debug_if" == "true" ]; then echo "${name} - v is null."; fi; fi -if [ "${e}" != "null" ]; then curl_message="${curl_message}e=${e},"; else if [ "$debug_if" == "true" ]; then echo "${name} - e is null."; fi; fi -if [ "${ao_w}" != "null" ]; then curl_message="${curl_message}ao_w=${ao_w},"; else if [ "$debug_if" == "true" ]; then echo "${name} - ao_w is null."; fi; fi - -if [ "${w}" != "null" ]; then curl_message="${curl_message}current_watts=${w}"; else if [ "$debug_if" == "true" ]; then echo "${name} - w is null. Skipping device."; fi; continue; fi - -## -## Add Epoch Time -## - -curl_message="${curl_message} ${epoch}" - -if [ "$debug" == "true" ]; then echo "device=${device}, device_id=${id}, name_escaped=${name_escaped}, current_watts=${w}, i=${i}, v=${v}, e=${e}"; fi - -## -## Remove a trailing comma in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -#curl_message="$(echo "${curl_message}" | sed 's/,$//')" - -#echo "curl_message=${curl_message}" - -#curl_message="$(echo "${curl_message}" | sed 's/\r$//')" - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -## -## Set InfluxDB Precision to Seconds to use the epoch time from Sense -## - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}&precision=s" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -## -## Clear ${sd} -## - -sd="null" -i="null" -v="null" -e="null" - -## Mac -#curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}&precision=s" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -) & - -if [[ $(jobs -r -p | wc -l) -ge $threads ]]; then wait -n; fi - -done - -wait -#echo "curl_message=${curl_message}" - -#echo "Device Loop Finished" - -#if [ "$debug" == "true" ]; then echo "Device Loop Finished"; fi - -## -## End "threading" -## - -## -## Remove a new line in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -curl_message="$(echo "${curl_message}" | sed 's/\r$//')" - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -## -## Set InfluxDB Precision to Seconds to use the epoch time from Sense -## - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}&precision=s" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -#curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}&precision=s" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -## -## Send Epoch Drift Metrics To InfluxDB -## - -server_epoch=$(date +%s) -difference_epoch=$((server_epoch-epoch)) - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="realtime_update_devices",source=${collector_type} difference_epoch=${difference_epoch}" - -fi - -## -## End Observations Timer -## - -observations_end=$(date +%s%N) -observations_duration=$((observations_end-observations_start)) - -if [ "$debug" == "true" ]; then echo "$(date) - observations_duration:${observations_duration}"; fi - -## -## Send Observations Metrics To InfluxDB -## - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " - -sense_o11y,host_hostname=${host_hostname},function="realtime_update_devices",source=${collector_type} duration=${observations_duration}" - -fi - -fi - -## -## ┌┐┌┌─┐┬ ┬ ┌┬┐┬┌┬┐┌─┐┬ ┬┌┐┌┌─┐ ┌─┐┬ ┬┌─┐┌┐┌┌┬┐ -## │││├┤ │││ │ ││││├┤ │ ││││├┤ ├┤ └┐┌┘├┤ │││ │ -## ┘└┘└─┘└┴┘────┴ ┴┴ ┴└─┘┴─┘┴┘└┘└─┘────└─┘ └┘ └─┘┘└┘ ┴ -## - -if [[ $line == *"new_timeline_event"* ]]; then - -## -## Start Observations Timer -## - -observations_start=$(date +%s%N) - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -eval "$(echo "${line}" | jq -r '.payload.items_added[] | {"time", "type", "icon", "body", "device_id", "device_state", "user_device_type", "device_transition_from_state"} | to_entries | .[] | .key + "=" + "\"" + ( .value|tostring ) + "\""')" - -if [ "$debug" == "true" ]; then - -echo " -time=${time} -type=${type} -icon=${icon} -body=${body} -device_id=${device_id} -device_state=${device_state} -user_device_type=${user_device_type} -device_transition_from_state=${device_transition_from_state}" -fi - -if [ "${device_id}" != "null" ]; then url_sense_device="https://api.sense.com/apiservice/api/v1/app/monitors/${sense_monitor_id}/devices/${device_id}"; else if [ "$debug_if" == "true" ]; then echo "device_id is null. Skipping device."; fi; continue; fi - -if [ "$debug" == "true" ]; then echo "url_sense_device=${url_sense_device}"; fi - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -response_url_sense_device=$(curl "${curl[@]}" -k -H "Sense-Collector-Client-Version: 1.0.0" -H "X-Sense-Protocol: 3" -H "User-Agent: okhttp/3.8.0" -H "Authorization: bearer ${sense_token}" "${url_sense_device}") - -if [ "$debug" == "true" ]; then echo "response_url_sense_device=${response_url_sense_device}"; fi - -name=$(echo "${response_url_sense_device}" | jq -r '.device.name') - -curl_message="sense_event," - -if [ "${device_id}" != "null" ]; then curl_message="device_id=${device_id},"; else echo "device_id is null. Skipping device."; continue; fi - -curl_message="${curl_message}event_type=new_timeline," - -## -## Escape Names (Function) -## - -if [ "${name}" != "null" ]; then escape_names; curl_message="name=${name_escaped} "; else echo "name is null. Skipping device."; continue; fi - -if [ "${time}" != "null" ]; then time_epoch_ns=$(date -d"${time}" +%s%N ); time_epoch=$(date -d"${time}" +%s%3N ); curl_message="${curl_message}time=${time},"; else if [ "$debug_if" == "true" ]; then echo "time is null. Skipping device."; fi; continue; fi -if [ "${type}" != "null" ]; then curl_message="${curl_message}type=\"${type}\","; else if [ "$debug_if" == "true" ]; then echo "type is null. Skipping device."; fi; continue; fi -if [ "${icon}" != "null" ]; then curl_message="${curl_message}icon=\"${icon}\","; else if [ "$debug_if" == "true" ]; then echo "icon is null. Skipping device."; fi; continue; fi -if [ "${body}" != "null" ]; then curl_message="${curl_message}body=\"${body}\","; else if [ "$debug_if" == "true" ]; then echo "body is null. Skipping device."; fi; continue; fi - -if [ "${device_state}" != "null" ]; then curl_message="${curl_message}device_state=${device_state},"; else if [ "$debug_if" == "true" ]; then echo "device_state is null. Skipping device."; fi; continue; fi -if [ "${user_device_type}" != "null" ]; then curl_message="${curl_message}user_device_type=${user_device_type},"; else if [ "$debug_if" == "true" ]; then echo "user_device_type is null. Skipping device."; fi; continue; fi -if [ "${device_transition_from_state}" != "null" ]; then curl_message="${curl_message}device_transition_from_state=${device_transition_from_state},"; else if [ "$debug_if" == "true" ]; then echo "device_transition_from_state is null. Skipping device."; fi; continue; fi -if [ "${time_epoch}" != "null" ]; then curl_message="${curl_message}time_epoch=${time_epoch},"; else if [ "$debug_if" == "true" ]; then echo "time_epoch is null. Skipping device."; fi; continue; fi - -## -## Remove a comma in curl_message if the last element happens to be null (so that there's still a properly formatted InfluxDB mmessage) -## - -curl_message="$(echo "${curl_message}" | sed 's/,$//')" -curl_message="${curl_message} ${time_epoch_ns}" - -curl_message="sense_event,device_id=${device_id},event_type=new_timeline,name=${name_escaped} type=\"${type}\",icon=\"${icon}\",body=\"${body}\",device_state=\"${device_state}\",user_device_type=\"${user_device_type}\",device_transition_from_state=\"${device_transition_from_state}\",time_epoch=${time_epoch} ${time_epoch_ns}" - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "${curl_message}" & - -## -## End Observations Timer -## - -observations_end=$(date +%s%N) -observations_duration=$((observations_end-observations_start)) - -if [ "$debug" == "true" ]; then echo "$(date) - observations_duration:${observations_duration}"; fi - -## -## Send Timer Metrics To InfluxDB -## - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="new_timeline_event",source=${collector_type} duration=${observations_duration}" - -fi - -fi - -## -## ┬ ┬┌─┐┬ ┬ ┌─┐ -## ├─┤├┤ │ │ │ │ -## ┴ ┴└─┘┴─┘┴─┘└─┘ -## - -if [[ $line == *"hello"* ]]; then - -## -## Start Observations Timer -## - -observations_start=$(date +%s%N) - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -time_epoch=$(date +%s%3N) - -if [ "$debug" == "true" ]; then echo "sense_event,event_type=hello time_epoch=${time_epoch}"; fi - -/usr/bin/timeout -k 1 10s curl "${curl[@]}" --connect-timeout 2 --max-time 2 --retry 5 --retry-delay 0 --retry-max-time 30 -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary "sense_event,event_type=hello time_epoch=${time_epoch}" & - -## -## End Observations Timer -## - -observations_end=$(date +%s%N) -observations_duration=$((observations_end-observations_start)) - -if [ "$debug" == "true" ]; then echo "$(date) - observations_duration:${observations_duration}"; fi - -## -## Send Timer Metrics To InfluxDB -## - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="hello",source=${collector_type} duration=${observations_duration}" - -fi - -fi - -done < /dev/stdin \ No newline at end of file diff --git a/sense-collector/sense-collector-details.sh b/sense-collector/sense-collector-details.sh deleted file mode 100644 index fcfe0ba..0000000 --- a/sense-collector/sense-collector-details.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - sense-collector-details.sh -## - -sense_collector_version="1.0.1" -#grafana_loki_binary_path="./promtail-linux-amd64" -grafana_loki_binary_path="/usr/bin/promtail" -debug_sleeping=$SENSE_COLLECTOR_DEBUG_SLEEPING - -## -## Echo Details -## - -echo_bold=$(tput -T xterm bold) -echo_blink=$(tput -T xterm blink) -echo_black=$(tput -T xterm setaf 0) -echo_blue=$(tput -T xterm setaf 4) -echo_dim=$(tput -T xterm dim) - -echo_color_random=$(echo -e "\e[3$(( $RANDOM * 6 / 32767 + 1 ))m") - -echo_normal=$(tput -T xterm sgr0) - -## -## Functions -## - -## -## ┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐ ┌┐┌┌─┐┌┬┐┌─┐┌─┐ -## ├┤ └─┐│ ├─┤├─┘├┤ │││├─┤│││├┤ └─┐ -## └─┘└─┘└─┘┴ ┴┴ └─┘────┘└┘┴ ┴┴ ┴└─┘└─┘ -## - -function escape_names { - -## -## Spaces -## - -name_escaped="${name// /\\ }" - -## -## Commas -## - -name_escaped="${name_escaped//,/\\,}" - -## -## Equal Signs -## - -name_escaped="${name_escaped//=/\\=}" - -} - -## -## ┬ ┬┌─┐┌─┐┬ ┌┬┐┬ ┬ ┌─┐┬ ┬┌─┐┌─┐┬┌─ -## ├─┤├┤ ├─┤│ │ ├─┤ │ ├─┤├┤ │ ├┴┐ -## ┴ ┴└─┘┴ ┴┴─┘┴ ┴ ┴────└─┘┴ ┴└─┘└─┘┴ ┴ -## - -function health_check () { - -if [ "$healthcheck" == "true" ]; then health_check_file="health-check-${collector_type}.txt"; touch "${health_check_file}"; fi - -} - -## -## ┌─┐┬─┐┌─┐┌─┐┌─┐┌─┐┌─┐ ┌─┐┌┬┐┌─┐┬─┐┌┬┐ -## ├─┘├┬┘│ ││ ├┤ └─┐└─┐ └─┐ │ ├─┤├┬┘ │ -## ┴ ┴└─└─┘└─┘└─┘└─┘└─┘────└─┘ ┴ ┴ ┴┴└─ ┴ -## - -## -## Send Startup Event Timestamp to InfluxDB -## - -function process_start () { - -if [ "$curl_debug" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -current_time=$(date +%s) - -#echo "${bold}${collector_type}:${normal} time_epoch: ${current_time}" - -if [ -n "$influxdb_url" ]; then - -curl "${curl[@]}" -i -XPOST "${influxdb_url}" -u "${influxdb_username}":"${influxdb_password}" --data-binary " -sense_o11y,host_hostname=${host_hostname},function="process_start",source=${collector_type},type=event time_epoch=${current_time}000" - -fi - -} \ No newline at end of file diff --git a/sense-collector/sense-collector-init.sh b/sense-collector/sense-collector-init.sh deleted file mode 100644 index e80f20b..0000000 --- a/sense-collector/sense-collector-init.sh +++ /dev/null @@ -1,161 +0,0 @@ -#!/bin/bash - -## -## Sense Startup -## - -## -## Set Specific Variables -## - -collector_type="sense-init" - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -echo " - - ███████╗███████╗███╗ ██╗███████╗███████╗ - ██╔════╝██╔════╝████╗ ██║██╔════╝██╔════╝ - ███████╗█████╗ ██╔██╗ ██║███████╗█████╗ - ╚════██║██╔══╝ ██║╚██╗██║╚════██║██╔══╝ - ███████║███████╗██║ ╚████║███████║███████╗ - ╚══════╝╚══════╝╚═╝ ╚═══╝╚══════╝╚══════╝ - - ██████╗ ██████╗ ██╗ ██╗ ███████╗ ██████╗████████╗ ██████╗ ██████╗ -██╔════╝██╔═══██╗██║ ██║ ██╔════╝██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗ -██║ ██║ ██║██║ ██║ █████╗ ██║ ██║ ██║ ██║██████╔╝ -██║ ██║ ██║██║ ██║ ██╔══╝ ██║ ██║ ██║ ██║██╔══██╗ -╚██████╗╚██████╔╝███████╗███████╗███████╗╚██████╗ ██║ ╚██████╔╝██║ ██║ - ╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ - -" - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -disable_device_details=$SENSE_COLLECTOR_DISABLE_DEVICE_DETAILS -disable_health_check=$SENSE_COLLECTOR_DISABLE_HEALTH_CHECK -disable_host_performance=$SENSE_COLLECTOR_DISABLE_HOST_PERFORMANCE -disable_monitor_status=$SENSE_COLLECTOR_DISABLE_MONITOR_STATUS -disable_sense_collector=$SENSE_COLLECTOR_DISABLE_SENSE_COLLECTOR -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -poll_interval=$SENSE_COLLECTOR_POLL_INTERVAL -sense_monitor_id=$SENSE_COLLECTOR_MONITOR_ID -sense_token=$SENSE_COLLECTOR_TOKEN - -if [ "$debug" == "true" ]; then - -echo "debug=${debug} -debug_curl=${debug_curl} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -sense_monitor_id=${sense_monitor_id} -sense_token=${sense_token}" -fi - -## -## Curl Command -## - -#if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --output /dev/null --show-error --fail ); fi - -## -## :::::::: :::::::: ::: ::: :::::::::: :::::::: ::::::::::: :::::::: ::::::::: -## :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: -## +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ -## +#+ +#+ +:+ +#+ +#+ +#++:++# +#+ +#+ +#+ +:+ +#++:++#: -## +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ -## #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# -## ######## ######## ########## ########## ########## ######## ### ######## ### ### -## - - -## -## ┬ ┬┌─┐┌─┐┌┬┐ ┌─┐┌─┐┬─┐┌─┐┌─┐┬─┐┌┬┐┌─┐┌┐┌┌─┐┌─┐ -## ├─┤│ │└─┐ │───├─┘├┤ ├┬┘├┤ │ │├┬┘│││├─┤││││ ├┤ -## ┴ ┴└─┘└─┘ ┴ ┴ └─┘┴└─└ └─┘┴└─┴ ┴┴ ┴┘└┘└─┘└─┘ -## - -if [ "$disable_host_performance" != "true" ]; then - -while : ; do echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Starting ${echo_bold}Host Performance${echo_normal}"; ./start-host-performance.sh ; done & -else -echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_DISABLE_HOST_PERFORMANCE${echo_normal} set to \"true\" or ${echo_bold}SENSE_COLLECTOR_INFLUXDB_URL${echo_normal} is missing. Disabling ${echo_color_start}host-performance${echo_normal}." -export SENSE_COLLECTOR_DISABLE_HOST_PERFORMANCE="true" -fi - -## -## ┬ ┬┌─┐┌─┐┬ ┌┬┐┬ ┬ ┌─┐┬ ┬┌─┐┌─┐┬┌─ -## ├─┤├┤ ├─┤│ │ ├─┤───│ ├─┤├┤ │ ├┴┐ -## ┴ ┴└─┘┴ ┴┴─┘┴ ┴ ┴ └─┘┴ ┴└─┘└─┘┴ ┴ -## - -if [ "$disable_health_check" != "true" ]; then - -while : ; do echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Starting ${echo_bold}Health Check${echo_normal}"; ./start-health-check.sh ; done & -else -echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_DISABLE_HEALTH_CHECK${echo_normal} set to \"true\" or ${echo_bold}SENSE_COLLECTOR_INFLUXDB_URL${echo_normal} is missing. Disabling ${echo_color_start}health-check${echo_normal}." -export SENSE_COLLECTOR_DISABLE_HEALTH_CHECK="true" -fi - -## -## ┌┬┐┌─┐┬ ┬┬┌─┐┌─┐ ┌┬┐┌─┐┌┬┐┌─┐┬┬ ┌─┐ -## ││├┤ └┐┌┘││ ├┤───││├┤ │ ├─┤││ └─┐ -## ─┴┘└─┘ └┘ ┴└─┘└─┘ ─┴┘└─┘ ┴ ┴ ┴┴┴─┘└─┘ -## - -if [ "$disable_device_details" != "true" ]; then - -while : ; do echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Starting ${echo_bold}Device Details${echo_normal}"; ./start-device-details.sh ; done & -else -echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_DISABLE_DEVICE_DETAILS${echo_normal} set to \"true\" or ${echo_bold}SENSE_COLLECTOR_INFLUXDB_URL${echo_normal} is missing. Disabling ${echo_color_start}device-details${echo_normal}." -export SENSE_COLLECTOR_DISABLE_DEVICE_DETAILS="true" -fi - -## -## ┌┬┐┌─┐┌┐┌┬┌┬┐┌─┐┬─┐ ┌─┐┌┬┐┌─┐┌┬┐┬ ┬┌─┐ -## ││││ │││││ │ │ │├┬┘───└─┐ │ ├─┤ │ │ │└─┐ -## ┴ ┴└─┘┘└┘┴ ┴ └─┘┴└─ └─┘ ┴ ┴ ┴ ┴ └─┘└─┘ -## - -if [ "$disable_monitor_status" != "true" ]; then - -while : ; do echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Starting ${echo_bold}Monitor Status${echo_normal}"; ./start-monitor-status.sh ; done & -else -echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_DISABLE_MONITOR_STATUS${echo_normal} set to \"true\" or ${echo_bold}SENSE_COLLECTOR_INFLUXDB_URL${echo_normal} is missing. Disabling ${echo_color_start}monitor-status${echo_normal}." -export SENSE_COLLECTOR_DISABLE_MONITOR_STATUS="true" -fi - -## -## ┌─┐┌─┐┌┐┌┌─┐┌─┐ ┌─┐┌─┐┬ ┬ ┌─┐┌─┐┌┬┐┌─┐┬─┐ -## └─┐├┤ │││└─┐├┤───│ │ ││ │ ├┤ │ │ │ │├┬┘ -## └─┘└─┘┘└┘└─┘└─┘ └─┘└─┘┴─┘┴─┘└─┘└─┘ ┴ └─┘┴└─ -## - -if [ "$disable_sense_collector" != "true" ]; then - -while : ; do echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Starting ${echo_bold}Sense Collector${echo_normal}"; ./start-sense-collector.sh ; done & -else -echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_DISABLE_SENSE_COLLECTOR${echo_normal} set to \"true\" or ${echo_bold}SENSE_COLLECTOR_INFLUXDB_URL${echo_normal} is missing. Disabling ${echo_color_start}sense-collector${echo_normal}." -export SENSE_COLLECTOR_DISABLE_SENSE_COLLECTOR="true" -fi - -## -## ┌─┐┌─┐┌┐┌┌─┐┌─┐ ┬┌┐┌┬┌┬┐ -## └─┐├┤ │││└─┐├┤───│││││ │ -## └─┘└─┘┘└┘└─┘└─┘ ┴┘└┘┴ ┴ -## - -## -## Used to keep dumb-init running -## - -while : ; do sleep 69; done \ No newline at end of file diff --git a/sense-collector/start-device-details.sh b/sense-collector/start-device-details.sh deleted file mode 100644 index 332fc8e..0000000 --- a/sense-collector/start-device-details.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - start-device-details.sh -## - -## -## Set Specific Variables -## - -collector_type="device-details" - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Set Variables from Environmental Variables -## - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -debug_sleeping=$SENSE_COLLECTOR_DEBUG_SLEEPING -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -poll_interval=$SENSE_COLLECTOR_DEVICE_DETAILS_POLL_INTERVAL -sense_monitor_id=$SENSE_COLLECTOR_MONITOR_ID -sense_token=$SENSE_COLLECTOR_TOKEN -threads=$SENSE_COLLECTOR_THREADS - -## -## Check for required intervals -## - -if [ -z "${poll_interval}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_DEVICE_DETAILS_POLL_INTERVAL${echo_normal} environmental variable not set. Defaulting to ${echo_bold}60${echo_normal} seconds."; poll_interval="60"; export SENSE_COLLECTOR_POLL_INTERVAL="60"; fi - -if [ -z "${host_hostname}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_HOST_HOSTNAME${echo_normal} environmental variable not set. Defaulting to ${echo_bold}sense-collector${echo_normal}."; host_hostname="sense-collector"; export SENSE_COLLECTOR_HOST_HOSTNAME="sense-collector"; fi - -if [ -z "${threads}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_THREADS${echo_normal} environmental variable not set. Defaulting to ${echo_bold}4${echo_normal} threads."; threads="4"; export SENSE_COLLECTOR_THREADS="4"; fi - -if [ "$debug" == "true" ] - -then - -echo "$(date) - Starting Sense Collector (start-device-details.sh) - https://github.com/lux4rd0/sense-collector - -Debug Environmental Variables - -debug=${debug} -debug_curl=${debug_curl} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -poll_interval=${poll_interval} -sense_monitor_id=${sense_monitor_id} -sense_token=${sense_token}" -fi - -## -## Send Startup Event Timestamp to InfluxDB -## - -process_start - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -## -## Start Sense Device Details Loop -## - -while ( true ); do -before=$(date +%s%N) - -./exec-device-details.sh - -after=$(date +%s%N) -delay=$(echo "scale=4;(${poll_interval}-($after-$before) / 1000000000)" | bc) - -if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Sleeping: ${delay} seconds"; fi - -sleep "$delay" -done \ No newline at end of file diff --git a/sense-collector/start-host-performance.sh b/sense-collector/start-host-performance.sh deleted file mode 100644 index 8bc263a..0000000 --- a/sense-collector/start-host-performance.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - start-host-performance.sh -## - -## -## Set Specific Variables -## - -collector_type="host-performance" - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Set Variables from Environmental Variables -## - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -debug_sleeping=$SENSE_COLLECTOR_DEBUG_SLEEPING -function=$SENSE_COLLECTOR_FUNCTION -healthcheck=$SENSE_COLLECTOR_HEALTHCHECK -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -perf_interval=$SENSE_COLLECTOR_PERF_INTERVAL -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -poll_interval=$SENSE_COLLECTOR_HOST_PERFORMANCE_POLL_INTERVAL - -if [ "$debug" == "true" ] - -then - -echo "${echo_bold}${echo_color_host_performance}${collector_type}:${echo_normal} $(date) - Starting Sense Collector (start-host-performance.sh) - https://github.com/lux4rd0/sense-collector - -Debug Environmental Variables - -collector_type=${collector_type} -debug=${debug} -debug_curl=${debug_curl} -debug_sleeping=${debug_sleeping} -function=${function} -healthcheck=${healthcheck} -host_hostname=${host_hostname} -perf_interval=${perf_interval} -weatherflow_collector_version=${weatherflow_collector_version}" - -fi - -## -## Check for required intervals -## - -if [ -z "${poll_interval}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_POLL_INTERVAL${echo_normal} environmental variable not set. Defaulting to ${echo_bold}60${echo_normal} seconds."; poll_interval="60"; export SENSE_COLLECTOR_POLL_INTERVAL="60"; fi - -if [ -z "${host_hostname}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_HOST_HOSTNAME${echo_normal} environmental variable not set. Defaulting to ${echo_bold}sense-collector${echo_normal}."; host_hostname="sense-collector"; export SENSE_COLLECTOR_HOST_HOSTNAME="sense-collector"; fi - -## -## Send Startup Event Timestamp to InfluxDB -## - -process_start - -## -## Curl Command -## - -if [ "$debug" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -## -## Start Host Performance Loop -## - -while ( true ); do -before=$(date +%s%N) - -./exec-host-performance.sh - -after=$(date +%s%N) -delay=$(echo "scale=4;(${perf_interval}-($after-$before) / 1000000000)" | bc) -if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_host_performance}${collector_type}:${echo_normal} Sleeping: ${delay} seconds"; fi -sleep "$delay" -done \ No newline at end of file diff --git a/sense-collector/start-monitor-status.sh b/sense-collector/start-monitor-status.sh deleted file mode 100644 index bcd7b00..0000000 --- a/sense-collector/start-monitor-status.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - start-monitor-status.sh -## - -## -## Set Specific Variables -## - -collector_type="monitor-status" - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Set Variables from Environmental Variables -## - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -debug_sleeping=$SENSE_COLLECTOR_DEBUG_SLEEPING -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -poll_interval=$SENSE_COLLECTOR_MONITOR_STATUS_POLL_INTERVAL -sense_monitor_id=$SENSE_COLLECTOR_MONITOR_ID -sense_token=$SENSE_COLLECTOR_TOKEN -threads=$SENSE_COLLECTOR_THREADS - -## -## Check for required intervals -## - -if [ -z "${poll_interval}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_POLL_INTERVAL${echo_normal} environmental variable not set. Defaulting to ${echo_bold}60${echo_normal} seconds."; poll_interval="60"; export SENSE_COLLECTOR_POLL_INTERVAL="60"; fi - -if [ -z "${host_hostname}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_HOST_HOSTNAME${echo_normal} environmental variable not set. Defaulting to ${echo_bold}sense-collector${echo_normal}."; host_hostname="sense-collector"; export SENSE_COLLECTOR_HOST_HOSTNAME="sense-collector"; fi - -if [ -z "${threads}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_THREADS${echo_normal} environmental variable not set. Defaulting to ${echo_bold}4${echo_normal} threads."; threads="4"; export SENSE_COLLECTOR_THREADS="4"; fi - -if [ "$debug" == "true" ] - -then - -echo "$(date) - Starting Sense Collector (start-monitor-status.sh) - https://github.com/lux4rd0/sense-collector - -Debug Environmental Variables - -debug=${debug} -debug_curl=${debug_curl} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -poll_interval=${poll_interval} -sense_monitor_id=${sense_monitor_id} -sense_token=${sense_token}" -fi - -## -## Send Startup Event Timestamp to InfluxDB -## - -process_start - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -## -## Start Sense Monitor Status Loop -## - -while ( true ); do -before=$(date +%s%N) - -./exec-monitor-status.sh - -after=$(date +%s%N) -delay=$(echo "scale=4;(${poll_interval}-($after-$before) / 1000000000)" | bc) - -if [ "$debug_sleeping" == "true" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Sleeping: ${delay} seconds"; fi - -sleep "$delay" -done \ No newline at end of file diff --git a/sense-collector/start-sense-collector.sh b/sense-collector/start-sense-collector.sh deleted file mode 100644 index 2572bf4..0000000 --- a/sense-collector/start-sense-collector.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash - -## -## Sense Collector - start-sense-collector.sh -## - -## -## Set Specific Variables -## - -collector_type="sense-collector" - -## -## Sense-Collector Details -## - -source sense-collector-details.sh - -## -## Set Variables from Environmental Variables -## - -debug=$SENSE_COLLECTOR_DEBUG -debug_curl=$SENSE_COLLECTOR_DEBUG_CURL -debug_sleeping=$SENSE_COLLECTOR_DEBUG_SLEEPING -host_hostname=$SENSE_COLLECTOR_HOST_HOSTNAME -influxdb_password=$SENSE_COLLECTOR_INFLUXDB_PASSWORD -influxdb_url=$SENSE_COLLECTOR_INFLUXDB_URL -influxdb_username=$SENSE_COLLECTOR_INFLUXDB_USERNAME -poll_interval=$SENSE_COLLECTOR_SENSE_COLLECTOR_POLL_INTERVAL -sense_monitor_id=$SENSE_COLLECTOR_MONITOR_ID -sense_token=$SENSE_COLLECTOR_TOKEN -threads=$SENSE_COLLECTOR_THREADS - -## -## Check for required intervals -## - -if [ -z "${poll_interval}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_SENSE_COLLECTOR_POLL_INTERVAL${echo_normal} environmental variable not set. Defaulting to ${echo_bold}60${echo_normal} seconds."; poll_interval="60"; export SENSE_COLLECTOR_SENSE_COLLECTOR_POLL_INTERVAL="60"; fi - -if [ -z "${host_hostname}" ]; then echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} ${echo_bold}SENSE_COLLECTOR_HOST_HOSTNAME${echo_normal} environmental variable not set. Defaulting to ${echo_bold}sense-collector${echo_normal}."; host_hostname="sense-collector"; export SENSE_COLLECTOR_HOST_HOSTNAME="sense-collector"; fi - -if [ "$debug" == "true" ] - -then - -echo "$(date) - Starting Sense Collector (start-sense-collector.sh) - https://github.com/lux4rd0/sense-collector - -Debug Environmental Variables - -collector_type=${collector_type} -debug=${debug} -debug_curl=${debug_curl} -host_hostname=${host_hostname} -influxdb_password=${influxdb_password} -influxdb_url=${influxdb_url} -influxdb_username=${influxdb_username} -poll_interval=${poll_interval} -sense_monitor_id=${sense_monitor_id} -sense_token=${sense_token} -threads=${threads}" -fi - -## -## Send Startup Event Timestamp to InfluxDB -## - -process_start - -## -## Curl Command -## - -if [ "$debug_curl" == "true" ]; then curl=( ); else curl=( --silent --show-error --fail ); fi - -## -## Start Sensor Collector Socket Connection -## - -echo "${echo_bold}${echo_color_random}${collector_type}:${echo_normal} Starting up ${echo_bold}${echo_color_start}sense_collector${echo_normal} - Stream Type: ${echo_bold}${poll_interval}${echo_normal}." -timeout 10m ./websocat_amd64-linux-static -n -t - autoreconnect:wss://clientrt.sense.com/monitors/"${sense_monitor_id}"/realtimefeed -H "Authorization: bearer ${sense_token}" -H "Sense-Collector-Client-Version: 1.0.0" -H "X-Sense-Protocol: 3" -H "User-Agent: okhttp/3.8.0" | ./exec-sense-collector.sh \ No newline at end of file diff --git a/storage.py b/storage.py new file mode 100644 index 0000000..8961b5d --- /dev/null +++ b/storage.py @@ -0,0 +1,345 @@ +import aiohttp +from datetime import datetime, timezone +from influxdb_client import InfluxDBClient, Point, WriteOptions +from influxdb_client.client.write_api import ASYNCHRONOUS +import logging +from dateutil import parser +import pytz + +# Configure logging +storage_logger = logging.getLogger("storage") + + +class InfluxDBStorage: + def __init__(self, influxdb_params): + self.influxdb_client = InfluxDBClient( + url=influxdb_params["url"], + token=influxdb_params["token"], + org=influxdb_params["org"], + ) + self.bucket = influxdb_params["bucket"] + + write_options = WriteOptions( + write_type=ASYNCHRONOUS, + batch_size=10000, + flush_interval=10000, + ) + self.write_api = self.influxdb_client.write_api(write_options=write_options) + + async def persist_realtime_data( + self, + monitor_id, + hertz, + total_current, + total_watts, + epoch, + voltage, + devices, + channels, + ): + storage_logger.info("Persisting realtime data") + + current_time = datetime.now(timezone.utc) + epoch_time = datetime.fromtimestamp(epoch, timezone.utc) + time_difference = (current_time - epoch_time).total_seconds() + + storage_logger.debug(f"Time difference: {time_difference}") + + main_point = ( + Point("sense_mains") + .tag("monitor_id", monitor_id) + .field("hertz", hertz) + .field("current", total_current) + .field("watts", total_watts) + .time(epoch, write_precision="s") + ) + + channel_point_1 = ( + Point("sense_mains") + .tag("monitor_id", monitor_id) + .tag("leg", "L1") + .field("watts", channels[0]) + .time(epoch, write_precision="s") + ) + + channel_point_2 = ( + Point("sense_mains") + .tag("monitor_id", monitor_id) + .tag("leg", "L2") + .field("watts", channels[1]) + .time(epoch, write_precision="s") + ) + + voltage_point_1 = ( + Point("sense_mains") + .tag("monitor_id", monitor_id) + .tag("leg", "L1") + .field("voltage", voltage[0]) + .time(epoch, write_precision="s") + ) + + voltage_point_2 = ( + Point("sense_mains") + .tag("monitor_id", monitor_id) + .tag("leg", "L2") + .field("voltage", voltage[1]) + .time(epoch, write_precision="s") + ) + + o11y_point = ( + Point("sense_o11y") + .tag("monitor_id", monitor_id) + .field("time_difference", time_difference) + .time(epoch, write_precision="s") + ) + + points = [ + main_point, + channel_point_1, + channel_point_2, + voltage_point_1, + voltage_point_2, + o11y_point, + ] + + for device in devices: + device_id = device.get("id") + device_watts = device.get("w") + device_name = device.get("name") + device_icon = device.get("icon") + device_sd = device.get("sd", {}) + is_plug = any( + device_sd.get(key) is not None for key in ["w", "i", "v", "e"] + ) + + # Add detailed debug logging + storage_logger.debug(f"Processing device {device_id} - {device_name}") + storage_logger.debug(f"Device Watts: {device_watts}") + storage_logger.debug(f"Always On Watts (ao_w): {device.get('ao_w')}") + storage_logger.debug(f"Always On State (ao_st): {device.get('ao_st')}") + + device_point = ( + Point("sense_devices") + .tag("monitor_id", monitor_id) + .tag("device_id", device_id) + .tag("device_name", device_name) + .tag("is_plug", str(is_plug).lower()) + .field("icon", device_icon) + .field("watts", device_watts) + .field("sd_watts", device_sd.get("w")) + .field("sd_current", device_sd.get("i")) + .field("sd_voltage", device_sd.get("v")) + .field("sd_energy", device_sd.get("e")) + .field("always_on_watts", device.get("ao_w")) + .field("always_on_state", device.get("ao_st")) + .time(epoch, write_precision="s") + ) + + points.append(device_point) + + await self.write_points(points) + + async def persist_device_data(self, device_data): + storage_logger.info("Persisting device data") + storage_logger.debug(f"Device data: {device_data}") + + try: + device_info = device_data.get("device", {}) + device_id = device_info.get("id") + device_name = device_info.get("name") + icon = device_info.get("icon") + monitor_id = device_info.get("monitor_id") + + last_state_timestamp_seconds = None + if "last_state_time" in device_info: + last_state_timestamp = parser.parse( + device_info["last_state_time"] + ).astimezone(pytz.UTC) + last_state_timestamp_seconds = int(last_state_timestamp.timestamp()) + + timestamp = int(datetime.now(timezone.utc).timestamp()) + + device_detail_point = ( + Point("sense_devices") + .tag("device_id", device_id) + .tag("device_name", device_name) + .tag("monitor_id", monitor_id) + .field("icon", icon) + .time(timestamp, write_precision="s") + ) + + storage_logger.debug( + f"Tags - device_id: {device_id}, device_name: {device_name}, monitor_id: {monitor_id}" + ) + storage_logger.debug(f"Field - icon: {icon}") + + if device_info.get("last_state") is not None: + device_detail_point.field("last_state", device_info["last_state"]) + storage_logger.debug(f"Field - last_state: {device_info['last_state']}") + if last_state_timestamp_seconds is not None: + device_detail_point.field( + "last_state_time", last_state_timestamp_seconds + ) + storage_logger.debug( + f"Field - last_state_time: {last_state_timestamp_seconds}" + ) + + for field, value in device_data.get("usage", {}).items(): + if value is not None: + device_detail_point.field( + field, float(value) / 100 if field == "yearly_cost" else value + ) + storage_logger.debug(f"Field - {field}: {value}") + + if device_data.get("info") is not None: + device_detail_point.field("info", str(device_data["info"])) + storage_logger.debug(f"Field - info: {device_data['info']}") + + await self.write_points([device_detail_point]) + storage_logger.info(f"Persisted device data for {device_id}") + + except KeyError as e: + storage_logger.error( + f"KeyError in persist_device_data: {e}, device_data: {device_data}" + ) + except Exception as e: + storage_logger.error( + f"Error in persist_device_data: {e}, device_data: {device_data}" + ) + + async def persist_timeline_data( + self, + device_id, + device_name, + time, + event_type, + icon, + body, + device_state, + user_device_type, + device_transition_from_state, + ): + timeline_point = ( + Point("sense_event") + .tag("device_id", device_id) + .tag("device_name", device_name) + .field("time", time) + .field("type", event_type) + .field("icon", icon) + .field("body", body) + .field("device_state", device_state) + .field("user_device_type", user_device_type) + .field("device_transition_from_state", device_transition_from_state) + .time(time, write_precision="s") + ) + await self.write_points([timeline_point]) + + async def persist_hello_event(self, monitor_id, online_status, timestamp): + hello_point = ( + Point("hello_event") + .tag("monitor_id", monitor_id) + .field("online", online_status) + .time(timestamp, write_precision="s") + ) + await self.write_points([hello_point]) + + async def persist_data_change_event( + self, + monitor_id, + device_id, + user_version, + guid, + epoch_timestamp, + influxdb_timestamp, + ): + data_change_point = ( + Point("data_change_event") + .tag("monitor_id", monitor_id) + .tag("device_id", device_id) + .field("user_version", user_version) + .field("guid", guid) + .field("json_timestamp", epoch_timestamp) + .time(influxdb_timestamp, write_precision="s") + ) + await self.write_points([data_change_point]) + + async def persist_device_state( + self, monitor_id, device_id, mode, device_state, timestamp + ): + device_state_point = ( + Point("device_state_event") + .tag("monitor_id", monitor_id) + .tag("device_id", device_id) + .field("mode", mode) + .field("state", device_state) + .time(timestamp, write_precision="s") + ) + await self.write_points([device_state_point]) + + async def persist_monitor_status(self, monitor_id, monitor_status): + storage_logger.info(f"Persisting monitor status for monitor_id: {monitor_id}") + storage_logger.debug(f"Monitor status data: {monitor_status}") + + try: + timestamp = int(datetime.now(timezone.utc).timestamp()) + signals = monitor_status.get("signals", {}) + monitor_info = monitor_status.get("monitor_info", {}) + wifi_strength = float(monitor_info.get("wifi_strength", 0)) + + monitor_info_point = ( + Point("sense_monitor_status") + .tag("monitor_id", monitor_id) + .field("ethernet", monitor_info.get("ethernet")) + .field("online", monitor_info.get("online")) + .field("ip_address", monitor_info.get("ip_address")) + .field("version", monitor_info.get("version")) + .field("ssid", monitor_info.get("ssid")) + .field("ndt_enabled", monitor_info.get("ndt_enabled")) + .field("mac", monitor_info.get("mac")) + .field("progress", float(signals.get("progress"))) + .field("status", signals.get("status")) + .time(timestamp, write_precision="s") + ) + + if wifi_strength != 0: + monitor_info_point.field("wifi_strength", wifi_strength) + + await self.write_points([monitor_info_point]) + + device_detection = monitor_status.get("device_detection", {}) + points = [] + for status in ["in_progress", "found"]: + for device in device_detection.get(status, []): + points.append( + Point("sense_device_detection") + .tag("monitor_id", monitor_id) + .tag("status", status) + .field("icon", device.get("icon")) + .tag("name", device.get("name")) + .field("progress", float(device.get("progress", 0))) + .time(timestamp, write_precision="s") + ) + + await self.write_points(points) + storage_logger.info(f"Persisted monitor status for {monitor_id}") + + except KeyError as e: + storage_logger.error( + f"KeyError in persist_monitor_status: {e}, monitor_status: {monitor_status}" + ) + except Exception as e: + storage_logger.error( + f"Error in persist_monitor_status: {e}, monitor_status: {monitor_status}" + ) + + async def write_points(self, points): + storage_logger.info("Writing points to InfluxDB") + try: + self.write_api.write( + bucket=self.bucket, + org=self.influxdb_client.org, + record=points, + write_precision="s", + ) + except Exception as e: + storage_logger.error(f"Error writing points to InfluxDB: {e}")