From 0c6672ffc7729dae6a56228b4209747954f37882 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Tue, 18 Jun 2019 05:14:21 -0500 Subject: [PATCH] Life360 integration (#9539) * Life360 integration * Update source/_components/life360.markdown Fix ha_release per review comment. Co-Authored-By: Klaas Schoute * Update source/_components/life360.markdown Fix ha_category per review comment. Co-Authored-By: Klaas Schoute * Update per code changes * :pencil2: Tweaks --- source/_components/life360.markdown | 239 +++++++++++++++++++++ source/images/supported_brands/life360.png | Bin 0 -> 14862 bytes 2 files changed, 239 insertions(+) create mode 100644 source/_components/life360.markdown create mode 100644 source/images/supported_brands/life360.png diff --git a/source/_components/life360.markdown b/source/_components/life360.markdown new file mode 100644 index 00000000000..51cfae1e40a --- /dev/null +++ b/source/_components/life360.markdown @@ -0,0 +1,239 @@ +--- +layout: page +title: "Life360" +description: "Instructions how to use Life360 to track devices in Home Assistant." +date: 2019-04-24 16:00 +sidebar: true +comments: false +sharing: true +footer: true +logo: life360.png +ha_release: 0.95 +ha_category: + - Presence Detection +ha_iot_class: Cloud Polling +--- + +The `life360` integration allows you to detect presence using the [unofficial API](#disclaimer) of [Life360](https://www.life360.com/). + +## {% linkable_title Life360 Account %} + +You must first [create a Life360 account](https://www.life360.com/websignup). + +Then in the Home Assistant user interface (UI), click on Configuration in the left pane, then on Integrations and then on the yellow circle in the lower-right corner to "Set up a new integration." Scroll through the list and click on Life360. Enter your Life360 username and password and click SUBMIT. You can add as many Life360 accounts as you like. + +If you would like to set any advanced options, see the following section. You may want to do this before entering your Life360 account information in the UI, or you can change it at any time. You can also enter your account information in the configuration file (in addition to, or instead of, the UI) if you prefer. + +{% configuration %} +accounts: + description: Your Life360 account information. + required: false + type: [list, map] + default: None + keys: + username: + description: Your Life360 username. + required: true + type: string + password: + description: Your Life360 password. + required: true + type: string +circles: + description: See [Filtering](#filtering) for a detailed description. Must specify **include** or **exclude**, but not both. + required: false + type: map + default: Include all Circles. + keys: + include: + description: Circles to include. + required: false + type: [string, list] + exclude: + description: Circles to exclude. + required: false + type: [string, list] +driving_speed: + description: The minimum speed at which the device is considered to be "driving" (which can also set the `driving` [attribute](#additional-attributes) to `true`.) MPH or KPH, depending on Home Assistant's unit system configuration. + required: false + type: float + default: "\"Driving\" determined strictly by Life360." +error_threshold: + description: See [Communication Errors](#communication-errors) for a detailed description. + required: false + type: integer +interval_seconds: + description: This defines how often the Life360 server will be queried (in seconds.) The resulting device_tracker entities will actually only be updated when the Life360 server provides new location information for each member. + required: false + type: integer + default: 12 +max_gps_accuracy: + description: If specified, and the reported GPS accuracy is larger (i.e., *less* accurate), then update is ignored. + required: false + type: float +max_update_wait: + description: If specified and Life360 does not provide an update for a member within that maximum time window, an event named `life360_update_overdue` will be fired with the `entity_id` of the corresponding member's device_tracker entity. Once an update does come, an event named `life360_update_restored` will be fired with the `entity_id` of the corresponding member's device_tracker entity and another data item named `wait` that will indicate the amount of time spent waiting for the update. You can use these events in automations to be notified when they occur. See [example automations](#example-overdue-update-automations) below. + required: false + type: time +members: + description: See [Filtering](#filtering) for a detailed description. Must specify **include** or **exclude**, but not both. + required: false + type: map + default: Include all Members from all included Circles. + keys: + include: + description: Members to include. + required: false + type: [string, list] + exclude: + description: Members to exclude. + required: false + type: [string, list] +prefix: + description: Device ID prefix. Entity IDs will be in the form of `device_tracker.PREFIX_FIRST_LAST`, or `device_tracker.PREFIX_NAME` if the Member has only one name. To use no prefix, specify `''`. + required: false + type: string + default: life360 +warning_threshold: + description: See [Communication Errors](#communication-errors) for a detailed description. + required: false + type: integer +{% endconfiguration %} + +## {% linkable_title Additional attributes %} + +Attribute | Description +-|- +address | Address of the current location, or `none`. +at_loc_since | Date and time when first at current location (in UTC.) +battery_charging | Device is charging (`true`/`false`.) +driving | Device movement indicates driving (`true`/`false`.) +last_seen | Date and time when Life360 last updated device location (in UTC.) +moving | Device is moving (`true`/`false`.) +place | Name of Life360 Place where the device is located, or `none` if not located within one. +raw_speed | "Raw" speed value provided by Life360 server. (Units unknown.) +speed | Estimated speed of device (in MPH or KPH depending on Home Assistant's unit system configuration.) +wifi_on | Device WiFi is turned on (`true`/`false`.) + +## {% linkable_title Filtering %} + +For most users, filtering is not needed, and in such cases, the corresponding configuration variables should not be used. + +However, in some circumstances, it might be helpful to limit which Life360 Circles and/or Members are used. For these cases [**circles**](#circles) and/or [**members**](#members) can be used. + +**circles** can limit which Life360 Circles are used. + +**members** can limit which Life360 Members will be tracked. + +For a particular Member to be tracked, they must be included (or at least not excluded), and must be in at least one of the included Circles. See [example configuration](#circle-and-member-filtering-example) below. + +Note that Life360's app and website typically only show Members' first names. However, you must use their _full_ names here. If you're not sure what a Member's full name (i.e., first and last) is in Life360, ask them. Alternatively, you can set the [`logger`](https://www.home-assistant.io/components/logger/) to `debug` and look in `home-assistant.log`. The full names of all Life360 Circles & Members will be logged. + +## {% linkable_title Home - Home Assistant vs. Life360 %} + +Normally Home Assistant device trackers are "Home" when they enter `zone.home`. Also, Life360 normally considers your device "Home" when it enters the Place that coincides with your home. Since the definitions of these areas can be different, this can lead to a disagreement between Home Assistant and Life360 as to whether or not you're "Home." To avoid this, make sure these two areas are defined the same -- i.e., same location and radius. (See next section.) + +## {% linkable_title Home Assistant Zones & Life360 Places %} + +See [Zone documentation](https://www.home-assistant.io/components/zone/#home-zone) for details about how HA zones are defined. If you'd like to create HA zones from Life360 Places (e.g., to make HA's `zone.home` be identical to Life360's "Home Place"), make sure `logger` is set to `debug`. Then when HA starts the details of all the Places defined in the included Circles will be written to `home-assistant.log` in a format that can be copied into your configuration under `zone:`. E.g., you would see something like this: + +```text +2019-05-31 12:16:58 DEBUG (SyncWorker_3) [homeassistant.components.life360.device_tracker] My Family Circle: will be included, id=xxxxx +2019-05-31 12:16:58 DEBUG (SyncWorker_3) [homeassistant.components.life360.device_tracker] Circle's Places: +- name: Home + latitude: XX.XXX + longitude: YY.YYY + radius: ZZZ +``` + +## {% linkable_title Communication Errors %} + +It is not uncommon for communication errors to occur between Home Assistant and the Life360 server. This can happen for many reasons, including Internet connection issues, Life360 server load, etc. However, in most cases, they are temporary and do not significantly affect the ability to keep device_tracker entities up to date. + +Therefore, an optional filtering mechanism has been implemented to prevent inconsequential communication errors from filling the log, while still logging unusual error activity. Two thresholds are defined: [**warning_threshold**](#warning_threshold) and [**error_threshold**](#error_threshold). When a particular type of communication error happens on consecutive update cycles, it will not be logged until the number of occurrences reaches these thresholds. When the number reaches **warning_threshold** (but does not exceed **error_threshold**, and only if **warning_threshold** is defined), it will be logged as a WARNING. Once the number reaches **error_threshold**, it will be logged as an ERROR. Only two consecutive communication errors of a particular type will be logged as an ERROR, after which it will no longer be logged until it stops occurring and then happens again. + +## {% linkable_title Examples %} + +### {% linkable_title Typical configuration %} + +{% raw %} +```yaml +life360: + # MPH, assuming imperial units. + # If using metric (KPH), the equivalent would be 29. + driving_speed: 18 + interval_seconds: 10 + max_gps_accuracy: 200 + max_update_wait: + minutes: 45 + # Set comm error thresholds so first is not logged, + # second is logged as a WARNING, and third and fourth + # are logged as ERRORs. + warning_threshold: 2 + error_threshold: 3 +``` +{% endraw %} + +### {% linkable_title Circle and Member Filtering Example %} + +{% raw %} +```yaml +life360: + # Only track Members that are in these Circles. + circles: + include: [My Family, Friends] + # But do not track this Member. + members: + exclude: John Doe +``` +{% endraw %} + +### {% linkable_title Entering accounts in configuration %} + +{% raw %} +```yaml +life360: + accounts: + - username: LIFE360_USERNAME + password: LIFE360_PASSWORD +``` +{% endraw %} + +### {% linkable_title Example overdue update automations %} + +{% raw %} +```yaml +automation: + - alias: Life360 Overdue Update + trigger: + platform: event + event_type: life360_update_overdue + action: + service: notify.email_me + data_template: + title: Life360 update overdue + message: > + Update for {{ + state_attr(trigger.event.data.entity_id, 'friendly_name') or + trigger.event.data.entity_id + }} is overdue. + + - alias: Life360 Update Restored + trigger: + platform: event + event_type: life360_update_restored + action: + service: notify.email_me + data_template: + title: Life360 update restored + message: > + Update for {{ + state_attr(trigger.event.data.entity_id, 'friendly_name') or + trigger.event.data.entity_id + }} restored after {{ trigger.event.data.wait }}. +``` +{% endraw %} + +## {% linkable_title Disclaimer %} + +It does not appear that Life360 officially supports its REST API for use with other than its own apps. This integration is based on reverse engineering that has been done by the open source community, and an API token that was somehow discovered by the same community. At any time Life360 could disable that token or otherwise change its REST API such that this component would no longer work. diff --git a/source/images/supported_brands/life360.png b/source/images/supported_brands/life360.png new file mode 100644 index 0000000000000000000000000000000000000000..210e825d5545f263ad89bbc0aa4b50e9d96c20ab GIT binary patch literal 14862 zcmbWe1yo$ymNrTtK!5-V?yiLtg%=v!y$BH8or1z0g1b8jf#5E|-8B&0U4vVYU_UwM z-qZcR(fzvL+hc57)93u=S~bRA6{f5xg^o&u3I_*=E+Z|j`h5TWd>X$(dcIZ%k=8xm zQ0%3(9O2;5u>YJd;LULldZxDXW{U{WCWloPe;Ky`izS zsS|~fskx<{Ak|58I~9eci6E5*mjauDy_l(mrL+gkRLw&XV(ejU3^t(>7NQVv<9k+M zYwBc3;bv=N=g8+KNcERqzUSvZ!$2yEzgV2C1*t^-7^KitP^J)r!b~Z+Sh-n@L7Z$9 zJYZH3HybAxh?#<&4a5Uv;{vjAv#^2q*m(KaK@|TwsGhCCOw9OH#U=k`>-kNP%EHOX zo(~9gb#-NR<7z|`%2ePxXJae!(y4yJ!y0O?fQvX|nxT&Kt%+lV;5^6{B zN28$;)Y(ap>eI8MPfc`hC{~`aCh<_V!vNZc|lJJM}f3cdnS^hVy ze<=U3KG%Rx31;~WMME2LsIl`OM`gqXsh(qIHL*0|6JZzUVUy$)<>X@L;N}H^c-eS4 zM7bqJxj-V~U`|o)Kc4;3@n6ZZONxt&aC3^Yvx!T9Koab{Vv?L<+@fM);u1U{Ua-W! zWo7Ifoeb@aP5*7z^4TtkgM$~$Vam=A{^XkRKM9zM8#WFUxbou=7AboP3;Ye9vP4nf18~O$?n3|3_vM zV?Hw|%+~N3ke0TF=B7YlzZwsbSK(G<6QhLYo7j<9n*oBB)SFOt@O zB7wnwW{ZD8$P{MjVrn7@gW6L3)$V+j{{=AqXF&cfYx4gG%)oz~k+yVv29^82CPOt- zhkrfUSU&sqJe%+t8vmV8N5f~Iss1(iUpdUdY0SlGV#LE@$j0@IWG)a73oi#d4~r=; z7l*NliJ=M4pY;5#)YZcDxp04h67&p2b_j@r5A=-7=V_YvFHru?<_I-&ay5jRikLrF z`+o-g-$KR~hIZzr&kGHZ>Q9(|X5|0x!Tj&q^8XRcf7dqu+hCsa!TW!=@qZZ+@J~p8 zR|@zyqW+_Zf6@Nag7ln#zlNSyxj&EpEU(Wm|18j^cF)OzJukn}{CbFRaG-q|aS@1H z=FunD1`~<2C*hNyZLaX$6l|{@LPz~fXuiH;kRg}Aaqi4yV=l423bF1QC=dPUj&7~V z0eBT|{q7y)jmRLN3l1xU5`)8Wcjef_*SCcpGF>!Tzst_?8rC+ZHg+WBt?0ZwxvLE= z$5PLeh`@VV+RMjSdHT3qFq08}t?^E+pGF{=$2rZF8BM6<1Gj>DUH9OXMIG0G0Ay#~ zgf+b^X2{8TUgyM1Vp|c?=P+*s!OMn4SY2U4@N&*Ibn4zD`JA>Y{#YOgAFGDlrxTU9 zl6$0&(;JI&c)Suk`Z6$ed<5wGrq^exz0&V`^&6RkjxXlY{zEB}*MasbgFby&8(ucT zA>tuA$;-pA>bC=>unBwI`6*iZ(0+BAOL~fT+b`Bw*RDGxI=0=d$2YQ2t*OVQ{1E(% z{koOs@xDu{pwJe|$=v5BCyCFh)(pHe6@<(nXu+McCvG#$BACPP!lxLhiALbXSd~hM z&z4d0<8ARK>LPGyIdaWQ^!!qPqI^}+_ER|(w@t=>1a4kEPS^h$KIO$KLL0);dFXOF z9t#=W3{uWX5ujh}$_v6>hz-7w&Q$$g^R0}e6;tN8|Jz5WHDJuv zcdKzDzyMk2$qHgpMad9tVM`sAyY$o`jq}4rsp7ea_YY}UyKU&ufl4FJ4N2uDwcF~4 zZ<>|@SmLX%d2y3gv(K?0eBDJ0C@4A}m5%ByZu-27@VgniM;KGCIc@g`t?j{D9S439 z{!j2b2$v%lzk%3%^+sz#u^owoaCyq{Z`E%`?&d64i;)lBZd}5SbH*q zSw56bYpQYo#_Nz$ad*(A#XLIm>p>7W2s=pm0uu&o7E1d>=ddG~c;CszpCZt|*8eS5 zL|WLoLdJ`512`q`II`aJsk5k3)x;L}}z{`7Wr>+%hHk$*(1?T_}c`8&~JDv;ci?FdLc;DIlaYw z`xL+PJ7!hDP8PdbMHuE=`!^cOZBcmzbYBAXU?QH&xrEAO*gW$M^nUu9378*S-Xx{K zHz(_BEgZd(c0%umZ+UNMyyj+bc3NL95iuR0u5hWK492Fvn>soMpij$*{L+6y-^NZg zwk|y*{!Pl?zUuEoFPl6F5S0|yI7&XDb-emORO`pjHh2$)L?gHtblvY8hdu6`zN#1) z?+EU=@RR6X>3B{b@_SP^TUL9z5A^P;UGGm)iq9z%5#9(X$-`9mD|kMZrv_h18W{D+ z`@#8~@=Lnu+>w4;m7tf*=7E3k5>RcnCp+_1w zypVtH8Eaj|RKI^0W=0KPmBlTY>J>7(wy9$@peehvpOUS8uI5pp{&etVbJFpnD`VOm zbC?GFb1i?IENqP*ED#*5Isi|}5(E6vK8j~r{V=V-;E>3C`zS;4gf^my7+^PUBr9Ef z?A)0w`Is4#bFPrE^|{9-2%!Xz3?T^xq9}R$hK=ls;m2@ZnG3*^w{95y06qhb_{E}u zma7&Q=gXQ_=!?e}OXy2sGW&cmsqFZYYiIQJ7={pp6A5?v&}CtCnPbi;?ON)0Z8WD? zK{B5ZpJ8Gp;6<#v?F9L3M}yLBYKUqrbG4`pCSyVy*gY}iv{{u@D2K{fuwQf~8u zcXpTP+OoCSatU5`7hVjCfKE>PTPEou&v7ogbU(8c|CZMc`TJc1rP$PLLdzJqF783@ zscHunkk6_?%~Y^iJ>y;V9)0{I!SgFB14etY_i0yRMlvVtXhqqvmdtBo@OE%*FAnu$ zm(=DjX6d>vW=~)Mjq8E7i&MM1?hp&)g82{CD+&?QQMpm&-JIKSI#=s5B({%ta{gb# zWL8eP=3Y~~FJJk0 zl8>nz>2uitZn3Cv7Ni)dc*R;A2TVFCOXkp!yd6U*ubxwZcJ2-_%M<;OmrF}=>*%-m zQP1P+-VNs$y!~{v7VK>|$V}AqdG?TEgn-NGN`lZVT^OmK5eOx3Kh@41&asTyQXEg>{Hdniq(0RKVoO;Db0gUMpA(_IPs>4cH3#BYtykj&vb zm3i941)nO}*rO|CNOT{4BZGY877ehivgbmCRdSofqRx&w+c<|i4bz9Wblm53yK<&?L^v8XL=6oP}T@F7?BEXWz&#hI|!r zk`<((L+!Jlpl2JAgoR6fLl>tdP2z@LL?1M!&5f{rc^29v&_R>y9I<}Gsd*`{U3kExwQ z#mAG%;3R{p&_D&dt#M-To8 z7Y;jK;Yr_`x#7^{E|bEuq-S{qS5=Gq;sQLAikpDn530_2;8Kz`!gHANyTgeFT~(7# zva2^KxV%;4G5LTL&}e1d3Td*$Joo{dHEm741M>?d`;G<|1z0km@r%8G05<74BYK#w z#?Lr&{w>1ddna1okj^)$+$-BaZPuM3+hbNcUv%o$(c(cttEw4lh09CkcA?WNnK08M zGy?GJ9J82;!lDB-oKXqWm3P`+Y>g*UjTlMRDt3s+$j8HXSrOH&0PU~U9F^37$@|CR z@f(wr{ZvYXTbyLCi{6cdjY|h}SSzn~oKndUWw?OGi-MP*-D2+3sOb5&1dz6`D`$2Wj=N5npD7l=<$8yjSKK&p#a&GV$Zf-OljZfZU@sq6ZjA|kNGEZ!U zYx!&8TnO-lIgHNJ@LIPnI<7T-XHumt8^DjAyrESJS_&QF_Y?Hfp!UL)v-}jfWq@^o zw8l|L5;U|D?uBAnnrQ&%`?1PX#d$535LF*xIj=?i9exNL)3@P3m((8^-qqTq=jDk} zcB6dvZhYKTXD%RHBi=1Y7s-Ofer9tYZot zac@QSYX}9uSI!thMpUl_55v8_Q?@I3tfJ8Q1(2L4=3fNcdIu_240kD$F5zfH7A zgc;KOeywwL-uL@Fr_tv1mY^xQM5&hzQEWlX2Tu$D{gm|2YA;@{_$QR40{D~u3VWu$ zpX}dwK3+9h`1TAbdg=~0Fg;#1yIWepI6CG0s8xhiuTVOdl+jX(4lC?c-?5QQ=0b^? z-ec7j4BSB^)J7Y+WKysnofSFA6o)-@@ha*g(XJnfm-U(*j3Q9G)SZ zWA#NE>sn-cQ6X?+7lKl8YFv#?OpL!CkUO&H`1W5+IsXWI`)&0XUHGe@B4tH7(h~EE z{tgYeC;xN6ke4fezw2iS8t()#3C5~blP(4vKR>xaIu-Tu zBritEhM z(oLD=@CCO@q?S9ZbT`&qfMnV`=j&4IdOY{rdjlyxbFo_V^_ z>P3PU-D;&Xx}|_Jl0v^B!nr2bf;%i;;8szWiR4s(-`DBlRpk-1)m~7(ZZ6Rd5SLYb zuAj+X=GPrnfmbr)RB4R7Q={_=)v@=ISmd|6PxYZ}S|r~35N;XS^J2!Kri6CKM4$PJG^43@KURs`ZgC)^K59Aq&h}dzkdAUr@x|xh)l`Ls9zOG@{OYNe(9d-NIi(-aO zQjJC!?}lZFSY?L}6*?qCr=Rc6pQxtd^D%Gf{lEi;hzROAcZiPqMWS^ndrd*je(k-t zBppSL8IJ}=a}7>o{h;L7>eyj|V53ss&^IzdZ1|@+qqm>xh{`1313rg4E4!=ic}0-g z6Yg&{7f*2H7_Hc*^yYfe0uH7sDr#c_?kOvO(M#H@gjcrLfAk_=bf=A9!tM0`Z9YKB zAdFlW4wVqL&lwCrg*t~u43-5>%u&+!oE^T^S&8|z%h~+BC^IX5>+MfWZLP(Y$phHp zPJ|Y>VmN_m6@hGYn9S%sC?h#!`|2G3bnMKsOZL_`K+5OzsPe}kAx4Ab9#m8k=}YMP21$2 zmwSe>m(%S1TH{|InAOD$y-yVRd{upj1I8UztLXc2&_}b1n>1-uE1b9=NTZZHwd0!= z4!%Fc`jSu~#2=Tyb)A~E5nCmK2nd%~bF-_mNxb-dscs{6t&hnb1 zOUy?_HwVd@HAt{e-PLn2%NJ74pkvJofHsl~muMA|j$J*l-)IU6x26bXLid_2L~EJ*`h7Sdb?lB&1ZakQ ziRX2uQ?Y|i5g(MtTP8jF&h3qN)BA z7Lx1cABTMu5#{f{)7|B;$J4myy|_n58&}UNu)PqoA`+vRM})MMRSI_Jz9P21``8LsnLF!1*l*{sD1iWa4Bv&4Se<#bsZmvX~WKZH%eoK96nZrN-2KQ_**9Tn8 zgzr=EUD)U9c6Rt0?`I+^r>sqpGX;LOTu+jsDYadhZ; z_6$wcgn3J8i(guQ8p?^ELrKz(bH|ux7r-DYK8e89!qMnKR3%Nz2V`uw#e0aw>V-&T ztP)xav3#Hzrx{03azYS+5?gRAdK_LLU->g5lmpu}WSa%|Lt4e?bxbmt?7LmG|Xxhc1F4nVS1#OsYllfs?7P7b10~RZNuj7I^qddj$NXvAj4}98xX*{Ao{VhdydZ~1 zowcJ5hYlpjqByYMrRstGRKK)Kf=&B%O#VJsS=sD+edZi|U2JS`V;JTo+^ib%e*JNvl+$syL!&5ci*Q4Dncp*gO1Tha89Iz-6L%{b{IfqKpgAj3d) z+d1}0E|Uk=dZ?@5usMsdNw39zqJrP2i&^Zs_{E>LImpI9(Rh3}P+ew9yJAOv2e<

3HYOVLI$SZI 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