Skip to content
This repository has been archived by the owner on Jul 10, 2024. It is now read-only.

Commit

Permalink
Fix concurrent user issue (#8)
Browse files Browse the repository at this point in the history
* Sign out of website after intent completes
* Fix security warning in dependencies
* Fix stopping callback
  • Loading branch information
Matt Turner authored Mar 28, 2019
1 parent 6280996 commit 32d216a
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 171 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
],
"linebreak-style": [
"error",
"windows"
"linux"
],
"quotes": [
"error",
Expand Down
6 changes: 5 additions & 1 deletion aws/HoldStrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,20 @@ class HoldStrategy {
}
console.log(`Stopping hold ${executionId}...`);
let params = {
cause: 'Superceded by user request',
executionArn: executionId
};
try {
let currentExecution = await this._stepFunctions.describeExecution(params).promise();
if (currentExecution.status === 'RUNNING') {
params = {
cause: 'Superceded by user request',
executionArn: executionId
};
await this._stepFunctions.stopExecution(params).promise();
}
} catch (error) {
console.log('Execution could not be stopped');
console.log(error);
}
}

Expand Down
1 change: 1 addition & 0 deletions aws/ThermostatRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ThermostatRepository {

let response = await this.client.get(params).promise();
if (response.Item) {
console.log(`Found thermostat for user ${userId} with username ${response.Item.options.username}`);
return response.Item;
}
return null;
Expand Down
31 changes: 19 additions & 12 deletions aws/lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ module.change_code = 0;

let app = new alexa.app('boiler');

const controlService = (userId) => {
let context = { userId };
const controlService = (request) => {
let userId = request.userId || request.data.session.user.userId;
let source = 'user';
if (!request.data.context) {
source = 'callback';
}
let context = { userId: userId, source: source };
console.log(`Creating context for source: ${context.source}, user: ${context.userId}...`);
let repository;
if (process.env.THERMOSTAT_REPOSITORY === 'dynamodb') {
repository = new DynamodbThermostatRepository();
Expand Down Expand Up @@ -44,7 +50,7 @@ const say = (response, messages) => {

app.launch(async (request, response) => {
console.log('Launching...');
let service = controlService(request.userId);
let service = controlService(request);
try {
let messages = await service.launch();
say(response, messages);
Expand All @@ -57,7 +63,7 @@ app.launch(async (request, response) => {
app.intent('TempIntent', {
'utterances': ['what the temperature is', 'the temperature', 'how hot it is']
}, async (request, response) => {
let service = controlService(request.userId);
let service = controlService(request);
try {
let messages = await service.status();
say(response, messages);
Expand All @@ -70,7 +76,7 @@ app.intent('TempIntent', {
app.intent('TurnUpIntent', {
'utterances': ['to increase', 'to turn up', 'set warmer', 'set higher']
}, async (request, response) => {
let service = controlService(request.userId);
let service = controlService(request);
try {
let messages = await service.turnUp();
say(response, messages);
Expand All @@ -83,7 +89,7 @@ app.intent('TurnUpIntent', {
app.intent('TurnDownIntent', {
'utterances': ['to decrease', 'to turn down', 'set cooler', 'set lower']
}, async (request, response) => {
let service = controlService(request.userId);
let service = controlService(request);
try {
let messages = await service.turnDown();
say(response, messages);
Expand All @@ -99,9 +105,11 @@ app.intent('SetTempIntent', {
},
'utterances': ['to set to {temp} degrees', 'to set the temperature to {temp} degrees', 'to set the temp to {temp} degrees']
}, async (request, response) => {
let service = controlService(request.userId);
let service = controlService(request);
try {
let messages = await service.setTemperature(request.slot('temp'), request.slot('duration'));
let targetTemp = parseFloat(request.slot('temp'));
let optionalDuration = request.slot('duration', null);
let messages = await service.setTemperature(targetTemp, optionalDuration);
say(response, messages);
} catch (e) {
say(response, e);
Expand All @@ -114,12 +122,11 @@ app.intent('TurnIntent', {
'onoff': 'ONOFF'
},
'utterances': ['to turn {onoff}', 'to turn heating {onoff}', 'to turn the heating {onoff}']
}, async (request, response) => {
}, async (request, response) => {
let onOff = request.slot('onoff');
let duration = request.slot('duration');
// this could be a callback from a step function
let userId = request.userId || request.data.session.user.userId;
let service = controlService(userId);
let service = controlService(request);
try {
let messages = await service.turn(onOff, duration);
say(response, messages);
Expand All @@ -143,7 +150,7 @@ app.intent('AMAZON.StopIntent', {
'slots': {},
'utterances': []
}, async (request, response) => {
let service = controlService(request.userId);
let service = controlService(request);
try {
let messages = await service.turn('off');
say(response, messages);
Expand Down
193 changes: 108 additions & 85 deletions core/ControlService.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ class ControlService {

async obtainThermostat() {
let thermostat = await this._thermostatRepository.find(this._context.userId);
if (!thermostat) {
thermostat = await this._thermostatRepository.find('template');
if (thermostat) {
thermostat.userId = this._context.userId;
}
else {
thermostat = { userId: this._context.userId, executionId: null };
}
await this._thermostatRepository.add(thermostat);
if (thermostat) {
return thermostat;
}

thermostat = await this._thermostatRepository.find('template');
if (thermostat) {
thermostat.userId = this._context.userId;
}
else {
thermostat = { userId: this._context.userId, executionId: null };
}
await this._thermostatRepository.add(thermostat);
return thermostat;
}

Expand All @@ -48,27 +50,35 @@ class ControlService {

async launch() {
let client = await this.login();
if (await client.online()) {
return 'Thermostat is online';
} else {
return 'Sorry, the thermostat is offline at the moment.';
try {
if (await client.online()) {
return 'Thermostat is online';
} else {
return 'Sorry, the thermostat is offline at the moment.';
}
} finally {
await client.logout();
}
}

async status() {
console.log('Requesting status...');
let client = await this.login();
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

let messages = [];
messages.push(`The current temperature is ${this.speakTemperature(device.currentTemperature)} degrees.`);
messages.push(`The target is ${this.speakTemperature(device.targetTemperature)} degrees.`);
await this.determineIfHolding(device, messages);

this.logStatus(device);
return messages;
try {
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

let messages = [];
messages.push(`The current temperature is ${this.speakTemperature(device.currentTemperature)} degrees.`);
messages.push(`The target is ${this.speakTemperature(device.targetTemperature)} degrees.`);
await this.determineIfHolding(device, messages);

this.logStatus(device);
return messages;
} finally {
await client.logout();
}
}

async determineIfHolding(device, messages, qualifier = '') {
Expand All @@ -90,51 +100,61 @@ class ControlService {
async turnUp() {
console.log('Turning up...');
let client = await this.login();
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

if (device.status == 'on') {
throw 'The heating is already on.';
}
try {
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

let t = device.targetTemperature + 0.5;
await client.setTemperature(t);
let updatedDevice = await client.device();
if (device.status == 'on') {
throw 'The heating is already on.';
}

let t = device.targetTemperature + 0.5;
await client.setTemperature(t);
let updatedDevice = await client.device();

let messages = [];
messages.push(`The target temperature is now ${this.speakTemperature(updatedDevice.targetTemperature)} degrees.`);
await this.determineIfHolding(updatedDevice, messages, 'now');
let messages = [];
messages.push(`The target temperature is now ${this.speakTemperature(updatedDevice.targetTemperature)} degrees.`);
await this.determineIfHolding(updatedDevice, messages, 'now');

this.logStatus(device);
return messages;
this.logStatus(device);
return messages;
} finally {
await client.logout();
}
}

async turnDown() {
console.log('Turning down...');
let client = await this.login();
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

let t = device.targetTemperature - 1.0;
await client.setTemperature(t);
let updatedDevice = await client.device();

let messages = [];
messages.push(`The target temperature is now ${this.speakTemperature(updatedDevice.targetTemperature)} degrees.`);
await this.determineIfHolding(updatedDevice, messages, 'still');

this.logStatus(updatedDevice);
return messages;
try {
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

let t = device.targetTemperature - 1.0;
await client.setTemperature(t);
let updatedDevice = await client.device();

let messages = [];
messages.push(`The target temperature is now ${this.speakTemperature(updatedDevice.targetTemperature)} degrees.`);
await this.determineIfHolding(updatedDevice, messages, 'still');

this.logStatus(updatedDevice);
return messages;
} finally {
await client.logout();
}
}

async turn(onOff, duration) {
console.log(`Turning ${onOff}...`);

let t = process.env.DEFAULT_ON_TEMP || '20';
let thermostat = await this.obtainThermostat();
let t = thermostat.defaultOnTemp;
if (onOff === 'off') {
t = process.env.DEFAULT_OFF_TEMP || '14';
t = thermostat.defaultOffTemp;
}

return this.setTemperature(t, duration);
Expand All @@ -143,41 +163,45 @@ class ControlService {
async setTemperature(targetTemperature, forDuration) {
console.log(`Setting temperature to ${targetTemperature}...`);
let client = await this.login();
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

await client.setTemperature(targetTemperature);
let updatedDevice = await client.device();

let messages = [];
messages.push(`The target temperature is now ${this.speakTemperature(updatedDevice.targetTemperature)} degrees.`);
this.logStatus(updatedDevice);

let duration = forDuration || process.env.DEFAULT_DURATION;

let intent = await this._holdStrategy.holdIfRequiredFor(duration);
return messages.concat(this.summarize(intent, updatedDevice));
try {
await this.verifyOnline(client);
let device = await client.device();
this.verifyContactable(device);

await client.setTemperature(targetTemperature);
let updatedDevice = await client.device();

let messages = [];
messages.push(`The target temperature is now ${this.speakTemperature(updatedDevice.targetTemperature)} degrees.`);
this.logStatus(updatedDevice);

if (this._context.source === 'user') {
let thermostat = await this.obtainThermostat();
let duration = forDuration || thermostat.defaultDuration;
let intent = await this._holdStrategy.holdIfRequiredFor(duration);
return messages.concat(this.summarize(intent, updatedDevice));
}
return messages;
} finally {
await client.logout();
}
}

summarize(intent, updatedDevice) {
let messages = [];
if (intent.holding) {
let durationText = this.speakDuration(intent.duration);
console.log(`Holding for ${durationText} {${intent.executionId}}`);
if (!intent.holding) {
if (updatedDevice.status == 'on') {
messages.push(`The heating is now on and will turn off in ${durationText}`);
}
else {
messages.push(`The heating will turn off in ${durationText}`);
return ['The heating is now on.'];
}
return [];
}
else {
if (updatedDevice.status == 'on') {
messages.push('The heating is now on.');
}

let durationText = this.speakDuration(intent.duration);
console.log(`Holding for ${durationText} {${intent.executionId}}`);
if (updatedDevice.status == 'on') {
return [`The heating is now on and will turn off in ${durationText}`];
}
return messages;

return [`The heating will turn off in ${durationText}`];
}

logStatus(device) {
Expand All @@ -193,9 +217,8 @@ class ControlService {
}

speakTemperature(temp) {
let t = parseFloat(temp);
if (parseFloat(t.toFixed(0)) != t) return t.toFixed(1);
else return t.toFixed(0);
if (parseFloat(temp.toFixed(0)) != temp) return temp.toFixed(1);
else return temp.toFixed(0);
}
}

Expand Down
5 changes: 4 additions & 1 deletion core/ThermostatRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ class ThermostatRepository {
options: {
username: process.env.USERNAME,
password: process.env.PASSWORD
}
},
defaultOnTemp: parseFloat(process.env.DEFAULT_ON_TEMP || '20'),
defaultOffTemp: parseFloat(process.env.DEFAULT_OFF_TEMP || '14'),
defaultDuration: process.env.DEFAULT_DURATION || 'PT1H'
};
}

Expand Down
Loading

0 comments on commit 32d216a

Please sign in to comment.