mirror of
https://github.com/beestat/app.git
synced 2025-07-09 03:04:07 -04:00
Metrics
There's no description for a commit with changes to 65 files.
This commit is contained in:
parent
d9fd07618a
commit
6d7b4ff3f5
372
api/beestat.sql
372
api/beestat.sql
@ -1,8 +1,16 @@
|
|||||||
-- MySQL dump 10.13 Distrib 8.0.18, for Linux (x86_64)
|
-- mysqldump -u root -p --opt beestat -d --single-transaction | sed 's/ AUTO_INCREMENT=[0-9]*//g' > beestat.sql
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- MySQL dump 10.13 Distrib 8.0.20, for Linux (x86_64)
|
||||||
--
|
--
|
||||||
-- Host: localhost Database: beestat
|
-- Host: localhost Database: beestat
|
||||||
-- ------------------------------------------------------
|
-- ------------------------------------------------------
|
||||||
-- Server version 8.0.18
|
-- Server version 8.0.20
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
@ -23,8 +31,8 @@ DROP TABLE IF EXISTS `address`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `address` (
|
CREATE TABLE `address` (
|
||||||
`address_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`address_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`normalized` json DEFAULT NULL,
|
`normalized` json DEFAULT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
@ -43,7 +51,7 @@ DROP TABLE IF EXISTS `announcement`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `announcement` (
|
CREATE TABLE `announcement` (
|
||||||
`announcement_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`announcement_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`important` tinyint(1) NOT NULL DEFAULT '0',
|
`important` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
||||||
@ -62,14 +70,11 @@ DROP TABLE IF EXISTS `api_cache`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `api_cache` (
|
CREATE TABLE `api_cache` (
|
||||||
`api_cache_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`api_cache_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
`user_id` int unsigned DEFAULT NULL,
|
||||||
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`expires_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`expires_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`request_resource` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
|
||||||
`request_method` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
|
||||||
`request_arguments` text CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
|
||||||
`response_data` json DEFAULT NULL,
|
`response_data` json DEFAULT NULL,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`api_cache_id`),
|
PRIMARY KEY (`api_cache_id`),
|
||||||
@ -86,26 +91,30 @@ DROP TABLE IF EXISTS `api_log`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `api_log` (
|
CREATE TABLE `api_log` (
|
||||||
`api_log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`api_log_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`request_ip` int(10) unsigned NOT NULL,
|
`user_id` int unsigned DEFAULT NULL,
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
`api_user_id` int unsigned DEFAULT NULL,
|
||||||
`request_api_user_id` int(10) unsigned DEFAULT NULL,
|
`ip_address` int unsigned NOT NULL,
|
||||||
`request_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`request_resource` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`request` json DEFAULT NULL,
|
||||||
`request_method` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`response` json DEFAULT NULL,
|
||||||
`request_arguments` text CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`error_code` int unsigned DEFAULT NULL,
|
||||||
`response_error_code` int(10) unsigned DEFAULT NULL,
|
`error_detail` json DEFAULT NULL,
|
||||||
`response_data` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`total_time` decimal(10,4) unsigned DEFAULT NULL,
|
||||||
`response_time` decimal(10,4) unsigned DEFAULT NULL,
|
`query_count` int unsigned DEFAULT NULL,
|
||||||
`response_query_count` int(10) unsigned DEFAULT NULL,
|
`query_time` decimal(10,4) unsigned DEFAULT NULL,
|
||||||
`response_query_time` decimal(10,4) unsigned DEFAULT NULL,
|
PRIMARY KEY (`api_log_id`,`timestamp`),
|
||||||
`from_cache` tinyint(1) unsigned DEFAULT '0',
|
|
||||||
PRIMARY KEY (`api_log_id`),
|
|
||||||
KEY `user_id` (`user_id`),
|
KEY `user_id` (`user_id`),
|
||||||
KEY `request_ip_request_timestamp` (`request_ip`,`request_timestamp`),
|
KEY `request_ip_request_timestamp` (`ip_address`,`timestamp`),
|
||||||
KEY `request_timestamp` (`request_timestamp`),
|
KEY `request_timestamp` (`timestamp`),
|
||||||
KEY `request_api_user_id_request_timestamp` (`request_api_user_id`,`request_timestamp`)
|
KEY `request_api_user_id_request_timestamp` (`api_user_id`,`timestamp`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPRESSED
|
||||||
|
/*!50100 PARTITION BY RANGE (unix_timestamp(`timestamp`))
|
||||||
|
(PARTITION 2020_10 VALUES LESS THAN (1604188800) ENGINE = InnoDB,
|
||||||
|
PARTITION 2020_11 VALUES LESS THAN (1606780800) ENGINE = InnoDB,
|
||||||
|
PARTITION 2020_12 VALUES LESS THAN (1609459200) ENGINE = InnoDB,
|
||||||
|
PARTITION 2021_01 VALUES LESS THAN (1612137600) ENGINE = InnoDB,
|
||||||
|
PARTITION 2021_02 VALUES LESS THAN (1614556800) ENGINE = InnoDB) */;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -116,7 +125,7 @@ DROP TABLE IF EXISTS `api_user`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `api_user` (
|
CREATE TABLE `api_user` (
|
||||||
`api_user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`api_user_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`api_key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`api_key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`session_key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`session_key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
@ -136,7 +145,7 @@ DROP TABLE IF EXISTS `ecobee_api_cache`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `ecobee_api_cache` (
|
CREATE TABLE `ecobee_api_cache` (
|
||||||
`ecobee_api_cache_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`ecobee_api_cache_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
||||||
@ -154,16 +163,17 @@ DROP TABLE IF EXISTS `ecobee_api_log`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `ecobee_api_log` (
|
CREATE TABLE `ecobee_api_log` (
|
||||||
`ecobee_api_log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`ecobee_api_log_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
`user_id` int unsigned DEFAULT NULL,
|
||||||
`api_user_id` int(10) unsigned DEFAULT NULL,
|
`api_user_id` int unsigned DEFAULT NULL,
|
||||||
`request_timestamp` timestamp NULL DEFAULT NULL,
|
`request_timestamp` timestamp NULL DEFAULT NULL,
|
||||||
`request` json DEFAULT NULL,
|
`request` json DEFAULT NULL,
|
||||||
`response` text CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`ecobee_api_log_id`),
|
PRIMARY KEY (`ecobee_api_log_id`),
|
||||||
KEY `user_id` (`user_id`),
|
KEY `user_id` (`user_id`),
|
||||||
KEY `api_user_id` (`api_user_id`)
|
KEY `api_user_id` (`api_user_id`),
|
||||||
|
KEY `request_timestamp` (`request_timestamp`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
@ -175,10 +185,10 @@ DROP TABLE IF EXISTS `ecobee_sensor`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `ecobee_sensor` (
|
CREATE TABLE `ecobee_sensor` (
|
||||||
`ecobee_sensor_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`ecobee_sensor_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`identifier` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`identifier` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`ecobee_thermostat_id` int(10) unsigned NOT NULL,
|
`ecobee_thermostat_id` int unsigned NOT NULL,
|
||||||
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
@ -202,10 +212,10 @@ DROP TABLE IF EXISTS `ecobee_thermostat`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `ecobee_thermostat` (
|
CREATE TABLE `ecobee_thermostat` (
|
||||||
`ecobee_thermostat_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`ecobee_thermostat_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`identifier` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`identifier` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`guid` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`guid` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`connected` tinyint(1) DEFAULT NULL,
|
`connected` tinyint(1) DEFAULT NULL,
|
||||||
`thermostat_revision` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`thermostat_revision` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
@ -253,10 +263,10 @@ DROP TABLE IF EXISTS `ecobee_token`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `ecobee_token` (
|
CREATE TABLE `ecobee_token` (
|
||||||
`ecobee_token_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`ecobee_token_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`access_token` char(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`access_token` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`refresh_token` char(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`refresh_token` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`ecobee_token_id`),
|
PRIMARY KEY (`ecobee_token_id`),
|
||||||
@ -266,41 +276,42 @@ CREATE TABLE `ecobee_token` (
|
|||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Table structure for table `mailchimp_api_cache`
|
-- Table structure for table `mailgun_api_cache`
|
||||||
--
|
--
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `mailchimp_api_cache`;
|
DROP TABLE IF EXISTS `mailgun_api_cache`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `mailchimp_api_cache` (
|
CREATE TABLE `mailgun_api_cache` (
|
||||||
`ecobee_api_cache_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`mailgun_api_cache_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`ecobee_api_cache_id`),
|
PRIMARY KEY (`mailgun_api_cache_id`),
|
||||||
KEY `user_id_key` (`key`)
|
KEY `user_id_key` (`key`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Table structure for table `mailchimp_api_log`
|
-- Table structure for table `mailgun_api_log`
|
||||||
--
|
--
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `mailchimp_api_log`;
|
DROP TABLE IF EXISTS `mailgun_api_log`;
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `mailchimp_api_log` (
|
CREATE TABLE `mailgun_api_log` (
|
||||||
`mailchimp_api_log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`mailgun_api_log_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
`user_id` int unsigned DEFAULT NULL,
|
||||||
`api_user_id` int(10) unsigned DEFAULT NULL,
|
`api_user_id` int unsigned DEFAULT NULL,
|
||||||
`request_timestamp` timestamp NULL DEFAULT NULL,
|
`request_timestamp` timestamp NULL DEFAULT NULL,
|
||||||
`request` json DEFAULT NULL,
|
`request` json DEFAULT NULL,
|
||||||
`response` text COLLATE utf8_unicode_ci,
|
`response` text COLLATE utf8_unicode_ci,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`mailchimp_api_log_id`),
|
PRIMARY KEY (`mailgun_api_log_id`),
|
||||||
KEY `user_id` (`user_id`),
|
KEY `user_id` (`user_id`),
|
||||||
KEY `api_user_id` (`api_user_id`)
|
KEY `api_user_id` (`api_user_id`),
|
||||||
|
KEY `request_timestamp` (`request_timestamp`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
@ -312,12 +323,12 @@ DROP TABLE IF EXISTS `patreon_api_cache`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `patreon_api_cache` (
|
CREATE TABLE `patreon_api_cache` (
|
||||||
`ecobee_api_cache_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`patreon_api_cache_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`ecobee_api_cache_id`),
|
PRIMARY KEY (`patreon_api_cache_id`),
|
||||||
KEY `user_id_key` (`key`)
|
KEY `user_id_key` (`key`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
@ -330,16 +341,17 @@ DROP TABLE IF EXISTS `patreon_api_log`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `patreon_api_log` (
|
CREATE TABLE `patreon_api_log` (
|
||||||
`patreon_api_log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`patreon_api_log_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
`user_id` int unsigned DEFAULT NULL,
|
||||||
`api_user_id` int(10) unsigned DEFAULT NULL,
|
`api_user_id` int unsigned DEFAULT NULL,
|
||||||
`request_timestamp` timestamp NULL DEFAULT NULL,
|
`request_timestamp` timestamp NULL DEFAULT NULL,
|
||||||
`request` json DEFAULT NULL,
|
`request` json DEFAULT NULL,
|
||||||
`response` text COLLATE utf8_unicode_ci,
|
`response` text COLLATE utf8_unicode_ci,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`patreon_api_log_id`),
|
PRIMARY KEY (`patreon_api_log_id`),
|
||||||
KEY `user_id` (`user_id`),
|
KEY `user_id` (`user_id`),
|
||||||
KEY `api_user_id` (`api_user_id`)
|
KEY `api_user_id` (`api_user_id`),
|
||||||
|
KEY `request_timestamp` (`request_timestamp`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
@ -351,8 +363,8 @@ DROP TABLE IF EXISTS `patreon_token`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `patreon_token` (
|
CREATE TABLE `patreon_token` (
|
||||||
`patreon_token_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`patreon_token_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`access_token` char(43) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`access_token` char(43) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`refresh_token` char(43) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`refresh_token` char(43) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
@ -363,6 +375,30 @@ CREATE TABLE `patreon_token` (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `runtime_sensor`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `runtime_sensor`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
|
CREATE TABLE `runtime_sensor` (
|
||||||
|
`runtime_sensor_id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`sensor_id` int unsigned NOT NULL,
|
||||||
|
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`temperature` smallint DEFAULT NULL,
|
||||||
|
`occupancy` tinyint unsigned DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`runtime_sensor_id`,`timestamp`),
|
||||||
|
UNIQUE KEY `thermostat_id_timestamp` (`sensor_id`,`timestamp`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPRESSED
|
||||||
|
/*!50100 PARTITION BY RANGE (unix_timestamp(`timestamp`))
|
||||||
|
(PARTITION 2020_10 VALUES LESS THAN (1604188800) ENGINE = InnoDB,
|
||||||
|
PARTITION 2020_11 VALUES LESS THAN (1606780800) ENGINE = InnoDB,
|
||||||
|
PARTITION 2020_12 VALUES LESS THAN (1609459200) ENGINE = InnoDB,
|
||||||
|
PARTITION 2021_01 VALUES LESS THAN (1612137600) ENGINE = InnoDB,
|
||||||
|
PARTITION 2021_02 VALUES LESS THAN (1614556800) ENGINE = InnoDB) */;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Table structure for table `runtime_thermostat`
|
-- Table structure for table `runtime_thermostat`
|
||||||
--
|
--
|
||||||
@ -371,45 +407,44 @@ DROP TABLE IF EXISTS `runtime_thermostat`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `runtime_thermostat` (
|
CREATE TABLE `runtime_thermostat` (
|
||||||
`runtime_thermostat_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`runtime_thermostat_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`thermostat_id` int(10) unsigned NOT NULL,
|
`thermostat_id` int unsigned NOT NULL,
|
||||||
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`compressor_1` smallint(5) unsigned DEFAULT NULL,
|
`compressor_1` smallint unsigned DEFAULT NULL,
|
||||||
`compressor_2` smallint(5) unsigned DEFAULT NULL,
|
`compressor_2` smallint unsigned DEFAULT NULL,
|
||||||
`compressor_mode` enum('heat','cool','off') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`compressor_mode` enum('heat','cool','off') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`auxiliary_heat_1` smallint(5) unsigned DEFAULT NULL,
|
`auxiliary_heat_1` smallint unsigned DEFAULT NULL,
|
||||||
`auxiliary_heat_2` smallint(5) unsigned DEFAULT NULL,
|
`auxiliary_heat_2` smallint unsigned DEFAULT NULL,
|
||||||
`fan` smallint(5) unsigned DEFAULT NULL,
|
`fan` smallint unsigned DEFAULT NULL,
|
||||||
`accessory` smallint(5) unsigned DEFAULT NULL,
|
`accessory` smallint unsigned DEFAULT NULL,
|
||||||
`accessory_type` enum('humidifier','dehumidifier','ventilator','economizer','off') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`accessory_type` enum('humidifier','dehumidifier','ventilator','economizer','off') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`system_mode` enum('auto','auxiliary_heat','cool','heat','off') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`system_mode` enum('auto','auxiliary_heat','cool','heat','off') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`indoor_temperature` smallint(6) DEFAULT NULL,
|
`indoor_temperature` smallint DEFAULT NULL,
|
||||||
`indoor_humidity` tinyint(3) unsigned DEFAULT NULL,
|
`indoor_humidity` tinyint unsigned DEFAULT NULL,
|
||||||
`outdoor_temperature` smallint(6) DEFAULT NULL,
|
`outdoor_temperature` smallint DEFAULT NULL,
|
||||||
`outdoor_humidity` tinyint(3) unsigned DEFAULT NULL,
|
`outdoor_humidity` tinyint unsigned DEFAULT NULL,
|
||||||
`event_runtime_thermostat_text_id` smallint(5) unsigned DEFAULT NULL,
|
`event_runtime_thermostat_text_id` smallint unsigned DEFAULT NULL,
|
||||||
`climate_runtime_thermostat_text_id` smallint(5) unsigned DEFAULT NULL,
|
`climate_runtime_thermostat_text_id` smallint unsigned DEFAULT NULL,
|
||||||
`setpoint_cool` smallint(5) unsigned DEFAULT NULL,
|
`setpoint_cool` smallint unsigned DEFAULT NULL,
|
||||||
`setpoint_heat` smallint(5) unsigned DEFAULT NULL,
|
`setpoint_heat` smallint unsigned DEFAULT NULL,
|
||||||
PRIMARY KEY (`runtime_thermostat_id`,`timestamp`),
|
PRIMARY KEY (`runtime_thermostat_id`,`timestamp`),
|
||||||
UNIQUE KEY `thermostat_id_timestamp` (`thermostat_id`,`timestamp`)
|
UNIQUE KEY `thermostat_id_timestamp` (`thermostat_id`,`timestamp`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPRESSED
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPRESSED
|
||||||
/*!50100 PARTITION BY RANGE (unix_timestamp(`timestamp`))
|
/*!50100 PARTITION BY RANGE (unix_timestamp(`timestamp`))
|
||||||
(PARTITION 2018_10 VALUES LESS THAN (1541030400) ENGINE = InnoDB,
|
(PARTITION 2020_01 VALUES LESS THAN (1580515200) ENGINE = InnoDB,
|
||||||
PARTITION 2018_11 VALUES LESS THAN (1543622400) ENGINE = InnoDB,
|
PARTITION 2020_02 VALUES LESS THAN (1583020800) ENGINE = InnoDB,
|
||||||
PARTITION 2018_12 VALUES LESS THAN (1546300800) ENGINE = InnoDB,
|
PARTITION 2020_03 VALUES LESS THAN (1585699200) ENGINE = InnoDB,
|
||||||
PARTITION 2019_01 VALUES LESS THAN (1548979200) ENGINE = InnoDB,
|
PARTITION 2020_04 VALUES LESS THAN (1588291200) ENGINE = InnoDB,
|
||||||
PARTITION 2019_02 VALUES LESS THAN (1551398400) ENGINE = InnoDB,
|
PARTITION 2020_05 VALUES LESS THAN (1590969600) ENGINE = InnoDB,
|
||||||
PARTITION 2019_03 VALUES LESS THAN (1554076800) ENGINE = InnoDB,
|
PARTITION 2020_06 VALUES LESS THAN (1593561600) ENGINE = InnoDB,
|
||||||
PARTITION 2019_04 VALUES LESS THAN (1556668800) ENGINE = InnoDB,
|
PARTITION 2020_07 VALUES LESS THAN (1596240000) ENGINE = InnoDB,
|
||||||
PARTITION 2019_05 VALUES LESS THAN (1559347200) ENGINE = InnoDB,
|
PARTITION 2020_08 VALUES LESS THAN (1598918400) ENGINE = InnoDB,
|
||||||
PARTITION 2019_06 VALUES LESS THAN (1561939200) ENGINE = InnoDB,
|
PARTITION 2020_09 VALUES LESS THAN (1601510400) ENGINE = InnoDB,
|
||||||
PARTITION 2019_07 VALUES LESS THAN (1564617600) ENGINE = InnoDB,
|
PARTITION 2020_10 VALUES LESS THAN (1604188800) ENGINE = InnoDB,
|
||||||
PARTITION 2019_08 VALUES LESS THAN (1567296000) ENGINE = InnoDB,
|
PARTITION 2020_11 VALUES LESS THAN (1606780800) ENGINE = InnoDB,
|
||||||
PARTITION 2019_09 VALUES LESS THAN (1569888000) ENGINE = InnoDB,
|
PARTITION 2020_12 VALUES LESS THAN (1609459200) ENGINE = InnoDB,
|
||||||
PARTITION 2019_10 VALUES LESS THAN (1572566400) ENGINE = InnoDB,
|
PARTITION 2021_01 VALUES LESS THAN (1612137600) ENGINE = InnoDB,
|
||||||
PARTITION 2019_11 VALUES LESS THAN (1575158400) ENGINE = InnoDB,
|
PARTITION 2021_02 VALUES LESS THAN (1614556800) ENGINE = InnoDB) */;
|
||||||
PARTITION 2019_12 VALUES LESS THAN (1577836800) ENGINE = InnoDB) */;
|
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -420,28 +455,28 @@ DROP TABLE IF EXISTS `runtime_thermostat_summary`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `runtime_thermostat_summary` (
|
CREATE TABLE `runtime_thermostat_summary` (
|
||||||
`runtime_thermostat_summary_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`runtime_thermostat_summary_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`thermostat_id` int(10) unsigned NOT NULL,
|
`thermostat_id` int unsigned NOT NULL,
|
||||||
`date` date NOT NULL,
|
`date` date NOT NULL,
|
||||||
`count` smallint(5) unsigned NOT NULL,
|
`count` smallint unsigned NOT NULL,
|
||||||
`sum_compressor_cool_1` mediumint(8) unsigned NOT NULL,
|
`sum_compressor_cool_1` mediumint unsigned NOT NULL,
|
||||||
`sum_compressor_cool_2` mediumint(8) unsigned NOT NULL,
|
`sum_compressor_cool_2` mediumint unsigned NOT NULL,
|
||||||
`sum_compressor_heat_1` mediumint(8) unsigned NOT NULL,
|
`sum_compressor_heat_1` mediumint unsigned NOT NULL,
|
||||||
`sum_compressor_heat_2` mediumint(8) unsigned NOT NULL,
|
`sum_compressor_heat_2` mediumint unsigned NOT NULL,
|
||||||
`sum_auxiliary_heat_1` mediumint(8) unsigned NOT NULL,
|
`sum_auxiliary_heat_1` mediumint unsigned NOT NULL,
|
||||||
`sum_auxiliary_heat_2` mediumint(8) unsigned NOT NULL,
|
`sum_auxiliary_heat_2` mediumint unsigned NOT NULL,
|
||||||
`sum_fan` mediumint(8) unsigned NOT NULL,
|
`sum_fan` mediumint unsigned NOT NULL,
|
||||||
`sum_humidifier` mediumint(8) unsigned NOT NULL,
|
`sum_humidifier` mediumint unsigned NOT NULL,
|
||||||
`sum_dehumidifier` mediumint(8) unsigned NOT NULL,
|
`sum_dehumidifier` mediumint unsigned NOT NULL,
|
||||||
`sum_ventilator` mediumint(8) unsigned NOT NULL,
|
`sum_ventilator` mediumint unsigned NOT NULL,
|
||||||
`sum_economizer` mediumint(8) unsigned NOT NULL,
|
`sum_economizer` mediumint unsigned NOT NULL,
|
||||||
`avg_outdoor_temperature` smallint(6) NOT NULL,
|
`avg_outdoor_temperature` smallint NOT NULL,
|
||||||
`avg_outdoor_humidity` tinyint(3) unsigned NOT NULL,
|
`avg_outdoor_humidity` tinyint unsigned NOT NULL,
|
||||||
`min_outdoor_temperature` smallint(6) NOT NULL,
|
`min_outdoor_temperature` smallint NOT NULL,
|
||||||
`max_outdoor_temperature` smallint(6) NOT NULL,
|
`max_outdoor_temperature` smallint NOT NULL,
|
||||||
`avg_indoor_temperature` smallint(6) NOT NULL,
|
`avg_indoor_temperature` smallint NOT NULL,
|
||||||
`avg_indoor_humidity` tinyint(3) unsigned NOT NULL,
|
`avg_indoor_humidity` tinyint unsigned NOT NULL,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`runtime_thermostat_summary_id`),
|
PRIMARY KEY (`runtime_thermostat_summary_id`),
|
||||||
UNIQUE KEY `thermostat_id_date` (`thermostat_id`,`date`),
|
UNIQUE KEY `thermostat_id_date` (`thermostat_id`,`date`),
|
||||||
@ -459,7 +494,7 @@ DROP TABLE IF EXISTS `runtime_thermostat_text`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `runtime_thermostat_text` (
|
CREATE TABLE `runtime_thermostat_text` (
|
||||||
`runtime_thermostat_text_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
|
`runtime_thermostat_text_id` smallint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`runtime_thermostat_text_id`),
|
PRIMARY KEY (`runtime_thermostat_text_id`),
|
||||||
@ -475,16 +510,18 @@ DROP TABLE IF EXISTS `sensor`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `sensor` (
|
CREATE TABLE `sensor` (
|
||||||
`sensor_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`sensor_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`thermostat_id` int(10) unsigned NOT NULL,
|
`thermostat_id` int unsigned NOT NULL,
|
||||||
`ecobee_sensor_id` int(10) unsigned DEFAULT NULL,
|
`ecobee_sensor_id` int unsigned DEFAULT NULL,
|
||||||
|
`identifier` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`in_use` tinyint(1) DEFAULT NULL,
|
`in_use` tinyint(1) DEFAULT NULL,
|
||||||
`temperature` decimal(4,1) DEFAULT NULL,
|
`temperature` decimal(4,1) DEFAULT NULL,
|
||||||
`humidity` int(10) unsigned DEFAULT NULL,
|
`humidity` int unsigned DEFAULT NULL,
|
||||||
`occupancy` tinyint(1) DEFAULT NULL,
|
`occupancy` tinyint(1) DEFAULT NULL,
|
||||||
|
`capability` json DEFAULT NULL,
|
||||||
`inactive` tinyint(1) NOT NULL DEFAULT '0',
|
`inactive` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`sensor_id`),
|
PRIMARY KEY (`sensor_id`),
|
||||||
@ -505,16 +542,16 @@ DROP TABLE IF EXISTS `session`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `session` (
|
CREATE TABLE `session` (
|
||||||
`session_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`session_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`session_key` char(128) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`session_key` char(128) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
`user_id` int unsigned DEFAULT NULL,
|
||||||
`api_user_id` int(10) unsigned DEFAULT NULL,
|
`api_user_id` int unsigned DEFAULT NULL,
|
||||||
`timeout` int(10) unsigned DEFAULT NULL,
|
`timeout` int unsigned DEFAULT NULL,
|
||||||
`life` int(10) unsigned DEFAULT NULL,
|
`life` int unsigned DEFAULT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`created_by` int(10) unsigned NOT NULL,
|
`created_by` int unsigned NOT NULL,
|
||||||
`last_used_at` timestamp NULL DEFAULT NULL,
|
`last_used_at` timestamp NULL DEFAULT NULL,
|
||||||
`last_used_by` int(10) unsigned NOT NULL,
|
`last_used_by` int unsigned NOT NULL,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`session_id`),
|
PRIMARY KEY (`session_id`),
|
||||||
UNIQUE KEY `key` (`session_key`),
|
UNIQUE KEY `key` (`session_key`),
|
||||||
@ -533,7 +570,7 @@ DROP TABLE IF EXISTS `smarty_streets_api_cache`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `smarty_streets_api_cache` (
|
CREATE TABLE `smarty_streets_api_cache` (
|
||||||
`smarty_streets_api_cache_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`smarty_streets_api_cache_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
`key` char(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`response` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
||||||
@ -551,16 +588,17 @@ DROP TABLE IF EXISTS `smarty_streets_api_log`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `smarty_streets_api_log` (
|
CREATE TABLE `smarty_streets_api_log` (
|
||||||
`smarty_streets_api_log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`smarty_streets_api_log_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
`user_id` int unsigned DEFAULT NULL,
|
||||||
`api_user_id` int(10) unsigned DEFAULT NULL,
|
`api_user_id` int unsigned DEFAULT NULL,
|
||||||
`request_timestamp` timestamp NULL DEFAULT NULL,
|
`request_timestamp` timestamp NULL DEFAULT NULL,
|
||||||
`request` json DEFAULT NULL,
|
`request` json DEFAULT NULL,
|
||||||
`response` text COLLATE utf8_unicode_ci,
|
`response` text COLLATE utf8_unicode_ci,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`smarty_streets_api_log_id`),
|
PRIMARY KEY (`smarty_streets_api_log_id`),
|
||||||
KEY `user_id` (`user_id`),
|
KEY `user_id` (`user_id`),
|
||||||
KEY `api_user_id` (`api_user_id`)
|
KEY `api_user_id` (`api_user_id`),
|
||||||
|
KEY `request_timestamp` (`request_timestamp`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
@ -572,34 +610,55 @@ DROP TABLE IF EXISTS `thermostat`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `thermostat` (
|
CREATE TABLE `thermostat` (
|
||||||
`thermostat_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`thermostat_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`ecobee_thermostat_id` int(10) unsigned DEFAULT NULL,
|
`ecobee_thermostat_id` int unsigned DEFAULT NULL,
|
||||||
`thermostat_group_id` int(10) unsigned DEFAULT NULL,
|
`thermostat_group_id` int unsigned DEFAULT NULL,
|
||||||
`address_id` int(10) unsigned DEFAULT NULL,
|
`address_id` int unsigned DEFAULT NULL,
|
||||||
`name` varchar(255) DEFAULT NULL,
|
`name` varchar(255) DEFAULT NULL,
|
||||||
`temperature` decimal(4,1) DEFAULT NULL,
|
`temperature` decimal(4,1) DEFAULT NULL,
|
||||||
`temperature_unit` enum('°F','°C') DEFAULT NULL,
|
`temperature_unit` enum('°F','°C') DEFAULT NULL,
|
||||||
`humidity` int(10) unsigned DEFAULT NULL,
|
`humidity` int unsigned DEFAULT NULL,
|
||||||
`alerts` json DEFAULT NULL,
|
`alerts` json DEFAULT NULL,
|
||||||
`first_connected` timestamp NULL DEFAULT NULL,
|
`first_connected` timestamp NULL DEFAULT NULL,
|
||||||
`sync_begin` timestamp NULL DEFAULT NULL,
|
`sync_begin` timestamp NULL DEFAULT NULL,
|
||||||
`sync_end` timestamp NULL DEFAULT NULL,
|
`sync_end` timestamp NULL DEFAULT NULL,
|
||||||
|
`data_begin` timestamp NULL DEFAULT NULL,
|
||||||
|
`data_end` timestamp NULL DEFAULT NULL,
|
||||||
`time_zone` varchar(255) DEFAULT NULL,
|
`time_zone` varchar(255) DEFAULT NULL,
|
||||||
`filters` json DEFAULT NULL,
|
`filters` json DEFAULT NULL,
|
||||||
`temperature_profile` json DEFAULT NULL,
|
`temperature_profile` json DEFAULT NULL,
|
||||||
|
`profile` json DEFAULT NULL,
|
||||||
`property` json DEFAULT NULL,
|
`property` json DEFAULT NULL,
|
||||||
`system_type` json DEFAULT NULL,
|
`system_type` json DEFAULT NULL,
|
||||||
|
`system_type2` json DEFAULT NULL,
|
||||||
`weather` json DEFAULT NULL,
|
`weather` json DEFAULT NULL,
|
||||||
`fan_fixed` timestamp NULL DEFAULT NULL,
|
`settings` json DEFAULT NULL,
|
||||||
`fan_fixed2` timestamp NULL DEFAULT NULL,
|
`program` json DEFAULT NULL,
|
||||||
|
`system_type_heat` enum('geothermal','compressor','boiler','gas','oil','electric','none') DEFAULT NULL,
|
||||||
|
`system_type_heat_stages` int unsigned DEFAULT NULL,
|
||||||
|
`system_type_heat_auxiliary` enum('electric','gas','oil','none') DEFAULT NULL,
|
||||||
|
`system_type_heat_auxiliary_stages` int unsigned DEFAULT NULL,
|
||||||
|
`system_type_cool` enum('geothermal','compressor','none') DEFAULT NULL,
|
||||||
|
`system_type_cool_stages` int unsigned DEFAULT NULL,
|
||||||
|
`property_age` int unsigned DEFAULT NULL,
|
||||||
|
`property_square_feet` int unsigned DEFAULT NULL,
|
||||||
|
`property_stories` int unsigned DEFAULT NULL,
|
||||||
|
`property_structure_type` enum('detached','apartment','condominium','loft','multiplex','townhouse','semi-detached') DEFAULT NULL,
|
||||||
|
`address_latitude` decimal(8,6) DEFAULT NULL,
|
||||||
|
`address_longitude` decimal(9,6) DEFAULT NULL,
|
||||||
`inactive` tinyint(1) NOT NULL DEFAULT '0',
|
`inactive` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`thermostat_id`),
|
PRIMARY KEY (`thermostat_id`),
|
||||||
KEY `ecobee_thermostat_id` (`ecobee_thermostat_id`),
|
KEY `ecobee_thermostat_id` (`ecobee_thermostat_id`),
|
||||||
KEY `user_id` (`user_id`),
|
KEY `user_id` (`user_id`),
|
||||||
KEY `thermostat_group_id` (`thermostat_group_id`),
|
KEY `thermostat_group_id` (`thermostat_group_id`),
|
||||||
KEY `address_id` (`address_id`)
|
KEY `address_id` (`address_id`),
|
||||||
|
KEY `comparison` (`system_type_heat`,`system_type_cool`,`system_type_heat_stages`,`system_type_cool_stages`,`property_structure_type`,`address_latitude`,`address_longitude`),
|
||||||
|
CONSTRAINT `thermostat_ibfk_1` FOREIGN KEY (`ecobee_thermostat_id`) REFERENCES `ecobee_thermostat` (`ecobee_thermostat_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
|
CONSTRAINT `thermostat_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
|
CONSTRAINT `thermostat_ibfk_3` FOREIGN KEY (`thermostat_group_id`) REFERENCES `thermostat_group` (`thermostat_group_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
|
CONSTRAINT `thermostat_ibfk_4` FOREIGN KEY (`address_id`) REFERENCES `address` (`address_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
@ -611,19 +670,20 @@ DROP TABLE IF EXISTS `thermostat_group`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `thermostat_group` (
|
CREATE TABLE `thermostat_group` (
|
||||||
`thermostat_group_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`thermostat_group_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
`user_id` int unsigned NOT NULL,
|
||||||
`address_id` int(10) unsigned NOT NULL,
|
`address_id` int unsigned NOT NULL,
|
||||||
`system_type_heat` enum('geothermal','compressor','boiler','gas','oil','electric','none') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`system_type_heat` enum('geothermal','compressor','boiler','gas','oil','electric','none') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`system_type_heat_auxiliary` enum('electric','gas','oil','none') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`system_type_heat_auxiliary` enum('electric','gas','oil','none') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`system_type_cool` enum('geothermal','compressor','none') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`system_type_cool` enum('geothermal','compressor','none') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`property_age` int(10) unsigned DEFAULT NULL,
|
`property_age` int unsigned DEFAULT NULL,
|
||||||
`property_square_feet` int(10) unsigned DEFAULT NULL,
|
`property_square_feet` int unsigned DEFAULT NULL,
|
||||||
`property_stories` int(10) unsigned DEFAULT NULL,
|
`property_stories` int unsigned DEFAULT NULL,
|
||||||
`property_structure_type` enum('detached','apartment','condominium','loft','multiplex','townhouse','semi-detached') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`property_structure_type` enum('detached','apartment','condominium','loft','multiplex','townhouse','semi-detached') CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`address_latitude` decimal(10,8) DEFAULT NULL,
|
`address_latitude` decimal(10,8) DEFAULT NULL,
|
||||||
`address_longitude` decimal(11,8) DEFAULT NULL,
|
`address_longitude` decimal(11,8) DEFAULT NULL,
|
||||||
`temperature_profile` json DEFAULT NULL,
|
`temperature_profile` json DEFAULT NULL,
|
||||||
|
`profile` json DEFAULT NULL,
|
||||||
`weather` json DEFAULT NULL,
|
`weather` json DEFAULT NULL,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
PRIMARY KEY (`thermostat_group_id`),
|
PRIMARY KEY (`thermostat_group_id`),
|
||||||
@ -644,13 +704,15 @@ DROP TABLE IF EXISTS `user`;
|
|||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `user` (
|
CREATE TABLE `user` (
|
||||||
`user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`user_id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`password` char(60) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
`password` char(60) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`anonymous` tinyint(1) NOT NULL,
|
`anonymous` tinyint(1) NOT NULL,
|
||||||
|
`email_address` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||||
`settings` json DEFAULT NULL,
|
`settings` json DEFAULT NULL,
|
||||||
`patreon_status` json DEFAULT NULL,
|
`patreon_status` json DEFAULT NULL,
|
||||||
`sync_status` json DEFAULT NULL,
|
`sync_status` json DEFAULT NULL,
|
||||||
|
`debug` tinyint(1) DEFAULT '0',
|
||||||
`comment` text CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
`comment` text CHARACTER SET utf8 COLLATE utf8_unicode_ci,
|
||||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
`deleted` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
@ -668,4 +730,4 @@ CREATE TABLE `user` (
|
|||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
-- Dump completed on 2019-12-09 14:00:17
|
-- Dump completed on 2021-01-23 19:10:19
|
||||||
|
@ -292,7 +292,7 @@ final class database extends \mysqli {
|
|||||||
* @return string The appropriate escaped string. Examples: `foo` is null
|
* @return string The appropriate escaped string. Examples: `foo` is null
|
||||||
* `foo` in(1,2,3) `foo`='bar'
|
* `foo` in(1,2,3) `foo`='bar'
|
||||||
*/
|
*/
|
||||||
private function column_equals_value_where($column, $value) {
|
public function column_equals_value_where($column, $value) {
|
||||||
if($value === null) {
|
if($value === null) {
|
||||||
return $this->escape_identifier($column) . ' is null';
|
return $this->escape_identifier($column) . ' is null';
|
||||||
}
|
}
|
||||||
|
@ -212,39 +212,42 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
$attributes['property'] = $this->get_property($thermostat, $ecobee_thermostat);
|
$attributes['property'] = $this->get_property($thermostat, $ecobee_thermostat);
|
||||||
$attributes['filters'] = $this->get_filters($thermostat, $ecobee_thermostat);
|
$attributes['filters'] = $this->get_filters($thermostat, $ecobee_thermostat);
|
||||||
$attributes['weather'] = $this->get_weather($thermostat, $ecobee_thermostat);
|
$attributes['weather'] = $this->get_weather($thermostat, $ecobee_thermostat);
|
||||||
|
$attributes['settings'] = $this->get_settings($thermostat, $ecobee_thermostat);
|
||||||
$attributes['time_zone'] = $this->get_time_zone($thermostat, $ecobee_thermostat);
|
$attributes['time_zone'] = $this->get_time_zone($thermostat, $ecobee_thermostat);
|
||||||
|
$attributes['program'] = $this->get_program($thermostat, $ecobee_thermostat);
|
||||||
|
|
||||||
$detected_system_type = $this->get_detected_system_type($thermostat, $ecobee_thermostat);
|
$detected_system_type2 = $this->get_detected_system_type2($thermostat, $ecobee_thermostat);
|
||||||
if($thermostat['system_type'] === null) {
|
if($thermostat['system_type2'] === null) {
|
||||||
$attributes['system_type'] = [
|
$attributes['system_type2'] = [
|
||||||
'reported' => [
|
'reported' => [
|
||||||
'heat' => null,
|
'heat' => [
|
||||||
'heat_auxiliary' => null,
|
'equipment' => null,
|
||||||
'cool' => null
|
'stages' => null
|
||||||
|
],
|
||||||
|
'heat_auxiliary' => [
|
||||||
|
'equipment' => null,
|
||||||
|
'stages' => null
|
||||||
|
],
|
||||||
|
'cool' => [
|
||||||
|
'equipment' => null,
|
||||||
|
'stages' => null
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'detected' => $detected_system_type
|
'detected' => $detected_system_type2
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$attributes['system_type'] = [
|
$attributes['system_type2'] = [
|
||||||
'reported' => $thermostat['system_type']['reported'],
|
'reported' => $thermostat['system_type2']['reported'],
|
||||||
'detected' => $detected_system_type
|
'detected' => $detected_system_type2
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$attributes['alerts'] = $this->get_alerts(
|
$attributes['alerts'] = $this->get_alerts(
|
||||||
$thermostat,
|
$thermostat,
|
||||||
$ecobee_thermostat,
|
$ecobee_thermostat,
|
||||||
$attributes['system_type']
|
$attributes['system_type2']
|
||||||
);
|
);
|
||||||
|
|
||||||
$thermostat_group = $this->get_thermostat_group(
|
|
||||||
$thermostat,
|
|
||||||
$ecobee_thermostat,
|
|
||||||
$attributes['property'],
|
|
||||||
$address
|
|
||||||
);
|
|
||||||
$attributes['thermostat_group_id'] = $thermostat_group['thermostat_group_id'];
|
|
||||||
|
|
||||||
$this->api(
|
$this->api(
|
||||||
'thermostat',
|
'thermostat',
|
||||||
'update',
|
'update',
|
||||||
@ -255,16 +258,6 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update the thermostat_group system type and property type columns with
|
|
||||||
// the merged data from all of the thermostats in it.
|
|
||||||
$this->api(
|
|
||||||
'thermostat_group',
|
|
||||||
'sync_attributes',
|
|
||||||
[
|
|
||||||
'thermostat_group_id' => $thermostat_group['thermostat_group_id']
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the email_address on the user.
|
// Update the email_address on the user.
|
||||||
@ -426,7 +419,7 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
if(isset($ecobee_thermostat['house_details']['size']) === true) {
|
if(isset($ecobee_thermostat['house_details']['size']) === true) {
|
||||||
$square_feet = $ecobee_thermostat['house_details']['size'];
|
$square_feet = $ecobee_thermostat['house_details']['size'];
|
||||||
if(ctype_digit((string) $square_feet) === true && $square_feet > 0) {
|
if(ctype_digit((string) $square_feet) === true && $square_feet > 0) {
|
||||||
$property['square_feet'] = (int) $square_feet;
|
$property['square_feet'] = round($square_feet / 500) * 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,14 +552,14 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
|
|
||||||
// Has heat or cool
|
// Has heat or cool
|
||||||
if($system_type['reported']['heat'] !== null) {
|
if($system_type['reported']['heat'] !== null) {
|
||||||
$system_type_heat = $system_type['reported']['heat'];
|
$system_type_heat = $system_type['reported']['heat']['equipment'];
|
||||||
} else {
|
} else {
|
||||||
$system_type_heat = $system_type['detected']['heat'];
|
$system_type_heat = $system_type['detected']['heat']['equipment'];
|
||||||
}
|
}
|
||||||
if($system_type['reported']['heat_auxiliary'] !== null) {
|
if($system_type['reported']['heat_auxiliary'] !== null) {
|
||||||
$system_type_heat_auxiliary = $system_type['reported']['heat_auxiliary'];
|
$system_type_heat_auxiliary = $system_type['reported']['heat_auxiliary']['equipment'];
|
||||||
} else {
|
} else {
|
||||||
$system_type_heat_auxiliary = $system_type['detected']['heat_auxiliary'];
|
$system_type_heat_auxiliary = $system_type['detected']['heat_auxiliary']['equipment'];
|
||||||
}
|
}
|
||||||
$has_heat = (
|
$has_heat = (
|
||||||
$system_type_heat !== 'none' ||
|
$system_type_heat !== 'none' ||
|
||||||
@ -574,9 +567,9 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if($system_type['reported']['cool'] !== null) {
|
if($system_type['reported']['cool'] !== null) {
|
||||||
$system_type_cool = $system_type['reported']['cool'];
|
$system_type_cool = $system_type['reported']['cool']['equipment'];
|
||||||
} else {
|
} else {
|
||||||
$system_type_cool = $system_type['detected']['cool'];
|
$system_type_cool = $system_type['detected']['cool']['equipment'];
|
||||||
}
|
}
|
||||||
$has_cool = ($system_type_cool !== 'none');
|
$has_cool = ($system_type_cool !== 'none');
|
||||||
|
|
||||||
@ -653,42 +646,6 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
return sha1($alert['text'] . $alert['source']);
|
return sha1($alert['text'] . $alert['source']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Figure out which group this thermostat belongs in based on the address.
|
|
||||||
*
|
|
||||||
* @param array $thermostat
|
|
||||||
* @param array $ecobee_thermostat
|
|
||||||
* @param array $property
|
|
||||||
* @param array $address
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function get_thermostat_group($thermostat, $ecobee_thermostat, $property, $address) {
|
|
||||||
$thermostat_group = $this->api(
|
|
||||||
'thermostat_group',
|
|
||||||
'get',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'address_id' => $address['address_id']
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
if($thermostat_group === null) {
|
|
||||||
$thermostat_group = $this->api(
|
|
||||||
'thermostat_group',
|
|
||||||
'create',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'address_id' => $address['address_id']
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $thermostat_group;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try and detect the type of HVAC system.
|
* Try and detect the type of HVAC system.
|
||||||
*
|
*
|
||||||
@ -697,7 +654,7 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
*
|
*
|
||||||
* @return array System type for each of heat, cool, and aux.
|
* @return array System type for each of heat, cool, and aux.
|
||||||
*/
|
*/
|
||||||
private function get_detected_system_type($thermostat, $ecobee_thermostat) {
|
private function get_detected_system_type2($thermostat, $ecobee_thermostat) {
|
||||||
$detected_system_type = [];
|
$detected_system_type = [];
|
||||||
|
|
||||||
$settings = $ecobee_thermostat['settings'];
|
$settings = $ecobee_thermostat['settings'];
|
||||||
@ -716,12 +673,16 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Heat
|
// Heat
|
||||||
|
$detected_system_type['heat'] = [
|
||||||
|
'equipment' => null,
|
||||||
|
'stages' => null
|
||||||
|
];
|
||||||
if($settings['heatPumpGroundWater'] === true) {
|
if($settings['heatPumpGroundWater'] === true) {
|
||||||
$detected_system_type['heat'] = 'geothermal';
|
$detected_system_type['heat']['equipment'] = 'geothermal';
|
||||||
} else if($settings['hasHeatPump'] === true) {
|
} else if($settings['hasHeatPump'] === true) {
|
||||||
$detected_system_type['heat'] = 'compressor';
|
$detected_system_type['heat']['equipment'] = 'compressor';
|
||||||
} else if($settings['hasBoiler'] === true) {
|
} else if($settings['hasBoiler'] === true) {
|
||||||
$detected_system_type['heat'] = 'boiler';
|
$detected_system_type['heat']['equipment'] = 'boiler';
|
||||||
} else if(in_array('heat1', $outputs) === true) {
|
} else if(in_array('heat1', $outputs) === true) {
|
||||||
// This is the fastest way I was able to determine this. The further north
|
// This is the fastest way I was able to determine this. The further north
|
||||||
// you are the less likely you are to use electric heat.
|
// you are the less likely you are to use electric heat.
|
||||||
@ -731,39 +692,67 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
isset($address['normalized']['metadata']['latitude']) === true &&
|
isset($address['normalized']['metadata']['latitude']) === true &&
|
||||||
$address['normalized']['metadata']['latitude'] > 30
|
$address['normalized']['metadata']['latitude'] > 30
|
||||||
) {
|
) {
|
||||||
$detected_system_type['heat'] = 'gas';
|
$detected_system_type['heat']['equipment'] = 'gas';
|
||||||
} else {
|
} else {
|
||||||
$detected_system_type['heat'] = 'electric';
|
$detected_system_type['heat']['equipment'] = 'electric';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$detected_system_type['heat'] = 'electric';
|
$detected_system_type['heat']['equipment'] = 'electric';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$detected_system_type['heat'] = 'none';
|
$detected_system_type['heat']['equipment'] = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Rudimentary aux heat guess. It's pretty good overall but not as good as
|
// Rudimentary aux heat guess. It's pretty good overall but not as good as
|
||||||
// heat/cool.
|
// heat/cool.
|
||||||
|
$detected_system_type['heat_auxiliary'] = [
|
||||||
|
'equipment' => null,
|
||||||
|
'stages' => null
|
||||||
|
];
|
||||||
if(
|
if(
|
||||||
$detected_system_type['heat'] === 'gas' ||
|
$detected_system_type['heat']['equipment'] === 'gas' ||
|
||||||
$detected_system_type['heat'] === 'boiler' ||
|
$detected_system_type['heat']['equipment'] === 'boiler' ||
|
||||||
$detected_system_type['heat'] === 'oil' ||
|
$detected_system_type['heat']['equipment'] === 'oil' ||
|
||||||
$detected_system_type['heat'] === 'electric'
|
$detected_system_type['heat']['equipment'] === 'electric'
|
||||||
) {
|
) {
|
||||||
$detected_system_type['heat_auxiliary'] = 'none';
|
$detected_system_type['heat_auxiliary']['equipment'] = 'none';
|
||||||
} else if($detected_system_type['heat'] === 'compressor') {
|
} else if($detected_system_type['heat']['equipment'] === 'compressor') {
|
||||||
$detected_system_type['heat_auxiliary'] = 'electric';
|
$detected_system_type['heat_auxiliary']['equipment'] = 'electric';
|
||||||
} else {
|
|
||||||
$detected_system_type['heat_auxiliary'] = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cool
|
// Cool
|
||||||
|
$detected_system_type['cool'] = [
|
||||||
|
'equipment' => null,
|
||||||
|
'stages' => null
|
||||||
|
];
|
||||||
if($settings['heatPumpGroundWater'] === true) {
|
if($settings['heatPumpGroundWater'] === true) {
|
||||||
$detected_system_type['cool'] = 'geothermal';
|
$detected_system_type['cool']['equipment'] = 'geothermal';
|
||||||
} else if(in_array('compressor1', $outputs) === true) {
|
} else if(in_array('compressor1', $outputs) === true) {
|
||||||
$detected_system_type['cool'] = 'compressor';
|
$detected_system_type['cool']['equipment'] = 'compressor';
|
||||||
} else {
|
} else {
|
||||||
$detected_system_type['cool'] = 'none';
|
$detected_system_type['cool']['equipment'] = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stages. For whatever reason, heat stages seem wrong. They appear to
|
||||||
|
* match the number of "furnace" stages on the ecobee. Attempt to fix this
|
||||||
|
* by assuming that if heat and cool are both a compressor then pick the
|
||||||
|
* max of these two for both.
|
||||||
|
*/
|
||||||
|
if(
|
||||||
|
$detected_system_type['heat']['equipment'] === 'compressor' &&
|
||||||
|
$detected_system_type['cool']['equipment'] === 'compressor'
|
||||||
|
) {
|
||||||
|
$stages = max(
|
||||||
|
$ecobee_thermostat['settings']['coolStages'],
|
||||||
|
$ecobee_thermostat['settings']['heatStages']
|
||||||
|
);
|
||||||
|
$detected_system_type['heat']['stages'] = $stages;
|
||||||
|
$detected_system_type['cool']['stages'] = $stages;
|
||||||
|
} else {
|
||||||
|
$detected_system_type['heat']['stages'] = $ecobee_thermostat['settings']['heatStages'];
|
||||||
|
$detected_system_type['cool']['stages'] = $ecobee_thermostat['settings']['coolStages'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $detected_system_type;
|
return $detected_system_type;
|
||||||
@ -779,7 +768,6 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
*/
|
*/
|
||||||
private function get_weather($thermostat, $ecobee_thermostat) {
|
private function get_weather($thermostat, $ecobee_thermostat) {
|
||||||
$weather = [
|
$weather = [
|
||||||
'station' => null,
|
|
||||||
'dew_point' => null,
|
'dew_point' => null,
|
||||||
'barometric_pressure' => null,
|
'barometric_pressure' => null,
|
||||||
'humidity_relative' => null,
|
'humidity_relative' => null,
|
||||||
@ -791,10 +779,6 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
'condition' => null
|
'condition' => null
|
||||||
];
|
];
|
||||||
|
|
||||||
if(isset($ecobee_thermostat['weather']['weatherStation']) === true) {
|
|
||||||
$weather['station'] = $ecobee_thermostat['weather']['weatherStation'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(
|
if(
|
||||||
isset($ecobee_thermostat['weather']['forecasts']) === true &&
|
isset($ecobee_thermostat['weather']['forecasts']) === true &&
|
||||||
isset($ecobee_thermostat['weather']['forecasts'][0]) === true
|
isset($ecobee_thermostat['weather']['forecasts'][0]) === true
|
||||||
@ -910,6 +894,55 @@ class ecobee_thermostat extends cora\crud {
|
|||||||
return $weather;
|
return $weather;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get certain settings.
|
||||||
|
*
|
||||||
|
* @param array $thermostat
|
||||||
|
* @param array $ecobee_thermostat
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function get_settings($thermostat, $ecobee_thermostat) {
|
||||||
|
$settings = [];
|
||||||
|
|
||||||
|
if(isset($ecobee_thermostat['settings']['stage1CoolingDifferentialTemp']) === true) {
|
||||||
|
$settings['differential_cool'] = ($ecobee_thermostat['settings']['stage1CoolingDifferentialTemp'] / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($ecobee_thermostat['settings']['stage1HeatingDifferentialTemp']) === true) {
|
||||||
|
$settings['differential_heat'] = ($ecobee_thermostat['settings']['stage1HeatingDifferentialTemp'] / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get program. This is just a clone of the ecobee_thermostat program with a
|
||||||
|
* slight modification to the temperature values. First, divide by 10 since
|
||||||
|
* this data is returned directly by the API. Second, round. This is weird
|
||||||
|
* to do, but as an example one of my comfort settings said "74" in the
|
||||||
|
* ecobee GUI but "73.5" in the API. After changing it in the GUI the
|
||||||
|
* fractional amount was removed. I assume this is an old ecobee bug but it
|
||||||
|
* affects like 10% of thermostats in my database.
|
||||||
|
*
|
||||||
|
* @param array $thermostat
|
||||||
|
* @param array $ecobee_thermostat
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function get_program($thermostat, $ecobee_thermostat) {
|
||||||
|
$program = $ecobee_thermostat['program'];
|
||||||
|
if(isset($program['climates']) === true) {
|
||||||
|
foreach($program['climates'] as &$climate) {
|
||||||
|
$climate['coolTemp'] = round($climate['coolTemp'] / 10);
|
||||||
|
$climate['heatTemp'] = round($climate['heatTemp'] / 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $program;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current time zone. It's usually set. If not set use the offset
|
* Get the current time zone. It's usually set. If not set use the offset
|
||||||
* minutes to find it. Worst case default to the most common time zone.
|
* minutes to find it. Worst case default to the most common time zone.
|
||||||
|
223
api/profile.php
223
api/profile.php
@ -14,9 +14,7 @@ class profile extends cora\api {
|
|||||||
'public' => []
|
'public' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
public static $cache = [
|
public static $cache = [];
|
||||||
'generate' => 604800 // 7 Days
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a profile for the specified thermostat.
|
* Generate a profile for the specified thermostat.
|
||||||
@ -118,6 +116,30 @@ class profile extends cora\api {
|
|||||||
// Get some stuff
|
// Get some stuff
|
||||||
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
|
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
|
||||||
|
|
||||||
|
if($thermostat['system_type2']['reported']['heat']['equipment'] !== null) {
|
||||||
|
$system_type_heat = $thermostat['system_type2']['reported']['heat']['equipment'];
|
||||||
|
} else {
|
||||||
|
$system_type_heat = $thermostat['system_type2']['detected']['heat']['equipment'];
|
||||||
|
}
|
||||||
|
if($thermostat['system_type2']['reported']['cool']['equipment'] !== null) {
|
||||||
|
$system_type_cool = $thermostat['system_type2']['reported']['cool']['equipment'];
|
||||||
|
} else {
|
||||||
|
$system_type_cool = $thermostat['system_type2']['detected']['cool']['equipment'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if($thermostat['system_type2']['reported']['heat']['stages'] !== null) {
|
||||||
|
$heat_stages = $thermostat['system_type2']['reported']['heat']['stages'];
|
||||||
|
} else {
|
||||||
|
$heat_stages = $thermostat['system_type2']['detected']['heat']['stages'];
|
||||||
|
}
|
||||||
|
if($thermostat['system_type2']['reported']['cool']['stages'] !== null) {
|
||||||
|
$cool_stages = $thermostat['system_type2']['reported']['cool']['stages'];
|
||||||
|
} else {
|
||||||
|
$cool_stages = $thermostat['system_type2']['detected']['cool']['stages'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Figure out all the starting and ending times. Round begin/end to the
|
// Figure out all the starting and ending times. Round begin/end to the
|
||||||
// nearest 5 minutes to help with the looping later on.
|
// nearest 5 minutes to help with the looping later on.
|
||||||
$end_timestamp = time();
|
$end_timestamp = time();
|
||||||
@ -132,7 +154,7 @@ class profile extends cora\api {
|
|||||||
'read',
|
'read',
|
||||||
[
|
[
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'thermostat_group_id' => $thermostat['thermostat_group_id'],
|
'address_id' => $thermostat['address_id'],
|
||||||
'inactive' => 0
|
'inactive' => 0
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@ -185,7 +207,7 @@ class profile extends cora\api {
|
|||||||
'cool_1' => 0,
|
'cool_1' => 0,
|
||||||
'cool_2' => 0
|
'cool_2' => 0
|
||||||
];
|
];
|
||||||
$degree_days_baseline = 65;
|
$degree_days_base_temperature = 65;
|
||||||
$degree_days = [];
|
$degree_days = [];
|
||||||
$begin_runtime = [];
|
$begin_runtime = [];
|
||||||
|
|
||||||
@ -232,7 +254,7 @@ class profile extends cora\api {
|
|||||||
|
|
||||||
// Degree days
|
// Degree days
|
||||||
if($date !== $degree_days_date) {
|
if($date !== $degree_days_date) {
|
||||||
$degree_days[] = (array_mean($degree_days_temperatures) / 10) - $degree_days_baseline;
|
$degree_days[] = (array_mean($degree_days_temperatures) / 10) - $degree_days_base_temperature;
|
||||||
$degree_days_date = $date;
|
$degree_days_date = $date;
|
||||||
$degree_days_temperatures = [];
|
$degree_days_temperatures = [];
|
||||||
}
|
}
|
||||||
@ -700,40 +722,40 @@ class profile extends cora\api {
|
|||||||
'heat' => null,
|
'heat' => null,
|
||||||
'cool' => null
|
'cool' => null
|
||||||
],
|
],
|
||||||
|
'differential' => [
|
||||||
|
'heat' => null,
|
||||||
|
'cool' => null
|
||||||
|
],
|
||||||
|
'setback' => [
|
||||||
|
'heat' => null,
|
||||||
|
'cool' => null
|
||||||
|
],
|
||||||
'runtime' => [
|
'runtime' => [
|
||||||
'heat_1' => round($runtime_seconds['heat_1'] / 3600),
|
'heat_1' => round($runtime_seconds['heat_1'] / 60),
|
||||||
'heat_2' => round($runtime_seconds['heat_2'] / 3600),
|
'heat_2' => round($runtime_seconds['heat_2'] / 60),
|
||||||
'auxiliary_heat_1' => round($runtime_seconds['auxiliary_heat_1'] / 3600),
|
'auxiliary_heat_1' => round($runtime_seconds['auxiliary_heat_1'] / 60),
|
||||||
'auxiliary_heat_2' => round($runtime_seconds['auxiliary_heat_2'] / 3600),
|
'auxiliary_heat_2' => round($runtime_seconds['auxiliary_heat_2'] / 60),
|
||||||
'cool_1' => round($runtime_seconds['cool_1'] / 3600),
|
'cool_1' => round($runtime_seconds['cool_1'] / 60),
|
||||||
'cool_2' => round($runtime_seconds['cool_2'] / 3600),
|
'cool_2' => round($runtime_seconds['cool_2'] / 60),
|
||||||
|
],
|
||||||
|
'runtime_per_degree_day' => [
|
||||||
|
'heat_1' => null,
|
||||||
|
'heat_2' => null,
|
||||||
|
'cool_1' => null,
|
||||||
|
'cool_2' => null
|
||||||
|
],
|
||||||
|
'balance_point' => [
|
||||||
|
'heat_1' => null,
|
||||||
|
'heat_2' => null,
|
||||||
|
'resist' => null
|
||||||
|
],
|
||||||
|
'property' => [
|
||||||
|
'age' => null,
|
||||||
|
'square_feet' => null
|
||||||
],
|
],
|
||||||
'metadata' => [
|
'metadata' => [
|
||||||
'generated_at' => date('c'),
|
'generated_at' => date('c'),
|
||||||
'duration' => round((time() - strtotime($first_timestamp)) / 86400),
|
'duration' => round((time() - strtotime($first_timestamp)) / 86400),
|
||||||
'temperature' => [
|
|
||||||
'heat_1' => [
|
|
||||||
'deltas' => []
|
|
||||||
],
|
|
||||||
'heat_2' => [
|
|
||||||
'deltas' => []
|
|
||||||
],
|
|
||||||
'auxiliary_heat_1' => [
|
|
||||||
'deltas' => []
|
|
||||||
],
|
|
||||||
'auxiliary_heat_2' => [
|
|
||||||
'deltas' => []
|
|
||||||
],
|
|
||||||
'cool_1' => [
|
|
||||||
'deltas' => []
|
|
||||||
],
|
|
||||||
'cool_2' => [
|
|
||||||
'deltas' => []
|
|
||||||
],
|
|
||||||
'resist' => [
|
|
||||||
'deltas' => []
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -748,7 +770,6 @@ class profile extends cora\api {
|
|||||||
count($data['deltas_per_hour']) >= $required_samples
|
count($data['deltas_per_hour']) >= $required_samples
|
||||||
) {
|
) {
|
||||||
$deltas[$type][$outdoor_temperature] = round(array_median($data['deltas_per_hour']) / 10, 2);
|
$deltas[$type][$outdoor_temperature] = round(array_median($data['deltas_per_hour']) / 10, 2);
|
||||||
$profile['metadata']['temperature'][$type]['deltas'][$outdoor_temperature]['samples'] = count($data['deltas_per_hour']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -766,19 +787,28 @@ class profile extends cora\api {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(['heat', 'cool'] as $type) {
|
if(
|
||||||
if(count($setpoints[$type]) > 0) {
|
$system_type_cool !== null &&
|
||||||
$profile['setpoint'][$type] = round(array_mean($setpoints[$type])) / 10;
|
$system_type_cool !== 'none' &&
|
||||||
$profile['metadata']['setpoint'][$type]['samples'] = count($setpoints[$type]);
|
count($setpoints['cool']) > 0
|
||||||
}
|
) {
|
||||||
|
$profile['setpoint']['cool'] = round(array_mean($setpoints['cool'])) / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
$system_type_heat !== null &&
|
||||||
|
$system_type_heat !== 'none' &&
|
||||||
|
count($setpoints['heat']) > 0
|
||||||
|
) {
|
||||||
|
$profile['setpoint']['heat'] = round(array_mean($setpoints['heat'])) / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heating and cooling degree days.
|
// Heating and cooling degree days.
|
||||||
foreach($degree_days as $degree_day) {
|
foreach($degree_days as $degree_day) {
|
||||||
if($degree_day < 0) {
|
if($degree_day < 0) {
|
||||||
$profile['degree_days']['cool'] += ($degree_day * -1);
|
$profile['degree_days']['heat'] += ($degree_day * -1);
|
||||||
} else {
|
} else {
|
||||||
$profile['degree_days']['heat'] += ($degree_day);
|
$profile['degree_days']['cool'] += ($degree_day);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($profile['degree_days']['cool'] !== null) {
|
if ($profile['degree_days']['cool'] !== null) {
|
||||||
@ -788,6 +818,113 @@ class profile extends cora\api {
|
|||||||
$profile['degree_days']['heat'] = round($profile['degree_days']['heat']);
|
$profile['degree_days']['heat'] = round($profile['degree_days']['heat']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runtime per degree day
|
||||||
|
if($profile['degree_days']['heat'] !== null) {
|
||||||
|
if(
|
||||||
|
$system_type_heat !== null &&
|
||||||
|
$system_type_heat !== 'none'
|
||||||
|
) {
|
||||||
|
$profile['runtime_per_degree_day']['heat_1'] = round($profile['runtime']['heat_1'] / $profile['degree_days']['heat'], 2);
|
||||||
|
if($heat_stages === 2) {
|
||||||
|
$profile['runtime_per_degree_day']['heat_2'] = round($profile['runtime']['heat_2'] / $profile['degree_days']['heat'], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($profile['degree_days']['cool'] !== null) {
|
||||||
|
if(
|
||||||
|
$system_type_cool !== null &&
|
||||||
|
$system_type_cool !== 'none'
|
||||||
|
) {
|
||||||
|
$profile['runtime_per_degree_day']['cool_1'] = round($profile['runtime']['cool_1'] / $profile['degree_days']['cool'], 2);
|
||||||
|
if($cool_stages === 2) {
|
||||||
|
$profile['runtime_per_degree_day']['cool_2'] = round($profile['runtime']['cool_2'] / $profile['degree_days']['cool'], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balance point
|
||||||
|
if($system_type_heat === 'compressor') {
|
||||||
|
if(
|
||||||
|
$profile['temperature']['heat_1'] !== null &&
|
||||||
|
$profile['temperature']['heat_1']['linear_trendline'] !== null
|
||||||
|
) {
|
||||||
|
$linear_trendline = $profile['temperature']['heat_1']['linear_trendline'];
|
||||||
|
if($linear_trendline['slope'] > 0) {
|
||||||
|
$profile['balance_point']['heat_1'] = round((-1 * $linear_trendline['intercept']) / $linear_trendline['slope'], 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
$profile['temperature']['heat_2'] !== null &&
|
||||||
|
$profile['temperature']['heat_2']['linear_trendline'] !== null
|
||||||
|
) {
|
||||||
|
$linear_trendline = $profile['temperature']['heat_2']['linear_trendline'];
|
||||||
|
if($linear_trendline['slope'] > 0) {
|
||||||
|
$profile['balance_point']['heat_2'] = round((-1 * $linear_trendline['intercept']) / $linear_trendline['slope'], 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
$profile['temperature']['resist'] !== null &&
|
||||||
|
$profile['temperature']['resist']['linear_trendline'] !== null
|
||||||
|
) {
|
||||||
|
$linear_trendline = $profile['temperature']['resist']['linear_trendline'];
|
||||||
|
if($linear_trendline['slope'] > 0) {
|
||||||
|
$profile['balance_point']['resist'] = round((-1 * $linear_trendline['intercept']) / $linear_trendline['slope'], 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Differential
|
||||||
|
if(isset($thermostat['settings']['differential_heat']) === true) {
|
||||||
|
$profile['differential']['heat'] = $thermostat['settings']['differential_heat'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($thermostat['settings']['differential_cool']) === true) {
|
||||||
|
$profile['differential']['cool'] = $thermostat['settings']['differential_cool'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setback
|
||||||
|
if(isset($thermostat['program']['climates']) === true) {
|
||||||
|
foreach($thermostat['program']['climates'] as $climate) {
|
||||||
|
if($climate['climateRef'] === 'home') {
|
||||||
|
$temperature_home_cool = $climate['coolTemp'];
|
||||||
|
$temperature_home_heat = $climate['heatTemp'];
|
||||||
|
} else if($climate['climateRef'] === 'away') {
|
||||||
|
$temperature_away_cool = $climate['coolTemp'];
|
||||||
|
$temperature_away_heat = $climate['heatTemp'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
$system_type_cool !== null &&
|
||||||
|
$system_type_cool !== 'none' &&
|
||||||
|
isset($temperature_home_cool) === true &&
|
||||||
|
isset($temperature_away_cool) === true &&
|
||||||
|
$temperature_away_cool >= $temperature_home_cool
|
||||||
|
) {
|
||||||
|
$profile['setback']['cool'] = $temperature_away_cool - $temperature_home_cool;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
$system_type_heat !== null &&
|
||||||
|
$system_type_heat !== 'none' &&
|
||||||
|
isset($temperature_home_heat) === true &&
|
||||||
|
isset($temperature_away_heat) === true &&
|
||||||
|
$temperature_home_heat >= $temperature_away_heat
|
||||||
|
) {
|
||||||
|
$profile['setback']['heat'] = $temperature_home_heat - $temperature_away_heat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property
|
||||||
|
if(isset($thermostat['property']['age']) === true) {
|
||||||
|
$profile['property']['age'] = $thermostat['property']['age'];
|
||||||
|
}
|
||||||
|
if(isset($thermostat['property']['square_feet']) === true) {
|
||||||
|
$profile['property']['square_feet'] = $thermostat['property']['square_feet'];
|
||||||
|
}
|
||||||
|
|
||||||
return $profile;
|
return $profile;
|
||||||
}
|
}
|
||||||
|
@ -1,752 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some functionality for generating and working with temperature profiles.
|
|
||||||
* Per ecobee documentation: The values supplied for any given 5-minute
|
|
||||||
* interval is the value at the start of the interval and is not an average.
|
|
||||||
*
|
|
||||||
* @author Jon Ziebell
|
|
||||||
*/
|
|
||||||
class temperature_profile extends cora\api {
|
|
||||||
|
|
||||||
public static $exposed = [
|
|
||||||
'private' => [],
|
|
||||||
'public' => []
|
|
||||||
];
|
|
||||||
|
|
||||||
public static $cache = [
|
|
||||||
'generate' => 604800 // 7 Days
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a temperature profile for the specified thermostat.
|
|
||||||
*
|
|
||||||
* @param int $thermostat_id
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function generate($thermostat_id) {
|
|
||||||
set_time_limit(0);
|
|
||||||
|
|
||||||
// Make sure the thermostat_id provided is one of yours since there's no
|
|
||||||
// user_id security on the runtime_thermostat table.
|
|
||||||
$thermostats = $this->api('thermostat', 'read_id');
|
|
||||||
if (isset($thermostats[$thermostat_id]) === false) {
|
|
||||||
throw new Exception('Invalid thermostat_id.', 10300);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an interesting thing to fiddle with. Basically, the longer the
|
|
||||||
* minimum sample duration, the better your score. For example, let's say
|
|
||||||
* I set this to 10m and my 30° delta is -1°. If I increase the time to
|
|
||||||
* 60m, I may find that my 30° delta decreases to -0.5°.
|
|
||||||
*
|
|
||||||
* Initially I thought something was wrong, but this makes logical sense.
|
|
||||||
* If I'm only utilizing datasets where the system was completely off for
|
|
||||||
* a longer period of time, then I can infer that the outdoor conditions
|
|
||||||
* were favorable to allowing that to happen. Higher minimums most likely
|
|
||||||
* only include sunny periods with low wind.
|
|
||||||
*
|
|
||||||
* For now this is set to 30m, which I feel is an appropriate requirement.
|
|
||||||
* I am not factoring in any variables outside of temperature for now.
|
|
||||||
* Note that 30m is a MINIMUM due to the event_runtime_thermostat_text_id logic that
|
|
||||||
* will go back in time by 30m to account for sensor changes if the
|
|
||||||
* calendar event changes.
|
|
||||||
*/
|
|
||||||
$minimum_sample_duration = [
|
|
||||||
'heat' => 300,
|
|
||||||
'cool' => 300,
|
|
||||||
'resist' => 1800
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How long the system must be on/off for before starting a sample. Setting
|
|
||||||
* this to 5 minutes will use the very first sample which is fine if you
|
|
||||||
* assume the temperature in the sample is taken at the end of the 5m.
|
|
||||||
*/
|
|
||||||
$minimum_off_for = 300;
|
|
||||||
$minimum_on_for = 300;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increasing this value will decrease the number of data points by
|
|
||||||
* allowing for larger outdoor temperature swings in a single sample. For
|
|
||||||
* example, a value of 1 will start a new sample if the temperature
|
|
||||||
* changes by 1°, and a value of 5 will start a new sample if the
|
|
||||||
* temperature changes by 5°.
|
|
||||||
*/
|
|
||||||
$smoothing = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Require this many individual samples in a delta for a specific outdoor
|
|
||||||
* temperature. Increasing this basically cuts off the extremes where
|
|
||||||
* there are fewer samples.
|
|
||||||
*/
|
|
||||||
$required_samples = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Require this many individual points before a valid temperature profile
|
|
||||||
* can be returned.
|
|
||||||
*/
|
|
||||||
$required_points = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How far back to query for additional data. For example, when the
|
|
||||||
* event_runtime_thermostat_text_id changes I pull data from 30m ago. If that data is
|
|
||||||
* not available in the current runtime chunk, then it will fail. This
|
|
||||||
* will make sure that data is always included.
|
|
||||||
*/
|
|
||||||
$max_lookback = 1800; // 30 min
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How far in the future to query for additional data. For example, if a
|
|
||||||
* sample ends 20 minutes prior to an event change, I need to look ahead
|
|
||||||
* to see if an event change is in the future. If so, I need to adjust for
|
|
||||||
* that because the sensor averages will already be wrong.
|
|
||||||
*/
|
|
||||||
$max_lookahead = 1800; // 30 min
|
|
||||||
|
|
||||||
// Get some stuff
|
|
||||||
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
|
|
||||||
|
|
||||||
// Figure out all the starting and ending times. Round begin/end to the
|
|
||||||
// nearest 5 minutes to help with the looping later on.
|
|
||||||
$end_timestamp = time();
|
|
||||||
$begin_timestamp = strtotime('-1 year', $end_timestamp);
|
|
||||||
|
|
||||||
// Round to 5 minute intervals.
|
|
||||||
$begin_timestamp = floor($begin_timestamp / 300) * 300;
|
|
||||||
$end_timestamp = floor($end_timestamp / 300) * 300;
|
|
||||||
|
|
||||||
$group_thermostats = $this->api(
|
|
||||||
'thermostat',
|
|
||||||
'read',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_group_id' => $thermostat['thermostat_group_id'],
|
|
||||||
'inactive' => 0
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all of the relevant data
|
|
||||||
$thermostat_ids = array_column($group_thermostats, 'thermostat_id');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the largest possible chunk size given the number of thermostats I
|
|
||||||
* have to select data for. This is necessary to prevent the script from
|
|
||||||
* running out of memory. Also, as of PHP 7, structures have 6-7x of
|
|
||||||
* memory overhead.
|
|
||||||
*/
|
|
||||||
$memory_limit = 16; // mb
|
|
||||||
$memory_per_thermostat_per_day = 0.6; // mb
|
|
||||||
$days = (int) floor($memory_limit / ($memory_per_thermostat_per_day * count($thermostat_ids)));
|
|
||||||
|
|
||||||
$chunk_size = $days * 86400;
|
|
||||||
|
|
||||||
if($chunk_size === 0) {
|
|
||||||
throw new Exception('Too many thermostats; cannot generate temperature profile.', 10301);
|
|
||||||
}
|
|
||||||
|
|
||||||
$current_timestamp = $begin_timestamp;
|
|
||||||
$chunk_end_timestamp = 0;
|
|
||||||
$five_minutes = 300;
|
|
||||||
$thirty_minutes = 1800;
|
|
||||||
$all_off_for = 0;
|
|
||||||
$cool_on_for = 0;
|
|
||||||
$heat_on_for = 0;
|
|
||||||
$samples = [];
|
|
||||||
$times = [
|
|
||||||
'heat' => [],
|
|
||||||
'cool' => [],
|
|
||||||
'resist' => []
|
|
||||||
];
|
|
||||||
$begin_runtime = [];
|
|
||||||
|
|
||||||
while($current_timestamp <= $end_timestamp) {
|
|
||||||
// Get a new chunk of data.
|
|
||||||
if($current_timestamp >= $chunk_end_timestamp) {
|
|
||||||
$chunk_end_timestamp = $current_timestamp + $chunk_size;
|
|
||||||
|
|
||||||
$query = '
|
|
||||||
select
|
|
||||||
`timestamp`,
|
|
||||||
`thermostat_id`,
|
|
||||||
`indoor_temperature`,
|
|
||||||
`outdoor_temperature`,
|
|
||||||
`compressor_1`,
|
|
||||||
`compressor_2`,
|
|
||||||
`compressor_mode`,
|
|
||||||
`auxiliary_heat_1`,
|
|
||||||
`auxiliary_heat_2`,
|
|
||||||
`event_runtime_thermostat_text_id`,
|
|
||||||
`climate_runtime_thermostat_text_id`
|
|
||||||
from
|
|
||||||
`runtime_thermostat`
|
|
||||||
where
|
|
||||||
`thermostat_id` in (' . implode(',', $thermostat_ids) . ')
|
|
||||||
and `timestamp` >= "' . date('Y-m-d H:i:s', ($current_timestamp - $max_lookback)) . '"
|
|
||||||
and `timestamp` < "' . date('Y-m-d H:i:s', ($chunk_end_timestamp + $max_lookahead)) . '"
|
|
||||||
';
|
|
||||||
$result = $this->database->query($query);
|
|
||||||
|
|
||||||
$runtime = [];
|
|
||||||
while($row = $result->fetch_assoc()) {
|
|
||||||
if(
|
|
||||||
$thermostat['system_type']['detected']['heat'] === 'compressor' ||
|
|
||||||
$thermostat['system_type']['detected']['heat'] === 'geothermal'
|
|
||||||
) {
|
|
||||||
if($row['compressor_mode'] === 'heat') {
|
|
||||||
$row['heat'] = max(
|
|
||||||
$row['compressor_1'],
|
|
||||||
$row['compressor_2']
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$row['heat'] = 0;
|
|
||||||
}
|
|
||||||
$row['auxiliary_heat'] = max(
|
|
||||||
$row['auxiliary_heat_1'],
|
|
||||||
$row['auxiliary_heat_2']
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$row['heat'] = max(
|
|
||||||
$row['auxiliary_heat_1'],
|
|
||||||
$row['auxiliary_heat_2']
|
|
||||||
);
|
|
||||||
$row['auxiliary_heat'] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($row['compressor_mode'] === 'cool') {
|
|
||||||
$row['cool'] = max(
|
|
||||||
$row['compressor_1'],
|
|
||||||
$row['compressor_2']
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$row['cool'] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$timestamp = strtotime($row['timestamp']);
|
|
||||||
if (isset($runtime[$timestamp]) === false) {
|
|
||||||
$runtime[$timestamp] = [];
|
|
||||||
}
|
|
||||||
$runtime[$timestamp][$row['thermostat_id']] = $row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(
|
|
||||||
isset($runtime[$current_timestamp]) === true && // Had data for at least one thermostat
|
|
||||||
isset($runtime[$current_timestamp][$thermostat_id]) === true // Had data for the requested thermostat
|
|
||||||
) {
|
|
||||||
$current_runtime = $runtime[$current_timestamp][$thermostat_id];
|
|
||||||
if($current_runtime['outdoor_temperature'] !== null) {
|
|
||||||
// Rounds to the nearest degree (because temperatures are stored in tenths).
|
|
||||||
$current_runtime['outdoor_temperature'] = round($current_runtime['outdoor_temperature'] / 10) * 10;
|
|
||||||
|
|
||||||
// Applies further smoothing if required.
|
|
||||||
$current_runtime['outdoor_temperature'] = round($current_runtime['outdoor_temperature'] / $smoothing) * $smoothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OFF START
|
|
||||||
*/
|
|
||||||
|
|
||||||
$most_off = true;
|
|
||||||
$all_off = true;
|
|
||||||
if(
|
|
||||||
count($runtime[$current_timestamp]) < count($thermostat_ids)
|
|
||||||
) {
|
|
||||||
// If I didn't get data at this timestamp for all thermostats in the
|
|
||||||
// group, all off can't be true.
|
|
||||||
$all_off = false;
|
|
||||||
$most_off = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
foreach($runtime[$current_timestamp] as $runtime_thermostat_id => $thermostat_runtime) {
|
|
||||||
if(
|
|
||||||
$thermostat_runtime['compressor_1'] !== 0 ||
|
|
||||||
$thermostat_runtime['compressor_2'] !== 0 ||
|
|
||||||
$thermostat_runtime['auxiliary_heat_1'] !== 0 ||
|
|
||||||
$thermostat_runtime['auxiliary_heat_2'] !== 0 ||
|
|
||||||
$thermostat_runtime['outdoor_temperature'] === null ||
|
|
||||||
$thermostat_runtime['indoor_temperature'] === null ||
|
|
||||||
(
|
|
||||||
// Wasn't syncing this until mid-November 2018. Just going with December to be safe.
|
|
||||||
$thermostat_runtime['climate_runtime_thermostat_text_id'] === null &&
|
|
||||||
$current_timestamp > 1543640400
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// If I did have data at this timestamp for all thermostats in the
|
|
||||||
// group, check and see if were fully off. Also if any of the
|
|
||||||
// things used in the algorithm are just missing, assume the
|
|
||||||
// system might have been running.
|
|
||||||
$all_off = false;
|
|
||||||
|
|
||||||
// If everything _but_ the requested thermostat is off. This is
|
|
||||||
// used for the heat/cool scores as I need to only gather samples
|
|
||||||
// when everything else is off.
|
|
||||||
if($runtime_thermostat_id !== $thermostat_id) {
|
|
||||||
$most_off = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume that the runtime rows represent data at the end of that 5
|
|
||||||
// minutes.
|
|
||||||
if($all_off === true) {
|
|
||||||
$all_off_for += $five_minutes;
|
|
||||||
|
|
||||||
// Store the begin runtime row if the system has been off for the
|
|
||||||
// requisite length. This gives the temperatures a chance to settle.
|
|
||||||
if($all_off_for === $minimum_off_for) {
|
|
||||||
$begin_runtime['resist'] = $current_runtime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$all_off_for = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HEAT START
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Track how long the heat has been on for.
|
|
||||||
if($current_runtime['heat'] > 0) {
|
|
||||||
$heat_on_for += $current_runtime['heat'];
|
|
||||||
} else {
|
|
||||||
if($heat_on_for > 0) {
|
|
||||||
$times['heat'][] = $heat_on_for;
|
|
||||||
}
|
|
||||||
$heat_on_for = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the begin runtime for heat when the heat has been on for this
|
|
||||||
// thermostat only for the required minimum and everything else is off.
|
|
||||||
if(
|
|
||||||
$most_off === true &&
|
|
||||||
$heat_on_for >= $minimum_on_for &&
|
|
||||||
$current_runtime['auxiliary_heat'] === 0 &&
|
|
||||||
isset($begin_runtime['heat']) === false
|
|
||||||
) {
|
|
||||||
$begin_runtime['heat'] = $current_runtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* COOL START
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Track how long the cool has been on for.
|
|
||||||
if($current_runtime['cool'] > 0) {
|
|
||||||
$cool_on_for += $current_runtime['cool'];
|
|
||||||
} else {
|
|
||||||
if($cool_on_for > 0) {
|
|
||||||
$times['cool'][] = $cool_on_for;
|
|
||||||
}
|
|
||||||
$cool_on_for = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the begin runtime for cool when the cool has been on for this
|
|
||||||
// thermostat only for the required minimum and everything else is off.
|
|
||||||
if(
|
|
||||||
$most_off === true &&
|
|
||||||
$cool_on_for >= $minimum_on_for &&
|
|
||||||
isset($begin_runtime['cool']) === false
|
|
||||||
) {
|
|
||||||
$begin_runtime['cool'] = $current_runtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Look for changes which would trigger a sample to be gathered.
|
|
||||||
if(
|
|
||||||
(
|
|
||||||
// Heat
|
|
||||||
// Gather a "heat" delta for one of the following reasons.
|
|
||||||
// - The outdoor temperature changed
|
|
||||||
// - The calendar event changed
|
|
||||||
// - The climate changed
|
|
||||||
// - One of the other thermostats in this group turned on
|
|
||||||
($sample_type = 'heat') &&
|
|
||||||
isset($begin_runtime['heat']) === true &&
|
|
||||||
isset($previous_runtime) === true &&
|
|
||||||
(
|
|
||||||
$current_runtime['outdoor_temperature'] !== $begin_runtime['heat']['outdoor_temperature'] ||
|
|
||||||
$current_runtime['event_runtime_thermostat_text_id'] !== $begin_runtime['heat']['event_runtime_thermostat_text_id'] ||
|
|
||||||
$current_runtime['climate_runtime_thermostat_text_id'] !== $begin_runtime['heat']['climate_runtime_thermostat_text_id'] ||
|
|
||||||
$most_off === false
|
|
||||||
)
|
|
||||||
) ||
|
|
||||||
(
|
|
||||||
// Cool
|
|
||||||
// Gather a "cool" delta for one of the following reasons.
|
|
||||||
// - The outdoor temperature changed
|
|
||||||
// - The calendar event changed
|
|
||||||
// - The climate changed
|
|
||||||
// - One of the other thermostats in this group turned on
|
|
||||||
($sample_type = 'cool') &&
|
|
||||||
isset($begin_runtime['cool']) === true &&
|
|
||||||
isset($previous_runtime) === true &&
|
|
||||||
(
|
|
||||||
$current_runtime['outdoor_temperature'] !== $begin_runtime['cool']['outdoor_temperature'] ||
|
|
||||||
$current_runtime['event_runtime_thermostat_text_id'] !== $begin_runtime['cool']['event_runtime_thermostat_text_id'] ||
|
|
||||||
$current_runtime['climate_runtime_thermostat_text_id'] !== $begin_runtime['cool']['climate_runtime_thermostat_text_id'] ||
|
|
||||||
$most_off === false
|
|
||||||
)
|
|
||||||
) ||
|
|
||||||
(
|
|
||||||
// Resist
|
|
||||||
// Gather an "off" delta for one of the following reasons.
|
|
||||||
// - The outdoor temperature changed
|
|
||||||
// - The calendar event changed
|
|
||||||
// - The climate changed
|
|
||||||
// - The system turned back on after being off
|
|
||||||
($sample_type = 'resist') &&
|
|
||||||
isset($begin_runtime['resist']) === true &&
|
|
||||||
isset($previous_runtime) === true &&
|
|
||||||
(
|
|
||||||
$current_runtime['outdoor_temperature'] !== $begin_runtime['resist']['outdoor_temperature'] ||
|
|
||||||
$current_runtime['event_runtime_thermostat_text_id'] !== $begin_runtime['resist']['event_runtime_thermostat_text_id'] ||
|
|
||||||
$current_runtime['climate_runtime_thermostat_text_id'] !== $begin_runtime['resist']['climate_runtime_thermostat_text_id'] ||
|
|
||||||
$all_off === false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// By default the end sample is the previous sample (five minutes ago).
|
|
||||||
$offset = $five_minutes;
|
|
||||||
|
|
||||||
// If event_runtime_thermostat_text_id or climate_runtime_thermostat_text_id changes, need to ignore data
|
|
||||||
// from the previous 30 minutes as there are sensors changing during
|
|
||||||
// that time.
|
|
||||||
if(
|
|
||||||
$current_runtime['event_runtime_thermostat_text_id'] !== $begin_runtime[$sample_type]['event_runtime_thermostat_text_id'] ||
|
|
||||||
$current_runtime['climate_runtime_thermostat_text_id'] !== $begin_runtime[$sample_type]['climate_runtime_thermostat_text_id']
|
|
||||||
) {
|
|
||||||
$offset = $thirty_minutes;
|
|
||||||
} else {
|
|
||||||
// Start looking ahead into the next 30 minutes looking for changes
|
|
||||||
// to event_runtime_thermostat_text_id and climate_runtime_thermostat_text_id.
|
|
||||||
$lookahead = $five_minutes;
|
|
||||||
while($lookahead <= $thirty_minutes) {
|
|
||||||
if(
|
|
||||||
isset($runtime[$current_timestamp + $lookahead]) === true &&
|
|
||||||
isset($runtime[$current_timestamp + $lookahead][$thermostat_id]) === true &&
|
|
||||||
(
|
|
||||||
$runtime[$current_timestamp + $lookahead][$thermostat_id]['event_runtime_thermostat_text_id'] !== $current_runtime['event_runtime_thermostat_text_id'] ||
|
|
||||||
$runtime[$current_timestamp + $lookahead][$thermostat_id]['climate_runtime_thermostat_text_id'] !== $current_runtime['climate_runtime_thermostat_text_id']
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
$offset = ($thirty_minutes - $lookahead);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$lookahead += $five_minutes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now use the offset to set the proper end_runtime. This simply makes
|
|
||||||
// sure the data is present and then uses it. In the case where the
|
|
||||||
// desired data is missing, I *could* look back further but I'm not
|
|
||||||
// going to bother. It's pretty rare and adds some unwanted complexity
|
|
||||||
// to this.
|
|
||||||
if(
|
|
||||||
isset($runtime[$current_timestamp - $offset]) === true &&
|
|
||||||
isset($runtime[$current_timestamp - $offset][$thermostat_id]) === true &&
|
|
||||||
($current_timestamp - $offset) > strtotime($begin_runtime[$sample_type]['timestamp'])
|
|
||||||
) {
|
|
||||||
$end_runtime = $runtime[$current_timestamp - $offset][$thermostat_id];
|
|
||||||
} else {
|
|
||||||
$end_runtime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($end_runtime !== null) {
|
|
||||||
$delta = $end_runtime['indoor_temperature'] - $begin_runtime[$sample_type]['indoor_temperature'];
|
|
||||||
$duration = strtotime($end_runtime['timestamp']) - strtotime($begin_runtime[$sample_type]['timestamp']);
|
|
||||||
|
|
||||||
if($duration > 0) {
|
|
||||||
$sample = [
|
|
||||||
'type' => $sample_type,
|
|
||||||
'outdoor_temperature' => $begin_runtime[$sample_type]['outdoor_temperature'],
|
|
||||||
'delta' => $delta,
|
|
||||||
'duration' => $duration,
|
|
||||||
'delta_per_hour' => $delta / $duration * 3600,
|
|
||||||
];
|
|
||||||
$samples[] = $sample;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If in this block of code a change in runtime was detected, so
|
|
||||||
// update $begin_runtime[$sample_type] to the current runtime.
|
|
||||||
$begin_runtime[$sample_type] = $current_runtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
$previous_runtime = $current_runtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// After a change was detected it automatically moves begin to the
|
|
||||||
// current_runtime to start a new sample. This might be invalid so need to
|
|
||||||
// unset it if so.
|
|
||||||
if(
|
|
||||||
$heat_on_for === 0 ||
|
|
||||||
$current_runtime['outdoor_temperature'] === null ||
|
|
||||||
$current_runtime['indoor_temperature'] === null ||
|
|
||||||
$current_runtime['auxiliary_heat'] > 0
|
|
||||||
) {
|
|
||||||
unset($begin_runtime['heat']);
|
|
||||||
}
|
|
||||||
if(
|
|
||||||
$cool_on_for === 0 ||
|
|
||||||
$current_runtime['outdoor_temperature'] === null ||
|
|
||||||
$current_runtime['indoor_temperature'] === null
|
|
||||||
) {
|
|
||||||
unset($begin_runtime['cool']);
|
|
||||||
}
|
|
||||||
if($all_off_for === 0) {
|
|
||||||
unset($begin_runtime['resist']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$current_timestamp += $five_minutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the samples
|
|
||||||
$deltas_raw = [];
|
|
||||||
foreach($samples as $sample) {
|
|
||||||
$is_valid_sample = true;
|
|
||||||
if($sample['duration'] < $minimum_sample_duration[$sample['type']]) {
|
|
||||||
$is_valid_sample = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($is_valid_sample === true) {
|
|
||||||
if(isset($deltas_raw[$sample['type']]) === false) {
|
|
||||||
$deltas_raw[$sample['type']] = [];
|
|
||||||
}
|
|
||||||
if(isset($deltas_raw[$sample['type']][$sample['outdoor_temperature']]) === false) {
|
|
||||||
$deltas_raw[$sample['type']][$sample['outdoor_temperature']] = [
|
|
||||||
'deltas_per_hour' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$deltas_raw[$sample['type']][$sample['outdoor_temperature']]['deltas_per_hour'][] = $sample['delta_per_hour'];
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$deltas = [];
|
|
||||||
foreach($deltas_raw as $type => $raw) {
|
|
||||||
if(isset($deltas[$type]) === false) {
|
|
||||||
$deltas[$type] = [];
|
|
||||||
}
|
|
||||||
foreach($raw as $outdoor_temperature => $data) {
|
|
||||||
if(
|
|
||||||
isset($deltas[$type][$outdoor_temperature]) === false &&
|
|
||||||
count($data['deltas_per_hour']) >= $required_samples
|
|
||||||
) {
|
|
||||||
$deltas[$type][$outdoor_temperature] = round(array_median($data['deltas_per_hour']), 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the final temperature profile and save it.
|
|
||||||
$temperature_profile = [];
|
|
||||||
foreach($deltas as $type => $data) {
|
|
||||||
if(count($data) < $required_points) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort($deltas[$type]);
|
|
||||||
|
|
||||||
// For heating/cooling, factor in cycle time.
|
|
||||||
if(count($times[$type]) > 0) {
|
|
||||||
$cycles_per_hour = round(60 / (array_median($times[$type]) / 60), 2);
|
|
||||||
} else {
|
|
||||||
$cycles_per_hour = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$linear_trendline = $this->api(
|
|
||||||
'temperature_profile',
|
|
||||||
'get_linear_trendline',
|
|
||||||
[
|
|
||||||
'data' => $deltas[$type]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
$temperature_profile[$type] = [
|
|
||||||
'deltas' => $deltas[$type],
|
|
||||||
'linear_trendline' => $linear_trendline,
|
|
||||||
'cycles_per_hour' => $cycles_per_hour,
|
|
||||||
'metadata' => [
|
|
||||||
'generated_at' => date('Y-m-d H:i:s')
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
$temperature_profile[$type]['score'] = $this->api(
|
|
||||||
'temperature_profile',
|
|
||||||
'get_score',
|
|
||||||
[
|
|
||||||
'type' => $type,
|
|
||||||
'temperature_profile' => $temperature_profile[$type]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only actually save this profile to the thermostat if it was run with the
|
|
||||||
// default settings (aka the last year). Anything else is not valid to save.
|
|
||||||
// if($save === true) {
|
|
||||||
$this->api(
|
|
||||||
'thermostat',
|
|
||||||
'update',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_id' => $thermostat['thermostat_id'],
|
|
||||||
'temperature_profile' => $temperature_profile
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
// }
|
|
||||||
|
|
||||||
$this->database->set_time_zone(0);
|
|
||||||
|
|
||||||
// Force these to actually return, but set them to null if there's no data.
|
|
||||||
foreach(['heat', 'cool', 'resist'] as $type) {
|
|
||||||
if(
|
|
||||||
isset($temperature_profile[$type]) === false ||
|
|
||||||
count($temperature_profile[$type]['deltas']) === 0
|
|
||||||
) {
|
|
||||||
$temperature_profile[$type] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $temperature_profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the properties of a linear trendline for a given set of data.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
*
|
|
||||||
* @return array [slope, intercept]
|
|
||||||
*/
|
|
||||||
public function get_linear_trendline($data) {
|
|
||||||
// Requires at least two points.
|
|
||||||
if(count($data) < 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sum_x = 0;
|
|
||||||
$sum_y = 0;
|
|
||||||
$sum_xy = 0;
|
|
||||||
$sum_x_squared = 0;
|
|
||||||
$n = 0;
|
|
||||||
|
|
||||||
foreach($data as $x => $y) {
|
|
||||||
$sum_x += $x;
|
|
||||||
$sum_y += $y;
|
|
||||||
$sum_xy += ($x * $y);
|
|
||||||
$sum_x_squared += pow($x, 2);
|
|
||||||
$n++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$slope = (($n * $sum_xy) - ($sum_x * $sum_y)) / (($n * $sum_x_squared) - (pow($sum_x, 2)));
|
|
||||||
$intercept = (($sum_y) - ($slope * $sum_x)) / ($n);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'slope' => round($slope, 2),
|
|
||||||
'intercept' => round($intercept, 2)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the score from a linear trendline. For heating and cooling the slope
|
|
||||||
* is most of the score. For resist it is all of the score.
|
|
||||||
*
|
|
||||||
* Slope score is calculated as a percentage between 0 and whatever 3
|
|
||||||
* standard deviations from the mean is. For example, if that gives a range
|
|
||||||
* from 0-5, a slope of 2.5 would give you a base score of 0.5 which is then
|
|
||||||
* weighted in with the rest of the factors.
|
|
||||||
*
|
|
||||||
* Cycles per hour score is calculated as a flat 0.25 base score for every
|
|
||||||
* CPH under 4. For example, a CPH of 1
|
|
||||||
*
|
|
||||||
* @param array $temperature_profile
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function get_score($type, $temperature_profile) {
|
|
||||||
if(
|
|
||||||
$temperature_profile['linear_trendline'] === null
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$weights = [
|
|
||||||
'heat' => [
|
|
||||||
'slope' => 0.6,
|
|
||||||
'cycles_per_hour' => 0.1,
|
|
||||||
'balance_point' => 0.3
|
|
||||||
],
|
|
||||||
'cool' => [
|
|
||||||
'slope' => 0.6,
|
|
||||||
'cycles_per_hour' => 0.1,
|
|
||||||
'balance_point' => 0.3
|
|
||||||
],
|
|
||||||
'resist' => [
|
|
||||||
'slope' => 1
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Slope score
|
|
||||||
switch($type) {
|
|
||||||
case 'heat':
|
|
||||||
$slope_mean = 0.042;
|
|
||||||
$slope_standard_deviation = 0.179;
|
|
||||||
$balance_point_mean = -12.235;
|
|
||||||
// This is arbitrary. The actual SD is really high due to what I think
|
|
||||||
// is poor data. Further investigating but for now this does a pretty
|
|
||||||
// good job.
|
|
||||||
$balance_point_standard_deviation = 20;
|
|
||||||
break;
|
|
||||||
case 'cool':
|
|
||||||
$slope_mean = 0.066;
|
|
||||||
$slope_standard_deviation = 0.29;
|
|
||||||
$balance_point_mean = 90.002;
|
|
||||||
// This is arbitrary. The actual SD is really high due to what I think
|
|
||||||
// is poor data. Further investigating but for now this does a pretty
|
|
||||||
// good job.
|
|
||||||
$balance_point_standard_deviation = 20;
|
|
||||||
break;
|
|
||||||
case 'resist':
|
|
||||||
$slope_mean = 0.034;
|
|
||||||
$slope_standard_deviation = 0.018;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$parts = [];
|
|
||||||
$slope_max = $slope_mean + ($slope_standard_deviation * 3);
|
|
||||||
$parts['slope'] = ($slope_max - $temperature_profile['linear_trendline']['slope']) / $slope_max;
|
|
||||||
$parts['slope'] = max(0, min(1, $parts['slope']));
|
|
||||||
|
|
||||||
if($type === 'heat' || $type === 'cool') {
|
|
||||||
if($temperature_profile['linear_trendline']['slope'] == 0) {
|
|
||||||
$parts['balance_point'] = 1;
|
|
||||||
} else {
|
|
||||||
$balance_point_min = $balance_point_mean - ($balance_point_standard_deviation * 3);
|
|
||||||
$balance_point_max = $balance_point_mean + ($balance_point_standard_deviation * 3);
|
|
||||||
$balance_point = -$temperature_profile['linear_trendline']['intercept'] / $temperature_profile['linear_trendline']['slope'];
|
|
||||||
$parts['balance_point'] = ($balance_point - $balance_point_min) / ($balance_point_max - $balance_point_min);
|
|
||||||
$parts['balance_point'] = max(0, min(1, $parts['balance_point']));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cycles per hour score
|
|
||||||
if($temperature_profile['cycles_per_hour'] !== null) {
|
|
||||||
$parts['cycles_per_hour'] = (4 - $temperature_profile['cycles_per_hour']) * 0.25;
|
|
||||||
$parts['cycles_per_hour'] = max(0, min(1, $parts['cycles_per_hour']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$score = 0;
|
|
||||||
foreach($parts as $key => $value) {
|
|
||||||
$score += $value * $weights[$type][$key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return round($score * 10, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -12,15 +12,135 @@ class thermostat extends cora\crud {
|
|||||||
'read_id',
|
'read_id',
|
||||||
'sync',
|
'sync',
|
||||||
'dismiss_alert',
|
'dismiss_alert',
|
||||||
'restore_alert'
|
'restore_alert',
|
||||||
|
'set_reported_system_types',
|
||||||
|
'generate_profile',
|
||||||
|
'get_metrics'
|
||||||
],
|
],
|
||||||
'public' => []
|
'public' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
public static $cache = [
|
public static $cache = [
|
||||||
'sync' => 180 // 3 Minutes
|
'sync' => 180, // 3 Minutes
|
||||||
|
'generate_profile' => 604800, // 7 Days
|
||||||
|
'get_metrics' => 604800 // 7 Days
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a thermostat normally, plus does the generated columns.
|
||||||
|
*
|
||||||
|
* @param array $attributes
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function update($attributes) {
|
||||||
|
return parent::update(array_merge($attributes, $this->get_generated_columns($attributes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all of the generated columns.
|
||||||
|
*
|
||||||
|
* @param array $attributes The thermostat.
|
||||||
|
*
|
||||||
|
* @return array The generated columns only.
|
||||||
|
*/
|
||||||
|
private function get_generated_columns($attributes) {
|
||||||
|
$generated_columns = [];
|
||||||
|
|
||||||
|
if(isset($attributes['system_type2']) === true) {
|
||||||
|
foreach(['heat', 'heat_auxiliary', 'cool'] as $mode) {
|
||||||
|
if($attributes['system_type2']['reported'][$mode]['equipment'] !== null) {
|
||||||
|
$generated_columns['system_type_' . $mode] = $attributes['system_type2']['reported'][$mode]['equipment'];
|
||||||
|
} else {
|
||||||
|
$generated_columns['system_type_' . $mode] = $attributes['system_type2']['detected'][$mode]['equipment'];
|
||||||
|
}
|
||||||
|
if($attributes['system_type2']['reported'][$mode]['stages'] !== null) {
|
||||||
|
$generated_columns['system_type_' . $mode . '_stages'] = $attributes['system_type2']['reported'][$mode]['stages'];
|
||||||
|
} else {
|
||||||
|
$generated_columns['system_type_' . $mode . '_stages'] = $attributes['system_type2']['detected'][$mode]['stages'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($attributes['property']) === true) {
|
||||||
|
foreach(['age', 'square_feet', 'stories', 'structure_type'] as $characteristic) {
|
||||||
|
$generated_columns['property_' . $characteristic] = $attributes['property'][$characteristic];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($attributes['address_id']) === true) {
|
||||||
|
$address = $this->api('address', 'get', $attributes['address_id']);
|
||||||
|
if(
|
||||||
|
isset($address['normalized']['metadata']) === true &&
|
||||||
|
$address['normalized']['metadata'] !== null &&
|
||||||
|
isset($address['normalized']['metadata']['latitude']) === true &&
|
||||||
|
$address['normalized']['metadata']['latitude'] !== null &&
|
||||||
|
isset($address['normalized']['metadata']['longitude']) === true &&
|
||||||
|
$address['normalized']['metadata']['longitude'] !== null
|
||||||
|
) {
|
||||||
|
$generated_columns['address_latitude'] = $address['normalized']['metadata']['latitude'];
|
||||||
|
$generated_columns['address_longitude'] = $address['normalized']['metadata']['longitude'];
|
||||||
|
} else {
|
||||||
|
$generated_columns['address_latitude'] = null;
|
||||||
|
$generated_columns['address_longitude'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $generated_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the reported system type of this thermostat.
|
||||||
|
*
|
||||||
|
* @param int $thermostat_id
|
||||||
|
* @param array $system_types
|
||||||
|
*
|
||||||
|
* @return array The updated thermostat.
|
||||||
|
*/
|
||||||
|
public function set_reported_system_types($thermostat_id, $system_types) {
|
||||||
|
// Redundant, but makes sure you have access to edit the thermostat you
|
||||||
|
// submitted.
|
||||||
|
$thermostat = $this->get($thermostat_id);
|
||||||
|
|
||||||
|
foreach($system_types as $system_type => $value) {
|
||||||
|
if(in_array($system_type, ['heat', 'heat_auxiliary', 'cool']) === true) {
|
||||||
|
$thermostat['system_type2']['reported'][$system_type]['equipment'] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->update($thermostat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal read, but filter out generated columns. These columns exist only
|
||||||
|
* for indexing and searching purposes.
|
||||||
|
*
|
||||||
|
* @param array $attributes
|
||||||
|
* @param array $columns
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function read($attributes = [], $columns = []) {
|
||||||
|
$thermostats = parent::read($attributes, $columns);
|
||||||
|
|
||||||
|
foreach($thermostats as &$thermostat) {
|
||||||
|
unset($thermostat['system_type_heat']);
|
||||||
|
unset($thermostat['system_type_heat_stages']);
|
||||||
|
unset($thermostat['system_type_heat_auxiliary']);
|
||||||
|
unset($thermostat['system_type_heat_auxiliary_stages']);
|
||||||
|
unset($thermostat['system_type_cool']);
|
||||||
|
unset($thermostat['system_type_cool_stages']);
|
||||||
|
unset($thermostat['property_age']);
|
||||||
|
unset($thermostat['property_square_feet']);
|
||||||
|
unset($thermostat['property_stories']);
|
||||||
|
unset($thermostat['property_structure_type']);
|
||||||
|
unset($thermostat['address_latitude']);
|
||||||
|
unset($thermostat['address_longitude']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $thermostats;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync all thermostats for the current user. If we fail to get a lock, fail
|
* Sync all thermostats for the current user. If we fail to get a lock, fail
|
||||||
* silently (catch the exception) and just return false.
|
* silently (catch the exception) and just return false.
|
||||||
@ -96,4 +216,368 @@ class thermostat extends cora\crud {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new profile for this thermostat.
|
||||||
|
*
|
||||||
|
* @param int $thermostat_id
|
||||||
|
*/
|
||||||
|
public function generate_profile($thermostat_id) {
|
||||||
|
return $this->update([
|
||||||
|
'thermostat_id' => $thermostat_id,
|
||||||
|
'profile' => $this->api('profile', 'generate', $thermostat_id)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare this thermostat to all other matching ones.
|
||||||
|
*
|
||||||
|
* @param array $thermosat_id The base thermostat_id.
|
||||||
|
* @param array $attributes Optional attributes:
|
||||||
|
* property_structure_type
|
||||||
|
* property_age
|
||||||
|
* property_square_feet
|
||||||
|
* property_stories
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_metrics($thermostat_id, $attributes) {
|
||||||
|
$thermostat = $this->get($thermostat_id);
|
||||||
|
$generated_columns = $this->get_generated_columns($thermostat);
|
||||||
|
|
||||||
|
if(
|
||||||
|
$generated_columns['system_type_heat'] === null ||
|
||||||
|
$generated_columns['system_type_heat_stages'] === null ||
|
||||||
|
$generated_columns['system_type_cool'] === null ||
|
||||||
|
$generated_columns['system_type_cool_stages'] === null
|
||||||
|
) {
|
||||||
|
throw new cora\exception('System type is not defined.', 10700);
|
||||||
|
}
|
||||||
|
|
||||||
|
$where = [];
|
||||||
|
|
||||||
|
$keys_generated_columns = [
|
||||||
|
'system_type_heat',
|
||||||
|
'system_type_heat_stages',
|
||||||
|
'system_type_cool',
|
||||||
|
'system_type_cool_stages'
|
||||||
|
];
|
||||||
|
foreach($keys_generated_columns as $key) {
|
||||||
|
$where[] = $this->database->column_equals_value_where(
|
||||||
|
$key,
|
||||||
|
$generated_columns[$key]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$keys_custom = [
|
||||||
|
'property_structure_type',
|
||||||
|
'property_age',
|
||||||
|
'property_square_feet',
|
||||||
|
'property_stories'
|
||||||
|
];
|
||||||
|
foreach($keys_custom as $key) {
|
||||||
|
if(isset($attributes[$key]) === true) {
|
||||||
|
$where[] = $this->database->column_equals_value_where(
|
||||||
|
$key,
|
||||||
|
$attributes[$key]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally radius implies a circle. In this case it's a square as this
|
||||||
|
* helps with some optimization.
|
||||||
|
*/
|
||||||
|
if(isset($attributes['radius']) === true) {
|
||||||
|
if(
|
||||||
|
is_array($attributes['radius']) === false ||
|
||||||
|
$attributes['radius']['operator'] !== '<'
|
||||||
|
) {
|
||||||
|
throw new \Exception('Radius must be defined as less than a value.', 10702);
|
||||||
|
}
|
||||||
|
|
||||||
|
$radius = (int) $attributes['radius']['value'];
|
||||||
|
if(
|
||||||
|
isset($generated_columns['address_latitude']) === false ||
|
||||||
|
isset($generated_columns['address_longitude']) === false
|
||||||
|
) {
|
||||||
|
// Require a valid address (latitude/longitude) when using radius.
|
||||||
|
throw new cora\exception('Cannot compare by radius if address is invalid.', 10701);
|
||||||
|
} else {
|
||||||
|
// Latitude is 69mi / °
|
||||||
|
$degrees_latitude_delta = $radius / 69 / 2;
|
||||||
|
$minimum_latitude = $generated_columns['address_latitude'] - $degrees_latitude_delta;
|
||||||
|
$maximum_latitude = $generated_columns['address_latitude'] + $degrees_latitude_delta;
|
||||||
|
if ($minimum_latitude < -90) {
|
||||||
|
$overflow = abs($minimum_latitude + 90);
|
||||||
|
$between_a = [-90, $maximum_latitude];
|
||||||
|
sort($between_a);
|
||||||
|
$between_b = [90, (90 - $overflow)];
|
||||||
|
sort($between_b);
|
||||||
|
$where[] = '(`address_latitude` between ' . $between_a[0] . ' and ' . $between_a[1] . ' or `address_latitude` between ' . $between_b[0] . ' and ' . $between_b[1] . ')';
|
||||||
|
}
|
||||||
|
else if ($maximum_latitude > 90) {
|
||||||
|
$overflow = abs($maximum_latitude - 90);
|
||||||
|
$between_a = [90, $minimum_latitude];
|
||||||
|
sort($between_a);
|
||||||
|
$between_b = [-90, (-90 + $overflow)];
|
||||||
|
sort($between_b);
|
||||||
|
$where[] = '(`address_latitude` between ' . $between_a[0] . ' and ' . $between_a[1] . ' or `address_latitude` between ' . $between_b[0] . ' and ' . $between_b[1] . ')';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$between_a = [$minimum_latitude, $maximum_latitude];
|
||||||
|
sort($between_a);
|
||||||
|
$where[] = '`address_latitude` between ' . $between_a[0] . ' and ' . $between_a[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Longitude is 69mi / ° at the equator and then shrinks towards the poles.
|
||||||
|
$degrees_longitude_delta = $radius / 69 / 2;
|
||||||
|
$minimum_longitude = $generated_columns['address_longitude'] - $degrees_longitude_delta;
|
||||||
|
$maximum_longitude = $generated_columns['address_longitude'] + $degrees_longitude_delta;
|
||||||
|
if ($minimum_longitude < -180) {
|
||||||
|
$overflow = abs($minimum_longitude + 180);
|
||||||
|
$between_a = [-180, $maximum_longitude];
|
||||||
|
sort($between_a);
|
||||||
|
$between_b = [180, (180 - $overflow)];
|
||||||
|
sort($between_b);
|
||||||
|
$where[] = '(`address_longitude` between ' . $between_a[0] . ' and ' . $between_a[1] . ' or `address_longitude` between ' . $between_b[0] . ' and ' . $between_b[1] . ')';
|
||||||
|
}
|
||||||
|
else if ($maximum_longitude > 180) {
|
||||||
|
$overflow = abs($maximum_longitude - 180);
|
||||||
|
$between_a = [180, $minimum_longitude];
|
||||||
|
sort($between_a);
|
||||||
|
$between_b = [-180, (-180 + $overflow)];
|
||||||
|
sort($between_b);
|
||||||
|
$where[] = '(`address_longitude` between ' . $between_a[0] . ' and ' . $between_a[1] . ' or `address_longitude` between ' . $between_b[0] . ' and ' . $between_b[1] . ')';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$between_a = [$minimum_longitude, $maximum_longitude];
|
||||||
|
sort($between_a);
|
||||||
|
$where[] = '`address_longitude` between ' . $between_a[0] . ' and ' . $between_a[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should match their position in the thermostat profile exactly.
|
||||||
|
$metric_codes = [
|
||||||
|
'property' => [
|
||||||
|
'age',
|
||||||
|
'square_feet'
|
||||||
|
],
|
||||||
|
'runtime_per_degree_day' => [
|
||||||
|
'heat_1',
|
||||||
|
'heat_2',
|
||||||
|
'cool_1',
|
||||||
|
'cool_2'
|
||||||
|
],
|
||||||
|
'setpoint' => [
|
||||||
|
'heat',
|
||||||
|
'cool'
|
||||||
|
],
|
||||||
|
'setback' => [
|
||||||
|
'heat',
|
||||||
|
'cool'
|
||||||
|
],
|
||||||
|
'balance_point' => [
|
||||||
|
'heat_1',
|
||||||
|
'heat_2',
|
||||||
|
'resist'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set all of the metric intervals. If Celsius add a bit of precision.
|
||||||
|
$intervals = [];
|
||||||
|
|
||||||
|
$intervals['property'] = [
|
||||||
|
'age' => 1,
|
||||||
|
'square_feet' => 500
|
||||||
|
];
|
||||||
|
|
||||||
|
$intervals['runtime_per_degree_day'] = [
|
||||||
|
'heat_1' => 1,
|
||||||
|
'heat_2' => 1,
|
||||||
|
'cool_1' => 1,
|
||||||
|
'cool_2' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
$intervals['setpoint'] = [
|
||||||
|
'heat' => 0.5,
|
||||||
|
'cool' => 0.5
|
||||||
|
];
|
||||||
|
|
||||||
|
if($thermostat['temperature_unit'] === '°F') {
|
||||||
|
$intervals['setback'] = [
|
||||||
|
'heat' => 1,
|
||||||
|
'cool' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
$intervals['balance_point'] = [
|
||||||
|
'heat_1' => 1,
|
||||||
|
'heat_2' => 1,
|
||||||
|
'resist' => 1
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$intervals['setback'] = [
|
||||||
|
'heat' => 0.5,
|
||||||
|
'cool' => 0.5
|
||||||
|
];
|
||||||
|
|
||||||
|
$intervals['balance_point'] = [
|
||||||
|
'heat_1' => 0.5,
|
||||||
|
'heat_2' => 0.5,
|
||||||
|
'resist' => 0.5
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$get_metric_template = function() {
|
||||||
|
return [
|
||||||
|
'values' => [],
|
||||||
|
'histogram' => [],
|
||||||
|
'standard_deviation' => null,
|
||||||
|
'median' => null,
|
||||||
|
'precision' => null
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
$metrics = [];
|
||||||
|
foreach($metric_codes as $parent_metric_name => $parent_metric) {
|
||||||
|
$metrics[$parent_metric_name] = [];
|
||||||
|
foreach($parent_metric as $child_metric_name) {
|
||||||
|
$metrics[$parent_metric_name][$child_metric_name] = $get_metric_template();
|
||||||
|
$metrics[$parent_metric_name][$child_metric_name]['interval'] = $intervals[$parent_metric_name][$child_metric_name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$limit_start = 0;
|
||||||
|
$limit_count = 250;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selecting lots of rows can eventually run PHP out of memory, so chunk
|
||||||
|
* this up into several queries to avoid that.
|
||||||
|
*/
|
||||||
|
do {
|
||||||
|
$result = $this->database->query('
|
||||||
|
select
|
||||||
|
*
|
||||||
|
from
|
||||||
|
thermostat
|
||||||
|
where ' .
|
||||||
|
implode(' and ', $where) . '
|
||||||
|
limit ' . $limit_start . ',' . $limit_count . '
|
||||||
|
');
|
||||||
|
|
||||||
|
// Get all the scores from the other thermostats
|
||||||
|
while($other_thermostat = $result->fetch_assoc()) {
|
||||||
|
$other_thermostat['profile'] = json_decode($other_thermostat['profile'], true);
|
||||||
|
// Only use profiles with at least a year of data
|
||||||
|
// Only use profiles generated in the past year
|
||||||
|
if(
|
||||||
|
$other_thermostat['profile']['metadata']['duration'] >= 365 &&
|
||||||
|
strtotime($other_thermostat['profile']['metadata']['generated_at']) > strtotime('-1 year') &&
|
||||||
|
$other_thermostat['thermostat_id'] !== $thermostat_id
|
||||||
|
) {
|
||||||
|
foreach($metric_codes as $parent_metric_name => $parent_metric) {
|
||||||
|
foreach($parent_metric as $child_metric_name) {
|
||||||
|
if(
|
||||||
|
isset($thermostat['profile'][$parent_metric_name]) === true &&
|
||||||
|
isset($thermostat['profile'][$parent_metric_name][$child_metric_name]) === true &&
|
||||||
|
$thermostat['profile'][$parent_metric_name][$child_metric_name] !== null &&
|
||||||
|
isset($other_thermostat['profile'][$parent_metric_name]) === true &&
|
||||||
|
isset($other_thermostat['profile'][$parent_metric_name][$child_metric_name]) === true &&
|
||||||
|
$other_thermostat['profile'][$parent_metric_name][$child_metric_name] !== null
|
||||||
|
) {
|
||||||
|
$interval = $intervals[$parent_metric_name][$child_metric_name];
|
||||||
|
$data = round($other_thermostat['profile'][$parent_metric_name][$child_metric_name] / $interval) * $interval;
|
||||||
|
|
||||||
|
$precision = strlen(substr(strrchr($interval, "."), 1));
|
||||||
|
$data = number_format($data, $precision, '.', '');
|
||||||
|
|
||||||
|
$metrics[$parent_metric_name][$child_metric_name]['values'][] = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$limit_start += $limit_count;
|
||||||
|
} while ($result->num_rows === $limit_count);
|
||||||
|
|
||||||
|
// Cleanup. Set the standard deviation, median, and remove the temporary
|
||||||
|
// values and any metrics that have no data.
|
||||||
|
foreach($metric_codes as $parent_metric_name => $parent_metric) {
|
||||||
|
foreach($parent_metric as $child_metric_name) {
|
||||||
|
$data = $this->remove_outliers($metrics[$parent_metric_name][$child_metric_name]['values']);
|
||||||
|
// print_r($data);
|
||||||
|
if(count($data['values']) > 0) {
|
||||||
|
$metrics[$parent_metric_name][$child_metric_name]['histogram'] = $data['histogram'];
|
||||||
|
$metrics[$parent_metric_name][$child_metric_name]['standard_deviation'] = $this->standard_deviation($data['values']);
|
||||||
|
$metrics[$parent_metric_name][$child_metric_name]['median'] = floatval(array_median($data['values']));
|
||||||
|
unset($metrics[$parent_metric_name][$child_metric_name]['values']);
|
||||||
|
} else {
|
||||||
|
$metrics[$parent_metric_name][$child_metric_name] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the standard deviation of an array of numbers.
|
||||||
|
*
|
||||||
|
* @param array $array The values.
|
||||||
|
*
|
||||||
|
* @return int The standard deviation.
|
||||||
|
*/
|
||||||
|
private function standard_deviation($array) {
|
||||||
|
$count = count($array);
|
||||||
|
|
||||||
|
if ($count === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mean = array_mean($array);
|
||||||
|
|
||||||
|
$variance = 0;
|
||||||
|
foreach($array as $i) {
|
||||||
|
$variance += pow(($i - $mean), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return round(sqrt($variance / $count), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove outliers more than 2 standard deviations away from the mean. This
|
||||||
|
* is an effective way to keep the scales meaningul for normal data.
|
||||||
|
*
|
||||||
|
* @param array $array Input array
|
||||||
|
*
|
||||||
|
* @return array Input array minus outliers.
|
||||||
|
*/
|
||||||
|
private function remove_outliers($array) {
|
||||||
|
$mean = array_mean($array);
|
||||||
|
$standard_deviation = $this->standard_deviation($array);
|
||||||
|
|
||||||
|
$min = $mean - ($standard_deviation * 2);
|
||||||
|
$max = $mean + ($standard_deviation * 2);
|
||||||
|
|
||||||
|
$values = [];
|
||||||
|
$histogram = [];
|
||||||
|
foreach($array as $value) {
|
||||||
|
if($value >= $min && $value <= $max) {
|
||||||
|
$values[] = $value;
|
||||||
|
|
||||||
|
$value_string = strval($value);
|
||||||
|
if(isset($histogram[$value_string]) === false) {
|
||||||
|
$histogram[$value_string] = 0;
|
||||||
|
}
|
||||||
|
$histogram[$value_string]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'values' => $values,
|
||||||
|
'histogram' => $histogram
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,904 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A group of thermostats. Thermostats are grouped by address. A thermostat
|
|
||||||
* group has any number of thermostats and a single combined temperature
|
|
||||||
* profile.
|
|
||||||
*
|
|
||||||
* @author Jon Ziebell
|
|
||||||
*/
|
|
||||||
class thermostat_group extends cora\crud {
|
|
||||||
|
|
||||||
public static $exposed = [
|
|
||||||
'private' => [
|
|
||||||
'read_id',
|
|
||||||
'generate_temperature_profiles',
|
|
||||||
'generate_temperature_profile',
|
|
||||||
'generate_profiles',
|
|
||||||
'generate_profile',
|
|
||||||
'get_scores',
|
|
||||||
'get_metrics',
|
|
||||||
'update_system_types'
|
|
||||||
],
|
|
||||||
'public' => []
|
|
||||||
];
|
|
||||||
|
|
||||||
public static $cache = [
|
|
||||||
'generate_temperature_profile' => 604800, // 7 Days
|
|
||||||
'generate_temperature_profiles' => 604800, // 7 Days
|
|
||||||
'generate_profile' => 604800, // 7 Days
|
|
||||||
'generate_profiles' => 604800, // 7 Days
|
|
||||||
'get_scores' => 604800, // 7 Days
|
|
||||||
// 'get_metrics' => 604800 // 7 Days
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the group temperature profile.
|
|
||||||
*
|
|
||||||
* @param int $thermostat_group_id
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function generate_profile($thermostat_group_id) {
|
|
||||||
// Get all thermostats in this group.
|
|
||||||
$thermostats = $this->api(
|
|
||||||
'thermostat',
|
|
||||||
'read',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_group_id' => $thermostat_group_id,
|
|
||||||
'inactive' => 0
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate a temperature profile for each thermostat in this group.
|
|
||||||
$profiles = [];
|
|
||||||
foreach($thermostats as $thermostat) {
|
|
||||||
$profile = $this->api('profile', 'generate', $thermostat['thermostat_id']);
|
|
||||||
|
|
||||||
$this->api(
|
|
||||||
'thermostat',
|
|
||||||
'update',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_id' => $thermostat['thermostat_id'],
|
|
||||||
'profile' => $profile
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$profiles[] = $profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all of the individual deltas for averaging.
|
|
||||||
$group_profile = [
|
|
||||||
'setpoint' => [
|
|
||||||
'heat' => null,
|
|
||||||
'cool' => null
|
|
||||||
],
|
|
||||||
'degree_days' => [
|
|
||||||
'heat' => null,
|
|
||||||
'cool' => null
|
|
||||||
],
|
|
||||||
'runtime' => [
|
|
||||||
'heat_1' => 0,
|
|
||||||
'heat_2' => 0,
|
|
||||||
'auxiliary_heat_1' => 0,
|
|
||||||
'auxiliary_heat_2' => 0,
|
|
||||||
'cool_1' => 0,
|
|
||||||
'cool_2' => 0
|
|
||||||
],
|
|
||||||
'metadata' => [
|
|
||||||
'generated_at' => date('c'),
|
|
||||||
'duration' => null,
|
|
||||||
'setpoint' => [
|
|
||||||
'heat' => [
|
|
||||||
'samples' => null
|
|
||||||
],
|
|
||||||
'cool' => [
|
|
||||||
'samples' => null
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'temperature' => []
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
if (count($profiles) === 0) {
|
|
||||||
$this->update(
|
|
||||||
[
|
|
||||||
'thermostat_group_id' => $thermostat_group_id,
|
|
||||||
'profile' => $group_profile
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return $group_profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
$metadata_duration = [];
|
|
||||||
|
|
||||||
// Setpoint heat min/max/average.
|
|
||||||
$metadata_setpoint_heat_samples = [];
|
|
||||||
$setpoint_heat = [];
|
|
||||||
|
|
||||||
// Setpoint cool min/max/average.
|
|
||||||
$metadata_setpoint_cool_samples = [];
|
|
||||||
$setpoint_cool = [];
|
|
||||||
|
|
||||||
// Temperature profiles.
|
|
||||||
$temperature = [];
|
|
||||||
$metadata_temperature = [];
|
|
||||||
|
|
||||||
foreach($profiles as $profile) {
|
|
||||||
// Group profile duration is the minimum of all individual profile
|
|
||||||
// durations.
|
|
||||||
if($profile['metadata']['duration'] !== null) {
|
|
||||||
$metadata_duration[] = $profile['metadata']['duration'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if($profile['setpoint']['heat'] !== null) {
|
|
||||||
$setpoint_heat[] = [
|
|
||||||
'value' => $profile['setpoint']['heat'],
|
|
||||||
'samples' => $profile['metadata']['setpoint']['heat']['samples']
|
|
||||||
];
|
|
||||||
$metadata_setpoint_heat_samples[] = $profile['metadata']['setpoint']['heat']['samples'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if($profile['setpoint']['cool'] !== null) {
|
|
||||||
$setpoint_cool[] = [
|
|
||||||
'value' => $profile['setpoint']['cool'],
|
|
||||||
'samples' => $profile['metadata']['setpoint']['cool']['samples']
|
|
||||||
];
|
|
||||||
$metadata_setpoint_cool_samples[] = $profile['metadata']['setpoint']['cool']['samples'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temperature profiles.
|
|
||||||
foreach($profile['temperature'] as $type => $data) {
|
|
||||||
if($data !== null) {
|
|
||||||
foreach($data['deltas'] as $outdoor_temperature => $delta) {
|
|
||||||
$temperature[$type]['deltas'][$outdoor_temperature][] = [
|
|
||||||
'value' => $delta,
|
|
||||||
'samples' => $profile['metadata']['temperature'][$type]['deltas'][$outdoor_temperature]['samples']
|
|
||||||
];
|
|
||||||
$metadata_temperature[$type]['deltas'][$outdoor_temperature]['samples'][] =
|
|
||||||
$profile['metadata']['temperature'][$type]['deltas'][$outdoor_temperature]['samples'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Degree days.
|
|
||||||
if($profile['degree_days']['heat'] !== null) {
|
|
||||||
$group_profile['degree_days']['heat'] += $profile['degree_days']['heat'];
|
|
||||||
}
|
|
||||||
if($profile['degree_days']['cool'] !== null) {
|
|
||||||
$group_profile['degree_days']['cool'] += $profile['degree_days']['cool'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runtime
|
|
||||||
$group_profile['runtime']['heat_1'] += $profile['runtime']['heat_1'];
|
|
||||||
$group_profile['runtime']['heat_2'] += $profile['runtime']['heat_2'];
|
|
||||||
$group_profile['runtime']['auxiliary_heat_1'] += $profile['runtime']['auxiliary_heat_1'];
|
|
||||||
$group_profile['runtime']['auxiliary_heat_2'] += $profile['runtime']['auxiliary_heat_2'];
|
|
||||||
$group_profile['runtime']['cool_1'] += $profile['runtime']['cool_1'];
|
|
||||||
$group_profile['runtime']['cool_2'] += $profile['runtime']['cool_2'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// echo '<pre>';
|
|
||||||
// print_r($profiles);
|
|
||||||
// die();
|
|
||||||
|
|
||||||
$group_profile['metadata']['duration'] = min($metadata_duration);
|
|
||||||
|
|
||||||
// Setpoint heat min/max/average.
|
|
||||||
$group_profile['metadata']['setpoint']['heat']['samples'] = array_sum($metadata_setpoint_heat_samples);
|
|
||||||
if($group_profile['metadata']['setpoint']['heat']['samples'] > 0) {
|
|
||||||
$group_profile['setpoint']['heat'] = 0;
|
|
||||||
foreach($setpoint_heat as $data) {
|
|
||||||
$group_profile['setpoint']['heat'] +=
|
|
||||||
($data['value'] * $data['samples'] / $group_profile['metadata']['setpoint']['heat']['samples']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setpoint cool min/max/average.
|
|
||||||
$group_profile['metadata']['setpoint']['cool']['samples'] = array_sum($metadata_setpoint_cool_samples);
|
|
||||||
if($group_profile['metadata']['setpoint']['cool']['samples'] > 0) {
|
|
||||||
$group_profile['setpoint']['cool'] = 0;
|
|
||||||
foreach($setpoint_cool as $data) {
|
|
||||||
$group_profile['setpoint']['cool'] +=
|
|
||||||
($data['value'] * $data['samples'] / $group_profile['metadata']['setpoint']['cool']['samples']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// echo '<pre>';
|
|
||||||
// print_r($temperature);
|
|
||||||
// die();
|
|
||||||
|
|
||||||
// Temperature profiles.
|
|
||||||
foreach($temperature as $type => $data) {
|
|
||||||
foreach($data['deltas'] as $outdoor_temperature => $delta) {
|
|
||||||
$group_profile['metadata']['temperature'][$type]['deltas'][$outdoor_temperature]['samples'] =
|
|
||||||
array_sum($metadata_temperature[$type]['deltas'][$outdoor_temperature]['samples']);
|
|
||||||
if($group_profile['metadata']['temperature'][$type]['deltas'][$outdoor_temperature]['samples'] > 0) {
|
|
||||||
$group_profile['temperature'][$type]['deltas'][$outdoor_temperature] = 0;
|
|
||||||
foreach($temperature[$type]['deltas'][$outdoor_temperature] as $data) {
|
|
||||||
$group_profile['temperature'][$type]['deltas'][$outdoor_temperature] +=
|
|
||||||
($data['value'] * $data['samples'] / $group_profile['metadata']['temperature'][$type]['deltas'][$outdoor_temperature]['samples']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ksort($group_profile['temperature'][$type]['deltas']);
|
|
||||||
|
|
||||||
$group_profile['temperature'][$type]['linear_trendline'] = $this->api(
|
|
||||||
'profile',
|
|
||||||
'get_linear_trendline',
|
|
||||||
['data' => $group_profile['temperature'][$type]['deltas']]
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// echo '<pre>';
|
|
||||||
// print_r($group_profile);
|
|
||||||
// die();
|
|
||||||
|
|
||||||
$this->update(
|
|
||||||
[
|
|
||||||
'thermostat_group_id' => $thermostat_group_id,
|
|
||||||
'profile' => $group_profile
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Force these to actually return, but set them to null if there's no data.
|
|
||||||
foreach(['heat', 'cool', 'resist'] as $type) {
|
|
||||||
if(isset($group_profile['temperature'][$type]) === false) {
|
|
||||||
$group_profile['temperature'][$type] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $group_profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate temperature profiles for all thermostat_groups. This pretty much
|
|
||||||
* only exists for the cron job.
|
|
||||||
*/
|
|
||||||
public function generate_profiles() {
|
|
||||||
// Get all thermostat_groups.
|
|
||||||
$thermostat_groups = $this->read();
|
|
||||||
foreach($thermostat_groups as $thermostat_group) {
|
|
||||||
$this->generate_profile(
|
|
||||||
$thermostat_group['thermostat_group_id'],
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->api(
|
|
||||||
'user',
|
|
||||||
'update_sync_status',
|
|
||||||
['key' => 'thermostat_group.generate_profiles']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the group temperature profile.
|
|
||||||
*
|
|
||||||
* @param int $thermostat_group_id
|
|
||||||
* @param string $begin When to begin the temperature profile at.
|
|
||||||
* @param string $end When to end the temperature profile at.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function generate_temperature_profile($thermostat_group_id, $begin, $end) {
|
|
||||||
if($begin === null && $end === null) {
|
|
||||||
$save = true;
|
|
||||||
} else {
|
|
||||||
$save = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all thermostats in this group.
|
|
||||||
$thermostats = $this->api(
|
|
||||||
'thermostat',
|
|
||||||
'read',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_group_id' => $thermostat_group_id,
|
|
||||||
'inactive' => 0
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate a temperature profile for each thermostat in this group.
|
|
||||||
$temperature_profiles = [];
|
|
||||||
foreach($thermostats as $thermostat) {
|
|
||||||
$temperature_profiles[] = $this->api(
|
|
||||||
'temperature_profile',
|
|
||||||
'generate',
|
|
||||||
[
|
|
||||||
'thermostat_id' => $thermostat['thermostat_id'],
|
|
||||||
'begin' => $begin,
|
|
||||||
'end' => $end
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all of the individual deltas for averaging.
|
|
||||||
$group_temperature_profile = [];
|
|
||||||
foreach($temperature_profiles as $temperature_profile) {
|
|
||||||
foreach($temperature_profile as $type => $data) {
|
|
||||||
if($data !== null) {
|
|
||||||
foreach($data['deltas'] as $outdoor_temperature => $delta) {
|
|
||||||
$group_temperature_profile[$type]['deltas'][$outdoor_temperature][] = $delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isset($data['cycles_per_hour']) === true) {
|
|
||||||
$group_temperature_profile[$type]['cycles_per_hour'][] = $data['cycles_per_hour'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// if(isset($data['generated_at']) === true) {
|
|
||||||
// $group_temperature_profile[$type]['generated_at'][] = $data['generated_at'];
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the average deltas, then get the trendline and score.
|
|
||||||
foreach($group_temperature_profile as $type => $data) {
|
|
||||||
foreach($data['deltas'] as $outdoor_temperature => $delta) {
|
|
||||||
$group_temperature_profile[$type]['deltas'][$outdoor_temperature] =
|
|
||||||
array_sum($group_temperature_profile[$type]['deltas'][$outdoor_temperature]) /
|
|
||||||
count($group_temperature_profile[$type]['deltas'][$outdoor_temperature]);
|
|
||||||
}
|
|
||||||
ksort($group_temperature_profile[$type]['deltas']);
|
|
||||||
|
|
||||||
if(isset($data['cycles_per_hour']) === true) {
|
|
||||||
$group_temperature_profile[$type]['cycles_per_hour'] =
|
|
||||||
array_sum($data['cycles_per_hour']) / count($data['cycles_per_hour']);
|
|
||||||
} else {
|
|
||||||
$group_temperature_profile[$type]['cycles_per_hour'] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$group_temperature_profile[$type]['linear_trendline'] = $this->api(
|
|
||||||
'temperature_profile',
|
|
||||||
'get_linear_trendline',
|
|
||||||
['data' => $group_temperature_profile[$type]['deltas']]
|
|
||||||
);
|
|
||||||
|
|
||||||
$group_temperature_profile[$type]['score'] = $this->api(
|
|
||||||
'temperature_profile',
|
|
||||||
'get_score',
|
|
||||||
[
|
|
||||||
'type' => $type,
|
|
||||||
'temperature_profile' => $group_temperature_profile[$type]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$group_temperature_profile[$type]['metadata'] = [
|
|
||||||
'generated_at' => date('Y-m-d H:i:s')
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only actually save this profile to the thermostat if it was run with the
|
|
||||||
// default settings (aka the last year). Anything else is not valid to save.
|
|
||||||
if($save === true) {
|
|
||||||
$this->update(
|
|
||||||
[
|
|
||||||
'thermostat_group_id' => $thermostat_group_id,
|
|
||||||
'temperature_profile' => $group_temperature_profile
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force these to actually return, but set them to null if there's no data.
|
|
||||||
foreach(['heat', 'cool', 'resist'] as $type) {
|
|
||||||
if(isset($group_temperature_profile[$type]) === false) {
|
|
||||||
$group_temperature_profile[$type] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $group_temperature_profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate temperature profiles for all thermostat_groups. This pretty much
|
|
||||||
* only exists for the cron job.
|
|
||||||
*/
|
|
||||||
public function generate_temperature_profiles() {
|
|
||||||
// Get all thermostat_groups.
|
|
||||||
$thermostat_groups = $this->read();
|
|
||||||
foreach($thermostat_groups as $thermostat_group) {
|
|
||||||
$this->generate_temperature_profile(
|
|
||||||
$thermostat_group['thermostat_group_id'],
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->api(
|
|
||||||
'user',
|
|
||||||
'update_sync_status',
|
|
||||||
['key' => 'thermostat_group.generate_temperature_profiles']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare this thermostat_group to all other matching ones.
|
|
||||||
*
|
|
||||||
* @param string $type resist|heat|cool
|
|
||||||
* @param array $attributes The attributes to compare to.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_scores($type, $attributes) {
|
|
||||||
// See #281
|
|
||||||
set_time_limit(30);
|
|
||||||
|
|
||||||
// All or none are required.
|
|
||||||
if(
|
|
||||||
(
|
|
||||||
isset($attributes['address_latitude']) === true ||
|
|
||||||
isset($attributes['address_longitude']) === true ||
|
|
||||||
isset($attributes['address_radius']) === true
|
|
||||||
) &&
|
|
||||||
(
|
|
||||||
isset($attributes['address_latitude']) === false ||
|
|
||||||
isset($attributes['address_longitude']) === false ||
|
|
||||||
isset($attributes['address_radius']) === false
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new Exception('If one of address_latitude, address_longitude, or address_radius are set, then all are required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull these values out so they don't get queried; this comparison is done
|
|
||||||
// in PHP.
|
|
||||||
if(isset($attributes['address_radius']) === true) {
|
|
||||||
$address_latitude = $attributes['address_latitude'];
|
|
||||||
$address_longitude = $attributes['address_longitude'];
|
|
||||||
$address_radius = $attributes['address_radius'];
|
|
||||||
|
|
||||||
unset($attributes['address_latitude']);
|
|
||||||
unset($attributes['address_longitude']);
|
|
||||||
unset($attributes['address_radius']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scores = [];
|
|
||||||
$limit_start = 0;
|
|
||||||
$limit_count = 100;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selecting lots of rows can eventually run PHP out of memory, so chunk
|
|
||||||
* this up into several queries to avoid that.
|
|
||||||
*/
|
|
||||||
do {
|
|
||||||
// Get all matching thermostat groups.
|
|
||||||
$other_thermostat_groups = $this->database->read(
|
|
||||||
'thermostat_group',
|
|
||||||
$attributes,
|
|
||||||
[], // columns
|
|
||||||
[], // order_by
|
|
||||||
[$limit_start, $limit_count] // limit
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all the scores from the other thermostat groups
|
|
||||||
foreach($other_thermostat_groups as $other_thermostat_group) {
|
|
||||||
if(
|
|
||||||
isset($other_thermostat_group['temperature_profile'][$type]) === true &&
|
|
||||||
isset($other_thermostat_group['temperature_profile'][$type]['score']) === true &&
|
|
||||||
$other_thermostat_group['temperature_profile'][$type]['score'] !== null &&
|
|
||||||
isset($other_thermostat_group['temperature_profile'][$type]['metadata']) === true &&
|
|
||||||
isset($other_thermostat_group['temperature_profile'][$type]['metadata']['generated_at']) === true &&
|
|
||||||
strtotime($other_thermostat_group['temperature_profile'][$type]['metadata']['generated_at']) > strtotime('-1 month')
|
|
||||||
) {
|
|
||||||
// Skip thermostat_groups that are too far away.
|
|
||||||
if(
|
|
||||||
isset($address_radius) === true &&
|
|
||||||
$this->haversine_great_circle_distance(
|
|
||||||
$address_latitude,
|
|
||||||
$address_longitude,
|
|
||||||
$other_thermostat_group['address_latitude'],
|
|
||||||
$other_thermostat_group['address_longitude']
|
|
||||||
) > $address_radius
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore profiles with too few datapoints. Ideally this would be time-
|
|
||||||
// based...so don't use a profile if it hasn't experienced a full year
|
|
||||||
// or heating/cooling system, but that isn't stored presently. A good
|
|
||||||
// approximation is to make sure there is a solid set of data driving
|
|
||||||
// the profile.
|
|
||||||
$required_delta_count = (($type === 'resist') ? 40 : 20);
|
|
||||||
if(count($other_thermostat_group['temperature_profile'][$type]['deltas']) < $required_delta_count) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Round the scores so they can be better displayed on a histogram or
|
|
||||||
// bell curve.
|
|
||||||
// TODO: Might be able to get rid of this? I don't think new scores are calculated at this level of detail anymore...
|
|
||||||
// $scores[] = round(
|
|
||||||
// $other_thermostat_group['temperature_profile'][$type]['score'],
|
|
||||||
// 1
|
|
||||||
// );
|
|
||||||
$scores[] = $other_thermostat_group['temperature_profile'][$type]['score'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$limit_start += $limit_count;
|
|
||||||
} while (count($other_thermostat_groups) === $limit_count);
|
|
||||||
|
|
||||||
sort($scores);
|
|
||||||
|
|
||||||
return $scores;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare this thermostat_group to all other matching ones.
|
|
||||||
*
|
|
||||||
* @param array $attributes The attributes to compare to.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_metrics($type, $attributes) {
|
|
||||||
// All or none are required.
|
|
||||||
if(
|
|
||||||
(
|
|
||||||
isset($attributes['address_latitude']) === true ||
|
|
||||||
isset($attributes['address_longitude']) === true ||
|
|
||||||
isset($attributes['address_radius']) === true
|
|
||||||
) &&
|
|
||||||
(
|
|
||||||
isset($attributes['address_latitude']) === false ||
|
|
||||||
isset($attributes['address_longitude']) === false ||
|
|
||||||
isset($attributes['address_radius']) === false
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new Exception('If one of address_latitude, address_longitude, or address_radius are set, then all are required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull these values out so they don't get queried; this comparison is done
|
|
||||||
// in PHP.
|
|
||||||
if(isset($attributes['address_radius']) === true) {
|
|
||||||
$address_latitude = $attributes['address_latitude'];
|
|
||||||
$address_longitude = $attributes['address_longitude'];
|
|
||||||
$address_radius = $attributes['address_radius'];
|
|
||||||
|
|
||||||
unset($attributes['address_latitude']);
|
|
||||||
unset($attributes['address_longitude']);
|
|
||||||
unset($attributes['address_radius']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$metric_codes = [
|
|
||||||
'setpoint_heat',
|
|
||||||
'setpoint_cool',
|
|
||||||
// 'runtime_per_heating_degree_day',
|
|
||||||
'runtime_percentage_heat_1',
|
|
||||||
'runtime_percentage_heat_2',
|
|
||||||
'runtime_percentage_auxiliary_heat_1',
|
|
||||||
'runtime_percentage_auxiliary_heat_2',
|
|
||||||
'runtime_percentage_cool_1',
|
|
||||||
'runtime_percentage_cool_2'
|
|
||||||
];
|
|
||||||
|
|
||||||
$metrics = [];
|
|
||||||
foreach($metric_codes as $metric_code) {
|
|
||||||
$metrics[$metric_code] = [
|
|
||||||
'values' => [],
|
|
||||||
'histogram' => [],
|
|
||||||
'standard_deviation' => null,
|
|
||||||
'median' => null
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$limit_start = 0;
|
|
||||||
$limit_count = 100;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selecting lots of rows can eventually run PHP out of memory, so chunk
|
|
||||||
* this up into several queries to avoid that.
|
|
||||||
*/
|
|
||||||
do {
|
|
||||||
// Get all matching thermostat groups.
|
|
||||||
$other_thermostat_groups = $this->database->read(
|
|
||||||
'thermostat_group',
|
|
||||||
$attributes,
|
|
||||||
[], // columns
|
|
||||||
[], // order_by
|
|
||||||
[$limit_start, $limit_count] // limit
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all the scores from the other thermostat groups
|
|
||||||
foreach($other_thermostat_groups as $other_thermostat_group) {
|
|
||||||
// Only use profiles with at least a year of data
|
|
||||||
// Only use profiles generated in the past year
|
|
||||||
//
|
|
||||||
if(
|
|
||||||
$other_thermostat_group['profile']['metadata']['duration'] >= 365 &&
|
|
||||||
strtotime($other_thermostat_group['profile']['metadata']['generated_at']) > strtotime('-1 year')
|
|
||||||
) {
|
|
||||||
// Skip thermostat_groups that are too far away.
|
|
||||||
if(
|
|
||||||
isset($address_radius) === true &&
|
|
||||||
$this->haversine_great_circle_distance(
|
|
||||||
$address_latitude,
|
|
||||||
$address_longitude,
|
|
||||||
$other_thermostat_group['address_latitude'],
|
|
||||||
$other_thermostat_group['address_longitude']
|
|
||||||
) > $address_radius
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setpoint_heat
|
|
||||||
if($other_thermostat_group['profile']['setpoint']['heat'] !== null) {
|
|
||||||
$setpoint_heat = round($other_thermostat_group['profile']['setpoint']['heat']);
|
|
||||||
if(isset($metrics['setpoint_heat']['histogram'][$setpoint_heat]) === false) {
|
|
||||||
$metrics['setpoint_heat']['histogram'][$setpoint_heat] = 0;
|
|
||||||
}
|
|
||||||
$metrics['setpoint_heat']['histogram'][$setpoint_heat]++;
|
|
||||||
$metrics['setpoint_heat']['values'][] = $setpoint_heat;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setpoint_cool
|
|
||||||
if($other_thermostat_group['profile']['setpoint']['cool'] !== null) {
|
|
||||||
$setpoint_cool = round($other_thermostat_group['profile']['setpoint']['cool']);
|
|
||||||
if(isset($metrics['setpoint_cool']['histogram'][$setpoint_cool]) === false) {
|
|
||||||
$metrics['setpoint_cool']['histogram'][$setpoint_cool] = 0;
|
|
||||||
}
|
|
||||||
$metrics['setpoint_cool']['histogram'][$setpoint_cool]++;
|
|
||||||
$metrics['setpoint_cool']['values'][] = $setpoint_cool;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// runtime_per_heating_degree_day
|
|
||||||
// todo division by 0 error here. Also remove this?
|
|
||||||
/* if(
|
|
||||||
isset($other_thermostat_group['profile']) === true &&
|
|
||||||
isset($other_thermostat_group['profile']['runtime']) == true &&
|
|
||||||
$other_thermostat_group['profile']['runtime']['heat_1'] !== null &&
|
|
||||||
isset($other_thermostat_group['profile']['degree_days']) === true &&
|
|
||||||
$other_thermostat_group['profile']['degree_days']['heat'] !== null
|
|
||||||
) {
|
|
||||||
$runtime_per_heating_degree_day = round(
|
|
||||||
$other_thermostat_group['profile']['runtime']['heat_1'] / $other_thermostat_group['profile']['degree_days']['heat'],
|
|
||||||
1
|
|
||||||
);
|
|
||||||
if(isset($metrics['runtime_per_heating_degree_day']['histogram'][(string)$runtime_per_heating_degree_day]) === false) {
|
|
||||||
$metrics['runtime_per_heating_degree_day']['histogram'][(string)$runtime_per_heating_degree_day] = 0;
|
|
||||||
}
|
|
||||||
$metrics['runtime_per_heating_degree_day']['histogram'][(string)$runtime_per_heating_degree_day]++;
|
|
||||||
$metrics['runtime_per_heating_degree_day']['values'][] = $runtime_per_heating_degree_day;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// runtime_percentage_heat_1
|
|
||||||
/* $total_runtime_heat =
|
|
||||||
$other_thermostat_group['profile']['runtime']['heat_1'] +
|
|
||||||
$other_thermostat_group['profile']['runtime']['heat_2'] +
|
|
||||||
$other_thermostat_group['profile']['runtime']['auxiliary_heat_1'] +
|
|
||||||
$other_thermostat_group['profile']['runtime']['auxiliary_heat_2'];
|
|
||||||
|
|
||||||
if($total_runtime_heat > 0) {
|
|
||||||
$runtime_percentage_heat_1 = $other_thermostat_group['profile']['runtime']['heat_1'] / $total_runtime_heat * 100;
|
|
||||||
|
|
||||||
if(isset($metrics['runtime_percentage_heat_1']['histogram'][$runtime_percentage_heat_1]) === false) {
|
|
||||||
$metrics['runtime_percentage_heat_1']['histogram'][$runtime_percentage_heat_1] = 0;
|
|
||||||
}
|
|
||||||
$metrics['runtime_percentage_heat_1']['histogram'][$runtime_percentage_heat_1]++;
|
|
||||||
$metrics['runtime_percentage_heat_1']['values'][] = $runtime_percentage_heat_1;
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$limit_start += $limit_count;
|
|
||||||
} while (count($other_thermostat_groups) === $limit_count);
|
|
||||||
|
|
||||||
// setpoint_heat
|
|
||||||
$metrics['setpoint_heat']['standard_deviation'] = round($this->standard_deviation(
|
|
||||||
$metrics['setpoint_heat']['values']
|
|
||||||
), 2);
|
|
||||||
$metrics['setpoint_heat']['median'] = array_median($metrics['setpoint_heat']['values']);
|
|
||||||
unset($metrics['setpoint_heat']['values']);
|
|
||||||
|
|
||||||
// setpoint_cool
|
|
||||||
$metrics['setpoint_cool']['standard_deviation'] = round($this->standard_deviation(
|
|
||||||
$metrics['setpoint_cool']['values']
|
|
||||||
), 2);
|
|
||||||
$metrics['setpoint_cool']['median'] = array_median($metrics['setpoint_cool']['values']);
|
|
||||||
unset($metrics['setpoint_cool']['values']);
|
|
||||||
|
|
||||||
// runtime_per_heating_degree_day
|
|
||||||
// $metrics['runtime_per_heating_degree_day']['standard_deviation'] = round($this->standard_deviation(
|
|
||||||
// $metrics['runtime_per_heating_degree_day']['values']
|
|
||||||
// ), 2);
|
|
||||||
// $metrics['runtime_per_heating_degree_day']['median'] = array_median($metrics['runtime_per_heating_degree_day']['values']);
|
|
||||||
// unset($metrics['runtime_per_heating_degree_day']['values']);
|
|
||||||
|
|
||||||
// runtime_percentage_heat_1
|
|
||||||
// $metrics['runtime_percentage_heat_1']['standard_deviation'] = round($this->standard_deviation(
|
|
||||||
// $metrics['runtime_percentage_heat_1']['values']
|
|
||||||
// ), 2);
|
|
||||||
// $metrics['runtime_percentage_heat_1']['median'] = array_median($metrics['runtime_percentage_heat_1']['values']);
|
|
||||||
// unset($metrics['runtime_percentage_heat_1']['values']);
|
|
||||||
|
|
||||||
return $metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function standard_deviation($array) {
|
|
||||||
$count = count($array);
|
|
||||||
|
|
||||||
$mean = array_mean($array);
|
|
||||||
|
|
||||||
$variance = 0;
|
|
||||||
foreach($array as $i) {
|
|
||||||
$variance += pow(($i - $mean), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sqrt($variance / $count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the great-circle distance between two points, with the
|
|
||||||
* Haversine formula.
|
|
||||||
*
|
|
||||||
* @param float $latitude_from Latitude of start point in [deg decimal]
|
|
||||||
* @param float $longitude_from Longitude of start point in [deg decimal]
|
|
||||||
* @param float $latitude_to Latitude of target point in [deg decimal]
|
|
||||||
* @param float $longitude_to Longitude of target point in [deg decimal]
|
|
||||||
* @param float $earth_radius Mean earth radius in [mi]
|
|
||||||
*
|
|
||||||
* @link https://stackoverflow.com/a/10054282
|
|
||||||
*
|
|
||||||
* @return float Distance between points in [mi] (same as earth_radius)
|
|
||||||
*/
|
|
||||||
private function haversine_great_circle_distance($latitude_from, $longitude_from, $latitude_to, $longitude_to, $earth_radius = 3959) {
|
|
||||||
$latitude_from_radians = deg2rad($latitude_from);
|
|
||||||
$longitude_from_radians = deg2rad($longitude_from);
|
|
||||||
$latitude_to_radians = deg2rad($latitude_to);
|
|
||||||
$longitude_to_radians = deg2rad($longitude_to);
|
|
||||||
|
|
||||||
$latitude_delta = $latitude_to_radians - $latitude_from_radians;
|
|
||||||
$longitude_delta = $longitude_to_radians - $longitude_from_radians;
|
|
||||||
|
|
||||||
$angle = 2 * asin(sqrt(pow(sin($latitude_delta / 2), 2) +
|
|
||||||
cos($latitude_from_radians) * cos($latitude_to_radians) * pow(sin($longitude_delta / 2), 2)));
|
|
||||||
|
|
||||||
return $angle * $earth_radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look at all the properties of individual thermostats in this group and
|
|
||||||
* apply them to the thermostat_group. This resolves issues where values are
|
|
||||||
* set on one thermostat but null on another.
|
|
||||||
*
|
|
||||||
* @param int $thermostat_group_id
|
|
||||||
*
|
|
||||||
* @return array The updated thermostat_group.
|
|
||||||
*/
|
|
||||||
public function sync_attributes($thermostat_group_id) {
|
|
||||||
$attributes = [
|
|
||||||
'system_type_heat',
|
|
||||||
'system_type_heat_auxiliary',
|
|
||||||
'system_type_cool',
|
|
||||||
'property_age',
|
|
||||||
'property_square_feet',
|
|
||||||
'property_stories',
|
|
||||||
'property_structure_type',
|
|
||||||
'weather'
|
|
||||||
];
|
|
||||||
|
|
||||||
$thermostats = $this->api(
|
|
||||||
'thermostat',
|
|
||||||
'read',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_group_id' => $thermostat_group_id,
|
|
||||||
'inactive' => 0
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$final_attributes = [];
|
|
||||||
foreach($attributes as $attribute) {
|
|
||||||
$final_attributes[$attribute] = null;
|
|
||||||
foreach($thermostats as $thermostat) {
|
|
||||||
switch($attribute) {
|
|
||||||
case 'property_age':
|
|
||||||
case 'property_square_feet':
|
|
||||||
case 'property_stories':
|
|
||||||
// Use max found age, square_feet, stories
|
|
||||||
$key = str_replace('property_', '', $attribute);
|
|
||||||
if($thermostat['property'][$key] !== null) {
|
|
||||||
$final_attributes[$attribute] = max($final_attributes[$attribute], $thermostat['property'][$key]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'property_structure_type':
|
|
||||||
// Use the first non-null structure_type
|
|
||||||
if(
|
|
||||||
$thermostat['property']['structure_type'] !== null &&
|
|
||||||
$final_attributes[$attribute] === null
|
|
||||||
) {
|
|
||||||
$final_attributes[$attribute] = $thermostat['property']['structure_type'];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'system_type_heat':
|
|
||||||
case 'system_type_heat_auxiliary':
|
|
||||||
case 'system_type_cool':
|
|
||||||
$type = str_replace('system_type_', '', $attribute);
|
|
||||||
// Always prefer reported, otherwise fall back to detected.
|
|
||||||
if($thermostat['system_type']['reported'][$type] !== null) {
|
|
||||||
$system_type = $thermostat['system_type']['reported'][$type];
|
|
||||||
$reported = true;
|
|
||||||
} else {
|
|
||||||
$system_type = $thermostat['system_type']['detected'][$type];
|
|
||||||
$reported = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($reported === true) {
|
|
||||||
// User-reported values always take precedence
|
|
||||||
$final_attributes[$attribute] = $system_type;
|
|
||||||
} else if(
|
|
||||||
$final_attributes[$attribute] === null ||
|
|
||||||
(
|
|
||||||
$final_attributes[$attribute] === 'none' &&
|
|
||||||
$system_type !== null
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// None beats null
|
|
||||||
$final_attributes[$attribute] = $system_type;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Stuff that doesn't really matter (weather); just pick the last
|
|
||||||
// one.
|
|
||||||
$final_attributes[$attribute] = $thermostat[$attribute];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$final_attributes['thermostat_group_id'] = $thermostat_group_id;
|
|
||||||
return $this->update($final_attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update all of the thermostats in this group to a specified system type,
|
|
||||||
* then sync that forwards into the thermostat_group.
|
|
||||||
*
|
|
||||||
* @param int $thermostat_group_id
|
|
||||||
* @param array $system_types
|
|
||||||
*
|
|
||||||
* @return array The updated thermostat_group.
|
|
||||||
*/
|
|
||||||
public function update_system_types($thermostat_group_id, $system_types) {
|
|
||||||
$thermostats = $this->api(
|
|
||||||
'thermostat',
|
|
||||||
'read',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_group_id' => $thermostat_group_id
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach($thermostats as $thermostat) {
|
|
||||||
$current_system_types = $thermostat['system_type'];
|
|
||||||
foreach($system_types as $system_type => $value) {
|
|
||||||
$current_system_types['reported'][$system_type] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->api(
|
|
||||||
'thermostat',
|
|
||||||
'update',
|
|
||||||
[
|
|
||||||
'attributes' => [
|
|
||||||
'thermostat_id' => $thermostat['thermostat_id'],
|
|
||||||
'system_type' => $current_system_types
|
|
||||||
]
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->sync_attributes($thermostat_group_id);
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 72 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="215.09" height="97.268" viewBox="0.549 0 215.09 97.268" enable-background="new 0.549 0 215.09 97.268"><path fill="#A4ABB1" d="M215.64 31.792v-14.57h-11.993V0h-14.724v17.222h-29.658c-13.696.008-23.634 9.217-23.634 21.907 0 1.364.12 2.836.396 4.36-4.666-16.17-16.965-26.267-33.484-26.267-20.207 0-35.653 15.732-36.95 37.047v-4.53c0-17.93-14.59-32.517-32.522-32.517S.55 31.808.55 49.74v46.62h14.904V49.74c0-9.707 7.898-17.612 17.615-17.612 9.71 0 17.615 7.905 17.615 17.613v46.62H65.59V60.09c1.306 21.39 16.952 37.178 37.418 37.178 12.495 0 23.52-7.26 29.76-15.66 4.66 9.953 13.76 15.66 25.003 15.66 13.03 0 26.22-8.322 26.22-24.235 0-9.523-5.532-17.312-15.578-21.923-1.284-.587-2.322-1.076-3.314-1.548l-.136-.067c-1.124-.53-2.208-1.04-3.553-1.657-6.57-2.946-9.693-5.105-9.693-9.408 0-3.785 3.387-6.636 7.57-6.636h29.638V73.31c0 13.216 10.75 23.958 23.96 23.958V82.543c-5.092 0-9.236-4.143-9.236-9.235V31.793c5.887.002 10.426 0 11.99 0zm-113.212-.235c11.546 0 17.202 7.964 18.84 16.076H82.846c2.005-8.128 9.126-16.076 19.583-16.076zm58.85 33.84c4.15 1.774 6.626 5.112 6.626 8.916 0 5.04-3.876 8.04-10.368 8.04-6.893 0-10.095-6.068-10.912-7.923l-1.008-2.296H132.71l-.02-.012H120.73c-3.267 5.447-9.843 10.468-17.492 10.468-13.803 0-20.69-10.784-21.57-21.432h56.414v-2.335c0-4.415-.46-8.57-1.318-12.428 1.616 4.95 5.3 10.083 13.014 13.718 2.813 1.34 7.068 3.344 11.5 5.283z"/></svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
img/waveform.png
BIN
img/waveform.png
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
@ -56,7 +56,7 @@ beestat.get_climate = function(climate_ref) {
|
|||||||
/**
|
/**
|
||||||
* Get the color a thermostat should be based on what equipment is running.
|
* Get the color a thermostat should be based on what equipment is running.
|
||||||
*
|
*
|
||||||
* @return {string}
|
* @return {string} The color string.
|
||||||
*/
|
*/
|
||||||
beestat.get_thermostat_color = function(thermostat_id) {
|
beestat.get_thermostat_color = function(thermostat_id) {
|
||||||
var thermostat = beestat.cache.thermostat[thermostat_id];
|
var thermostat = beestat.cache.thermostat[thermostat_id];
|
||||||
@ -131,7 +131,8 @@ beestat.width = window.innerWidth;
|
|||||||
window.addEventListener('resize', rocket.throttle(100, function() {
|
window.addEventListener('resize', rocket.throttle(100, function() {
|
||||||
var breakpoints = [
|
var breakpoints = [
|
||||||
600,
|
600,
|
||||||
650
|
650,
|
||||||
|
1000
|
||||||
];
|
];
|
||||||
|
|
||||||
breakpoints.forEach(function(breakpoint) {
|
breakpoints.forEach(function(breakpoint) {
|
||||||
|
@ -1,98 +1,32 @@
|
|||||||
beestat.comparisons = {};
|
beestat.comparisons = {};
|
||||||
|
|
||||||
/**
|
|
||||||
* Fire off an API call to get the comparison scores using the currently
|
|
||||||
* defined settings. Updates the cache with the response which fires off the
|
|
||||||
* vent for anything bound to that data.
|
|
||||||
*
|
|
||||||
* Note that this fires off a batch API call for heat, cool, and resist
|
|
||||||
* scores. So if you *only* had the resist card on the dashboard you would
|
|
||||||
* still get all three. I think the most common use case is showing all three
|
|
||||||
* scores, so the layer loader will be able to optimize away the duplicate
|
|
||||||
* requests and do one multi API call instead of three distinct API calls.
|
|
||||||
*
|
|
||||||
* @param {Function} callback Optional callback to fire when the API call
|
|
||||||
* completes.
|
|
||||||
*/
|
|
||||||
beestat.comparisons.get_comparison_scores = function(callback) {
|
|
||||||
var types = [
|
|
||||||
'heat',
|
|
||||||
'cool',
|
|
||||||
'resist'
|
|
||||||
];
|
|
||||||
|
|
||||||
var api = new beestat.api();
|
|
||||||
types.forEach(function(type) {
|
|
||||||
beestat.cache.delete('data.comparison_scores_' + type);
|
|
||||||
api.add_call(
|
|
||||||
'thermostat_group',
|
|
||||||
'get_scores',
|
|
||||||
{
|
|
||||||
'type': type,
|
|
||||||
'attributes': beestat.comparisons.get_comparison_attributes(type)
|
|
||||||
},
|
|
||||||
'score_' + type
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (beestat.user.has_early_access() === true) {
|
|
||||||
api.add_call(
|
|
||||||
'thermostat_group',
|
|
||||||
'get_metrics',
|
|
||||||
{
|
|
||||||
'attributes': beestat.comparisons.get_comparison_attributes('resist') // todo
|
|
||||||
},
|
|
||||||
'metrics'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
api.set_callback(function(data) {
|
|
||||||
if (beestat.user.has_early_access() === true) {
|
|
||||||
beestat.cache.set('data.metrics', data.metrics);
|
|
||||||
}
|
|
||||||
|
|
||||||
types.forEach(function(type) {
|
|
||||||
beestat.cache.set('data.comparison_scores_' + type, data['score_' + type]);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (callback !== undefined) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
api.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on the comparison settings chosen in the GUI, get the proper broken
|
* Based on the comparison settings chosen in the GUI, get the proper broken
|
||||||
* out comparison attributes needed to make an API call.
|
* out comparison attributes needed to make an API call.
|
||||||
*
|
*
|
||||||
* @param {string} type heat|cool|resist
|
* @return {object} The comparison attributes.
|
||||||
*
|
|
||||||
* @return {Object} The comparison attributes.
|
|
||||||
*/
|
*/
|
||||||
beestat.comparisons.get_comparison_attributes = function(type) {
|
beestat.comparisons.get_attributes = function() {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||||
var thermostat_group =
|
var address = beestat.cache.address[thermostat.address_id];
|
||||||
beestat.cache.thermostat_group[thermostat.thermostat_group_id];
|
|
||||||
|
|
||||||
var attributes = {};
|
var attributes = {};
|
||||||
|
|
||||||
if (beestat.setting('comparison_property_type') === 'similar') {
|
if (beestat.setting('comparison_property_type') === 'similar') {
|
||||||
// Match structure type exactly.
|
// Match structure type exactly.
|
||||||
if (thermostat_group.property_structure_type !== null) {
|
if (thermostat.property.structure_type !== null) {
|
||||||
attributes.property_structure_type =
|
attributes.property_structure_type =
|
||||||
thermostat_group.property_structure_type;
|
thermostat.property.structure_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always a 10 year age delta on both sides.
|
// Always a 10 year age delta on both sides.
|
||||||
if (thermostat_group.property_age !== null) {
|
if (thermostat.property.age !== null) {
|
||||||
var property_age_delta = 10;
|
var property_age_delta = 10;
|
||||||
var min_property_age = Math.max(
|
var min_property_age = Math.max(
|
||||||
0,
|
0,
|
||||||
thermostat_group.property_age - property_age_delta
|
thermostat.property.age - property_age_delta
|
||||||
);
|
);
|
||||||
var max_property_age = thermostat_group.property_age + property_age_delta;
|
var max_property_age = thermostat.property.age + property_age_delta;
|
||||||
attributes.property_age = {
|
attributes.property_age = {
|
||||||
'operator': 'between',
|
'operator': 'between',
|
||||||
'value': [
|
'value': [
|
||||||
@ -103,14 +37,14 @@ beestat.comparisons.get_comparison_attributes = function(type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Always a 1000sqft size delta on both sides (total 2000 sqft).
|
// Always a 1000sqft size delta on both sides (total 2000 sqft).
|
||||||
if (thermostat_group.property_square_feet !== null) {
|
if (thermostat.property.square_feet !== null) {
|
||||||
var property_square_feet_delta = 1000;
|
var property_square_feet_delta = 1000;
|
||||||
var min_property_square_feet = Math.max(
|
var min_property_square_feet = Math.max(
|
||||||
0,
|
0,
|
||||||
thermostat_group.property_square_feet - property_square_feet_delta
|
thermostat.property.square_feet - property_square_feet_delta
|
||||||
);
|
);
|
||||||
var max_property_square_feet =
|
var max_property_square_feet =
|
||||||
thermostat_group.property_square_feet +
|
thermostat.property.square_feet +
|
||||||
property_square_feet_delta;
|
property_square_feet_delta;
|
||||||
attributes.property_square_feet = {
|
attributes.property_square_feet = {
|
||||||
'operator': 'between',
|
'operator': 'between',
|
||||||
@ -126,40 +60,39 @@ beestat.comparisons.get_comparison_attributes = function(type) {
|
|||||||
* Apartments ignore this.
|
* Apartments ignore this.
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
thermostat_group.property_stories !== null &&
|
thermostat.property.stories !== null &&
|
||||||
thermostat_group.property_structure_type !== 'apartment'
|
thermostat.property.structure_type !== 'apartment'
|
||||||
) {
|
) {
|
||||||
if (thermostat_group.property_stories < 2) {
|
if (thermostat.property.stories < 2) {
|
||||||
attributes.property_stories = thermostat_group.property_stories;
|
attributes.property_stories = thermostat.property.stories;
|
||||||
} else {
|
} else {
|
||||||
attributes.property_stories = {
|
attributes.property_stories = {
|
||||||
'operator': '>=',
|
'operator': '>=',
|
||||||
'value': thermostat_group.property_stories
|
'value': thermostat.property.stories
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (beestat.setting('comparison_property_type') === 'same_structure') {
|
} else if (beestat.setting('comparison_property_type') === 'same_structure') {
|
||||||
// Match structure type exactly.
|
// Match structure type exactly.
|
||||||
if (thermostat_group.property_structure_type !== null) {
|
if (thermostat.property.structure_type !== null) {
|
||||||
attributes.property_structure_type =
|
attributes.property_structure_type =
|
||||||
thermostat_group.property_structure_type;
|
thermostat.property.structure_type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
thermostat_group.address_latitude !== null &&
|
address.normalized !== null &&
|
||||||
thermostat_group.address_longitude !== null &&
|
address.normalized.metadata !== undefined &&
|
||||||
|
address.normalized.metadata.latitude !== undefined &&
|
||||||
|
address.normalized.metadata.latitude !== null &&
|
||||||
|
address.normalized.metadata.longitude !== undefined &&
|
||||||
|
address.normalized.metadata.longitude !== null &&
|
||||||
beestat.setting('comparison_region') !== 'global'
|
beestat.setting('comparison_region') !== 'global'
|
||||||
) {
|
) {
|
||||||
attributes.address_latitude = thermostat_group.address_latitude;
|
attributes.radius = {
|
||||||
attributes.address_longitude = thermostat_group.address_longitude;
|
'operator': '<',
|
||||||
attributes.address_radius = 250;
|
'value': 250
|
||||||
}
|
};
|
||||||
|
|
||||||
if (type === 'heat') {
|
|
||||||
attributes.system_type_heat = thermostat_group.system_type_heat;
|
|
||||||
} else if (type === 'cool') {
|
|
||||||
attributes.system_type_cool = thermostat_group.system_type_cool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
|
@ -1,35 +1,13 @@
|
|||||||
beestat.add_poll_interval = function(poll_interval) {
|
|
||||||
beestat.poll_intervals.push(poll_interval);
|
|
||||||
beestat.enable_poll();
|
|
||||||
};
|
|
||||||
|
|
||||||
beestat.remove_poll_interval = function(poll_interval) {
|
|
||||||
var index = beestat.poll_intervals.indexOf(poll_interval);
|
|
||||||
if (index !== -1) {
|
|
||||||
beestat.poll_intervals.splice(index, 1);
|
|
||||||
}
|
|
||||||
beestat.enable_poll();
|
|
||||||
};
|
|
||||||
|
|
||||||
beestat.reset_poll_interval = function() {
|
|
||||||
beestat.poll_intervals = [beestat.default_poll_interval];
|
|
||||||
beestat.enable_poll();
|
|
||||||
};
|
|
||||||
|
|
||||||
beestat.enable_poll = function() {
|
beestat.enable_poll = function() {
|
||||||
clearTimeout(beestat.poll_timeout);
|
window.clearTimeout(beestat.poll_timeout);
|
||||||
if (beestat.poll_intervals.length > 0) {
|
if (beestat.poll_intervals.length > 0) {
|
||||||
beestat.poll_timeout = setTimeout(
|
beestat.poll_timeout = window.setTimeout(
|
||||||
beestat.poll,
|
beestat.poll,
|
||||||
Math.min.apply(null, beestat.poll_intervals)
|
Math.min.apply(null, beestat.poll_intervals)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
beestat.disable_poll = function() {
|
|
||||||
clearTimeout(beestat.poll_timeout);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Poll the database for changes and update the cache.
|
* Poll the database for changes and update the cache.
|
||||||
*/
|
*/
|
||||||
|
@ -110,14 +110,3 @@ beestat.requestor.callback = function(response, api) {
|
|||||||
beestat.requestor.timeout_ = window.setTimeout(beestat.requestor.send, 3000);
|
beestat.requestor.timeout_ = window.setTimeout(beestat.requestor.send, 3000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
beestat.requestor.request([{'resource': 'thermostat','method': 'read_id','arguments': {'attributes': {'thermostat_id': 1}}}]);
|
|
||||||
beestat.requestor.request([{'resource': 'thermostat','method': 'read_id','arguments': {'attributes': {'thermostat_id': 1}}}]);
|
|
||||||
beestat.requestor.request([{'resource': 'sensor','method': 'read_id','arguments': {'attributes': {'sensor_id': 1}}}]);
|
|
||||||
beestat.requestor.request([{'resource': 'sensor','method': 'read_id','arguments': {'attributes': {'sensor_id': 2}}}]);
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
@ -6,10 +6,14 @@ beestat.runtime_thermostat = {};
|
|||||||
*
|
*
|
||||||
* @param {number} thermostat_id The thermostat_id to get data for.
|
* @param {number} thermostat_id The thermostat_id to get data for.
|
||||||
* @param {object} range Range settings.
|
* @param {object} range Range settings.
|
||||||
|
* @param {string} key The key to pull the data from inside
|
||||||
|
* beestat.cache.data. This exists because runtime_thermostat data exists in
|
||||||
|
* two spots: one for the Thermostat Detail chart, and once for the Sensor
|
||||||
|
* Detail chart.
|
||||||
*
|
*
|
||||||
* @return {object} The data.
|
* @return {object} The data.
|
||||||
*/
|
*/
|
||||||
beestat.runtime_thermostat.get_data = function(thermostat_id, range) {
|
beestat.runtime_thermostat.get_data = function(thermostat_id, range, key) {
|
||||||
var data = {
|
var data = {
|
||||||
'x': [],
|
'x': [],
|
||||||
'series': {},
|
'series': {},
|
||||||
@ -131,7 +135,7 @@ beestat.runtime_thermostat.get_data = function(thermostat_id, range) {
|
|||||||
.second(0)
|
.second(0)
|
||||||
.millisecond(0);
|
.millisecond(0);
|
||||||
|
|
||||||
var runtime_thermostats = beestat.runtime_thermostat.get_runtime_thermostats_by_date_();
|
var runtime_thermostats = beestat.runtime_thermostat.get_runtime_thermostats_by_date_(key);
|
||||||
|
|
||||||
// Initialize moving average.
|
// Initialize moving average.
|
||||||
var moving = [];
|
var moving = [];
|
||||||
@ -211,7 +215,6 @@ beestat.runtime_thermostat.get_data = function(thermostat_id, range) {
|
|||||||
data.metadata.series.setpoint_heat.data[current_m.valueOf()] = setpoint_heat;
|
data.metadata.series.setpoint_heat.data[current_m.valueOf()] = setpoint_heat;
|
||||||
|
|
||||||
data.metadata.series.setpoint_heat.active = true;
|
data.metadata.series.setpoint_heat.active = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
data.series.setpoint_heat.push(null);
|
data.series.setpoint_heat.push(null);
|
||||||
}
|
}
|
||||||
@ -227,7 +230,6 @@ beestat.runtime_thermostat.get_data = function(thermostat_id, range) {
|
|||||||
data.metadata.series.setpoint_cool.data[current_m.valueOf()] = setpoint_cool;
|
data.metadata.series.setpoint_cool.data[current_m.valueOf()] = setpoint_cool;
|
||||||
|
|
||||||
data.metadata.series.setpoint_cool.active = true;
|
data.metadata.series.setpoint_cool.active = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
data.series.setpoint_cool.push(null);
|
data.series.setpoint_cool.push(null);
|
||||||
}
|
}
|
||||||
@ -461,7 +463,6 @@ beestat.runtime_thermostat.get_data = function(thermostat_id, range) {
|
|||||||
|
|
||||||
data.metadata.series[series_code_1].data[current_m.valueOf()] =
|
data.metadata.series[series_code_1].data[current_m.valueOf()] =
|
||||||
equipment_y[series_code_1];
|
equipment_y[series_code_1];
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data.series[series_code].push(null);
|
data.series[series_code].push(null);
|
||||||
@ -538,12 +539,17 @@ beestat.runtime_thermostat.get_average_ = function(runtime_thermostats, series_c
|
|||||||
/**
|
/**
|
||||||
* Get all the runtime_thermostat rows indexed by date.
|
* Get all the runtime_thermostat rows indexed by date.
|
||||||
*
|
*
|
||||||
|
* @param {string} key The key to pull the data from inside
|
||||||
|
* beestat.cache.data. This exists because runtime_thermostat data exists in
|
||||||
|
* two spots: one for the Thermostat Detail chart, and once for the Sensor
|
||||||
|
* Detail chart.
|
||||||
|
*
|
||||||
* @return {array} The runtime_thermostat rows.
|
* @return {array} The runtime_thermostat rows.
|
||||||
*/
|
*/
|
||||||
beestat.runtime_thermostat.get_runtime_thermostats_by_date_ = function() {
|
beestat.runtime_thermostat.get_runtime_thermostats_by_date_ = function(key) {
|
||||||
var runtime_thermostats = {};
|
var runtime_thermostats = {};
|
||||||
if (beestat.cache.runtime_thermostat !== undefined) {
|
if (beestat.cache.data[key] !== undefined) {
|
||||||
beestat.cache.runtime_thermostat.forEach(function(runtime_thermostat) {
|
beestat.cache.data[key].forEach(function(runtime_thermostat) {
|
||||||
runtime_thermostats[moment(runtime_thermostat.timestamp).valueOf()] = runtime_thermostat;
|
runtime_thermostats[moment(runtime_thermostat.timestamp).valueOf()] = runtime_thermostat;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
*
|
*
|
||||||
* @return {string} The formatted temperature.
|
* @return {string} The formatted temperature.
|
||||||
*/
|
*/
|
||||||
// beestat.temperature = function(temperature, convert, round, include_units) {
|
|
||||||
beestat.temperature = function(args) {
|
beestat.temperature = function(args) {
|
||||||
// Allow passing a single argument of temperature for convenience.
|
// Allow passing a single argument of temperature for convenience.
|
||||||
if (typeof args !== 'object' || args === null) {
|
if (typeof args !== 'object' || args === null) {
|
||||||
|
@ -10,20 +10,6 @@
|
|||||||
beestat.time = function(seconds, opt_unit) {
|
beestat.time = function(seconds, opt_unit) {
|
||||||
var duration = moment.duration(seconds, opt_unit || 'seconds');
|
var duration = moment.duration(seconds, opt_unit || 'seconds');
|
||||||
|
|
||||||
/*
|
|
||||||
* // Used to work this way; switched this to return more consistent results.
|
|
||||||
*
|
|
||||||
* var days = duration.get('days');
|
|
||||||
* var hours = duration.get('hours');
|
|
||||||
* var minutes = duration.get('minutes');
|
|
||||||
*
|
|
||||||
* if (days >= 1) {
|
|
||||||
* return days + 'd ' + hours + 'h'
|
|
||||||
* } else {
|
|
||||||
* return hours + 'h ' + minutes + 'm'
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
var hours = Math.floor(duration.asHours());
|
var hours = Math.floor(duration.asHours());
|
||||||
var minutes = duration.get('minutes');
|
var minutes = duration.get('minutes');
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ beestat.component.card.prototype.decorate_top_right_ = function(parent) {};
|
|||||||
/**
|
/**
|
||||||
* Get the title of the card.
|
* Get the title of the card.
|
||||||
*
|
*
|
||||||
* @return {string}
|
* @return {string} The title.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.prototype.get_title_ = function() {
|
beestat.component.card.prototype.get_title_ = function() {
|
||||||
return null;
|
return null;
|
||||||
@ -132,7 +132,7 @@ beestat.component.card.prototype.get_title_ = function() {
|
|||||||
/**
|
/**
|
||||||
* Get the subtitle of the card.
|
* Get the subtitle of the card.
|
||||||
*
|
*
|
||||||
* @return {string}
|
* @return {string} The subtitle.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.prototype.get_subtitle_ = function() {
|
beestat.component.card.prototype.get_subtitle_ = function() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -114,7 +114,7 @@ beestat.component.card.alerts.prototype.unpin_ = function() {
|
|||||||
/**
|
/**
|
||||||
* Get the title of the card.
|
* Get the title of the card.
|
||||||
*
|
*
|
||||||
* @return {string}
|
* @return {string} The title.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.alerts.prototype.get_title_ = function() {
|
beestat.component.card.alerts.prototype.get_title_ = function() {
|
||||||
return 'Alerts';
|
return 'Alerts';
|
||||||
@ -130,7 +130,10 @@ beestat.component.card.alerts.prototype.decorate_top_right_ = function(parent) {
|
|||||||
|
|
||||||
var menu = (new beestat.component.menu()).render(parent);
|
var menu = (new beestat.component.menu()).render(parent);
|
||||||
|
|
||||||
var menu_item_show = new beestat.component.menu_item()
|
var menu_item_show;
|
||||||
|
var menu_item_hide;
|
||||||
|
|
||||||
|
menu_item_show = new beestat.component.menu_item()
|
||||||
.set_text('Show dismissed')
|
.set_text('Show dismissed')
|
||||||
.set_icon('bell')
|
.set_icon('bell')
|
||||||
.set_callback(function() {
|
.set_callback(function() {
|
||||||
@ -144,7 +147,7 @@ beestat.component.card.alerts.prototype.decorate_top_right_ = function(parent) {
|
|||||||
});
|
});
|
||||||
menu.add_menu_item(menu_item_show);
|
menu.add_menu_item(menu_item_show);
|
||||||
|
|
||||||
var menu_item_hide = new beestat.component.menu_item()
|
menu_item_hide = new beestat.component.menu_item()
|
||||||
.set_text('Hide dismissed')
|
.set_text('Hide dismissed')
|
||||||
.set_icon('bell_off')
|
.set_icon('bell_off')
|
||||||
.set_callback(function() {
|
.set_callback(function() {
|
||||||
|
33
js/component/card/compare_notification.js
Normal file
33
js/component/card/compare_notification.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Notification at the top of the new compare page to help users along with
|
||||||
|
* the change.
|
||||||
|
*/
|
||||||
|
beestat.component.card.compare_notification = function() {
|
||||||
|
beestat.component.card.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.card.compare_notification, beestat.component.card);
|
||||||
|
|
||||||
|
beestat.component.card.compare_notification.prototype.decorate_contents_ = function(parent) {
|
||||||
|
parent.style('background', beestat.style.color.blue.light);
|
||||||
|
|
||||||
|
parent.appendChild($.createElement('p').innerText('The comparisons you\'ve become accustomed to have evolved into a new feature: Metrics! Please be patient over the next few weeks as they are refined.'));
|
||||||
|
|
||||||
|
new beestat.component.button()
|
||||||
|
.set_icon('information')
|
||||||
|
.set_text('Learn more and discuss this change')
|
||||||
|
.set_background_color(beestat.style.color.blue.dark)
|
||||||
|
.set_background_hover_color(beestat.style.color.blue.base)
|
||||||
|
.addEventListener('click', function() {
|
||||||
|
window.open('https://community.beestat.io/t/metrics-are-replacing-scores/347');
|
||||||
|
})
|
||||||
|
.render(parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of the card.
|
||||||
|
*
|
||||||
|
* @return {string} The title.
|
||||||
|
*/
|
||||||
|
beestat.component.card.compare_notification.prototype.get_title_ = function() {
|
||||||
|
return 'Things have changed...';
|
||||||
|
};
|
@ -1,16 +1,27 @@
|
|||||||
/**
|
/**
|
||||||
* Home comparison settings.
|
* Home comparison settings.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat_id this card is displaying
|
||||||
|
* data for.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.comparison_settings = function() {
|
beestat.component.card.comparison_settings = function(thermostat_id) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the thermostat_group changes that means the property_type could change
|
* If the thermostat changes that means the property_type could change and
|
||||||
* and thus need to rerender.
|
* thus need to rerender.
|
||||||
*/
|
*/
|
||||||
beestat.dispatcher.addEventListener('cache.thermostat_group', function() {
|
beestat.dispatcher.addEventListener(
|
||||||
self.rerender();
|
[
|
||||||
});
|
'cache.thermostat',
|
||||||
|
'cache.data.metrics'
|
||||||
|
],
|
||||||
|
function() {
|
||||||
|
self.rerender();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
beestat.component.card.apply(this, arguments);
|
beestat.component.card.apply(this, arguments);
|
||||||
};
|
};
|
||||||
@ -22,10 +33,7 @@ beestat.extend(beestat.component.card.comparison_settings, beestat.component.car
|
|||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
beestat.component.card.comparison_settings.prototype.decorate_contents_ = function(parent) {
|
beestat.component.card.comparison_settings.prototype.decorate_contents_ = function(parent) {
|
||||||
var self = this;
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
|
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[thermostat.thermostat_group_id];
|
|
||||||
|
|
||||||
var row;
|
var row;
|
||||||
|
|
||||||
@ -47,42 +55,57 @@ beestat.component.card.comparison_settings.prototype.decorate_contents_ = functi
|
|||||||
row.appendChild(column_property);
|
row.appendChild(column_property);
|
||||||
this.decorate_property_(column_property);
|
this.decorate_property_(column_property);
|
||||||
|
|
||||||
/*
|
row = $.createElement('div').addClass('row');
|
||||||
* If the data is available, then get the data if we don't already have it
|
parent.appendChild(row);
|
||||||
* loaded. If the data is not available, poll until it becomes available.
|
|
||||||
*/
|
|
||||||
if (thermostat_group.profile === null) {
|
|
||||||
// This will show the loading screen.
|
|
||||||
self.data_available_();
|
|
||||||
|
|
||||||
var poll_interval = 10000;
|
var column_detail = $.createElement('div').addClass([
|
||||||
|
'column',
|
||||||
|
'column_12'
|
||||||
|
]);
|
||||||
|
row.appendChild(column_detail);
|
||||||
|
this.decorate_detail_(column_detail);
|
||||||
|
|
||||||
beestat.add_poll_interval(poll_interval);
|
const sync_progress = beestat.thermostat.get_sync_progress(this.thermostat_id_);
|
||||||
beestat.dispatcher.addEventListener('poll.comparisons_load', function() {
|
|
||||||
if (self.data_available_() === true) {
|
|
||||||
beestat.remove_poll_interval(poll_interval);
|
|
||||||
beestat.dispatcher.removeEventListener('poll.comparisons_load');
|
|
||||||
|
|
||||||
new beestat.api()
|
if (sync_progress === null || sync_progress < 100) {
|
||||||
.add_call(
|
this.show_loading_('Fetching');
|
||||||
'thermostat_group',
|
window.setTimeout(function() {
|
||||||
'generate_profiles',
|
var api = new beestat.api();
|
||||||
{},
|
api.add_call(
|
||||||
'generate_profiles'
|
'thermostat',
|
||||||
)
|
'read_id',
|
||||||
.add_call(
|
{
|
||||||
'thermostat_group',
|
'attributes': {
|
||||||
'read_id',
|
'inactive': 0
|
||||||
{},
|
}
|
||||||
'thermostat_group'
|
},
|
||||||
)
|
'thermostat'
|
||||||
.set_callback(function(response) {
|
);
|
||||||
beestat.cache.set('thermostat_group', response.thermostat_group);
|
|
||||||
(new beestat.layer.comparisons()).render();
|
api.set_callback(function(response) {
|
||||||
})
|
beestat.cache.set('thermostat', response.thermostat);
|
||||||
.send();
|
});
|
||||||
}
|
|
||||||
});
|
api.send();
|
||||||
|
}, 10000);
|
||||||
|
} else if (beestat.cache.data.metrics === undefined) {
|
||||||
|
this.show_loading_('Fetching');
|
||||||
|
} else {
|
||||||
|
if (thermostat.profile === null) {
|
||||||
|
new beestat.api()
|
||||||
|
.add_call(
|
||||||
|
'thermostat',
|
||||||
|
'generate_profile',
|
||||||
|
{
|
||||||
|
'thermostat_id': this.thermostat_id_
|
||||||
|
},
|
||||||
|
'thermostat'
|
||||||
|
)
|
||||||
|
.set_callback(function(response) {
|
||||||
|
beestat.cache.set('thermostat', response.thermostat);
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,6 +141,9 @@ beestat.component.card.comparison_settings.prototype.decorate_region_ = function
|
|||||||
button
|
button
|
||||||
.set_background_color(beestat.style.color.bluegray.light)
|
.set_background_color(beestat.style.color.bluegray.light)
|
||||||
.addEventListener('click', function() {
|
.addEventListener('click', function() {
|
||||||
|
// Delete from the cache to trigger the metrics loading screen
|
||||||
|
beestat.cache.delete('data.metrics');
|
||||||
|
|
||||||
// Update the setting
|
// Update the setting
|
||||||
beestat.setting('comparison_region', region);
|
beestat.setting('comparison_region', region);
|
||||||
|
|
||||||
@ -125,12 +151,21 @@ beestat.component.card.comparison_settings.prototype.decorate_region_ = function
|
|||||||
self.rerender();
|
self.rerender();
|
||||||
|
|
||||||
// Open up the loading window.
|
// Open up the loading window.
|
||||||
self.show_loading_('Calculating Score for ' + region + ' region');
|
self.show_loading_('Fetching');
|
||||||
|
|
||||||
beestat.comparisons.get_comparison_scores(function() {
|
new beestat.api()
|
||||||
// Rerender to get rid of the loader.
|
.add_call(
|
||||||
self.rerender();
|
'thermostat',
|
||||||
});
|
'get_metrics',
|
||||||
|
{
|
||||||
|
'thermostat_id': self.thermostat_id_,
|
||||||
|
'attributes': beestat.comparisons.get_attributes()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.set_callback(function(response) {
|
||||||
|
beestat.cache.set('data.metrics', response);
|
||||||
|
})
|
||||||
|
.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,10 +182,7 @@ beestat.component.card.comparison_settings.prototype.decorate_region_ = function
|
|||||||
beestat.component.card.comparison_settings.prototype.decorate_property_ = function(parent) {
|
beestat.component.card.comparison_settings.prototype.decorate_property_ = function(parent) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
|
|
||||||
(new beestat.component.title('Property')).render(parent);
|
(new beestat.component.title('Property')).render(parent);
|
||||||
|
|
||||||
@ -160,12 +192,12 @@ beestat.component.card.comparison_settings.prototype.decorate_property_ = functi
|
|||||||
'text': 'Very Similar'
|
'text': 'Very Similar'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (thermostat_group.property_structure_type !== null) {
|
if (thermostat.property.structure_type !== null) {
|
||||||
property_types.push({
|
property_types.push({
|
||||||
'value': 'same_structure',
|
'value': 'same_structure',
|
||||||
'text': 'Type: ' +
|
'text': 'Type: ' +
|
||||||
thermostat_group.property_structure_type.charAt(0).toUpperCase() +
|
thermostat.property.structure_type.charAt(0).toUpperCase() +
|
||||||
thermostat_group.property_structure_type.slice(1)
|
thermostat.property.structure_type.slice(1)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +223,9 @@ beestat.component.card.comparison_settings.prototype.decorate_property_ = functi
|
|||||||
button
|
button
|
||||||
.set_background_color(beestat.style.color.bluegray.light)
|
.set_background_color(beestat.style.color.bluegray.light)
|
||||||
.addEventListener('click', function() {
|
.addEventListener('click', function() {
|
||||||
|
// Delete from the cache to trigger the metrics loading screen
|
||||||
|
beestat.cache.delete('data.metrics');
|
||||||
|
|
||||||
// Update the setting
|
// Update the setting
|
||||||
beestat.setting('comparison_property_type', property_type.value);
|
beestat.setting('comparison_property_type', property_type.value);
|
||||||
|
|
||||||
@ -198,12 +233,21 @@ beestat.component.card.comparison_settings.prototype.decorate_property_ = functi
|
|||||||
self.rerender();
|
self.rerender();
|
||||||
|
|
||||||
// Open up the loading window.
|
// Open up the loading window.
|
||||||
self.show_loading_('Calculating Score for ' + property_type.text);
|
self.show_loading_('Fetching');
|
||||||
|
|
||||||
beestat.comparisons.get_comparison_scores(function() {
|
new beestat.api()
|
||||||
// Rerender to get rid of the loader.
|
.add_call(
|
||||||
self.rerender();
|
'thermostat',
|
||||||
});
|
'get_metrics',
|
||||||
|
{
|
||||||
|
'thermostat_id': self.thermostat_id_,
|
||||||
|
'attributes': beestat.comparisons.get_attributes()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.set_callback(function(response) {
|
||||||
|
beestat.cache.set('data.metrics', response);
|
||||||
|
})
|
||||||
|
.send();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +256,55 @@ beestat.component.card.comparison_settings.prototype.decorate_property_ = functi
|
|||||||
button_group.render(parent);
|
button_group.render(parent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beestat.component.card.comparison_settings.prototype.decorate_detail_ = function(parent) {
|
||||||
|
var strings = [];
|
||||||
|
|
||||||
|
strings.push('Matching system type and stages');
|
||||||
|
|
||||||
|
var comparison_attributes = beestat.comparisons.get_attributes();
|
||||||
|
|
||||||
|
if (comparison_attributes.property_structure_type !== undefined) {
|
||||||
|
strings.push('Property Type: ' + this.get_comparison_string_(comparison_attributes.property_structure_type));
|
||||||
|
} else {
|
||||||
|
strings.push('Any property type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparison_attributes.property_age !== undefined) {
|
||||||
|
strings.push(this.get_comparison_string_(comparison_attributes.property_age, 'years old'));
|
||||||
|
} else {
|
||||||
|
strings.push('Any property age');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparison_attributes.property_square_feet !== undefined) {
|
||||||
|
strings.push(this.get_comparison_string_(comparison_attributes.property_square_feet, 'sqft'));
|
||||||
|
} else {
|
||||||
|
strings.push('Any square footage');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparison_attributes.property_stories !== undefined) {
|
||||||
|
strings.push(this.get_comparison_string_(comparison_attributes.property_stories, 'stories'));
|
||||||
|
} else {
|
||||||
|
strings.push('Any number of stories');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparison_attributes.radius !== undefined) {
|
||||||
|
strings.push('Within ' + comparison_attributes.radius.value + ' miles of your location');
|
||||||
|
} else {
|
||||||
|
strings.push('Any region');
|
||||||
|
}
|
||||||
|
|
||||||
|
(new beestat.component.title('Comparing to homes like...')).render(parent);
|
||||||
|
|
||||||
|
strings.forEach(function(string) {
|
||||||
|
var div = $.createElement('div');
|
||||||
|
div.innerText(string);
|
||||||
|
if (string.match('Any') !== null) {
|
||||||
|
div.style({'color': beestat.style.color.gray.base});
|
||||||
|
}
|
||||||
|
parent.appendChild(div);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the title of the card.
|
* Get the title of the card.
|
||||||
*
|
*
|
||||||
@ -227,31 +320,19 @@ beestat.component.card.comparison_settings.prototype.get_title_ = function() {
|
|||||||
* @return {string} The subtitle of the card.
|
* @return {string} The subtitle of the card.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.comparison_settings.prototype.get_subtitle_ = function() {
|
beestat.component.card.comparison_settings.prototype.get_subtitle_ = function() {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
const thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
const address = beestat.cache.address[thermostat.address_id];
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
var address = beestat.cache.address[thermostat_group.address_id];
|
|
||||||
|
|
||||||
var string = '';
|
let string = 'Thermostat at ';
|
||||||
|
|
||||||
if (address.normalized !== null && address.normalized.delivery_line_1 !== undefined) {
|
if (address.normalized !== null && address.normalized.delivery_line_1 !== undefined) {
|
||||||
string = address.normalized.delivery_line_1;
|
string += address.normalized.delivery_line_1;
|
||||||
} else if (address.normalized !== null && address.normalized.address1 !== undefined) {
|
} else if (address.normalized !== null && address.normalized.address1 !== undefined) {
|
||||||
string = address.normalized.address1;
|
string += address.normalized.address1;
|
||||||
} else {
|
} else {
|
||||||
string = 'Unknown Address';
|
string += 'unknown address';
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = 0;
|
|
||||||
$.values(beestat.cache.thermostat).forEach(function(t) {
|
|
||||||
if (t.thermostat_group_id === thermostat_group.thermostat_group_id) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
string += ' (' + count + ' Thermostat' + (count > 1 ? 's' : '') + ')';
|
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -272,21 +353,32 @@ beestat.component.card.comparison_settings.prototype.decorate_top_right_ = funct
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether or not all of the data has been loaded so the scores can
|
* Helper function to display various comparison strings in a human-readable
|
||||||
* be generated.
|
* way.
|
||||||
*
|
*
|
||||||
* @return {boolean} Whether or not all of the data has been loaded.
|
* @param {mixed} comparison_attribute The attribute
|
||||||
|
* @param {string} suffix If a suffix (ex: "years") should be placed on the
|
||||||
|
* end.
|
||||||
|
*
|
||||||
|
* @return {string} The human-readable string.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.comparison_settings.prototype.data_available_ = function() {
|
beestat.component.card.comparison_settings.prototype.get_comparison_string_ = function(comparison_attribute, suffix) {
|
||||||
var sync_progress = beestat.thermostat.get_sync_progress(beestat.setting('thermostat_id'));
|
var s = (suffix !== undefined ? (' ' + suffix) : '');
|
||||||
|
if (comparison_attribute.operator !== undefined) {
|
||||||
if (sync_progress >= 95) {
|
if (comparison_attribute.operator === 'between') {
|
||||||
this.show_loading_('Calculating Scores');
|
return 'Between ' + comparison_attribute.value[0] + ' and ' + comparison_attribute.value[1] + s;
|
||||||
} else {
|
} else if (comparison_attribute.operator === '>=') {
|
||||||
this.show_loading_('Syncing Data (' +
|
return 'At least ' + comparison_attribute.value + s;
|
||||||
Math.round(sync_progress) +
|
} else if (comparison_attribute.operator === '<=') {
|
||||||
'%)');
|
return 'Less than or equal than ' + comparison_attribute.value + s;
|
||||||
|
} else if (comparison_attribute.operator === '>') {
|
||||||
|
return 'Greater than ' + comparison_attribute.value + s;
|
||||||
|
} else if (comparison_attribute.operator === '<') {
|
||||||
|
return 'Less than' + comparison_attribute.value + s;
|
||||||
|
}
|
||||||
|
return comparison_attribute.operator + ' ' + comparison_attribute.value + s;
|
||||||
|
} else if (Array.isArray(comparison_attribute.value) === true) {
|
||||||
|
return 'One of ' + comparison_attribute.value.join(', ') + s;
|
||||||
}
|
}
|
||||||
|
return comparison_attribute + s;
|
||||||
return sync_progress === 100;
|
|
||||||
};
|
};
|
||||||
|
@ -15,13 +15,3 @@ beestat.component.card.early_access.prototype.decorate_contents_ = function(pare
|
|||||||
parent.style('background', beestat.style.color.green.base);
|
parent.style('background', beestat.style.color.green.base);
|
||||||
parent.appendChild($.createElement('p').innerText('Experimental early access features below! ⤵'));
|
parent.appendChild($.createElement('p').innerText('Experimental early access features below! ⤵'));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title of the card.
|
|
||||||
*/
|
|
||||||
// beestat.component.card.early_access.prototype.get_title_ = function() {
|
|
||||||
// return 'Possible issue with your temperature profiles!';
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Metrics card.
|
* Metrics card.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat_id this card is displaying
|
||||||
|
* data for.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.metrics = function(thermostat_group_id) {
|
beestat.component.card.metrics = function(thermostat_id) {
|
||||||
this.thermostat_group_id_ = thermostat_group_id;
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@ -11,58 +14,153 @@ beestat.component.card.metrics = function(thermostat_group_id) {
|
|||||||
* event. This fires on the trailing edge so that all changes are accounted
|
* event. This fires on the trailing edge so that all changes are accounted
|
||||||
* for when rerendering.
|
* for when rerendering.
|
||||||
*/
|
*/
|
||||||
var data_change_function = beestat.debounce(function() {
|
var change_function = beestat.debounce(function() {
|
||||||
self.rerender();
|
self.rerender();
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
beestat.dispatcher.addEventListener(
|
beestat.dispatcher.addEventListener(
|
||||||
'cache.data.metrics',
|
[
|
||||||
data_change_function
|
'cache.data.metrics',
|
||||||
|
'cache.thermostat'
|
||||||
|
],
|
||||||
|
change_function
|
||||||
);
|
);
|
||||||
|
|
||||||
beestat.component.card.apply(this, arguments);
|
beestat.component.card.apply(this, arguments);
|
||||||
|
|
||||||
// this.layer_.register_loader(beestat.comparisons.get_comparison_metricss);
|
new beestat.api()
|
||||||
|
.add_call(
|
||||||
|
'thermostat',
|
||||||
|
'get_metrics',
|
||||||
|
{
|
||||||
|
'thermostat_id': this.thermostat_id_,
|
||||||
|
'attributes': beestat.comparisons.get_attributes()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.set_callback(function(response) {
|
||||||
|
beestat.cache.set('data.metrics', response);
|
||||||
|
})
|
||||||
|
.send();
|
||||||
};
|
};
|
||||||
beestat.extend(beestat.component.card.metrics, beestat.component.card);
|
beestat.extend(beestat.component.card.metrics, beestat.component.card);
|
||||||
|
|
||||||
|
beestat.component.card.metrics.prototype.rerender_on_breakpoint_ = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorate
|
* Decorate
|
||||||
*
|
*
|
||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
beestat.component.card.metrics.prototype.decorate_contents_ = function(parent) {
|
beestat.component.card.metrics.prototype.decorate_contents_ = function(parent) {
|
||||||
var self = this;
|
if (beestat.cache.data.metrics === undefined) {
|
||||||
|
parent.appendChild($.createElement('div').style('height', '100px'));
|
||||||
|
this.show_loading_('Fetching');
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* An entry for every possible metric is always returned for clarity.
|
||||||
|
* Remove the children with no data and then the parents with no children.
|
||||||
|
*/
|
||||||
|
this.filtered_metrics_ = {};
|
||||||
|
let metric_count = 0;
|
||||||
|
for (const parent_metric_name in beestat.cache.data.metrics) {
|
||||||
|
for (const child_metric_name in beestat.cache.data.metrics[parent_metric_name]) {
|
||||||
|
if (beestat.cache.data.metrics[parent_metric_name][child_metric_name] !== null) {
|
||||||
|
if (this.filtered_metrics_[parent_metric_name] === undefined) {
|
||||||
|
this.filtered_metrics_[parent_metric_name] = {};
|
||||||
|
}
|
||||||
|
this.filtered_metrics_[parent_metric_name][child_metric_name] = beestat.cache.data.metrics[parent_metric_name][child_metric_name];
|
||||||
|
metric_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var metrics = [
|
if (metric_count === 0) {
|
||||||
'setpoint_heat',
|
this.decorate_empty_(parent);
|
||||||
'setpoint_cool',
|
} else {
|
||||||
// 'runtime_per_heating_degree_day'
|
let column_count = 1;
|
||||||
];
|
if (beestat.width > 1000) {
|
||||||
|
column_count = 3;
|
||||||
|
} else if (beestat.width > 800) {
|
||||||
|
column_count = 2;
|
||||||
|
}
|
||||||
|
const column_span = 12 / column_count;
|
||||||
|
|
||||||
// Decorate the metrics
|
const columns = [];
|
||||||
var metric_container = $.createElement('div')
|
const row = $.createElement('div').addClass('row');
|
||||||
.style({
|
parent.appendChild(row);
|
||||||
'display': 'grid',
|
|
||||||
// 'grid-template-columns': 'repeat(auto-fit, minmax(160px, 1fr))',
|
for (let i = 0; i < column_count; i++) {
|
||||||
'grid-template-columns': '1fr 1fr 1fr',
|
const column = $.createElement('div')
|
||||||
'margin': '0 0 ' + beestat.style.size.gutter + 'px -' + beestat.style.size.gutter + 'px'
|
.addClass([
|
||||||
|
'column',
|
||||||
|
'column_' + column_span
|
||||||
|
]);
|
||||||
|
row.appendChild(column);
|
||||||
|
columns.push({
|
||||||
|
'size': 0,
|
||||||
|
'element': column
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const get_smallest_column = function() {
|
||||||
|
let smallest_column = columns[0];
|
||||||
|
columns.forEach(function(column) {
|
||||||
|
if (column.size < smallest_column.size) {
|
||||||
|
smallest_column = column;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return smallest_column;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const parent_metric_name in this.filtered_metrics_) {
|
||||||
|
const group_size = Object.keys(this.filtered_metrics_[parent_metric_name]).length;
|
||||||
|
const smallest_column = get_smallest_column();
|
||||||
|
this.decorate_group_(smallest_column.element, parent_metric_name);
|
||||||
|
smallest_column.size += group_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a message in there if no data is present.
|
||||||
|
*
|
||||||
|
* @param {rocket.Elements} parent Parent
|
||||||
|
*/
|
||||||
|
beestat.component.card.metrics.prototype.decorate_empty_ = function(parent) {
|
||||||
|
parent.appendChild($.createElement('p').innerText('We couldn\'t generate any metrics for your system. Try broadening your comparison settings and ensuring your system type and thermostat address are properly set.'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate a group of metrics.
|
||||||
|
*
|
||||||
|
* @param {rocket.Elements} parent Parent
|
||||||
|
* @param {string} parent_metric_name The name of the group.
|
||||||
|
*/
|
||||||
|
beestat.component.card.metrics.prototype.decorate_group_ = function(parent, parent_metric_name) {
|
||||||
|
const parent_metric = this.filtered_metrics_[parent_metric_name];
|
||||||
|
|
||||||
|
const title = parent_metric_name
|
||||||
|
.replace(/_/g, ' ')
|
||||||
|
.replace(/^(.)|\s+(.)/g, function($1) {
|
||||||
|
return $1.toUpperCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
parent.appendChild($.createElement('p').innerText(title));
|
||||||
|
|
||||||
|
const metric_container = $.createElement('div');
|
||||||
parent.appendChild(metric_container);
|
parent.appendChild(metric_container);
|
||||||
|
|
||||||
metrics.forEach(function(metric) {
|
for (const child_metric_name in parent_metric) {
|
||||||
var div = $.createElement('div')
|
const div = $.createElement('div')
|
||||||
.style({
|
.style({
|
||||||
'padding': beestat.style.size.gutter + 'px 0 0 ' + beestat.style.size.gutter + 'px'
|
'background': beestat.style.color.bluegray.dark,
|
||||||
|
'margin-bottom': beestat.style.size.gutter / 4
|
||||||
});
|
});
|
||||||
metric_container.appendChild(div);
|
metric_container.appendChild(div);
|
||||||
|
(new beestat.component.metric[parent_metric_name][child_metric_name](this.thermostat_id_)).render(div);
|
||||||
(new beestat.component.metric[metric](self.thermostat_group_id_)).render(div);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +177,44 @@ beestat.component.card.metrics.prototype.get_title_ = function() {
|
|||||||
*
|
*
|
||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
/*beestat.component.card.my_home.prototype.decorate_top_right_ = function(parent) {
|
beestat.component.card.metrics.prototype.decorate_top_right_ = function(parent) {
|
||||||
|
const menu = (new beestat.component.menu()).render(parent);
|
||||||
|
|
||||||
};*/
|
menu.add_menu_item(new beestat.component.menu_item()
|
||||||
|
.set_text('Help')
|
||||||
|
.set_icon('help_circle')
|
||||||
|
.set_callback(function() {
|
||||||
|
window.open('https://doc.beestat.io/ebfdf00f7f34436c980cd6344a767a12');
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the subtitle of the card.
|
||||||
|
*
|
||||||
|
* @return {string} The subtitle.
|
||||||
|
*/
|
||||||
|
beestat.component.card.metrics.prototype.get_subtitle_ = function() {
|
||||||
|
const thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
|
|
||||||
|
const generated_at_m = moment(
|
||||||
|
thermostat.profile.metadata.generated_at
|
||||||
|
);
|
||||||
|
|
||||||
|
let duration_text = '';
|
||||||
|
|
||||||
|
// How much data was used to generate this.
|
||||||
|
const duration_weeks = Math.round(thermostat.profile.metadata.duration / 7);
|
||||||
|
duration_text += ' from the past ';
|
||||||
|
if (duration_weeks === 0) {
|
||||||
|
duration_text += ' few days';
|
||||||
|
} else if (duration_weeks === 1) {
|
||||||
|
duration_text += ' week';
|
||||||
|
} else if (duration_weeks >= 52) {
|
||||||
|
duration_text += ' year';
|
||||||
|
} else {
|
||||||
|
duration_text += duration_weeks + ' weeks';
|
||||||
|
}
|
||||||
|
duration_text += ' of data.';
|
||||||
|
|
||||||
|
return 'Generated ' + generated_at_m.format('MMM Do @ h a') + ' (updated weekly) ' + duration_text;
|
||||||
|
};
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* Home properties.
|
* Home properties.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat_id this card is displaying
|
||||||
|
* data for.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.my_home = function() {
|
beestat.component.card.my_home = function(thermostat_id) {
|
||||||
var self = this;
|
var self = this;
|
||||||
beestat.dispatcher.addEventListener('cache.thermostat_group', function() {
|
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.dispatcher.addEventListener('cache.thermostat', function() {
|
||||||
self.rerender();
|
self.rerender();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -23,10 +29,7 @@ beestat.component.card.my_home.prototype.decorate_contents_ = function(parent) {
|
|||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
beestat.component.card.my_home.prototype.decorate_system_type_ = function(parent) {
|
beestat.component.card.my_home.prototype.decorate_system_type_ = function(parent) {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
|
|
||||||
(new beestat.component.title('System')).render(parent);
|
(new beestat.component.title('System')).render(parent);
|
||||||
|
|
||||||
@ -72,9 +75,8 @@ beestat.component.card.my_home.prototype.decorate_system_type_ = function(parent
|
|||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
beestat.component.card.my_home.prototype.decorate_region_ = function(parent) {
|
beestat.component.card.my_home.prototype.decorate_region_ = function(parent) {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
var thermostat_group = beestat.cache.thermostat_group[thermostat.thermostat_group_id];
|
var address = beestat.cache.address[thermostat.address_id];
|
||||||
var address = beestat.cache.address[thermostat_group.address_id];
|
|
||||||
|
|
||||||
(new beestat.component.title('Region')).render(parent);
|
(new beestat.component.title('Region')).render(parent);
|
||||||
|
|
||||||
@ -123,29 +125,28 @@ beestat.component.card.my_home.prototype.decorate_region_ = function(parent) {
|
|||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
beestat.component.card.my_home.prototype.decorate_property_ = function(parent) {
|
beestat.component.card.my_home.prototype.decorate_property_ = function(parent) {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
var thermostat_group = beestat.cache.thermostat_group[thermostat.thermostat_group_id];
|
|
||||||
|
|
||||||
(new beestat.component.title('Property')).render(parent);
|
(new beestat.component.title('Property')).render(parent);
|
||||||
|
|
||||||
var button_group = new beestat.component.button_group();
|
var button_group = new beestat.component.button_group();
|
||||||
|
|
||||||
if (thermostat_group.property_structure_type !== null) {
|
if (thermostat.property.structure_type !== null) {
|
||||||
button_group.add_button(new beestat.component.button()
|
button_group.add_button(new beestat.component.button()
|
||||||
.set_type('pill')
|
.set_type('pill')
|
||||||
.set_background_color(beestat.style.color.purple.base)
|
.set_background_color(beestat.style.color.purple.base)
|
||||||
.set_text_color('#fff')
|
.set_text_color('#fff')
|
||||||
.set_icon('home_floor_a')
|
.set_icon('home_floor_a')
|
||||||
.set_text(thermostat_group.property_structure_type.charAt(0).toUpperCase() +
|
.set_text(thermostat.property.structure_type.charAt(0).toUpperCase() +
|
||||||
thermostat_group.property_structure_type.slice(1)));
|
thermostat.property.structure_type.slice(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
thermostat_group.property_stories !== null &&
|
thermostat.property.stories !== null &&
|
||||||
(
|
(
|
||||||
thermostat_group.property_structure_type === 'detached' ||
|
thermostat.property.structure_type === 'detached' ||
|
||||||
thermostat_group.property_structure_type === 'townhouse' ||
|
thermostat.property.structure_type === 'townhouse' ||
|
||||||
thermostat_group.property_structure_type === 'semi-detached'
|
thermostat.property.structure_type === 'semi-detached'
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
button_group.add_button(new beestat.component.button()
|
button_group.add_button(new beestat.component.button()
|
||||||
@ -153,26 +154,26 @@ beestat.component.card.my_home.prototype.decorate_property_ = function(parent) {
|
|||||||
.set_background_color(beestat.style.color.purple.base)
|
.set_background_color(beestat.style.color.purple.base)
|
||||||
.set_text_color('#fff')
|
.set_text_color('#fff')
|
||||||
.set_icon('layers')
|
.set_icon('layers')
|
||||||
.set_text(thermostat_group.property_stories +
|
.set_text(thermostat.property.stories +
|
||||||
(thermostat_group.property_stories === 1 ? ' Story' : ' Stories')));
|
(thermostat.property.stories === 1 ? ' Story' : ' Stories')));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thermostat_group.property_square_feet !== null) {
|
if (thermostat.property.square_feet !== null) {
|
||||||
button_group.add_button(new beestat.component.button()
|
button_group.add_button(new beestat.component.button()
|
||||||
.set_type('pill')
|
.set_type('pill')
|
||||||
.set_background_color(beestat.style.color.purple.base)
|
.set_background_color(beestat.style.color.purple.base)
|
||||||
.set_text_color('#fff')
|
.set_text_color('#fff')
|
||||||
.set_icon('view_quilt')
|
.set_icon('view_quilt')
|
||||||
.set_text(Number(thermostat_group.property_square_feet).toLocaleString() + ' sqft'));
|
.set_text(Number(thermostat.property.square_feet).toLocaleString() + ' sqft'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thermostat_group.property_age !== null) {
|
if (thermostat.property.age !== null) {
|
||||||
button_group.add_button(new beestat.component.button()
|
button_group.add_button(new beestat.component.button()
|
||||||
.set_type('pill')
|
.set_type('pill')
|
||||||
.set_background_color(beestat.style.color.purple.base)
|
.set_background_color(beestat.style.color.purple.base)
|
||||||
.set_text_color('#fff')
|
.set_text_color('#fff')
|
||||||
.set_icon('clock_outline')
|
.set_icon('clock_outline')
|
||||||
.set_text(thermostat_group.property_age + ' Years'));
|
.set_text(thermostat.property.age + ' Years'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (button_group.get_buttons().length === 0) {
|
if (button_group.get_buttons().length === 0) {
|
||||||
@ -202,13 +203,15 @@ beestat.component.card.my_home.prototype.get_title_ = function() {
|
|||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
beestat.component.card.my_home.prototype.decorate_top_right_ = function(parent) {
|
beestat.component.card.my_home.prototype.decorate_top_right_ = function(parent) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
var menu = (new beestat.component.menu()).render(parent);
|
var menu = (new beestat.component.menu()).render(parent);
|
||||||
|
|
||||||
menu.add_menu_item(new beestat.component.menu_item()
|
menu.add_menu_item(new beestat.component.menu_item()
|
||||||
.set_text('Change System Type')
|
.set_text('Change System Type')
|
||||||
.set_icon('tune')
|
.set_icon('tune')
|
||||||
.set_callback(function() {
|
.set_callback(function() {
|
||||||
(new beestat.component.modal.change_system_type()).render();
|
(new beestat.component.modal.change_system_type(self.thermostat_id_)).render();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
menu.add_menu_item(new beestat.component.menu_item()
|
menu.add_menu_item(new beestat.component.menu_item()
|
||||||
|
@ -27,8 +27,8 @@ beestat.component.card.runtime_sensor_detail = function(thermostat_id) {
|
|||||||
[
|
[
|
||||||
'setting.runtime_sensor_detail_range_type',
|
'setting.runtime_sensor_detail_range_type',
|
||||||
'setting.runtime_sensor_detail_range_dynamic',
|
'setting.runtime_sensor_detail_range_dynamic',
|
||||||
'cache.runtime_thermostat',
|
'cache.data.runtime_thermostat_sensor_detail',
|
||||||
'cache.runtime_sensor'
|
'cache.data.runtime_sensor'
|
||||||
],
|
],
|
||||||
change_function
|
change_function
|
||||||
);
|
);
|
||||||
@ -131,11 +131,8 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_contents_ = func
|
|||||||
* the database, check every 2 seconds until it does.
|
* the database, check every 2 seconds until it does.
|
||||||
*/
|
*/
|
||||||
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
||||||
if (
|
if (beestat.cache.runtime_sensor === undefined) {
|
||||||
beestat.cache.runtime_sensor === undefined ||
|
this.show_loading_('Fetching');
|
||||||
beestat.cache.data.runtime_thermostat_last !== 'runtime_sensor_detail'
|
|
||||||
) {
|
|
||||||
this.show_loading_('Loading');
|
|
||||||
|
|
||||||
var value;
|
var value;
|
||||||
var operator;
|
var operator;
|
||||||
@ -191,8 +188,7 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_contents_ = func
|
|||||||
for (var alias in response) {
|
for (var alias in response) {
|
||||||
var r = response[alias];
|
var r = response[alias];
|
||||||
if (alias === 'runtime_thermostat') {
|
if (alias === 'runtime_thermostat') {
|
||||||
beestat.cache.set('data.runtime_thermostat_last', 'runtime_sensor_detail');
|
beestat.cache.set('data.runtime_thermostat_sensor_detail', r);
|
||||||
beestat.cache.set('runtime_thermostat', r);
|
|
||||||
} else {
|
} else {
|
||||||
runtime_sensors = runtime_sensors.concat(r);
|
runtime_sensors = runtime_sensors.concat(r);
|
||||||
}
|
}
|
||||||
@ -299,12 +295,12 @@ beestat.component.card.runtime_sensor_detail.prototype.decorate_top_right_ = fun
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
menu.add_menu_item(new beestat.component.menu_item()
|
menu.add_menu_item(new beestat.component.menu_item()
|
||||||
.set_text('Custom')
|
.set_text('Custom')
|
||||||
.set_icon('calendar_edit')
|
.set_icon('calendar_edit')
|
||||||
.set_callback(function() {
|
.set_callback(function() {
|
||||||
(new beestat.component.modal.runtime_sensor_detail_custom()).render();
|
(new beestat.component.modal.runtime_sensor_detail_custom()).render();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (this.has_data_() === true) {
|
if (this.has_data_() === true) {
|
||||||
menu.add_menu_item(new beestat.component.menu_item()
|
menu.add_menu_item(new beestat.component.menu_item()
|
||||||
@ -359,7 +355,6 @@ beestat.component.card.runtime_sensor_detail.prototype.has_data_ = function() {
|
|||||||
*/
|
*/
|
||||||
beestat.component.card.runtime_sensor_detail.prototype.get_data_ = function(force) {
|
beestat.component.card.runtime_sensor_detail.prototype.get_data_ = function(force) {
|
||||||
if (this.data_ === undefined || force === true) {
|
if (this.data_ === undefined || force === true) {
|
||||||
|
|
||||||
var range = {
|
var range = {
|
||||||
'type': beestat.setting('runtime_sensor_detail_range_type'),
|
'type': beestat.setting('runtime_sensor_detail_range_type'),
|
||||||
'dynamic': beestat.setting('runtime_sensor_detail_range_dynamic'),
|
'dynamic': beestat.setting('runtime_sensor_detail_range_dynamic'),
|
||||||
@ -368,7 +363,11 @@ beestat.component.card.runtime_sensor_detail.prototype.get_data_ = function(forc
|
|||||||
};
|
};
|
||||||
|
|
||||||
var sensor_data = beestat.runtime_sensor.get_data(this.thermostat_id_, range);
|
var sensor_data = beestat.runtime_sensor.get_data(this.thermostat_id_, range);
|
||||||
var thermostat_data = beestat.runtime_thermostat.get_data(this.thermostat_id_, range);
|
var thermostat_data = beestat.runtime_thermostat.get_data(
|
||||||
|
this.thermostat_id_,
|
||||||
|
range,
|
||||||
|
'runtime_thermostat_sensor_detail'
|
||||||
|
);
|
||||||
|
|
||||||
this.data_ = sensor_data;
|
this.data_ = sensor_data;
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ beestat.component.card.runtime_thermostat_detail = function(thermostat_id) {
|
|||||||
[
|
[
|
||||||
'setting.runtime_thermostat_detail_range_type',
|
'setting.runtime_thermostat_detail_range_type',
|
||||||
'setting.runtime_thermostat_detail_range_dynamic',
|
'setting.runtime_thermostat_detail_range_dynamic',
|
||||||
'cache.runtime_thermostat',
|
'cache.data.runtime_thermostat_thermostat_detail',
|
||||||
'cache.thermostat'
|
'cache.data.thermostat'
|
||||||
],
|
],
|
||||||
change_function
|
change_function
|
||||||
);
|
);
|
||||||
@ -150,11 +150,8 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_contents_ =
|
|||||||
* the database, check every 2 seconds until it does.
|
* the database, check every 2 seconds until it does.
|
||||||
*/
|
*/
|
||||||
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
||||||
if (
|
if (beestat.cache.data.runtime_thermostat_thermostat_detail === undefined) {
|
||||||
beestat.cache.runtime_thermostat === undefined ||
|
this.show_loading_('Fetching');
|
||||||
beestat.cache.data.runtime_thermostat_last !== 'runtime_thermostat_detail'
|
|
||||||
) {
|
|
||||||
this.show_loading_('Loading');
|
|
||||||
|
|
||||||
var value;
|
var value;
|
||||||
var operator;
|
var operator;
|
||||||
@ -185,8 +182,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_contents_ =
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.set_callback(function(response) {
|
.set_callback(function(response) {
|
||||||
beestat.cache.set('data.runtime_thermostat_last', 'runtime_thermostat_detail');
|
beestat.cache.set('data.runtime_thermostat_thermostat_detail', response);
|
||||||
beestat.cache.set('runtime_thermostat', response);
|
|
||||||
})
|
})
|
||||||
.send();
|
.send();
|
||||||
} else if (this.has_data_() === false) {
|
} else if (this.has_data_() === false) {
|
||||||
@ -246,7 +242,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_top_right_ =
|
|||||||
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 1 ||
|
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 1 ||
|
||||||
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
||||||
) {
|
) {
|
||||||
beestat.cache.delete('runtime_thermostat');
|
beestat.cache.delete('data.runtime_thermostat_thermostat_detail');
|
||||||
beestat.setting({
|
beestat.setting({
|
||||||
'runtime_thermostat_detail_range_dynamic': 1,
|
'runtime_thermostat_detail_range_dynamic': 1,
|
||||||
'runtime_thermostat_detail_range_type': 'dynamic'
|
'runtime_thermostat_detail_range_type': 'dynamic'
|
||||||
@ -262,7 +258,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_top_right_ =
|
|||||||
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 3 ||
|
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 3 ||
|
||||||
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
||||||
) {
|
) {
|
||||||
beestat.cache.delete('runtime_thermostat');
|
beestat.cache.delete('data.runtime_thermostat_thermostat_detail');
|
||||||
beestat.setting({
|
beestat.setting({
|
||||||
'runtime_thermostat_detail_range_dynamic': 3,
|
'runtime_thermostat_detail_range_dynamic': 3,
|
||||||
'runtime_thermostat_detail_range_type': 'dynamic'
|
'runtime_thermostat_detail_range_type': 'dynamic'
|
||||||
@ -278,7 +274,7 @@ beestat.component.card.runtime_thermostat_detail.prototype.decorate_top_right_ =
|
|||||||
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 7 ||
|
beestat.setting('runtime_thermostat_detail_range_dynamic') !== 7 ||
|
||||||
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
beestat.setting('runtime_thermostat_detail_range_type') !== 'dynamic'
|
||||||
) {
|
) {
|
||||||
beestat.cache.delete('runtime_thermostat');
|
beestat.cache.delete('data.runtime_thermostat_thermostat_detail');
|
||||||
beestat.setting({
|
beestat.setting({
|
||||||
'runtime_thermostat_detail_range_dynamic': 7,
|
'runtime_thermostat_detail_range_dynamic': 7,
|
||||||
'runtime_thermostat_detail_range_type': 'dynamic'
|
'runtime_thermostat_detail_range_type': 'dynamic'
|
||||||
@ -353,7 +349,11 @@ beestat.component.card.runtime_thermostat_detail.prototype.get_data_ = function(
|
|||||||
'static_end': beestat.setting('runtime_thermostat_detail_range_static_end')
|
'static_end': beestat.setting('runtime_thermostat_detail_range_static_end')
|
||||||
};
|
};
|
||||||
|
|
||||||
this.data_ = beestat.runtime_thermostat.get_data(this.thermostat_id_, range);
|
this.data_ = beestat.runtime_thermostat.get_data(
|
||||||
|
this.thermostat_id_,
|
||||||
|
range,
|
||||||
|
'runtime_thermostat_thermostat_detail'
|
||||||
|
);
|
||||||
|
|
||||||
this.data_.metadata.chart.title = this.get_title_();
|
this.data_.metadata.chart.title = this.get_title_();
|
||||||
this.data_.metadata.chart.subtitle = this.get_subtitle_();
|
this.data_.metadata.chart.subtitle = this.get_subtitle_();
|
||||||
|
@ -1,233 +0,0 @@
|
|||||||
/**
|
|
||||||
* Parent score card.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score = function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Debounce so that multiple setting changes don't re-trigger the same
|
|
||||||
* event. This fires on the trailing edge so that all changes are accounted
|
|
||||||
* for when rerendering.
|
|
||||||
*/
|
|
||||||
var data_change_function = beestat.debounce(function() {
|
|
||||||
self.rerender();
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
beestat.dispatcher.addEventListener(
|
|
||||||
'cache.data.comparison_scores_' + this.type_,
|
|
||||||
data_change_function
|
|
||||||
);
|
|
||||||
|
|
||||||
beestat.component.card.apply(this, arguments);
|
|
||||||
|
|
||||||
this.layer_.register_loader(beestat.comparisons.get_comparison_scores);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.card.score, beestat.component.card);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decorate
|
|
||||||
*
|
|
||||||
* @param {rocket.Elements} parent
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.prototype.decorate_contents_ = function(parent) {
|
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
|
||||||
beestat.cache.data['comparison_scores_' + this.type_] === undefined
|
|
||||||
) {
|
|
||||||
// Height buffer so the cards don't resize after they load.
|
|
||||||
parent.appendChild($.createElement('div')
|
|
||||||
.style('height', '166px')
|
|
||||||
.innerHTML(' '));
|
|
||||||
this.show_loading_('Calculating');
|
|
||||||
} else {
|
|
||||||
var percentile;
|
|
||||||
if (
|
|
||||||
thermostat_group.temperature_profile[this.type_] !== undefined &&
|
|
||||||
beestat.cache.data['comparison_scores_' + this.type_].length > 2
|
|
||||||
) {
|
|
||||||
percentile = this.get_percentile_(
|
|
||||||
thermostat_group.temperature_profile[this.type_].score,
|
|
||||||
beestat.cache.data['comparison_scores_' + this.type_]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
percentile = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var color;
|
|
||||||
if (percentile > 70) {
|
|
||||||
color = beestat.style.color.green.base;
|
|
||||||
} else if (percentile > 50) {
|
|
||||||
color = beestat.style.color.yellow.base;
|
|
||||||
} else if (percentile > 25) {
|
|
||||||
color = beestat.style.color.orange.base;
|
|
||||||
} else if (percentile !== null) {
|
|
||||||
color = beestat.style.color.red.base;
|
|
||||||
} else {
|
|
||||||
color = '#fff';
|
|
||||||
}
|
|
||||||
|
|
||||||
var container = $.createElement('div')
|
|
||||||
.style({
|
|
||||||
'text-align': 'center',
|
|
||||||
'position': 'relative',
|
|
||||||
'margin-top': beestat.style.size.gutter
|
|
||||||
});
|
|
||||||
parent.appendChild(container);
|
|
||||||
|
|
||||||
var percentile_text;
|
|
||||||
var percentile_font_size;
|
|
||||||
var percentile_color;
|
|
||||||
if (percentile !== null) {
|
|
||||||
percentile_text = '';
|
|
||||||
percentile_font_size = 48;
|
|
||||||
percentile_color = '#fff';
|
|
||||||
} else if (
|
|
||||||
thermostat_group['system_type_' + this.type_] === null ||
|
|
||||||
thermostat_group['system_type_' + this.type_] === 'none'
|
|
||||||
) {
|
|
||||||
percentile_text = 'None';
|
|
||||||
percentile_font_size = 16;
|
|
||||||
percentile_color = beestat.style.color.gray.base;
|
|
||||||
} else {
|
|
||||||
percentile_text = 'Insufficient data';
|
|
||||||
percentile_font_size = 16;
|
|
||||||
percentile_color = beestat.style.color.yellow.base;
|
|
||||||
}
|
|
||||||
|
|
||||||
var percentile_div = $.createElement('div')
|
|
||||||
.innerText(percentile_text)
|
|
||||||
.style({
|
|
||||||
'position': 'absolute',
|
|
||||||
'top': '50%',
|
|
||||||
'left': '50%',
|
|
||||||
'transform': 'translate(-50%, -50%)',
|
|
||||||
'font-size': percentile_font_size,
|
|
||||||
'font-weight': beestat.style.font_weight.light,
|
|
||||||
'color': percentile_color
|
|
||||||
});
|
|
||||||
container.appendChild(percentile_div);
|
|
||||||
|
|
||||||
var stroke = 3;
|
|
||||||
var size = 150;
|
|
||||||
var diameter = size - stroke;
|
|
||||||
var radius = diameter / 2;
|
|
||||||
var circumference = Math.PI * diameter;
|
|
||||||
|
|
||||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
||||||
svg.setAttribute('height', size);
|
|
||||||
svg.setAttribute('width', size);
|
|
||||||
svg.style.transform = 'rotate(-90deg)';
|
|
||||||
container.appendChild(svg);
|
|
||||||
|
|
||||||
var background = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
||||||
background.setAttribute('cx', (size / 2));
|
|
||||||
background.setAttribute('cy', (size / 2));
|
|
||||||
background.setAttribute('r', radius);
|
|
||||||
background.setAttribute('stroke', beestat.style.color.bluegray.dark);
|
|
||||||
background.setAttribute('stroke-width', stroke);
|
|
||||||
background.setAttribute('fill', 'none');
|
|
||||||
svg.appendChild(background);
|
|
||||||
|
|
||||||
var stroke_dasharray = circumference;
|
|
||||||
var stroke_dashoffset_initial = stroke_dasharray;
|
|
||||||
var stroke_dashoffset_final = stroke_dasharray * (1 - (percentile / 100));
|
|
||||||
|
|
||||||
var foreground = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
||||||
foreground.style.transition = 'stroke-dashoffset 1s ease';
|
|
||||||
foreground.setAttribute('cx', (size / 2));
|
|
||||||
foreground.setAttribute('cy', (size / 2));
|
|
||||||
foreground.setAttribute('r', radius);
|
|
||||||
foreground.setAttribute('stroke', color);
|
|
||||||
foreground.setAttribute('stroke-width', stroke);
|
|
||||||
foreground.setAttribute('stroke-linecap', 'round');
|
|
||||||
foreground.setAttribute('stroke-dasharray', stroke_dasharray);
|
|
||||||
foreground.setAttribute('stroke-dashoffset', stroke_dashoffset_initial);
|
|
||||||
foreground.setAttribute('fill', 'none');
|
|
||||||
svg.appendChild(foreground);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For some reason the render event (which is timeout 0) doesn't work well
|
|
||||||
* here.
|
|
||||||
*/
|
|
||||||
setTimeout(function() {
|
|
||||||
foreground.setAttribute('stroke-dashoffset', stroke_dashoffset_final);
|
|
||||||
|
|
||||||
if (percentile !== null) {
|
|
||||||
$.step(
|
|
||||||
function(percentage, sine) {
|
|
||||||
var calculated_percentile = Math.round(percentile * sine);
|
|
||||||
percentile_div.innerText(calculated_percentile);
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
null,
|
|
||||||
30
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the percentile rank of a score in a set of scores.
|
|
||||||
*
|
|
||||||
* @param {number} score
|
|
||||||
* @param {array} scores
|
|
||||||
*
|
|
||||||
* @return {number} The percentile rank.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.prototype.get_percentile_ = function(score, scores) {
|
|
||||||
var n = scores.length;
|
|
||||||
var below = 0;
|
|
||||||
scores.forEach(function(s) {
|
|
||||||
if (s < score) {
|
|
||||||
below++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Math.round(below / n * 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decorate the menu.
|
|
||||||
*
|
|
||||||
* @param {rocket.Elements} parent
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.prototype.decorate_top_right_ = function(parent) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var menu = (new beestat.component.menu()).render(parent);
|
|
||||||
|
|
||||||
menu.add_menu_item(new beestat.component.menu_item()
|
|
||||||
.set_text('Help')
|
|
||||||
.set_icon('help_circle')
|
|
||||||
.set_callback(function() {
|
|
||||||
window.open('https://doc.beestat.io/144d5dafbc6c43f7bc72341120717d8a');
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get subtitle.
|
|
||||||
*
|
|
||||||
* @return {string} The subtitle.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.prototype.get_subtitle_ = function() {
|
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
|
||||||
beestat.cache.data['comparison_scores_' + this.type_] !== undefined &&
|
|
||||||
beestat.cache.data['comparison_scores_' + this.type_].length > 2 &&
|
|
||||||
thermostat_group.temperature_profile[this.type_] !== null
|
|
||||||
) {
|
|
||||||
return 'Comparing to ' + Number(beestat.cache.data['comparison_scores_' + this.type_].length).toLocaleString() + ' Homes';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'N/A';
|
|
||||||
|
|
||||||
};
|
|
@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Cool score card.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.cool = function() {
|
|
||||||
this.type_ = 'cool';
|
|
||||||
beestat.component.card.score.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.card.score.cool, beestat.component.card.score);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title of the card.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.cool.prototype.get_title_ = function() {
|
|
||||||
return 'Cool Score';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the subtitle of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title of the card.
|
|
||||||
*/
|
|
||||||
// beestat.component.card.score.cool.prototype.get_subtitle_ = function() {
|
|
||||||
// return '#hype';
|
|
||||||
// };
|
|
@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Cool score card.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.heat = function() {
|
|
||||||
this.type_ = 'heat';
|
|
||||||
beestat.component.card.score.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.card.score.heat, beestat.component.card.score);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title of the card.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.heat.prototype.get_title_ = function() {
|
|
||||||
return 'Heat Score';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the subtitle of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title of the card.
|
|
||||||
*/
|
|
||||||
// beestat.component.card.score.heat.prototype.get_subtitle_ = function() {
|
|
||||||
// return '#hype';
|
|
||||||
// };
|
|
@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* Resist score card.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.resist = function() {
|
|
||||||
this.type_ = 'resist';
|
|
||||||
beestat.component.card.score.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.card.score.resist, beestat.component.card.score);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title of the card.
|
|
||||||
*/
|
|
||||||
beestat.component.card.score.resist.prototype.get_title_ = function() {
|
|
||||||
return 'Resist Score';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the subtitle of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title of the card.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* beestat.component.card.score.resist.prototype.get_subtitle_ = function() {
|
|
||||||
* return '#hype';
|
|
||||||
* };
|
|
||||||
*/
|
|
@ -162,7 +162,7 @@ beestat.component.card.sensors.prototype.decorate_sensor_ = function(parent, sen
|
|||||||
/**
|
/**
|
||||||
* Get the title of the card.
|
* Get the title of the card.
|
||||||
*
|
*
|
||||||
* @return {string}
|
* @return {string} The title.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.sensors.prototype.get_title_ = function() {
|
beestat.component.card.sensors.prototype.get_title_ = function() {
|
||||||
return 'Sensors';
|
return 'Sensors';
|
||||||
|
@ -87,6 +87,11 @@ beestat.component.card.system.prototype.decorate_circle_ = function(parent) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate the weather
|
||||||
|
*
|
||||||
|
* @param {rocket.Elements} parent Parent
|
||||||
|
*/
|
||||||
beestat.component.card.system.prototype.decorate_weather_ = function(parent) {
|
beestat.component.card.system.prototype.decorate_weather_ = function(parent) {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Temperature profiles.
|
* Temperature profiles.
|
||||||
*
|
*
|
||||||
* @param {number} thermostat_group_id The thermostat_group_id this card is
|
* @param {number} thermostat_id The thermostat_id this card is displaying
|
||||||
* displaying data for.
|
* data for.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.temperature_profiles = function(thermostat_group_id) {
|
beestat.component.card.temperature_profiles = function(thermostat_id) {
|
||||||
this.thermostat_group_id_ = thermostat_group_id;
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
beestat.component.card.apply(this, arguments);
|
beestat.component.card.apply(this, arguments);
|
||||||
};
|
};
|
||||||
@ -28,9 +28,7 @@ beestat.component.card.temperature_profiles.prototype.decorate_contents_ = funct
|
|||||||
* @return {object} The series data.
|
* @return {object} The series data.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
this.thermostat_group_id_
|
|
||||||
];
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
'x': [],
|
'x': [],
|
||||||
@ -41,7 +39,7 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
|||||||
'title': this.get_title_(),
|
'title': this.get_title_(),
|
||||||
'subtitle': this.get_subtitle_(),
|
'subtitle': this.get_subtitle_(),
|
||||||
'outdoor_temperature': beestat.temperature({
|
'outdoor_temperature': beestat.temperature({
|
||||||
'temperature': (thermostat_group.weather.temperature / 10),
|
'temperature': (thermostat.weather.temperature / 10),
|
||||||
'round': 0
|
'round': 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -49,10 +47,10 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
thermostat_group.temperature_profile === null
|
thermostat.profile === null
|
||||||
) {
|
) {
|
||||||
this.chart_.render(parent);
|
this.chart_.render(parent);
|
||||||
this.show_loading_('Calculating');
|
this.show_loading_('Fetching');
|
||||||
} else {
|
} else {
|
||||||
// Global x range.
|
// Global x range.
|
||||||
var x_min = Infinity;
|
var x_min = Infinity;
|
||||||
@ -60,19 +58,19 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
|
|||||||
|
|
||||||
var y_min = Infinity;
|
var y_min = Infinity;
|
||||||
var y_max = -Infinity;
|
var y_max = -Infinity;
|
||||||
for (var type in thermostat_group.temperature_profile) {
|
for (var type in thermostat.profile.temperature) {
|
||||||
// Cloned because I mutate this data for temperature conversions.
|
// Cloned because I mutate this data for temperature conversions.
|
||||||
var profile = beestat.clone(
|
var profile = beestat.clone(
|
||||||
thermostat_group.temperature_profile[type]
|
thermostat.profile.temperature[type]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (profile !== null) {
|
if (profile !== null) {
|
||||||
// Convert the data to Celsius if necessary
|
// Convert the data to Celsius if necessary
|
||||||
var deltas_converted = {};
|
var deltas_converted = {};
|
||||||
for (var key in profile.deltas) {
|
for (var key in profile.deltas) {
|
||||||
deltas_converted[beestat.temperature({'temperature': (key / 10)})] =
|
deltas_converted[beestat.temperature({'temperature': key})] =
|
||||||
beestat.temperature({
|
beestat.temperature({
|
||||||
'temperature': (profile.deltas[key] / 10),
|
'temperature': (profile.deltas[key]),
|
||||||
'delta': true,
|
'delta': true,
|
||||||
'round': 3
|
'round': 3
|
||||||
});
|
});
|
||||||
@ -199,24 +197,29 @@ beestat.component.card.temperature_profiles.prototype.get_title_ = function() {
|
|||||||
* @return {string} The subtitle.
|
* @return {string} The subtitle.
|
||||||
*/
|
*/
|
||||||
beestat.component.card.temperature_profiles.prototype.get_subtitle_ = function() {
|
beestat.component.card.temperature_profiles.prototype.get_subtitle_ = function() {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
const thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
|
|
||||||
/*
|
const generated_at_m = moment(
|
||||||
* All profiles are calculated at the same time, and resist is the most
|
thermostat.profile.metadata.generated_at
|
||||||
* guaranteed one to have.
|
);
|
||||||
*/
|
|
||||||
if (thermostat_group.temperature_profile.resist !== undefined) {
|
|
||||||
var generated_at_m = moment(
|
|
||||||
thermostat_group.temperature_profile.resist.metadata.generated_at
|
|
||||||
);
|
|
||||||
|
|
||||||
return 'Generated ' + generated_at_m.format('MMM Do @ h a') + ' (updated weekly)';
|
let duration_text = '';
|
||||||
|
|
||||||
|
// How much data was used to generate this.
|
||||||
|
const duration_weeks = Math.round(thermostat.profile.metadata.duration / 7);
|
||||||
|
duration_text += ' from the past ';
|
||||||
|
if (duration_weeks === 0) {
|
||||||
|
duration_text += ' few days';
|
||||||
|
} else if (duration_weeks === 1) {
|
||||||
|
duration_text += ' week';
|
||||||
|
} else if (duration_weeks >= 52) {
|
||||||
|
duration_text += ' year';
|
||||||
|
} else {
|
||||||
|
duration_text += duration_weeks + ' weeks';
|
||||||
}
|
}
|
||||||
|
duration_text += ' of data.';
|
||||||
|
|
||||||
return null;
|
return 'Generated ' + generated_at_m.format('MMM Do @ h a') + ' (updated weekly) ' + duration_text;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,237 +0,0 @@
|
|||||||
/**
|
|
||||||
* Temperature profiles.
|
|
||||||
*
|
|
||||||
* @param {number} thermostat_group_id The thermostat_group_id this card is
|
|
||||||
* displaying data for.
|
|
||||||
*/
|
|
||||||
beestat.component.card.temperature_profiles_new = function(thermostat_group_id) {
|
|
||||||
this.thermostat_group_id_ = thermostat_group_id;
|
|
||||||
|
|
||||||
beestat.component.card.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.card.temperature_profiles_new, beestat.component.card);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decorate card.
|
|
||||||
*
|
|
||||||
* @param {rocket.Elements} parent
|
|
||||||
*/
|
|
||||||
beestat.component.card.temperature_profiles_new.prototype.decorate_contents_ = function(parent) {
|
|
||||||
var data = this.get_data_();
|
|
||||||
this.chart_ = new beestat.component.chart.temperature_profiles_new(data);
|
|
||||||
this.chart_.render(parent);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all of the series data.
|
|
||||||
*
|
|
||||||
* @return {object} The series data.
|
|
||||||
*/
|
|
||||||
beestat.component.card.temperature_profiles_new.prototype.get_data_ = function() {
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
this.thermostat_group_id_
|
|
||||||
];
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
'x': [],
|
|
||||||
'series': {},
|
|
||||||
'metadata': {
|
|
||||||
'series': {},
|
|
||||||
'chart': {
|
|
||||||
'title': this.get_title_(),
|
|
||||||
'subtitle': this.get_subtitle_(),
|
|
||||||
'outdoor_temperature': beestat.temperature({
|
|
||||||
'temperature': (thermostat_group.weather.temperature / 10),
|
|
||||||
'round': 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
thermostat_group.profile === null
|
|
||||||
) {
|
|
||||||
this.chart_.render(parent);
|
|
||||||
this.show_loading_('Calculating');
|
|
||||||
} else {
|
|
||||||
// Global x range.
|
|
||||||
var x_min = Infinity;
|
|
||||||
var x_max = -Infinity;
|
|
||||||
|
|
||||||
var y_min = Infinity;
|
|
||||||
var y_max = -Infinity;
|
|
||||||
for (var type in thermostat_group.profile.temperature) {
|
|
||||||
// Cloned because I mutate this data for temperature conversions.
|
|
||||||
var profile = beestat.clone(
|
|
||||||
thermostat_group.profile.temperature[type]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (profile !== null) {
|
|
||||||
// Convert the data to Celsius if necessary
|
|
||||||
var deltas_converted = {};
|
|
||||||
for (var key in profile.deltas) {
|
|
||||||
deltas_converted[beestat.temperature({'temperature': key})] =
|
|
||||||
beestat.temperature({
|
|
||||||
'temperature': (profile.deltas[key]),
|
|
||||||
'delta': true,
|
|
||||||
'round': 3
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
profile.deltas = deltas_converted;
|
|
||||||
var linear_trendline = this.get_linear_trendline_(profile.deltas);
|
|
||||||
|
|
||||||
var min_max_keys = Object.keys(profile.deltas);
|
|
||||||
|
|
||||||
// This specific trendline x range.
|
|
||||||
var this_x_min = Math.min.apply(null, min_max_keys);
|
|
||||||
var this_x_max = Math.max.apply(null, min_max_keys);
|
|
||||||
|
|
||||||
// Global x range.
|
|
||||||
x_min = Math.min(x_min, this_x_min);
|
|
||||||
x_max = Math.max(x_max, this_x_max);
|
|
||||||
|
|
||||||
data.series['trendline_' + type] = [];
|
|
||||||
data.series['raw_' + type] = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data is stored internally as °F with 1 value per degree. That data
|
|
||||||
* gets converted to °C which then requires additional precision
|
|
||||||
* (increment).
|
|
||||||
*
|
|
||||||
* The additional precision introduces floating point error, so
|
|
||||||
* convert the x value to a fixed string.
|
|
||||||
*
|
|
||||||
* The string then needs converted to a number for highcharts, so
|
|
||||||
* later on use parseFloat to get back to that.
|
|
||||||
*
|
|
||||||
* Stupid Celsius.
|
|
||||||
*/
|
|
||||||
var increment;
|
|
||||||
var fixed;
|
|
||||||
if (beestat.setting('temperature_unit') === '°F') {
|
|
||||||
increment = 1;
|
|
||||||
fixed = 0;
|
|
||||||
} else {
|
|
||||||
increment = 0.1;
|
|
||||||
fixed = 1;
|
|
||||||
}
|
|
||||||
for (var x = this_x_min; x <= this_x_max; x += increment) {
|
|
||||||
var x_fixed = x.toFixed(fixed);
|
|
||||||
var y = (linear_trendline.slope * x_fixed) +
|
|
||||||
linear_trendline.intercept;
|
|
||||||
|
|
||||||
data.series['trendline_' + type].push([
|
|
||||||
parseFloat(x_fixed),
|
|
||||||
y
|
|
||||||
]);
|
|
||||||
if (profile.deltas[x_fixed] !== undefined) {
|
|
||||||
data.series['raw_' + type].push([
|
|
||||||
parseFloat(x_fixed),
|
|
||||||
profile.deltas[x_fixed]
|
|
||||||
]);
|
|
||||||
y_min = Math.min(y_min, profile.deltas[x_fixed]);
|
|
||||||
y_max = Math.max(y_max, profile.deltas[x_fixed]);
|
|
||||||
}
|
|
||||||
|
|
||||||
data.metadata.chart.y_min = y_min;
|
|
||||||
data.metadata.chart.y_max = y_max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a linear trendline from a set of data.
|
|
||||||
*
|
|
||||||
* @param {Object} data The data; at least two points required.
|
|
||||||
*
|
|
||||||
* @return {Object} The slope and intercept of the trendline.
|
|
||||||
*/
|
|
||||||
beestat.component.card.temperature_profiles_new.prototype.get_linear_trendline_ = function(data) {
|
|
||||||
// Requires at least two points.
|
|
||||||
if (Object.keys(data).length < 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sum_x = 0;
|
|
||||||
var sum_y = 0;
|
|
||||||
var sum_xy = 0;
|
|
||||||
var sum_x_squared = 0;
|
|
||||||
var n = 0;
|
|
||||||
|
|
||||||
for (var x in data) {
|
|
||||||
x = parseFloat(x);
|
|
||||||
var y = parseFloat(data[x]);
|
|
||||||
|
|
||||||
sum_x += x;
|
|
||||||
sum_y += y;
|
|
||||||
sum_xy += (x * y);
|
|
||||||
sum_x_squared += Math.pow(x, 2);
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var slope = ((n * sum_xy) - (sum_x * sum_y)) /
|
|
||||||
((n * sum_x_squared) - (Math.pow(sum_x, 2)));
|
|
||||||
var intercept = ((sum_y) - (slope * sum_x)) / (n);
|
|
||||||
|
|
||||||
return {
|
|
||||||
'slope': slope,
|
|
||||||
'intercept': intercept
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The title.
|
|
||||||
*/
|
|
||||||
beestat.component.card.temperature_profiles_new.prototype.get_title_ = function() {
|
|
||||||
return 'Temperature Profiles';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the subtitle of the card.
|
|
||||||
*
|
|
||||||
* @return {string} The subtitle.
|
|
||||||
*/
|
|
||||||
beestat.component.card.temperature_profiles_new.prototype.get_subtitle_ = function() {
|
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
|
|
||||||
var generated_at_m = moment(
|
|
||||||
thermostat_group.profile.metadata.generated_at
|
|
||||||
);
|
|
||||||
|
|
||||||
return 'Generated ' + generated_at_m.format('MMM Do @ h a') + ' (updated weekly)';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decorate the menu.
|
|
||||||
*
|
|
||||||
* @param {rocket.Elements} parent
|
|
||||||
*/
|
|
||||||
beestat.component.card.temperature_profiles_new.prototype.decorate_top_right_ = function(parent) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var menu = (new beestat.component.menu()).render(parent);
|
|
||||||
|
|
||||||
menu.add_menu_item(new beestat.component.menu_item()
|
|
||||||
.set_text('Download Chart')
|
|
||||||
.set_icon('download')
|
|
||||||
.set_callback(function() {
|
|
||||||
self.chart_.export();
|
|
||||||
}));
|
|
||||||
|
|
||||||
menu.add_menu_item(new beestat.component.menu_item()
|
|
||||||
.set_text('Help')
|
|
||||||
.set_icon('help_circle')
|
|
||||||
.set_callback(function() {
|
|
||||||
window.open('https://doc.beestat.io/9c0fba6793dd4bc68f798c1516f0ea25');
|
|
||||||
}));
|
|
||||||
};
|
|
@ -229,7 +229,7 @@ beestat.component.chart.runtime_sensor_detail_temperature.prototype.get_options_
|
|||||||
});
|
});
|
||||||
var occupancy_key = series.name.replace('temperature', 'occupancy');
|
var occupancy_key = series.name.replace('temperature', 'occupancy');
|
||||||
occupancy[occupancy_key] =
|
occupancy[occupancy_key] =
|
||||||
(self.data_.metadata.series[occupancy_key].data[x.valueOf()] !== undefined)
|
(self.data_.metadata.series[occupancy_key].data[x.valueOf()] !== undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -31,9 +31,9 @@ beestat.component.chart.temperature_profiles.prototype.get_options_series_ = fun
|
|||||||
|
|
||||||
// Trendline data
|
// Trendline data
|
||||||
series.push({
|
series.push({
|
||||||
'data': this.data_.series.trendline_heat,
|
'data': this.data_.series.trendline_heat_1,
|
||||||
'name': 'indoor_heat_delta',
|
'name': 'indoor_heat_1_delta',
|
||||||
'color': beestat.series.compressor_heat_1.color,
|
'color': beestat.series.indoor_heat_1_delta.color,
|
||||||
'marker': {
|
'marker': {
|
||||||
'enabled': false,
|
'enabled': false,
|
||||||
'states': {'hover': {'enabled': false}}
|
'states': {'hover': {'enabled': false}}
|
||||||
@ -45,9 +45,37 @@ beestat.component.chart.temperature_profiles.prototype.get_options_series_ = fun
|
|||||||
|
|
||||||
// Trendline data
|
// Trendline data
|
||||||
series.push({
|
series.push({
|
||||||
'data': this.data_.series.trendline_cool,
|
'data': this.data_.series.trendline_heat_2,
|
||||||
'name': 'indoor_cool_delta',
|
'name': 'indoor_heat_2_delta',
|
||||||
'color': beestat.series.compressor_cool_1.color,
|
'color': beestat.series.indoor_heat_2_delta.color,
|
||||||
|
'marker': {
|
||||||
|
'enabled': false,
|
||||||
|
'states': {'hover': {'enabled': false}}
|
||||||
|
},
|
||||||
|
'type': 'line',
|
||||||
|
'lineWidth': 2,
|
||||||
|
'states': {'hover': {'lineWidthPlus': 0}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trendline data
|
||||||
|
series.push({
|
||||||
|
'data': this.data_.series.trendline_cool_1,
|
||||||
|
'name': 'indoor_cool_1_delta',
|
||||||
|
'color': beestat.series.indoor_cool_1_delta.color,
|
||||||
|
'marker': {
|
||||||
|
'enabled': false,
|
||||||
|
'states': {'hover': {'enabled': false}}
|
||||||
|
},
|
||||||
|
'type': 'line',
|
||||||
|
'lineWidth': 2,
|
||||||
|
'states': {'hover': {'lineWidthPlus': 0}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trendline data
|
||||||
|
series.push({
|
||||||
|
'data': this.data_.series.trendline_cool_2,
|
||||||
|
'name': 'indoor_cool_2_delta',
|
||||||
|
'color': beestat.series.indoor_cool_2_delta.color,
|
||||||
'marker': {
|
'marker': {
|
||||||
'enabled': false,
|
'enabled': false,
|
||||||
'states': {'hover': {'enabled': false}}
|
'states': {'hover': {'enabled': false}}
|
||||||
@ -61,7 +89,7 @@ beestat.component.chart.temperature_profiles.prototype.get_options_series_ = fun
|
|||||||
series.push({
|
series.push({
|
||||||
'data': this.data_.series.trendline_resist,
|
'data': this.data_.series.trendline_resist,
|
||||||
'name': 'indoor_resist_delta',
|
'name': 'indoor_resist_delta',
|
||||||
'color': beestat.style.color.gray.dark,
|
'color': beestat.series.indoor_resist_delta.color,
|
||||||
'marker': {
|
'marker': {
|
||||||
'enabled': false,
|
'enabled': false,
|
||||||
'states': {'hover': {'enabled': false}}
|
'states': {'hover': {'enabled': false}}
|
||||||
@ -73,9 +101,9 @@ beestat.component.chart.temperature_profiles.prototype.get_options_series_ = fun
|
|||||||
|
|
||||||
// Raw data
|
// Raw data
|
||||||
series.push({
|
series.push({
|
||||||
'data': this.data_.series.raw_heat,
|
'data': this.data_.series.raw_heat_1,
|
||||||
'name': 'indoor_heat_delta_raw',
|
'name': 'indoor_heat_1_delta_raw',
|
||||||
'color': beestat.series.compressor_heat_1.color,
|
'color': beestat.series.indoor_heat_1_delta_raw.color,
|
||||||
'dashStyle': 'ShortDot',
|
'dashStyle': 'ShortDot',
|
||||||
'marker': {
|
'marker': {
|
||||||
'enabled': false,
|
'enabled': false,
|
||||||
@ -88,9 +116,39 @@ beestat.component.chart.temperature_profiles.prototype.get_options_series_ = fun
|
|||||||
|
|
||||||
// Raw data
|
// Raw data
|
||||||
series.push({
|
series.push({
|
||||||
'data': this.data_.series.raw_cool,
|
'data': this.data_.series.raw_heat_2,
|
||||||
'name': 'indoor_cool_delta_raw',
|
'name': 'indoor_heat_2_delta_raw',
|
||||||
'color': beestat.series.compressor_cool_1.color,
|
'color': beestat.series.indoor_heat_2_delta_raw.color,
|
||||||
|
'dashStyle': 'ShortDot',
|
||||||
|
'marker': {
|
||||||
|
'enabled': false,
|
||||||
|
'states': {'hover': {'enabled': false}}
|
||||||
|
},
|
||||||
|
'type': 'spline',
|
||||||
|
'lineWidth': 1,
|
||||||
|
'states': {'hover': {'lineWidthPlus': 0}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Raw data
|
||||||
|
series.push({
|
||||||
|
'data': this.data_.series.raw_cool_1,
|
||||||
|
'name': 'indoor_cool_1_delta_raw',
|
||||||
|
'color': beestat.series.indoor_cool_1_delta_raw.color,
|
||||||
|
'dashStyle': 'ShortDot',
|
||||||
|
'marker': {
|
||||||
|
'enabled': false,
|
||||||
|
'states': {'hover': {'enabled': false}}
|
||||||
|
},
|
||||||
|
'type': 'spline',
|
||||||
|
'lineWidth': 1,
|
||||||
|
'states': {'hover': {'lineWidthPlus': 0}}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Raw data
|
||||||
|
series.push({
|
||||||
|
'data': this.data_.series.raw_cool_2,
|
||||||
|
'name': 'indoor_cool_2_delta_raw',
|
||||||
|
'color': beestat.series.indoor_cool_2_delta_raw.color,
|
||||||
'dashStyle': 'ShortDot',
|
'dashStyle': 'ShortDot',
|
||||||
'marker': {
|
'marker': {
|
||||||
'enabled': false,
|
'enabled': false,
|
||||||
@ -105,7 +163,7 @@ beestat.component.chart.temperature_profiles.prototype.get_options_series_ = fun
|
|||||||
series.push({
|
series.push({
|
||||||
'data': this.data_.series.raw_resist,
|
'data': this.data_.series.raw_resist,
|
||||||
'name': 'indoor_resist_delta_raw',
|
'name': 'indoor_resist_delta_raw',
|
||||||
'color': beestat.style.color.gray.dark,
|
'color': beestat.series.indoor_resist_delta_raw.color,
|
||||||
'dashStyle': 'ShortDot',
|
'dashStyle': 'ShortDot',
|
||||||
'marker': {
|
'marker': {
|
||||||
'enabled': false,
|
'enabled': false,
|
||||||
|
@ -1,346 +0,0 @@
|
|||||||
/**
|
|
||||||
* Temperature profiles chart.
|
|
||||||
*
|
|
||||||
* @param {object} data The chart data.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new = function(data) {
|
|
||||||
this.data_ = data;
|
|
||||||
|
|
||||||
beestat.component.chart.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.chart.temperature_profiles_new, beestat.component.chart);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_xAxis_labels_formatter_.
|
|
||||||
*
|
|
||||||
* @return {Function} xAxis labels formatter.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_xAxis_labels_formatter_ = function() {
|
|
||||||
return function() {
|
|
||||||
return this.value + beestat.setting('temperature_unit');
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_series_.
|
|
||||||
*
|
|
||||||
* @return {Array} All of the series to display on the chart.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_series_ = function() {
|
|
||||||
var series = [];
|
|
||||||
|
|
||||||
// Trendline data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.trendline_heat_1,
|
|
||||||
'name': 'indoor_heat_1_delta',
|
|
||||||
'color': beestat.series.indoor_heat_1_delta.color,
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'line',
|
|
||||||
'lineWidth': 2,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trendline data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.trendline_heat_2,
|
|
||||||
'name': 'indoor_heat_2_delta',
|
|
||||||
'color': beestat.series.indoor_heat_2_delta.color,
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'line',
|
|
||||||
'lineWidth': 2,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trendline data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.trendline_cool_1,
|
|
||||||
'name': 'indoor_cool_1_delta',
|
|
||||||
'color': beestat.series.indoor_cool_1_delta.color,
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'line',
|
|
||||||
'lineWidth': 2,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trendline data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.trendline_cool_2,
|
|
||||||
'name': 'indoor_cool_2_delta',
|
|
||||||
'color': beestat.series.indoor_cool_2_delta.color,
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'line',
|
|
||||||
'lineWidth': 2,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trendline data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.trendline_resist,
|
|
||||||
'name': 'indoor_resist_delta',
|
|
||||||
'color': beestat.series.indoor_resist_delta.color,
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'line',
|
|
||||||
'lineWidth': 2,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Raw data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.raw_heat_1,
|
|
||||||
'name': 'indoor_heat_1_delta_raw',
|
|
||||||
'color': beestat.series.indoor_heat_1_delta_raw.color,
|
|
||||||
'dashStyle': 'ShortDot',
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'spline',
|
|
||||||
'lineWidth': 1,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Raw data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.raw_heat_2,
|
|
||||||
'name': 'indoor_heat_2_delta_raw',
|
|
||||||
'color': beestat.series.indoor_heat_2_delta_raw.color,
|
|
||||||
'dashStyle': 'ShortDot',
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'spline',
|
|
||||||
'lineWidth': 1,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Raw data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.raw_cool_1,
|
|
||||||
'name': 'indoor_cool_1_delta_raw',
|
|
||||||
'color': beestat.series.indoor_cool_1_delta_raw.color,
|
|
||||||
'dashStyle': 'ShortDot',
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'spline',
|
|
||||||
'lineWidth': 1,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Raw data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.raw_cool_2,
|
|
||||||
'name': 'indoor_cool_2_delta_raw',
|
|
||||||
'color': beestat.series.indoor_cool_2_delta_raw.color,
|
|
||||||
'dashStyle': 'ShortDot',
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'spline',
|
|
||||||
'lineWidth': 1,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Raw data
|
|
||||||
series.push({
|
|
||||||
'data': this.data_.series.raw_resist,
|
|
||||||
'name': 'indoor_resist_delta_raw',
|
|
||||||
'color': beestat.series.indoor_resist_delta_raw.color,
|
|
||||||
'dashStyle': 'ShortDot',
|
|
||||||
'marker': {
|
|
||||||
'enabled': false,
|
|
||||||
'states': {'hover': {'enabled': false}}
|
|
||||||
},
|
|
||||||
'type': 'spline',
|
|
||||||
'lineWidth': 1,
|
|
||||||
'states': {'hover': {'lineWidthPlus': 0}}
|
|
||||||
});
|
|
||||||
|
|
||||||
return series;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_yAxis_.
|
|
||||||
*
|
|
||||||
* @return {Array} The y-axis options.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_yAxis_ = function() {
|
|
||||||
var absolute_y_max = Math.max(
|
|
||||||
Math.abs(this.data_.metadata.chart.y_min),
|
|
||||||
Math.abs(this.data_.metadata.chart.y_max)
|
|
||||||
);
|
|
||||||
|
|
||||||
var y_min = absolute_y_max * -1;
|
|
||||||
var y_max = absolute_y_max;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'alignTicks': false,
|
|
||||||
'gridLineColor': beestat.style.color.bluegray.light,
|
|
||||||
'gridLineDashStyle': 'longdash',
|
|
||||||
'title': {'text': null},
|
|
||||||
'labels': {
|
|
||||||
'style': {'color': beestat.style.color.gray.base},
|
|
||||||
'formatter': function() {
|
|
||||||
return this.value + beestat.setting('temperature_unit');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'min': y_min,
|
|
||||||
'max': y_max,
|
|
||||||
'plotLines': [
|
|
||||||
{
|
|
||||||
'color': beestat.style.color.bluegray.light,
|
|
||||||
'dashStyle': 'solid',
|
|
||||||
'width': 3,
|
|
||||||
'value': 0,
|
|
||||||
'zIndex': 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_tooltip_formatter_.
|
|
||||||
*
|
|
||||||
* @return {Function} The tooltip formatter.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_tooltip_formatter_ = function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
var sections = [];
|
|
||||||
var section = [];
|
|
||||||
this.points.forEach(function(point) {
|
|
||||||
var series = point.series;
|
|
||||||
|
|
||||||
var value = beestat.temperature({
|
|
||||||
'temperature': point.y,
|
|
||||||
'units': true,
|
|
||||||
'convert': false,
|
|
||||||
'delta': true,
|
|
||||||
'type': 'string'
|
|
||||||
}) + ' / h';
|
|
||||||
|
|
||||||
if (series.name.indexOf('raw') === -1) {
|
|
||||||
section.push({
|
|
||||||
'label': beestat.series[series.name].name,
|
|
||||||
'value': value,
|
|
||||||
'color': series.color
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sections.push(section);
|
|
||||||
|
|
||||||
return self.tooltip_formatter_helper_(
|
|
||||||
'Outdoor Temp: ' +
|
|
||||||
beestat.temperature({
|
|
||||||
'temperature': this.x,
|
|
||||||
'round': 0,
|
|
||||||
'units': true,
|
|
||||||
'convert': false
|
|
||||||
}),
|
|
||||||
sections
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_chart_zoomType_.
|
|
||||||
*
|
|
||||||
* @return {string} The zoom type.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_chart_zoomType_ = function() {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_legend_.
|
|
||||||
*
|
|
||||||
* @return {object} The legend options.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_legend_ = function() {
|
|
||||||
return {
|
|
||||||
'enabled': false
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_xAxis_.
|
|
||||||
*
|
|
||||||
* @return {object} The xAxis options.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_xAxis_ = function() {
|
|
||||||
return {
|
|
||||||
'lineWidth': 0,
|
|
||||||
'tickLength': 0,
|
|
||||||
'tickInterval': 5,
|
|
||||||
'gridLineWidth': 1,
|
|
||||||
'gridLineColor': beestat.style.color.bluegray.light,
|
|
||||||
'gridLineDashStyle': 'longdash',
|
|
||||||
'labels': {
|
|
||||||
'style': {
|
|
||||||
'color': beestat.style.color.gray.base
|
|
||||||
},
|
|
||||||
'formatter': this.get_options_xAxis_labels_formatter_()
|
|
||||||
},
|
|
||||||
'crosshair': this.get_options_xAxis_crosshair_(),
|
|
||||||
'plotLines': [
|
|
||||||
{
|
|
||||||
'color': beestat.series.outdoor_temperature.color,
|
|
||||||
'dashStyle': 'ShortDash',
|
|
||||||
'width': 1,
|
|
||||||
'label': {
|
|
||||||
'style': {
|
|
||||||
'color': beestat.series.outdoor_temperature.color
|
|
||||||
},
|
|
||||||
'useHTML': true,
|
|
||||||
'text': 'Now: ' + beestat.temperature({
|
|
||||||
'temperature': this.data_.metadata.chart.outdoor_temperature,
|
|
||||||
'convert': false,
|
|
||||||
'units': true,
|
|
||||||
'round': 0
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'value': this.data_.metadata.chart.outdoor_temperature,
|
|
||||||
'zIndex': 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_chart_height_.
|
|
||||||
*
|
|
||||||
* @return {number} The height of the chart.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_chart_height_ = function() {
|
|
||||||
return 300;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override for get_options_plotOptions_series_connectNulls_.
|
|
||||||
*
|
|
||||||
* @return {boolean} Whether or not to connect nulls.
|
|
||||||
*/
|
|
||||||
beestat.component.chart.temperature_profiles_new.prototype.get_options_plotOptions_series_connectNulls_ = function() {
|
|
||||||
return true;
|
|
||||||
};
|
|
@ -6,130 +6,415 @@ beestat.component.metric = function() {
|
|||||||
};
|
};
|
||||||
beestat.extend(beestat.component.metric, beestat.component);
|
beestat.extend(beestat.component.metric, beestat.component);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this is a temperature value. If so, do the appropriate
|
||||||
|
* conversion on display.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.is_temperature_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this temperature value is a delta instead of an absolute
|
||||||
|
* value. If so, do the appropriate conversion on display.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.is_temperature_delta_ = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorate
|
* Decorate
|
||||||
*
|
*
|
||||||
* @param {rocket.Elements} parent
|
* @param {rocket.Elements} parent
|
||||||
*/
|
*/
|
||||||
beestat.component.metric.prototype.decorate_ = function(parent) {
|
beestat.component.metric.prototype.decorate_ = function(parent) {
|
||||||
if (beestat.cache.data.metrics === undefined) { // todo
|
const self = this;
|
||||||
parent.appendChild($.createElement('div').innerText('Loading...'));
|
const metric = this.get_metric_();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var outer_container = $.createElement('div').style({
|
// Construct the table
|
||||||
'background': beestat.style.color.bluegray.dark,
|
var table = $.createElement('table').style('width', '100%');
|
||||||
'padding': (beestat.style.size.gutter / 2)
|
table.setAttribute({
|
||||||
|
'cellpadding': '0',
|
||||||
|
'cellspacing': '0'
|
||||||
});
|
});
|
||||||
|
parent.appendChild(table);
|
||||||
|
|
||||||
outer_container.appendChild(
|
var tr = $.createElement('tr');
|
||||||
$.createElement('div').innerText(this.get_title_())
|
table.appendChild(tr);
|
||||||
|
var td_icon = $.createElement('td')
|
||||||
|
.style({
|
||||||
|
'width': '36px',
|
||||||
|
'text-align': 'center',
|
||||||
|
'background': this.get_color_()
|
||||||
|
});
|
||||||
|
tr.appendChild(td_icon);
|
||||||
|
|
||||||
|
var td_title = $.createElement('td')
|
||||||
|
.style({
|
||||||
|
'width': '100px',
|
||||||
|
'background': this.get_color_(),
|
||||||
|
'color': '#fff'
|
||||||
|
});
|
||||||
|
tr.appendChild(td_title);
|
||||||
|
|
||||||
|
var td_chart = $.createElement('td')
|
||||||
|
.style({
|
||||||
|
'text-align': 'right'
|
||||||
|
});
|
||||||
|
tr.appendChild(td_chart);
|
||||||
|
|
||||||
|
// Fill in the content.
|
||||||
|
(new beestat.component.icon(this.get_icon_()))
|
||||||
|
.set_color('#fff')
|
||||||
|
.render(td_icon);
|
||||||
|
|
||||||
|
td_title.appendChild($.createElement('div').innerText(this.get_title_() + ' '));
|
||||||
|
|
||||||
|
td_title.appendChild(
|
||||||
|
$.createElement('div')
|
||||||
|
.innerText(this.get_histogram_sum_().toLocaleString() + ' others')
|
||||||
);
|
);
|
||||||
|
|
||||||
var inner_container = $.createElement('div').style({
|
var chart_container = $.createElement('div').style({
|
||||||
'position': 'relative',
|
'position': 'relative',
|
||||||
'margin-top': '50px',
|
'height': '60px'
|
||||||
'margin-bottom': '20px',
|
|
||||||
'margin-left': '25px'
|
|
||||||
});
|
});
|
||||||
|
td_chart.appendChild(chart_container);
|
||||||
|
|
||||||
var icon = $.createElement('div').style({
|
var formatter = this.get_formatter_();
|
||||||
'position': 'absolute',
|
|
||||||
'top': '-12px',
|
const chart_height = 60;
|
||||||
'left': '-28px'
|
const chart_padding = 20;
|
||||||
});
|
const chart = $.createElement('div').style({
|
||||||
|
'height': chart_height + 'px',
|
||||||
(new beestat.component.icon(this.get_icon_()))
|
'padding-top': chart_padding + 'px',
|
||||||
.set_color(this.get_color_())
|
'position': 'relative'
|
||||||
.render(icon);
|
|
||||||
|
|
||||||
var line = $.createElement('div').style({
|
|
||||||
'background': this.get_color_(),
|
|
||||||
'height': '5px',
|
|
||||||
'border-radius': '5px'
|
|
||||||
});
|
|
||||||
|
|
||||||
var min = $.createElement('div')
|
|
||||||
.innerText(this.get_min_(true))
|
|
||||||
.style({
|
|
||||||
'position': 'absolute',
|
|
||||||
'top': '10px',
|
|
||||||
'left': '0px'
|
|
||||||
});
|
|
||||||
|
|
||||||
var max = $.createElement('div')
|
|
||||||
.innerText(this.get_max_(true))
|
|
||||||
.style({
|
|
||||||
'position': 'absolute',
|
|
||||||
'top': '10px',
|
|
||||||
'right': '0px'
|
|
||||||
});
|
|
||||||
|
|
||||||
var label = $.createElement('div')
|
|
||||||
.innerText(this.get_value_())
|
|
||||||
.style({
|
|
||||||
'position': 'absolute',
|
|
||||||
'top': '-25px',
|
|
||||||
'left': this.get_marker_position_() + '%',
|
|
||||||
'width': '100px',
|
|
||||||
'text-align': 'center',
|
|
||||||
'margin-left': '-50px',
|
|
||||||
'font-weight': beestat.style.font_weight.bold
|
|
||||||
});
|
|
||||||
|
|
||||||
var circle = $.createElement('div').style({
|
|
||||||
'background': this.get_color_(),
|
|
||||||
'position': 'absolute',
|
|
||||||
'top': '-4px',
|
|
||||||
'left': this.get_marker_position_() + '%',
|
|
||||||
'margin-left': '-7px',
|
|
||||||
'width': '14px',
|
|
||||||
'height': '14px',
|
|
||||||
'border-radius': '50%'
|
|
||||||
});
|
|
||||||
|
|
||||||
var chart = $.createElement('div').style({
|
|
||||||
'position': 'absolute',
|
|
||||||
'top': '-40px',
|
|
||||||
'left': '0px',
|
|
||||||
'width': '100%',
|
|
||||||
'height': '40px'
|
|
||||||
});
|
});
|
||||||
|
chart_container.appendChild(chart);
|
||||||
|
|
||||||
var histogram = this.get_histogram_();
|
var histogram = this.get_histogram_();
|
||||||
var histogram_max = this.get_histogram_max_();
|
var histogram_mode = this.get_histogram_mode_();
|
||||||
var column_width = (100 / histogram.length) + '%';
|
var column_width = (100 / histogram.length);
|
||||||
histogram.forEach(function(data) {
|
|
||||||
var column = $.createElement('div').style({
|
let my_column_index;
|
||||||
|
let my_column_height;
|
||||||
|
let sum_less = 0;
|
||||||
|
let sum_more = 0;
|
||||||
|
histogram.forEach(function(data, i) {
|
||||||
|
const height = (data.count / histogram_mode * 100);
|
||||||
|
const column = $.createElement('div').style({
|
||||||
'display': 'inline-block',
|
'display': 'inline-block',
|
||||||
'background': 'rgba(255, 255, 255, 0.1)',
|
'width': column_width + '%',
|
||||||
'width': column_width,
|
'height': height + '%'
|
||||||
'height': (data.count / histogram_max * 100) + '%'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const value = self.get_value_();
|
||||||
|
if (
|
||||||
|
value >= data.value &&
|
||||||
|
value < data.value + metric.interval
|
||||||
|
) {
|
||||||
|
column.style({
|
||||||
|
'background': '#516169',
|
||||||
|
'position': 'relative',
|
||||||
|
|
||||||
|
// Makes it visible even if it's 0px high.
|
||||||
|
'border-bottom': '2px solid #516169'
|
||||||
|
});
|
||||||
|
my_column_index = i;
|
||||||
|
my_column_height = height;
|
||||||
|
} else {
|
||||||
|
if (my_column_index === undefined) {
|
||||||
|
sum_less += data.count;
|
||||||
|
} else {
|
||||||
|
sum_more += data.count;
|
||||||
|
}
|
||||||
|
column.style({
|
||||||
|
'background': beestat.style.color.bluegray.light,
|
||||||
|
'border-bottom': '2px solid ' + beestat.style.color.bluegray.light
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
chart.appendChild(column);
|
chart.appendChild(column);
|
||||||
});
|
});
|
||||||
|
|
||||||
inner_container.appendChild(icon);
|
const label_height = 16;
|
||||||
inner_container.appendChild(line);
|
const label_bottom = Math.max(20, ((chart_height - chart_padding) * my_column_height / 100));
|
||||||
inner_container.appendChild(min);
|
var label = $.createElement('div')
|
||||||
inner_container.appendChild(max);
|
.innerText(formatter(this.get_value_(), this.get_precision_()))
|
||||||
inner_container.appendChild(label);
|
.style({
|
||||||
inner_container.appendChild(circle);
|
'position': 'absolute',
|
||||||
inner_container.appendChild(chart);
|
'bottom': label_bottom + 'px',
|
||||||
|
'left': Math.min(85, ((my_column_index * column_width) + (column_width / 2))) + '%',
|
||||||
|
'width': '60px',
|
||||||
|
'height': label_height + 'px',
|
||||||
|
'line-height': label_height + 'px',
|
||||||
|
'margin-left': '-30px',
|
||||||
|
'text-align': 'center',
|
||||||
|
'font-weight': beestat.style.font_weight.bold,
|
||||||
|
'text-shadow': '1px 1px 1px rgba(0, 0, 0, 0.5)'
|
||||||
|
});
|
||||||
|
chart.appendChild(label);
|
||||||
|
|
||||||
outer_container.appendChild(inner_container);
|
// Min & Max
|
||||||
|
chart.appendChild(
|
||||||
|
$.createElement('div')
|
||||||
|
.style({
|
||||||
|
'position': 'absolute',
|
||||||
|
'left': '4px',
|
||||||
|
'bottom': '2px',
|
||||||
|
'color': 'rgba(255, 255, 255, 0.5)',
|
||||||
|
'font-size': '11px'
|
||||||
|
})
|
||||||
|
.innerText(formatter(this.get_min_(), this.get_precision_()))
|
||||||
|
);
|
||||||
|
chart.appendChild(
|
||||||
|
$.createElement('div')
|
||||||
|
.style({
|
||||||
|
'position': 'absolute',
|
||||||
|
'right': '4px',
|
||||||
|
'bottom': '2px',
|
||||||
|
'color': 'rgba(255, 255, 255, 0.5)',
|
||||||
|
'font-size': '11px'
|
||||||
|
})
|
||||||
|
.innerText(formatter(this.get_max_(), this.get_precision_()))
|
||||||
|
);
|
||||||
|
|
||||||
parent.appendChild(outer_container);
|
// Greater or less than % label
|
||||||
|
const percentage_label = $.createElement('div');
|
||||||
|
|
||||||
|
let percentage;
|
||||||
|
let symbol;
|
||||||
|
if (sum_less >= sum_more) {
|
||||||
|
symbol = '>';
|
||||||
|
percentage = sum_less / this.get_histogram_sum_();
|
||||||
|
} else {
|
||||||
|
symbol = '<';
|
||||||
|
percentage = sum_more / this.get_histogram_sum_();
|
||||||
|
}
|
||||||
|
|
||||||
|
percentage_label.innerText(
|
||||||
|
symbol + ' ' + (percentage * 100).toFixed(0) + '% homes'
|
||||||
|
);
|
||||||
|
td_title.appendChild(percentage_label);
|
||||||
};
|
};
|
||||||
|
|
||||||
beestat.component.metric.prototype.get_marker_position_ = function() {
|
/**
|
||||||
return 100 * (this.get_value_() - this.get_min_()) / (this.get_max_() - this.get_min_());
|
* Get the largest histogram count.
|
||||||
};
|
*
|
||||||
|
* @return {number} The largest histogram count.
|
||||||
beestat.component.metric.prototype.get_histogram_max_ = function() {
|
*/
|
||||||
var max = -Infinity;
|
beestat.component.metric.prototype.get_histogram_mode_ = function() {
|
||||||
|
let mode = -Infinity;
|
||||||
this.get_histogram_().forEach(function(data) {
|
this.get_histogram_().forEach(function(data) {
|
||||||
max = Math.max(max, data.count);
|
mode = Math.max(mode, data.count);
|
||||||
});
|
});
|
||||||
|
return mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sum of the histogram counts.
|
||||||
|
*
|
||||||
|
* @return {number} The sum of the histogram counts.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_histogram_sum_ = function() {
|
||||||
|
let sum = 0;
|
||||||
|
this.get_histogram_().forEach(function(data) {
|
||||||
|
sum += data.count;
|
||||||
|
});
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unit string to append to the end of the value.
|
||||||
|
*
|
||||||
|
* @return {mixed} The unit string.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_units_ = function() {
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the a formatter function that applies a transformation to the value.
|
||||||
|
*
|
||||||
|
* @return {mixed} A function that formats the string.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_formatter_ = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return function(value) {
|
||||||
|
let return_value = value;
|
||||||
|
if (self.is_temperature_ === true) {
|
||||||
|
return_value = beestat.temperature({
|
||||||
|
'temperature': value,
|
||||||
|
'delta': self.is_temperature_delta_
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return return_value.toFixed(self.get_precision_()) + self.get_units_();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the minimum value of this metric (within two standard deviations). Then
|
||||||
|
* make it go to the min of that and the actual value.
|
||||||
|
*
|
||||||
|
* @return {mixed} The minimum value of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_min_ = function() {
|
||||||
|
const metric = this.get_metric_();
|
||||||
|
const cutoff_min = this.get_cutoff_min_();
|
||||||
|
|
||||||
|
// Median minus 2 * standard deviation
|
||||||
|
let min = (metric.median - (metric.standard_deviation * 2));
|
||||||
|
|
||||||
|
// If lower than the cutoff, place at the cutoff
|
||||||
|
if (cutoff_min !== null) {
|
||||||
|
min = Math.max(min, cutoff_min);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unless the thermostat value is lower than the cutoff, then go there
|
||||||
|
min = Math.min(min, this.get_value_());
|
||||||
|
|
||||||
|
// Round down to the nearest interval
|
||||||
|
min = this.round_(min, 'floor');
|
||||||
|
|
||||||
|
return min;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum value of this metric (within two standard deviations). Then
|
||||||
|
* make it go to the max of that and the actual value.
|
||||||
|
*
|
||||||
|
* @return {mixed} The maximum value of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_max_ = function() {
|
||||||
|
const metric = this.get_metric_();
|
||||||
|
const cutoff_max = this.get_cutoff_max_();
|
||||||
|
|
||||||
|
// Median plus 2 * standard deviation
|
||||||
|
let max = (metric.median + (metric.standard_deviation * 2));
|
||||||
|
|
||||||
|
// If higher than the cutoff, place at the cutoff
|
||||||
|
if (cutoff_max !== null) {
|
||||||
|
max = Math.min(max, cutoff_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unless the thermostat value is higher than the cutoff, then go there
|
||||||
|
max = Math.max(max, this.get_value_());
|
||||||
|
|
||||||
|
// Round up to the nearest interval
|
||||||
|
max = this.round_(max, 'ceil');
|
||||||
|
|
||||||
return max;
|
return max;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max cutoff. This is used to set the chart min to max(median - 2 *
|
||||||
|
* stddev, max cutoff).
|
||||||
|
*
|
||||||
|
* @return {object} The cutoff value.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_cutoff_min_ = function() {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max cutoff. This is used to set the chart max to min(median + 2 *
|
||||||
|
* stddev, max cutoff).
|
||||||
|
*
|
||||||
|
* @return {object} The cutoff value.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_cutoff_max_ = function() {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of this metric.
|
||||||
|
*
|
||||||
|
* @return {mixed} The value of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_value_ = function() {
|
||||||
|
const thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
|
|
||||||
|
if (this.child_metric_name_ !== undefined) {
|
||||||
|
return this.round_(
|
||||||
|
thermostat.profile[this.parent_metric_name_][this.child_metric_name_]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.round_(thermostat.profile[this.parent_metric_name_]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual metric object as returned from thermostat->get_metrics().
|
||||||
|
*
|
||||||
|
* @return {array} THe metric object.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_metric_ = function() {
|
||||||
|
if (this.child_metric_name_ !== undefined) {
|
||||||
|
return beestat.cache.data.metrics[this.parent_metric_name_][this.child_metric_name_];
|
||||||
|
}
|
||||||
|
|
||||||
|
return beestat.cache.data.metrics[this.parent_metric_name_];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the histogram returned from the API, fill in missing values, and
|
||||||
|
* remove anything outside the min and max.
|
||||||
|
*
|
||||||
|
* @return {array} Histogram data.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_histogram_ = function() {
|
||||||
|
const metric = this.get_metric_();
|
||||||
|
|
||||||
|
const min = this.get_min_();
|
||||||
|
const max = this.get_max_();
|
||||||
|
|
||||||
|
const my_value = this.get_value_();
|
||||||
|
|
||||||
|
var histogram = [];
|
||||||
|
for (let value = min; value <= max; value += metric.interval) {
|
||||||
|
let count = metric.histogram[value.toFixed(this.get_precision_())] || 0;
|
||||||
|
|
||||||
|
// The API call does not include me in the histogram; add it here.
|
||||||
|
if (value.toFixed(this.get_precision_()) === my_value.toFixed(this.get_precision_())) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
histogram.push({
|
||||||
|
'value': value,
|
||||||
|
'count': count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return histogram;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on the interval, get the precision.
|
||||||
|
*
|
||||||
|
* @return {number} The precision.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.get_precision_ = function() {
|
||||||
|
const metric = this.get_metric_();
|
||||||
|
if (Math.floor(metric.interval) === metric.interval) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return metric.interval.toString().split('.')[1].length || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Round a number to the precision that this metric supports. Useful, for
|
||||||
|
* example, because the profile is sometimes a higher precision than the
|
||||||
|
* metric uses for display purposes.
|
||||||
|
*
|
||||||
|
* @param {number} value The value to round.
|
||||||
|
* @param {string} mode The math function to use when rounding. Default round,
|
||||||
|
* can also choose floor or ceil.
|
||||||
|
*
|
||||||
|
* @return {number} The rounded value.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.prototype.round_ = function(value, mode) {
|
||||||
|
const metric = this.get_metric_();
|
||||||
|
const math_function = (mode === undefined) ? 'round' : mode;
|
||||||
|
return Math[math_function](value / metric.interval) * metric.interval;
|
||||||
|
};
|
||||||
|
43
js/component/metric/balance_point.js
Normal file
43
js/component/metric/balance_point.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Balance point metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.balance_point, beestat.component.metric);
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.prototype.parent_metric_name_ = 'balance_point';
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.prototype.is_temperature_ = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the units for this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The units for this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.prototype.get_units_ = function() {
|
||||||
|
return beestat.setting('temperature_unit');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.prototype.get_title_ = function() {
|
||||||
|
return beestat.series['compressor_' + this.child_metric_name_].name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The color of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.prototype.get_color_ = function() {
|
||||||
|
return beestat.series['compressor_' + this.child_metric_name_].color;
|
||||||
|
};
|
||||||
|
|
22
js/component/metric/balance_point/heat_1.js
Normal file
22
js/component/metric/balance_point/heat_1.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Balance Point for Heat Stage 1
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat ID.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.heat_1 = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.balance_point.heat_1, beestat.component.metric.balance_point);
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.heat_1.prototype.child_metric_name_ = 'heat_1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.heat_1.prototype.get_icon_ = function() {
|
||||||
|
return 'fire';
|
||||||
|
};
|
22
js/component/metric/balance_point/heat_2.js
Normal file
22
js/component/metric/balance_point/heat_2.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Balance Point for Heat Stage 2
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat ID.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.heat_2 = function(thermostat_id) {
|
||||||
|
this.thermostat_group_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.balance_point.heat_2, beestat.component.metric.balance_point);
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.heat_2.prototype.child_metric_name_ = 'heat_2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.heat_2.prototype.get_icon_ = function() {
|
||||||
|
return 'fire';
|
||||||
|
};
|
40
js/component/metric/balance_point/resist.js
Normal file
40
js/component/metric/balance_point/resist.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Balance Point for Resist
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat ID.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.resist = function(thermostat_id) {
|
||||||
|
this.thermostat_group_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.balance_point.resist, beestat.component.metric.balance_point);
|
||||||
|
|
||||||
|
beestat.component.metric.balance_point.resist.prototype.child_metric_name_ = 'resist';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.resist.prototype.get_icon_ = function() {
|
||||||
|
return 'resistor';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.resist.prototype.get_title_ = function() {
|
||||||
|
return 'Resist';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The color of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.balance_point.resist.prototype.get_color_ = function() {
|
||||||
|
return beestat.style.color.gray.dark;
|
||||||
|
};
|
32
js/component/metric/property.js
Normal file
32
js/component/metric/property.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Property metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.property, beestat.component.metric);
|
||||||
|
|
||||||
|
beestat.component.metric.property.prototype.parent_metric_name_ = 'property';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.prototype.get_title_ = function() {
|
||||||
|
return this.child_metric_name_.charAt(0).toUpperCase() + this.child_metric_name_.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The color of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.prototype.get_color_ = function() {
|
||||||
|
return beestat.style.color.purple.base;
|
||||||
|
};
|
||||||
|
|
50
js/component/metric/property/age.js
Normal file
50
js/component/metric/property/age.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Property age metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.age = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.property.age, beestat.component.metric.property);
|
||||||
|
|
||||||
|
beestat.component.metric.property.age.prototype.child_metric_name_ = 'age';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the units for this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The units for this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.age.prototype.get_units_ = function() {
|
||||||
|
return 'y';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.age.prototype.get_title_ = function() {
|
||||||
|
return 'Age';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.age.prototype.get_icon_ = function() {
|
||||||
|
return 'clock_outline';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max cutoff. This is used to set the chart min to max(median - 2 *
|
||||||
|
* stddev, max cutoff).
|
||||||
|
*
|
||||||
|
* @return {object} The cutoff value.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.age.prototype.get_cutoff_min_ = function() {
|
||||||
|
return 0;
|
||||||
|
};
|
72
js/component/metric/property/square_feet.js
Normal file
72
js/component/metric/property/square_feet.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Property square feet metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.square_feet = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.property.square_feet, beestat.component.metric.property);
|
||||||
|
|
||||||
|
beestat.component.metric.property.square_feet.prototype.child_metric_name_ = 'square_feet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the units for this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The units for this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.square_feet.prototype.get_units_ = function() {
|
||||||
|
return 'ft²';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the a formatter function that applies a transformation to the value.
|
||||||
|
*
|
||||||
|
* @return {mixed} A function that formats the string.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.square_feet.prototype.get_formatter_ = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return function(value, precision) {
|
||||||
|
return value.toLocaleString() + self.get_units_();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.square_feet.prototype.get_title_ = function() {
|
||||||
|
return 'Square Feet';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.square_feet.prototype.get_icon_ = function() {
|
||||||
|
return 'view_quilt';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max cutoff. This is used to set the chart min to max(median - 2 *
|
||||||
|
* stddev, max cutoff).
|
||||||
|
*
|
||||||
|
* @return {object} The cutoff value.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.square_feet.prototype.get_cutoff_min_ = function() {
|
||||||
|
return 500;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the counting interval for the histogram.
|
||||||
|
*
|
||||||
|
* @return {number} The interval.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.property.square_feet.prototype.get_interval_ = function() {
|
||||||
|
return 500;
|
||||||
|
};
|
50
js/component/metric/runtime_per_degree_day.js
Normal file
50
js/component/metric/runtime_per_degree_day.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Runtime per heating degree day metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat group.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.runtime_per_degree_day, beestat.component.metric);
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.prototype.parent_metric_name_ = 'runtime_per_degree_day';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the units for this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The units for this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.prototype.get_units_ = function() {
|
||||||
|
return 'm';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.prototype.get_title_ = function() {
|
||||||
|
return beestat.series['compressor_' + this.child_metric_name_].name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The color of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.prototype.get_color_ = function() {
|
||||||
|
return beestat.series['compressor_' + this.child_metric_name_].color;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max cutoff. This is used to set the chart min to max(median - 2 *
|
||||||
|
* stddev, max cutoff).
|
||||||
|
*
|
||||||
|
* @return {object} The cutoff value.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.prototype.get_cutoff_min_ = function() {
|
||||||
|
return 0;
|
||||||
|
};
|
22
js/component/metric/runtime_per_degree_day/cool_1.js
Normal file
22
js/component/metric/runtime_per_degree_day/cool_1.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Runtime / CDD for Cool Stage 1
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat ID.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.cool_1 = function(thermostat_id) {
|
||||||
|
this.thermostat_group_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.runtime_per_degree_day.cool_1, beestat.component.metric.runtime_per_degree_day);
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.cool_1.prototype.child_metric_name_ = 'cool_1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.cool_1.prototype.get_icon_ = function() {
|
||||||
|
return 'snowflake';
|
||||||
|
};
|
22
js/component/metric/runtime_per_degree_day/cool_2.js
Normal file
22
js/component/metric/runtime_per_degree_day/cool_2.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Runtime / CDD for Cool Stage 2
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat ID.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.cool_2 = function(thermostat_id) {
|
||||||
|
this.thermostat_group_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.runtime_per_degree_day.cool_2, beestat.component.metric.runtime_per_degree_day);
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.cool_2.prototype.child_metric_name_ = 'cool_2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.cool_2.prototype.get_icon_ = function() {
|
||||||
|
return 'snowflake';
|
||||||
|
};
|
22
js/component/metric/runtime_per_degree_day/heat_1.js
Normal file
22
js/component/metric/runtime_per_degree_day/heat_1.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Runtime / HDD for Heat Stage 1
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat ID.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.heat_1 = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.runtime_per_degree_day.heat_1, beestat.component.metric.runtime_per_degree_day);
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.heat_1.prototype.child_metric_name_ = 'heat_1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.heat_1.prototype.get_icon_ = function() {
|
||||||
|
return 'fire';
|
||||||
|
};
|
22
js/component/metric/runtime_per_degree_day/heat_2.js
Normal file
22
js/component/metric/runtime_per_degree_day/heat_2.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Runtime / HDD for Heat Stage 2
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat ID.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.heat_2 = function(thermostat_id) {
|
||||||
|
this.thermostat_group_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.runtime_per_degree_day.heat_2, beestat.component.metric.runtime_per_degree_day);
|
||||||
|
|
||||||
|
beestat.component.metric.runtime_per_degree_day.heat_2.prototype.child_metric_name_ = 'heat_2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.runtime_per_degree_day.heat_2.prototype.get_icon_ = function() {
|
||||||
|
return 'fire';
|
||||||
|
};
|
@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* Runtime per heating degree day metric.
|
|
||||||
*
|
|
||||||
* @param {number} thermostat_group_id The thermostat group.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day = function(thermostat_group_id) {
|
|
||||||
this.thermostat_group_id_ = thermostat_group_id;
|
|
||||||
|
|
||||||
beestat.component.metric.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.metric.runtime_per_heating_degree_day, beestat.component.metric);
|
|
||||||
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.rerender_on_breakpoint_ = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The title of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.get_title_ = function() {
|
|
||||||
return 'Runtime / HDD';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the icon of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The icon of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.get_icon_ = function() {
|
|
||||||
return 'fire';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the color of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The color of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.get_color_ = function() {
|
|
||||||
return beestat.series.compressor_heat_1.color;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the minimum value of this metric (within two standard deviations).
|
|
||||||
*
|
|
||||||
* @return {mixed} The minimum value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.get_min_ = function() {
|
|
||||||
var standard_deviation =
|
|
||||||
beestat.cache.data.metrics.runtime_per_heating_degree_day.standard_deviation;
|
|
||||||
return (beestat.cache.data.metrics.runtime_per_heating_degree_day.median - (standard_deviation * 2)).toFixed(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the maximum value of this metric (within two standard deviations).
|
|
||||||
*
|
|
||||||
* @return {mixed} The maximum value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.get_max_ = function() {
|
|
||||||
var standard_deviation =
|
|
||||||
beestat.cache.data.metrics.runtime_per_heating_degree_day.standard_deviation;
|
|
||||||
return (beestat.cache.data.metrics.runtime_per_heating_degree_day.median + (standard_deviation * 2)).toFixed(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of this metric.
|
|
||||||
*
|
|
||||||
* @return {mixed} The value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.get_value_ = function() {
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
this.thermostat_group_id_
|
|
||||||
];
|
|
||||||
// todo: store this explicitly on the profile so it doesn't have to be calculated in JS?
|
|
||||||
return (thermostat_group.profile.runtime.heat_1 /
|
|
||||||
thermostat_group.profile.degree_days.heat).toFixed(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a histogram between the min and max values of this metric.
|
|
||||||
*
|
|
||||||
* @return {array} The histogram.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.runtime_per_heating_degree_day.prototype.get_histogram_ = function() {
|
|
||||||
var histogram = [];
|
|
||||||
for (var value in beestat.cache.data.metrics.runtime_per_heating_degree_day.histogram) {
|
|
||||||
if (
|
|
||||||
value >= this.get_min_() &&
|
|
||||||
value <= this.get_max_()
|
|
||||||
) {
|
|
||||||
var count = beestat.cache.data.metrics.runtime_per_heating_degree_day.histogram[value];
|
|
||||||
histogram.push({
|
|
||||||
'value': value,
|
|
||||||
'count': count
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return histogram;
|
|
||||||
};
|
|
54
js/component/metric/setback.js
Normal file
54
js/component/metric/setback.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Setback metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.setback, beestat.component.metric);
|
||||||
|
|
||||||
|
beestat.component.metric.setback.prototype.parent_metric_name_ = 'setback';
|
||||||
|
|
||||||
|
beestat.component.metric.setback.prototype.is_temperature_ = true;
|
||||||
|
|
||||||
|
beestat.component.metric.setback.prototype.is_temperature_delta_ = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the units for this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The units for this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.prototype.get_units_ = function() {
|
||||||
|
return beestat.setting('temperature_unit');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.prototype.get_title_ = function() {
|
||||||
|
return this.child_metric_name_.charAt(0).toUpperCase() + this.child_metric_name_.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The color of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.prototype.get_color_ = function() {
|
||||||
|
return beestat.series['compressor_' + this.child_metric_name_ + '_1'].color;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max cutoff. This is used to set the chart min to max(median - 2 *
|
||||||
|
* stddev, max cutoff).
|
||||||
|
*
|
||||||
|
* @return {object} The cutoff value.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.prototype.get_cutoff_min_ = function() {
|
||||||
|
return 0;
|
||||||
|
};
|
22
js/component/metric/setback/cool.js
Normal file
22
js/component/metric/setback/cool.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Cool setback metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.cool = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.setback.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.setback.cool, beestat.component.metric.setback);
|
||||||
|
|
||||||
|
beestat.component.metric.setback.cool.prototype.child_metric_name_ = 'cool';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.cool.prototype.get_icon_ = function() {
|
||||||
|
return 'snowflake';
|
||||||
|
};
|
22
js/component/metric/setback/heat.js
Normal file
22
js/component/metric/setback/heat.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Heat setback metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.heat = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.setback.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.setback.heat, beestat.component.metric.setback);
|
||||||
|
|
||||||
|
beestat.component.metric.setback.heat.prototype.child_metric_name_ = 'heat';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setback.heat.prototype.get_icon_ = function() {
|
||||||
|
return 'fire';
|
||||||
|
};
|
43
js/component/metric/setpoint.js
Normal file
43
js/component/metric/setpoint.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Setpoint metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.setpoint, beestat.component.metric);
|
||||||
|
|
||||||
|
beestat.component.metric.setpoint.prototype.parent_metric_name_ = 'setpoint';
|
||||||
|
|
||||||
|
beestat.component.metric.setpoint.prototype.is_temperature_ = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the units for this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The units for this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint.prototype.get_units_ = function() {
|
||||||
|
return beestat.setting('temperature_unit');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the title of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The title of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint.prototype.get_title_ = function() {
|
||||||
|
return this.child_metric_name_.charAt(0).toUpperCase() + this.child_metric_name_.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The color of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint.prototype.get_color_ = function() {
|
||||||
|
return beestat.series['compressor_' + this.child_metric_name_ + '_1'].color;
|
||||||
|
};
|
||||||
|
|
22
js/component/metric/setpoint/cool.js
Normal file
22
js/component/metric/setpoint/cool.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Cool setpoint metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint.cool = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.setpoint.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.setpoint.cool, beestat.component.metric.setpoint);
|
||||||
|
|
||||||
|
beestat.component.metric.setpoint.cool.prototype.child_metric_name_ = 'cool';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint.cool.prototype.get_icon_ = function() {
|
||||||
|
return 'snowflake';
|
||||||
|
};
|
22
js/component/metric/setpoint/heat.js
Normal file
22
js/component/metric/setpoint/heat.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Heat setpoint metric.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint.heat = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
|
|
||||||
|
beestat.component.metric.setpoint.apply(this, arguments);
|
||||||
|
};
|
||||||
|
beestat.extend(beestat.component.metric.setpoint.heat, beestat.component.metric.setpoint);
|
||||||
|
|
||||||
|
beestat.component.metric.setpoint.heat.prototype.child_metric_name_ = 'heat';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon of this metric.
|
||||||
|
*
|
||||||
|
* @return {string} The icon of this metric.
|
||||||
|
*/
|
||||||
|
beestat.component.metric.setpoint.heat.prototype.get_icon_ = function() {
|
||||||
|
return 'fire';
|
||||||
|
};
|
@ -1,119 +0,0 @@
|
|||||||
/**
|
|
||||||
* Cool setpoint metric.
|
|
||||||
*
|
|
||||||
* @param {number} thermostat_group_id The thermostat group.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool = function(thermostat_group_id) {
|
|
||||||
this.thermostat_group_id_ = thermostat_group_id;
|
|
||||||
|
|
||||||
beestat.component.metric.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.metric.setpoint_cool, beestat.component.metric);
|
|
||||||
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.rerender_on_breakpoint_ = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The title of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.get_title_ = function() {
|
|
||||||
return 'Cool Setpoint';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the icon of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The icon of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.get_icon_ = function() {
|
|
||||||
return 'snowflake';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the color of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The color of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.get_color_ = function() {
|
|
||||||
return beestat.series.compressor_cool_1.color;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the minimum value of this metric (within two standard deviations).
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {mixed} The minimum value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.get_min_ = function(units) {
|
|
||||||
var standard_deviation =
|
|
||||||
beestat.cache.data.metrics.setpoint_cool.standard_deviation;
|
|
||||||
return beestat.temperature({
|
|
||||||
'temperature': beestat.cache.data.metrics.setpoint_cool.median - (standard_deviation * 2),
|
|
||||||
'round': 0,
|
|
||||||
'units': units
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the maximum value of this metric (within two standard deviations).
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {mixed} The maximum value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.get_max_ = function(units) {
|
|
||||||
var standard_deviation =
|
|
||||||
beestat.cache.data.metrics.setpoint_cool.standard_deviation;
|
|
||||||
return beestat.temperature({
|
|
||||||
'temperature': beestat.cache.data.metrics.setpoint_cool.median + (standard_deviation * 2),
|
|
||||||
'round': 0,
|
|
||||||
'units': units
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of this metric.
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {mixed} The value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.get_value_ = function(units) {
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
this.thermostat_group_id_
|
|
||||||
];
|
|
||||||
return beestat.temperature({
|
|
||||||
'temperature': thermostat_group.profile.setpoint.cool,
|
|
||||||
'units': units
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a histogram between the min and max values of this metric.
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {array} The histogram.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_cool.prototype.get_histogram_ = function(units) {
|
|
||||||
var histogram = [];
|
|
||||||
for (var temperature in beestat.cache.data.metrics.setpoint_cool.histogram) {
|
|
||||||
if (
|
|
||||||
temperature >= this.get_min_(units) &&
|
|
||||||
temperature <= this.get_max_(units)
|
|
||||||
) {
|
|
||||||
var count = beestat.cache.data.metrics.setpoint_cool.histogram[temperature];
|
|
||||||
histogram.push({
|
|
||||||
'value': beestat.temperature(temperature),
|
|
||||||
'count': count
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return histogram;
|
|
||||||
};
|
|
@ -1,119 +0,0 @@
|
|||||||
/**
|
|
||||||
* Heat setpoint metric.
|
|
||||||
*
|
|
||||||
* @param {number} thermostat_group_id The thermostat group.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat = function(thermostat_group_id) {
|
|
||||||
this.thermostat_group_id_ = thermostat_group_id;
|
|
||||||
|
|
||||||
beestat.component.metric.apply(this, arguments);
|
|
||||||
};
|
|
||||||
beestat.extend(beestat.component.metric.setpoint_heat, beestat.component.metric);
|
|
||||||
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.rerender_on_breakpoint_ = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the title of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The title of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.get_title_ = function() {
|
|
||||||
return 'Heat Setpoint';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the icon of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The icon of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.get_icon_ = function() {
|
|
||||||
return 'fire';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the color of this metric.
|
|
||||||
*
|
|
||||||
* @return {string} The color of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.get_color_ = function() {
|
|
||||||
return beestat.series.compressor_heat_1.color;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the minimum value of this metric (within two standard deviations).
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {mixed} The minimum value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.get_min_ = function(units) {
|
|
||||||
var standard_deviation =
|
|
||||||
beestat.cache.data.metrics.setpoint_heat.standard_deviation;
|
|
||||||
return beestat.temperature({
|
|
||||||
'temperature': beestat.cache.data.metrics.setpoint_heat.median - (standard_deviation * 2),
|
|
||||||
'round': 0,
|
|
||||||
'units': units
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the maximum value of this metric (within two standard deviations).
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {mixed} The maximum value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.get_max_ = function(units) {
|
|
||||||
var standard_deviation =
|
|
||||||
beestat.cache.data.metrics.setpoint_heat.standard_deviation;
|
|
||||||
return beestat.temperature({
|
|
||||||
'temperature': beestat.cache.data.metrics.setpoint_heat.median + (standard_deviation * 2),
|
|
||||||
'round': 0,
|
|
||||||
'units': units
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of this metric.
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {mixed} The value of this metric.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.get_value_ = function(units) {
|
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
this.thermostat_group_id_
|
|
||||||
];
|
|
||||||
return beestat.temperature({
|
|
||||||
'temperature': thermostat_group.profile.setpoint.heat,
|
|
||||||
'units': units
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a histogram between the min and max values of this metric.
|
|
||||||
*
|
|
||||||
* @param {boolean} units Whether or not to return a numerical value or a
|
|
||||||
* string with units.
|
|
||||||
*
|
|
||||||
* @return {array} The histogram.
|
|
||||||
*/
|
|
||||||
beestat.component.metric.setpoint_heat.prototype.get_histogram_ = function(units) {
|
|
||||||
var histogram = [];
|
|
||||||
for (var temperature in beestat.cache.data.metrics.setpoint_heat.histogram) {
|
|
||||||
if (
|
|
||||||
temperature >= this.get_min_(units) &&
|
|
||||||
temperature <= this.get_max_(units)
|
|
||||||
) {
|
|
||||||
var count = beestat.cache.data.metrics.setpoint_heat.histogram[temperature];
|
|
||||||
histogram.push({
|
|
||||||
'value': beestat.temperature(temperature),
|
|
||||||
'count': count
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return histogram;
|
|
||||||
};
|
|
@ -59,7 +59,7 @@ beestat.component.modal.prototype.decorate_ = function() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'(max-width: 900px)': {
|
'(max-width: 900px)': {
|
||||||
'max-height': 'calc(100vh - ' + (beestat.style.size.gutter * 2) + 'px)',
|
'max-height': 'calc(100vh - ' + (beestat.style.size.gutter * 2) + 'px)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Change system type.
|
* Change system type.
|
||||||
|
*
|
||||||
|
* @param {number} thermostat_id The thermostat_id this card is displaying
|
||||||
|
* data for.
|
||||||
*/
|
*/
|
||||||
beestat.component.modal.change_system_type = function() {
|
beestat.component.modal.change_system_type = function(thermostat_id) {
|
||||||
|
this.thermostat_id_ = thermostat_id;
|
||||||
beestat.component.modal.apply(this, arguments);
|
beestat.component.modal.apply(this, arguments);
|
||||||
};
|
};
|
||||||
beestat.extend(beestat.component.modal.change_system_type, beestat.component.modal);
|
beestat.extend(beestat.component.modal.change_system_type, beestat.component.modal);
|
||||||
@ -9,12 +13,9 @@ beestat.extend(beestat.component.modal.change_system_type, beestat.component.mod
|
|||||||
beestat.component.modal.change_system_type.prototype.decorate_contents_ = function(parent) {
|
beestat.component.modal.change_system_type.prototype.decorate_contents_ = function(parent) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
var thermostat_group = beestat.cache.thermostat_group[
|
|
||||||
thermostat.thermostat_group_id
|
|
||||||
];
|
|
||||||
|
|
||||||
parent.appendChild($.createElement('p').innerHTML('What type of HVAC system do you have? This applies to all thermostats in this group. System types that beestat detected are indicated.'));
|
parent.appendChild($.createElement('p').innerHTML('What type of HVAC system do you have? System types that beestat detected are indicated.'));
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
'heat': [
|
'heat': [
|
||||||
@ -108,7 +109,7 @@ beestat.component.modal.change_system_type.prototype.get_title_ = function() {
|
|||||||
* @return {[beestat.component.button]} The buttons.
|
* @return {[beestat.component.button]} The buttons.
|
||||||
*/
|
*/
|
||||||
beestat.component.modal.change_system_type.prototype.get_buttons_ = function() {
|
beestat.component.modal.change_system_type.prototype.get_buttons_ = function() {
|
||||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@ -132,26 +133,42 @@ beestat.component.modal.change_system_type.prototype.get_buttons_ = function() {
|
|||||||
.set_background_hover_color()
|
.set_background_hover_color()
|
||||||
.removeEventListener('click');
|
.removeEventListener('click');
|
||||||
|
|
||||||
|
// Delete from the cache to trigger the metrics loading screen
|
||||||
|
beestat.cache.delete('data.metrics');
|
||||||
|
|
||||||
new beestat.api()
|
new beestat.api()
|
||||||
.add_call(
|
.add_call(
|
||||||
'thermostat_group',
|
'thermostat',
|
||||||
'update_system_types',
|
'set_reported_system_types',
|
||||||
{
|
{
|
||||||
'thermostat_group_id': thermostat.thermostat_group_id,
|
'thermostat_id': thermostat.thermostat_id,
|
||||||
'system_types': self.selected_types_
|
'system_types': self.selected_types_
|
||||||
},
|
},
|
||||||
'update_system_types'
|
'set_reported_system_types'
|
||||||
|
)
|
||||||
|
.add_call(
|
||||||
|
'thermostat',
|
||||||
|
'read_id',
|
||||||
|
{
|
||||||
|
'attributes': {
|
||||||
|
'inactive': 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'thermostat'
|
||||||
|
)
|
||||||
|
.add_call(
|
||||||
|
'thermostat',
|
||||||
|
'get_metrics',
|
||||||
|
{
|
||||||
|
'thermostat_id': self.thermostat_id_,
|
||||||
|
'attributes': beestat.comparisons.get_attributes()
|
||||||
|
},
|
||||||
|
'metrics'
|
||||||
)
|
)
|
||||||
.add_call('thermostat', 'read_id', {}, 'thermostat')
|
|
||||||
.add_call('thermostat_group', 'read_id', {}, 'thermostat_group')
|
|
||||||
.set_callback(function(response) {
|
.set_callback(function(response) {
|
||||||
// Update the cache.
|
// Update the cache.
|
||||||
beestat.cache.set('thermostat', response.thermostat);
|
beestat.cache.set('thermostat', response.thermostat);
|
||||||
beestat.cache.set('thermostat_group', response.thermostat_group);
|
beestat.cache.set('data.metrics', response.metrics);
|
||||||
|
|
||||||
// Re-run comparison scores as they are invalid for the new system
|
|
||||||
// type.
|
|
||||||
beestat.comparisons.get_comparison_scores();
|
|
||||||
|
|
||||||
// Close the modal.
|
// Close the modal.
|
||||||
self.dispose();
|
self.dispose();
|
||||||
|
@ -35,13 +35,10 @@ beestat.component.modal.change_thermostat.prototype.decorate_contents_ = functio
|
|||||||
|
|
||||||
beestat.component.modal.change_thermostat.prototype.decorate_thermostat_ = function(parent, thermostat_id) {
|
beestat.component.modal.change_thermostat.prototype.decorate_thermostat_ = function(parent, thermostat_id) {
|
||||||
var thermostat = beestat.cache.thermostat[thermostat_id];
|
var thermostat = beestat.cache.thermostat[thermostat_id];
|
||||||
var ecobee_thermostat = beestat.cache.ecobee_thermostat[
|
|
||||||
thermostat.ecobee_thermostat_id
|
|
||||||
];
|
|
||||||
|
|
||||||
var container_height = 60;
|
var container_height = 60;
|
||||||
var gutter = beestat.style.size.gutter / 2;
|
var gutter = beestat.style.size.gutter / 2;
|
||||||
var thermostat_height = container_height - (gutter * 2)
|
var thermostat_height = container_height - (gutter * 2);
|
||||||
|
|
||||||
var container = $.createElement('div')
|
var container = $.createElement('div')
|
||||||
.style({
|
.style({
|
||||||
@ -52,7 +49,7 @@ beestat.component.modal.change_thermostat.prototype.decorate_thermostat_ = funct
|
|||||||
'user-select': 'none'
|
'user-select': 'none'
|
||||||
});
|
});
|
||||||
|
|
||||||
if(thermostat_id == beestat.cache.thermostat[beestat.setting('thermostat_id')].thermostat_id) {
|
if (thermostat_id == beestat.cache.thermostat[beestat.setting('thermostat_id')].thermostat_id) {
|
||||||
container.style({
|
container.style({
|
||||||
'background': '#4b6584',
|
'background': '#4b6584',
|
||||||
'color': '#fff'
|
'color': '#fff'
|
||||||
|
35
js/js.php
35
js/js.php
@ -36,13 +36,14 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
|||||||
echo '<script src="/js/beestat/runtime_thermostat.js"></script>' . PHP_EOL;
|
echo '<script src="/js/beestat/runtime_thermostat.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/beestat/runtime_sensor.js"></script>' . PHP_EOL;
|
echo '<script src="/js/beestat/runtime_sensor.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/beestat/sensor.js"></script>' . PHP_EOL;
|
echo '<script src="/js/beestat/sensor.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/beestat/requestor.js"></script>' . PHP_EOL;
|
||||||
|
|
||||||
// Layer
|
// Layer
|
||||||
echo '<script src="/js/layer.js"></script>' . PHP_EOL;
|
echo '<script src="/js/layer.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/layer/load.js"></script>' . PHP_EOL;
|
echo '<script src="/js/layer/load.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/layer/dashboard.js"></script>' . PHP_EOL;
|
echo '<script src="/js/layer/detail.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/layer/comparisons.js"></script>' . PHP_EOL;
|
echo '<script src="/js/layer/compare.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/layer/sensors.js"></script>' . PHP_EOL;
|
echo '<script src="/js/layer/analyze.js"></script>' . PHP_EOL;
|
||||||
|
|
||||||
// Component
|
// Component
|
||||||
echo '<script src="/js/component.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component.js"></script>' . PHP_EOL;
|
||||||
@ -51,6 +52,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
|||||||
echo '<script src="/js/component/card.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/runtime_thermostat_summary.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/runtime_thermostat_summary.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/alerts.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/alerts.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/card/compare_notification.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/comparison_settings.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/comparison_settings.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/early_access.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/early_access.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/demo.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/demo.js"></script>' . PHP_EOL;
|
||||||
@ -59,19 +61,13 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
|||||||
echo '<script src="/js/component/card/patreon.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/patreon.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/runtime_thermostat_detail.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/runtime_thermostat_detail.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/runtime_sensor_detail.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/runtime_sensor_detail.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/score.js"></script>' . PHP_EOL;
|
|
||||||
echo '<script src="/js/component/card/score/cool.js"></script>' . PHP_EOL;
|
|
||||||
echo '<script src="/js/component/card/score/heat.js"></script>' . PHP_EOL;
|
|
||||||
echo '<script src="/js/component/card/score/resist.js"></script>' . PHP_EOL;
|
|
||||||
echo '<script src="/js/component/card/sensors.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/sensors.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/system.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/system.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/temperature_profiles.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/temperature_profiles.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/card/temperature_profiles_new.js"></script>' . PHP_EOL;
|
|
||||||
echo '<script src="/js/component/card/metrics.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/card/metrics.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/chart.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/chart.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/chart/runtime_thermostat_summary.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/chart/runtime_thermostat_summary.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/chart/temperature_profiles.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/chart/temperature_profiles.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/chart/temperature_profiles_new.js"></script>' . PHP_EOL;
|
|
||||||
echo '<script src="/js/component/chart/runtime_thermostat_detail_temperature.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/chart/runtime_thermostat_detail_temperature.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/chart/runtime_thermostat_detail_equipment.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/chart/runtime_thermostat_detail_equipment.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/chart/runtime_sensor_detail_temperature.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/chart/runtime_sensor_detail_temperature.js"></script>' . PHP_EOL;
|
||||||
@ -104,9 +100,24 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
|
|||||||
echo '<script src="/js/component/button_group.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/button_group.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/title.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/title.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/metric.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/metric.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/metric/setpoint_heat.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/metric/setpoint.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/metric/setpoint_cool.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/metric/setpoint/heat.js"></script>' . PHP_EOL;
|
||||||
echo '<script src="/js/component/metric/runtime_per_heating_degree_day.js"></script>' . PHP_EOL;
|
echo '<script src="/js/component/metric/setpoint/cool.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/setback.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/setback/heat.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/setback/cool.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/runtime_per_degree_day.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/runtime_per_degree_day/heat_1.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/runtime_per_degree_day/heat_2.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/runtime_per_degree_day/cool_1.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/runtime_per_degree_day/cool_2.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/balance_point.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/balance_point/heat_1.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/balance_point/heat_2.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/balance_point/resist.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/property.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/property/age.js"></script>' . PHP_EOL;
|
||||||
|
echo '<script src="/js/component/metric/property/square_feet.js"></script>' . PHP_EOL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
echo '<script src="/js/beestat.js?' . $setting->get('commit') . '"></script>' . PHP_EOL;
|
echo '<script src="/js/beestat.js?' . $setting->get('commit') . '"></script>' . PHP_EOL;
|
||||||
|
@ -66,13 +66,6 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
|
|||||||
'thermostat'
|
'thermostat'
|
||||||
);
|
);
|
||||||
|
|
||||||
api.add_call(
|
|
||||||
'thermostat_group',
|
|
||||||
'read_id',
|
|
||||||
{},
|
|
||||||
'thermostat_group'
|
|
||||||
);
|
|
||||||
|
|
||||||
api.add_call(
|
api.add_call(
|
||||||
'sensor',
|
'sensor',
|
||||||
'read_id',
|
'read_id',
|
||||||
@ -137,7 +130,6 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beestat.cache.set('thermostat', response.thermostat);
|
beestat.cache.set('thermostat', response.thermostat);
|
||||||
beestat.cache.set('thermostat_group', response.thermostat_group);
|
|
||||||
beestat.cache.set('sensor', response.sensor);
|
beestat.cache.set('sensor', response.sensor);
|
||||||
|
|
||||||
beestat.cache.set('ecobee_thermostat', response.ecobee_thermostat);
|
beestat.cache.set('ecobee_thermostat', response.ecobee_thermostat);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user