Skip to content

Instantly share code, notes, and snippets.

@Garfonso
Last active February 2, 2024 13:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Garfonso/16a7978e50b5d18b98bf408c2ff82060 to your computer and use it in GitHub Desktop.
Save Garfonso/16a7978e50b5d18b98bf408c2ff82060 to your computer and use it in GitHub Desktop.
ioBroker: Sync Lovelace and Alexa Shopping Lists
const shoppingListId = 'lovelace.0.control.shopping_list';
const alexa2BaseId = 'alexa2.0.Lists.SHOPPING_LIST'; //important. Needed for delete & update also.
const alexaAddToList = alexa2BaseId + '.#New';
const alexaListId = alexa2BaseId + '.json';
//switch off to silence:
const printDebug = true;
function debug(msg) {
if (printDebug) {
log(msg)
}
}
const TodoItemStatus = {
NeedsAction: 'needs_action',
Completed: 'completed',
};
/**
* @typedef lovelaceItem
* @type {object}
* @property {string} summary
* @property {string} status
* @property {string} uid
* @property {boolean} [found] - keep track if found or not.
*/
/**
* @typedef alexaItem
* @type {object}
* @property {string} value
* @property {string} id
* @property {boolean} completed
* @property {number} updatedDateTime
* @proerpty {boolean} [found] - keep track if found or not.
*/
/**
* Compare alexaItem complete and lovelaceItem status -> returns true if same status.
* @param {Array<alexaItem>} alexaList
* @param {Array<lovelaceItem>} list
* @returns {boolean} true if same status.
*/
function compareCompleted(alexaItem, lovelaceItem) {
if (alexaItem.completed && lovelaceItem.status !== TodoItemStatus.Completed) {
return false;
}
if (!alexaItem.completed && lovelaceItem.status !== TodoItemStatus.NeedsAction) {
return false;
}
return true;
}
/**
* sync lists
* @param {Array<alexaItem>} alexaList
* @param {Array<lovelaceItem>} list
* @param {number} timestampLovelace
* @returns {Array<lovelaceItem>} new lovelace List
*/
function syncLists(alexaList, list, timestampLovelace) {
const newLovelaceList = [];
for (const alexaItem of alexaList) {
for (const lovelaceItem of list) {
if (lovelaceItem.uid === alexaItem.id) {
alexaItem.found = true;
lovelaceItem.found = true;
//found item. Update completed state from 'newer' list:
if (alexaItem.updatedDateTime > timestampLovelace) {
if (alexaItem.value !== lovelaceItem.summary || !compareCompleted(alexaItem, lovelaceItem)) {
debug('Updating ' + lovelaceItem.name + ' from alexa');
}
//update lovelace:
newLovelaceList.push({
summary: alexaItem.value,
status: alexaItem.completed ? TodoItemStatus.Completed : TodoItemStatus.NeedsAction,
uid: alexaItem.id
});
} else {
//keep lovelace:
newLovelaceList.push({
summary: lovelaceItem.summary,
status: lovelaceItem.status,
uid: alexaItem.id
});
//update alexa:
if (!compareCompleted(alexaItem, lovelaceItem)) {
debug('Update ' + alexaItem.value + ' to ' + (lovelaceItem.status === TodoItemStatus.Completed ? 'done' : 'undone') + ' from lovelace.');
setState(`${alexa2BaseId}.items.${alexaItem.id}.completed`, lovelaceItem.status === TodoItemStatus.Completed);
}
if (alexaItem.value !== lovelaceItem.summary) {
debug('Update ' + alexaItem.value + ' to ' + lovelaceItem.summary + ' from lovelace');
setState(`${alexa2BaseId}.items.${alexaItem.id}.value`, lovelaceItem.summary);
}
}
}
}
if (!alexaItem.found) {
//alexa item not found:
if (alexaItem.completed) {
//if completed and not in lovelace -> remove.
debug('Delete ' + alexaItem.value + ' because done and not in lovelace list.');
setState(`${alexa2BaseId}.items.${alexaItem.id}.#delete`, true);
} else {
debug('Add ' + alexaItem.value + ' to lovelace list');
newLovelaceList.push({
summary: alexaItem.value,
status: alexaItem.completed ? TodoItemStatus.Completed : TodoItemStatus.NeedsAction,
uid: alexaItem.id
});
}
}
}
for (const lovelaceItem of list) {
if (!lovelaceItem.found) {
if (lovelaceItem.status === TodoItemStatus.Completed) {
debug('Delete ' + lovelaceItem.summary + ' because done and not on alexa list');
} else {
//this will add a new entry to alexa list.
//of course ids won't match. So we will not keep the item in lovelace at this place
//but it will be re-added once it traveled through the alexa ecosystem.
debug('Adding ' + lovelaceItem.summary + ' to alexa list.');
setState(alexaAddToList, lovelaceItem.summary);
}
}
}
return newLovelaceList;
}
function doSync() {
const alexaList = JSON.parse(getState(alexaListId).val);
const state = getState(shoppingListId);
const lovelaceList = JSON.parse(state.val);
const newList = syncLists(alexaList, lovelaceList, state.ts);
setState(shoppingListId, JSON.stringify(newList));
}
on({id: shoppingListId, ack: true}, e => {
debug('Got shopping list update: ' + e.state.val);
doSync();
});
on({id: alexaListId, ack: true}, e => {
debug('Got alexa shopping list update.');
doSync();
});
doSync();
@Garfonso
Copy link
Author

Known issue: You can not delete uncompleted items, they will be added again and again.

@Garfonso
Copy link
Author

Garfonso commented Feb 2, 2024

Updated to new ioBroker.lovelace version (4.x)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment