1
0
mirror of https://github.com/beestat/app.git synced 2025-05-23 18:04:14 -04:00
Loading now doesn't break if the sync fails, and as a bonus that made it trivial to display whether or not ecobee is down.
This commit is contained in:
Jon Ziebell 2020-01-29 22:39:05 -05:00
parent deaf81e214
commit a6564305b0
15 changed files with 191 additions and 113 deletions

View File

@ -857,7 +857,7 @@ final class database extends \mysqli {
');
$row = $result->fetch_assoc();
if($row['lock'] !== 1) {
throw new \Exception('Could not get lock.', 1209);
throw new exception('Could not get lock.', 1209);
}
}

View File

@ -33,6 +33,8 @@ class runtime extends cora\api {
*
* @param int $thermostat_id Optional thermostat_id to sync. If not set will
* sync all thermostats attached to this user.
*
* @return boolean true if the sync ran, false if not.
*/
public function sync($thermostat_id = null) {
// Skip this for the demo
@ -42,54 +44,58 @@ class runtime extends cora\api {
set_time_limit(0);
if($thermostat_id === null) {
$thermostat_ids = array_keys(
$this->api(
'thermostat',
'read_id',
[
'attributes' => [
'inactive' => 0
try {
if($thermostat_id === null) {
$thermostat_ids = array_keys(
$this->api(
'thermostat',
'read_id',
[
'attributes' => [
'inactive' => 0
]
]
]
)
);
} else {
$this->user_lock($thermostat_id);
$thermostat_ids = [$thermostat_id];
}
foreach($thermostat_ids as $thermostat_id) {
// Get a lock to ensure that this is not invoked more than once at a time
// per thermostat.
$lock_name = 'runtime->sync(' . $thermostat_id . ')';
$this->database->get_lock($lock_name);
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
if(
$thermostat['sync_begin'] === $thermostat['first_connected'] ||
(
$thermostat['sync_begin'] !== null &&
strtotime($thermostat['sync_begin']) <= strtotime('-1 year')
)
) {
$this->sync_forwards($thermostat_id);
)
);
} else {
$this->sync_backwards($thermostat_id);
$this->user_lock($thermostat_id);
$thermostat_ids = [$thermostat_id];
}
// If only syncing one thermostat this will delay the sync of the other
// thermostat. Not a huge deal, just FYI.
$this->api(
'user',
'update_sync_status',
[
'key' => 'runtime'
]
);
foreach($thermostat_ids as $thermostat_id) {
// Get a lock to ensure that this is not invoked more than once at a time
// per thermostat.
$lock_name = 'runtime->sync(' . $thermostat_id . ')';
$this->database->get_lock($lock_name);
$this->database->release_lock($lock_name);
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
if(
$thermostat['sync_begin'] === $thermostat['first_connected'] ||
(
$thermostat['sync_begin'] !== null &&
strtotime($thermostat['sync_begin']) <= strtotime('-1 year')
)
) {
$this->sync_forwards($thermostat_id);
} else {
$this->sync_backwards($thermostat_id);
}
// If only syncing one thermostat this will delay the sync of the other
// thermostat. Not a huge deal, just FYI.
$this->api(
'user',
'update_sync_status',
[
'key' => 'runtime'
]
);
$this->database->release_lock($lock_name);
}
} catch(cora\exception $e) {
return false;
}
}

View File

@ -46,9 +46,10 @@ class sensor extends cora\crud {
}
/**
* Sync all sensors connected to this account. Once Nest support is
* added this will need to check for all connected accounts and run the
* appropriate ones.
* Sync all sensors for the current user. If we fail to get a lock, fail
* silently (catch the exception) and just return false.
*
* @return boolean true if the sync ran, false if not.
*/
public function sync() {
// Skip this for the demo
@ -56,20 +57,24 @@ class sensor extends cora\crud {
return;
}
$lock_name = 'sensor->sync(' . $this->session->get_user_id() . ')';
$this->database->get_lock($lock_name);
try {
$lock_name = 'sensor->sync(' . $this->session->get_user_id() . ')';
$this->database->get_lock($lock_name);
$this->api('ecobee_sensor', 'sync');
$this->api('ecobee_sensor', 'sync');
$this->api(
'user',
'update_sync_status',
[
'key' => 'sensor'
]
);
$this->api(
'user',
'update_sync_status',
[
'key' => 'sensor'
]
);
$this->database->release_lock($lock_name);
$this->database->release_lock($lock_name);
} catch(cora\exception $e) {
return false;
}
}
}

View File

@ -22,26 +22,35 @@ class thermostat extends cora\crud {
];
/**
* Sync all thermostats for the current user with their associated service.
* Sync all thermostats for the current user. If we fail to get a lock, fail
* silently (catch the exception) and just return false.
*
* @return boolean true if the sync ran, false if not.
*/
public function sync() {
// Skip this for the demo
if($this->setting->is_demo() === true) {
return;
return true;
}
$lock_name = 'thermostat->sync(' . $this->session->get_user_id() . ')';
$this->database->get_lock($lock_name);
try {
$lock_name = 'thermostat->sync(' . $this->session->get_user_id() . ')';
$this->database->get_lock($lock_name);
$this->api('ecobee_thermostat', 'sync');
$this->api('ecobee_thermostat', 'sync');
$this->api(
'user',
'update_sync_status',
['key' => 'thermostat']
);
$this->api(
'user',
'update_sync_status',
['key' => 'thermostat']
);
$this->database->release_lock($lock_name);
$this->database->release_lock($lock_name);
return true;
} catch(cora\exception $e) {
return false;
}
}
/**

View File

@ -20,7 +20,6 @@ beestat.ecobee_thermostat_models = {
'vulcanSmart': 'SmartThermostat'
};
/**
* Get a default value for an argument if it is not currently set.
*
@ -106,16 +105,6 @@ beestat.get_thermostat_color = function(thermostat_id) {
return beestat.style.color.bluegray.dark;
};
/**
* Get the current user.
*
* @return {object}
*/
beestat.get_user = function() {
var user_id = Object.keys(beestat.cache.user)[0];
return beestat.cache.user[user_id];
};
// Register service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {

View File

@ -178,8 +178,6 @@ beestat.api.prototype.load_ = function(response_text) {
) {
window.location.href = '/';
return;
} else if (response.data && response.data.error_code === 1209) {
// Could not get lock; safe to ignore as that means sync is running.
} else if (response.success !== true) {
beestat.error(
'API call failed: ' + response.data.error_message,

25
js/beestat/ecobee.js Normal file
View File

@ -0,0 +1,25 @@
beestat.ecobee = {};
/**
* Check to see if ecobee is down. If so, render the footer component.
*/
beestat.ecobee.notify_if_down = function() {
if (
beestat.cache !== undefined &&
beestat.cache.thermostat !== undefined &&
beestat.user.get() !== undefined
) {
var last_update = moment.utc(beestat.user.get().sync_status.thermostat);
var down = last_update.isBefore(moment().subtract(15, 'minutes'));
if (beestat.ecobee.down_notification_ === undefined) {
beestat.ecobee.down_notification_ = new beestat.component.down_notification();
}
if (down === true) {
beestat.ecobee.down_notification_.render($('body'));
} else {
beestat.ecobee.down_notification_.dispose();
}
}
};

View File

@ -95,6 +95,8 @@ beestat.poll = function() {
beestat.cache.set('ecobee_sensor', response.ecobee_sensor);
beestat.enable_poll();
beestat.dispatcher.dispatchEvent('poll');
beestat.ecobee.notify_if_down();
});
api.send();

View File

@ -6,8 +6,6 @@ beestat.component = function() {
// Give every component a state object to use for storing data.
this.state_ = {};
// this.render_count_ = 0;
this.layer_ = beestat.current_layer;
if (this.rerender_on_breakpoint_ === true) {
@ -28,26 +26,27 @@ beestat.extend(beestat.component, rocket.EventTarget);
* @return {beestat.component} This
*/
beestat.component.prototype.render = function(parent) {
var self = this;
if (this.rendered_ === false) {
var self = this;
if (parent !== undefined) {
this.component_container_ = $.createElement('div')
.style('position', 'relative');
this.decorate_(this.component_container_);
parent.appendChild(this.component_container_);
} else {
this.decorate_();
if (parent !== undefined) {
this.component_container_ = $.createElement('div')
.style('position', 'relative');
this.decorate_(this.component_container_);
parent.appendChild(this.component_container_);
} else {
this.decorate_();
}
// The element should now exist on the DOM.
setTimeout(function() {
self.dispatchEvent('render');
}, 0);
// The render function was called.
this.rendered_ = true;
}
// The element should now exist on the DOM.
setTimeout(function() {
self.dispatchEvent('render');
}, 0);
// The render function was called.
this.rendered_ = true;
// this.render_count_++;
return this;
};
@ -71,21 +70,20 @@ beestat.component.prototype.rerender = function() {
setTimeout(function() {
self.dispatchEvent('render');
}, 0);
// this.render_count_++;
return this;
}
return this;
};
/**
* Remove this component from the page.
*/
beestat.component.prototype.dispose = function() {
var child = this.component_container_.parentNode();
var parent = child.parentNode();
parent.removeChild(child);
this.rendered_ = false;
if (this.rendered_ === true) {
var child = this.component_container_;
var parent = child.parentNode();
parent.removeChild(child);
this.rendered_ = false;
}
};
beestat.component.prototype.decorate_ = function() {

View File

@ -0,0 +1,34 @@
/**
* Ecobee is down!
*/
beestat.component.down_notification = function() {
beestat.component.apply(this, arguments);
};
beestat.extend(beestat.component.down_notification, beestat.component);
beestat.component.down_notification.prototype.rerender_on_breakpoint_ = false;
/**
* Decorate a floating banner at the bottom of the page.
*
* @param {rocket.Elements} parent
*/
beestat.component.down_notification.prototype.decorate_ = function(parent) {
var div = $.createElement('div');
div.style({
'position': 'fixed',
'bottom': '0px',
'left': '0px',
'width': '100%',
'text-align': 'center',
'padding-left': beestat.style.size.gutter,
'padding-right': beestat.style.size.gutter,
'background': beestat.style.color.red.dark
});
var last_update = moment.utc(beestat.user.get().sync_status.thermostat).local()
.format('h:m a');
div.appendChild($.createElement('p').innerText('Ecobee seems to be down. Your data will update as soon as possible. Last update was at ' + last_update + '.'));
parent.appendChild(div);
};

View File

@ -19,7 +19,6 @@ beestat.component.menu.prototype.decorate_ = function(parent) {
.set_bubble_text(this.bubble_text_)
.set_bubble_color(this.bubble_color_)
.set_text_color('#fff')
// .set_background_hover_color(beestat.style.color.bluegray.light)
.set_background_hover_color('rgba(255, 255, 255, 0.1')
.addEventListener('click', function() {
// Did I just try to open the same menu as last time?
@ -41,12 +40,20 @@ beestat.component.menu.prototype.decorate_ = function(parent) {
* Close this menu by hiding the container and removing the event listeners.
*/
beestat.component.menu.prototype.dispose = function() {
var self = this;
beestat.component.menu.open_menu.rendered_ = false;
this.rendered_ = false;
if (beestat.component.menu.open_menu !== undefined) {
var container = beestat.component.menu.open_menu.container_;
container.style('transform', 'scale(0)');
delete beestat.component.menu.open_menu;
setTimeout(function() {
self.menu_items_.forEach(function(menu_item) {
menu_item.dispose();
});
container.parentNode().removeChild(container);
}, 200);
}

View File

@ -34,7 +34,7 @@ beestat.component.menu_item.prototype.decorate_ = function(parent) {
.render(parent);
// Events
parent.addEventListener('mouseenter', function() {
parent.addEventListener('mouseenter', function() {
parent.style({
'background': beestat.style.color.blue.light,
'color': '#fff'
@ -48,7 +48,7 @@ beestat.component.menu_item.prototype.decorate_ = function(parent) {
});
parent.addEventListener('click', function() {
self.menu_.dispose();
if(self.callback_ !== undefined) {
if (self.callback_ !== undefined) {
self.callback_();
}
});

View File

@ -31,6 +31,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/beestat/highcharts.js"></script>' . PHP_EOL;
echo '<script src="/js/beestat/get_sync_progress.js"></script>' . PHP_EOL;
echo '<script src="/js/beestat/user.js"></script>' . PHP_EOL;
echo '<script src="/js/beestat/ecobee.js"></script>' . PHP_EOL;
// Layer
echo '<script src="/js/layer.js"></script>' . PHP_EOL;
@ -42,6 +43,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
// Component
echo '<script src="/js/component.js"></script>' . PHP_EOL;
echo '<script src="/js/component/alert.js"></script>' . PHP_EOL;
echo '<script src="/js/component/down_notification.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/alerts.js"></script>' . PHP_EOL;

View File

@ -22,6 +22,8 @@ beestat.layer.prototype.render = function() {
body.innerHTML('');
body.appendChild(container);
beestat.ecobee.notify_if_down();
};
beestat.layer.prototype.decorate_ = function(parent) {

View File

@ -128,7 +128,6 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
);
api.set_callback(function(response) {
beestat.cache.set('user', response.user);
// Rollbar isn't defined on dev.
@ -217,6 +216,8 @@ beestat.layer.load.prototype.decorate_ = function(parent) {
(new beestat.layer.dashboard()).render();
beestat.ecobee.notify_if_down();
/*
* If never seen an announcement, or if there is an unread important
* announcement, show the modal.