[{"data":1,"prerenderedAt":2380},["ShallowReactive",2],{"article-calendar-integration-between-home-assistant-and-appdaemon":3},{"article":4,"prev":2377,"next":11},{"id":5,"title":6,"alt":6,"authors":7,"body":27,"createdAt":2361,"description":32,"dummy":2362,"extension":13,"img":2363,"meta":2364,"navigation":589,"path":2369,"readingTime":118,"seo":2370,"stem":2371,"tags":2372,"updatedAt":2361,"__hash__":2375,"slug":2376},"articles\u002Farticles\u002Fcalendar-integration-between-home-assistant-and-appdaemon.md","Calendar integration betwen Home Assistant and AppDaemon",{"Markus Ressel":8},{"id":9,"bio":10,"cite":11,"country":12,"extension":13,"image":14,"jobtitle":15,"meta":16,"name":24,"stem":25,"__hash__":26},"authors\u002Fauthors\u002Fmarkusressel.md","Some super nice bio",null,"Germany","md","\u002Fimages\u002Favatar.jpg","Software Engineer",{"body":17},{"type":18,"value":19,"toc":20},"minimark",[],{"title":21,"searchDepth":22,"depth":22,"links":23},"",3,[],"Markus Ressel","authors\u002Fmarkusressel","cdZR-tMqLASDmxL9q7xXk60honEv8HVVlN770x0e7M4",{"type":18,"value":28,"toc":2357},[29,33,38,65,69,72,87,90,540,551,1169,1180,1223,2033,2040,2093,2353],[30,31,32],"p",{},"Creating automations based on events in your calendar can enhance the autonomy of your Home Assistant setup quite a lot. Setting it up using AppDaemon is - sadly - not straight forward though.",[34,35,37],"h2",{"id":36},"whats-the-problem","What's the Problem?",[30,39,40,47,48,53,54,58,59,64],{},[41,42,46],"a",{"href":43,"rel":44},"https:\u002F\u002Fgithub.com\u002Fappdaemon\u002Fappdaemon",[45],"nofollow","AppDaemon"," does not (yet) use the Websocket API that ",[41,49,52],{"href":50,"rel":51},"https:\u002F\u002Fwww.home-assistant.io\u002F",[45],"Home Assistant"," itself uses for its frontend, but the REST API instead. While this is fine for most things, the REST API is not officially supported anymore.\nSince the calendar services to create automations have been introduced much later than this technological shift, using calendar services like ",[55,56,57],"code",{},"calendar.get_events"," doesn't quite work when using the REST API.\nIf you would like to see this change, have a look at ",[41,60,63],{"href":61,"rel":62},"https:\u002F\u002Fgithub.com\u002Fappdaemon\u002Fappdaemon\u002Fissues\u002F1837",[45],"this issue"," and give it a 👍 (but please don't comment \"I need this too\", it spams all participants of the issue)!",[34,66,68],{"id":67},"the-workaround","The Workaround",[30,70,71],{},"There is, however, a way to work around this.",[30,73,74,75,78,79,82,83,86],{},"Instead of directly querying the calendar service from ",[41,76,46],{"href":43,"rel":77},[45],", we call\na script within ",[41,80,52],{"href":50,"rel":81},[45],", which internally calls the calendar service and sends the result\nback to ",[41,84,46],{"href":43,"rel":85},[45]," as an \"event\".",[30,88,89],{},"To get this running we first need to create our \"wrapper script\":",[91,92,97],"pre",{"className":93,"code":94,"filename":95,"language":96,"meta":21,"style":21},"language-yaml shiki shiki-themes dark-plus","alias: get_calendar_events\nsequence:\n  - service: calendar.get_events\n    data:\n      start_date_time: '{{ start_date_time }}'\n      duration:\n        hours: '{{ duration.hours }}'\n        minutes: '{{ duration.minutes }}'\n        seconds: '{{ duration.seconds }}'\n    response_variable: calendar_get_events_result\n    target:\n      entity_id: '{{ calendar_entity_id }}'\n  - variables:\n      event_entries: '{{ calendar_get_events_result[calendar_entity_id].events }}'\n  - event: custom_appdaemon_get_calendar_events_result\n    event_data_template:\n      data: '{{ calendar_get_events_result }}'\nmode: queued\nmax: 10\nfields:\n  calendar_entity_id:\n    selector:\n      entity: {}\n    name: Calendar Entity ID\n    description: The entity ID of the calendar to query.\n    required: true\n  start_date_time:\n    selector:\n      datetime: {}\n    name: Start Date Time\n    description: >-\n      Return active events after this time (exclusive). When not set, defaults\n      to now.\n    required: false\n  duration:\n    selector:\n      duration:\n        enable_day: false\n    name: Duration\n    required: false\n    description: Return active events from start_date_time until the specified duration.\n    default:\n      hours: 24\n      minutes: 0\n      seconds: 0\n","get_calendar_events.yaml","yaml",[55,98,99,116,125,138,146,157,165,176,187,198,209,217,228,238,249,262,270,281,292,304,312,320,328,337,348,359,370,378,385,393,403,417,423,429,439,447,454,461,471,481,490,500,508,519,530],{"__ignoreMap":21},[100,101,104,108,112],"span",{"class":102,"line":103},"line",1,[100,105,107],{"class":106},"scz_3","alias",[100,109,111],{"class":110},"sTNBD",": ",[100,113,115],{"class":114},"sKc5r","get_calendar_events\n",[100,117,119,122],{"class":102,"line":118},2,[100,120,121],{"class":106},"sequence",[100,123,124],{"class":110},":\n",[100,126,127,130,133,135],{"class":102,"line":22},[100,128,129],{"class":110},"  - ",[100,131,132],{"class":106},"service",[100,134,111],{"class":110},[100,136,137],{"class":114},"calendar.get_events\n",[100,139,141,144],{"class":102,"line":140},4,[100,142,143],{"class":106},"    data",[100,145,124],{"class":110},[100,147,149,152,154],{"class":102,"line":148},5,[100,150,151],{"class":106},"      start_date_time",[100,153,111],{"class":110},[100,155,156],{"class":114},"'{{ start_date_time }}'\n",[100,158,160,163],{"class":102,"line":159},6,[100,161,162],{"class":106},"      duration",[100,164,124],{"class":110},[100,166,168,171,173],{"class":102,"line":167},7,[100,169,170],{"class":106},"        hours",[100,172,111],{"class":110},[100,174,175],{"class":114},"'{{ duration.hours }}'\n",[100,177,179,182,184],{"class":102,"line":178},8,[100,180,181],{"class":106},"        minutes",[100,183,111],{"class":110},[100,185,186],{"class":114},"'{{ duration.minutes }}'\n",[100,188,190,193,195],{"class":102,"line":189},9,[100,191,192],{"class":106},"        seconds",[100,194,111],{"class":110},[100,196,197],{"class":114},"'{{ duration.seconds }}'\n",[100,199,201,204,206],{"class":102,"line":200},10,[100,202,203],{"class":106},"    response_variable",[100,205,111],{"class":110},[100,207,208],{"class":114},"calendar_get_events_result\n",[100,210,212,215],{"class":102,"line":211},11,[100,213,214],{"class":106},"    target",[100,216,124],{"class":110},[100,218,220,223,225],{"class":102,"line":219},12,[100,221,222],{"class":106},"      entity_id",[100,224,111],{"class":110},[100,226,227],{"class":114},"'{{ calendar_entity_id }}'\n",[100,229,231,233,236],{"class":102,"line":230},13,[100,232,129],{"class":110},[100,234,235],{"class":106},"variables",[100,237,124],{"class":110},[100,239,241,244,246],{"class":102,"line":240},14,[100,242,243],{"class":106},"      event_entries",[100,245,111],{"class":110},[100,247,248],{"class":114},"'{{ calendar_get_events_result[calendar_entity_id].events }}'\n",[100,250,252,254,257,259],{"class":102,"line":251},15,[100,253,129],{"class":110},[100,255,256],{"class":106},"event",[100,258,111],{"class":110},[100,260,261],{"class":114},"custom_appdaemon_get_calendar_events_result\n",[100,263,265,268],{"class":102,"line":264},16,[100,266,267],{"class":106},"    event_data_template",[100,269,124],{"class":110},[100,271,273,276,278],{"class":102,"line":272},17,[100,274,275],{"class":106},"      data",[100,277,111],{"class":110},[100,279,280],{"class":114},"'{{ calendar_get_events_result }}'\n",[100,282,284,287,289],{"class":102,"line":283},18,[100,285,286],{"class":106},"mode",[100,288,111],{"class":110},[100,290,291],{"class":114},"queued\n",[100,293,295,298,300],{"class":102,"line":294},19,[100,296,297],{"class":106},"max",[100,299,111],{"class":110},[100,301,303],{"class":302},"sDGX8","10\n",[100,305,307,310],{"class":102,"line":306},20,[100,308,309],{"class":106},"fields",[100,311,124],{"class":110},[100,313,315,318],{"class":102,"line":314},21,[100,316,317],{"class":106},"  calendar_entity_id",[100,319,124],{"class":110},[100,321,323,326],{"class":102,"line":322},22,[100,324,325],{"class":106},"    selector",[100,327,124],{"class":110},[100,329,331,334],{"class":102,"line":330},23,[100,332,333],{"class":106},"      entity",[100,335,336],{"class":110},": {}\n",[100,338,340,343,345],{"class":102,"line":339},24,[100,341,342],{"class":106},"    name",[100,344,111],{"class":110},[100,346,347],{"class":114},"Calendar Entity ID\n",[100,349,351,354,356],{"class":102,"line":350},25,[100,352,353],{"class":106},"    description",[100,355,111],{"class":110},[100,357,358],{"class":114},"The entity ID of the calendar to query.\n",[100,360,362,365,367],{"class":102,"line":361},26,[100,363,364],{"class":106},"    required",[100,366,111],{"class":110},[100,368,369],{"class":106},"true\n",[100,371,373,376],{"class":102,"line":372},27,[100,374,375],{"class":106},"  start_date_time",[100,377,124],{"class":110},[100,379,381,383],{"class":102,"line":380},28,[100,382,325],{"class":106},[100,384,124],{"class":110},[100,386,388,391],{"class":102,"line":387},29,[100,389,390],{"class":106},"      datetime",[100,392,336],{"class":110},[100,394,396,398,400],{"class":102,"line":395},30,[100,397,342],{"class":106},[100,399,111],{"class":110},[100,401,402],{"class":114},"Start Date Time\n",[100,404,406,408,410,414],{"class":102,"line":405},31,[100,407,353],{"class":106},[100,409,111],{"class":110},[100,411,413],{"class":412},"sYbnZ",">",[100,415,416],{"class":106},"-\n",[100,418,420],{"class":102,"line":419},32,[100,421,422],{"class":114},"      Return active events after this time (exclusive). When not set, defaults\n",[100,424,426],{"class":102,"line":425},33,[100,427,428],{"class":114},"      to now.\n",[100,430,432,434,436],{"class":102,"line":431},34,[100,433,364],{"class":106},[100,435,111],{"class":110},[100,437,438],{"class":106},"false\n",[100,440,442,445],{"class":102,"line":441},35,[100,443,444],{"class":106},"  duration",[100,446,124],{"class":110},[100,448,450,452],{"class":102,"line":449},36,[100,451,325],{"class":106},[100,453,124],{"class":110},[100,455,457,459],{"class":102,"line":456},37,[100,458,162],{"class":106},[100,460,124],{"class":110},[100,462,464,467,469],{"class":102,"line":463},38,[100,465,466],{"class":106},"        enable_day",[100,468,111],{"class":110},[100,470,438],{"class":106},[100,472,474,476,478],{"class":102,"line":473},39,[100,475,342],{"class":106},[100,477,111],{"class":110},[100,479,480],{"class":114},"Duration\n",[100,482,484,486,488],{"class":102,"line":483},40,[100,485,364],{"class":106},[100,487,111],{"class":110},[100,489,438],{"class":106},[100,491,493,495,497],{"class":102,"line":492},41,[100,494,353],{"class":106},[100,496,111],{"class":110},[100,498,499],{"class":114},"Return active events from start_date_time until the specified duration.\n",[100,501,503,506],{"class":102,"line":502},42,[100,504,505],{"class":106},"    default",[100,507,124],{"class":110},[100,509,511,514,516],{"class":102,"line":510},43,[100,512,513],{"class":106},"      hours",[100,515,111],{"class":110},[100,517,518],{"class":302},"24\n",[100,520,522,525,527],{"class":102,"line":521},44,[100,523,524],{"class":106},"      minutes",[100,526,111],{"class":110},[100,528,529],{"class":302},"0\n",[100,531,533,536,538],{"class":102,"line":532},45,[100,534,535],{"class":106},"      seconds",[100,537,111],{"class":110},[100,539,529],{"class":302},[30,541,542,543,546,547,550],{},"You can trigger this script directly from ",[41,544,52],{"href":50,"rel":545},[45],", but since we want to be able to trigger the query from ",[41,548,46],{"href":43,"rel":549},[45],",\nwe need to call a script and wait for an event to get the result. To make this a little easier I use these helper functions:",[91,552,557],{"className":553,"code":554,"filename":555,"language":556,"meta":21,"style":21},"language-python shiki shiki-themes dark-plus","from datetime import datetime, timedelta\nfrom typing import Optional, Dict, Callable\n\nimport appdaemon.plugins.hass.hassapi as hass\n\nfrom util import util_common\n\n\nasync def get_calendar_events(\n    controller: hass.Hass,\n    entity_id: str,\n    start: Optional[datetime] = None,\n    end: Optional[datetime] = None,\n    duration: Optional[timedelta] = None,\n) -> Dict:\n    \"\"\"\n    Get calendar events between two dates\n    :param controller: usually 'self'\n    :param entity_id: the calendar entity to query\n    :param start: Return active events after this time (exclusive). When not set, defaults to now.\n    :param end: Return active events before this time (exclusive). Cannot be used with duration. You must specify either end_date_time or duration.\n    :param duration: Return active events from start_date_time until the specified duration. Cannot be used with end_date_time. You must specify either duration or end_date_time\n    \"\"\"\n\n    args = {}\n    if start is not None:\n        args[\"start_date_time\"] = start.isoformat()\n    # if end is not None:\n    #    args[\"end_date_time\"] = end.isoformat()\n    if duration is not None:\n        hours = (duration.days * 24) + (duration.seconds \u002F\u002F 3600)\n        minutes = (duration.seconds \u002F\u002F 60) % 60\n        seconds = duration.seconds % 60\n\n        duration_args = {}\n        duration_args[\"hours\"] = hours\n        duration_args[\"minutes\"] = minutes\n        duration_args[\"seconds\"] = seconds\n\n        args[\"duration\"] = duration_args\n\n    controller.log(f\"Getting calendar events for {entity_id} with args: {args}\")\n\n    # workaround using a wrapper script, see: https:\u002F\u002Fgithub.com\u002Fappdaemon\u002Fappdaemon\u002Fissues\u002F1837\n    await util_common.run_script(\n        controller=controller,\n        name=\"get_calendar_events\",\n        variables={\n            \"calendar_entity_id\": entity_id,\n            **args\n        }\n    )\n\n    # FIXME: not yet supported by appdaemon, see: https:\u002F\u002Fgithub.com\u002Fappdaemon\u002Fappdaemon\u002Fissues\u002F1837\n    # return await controller.call_service(\n    #     \"calendar\u002Fget_events\",\n    #     entity_id=entity_id,\n    #     **args,\n    #     return_result=True,\n    # )\n\n\nasync def register_calendar_event_callback(\n    controller: hass.Hass,\n    callback: Callable,\n) -> None:\n    \"\"\"\n    Register a callback to be called when a calendar event is received\n    :param controller: usually 'self'\n    :param callback: callback to call when an event is received\n    :return: A handle that can be used to cancel the callback.\n    \"\"\"\n    return await controller.listen_event(callback, \"custom_appdaemon_get_calendar_events_result\")\n","calendar_utils.py","python",[55,558,559,573,585,591,604,608,620,624,628,643,652,666,679,690,702,707,712,717,722,727,732,737,742,746,750,755,774,785,791,796,811,828,842,849,853,858,869,879,889,893,903,907,942,946,951,959,968,982,991,1000,1006,1012,1018,1023,1035,1041,1047,1053,1059,1065,1071,1076,1081,1093,1100,1109,1119,1124,1130,1135,1141,1147,1152],{"__ignoreMap":21},[100,560,561,564,567,570],{"class":102,"line":103},[100,562,563],{"class":412},"from",[100,565,566],{"class":110}," datetime ",[100,568,569],{"class":412},"import",[100,571,572],{"class":110}," datetime, timedelta\n",[100,574,575,577,580,582],{"class":102,"line":118},[100,576,563],{"class":412},[100,578,579],{"class":110}," typing ",[100,581,569],{"class":412},[100,583,584],{"class":110}," Optional, Dict, Callable\n",[100,586,587],{"class":102,"line":22},[100,588,590],{"emptyLinePlaceholder":589},true,"\n",[100,592,593,595,598,601],{"class":102,"line":140},[100,594,569],{"class":412},[100,596,597],{"class":110}," appdaemon.plugins.hass.hassapi ",[100,599,600],{"class":412},"as",[100,602,603],{"class":110}," hass\n",[100,605,606],{"class":102,"line":148},[100,607,590],{"emptyLinePlaceholder":589},[100,609,610,612,615,617],{"class":102,"line":159},[100,611,563],{"class":412},[100,613,614],{"class":110}," util ",[100,616,569],{"class":412},[100,618,619],{"class":110}," util_common\n",[100,621,622],{"class":102,"line":167},[100,623,590],{"emptyLinePlaceholder":589},[100,625,626],{"class":102,"line":178},[100,627,590],{"emptyLinePlaceholder":589},[100,629,630,633,636,640],{"class":102,"line":189},[100,631,632],{"class":106},"async",[100,634,635],{"class":106}," def",[100,637,639],{"class":638},"sCudf"," get_calendar_events",[100,641,642],{"class":110},"(\n",[100,644,645,649],{"class":102,"line":200},[100,646,648],{"class":647},"s3F5K","    controller",[100,650,651],{"class":110},": hass.Hass,\n",[100,653,654,657,659,663],{"class":102,"line":211},[100,655,656],{"class":647},"    entity_id",[100,658,111],{"class":110},[100,660,662],{"class":661},"sNl3T","str",[100,664,665],{"class":110},",\n",[100,667,668,671,674,677],{"class":102,"line":219},[100,669,670],{"class":647},"    start",[100,672,673],{"class":110},": Optional[datetime] = ",[100,675,676],{"class":106},"None",[100,678,665],{"class":110},[100,680,681,684,686,688],{"class":102,"line":230},[100,682,683],{"class":647},"    end",[100,685,673],{"class":110},[100,687,676],{"class":106},[100,689,665],{"class":110},[100,691,692,695,698,700],{"class":102,"line":240},[100,693,694],{"class":647},"    duration",[100,696,697],{"class":110},": Optional[timedelta] = ",[100,699,676],{"class":106},[100,701,665],{"class":110},[100,703,704],{"class":102,"line":251},[100,705,706],{"class":110},") -> Dict:\n",[100,708,709],{"class":102,"line":264},[100,710,711],{"class":114},"    \"\"\"\n",[100,713,714],{"class":102,"line":272},[100,715,716],{"class":114},"    Get calendar events between two dates\n",[100,718,719],{"class":102,"line":283},[100,720,721],{"class":114},"    :param controller: usually 'self'\n",[100,723,724],{"class":102,"line":294},[100,725,726],{"class":114},"    :param entity_id: the calendar entity to query\n",[100,728,729],{"class":102,"line":306},[100,730,731],{"class":114},"    :param start: Return active events after this time (exclusive). When not set, defaults to now.\n",[100,733,734],{"class":102,"line":314},[100,735,736],{"class":114},"    :param end: Return active events before this time (exclusive). Cannot be used with duration. You must specify either end_date_time or duration.\n",[100,738,739],{"class":102,"line":322},[100,740,741],{"class":114},"    :param duration: Return active events from start_date_time until the specified duration. Cannot be used with end_date_time. You must specify either duration or end_date_time\n",[100,743,744],{"class":102,"line":330},[100,745,711],{"class":114},[100,747,748],{"class":102,"line":339},[100,749,590],{"emptyLinePlaceholder":589},[100,751,752],{"class":102,"line":350},[100,753,754],{"class":110},"    args = {}\n",[100,756,757,760,763,766,769,772],{"class":102,"line":361},[100,758,759],{"class":412},"    if",[100,761,762],{"class":110}," start ",[100,764,765],{"class":106},"is",[100,767,768],{"class":106}," not",[100,770,771],{"class":106}," None",[100,773,124],{"class":110},[100,775,776,779,782],{"class":102,"line":372},[100,777,778],{"class":110},"        args[",[100,780,781],{"class":114},"\"start_date_time\"",[100,783,784],{"class":110},"] = start.isoformat()\n",[100,786,787],{"class":102,"line":380},[100,788,790],{"class":789},"sOLPB","    # if end is not None:\n",[100,792,793],{"class":102,"line":387},[100,794,795],{"class":789},"    #    args[\"end_date_time\"] = end.isoformat()\n",[100,797,798,800,803,805,807,809],{"class":102,"line":395},[100,799,759],{"class":412},[100,801,802],{"class":110}," duration ",[100,804,765],{"class":106},[100,806,768],{"class":106},[100,808,771],{"class":106},[100,810,124],{"class":110},[100,812,813,816,819,822,825],{"class":102,"line":405},[100,814,815],{"class":110},"        hours = (duration.days * ",[100,817,818],{"class":302},"24",[100,820,821],{"class":110},") + (duration.seconds \u002F\u002F ",[100,823,824],{"class":302},"3600",[100,826,827],{"class":110},")\n",[100,829,830,833,836,839],{"class":102,"line":419},[100,831,832],{"class":110},"        minutes = (duration.seconds \u002F\u002F ",[100,834,835],{"class":302},"60",[100,837,838],{"class":110},") % ",[100,840,841],{"class":302},"60\n",[100,843,844,847],{"class":102,"line":425},[100,845,846],{"class":110},"        seconds = duration.seconds % ",[100,848,841],{"class":302},[100,850,851],{"class":102,"line":431},[100,852,590],{"emptyLinePlaceholder":589},[100,854,855],{"class":102,"line":441},[100,856,857],{"class":110},"        duration_args = {}\n",[100,859,860,863,866],{"class":102,"line":449},[100,861,862],{"class":110},"        duration_args[",[100,864,865],{"class":114},"\"hours\"",[100,867,868],{"class":110},"] = hours\n",[100,870,871,873,876],{"class":102,"line":456},[100,872,862],{"class":110},[100,874,875],{"class":114},"\"minutes\"",[100,877,878],{"class":110},"] = minutes\n",[100,880,881,883,886],{"class":102,"line":463},[100,882,862],{"class":110},[100,884,885],{"class":114},"\"seconds\"",[100,887,888],{"class":110},"] = seconds\n",[100,890,891],{"class":102,"line":473},[100,892,590],{"emptyLinePlaceholder":589},[100,894,895,897,900],{"class":102,"line":483},[100,896,778],{"class":110},[100,898,899],{"class":114},"\"duration\"",[100,901,902],{"class":110},"] = duration_args\n",[100,904,905],{"class":102,"line":492},[100,906,590],{"emptyLinePlaceholder":589},[100,908,909,912,915,918,921,924,927,930,932,935,937,940],{"class":102,"line":502},[100,910,911],{"class":110},"    controller.log(",[100,913,914],{"class":106},"f",[100,916,917],{"class":114},"\"Getting calendar events for ",[100,919,920],{"class":106},"{",[100,922,923],{"class":110},"entity_id",[100,925,926],{"class":106},"}",[100,928,929],{"class":114}," with args: ",[100,931,920],{"class":106},[100,933,934],{"class":110},"args",[100,936,926],{"class":106},[100,938,939],{"class":114},"\"",[100,941,827],{"class":110},[100,943,944],{"class":102,"line":510},[100,945,590],{"emptyLinePlaceholder":589},[100,947,948],{"class":102,"line":521},[100,949,950],{"class":789},"    # workaround using a wrapper script, see: https:\u002F\u002Fgithub.com\u002Fappdaemon\u002Fappdaemon\u002Fissues\u002F1837\n",[100,952,953,956],{"class":102,"line":532},[100,954,955],{"class":412},"    await",[100,957,958],{"class":110}," util_common.run_script(\n",[100,960,962,965],{"class":102,"line":961},46,[100,963,964],{"class":647},"        controller",[100,966,967],{"class":110},"=controller,\n",[100,969,971,974,977,980],{"class":102,"line":970},47,[100,972,973],{"class":647},"        name",[100,975,976],{"class":110},"=",[100,978,979],{"class":114},"\"get_calendar_events\"",[100,981,665],{"class":110},[100,983,985,988],{"class":102,"line":984},48,[100,986,987],{"class":647},"        variables",[100,989,990],{"class":110},"={\n",[100,992,994,997],{"class":102,"line":993},49,[100,995,996],{"class":114},"            \"calendar_entity_id\"",[100,998,999],{"class":110},": entity_id,\n",[100,1001,1003],{"class":102,"line":1002},50,[100,1004,1005],{"class":110},"            **args\n",[100,1007,1009],{"class":102,"line":1008},51,[100,1010,1011],{"class":110},"        }\n",[100,1013,1015],{"class":102,"line":1014},52,[100,1016,1017],{"class":110},"    )\n",[100,1019,1021],{"class":102,"line":1020},53,[100,1022,590],{"emptyLinePlaceholder":589},[100,1024,1026,1029,1032],{"class":102,"line":1025},54,[100,1027,1028],{"class":789},"    # ",[100,1030,1031],{"class":106},"FIXME",[100,1033,1034],{"class":789},": not yet supported by appdaemon, see: https:\u002F\u002Fgithub.com\u002Fappdaemon\u002Fappdaemon\u002Fissues\u002F1837\n",[100,1036,1038],{"class":102,"line":1037},55,[100,1039,1040],{"class":789},"    # return await controller.call_service(\n",[100,1042,1044],{"class":102,"line":1043},56,[100,1045,1046],{"class":789},"    #     \"calendar\u002Fget_events\",\n",[100,1048,1050],{"class":102,"line":1049},57,[100,1051,1052],{"class":789},"    #     entity_id=entity_id,\n",[100,1054,1056],{"class":102,"line":1055},58,[100,1057,1058],{"class":789},"    #     **args,\n",[100,1060,1062],{"class":102,"line":1061},59,[100,1063,1064],{"class":789},"    #     return_result=True,\n",[100,1066,1068],{"class":102,"line":1067},60,[100,1069,1070],{"class":789},"    # )\n",[100,1072,1074],{"class":102,"line":1073},61,[100,1075,590],{"emptyLinePlaceholder":589},[100,1077,1079],{"class":102,"line":1078},62,[100,1080,590],{"emptyLinePlaceholder":589},[100,1082,1084,1086,1088,1091],{"class":102,"line":1083},63,[100,1085,632],{"class":106},[100,1087,635],{"class":106},[100,1089,1090],{"class":638}," register_calendar_event_callback",[100,1092,642],{"class":110},[100,1094,1096,1098],{"class":102,"line":1095},64,[100,1097,648],{"class":647},[100,1099,651],{"class":110},[100,1101,1103,1106],{"class":102,"line":1102},65,[100,1104,1105],{"class":647},"    callback",[100,1107,1108],{"class":110},": Callable,\n",[100,1110,1112,1115,1117],{"class":102,"line":1111},66,[100,1113,1114],{"class":110},") -> ",[100,1116,676],{"class":106},[100,1118,124],{"class":110},[100,1120,1122],{"class":102,"line":1121},67,[100,1123,711],{"class":114},[100,1125,1127],{"class":102,"line":1126},68,[100,1128,1129],{"class":114},"    Register a callback to be called when a calendar event is received\n",[100,1131,1133],{"class":102,"line":1132},69,[100,1134,721],{"class":114},[100,1136,1138],{"class":102,"line":1137},70,[100,1139,1140],{"class":114},"    :param callback: callback to call when an event is received\n",[100,1142,1144],{"class":102,"line":1143},71,[100,1145,1146],{"class":114},"    :return: A handle that can be used to cancel the callback.\n",[100,1148,1150],{"class":102,"line":1149},72,[100,1151,711],{"class":114},[100,1153,1155,1158,1161,1164,1167],{"class":102,"line":1154},73,[100,1156,1157],{"class":412},"    return",[100,1159,1160],{"class":412}," await",[100,1162,1163],{"class":110}," controller.listen_event(callback, ",[100,1165,1166],{"class":114},"\"custom_appdaemon_get_calendar_events_result\"",[100,1168,827],{"class":110},[30,1170,1171,1172,1175,1176,1179],{},"Since we need to keep track of event data within ",[41,1173,46],{"href":43,"rel":1174},[45],", I also created an App for this exact purpose, called ",[55,1177,1178],{},"CalendarController",":",[91,1181,1184],{"className":93,"code":1182,"filename":1183,"language":96,"meta":21,"style":21},"---\ncalendar_controller:\n  module: calendar_controller\n  class: CalendarController\n...\n","calendar.yaml",[55,1185,1186,1191,1198,1208,1218],{"__ignoreMap":21},[100,1187,1188],{"class":102,"line":103},[100,1189,1190],{"class":110},"---\n",[100,1192,1193,1196],{"class":102,"line":118},[100,1194,1195],{"class":106},"calendar_controller",[100,1197,124],{"class":110},[100,1199,1200,1203,1205],{"class":102,"line":22},[100,1201,1202],{"class":106},"  module",[100,1204,111],{"class":110},[100,1206,1207],{"class":114},"calendar_controller\n",[100,1209,1210,1213,1215],{"class":102,"line":140},[100,1211,1212],{"class":106},"  class",[100,1214,111],{"class":110},[100,1216,1217],{"class":114},"CalendarController\n",[100,1219,1220],{"class":102,"line":148},[100,1221,1222],{"class":110},"...\n",[91,1224,1227],{"className":553,"code":1225,"filename":1226,"language":556,"meta":21,"style":21},"from datetime import datetime, timedelta\nfrom typing import Dict, Callable, List\n\nfrom base import BaseApp\nfrom const import *\nfrom util import util_calendar\n\n\n# Used to keep track of calendar events\n# and help with tracking event states.\nclass CalendarController(BaseApp):\n\n    async def initialize(self):\n        # used to keep track of calendar data\n        self._calendar_data = {}\n        # used to keep track of callbacks\n        self._state_callbacks = {}\n        await util_calendar.register_calendar_event_callback(\n            controller=self,\n            callback=self._on_calendar_events_callback\n        )\n\n        await self._update_calendar_events(None, None, None, None, None)\n        # this essentially runs every minute\n        await self.listen_state(self._update_calendar_events, \"sensor.date_time\")\n\n    async def _on_calendar_events_callback(self, event_id, payload_event, *args):\n        self.log(f\"Received calendar result event '{event_id}': {payload_event}\")\n        self._calendar_data = payload_event[\"data\"]\n\n        for key, callback_data in self._state_callbacks.items():\n            callback = callback_data[\"callback\"]\n            condition = callback_data[\"condition\"]\n            if condition is not None:\n                if condition():\n                    await callback()\n            else:\n                await callback()\n\n    async def _update_calendar_events(self, entity, attribute, old, new, kwargs):\n        # query data for all of the calendar entities you are interested in\n        await util_calendar.get_calendar_events(\n            controller=self,\n            entity_id=CALENDAR_IRIS_MARKUS_ENTITY,\n            start=datetime.now(),\n            duration=timedelta(hours=720) # events within the coming 30 days\n        )\n\n    async def get_current_events(self, calendar_id: str or None = None) -> List[Dict]:\n        \"\"\"\n        Get a list of all current events for a calendar\n        :param calendar_id: the id of the calendar, if None, all calendars are queried\n        :return: a list of events\n        \"\"\"\n        result = []\n        for cid, calendar_data in self._calendar_data.items():\n            if calendar_id is not None and cid != calendar_id:\n                continue\n            calendar_events = calendar_data.get(\"events\", [])\n            result.extend(calendar_events)\n\n        result = [event for event in result if\n                  datetime.fromisoformat(\n                      event[\"start\"]).timestamp() \u003C datetime.now().timestamp() \u003C datetime.fromisoformat(\n                      event[\"end\"]).timestamp()]\n        return result\n\n    async def register_state_callback(self, key: str, callback: Callable, condition: Callable or None = None):\n        \"\"\"\n        Register a callback to be called when the state of the calendar changes\n        and the value of the condition changed.\n        :param key: the key of the callback\n        :param callback: the callback to call\n        :param condition: the condition to check, returns True or False\n        \"\"\"\n        self._state_callbacks[key] = {\n            \"callback\": callback,\n            \"condition\": condition\n        }\n","calendar_controller.py",[55,1228,1229,1239,1250,1254,1266,1278,1289,1293,1297,1302,1307,1324,1328,1345,1350,1358,1363,1370,1378,1389,1401,1406,1410,1441,1446,1465,1469,1499,1530,1543,1547,1563,1573,1583,1599,1607,1615,1622,1629,1633,1673,1678,1685,1695,1703,1711,1733,1737,1741,1776,1781,1786,1791,1796,1800,1805,1819,1838,1843,1854,1859,1863,1882,1887,1898,1908,1916,1920,1967,1971,1976,1981,1986,1991,1997,2002,2010,2019,2028],{"__ignoreMap":21},[100,1230,1231,1233,1235,1237],{"class":102,"line":103},[100,1232,563],{"class":412},[100,1234,566],{"class":110},[100,1236,569],{"class":412},[100,1238,572],{"class":110},[100,1240,1241,1243,1245,1247],{"class":102,"line":118},[100,1242,563],{"class":412},[100,1244,579],{"class":110},[100,1246,569],{"class":412},[100,1248,1249],{"class":110}," Dict, Callable, List\n",[100,1251,1252],{"class":102,"line":22},[100,1253,590],{"emptyLinePlaceholder":589},[100,1255,1256,1258,1261,1263],{"class":102,"line":140},[100,1257,563],{"class":412},[100,1259,1260],{"class":110}," base ",[100,1262,569],{"class":412},[100,1264,1265],{"class":110}," BaseApp\n",[100,1267,1268,1270,1273,1275],{"class":102,"line":148},[100,1269,563],{"class":412},[100,1271,1272],{"class":110}," const ",[100,1274,569],{"class":412},[100,1276,1277],{"class":110}," *\n",[100,1279,1280,1282,1284,1286],{"class":102,"line":159},[100,1281,563],{"class":412},[100,1283,614],{"class":110},[100,1285,569],{"class":412},[100,1287,1288],{"class":110}," util_calendar\n",[100,1290,1291],{"class":102,"line":167},[100,1292,590],{"emptyLinePlaceholder":589},[100,1294,1295],{"class":102,"line":178},[100,1296,590],{"emptyLinePlaceholder":589},[100,1298,1299],{"class":102,"line":189},[100,1300,1301],{"class":789},"# Used to keep track of calendar events\n",[100,1303,1304],{"class":102,"line":200},[100,1305,1306],{"class":789},"# and help with tracking event states.\n",[100,1308,1309,1312,1315,1318,1321],{"class":102,"line":211},[100,1310,1311],{"class":106},"class",[100,1313,1314],{"class":661}," CalendarController",[100,1316,1317],{"class":110},"(",[100,1319,1320],{"class":661},"BaseApp",[100,1322,1323],{"class":110},"):\n",[100,1325,1326],{"class":102,"line":219},[100,1327,590],{"emptyLinePlaceholder":589},[100,1329,1330,1333,1335,1338,1340,1343],{"class":102,"line":230},[100,1331,1332],{"class":106},"    async",[100,1334,635],{"class":106},[100,1336,1337],{"class":638}," initialize",[100,1339,1317],{"class":110},[100,1341,1342],{"class":647},"self",[100,1344,1323],{"class":110},[100,1346,1347],{"class":102,"line":240},[100,1348,1349],{"class":789},"        # used to keep track of calendar data\n",[100,1351,1352,1355],{"class":102,"line":251},[100,1353,1354],{"class":106},"        self",[100,1356,1357],{"class":110},"._calendar_data = {}\n",[100,1359,1360],{"class":102,"line":264},[100,1361,1362],{"class":789},"        # used to keep track of callbacks\n",[100,1364,1365,1367],{"class":102,"line":272},[100,1366,1354],{"class":106},[100,1368,1369],{"class":110},"._state_callbacks = {}\n",[100,1371,1372,1375],{"class":102,"line":283},[100,1373,1374],{"class":412},"        await",[100,1376,1377],{"class":110}," util_calendar.register_calendar_event_callback(\n",[100,1379,1380,1383,1385,1387],{"class":102,"line":294},[100,1381,1382],{"class":647},"            controller",[100,1384,976],{"class":110},[100,1386,1342],{"class":106},[100,1388,665],{"class":110},[100,1390,1391,1394,1396,1398],{"class":102,"line":306},[100,1392,1393],{"class":647},"            callback",[100,1395,976],{"class":110},[100,1397,1342],{"class":106},[100,1399,1400],{"class":110},"._on_calendar_events_callback\n",[100,1402,1403],{"class":102,"line":314},[100,1404,1405],{"class":110},"        )\n",[100,1407,1408],{"class":102,"line":322},[100,1409,590],{"emptyLinePlaceholder":589},[100,1411,1412,1414,1417,1420,1422,1425,1427,1429,1431,1433,1435,1437,1439],{"class":102,"line":330},[100,1413,1374],{"class":412},[100,1415,1416],{"class":106}," self",[100,1418,1419],{"class":110},"._update_calendar_events(",[100,1421,676],{"class":106},[100,1423,1424],{"class":110},", ",[100,1426,676],{"class":106},[100,1428,1424],{"class":110},[100,1430,676],{"class":106},[100,1432,1424],{"class":110},[100,1434,676],{"class":106},[100,1436,1424],{"class":110},[100,1438,676],{"class":106},[100,1440,827],{"class":110},[100,1442,1443],{"class":102,"line":339},[100,1444,1445],{"class":789},"        # this essentially runs every minute\n",[100,1447,1448,1450,1452,1455,1457,1460,1463],{"class":102,"line":350},[100,1449,1374],{"class":412},[100,1451,1416],{"class":106},[100,1453,1454],{"class":110},".listen_state(",[100,1456,1342],{"class":106},[100,1458,1459],{"class":110},"._update_calendar_events, ",[100,1461,1462],{"class":114},"\"sensor.date_time\"",[100,1464,827],{"class":110},[100,1466,1467],{"class":102,"line":361},[100,1468,590],{"emptyLinePlaceholder":589},[100,1470,1471,1473,1475,1478,1480,1482,1484,1487,1489,1492,1495,1497],{"class":102,"line":372},[100,1472,1332],{"class":106},[100,1474,635],{"class":106},[100,1476,1477],{"class":638}," _on_calendar_events_callback",[100,1479,1317],{"class":110},[100,1481,1342],{"class":647},[100,1483,1424],{"class":110},[100,1485,1486],{"class":647},"event_id",[100,1488,1424],{"class":110},[100,1490,1491],{"class":647},"payload_event",[100,1493,1494],{"class":110},", *",[100,1496,934],{"class":647},[100,1498,1323],{"class":110},[100,1500,1501,1503,1506,1508,1511,1513,1515,1517,1520,1522,1524,1526,1528],{"class":102,"line":380},[100,1502,1354],{"class":106},[100,1504,1505],{"class":110},".log(",[100,1507,914],{"class":106},[100,1509,1510],{"class":114},"\"Received calendar result event '",[100,1512,920],{"class":106},[100,1514,1486],{"class":110},[100,1516,926],{"class":106},[100,1518,1519],{"class":114},"': ",[100,1521,920],{"class":106},[100,1523,1491],{"class":110},[100,1525,926],{"class":106},[100,1527,939],{"class":114},[100,1529,827],{"class":110},[100,1531,1532,1534,1537,1540],{"class":102,"line":387},[100,1533,1354],{"class":106},[100,1535,1536],{"class":110},"._calendar_data = payload_event[",[100,1538,1539],{"class":114},"\"data\"",[100,1541,1542],{"class":110},"]\n",[100,1544,1545],{"class":102,"line":395},[100,1546,590],{"emptyLinePlaceholder":589},[100,1548,1549,1552,1555,1558,1560],{"class":102,"line":405},[100,1550,1551],{"class":412},"        for",[100,1553,1554],{"class":110}," key, callback_data ",[100,1556,1557],{"class":412},"in",[100,1559,1416],{"class":106},[100,1561,1562],{"class":110},"._state_callbacks.items():\n",[100,1564,1565,1568,1571],{"class":102,"line":419},[100,1566,1567],{"class":110},"            callback = callback_data[",[100,1569,1570],{"class":114},"\"callback\"",[100,1572,1542],{"class":110},[100,1574,1575,1578,1581],{"class":102,"line":425},[100,1576,1577],{"class":110},"            condition = callback_data[",[100,1579,1580],{"class":114},"\"condition\"",[100,1582,1542],{"class":110},[100,1584,1585,1588,1591,1593,1595,1597],{"class":102,"line":431},[100,1586,1587],{"class":412},"            if",[100,1589,1590],{"class":110}," condition ",[100,1592,765],{"class":106},[100,1594,768],{"class":106},[100,1596,771],{"class":106},[100,1598,124],{"class":110},[100,1600,1601,1604],{"class":102,"line":441},[100,1602,1603],{"class":412},"                if",[100,1605,1606],{"class":110}," condition():\n",[100,1608,1609,1612],{"class":102,"line":449},[100,1610,1611],{"class":412},"                    await",[100,1613,1614],{"class":110}," callback()\n",[100,1616,1617,1620],{"class":102,"line":456},[100,1618,1619],{"class":412},"            else",[100,1621,124],{"class":110},[100,1623,1624,1627],{"class":102,"line":463},[100,1625,1626],{"class":412},"                await",[100,1628,1614],{"class":110},[100,1630,1631],{"class":102,"line":473},[100,1632,590],{"emptyLinePlaceholder":589},[100,1634,1635,1637,1639,1642,1644,1646,1648,1651,1653,1656,1658,1661,1663,1666,1668,1671],{"class":102,"line":483},[100,1636,1332],{"class":106},[100,1638,635],{"class":106},[100,1640,1641],{"class":638}," _update_calendar_events",[100,1643,1317],{"class":110},[100,1645,1342],{"class":647},[100,1647,1424],{"class":110},[100,1649,1650],{"class":647},"entity",[100,1652,1424],{"class":110},[100,1654,1655],{"class":647},"attribute",[100,1657,1424],{"class":110},[100,1659,1660],{"class":647},"old",[100,1662,1424],{"class":110},[100,1664,1665],{"class":647},"new",[100,1667,1424],{"class":110},[100,1669,1670],{"class":647},"kwargs",[100,1672,1323],{"class":110},[100,1674,1675],{"class":102,"line":492},[100,1676,1677],{"class":789},"        # query data for all of the calendar entities you are interested in\n",[100,1679,1680,1682],{"class":102,"line":502},[100,1681,1374],{"class":412},[100,1683,1684],{"class":110}," util_calendar.get_calendar_events(\n",[100,1686,1687,1689,1691,1693],{"class":102,"line":510},[100,1688,1382],{"class":647},[100,1690,976],{"class":110},[100,1692,1342],{"class":106},[100,1694,665],{"class":110},[100,1696,1697,1700],{"class":102,"line":521},[100,1698,1699],{"class":647},"            entity_id",[100,1701,1702],{"class":110},"=CALENDAR_IRIS_MARKUS_ENTITY,\n",[100,1704,1705,1708],{"class":102,"line":532},[100,1706,1707],{"class":647},"            start",[100,1709,1710],{"class":110},"=datetime.now(),\n",[100,1712,1713,1716,1719,1722,1724,1727,1730],{"class":102,"line":961},[100,1714,1715],{"class":647},"            duration",[100,1717,1718],{"class":110},"=timedelta(",[100,1720,1721],{"class":647},"hours",[100,1723,976],{"class":110},[100,1725,1726],{"class":302},"720",[100,1728,1729],{"class":110},") ",[100,1731,1732],{"class":789},"# events within the coming 30 days\n",[100,1734,1735],{"class":102,"line":970},[100,1736,1405],{"class":110},[100,1738,1739],{"class":102,"line":984},[100,1740,590],{"emptyLinePlaceholder":589},[100,1742,1743,1745,1747,1750,1752,1754,1756,1759,1761,1763,1766,1768,1771,1773],{"class":102,"line":993},[100,1744,1332],{"class":106},[100,1746,635],{"class":106},[100,1748,1749],{"class":638}," get_current_events",[100,1751,1317],{"class":110},[100,1753,1342],{"class":647},[100,1755,1424],{"class":110},[100,1757,1758],{"class":647},"calendar_id",[100,1760,111],{"class":110},[100,1762,662],{"class":661},[100,1764,1765],{"class":106}," or",[100,1767,771],{"class":106},[100,1769,1770],{"class":110}," = ",[100,1772,676],{"class":106},[100,1774,1775],{"class":110},") -> List[Dict]:\n",[100,1777,1778],{"class":102,"line":1002},[100,1779,1780],{"class":114},"        \"\"\"\n",[100,1782,1783],{"class":102,"line":1008},[100,1784,1785],{"class":114},"        Get a list of all current events for a calendar\n",[100,1787,1788],{"class":102,"line":1014},[100,1789,1790],{"class":114},"        :param calendar_id: the id of the calendar, if None, all calendars are queried\n",[100,1792,1793],{"class":102,"line":1020},[100,1794,1795],{"class":114},"        :return: a list of events\n",[100,1797,1798],{"class":102,"line":1025},[100,1799,1780],{"class":114},[100,1801,1802],{"class":102,"line":1037},[100,1803,1804],{"class":110},"        result = []\n",[100,1806,1807,1809,1812,1814,1816],{"class":102,"line":1043},[100,1808,1551],{"class":412},[100,1810,1811],{"class":110}," cid, calendar_data ",[100,1813,1557],{"class":412},[100,1815,1416],{"class":106},[100,1817,1818],{"class":110},"._calendar_data.items():\n",[100,1820,1821,1823,1826,1828,1830,1832,1835],{"class":102,"line":1049},[100,1822,1587],{"class":412},[100,1824,1825],{"class":110}," calendar_id ",[100,1827,765],{"class":106},[100,1829,768],{"class":106},[100,1831,771],{"class":106},[100,1833,1834],{"class":106}," and",[100,1836,1837],{"class":110}," cid != calendar_id:\n",[100,1839,1840],{"class":102,"line":1055},[100,1841,1842],{"class":412},"                continue\n",[100,1844,1845,1848,1851],{"class":102,"line":1061},[100,1846,1847],{"class":110},"            calendar_events = calendar_data.get(",[100,1849,1850],{"class":114},"\"events\"",[100,1852,1853],{"class":110},", [])\n",[100,1855,1856],{"class":102,"line":1067},[100,1857,1858],{"class":110},"            result.extend(calendar_events)\n",[100,1860,1861],{"class":102,"line":1073},[100,1862,590],{"emptyLinePlaceholder":589},[100,1864,1865,1868,1871,1874,1876,1879],{"class":102,"line":1078},[100,1866,1867],{"class":110},"        result = [event ",[100,1869,1870],{"class":412},"for",[100,1872,1873],{"class":110}," event ",[100,1875,1557],{"class":412},[100,1877,1878],{"class":110}," result ",[100,1880,1881],{"class":412},"if\n",[100,1883,1884],{"class":102,"line":1083},[100,1885,1886],{"class":110},"                  datetime.fromisoformat(\n",[100,1888,1889,1892,1895],{"class":102,"line":1095},[100,1890,1891],{"class":110},"                      event[",[100,1893,1894],{"class":114},"\"start\"",[100,1896,1897],{"class":110},"]).timestamp() \u003C datetime.now().timestamp() \u003C datetime.fromisoformat(\n",[100,1899,1900,1902,1905],{"class":102,"line":1102},[100,1901,1891],{"class":110},[100,1903,1904],{"class":114},"\"end\"",[100,1906,1907],{"class":110},"]).timestamp()]\n",[100,1909,1910,1913],{"class":102,"line":1111},[100,1911,1912],{"class":412},"        return",[100,1914,1915],{"class":110}," result\n",[100,1917,1918],{"class":102,"line":1121},[100,1919,590],{"emptyLinePlaceholder":589},[100,1921,1922,1924,1926,1929,1931,1933,1935,1938,1940,1942,1944,1947,1950,1953,1956,1959,1961,1963,1965],{"class":102,"line":1126},[100,1923,1332],{"class":106},[100,1925,635],{"class":106},[100,1927,1928],{"class":638}," register_state_callback",[100,1930,1317],{"class":110},[100,1932,1342],{"class":647},[100,1934,1424],{"class":110},[100,1936,1937],{"class":647},"key",[100,1939,111],{"class":110},[100,1941,662],{"class":661},[100,1943,1424],{"class":110},[100,1945,1946],{"class":647},"callback",[100,1948,1949],{"class":110},": Callable, ",[100,1951,1952],{"class":647},"condition",[100,1954,1955],{"class":110},": Callable ",[100,1957,1958],{"class":106},"or",[100,1960,771],{"class":106},[100,1962,1770],{"class":110},[100,1964,676],{"class":106},[100,1966,1323],{"class":110},[100,1968,1969],{"class":102,"line":1132},[100,1970,1780],{"class":114},[100,1972,1973],{"class":102,"line":1137},[100,1974,1975],{"class":114},"        Register a callback to be called when the state of the calendar changes\n",[100,1977,1978],{"class":102,"line":1143},[100,1979,1980],{"class":114},"        and the value of the condition changed.\n",[100,1982,1983],{"class":102,"line":1149},[100,1984,1985],{"class":114},"        :param key: the key of the callback\n",[100,1987,1988],{"class":102,"line":1154},[100,1989,1990],{"class":114},"        :param callback: the callback to call\n",[100,1992,1994],{"class":102,"line":1993},74,[100,1995,1996],{"class":114},"        :param condition: the condition to check, returns True or False\n",[100,1998,2000],{"class":102,"line":1999},75,[100,2001,1780],{"class":114},[100,2003,2005,2007],{"class":102,"line":2004},76,[100,2006,1354],{"class":106},[100,2008,2009],{"class":110},"._state_callbacks[key] = {\n",[100,2011,2013,2016],{"class":102,"line":2012},77,[100,2014,2015],{"class":114},"            \"callback\"",[100,2017,2018],{"class":110},": callback,\n",[100,2020,2022,2025],{"class":102,"line":2021},78,[100,2023,2024],{"class":114},"            \"condition\"",[100,2026,2027],{"class":110},": condition\n",[100,2029,2031],{"class":102,"line":2030},79,[100,2032,1011],{"class":110},[30,2034,2035,2036,2039],{},"In an actual ",[41,2037,46],{"href":43,"rel":2038},[45]," app, the usage would then look like this:",[91,2041,2044],{"className":93,"code":2042,"filename":2043,"language":96,"meta":21,"style":21},"---\nliving_room_controller:\n  module: living_room\n  class: LivingRoomController\n  dependencies:\n    - calendar_controller\n...\n","living_room.yaml",[55,2045,2046,2050,2057,2066,2075,2082,2089],{"__ignoreMap":21},[100,2047,2048],{"class":102,"line":103},[100,2049,1190],{"class":110},[100,2051,2052,2055],{"class":102,"line":118},[100,2053,2054],{"class":106},"living_room_controller",[100,2056,124],{"class":110},[100,2058,2059,2061,2063],{"class":102,"line":22},[100,2060,1202],{"class":106},[100,2062,111],{"class":110},[100,2064,2065],{"class":114},"living_room\n",[100,2067,2068,2070,2072],{"class":102,"line":140},[100,2069,1212],{"class":106},[100,2071,111],{"class":110},[100,2073,2074],{"class":114},"LivingRoomController\n",[100,2076,2077,2080],{"class":102,"line":148},[100,2078,2079],{"class":106},"  dependencies",[100,2081,124],{"class":110},[100,2083,2084,2087],{"class":102,"line":159},[100,2085,2086],{"class":110},"    - ",[100,2088,1207],{"class":114},[100,2090,2091],{"class":102,"line":167},[100,2092,1222],{"class":110},[91,2094,2097],{"className":553,"code":2095,"filename":2096,"language":556,"meta":21,"style":21},"class LivingRoomController(BaseApp):\n\n    async def initialize(self):\n        ...\n\n        calendar_controller = await self.get_app(\"calendar_controller\")\n        await calendar_controller.register_state_callback(\"living_room\", self._calendar_data_changed_callback)\n\n        # this essentially runs every minute\n        await self._check_calendar_events(None, None, None, None, None)\n        await self.listen_state(self._check_calendar_events, \"sensor.date_time\")\n\n    async def _calendar_data_changed_callback(self):\n        await self._check_calendar_events(None, None, None, None, None)\n\n    async def _check_calendar_events(self, entity, attribute, old, new, kwargs):\n        calendar_controller = await self.get_app(\"calendar_controller\")\n        current_events = await calendar_controller.get_current_events()\n\n        # TODO: do something with the events\n\n","living_room.py",[55,2098,2099,2112,2116,2130,2135,2139,2157,2174,2178,2182,2211,2228,2232,2247,2275,2279,2314,2328,2338,2342],{"__ignoreMap":21},[100,2100,2101,2103,2106,2108,2110],{"class":102,"line":103},[100,2102,1311],{"class":106},[100,2104,2105],{"class":661}," LivingRoomController",[100,2107,1317],{"class":110},[100,2109,1320],{"class":661},[100,2111,1323],{"class":110},[100,2113,2114],{"class":102,"line":118},[100,2115,590],{"emptyLinePlaceholder":589},[100,2117,2118,2120,2122,2124,2126,2128],{"class":102,"line":22},[100,2119,1332],{"class":106},[100,2121,635],{"class":106},[100,2123,1337],{"class":638},[100,2125,1317],{"class":110},[100,2127,1342],{"class":647},[100,2129,1323],{"class":110},[100,2131,2132],{"class":102,"line":140},[100,2133,2134],{"class":110},"        ...\n",[100,2136,2137],{"class":102,"line":148},[100,2138,590],{"emptyLinePlaceholder":589},[100,2140,2141,2144,2147,2149,2152,2155],{"class":102,"line":159},[100,2142,2143],{"class":110},"        calendar_controller = ",[100,2145,2146],{"class":412},"await",[100,2148,1416],{"class":106},[100,2150,2151],{"class":110},".get_app(",[100,2153,2154],{"class":114},"\"calendar_controller\"",[100,2156,827],{"class":110},[100,2158,2159,2161,2164,2167,2169,2171],{"class":102,"line":167},[100,2160,1374],{"class":412},[100,2162,2163],{"class":110}," calendar_controller.register_state_callback(",[100,2165,2166],{"class":114},"\"living_room\"",[100,2168,1424],{"class":110},[100,2170,1342],{"class":106},[100,2172,2173],{"class":110},"._calendar_data_changed_callback)\n",[100,2175,2176],{"class":102,"line":178},[100,2177,590],{"emptyLinePlaceholder":589},[100,2179,2180],{"class":102,"line":189},[100,2181,1445],{"class":789},[100,2183,2184,2186,2188,2191,2193,2195,2197,2199,2201,2203,2205,2207,2209],{"class":102,"line":200},[100,2185,1374],{"class":412},[100,2187,1416],{"class":106},[100,2189,2190],{"class":110},"._check_calendar_events(",[100,2192,676],{"class":106},[100,2194,1424],{"class":110},[100,2196,676],{"class":106},[100,2198,1424],{"class":110},[100,2200,676],{"class":106},[100,2202,1424],{"class":110},[100,2204,676],{"class":106},[100,2206,1424],{"class":110},[100,2208,676],{"class":106},[100,2210,827],{"class":110},[100,2212,2213,2215,2217,2219,2221,2224,2226],{"class":102,"line":211},[100,2214,1374],{"class":412},[100,2216,1416],{"class":106},[100,2218,1454],{"class":110},[100,2220,1342],{"class":106},[100,2222,2223],{"class":110},"._check_calendar_events, ",[100,2225,1462],{"class":114},[100,2227,827],{"class":110},[100,2229,2230],{"class":102,"line":219},[100,2231,590],{"emptyLinePlaceholder":589},[100,2233,2234,2236,2238,2241,2243,2245],{"class":102,"line":230},[100,2235,1332],{"class":106},[100,2237,635],{"class":106},[100,2239,2240],{"class":638}," _calendar_data_changed_callback",[100,2242,1317],{"class":110},[100,2244,1342],{"class":647},[100,2246,1323],{"class":110},[100,2248,2249,2251,2253,2255,2257,2259,2261,2263,2265,2267,2269,2271,2273],{"class":102,"line":240},[100,2250,1374],{"class":412},[100,2252,1416],{"class":106},[100,2254,2190],{"class":110},[100,2256,676],{"class":106},[100,2258,1424],{"class":110},[100,2260,676],{"class":106},[100,2262,1424],{"class":110},[100,2264,676],{"class":106},[100,2266,1424],{"class":110},[100,2268,676],{"class":106},[100,2270,1424],{"class":110},[100,2272,676],{"class":106},[100,2274,827],{"class":110},[100,2276,2277],{"class":102,"line":251},[100,2278,590],{"emptyLinePlaceholder":589},[100,2280,2281,2283,2285,2288,2290,2292,2294,2296,2298,2300,2302,2304,2306,2308,2310,2312],{"class":102,"line":264},[100,2282,1332],{"class":106},[100,2284,635],{"class":106},[100,2286,2287],{"class":638}," _check_calendar_events",[100,2289,1317],{"class":110},[100,2291,1342],{"class":647},[100,2293,1424],{"class":110},[100,2295,1650],{"class":647},[100,2297,1424],{"class":110},[100,2299,1655],{"class":647},[100,2301,1424],{"class":110},[100,2303,1660],{"class":647},[100,2305,1424],{"class":110},[100,2307,1665],{"class":647},[100,2309,1424],{"class":110},[100,2311,1670],{"class":647},[100,2313,1323],{"class":110},[100,2315,2316,2318,2320,2322,2324,2326],{"class":102,"line":272},[100,2317,2143],{"class":110},[100,2319,2146],{"class":412},[100,2321,1416],{"class":106},[100,2323,2151],{"class":110},[100,2325,2154],{"class":114},[100,2327,827],{"class":110},[100,2329,2330,2333,2335],{"class":102,"line":283},[100,2331,2332],{"class":110},"        current_events = ",[100,2334,2146],{"class":412},[100,2336,2337],{"class":110}," calendar_controller.get_current_events()\n",[100,2339,2340],{"class":102,"line":294},[100,2341,590],{"emptyLinePlaceholder":589},[100,2343,2344,2347,2350],{"class":102,"line":306},[100,2345,2346],{"class":789},"        # ",[100,2348,2349],{"class":106},"TODO",[100,2351,2352],{"class":789},": do something with the events\n",[2354,2355,2356],"style",{},"html pre.shiki code .scz_3, html code.shiki .scz_3{--shiki-default:#569CD6}html pre.shiki code .sTNBD, html code.shiki .sTNBD{--shiki-default:#D4D4D4}html pre.shiki code .sKc5r, html code.shiki .sKc5r{--shiki-default:#CE9178}html pre.shiki code .sDGX8, html code.shiki .sDGX8{--shiki-default:#B5CEA8}html pre.shiki code .sYbnZ, html code.shiki .sYbnZ{--shiki-default:#C586C0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sCudf, html code.shiki .sCudf{--shiki-default:#DCDCAA}html pre.shiki code .s3F5K, html code.shiki .s3F5K{--shiki-default:#9CDCFE}html pre.shiki code .sNl3T, html code.shiki .sNl3T{--shiki-default:#4EC9B0}html pre.shiki code .sOLPB, html code.shiki .sOLPB{--shiki-default:#6A9955}",{"title":21,"searchDepth":22,"depth":22,"links":2358},[2359,2360],{"id":36,"depth":118,"text":37},{"id":67,"depth":118,"text":68},"2024-01-28",false,"\u002Fimages\u002Fblog\u002Fcalendar-integration-between-home-assistant-and-appdaemon\u002Fheader.jpg",{"excerpt":2365},{"type":18,"value":2366},[2367],[30,2368,32],{},"\u002Farticles\u002Fcalendar-integration-between-home-assistant-and-appdaemon",{"title":6,"description":32},"articles\u002Fcalendar-integration-between-home-assistant-and-appdaemon",[2373,2374,556],"homeassistant","appdaemon","rw0JxfvKvY5HLhGYjOr37OA_mVBKioNv8WqbDJ4qOVw","calendar-integration-between-home-assistant-and-appdaemon",{"title":2378,"slug":2379},"Fixing bass heavy sound on the Nvidia Shield TV Pro","nvidia-shield-tv-pro-surround-upsampling",1793999264914]