Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How we Automated our Relationship

How we Automated our Relationship

Want to automate your home? Come learn how to sharpen your skills in Node, APIs, and the Internet of Things while making laundry, cooking, & calendar projects that improve your life and relationship.

It’s not every day you have a couple of devs (Daphne Liu and Tiger Oakes), who are also a couple, offer to teach us how hacking has kept them happy & sane.

Tiger Oakes

May 10, 2023
Tweet

More Decks by Tiger Oakes

Other Decks in Programming

Transcript

  1. Tiger Oakes Daphne Liu @[email protected] Software Dev working on Microsoft

    Loop @[email protected] Frontend Dev working on Lyft web app & design systems
  2. • Communication • Transparency • Teamwork ... all of which

    we Keys to our relationship @notwoods × @thebetterdaphne
  3. No more chores chart. Couples Better than couples therapy. Family

    I hope your kid knows how to code. Roommates Yourself It’s time to get your life together. Who’s the talk for? @notwoods × @thebetterdaphne
  4. Notion & Trello decide what you’re eating this week Recipes

    Laundry Vibration sensors tell you when your laundry is done Meetings Lights & IoT indicate when you’re in “Do not Disturb” mode Use Node.js, APIs & IoT for @notwoods × @thebetterdaphne
  5. Being adult is hard You’re hungry Too hangry to think

    and cook You try to cook No recipes & no groceries You got groceries You forget about them, they grow moldy
  6. Feeding Schedule @notwoods × @thebetterdaphne Taiwanese minced pork on rice

    Lo mein Lomo saltado Meat jun + mashed cauliflower 3 cup chicken + Eggplant Chicken adobo + Carrot Lo mein Air fry orange tofu + Broccoli Kale + Sweet potato salad Cold sesame noodle Air fry orange tofu + Broccoli Taiwanese egg pancake Veggie patty + Some veggie Korean Egg Tofu Thai coconut soup Beef chow mein
  7. Feeding Schedule M TU W TH F SA SU Lunch

    Eat out Dinner Eat out @notwoods × @thebetterdaphne Taiwanese minced pork on rice Lo mein Lomo saltado Meat jun + mashed cauliflower 3 cup chicken + Eggplant Chicken adobo + Carrot Air fry orange tofu + Broccoli Kale + Sweet potato salad Cold sesame noodle Air fry orange tofu + Broccoli Taiwanese egg pancake Veggie patty + Some veggie
  8. Feeding Schedule M TU W TH F SA SU Lunch

    Dinner @notwoods × @thebetterdaphne
  9. 1. Pull from recipe list in Notion 2. Assign meals

    onto days in Trello 3. Add the ingredients onto our grocery list in Trello. @notwoods × @thebetterdaphne
  10. Trello Notion • API • Comes with UI (property, tags,

    link) • Templates • API • Comes with UI (similar to our list) • Drag and drop • Works offline Why Notion & Trello? @notwoods × @thebetterdaphne
  11. 1. Pull from recipe list in Notion 2. Assign meals

    onto days in Trello 3. Add the ingredients onto our grocery list in Trello. @notwoods × @thebetterdaphne
  12. https://developers.notion.com • Fetch array of pages • Fetch array of

    blocks in a page Notion API @notwoods × @thebetterdaphne
  13. // Query Notion database using API // https://developers.notion.com/reference/post-database-query queryDatabases(databaseId, body)

    { return fetch(`${this.notionUrl}/v1/databases/${databaseId}/query`, { method: "post", body: JSON.stringify(body), headers: { "Authorization": `Bearer ${this.token}`, "Notion-Version": "2022-06-28", "Content-Type": "application/json", } }); } @notwoods × @thebetterdaphne
  14. import { shuffle } from 'd3-array'; const recipes: DatabasePage[] =

    await notionApi.queryDatabases(...); shuffle(recipes); 1. Pick random meals @notwoods × @thebetterdaphne
  15. 1. Pull from recipe list in Notion 2. Assign meals

    onto days in Trello 3. Add the ingredients onto our grocery list in Trello. @notwoods × @thebetterdaphne
  16. // Create new Trello card using API // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/ createCard(card)

    { const query = new URLSearchParams(card); query.set("key", this.key); query.set("token", this.token); return fetch(`${this.trelloUrl}/1/cards`, { method: "post", query, headers: { "Content-Type": "application/json" } }); } @notwoods × @thebetterdaphne
  17. const [groceryList, ...weekdays] = await trelloApi.listsOnBoard(ID); // For each weekday

    column... for (const [i, weekday] of weekdays.entries()) { await trelloApi.deleteAllCardsInList(weekday.id); await addRecipeAsCard(recipes[lunchPos(i)], weekday.id); await addRecipeAsCard(recipes[dinnerPos(i)], weekday.id); } 2. Assign meals to days @notwoods × @thebetterdaphne
  18. function addRecipeAsCard(recipe, listId) { // Populate the content in a

    card const name = await notionApi.retrievePageTitle(recipe.id); const card = await trelloApi.createCard({ idList, name, urlSource: recipe.url, }); if (recipe.cover) { await trelloApi.createAttachmentOnCard(card.id, { url: recipe.cover.external.url, setCover: true, }); } @notwoods × @thebetterdaphne
  19. Script so far Get recipes Fetch recipes as array Randomize

    Shuffle the array Get lists Get the id of every Trello list Add cards Add two cards per list @notwoods × @thebetterdaphne
  20. import { NotionApi } from './api.js'; // Hardcode API key

    const NOTION_API_KEY = "43273z8u3w"; const notionApi = new NotionApi(NOTION_API_KEY); // Run immediately using top-level await const blocks = await notionApi.queryDatabase(); @notwoods × @thebetterdaphne
  21. 1. Pull from recipe list in Notion 2. Assign meals

    onto days in Trello 3. Add the ingredients onto our grocery list in Trello. @notwoods × @thebetterdaphne
  22. async function extractShoppingListItems(blocks) { // Find heading name "Shopping list"

    const listHeading = await moveToHeading(blocks, /shopping list/i); if (!listHeading) { // No shopping list return []; } ... 3. Extract ingredients @notwoods × @thebetterdaphne
  23. const ingredients = []; switch (block.type) { // Add list

    items to result list case "bulleted_list_item": case "to_do": ingredients.push(block[block.type]); break; // just skip paragraphs, usually subheaders or empty lines case "paragraph": break; ... 3. Extract ingredients @notwoods × @thebetterdaphne
  24. switch (block.type) { ... // Break when entering a new

    heading at the same or above level case "heading_1": case "heading_2": case "heading_3": if (headingLevels[block.type] <= headingLevels[listHeading.type]) { continueLoop = false; } break; } 3. Extract ingredients @notwoods × @thebetterdaphne
  25. const allIngredients = []; for (const recipe of recipes) {

    const blocks = await notionApi.blockChildren(recipe.id); try { const ingredients = await extractShoppingListItems(blocks); ingredients.push(...ingredients); } catch (error) { console.warn("Couldn't get ingredients", recipe, error); } } 4. Export grocery list @notwoods × @thebetterdaphne
  26. 4. Export grocery list @notwoods × @thebetterdaphne const checklist =

    await trelloApi.createChecklistOnCard(card.id); for (const ingredient of ingredients) { await trelloApi.createCheckItems(checklist.id, ingredient); }
  27. Future Work Choose recipes with overlapping ingredients. Handle batch cooking

    one large meal for multiple days. Notes You can help us by contributing to the repo! https://github.com/ NotWoods/ automate-our-relationship @notwoods × @thebetterdaphne
  28. Meetings Lights & IoT indicate when you’re in “Do not

    Disturb” mode Lights & IoT indicate when you’re in “Do not Disturb” mode @notwoods × @thebetterdaphne
  29. Home Assistant API https://home-assistant.io/ • Universal API for talking to

    smart home devices (ex. smart plug) • Host on a server ◦ Raspberry Pi ◦ Virtual machine ◦ Buy one from them @notwoods × @thebetterdaphne
  30. Event emitters class EventNode extends EventEmitter { transform(event) { if

    (event.type === 'busy') { this.emit('data', event); } } } const filterBusy = new EventNode(); otherEventNode.on('data', (event) => filterBusy.transform(event) ); Emit events Filter data Send to devices @notwoods × @thebetterdaphne
  31. Calendar event flow Calendars Emit whenever a calendar event is

    starting Devices Turn light on when meeting starts Filtering Filter out non- meeting events Timer setTimeout until meeting ends
  32. https://nodered.org/ • Built on Node.js • Low-code, drag and drop

    UI • Integrates with Home Assistant • Get additional nodes as npm modules Node-RED @notwoods × @thebetterdaphne
  33. Calendars Emit whenever a calendar event is starting Filtering Filter

    out non- meeting events Devices Turn light on Timer Turn light off @notwoods × @thebetterdaphne
  34. Timer Turn light off, after delay @notwoods × @thebetterdaphne //

    Send event after a delay const delay = end.getTime() - Date.now(); setTimeout(() => { node.send(msg) }, delay);
  35. Node-RED Sensors • Device in your house to measure real

    life information • May get disconnected • May not report data when you want • May not detect events you care about Building a laundry system No machine data • Washer's on/off state isn't stored in a database
  36. Reports movement events $20 device Uses Zigbee instead of WiFi

    to communicate Aqara Vibration Sensor @notwoods × @thebetterdaphne
  37. Zigbee WiFi Devices wirelessly connect to hub, which connects to

    internet Devices wirelessly connect to router, which connects to internet Smart hubs and protocols
  38. Zigbee hub All these devices connect through the same Zigbee

    hub Aqara Philips Hue IKEA Tradri Zigbee hub
  39. Notes • device_id: which sensor sent the event • command:

    event type • args.strength: vibration strength Sensor events { "device_id": "587afb97ab882bd1", "command": "vibration_strength", "args": { "strength": 4 } } { "device_id": "587afb97ab882bd1", "command": "vibration_strength", "args": { "strength": 29 } } { "device_id": "1732994c20ae4142", "command": "vibration_strength", "args": { "strength": 32 } } { "device_id": "587afb97ab882bd1", "command": "vibration_strength", "args": { "strength": 24 } } @notwoods × @thebetterdaphne
  40. Sensor Listening for vibration sensor events Threshold Check if the

    previous event was above 24 and new event is below 24 Notify Send announcement message to Google Home speakers @notwoods × @thebetterdaphne
  41. Threshold Check if the previous event was above 24 and

    new event is below 24 @notwoods × @thebetterdaphne const THRESHOLD = 24; if (lastStrength >= THRESHOLD && strength < THRESHOLD) { node.send(msg); }
  42. // const THRESHOLD = 24; const THRESHOLD = 29; if

    (lastStrength >= THRESHOLD && strength < THRESHOLD) { node.send(msg); } @notwoods × @thebetterdaphne
  43. • Communication • Transparency • Teamwork ... all of which

    we Keys to our relationship @notwoods × @thebetterdaphne
  44. Do you have any questions? tigeroakes.com @notwoods daphneliu.com @thebetterdaphne CREDITS:

    This presentation template was created by Slidesgo, including icons by Flaticon, and infographics & images by Freepik