# Homey Automations

My smart home automations documented.

Here is the overall architecture of my most of my automations.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtpMHFGb9goEXQ-5Nqa%2F-LtpOxnHBsqF9WDAVMvW%2FArachiteture1b.png?alt=media\&token=f326bf01-167d-4fe1-b132-174abe453489)

In order to maintain more advanced logic for your ligthing, music, temperature etc, I reccomend to use multiple layers of state as illustrated in the figure above.

**Scenes** represent one state of multiple devices. I typically create scenes for each room. Examples of scenes can be "Bathroom night", "Bathroom dimmed", "Bathroom normal", "Living room dimmed", "Living room TV", "Living room dinner".

**Aggregated states** are varaibles representing a simple value for something. Most of my aggregated states are boolean, such as "is the TV on", "is the sun outside above 30 degrees above horizon", "is someone at home", "is there someone present in the living room". Often multiple sensors are involved in updating an aggregated state, such as a combination of door sensors and motion sensors to represent presence in a room, or multiple presence devices for multiple people to represent whether or not somebody is at home. I also have a few aggregated states that are strings, and not boolean, such as the [timeperiod](/aggregated-state/time-table), which can have one of a few predefined text values.

{% hint style="info" %}

### Where to store state?

Currently I am using [better logic](https://homey.app/en-us/app/net.i-dev.betterlogic/Better-Logic/) to store variables. It has the same functionality as the built-in logic, but in addition it has a card that triggers when one of a list of variables is changed. This is crucial functinoality to implement the model above. Therefore I am using better logic for now. If the buit-in logic later gets the same functionality I will switch back.
{% endhint %}

### Getting started

Here is my reccomended steps to getting started

* Setup logic variables for presence - [Person presence](/aggregated-state/person-presence)
* Add a guest virtual device, and [setup an aggregated logic for](/aggregated-state/home-state) `home`&#x20;
* [Create scene variables and scene flows for your rooms](/lighting/scenes)
* [Setup a time table](/aggregated-state/time-table)
* Then [setup room and floor presence](/aggregated-state/room-and-floor-presence)
* And now you are ready to sew everything together with [Scene activations](/lighting/scene-activations)

Test each step independently by inspecting the logic view on your phone.

**Good luck!**


# Person presence

For each person, I've created a logic boolean variable, such as i.e. `presence-andreas`

Strictly it is not needed to create an aggregate variable for presence, since I'm currently only using the Homey mobile app as input for this state. But the reason I've done this, is that I expect that I might want to add multiple input sources for this value at a later stage to improve reliability.

If you are happy with the iOS app, you may stick that that for presence tracking, but to increase reliability you may add multiple input sources. I can reccomend using Geofency for iOS users. [I've documented how to setup Geofency](/aggregated-state/person-presence/geofency-and-webhooks). If you do not use iOS, most likely there is a similar app for Android. Life360 is another option.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtnYRzX2U2uuYlVeNjt%2F-LtnZdBuz7VDku9L0ohw%2FSkjermbilde%202019-11-16%20kl.%2010.36.54.png?alt=media\&token=8490cebe-0e46-4040-8c05-b595cfaba20b)

For each person, I need two flows for each input source. If I need to expand this with multiple input sources later, the logic will be like:

* If Andreas came home \[mobile app], then set `presence-andreas` to true.
* If Andreas left home \[mobile app], then set `presence-andreas` to false.
* If Andreas came home \[geofency], then set `presence-andreas` to true.
* If Andreas left home \[geofency], then set `presence-andreas` to false.


# Geofency and webhooks

Adding reliability to presence tracking for a person.

If the built-in app is not sufficient reliable, you may add more sources to aggregate the presence of a person.

I can reccomend [Geofency](https://www.geofency.com) for iOS users. Geofency uses geo-fences or iBeacons (bluetooth LTE) to track when you enter and leave a zone. When you enter or leace a zone, you can configure geofency to trigger a webhook. And that is what you should do.

First setup a flow for each person to track.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtuptYEbEoh4uKfO5jA%2F-Ltuq_IrSEPblLlkVhZt%2FSkjermbilde%202019-11-17%20kl.%2020.32.37.png?alt=media\&token=3fb1d68d-225c-4765-ac8c-9cbc58529d79)

Notice the documentation to the right. To find your Homey cloud ID, open the Homey app and go to **More.. - Settings - General**.

You will need to setup a GET webhook request for when you enter your home, and one for when you leave. The URL will be something like this:

```
https://<cloudid>.connect.athom.com/api/manager/logic/webhook/presence-andreas?tag=Yes
```

And, the same with a different tag value for when you leave the zone:

```
https://<cloudid>.connect.athom.com/api/manager/logic/webhook/presence-andreas?tag=No
```

Now, install and setup geofency.

![The overview in the app shows you all the zones](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtuptYEbEoh4uKfO5jA%2F-LturOe3iksmT4ykqg2F%2FPNG-bilde.png?alt=media\&token=4d0c75a8-aa6c-4450-b238-f85a9ab977e9)

Create a home zone. You may off course add other zones as well, if needed. You may add a zone to detect when you leave work, or you make create a big circle to track when you leace the city.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtuptYEbEoh4uKfO5jA%2F-Lturl-fAi2CIRlJLYbF%2FSkjermbilde%202019%E2%80%9311%E2%80%9317%20kl.%C2%A020.27.00.png?alt=media\&token=83a5e9bf-fe63-4057-bb15-40458e6610aa)

Check the settings for your zone, and choose to add a webhook.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtuptYEbEoh4uKfO5jA%2F-LtursHEvY-UjDpZWDF3%2FSkjermbilde%202019%E2%80%9311%E2%80%9317%20kl.%C2%A020.28.05.png?alt=media\&token=7b0cf3d9-f49c-49bc-aefe-f8251309266c)

And add the enter and leave URLs. Remember to configure GET and not POST.


# Home state

One of the most important states of my home is whether or not there is anyone at home.

I've created a boolean logic variable `home`for this purpose.

### Person presence

I got one boolean presence variable for each person with the Homey app. See more information on [Person presence](/aggregated-state/person-presence).

### Guests

Often we have guests that stays for several days, and they may be at home even if none of us are present.

I've added a **virtual device** for Guests present. Each morning I get a notification if the visitor flag is turned on, to make sure I flip it off if the guests are gone. I've mapped the virtual device for guests to a boolean flag along with the other boolean presence variables.

### Aggregated state

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-Ltn_GQa-ANFYJmzEJik%2F-LtnahAKhbAW1qP6SiEo%2FSkjermbilde%202019-11-16%20kl.%2010.45.54.png?alt=media\&token=2bdd28df-0071-4fb2-b69d-d6723dd145f6)

Note: `presence-besok`, is norwegian, and it translates to presence-guests.

### Fall back when someone arrives home

As a fall back if someone arrives back at home, without the presence working properly, or if the kids enter home before the parents, I explicitly flip `home` to true always when the door lock opens.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-Ltn_GQa-ANFYJmzEJik%2F-LtnbU9geVx6O-l6PdMS%2FSkjermbilde%202019-11-16%20kl.%2010.49.18.png?alt=media\&token=4fb78094-9f14-475e-8359-5503956f3a0f)


# Room and floor presence v2

{% hint style="success" %}
In 2020, I decided to rework the logic to handle room and floor presence, and this is the result. It will reduce the number of flows significantly, and completely avoid using timers. I have used this for a few months now, and I am very happy with the results.
{% endhint %}

The built in zone functionality is very convenient and elegant, however it is useless if used out of the box. There are mainly two things that concerns me:

* If zone is active if the door is open
* I have motion sensors that should trigger two zones as active, in particular those in the stairs between floors.

### Preparing new zones

I created a new hierarcy for zones, only for tracking presence and active state.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OT_NaJW9OC9Nj8YkG%2F-M6OUdEJ78cW2ZIAnCar%2FIMG_57F7DEC6A750-1.jpeg?alt=media\&token=32563fd0-4869-4edd-a076-c19a4f7ff5cc)

I started with just creating zones for the zones I would like to track, and I grouped it in a zone I named "Aggregates".

### The overall idea

The overall idea is to add two sensors is each zone. One sensor that aggregates all relevant motions sensors. And one sensor that becomes active for a few seconds each time a door is either opened or closed.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OT_NaJW9OC9Nj8YkG%2F-M6OUltrN5mfqIX4nFfo%2FIMG_4614DA00DC14-1.jpeg?alt=media\&token=51ffd29f-7a8d-4d10-bebe-9cca521e4990)

### Required third party apps

* [Virtual devices](https://homey.app/no-no/app/com.arjankranenburg.virtual/Virtuelle-Enheter/)
* [< group >](https://homey.app/no-no/app/com.swttt.devicegroups/%3C-group-%3E/)

### Creating an aggregated motion sensor

* Add a new **< group >** device in the first zone you'd like to setup.
* Choose the type of device to be **alarm**.
* Choose **motion alarm**.
* Choose the motion sensors that is associated with that zone. Remember one motion sensor can now be associated with multiple zones.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OVhtwdAhF8BtA7KiM%2F-M6OYqZQG-YzIC44xEXQ%2FIMG_3C0305818DEA-1.jpeg?alt=media\&token=14993f48-4828-465b-9703-795f06ee13df)

* Choose an appropriate icon indicating motion.
* Set capabilities to **Motion Alarm (On if any on)**
* Give the new device a proper name
* Notice that you can edit which motion sensors if included later, by entering settings and then advanced settings on the device.

### Creating a door sensor

* Create a **new virtual device**
* Choose **device**, not mode
* Click install
* Give the device a proper name, like **Door activity 2nd floor**
* Select a door icon
* Choose the device class **Sensor**
* I've chose the capability **motion**, to represent that door state change represents motion in that area

Now some logic is needed for the new door sensor to change state.&#x20;

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OZJDKcvzhEFYdjIMl%2F-M6Okd73ckmc7bWIctPe%2FSkjermbilde%202020-05-03%20kl.%2010.58.32.png?alt=media\&token=d0362242-eae2-4d97-b87f-1bb8da59ffd9)

You will need one flow for contact alarm turned on and one for alarm turned off. I use the trigger that triggers on all contact devices, in order to reduce the number of flows here. And then use and conditions to limit the doors to the one in the proper zones.

In the then section I set the newly created virtual sensor `alarm_motion` to `true`, waits for 5 seconds and sets it to `false`.

The end result will look like the screenshot in the section "the overall idea" above.

### Setting a logic variable

You do not need to do this step, but I prepfare to have a boolean variable to indicate that there is presence in an area. In example I have `presence-2etg` representing whether there is presence in second floor.

![Remember to have a corresponding flow to set active when zone is active.](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OZJDKcvzhEFYdjIMl%2F-M6OluEVp6yJxWrQValp%2FSkjermbilde%202020-05-03%20kl.%2011.04.18.png?alt=media\&token=90fad308-6632-4ad0-9091-c2ae08f11199)

The resulting variable will not turn inactive, 30 minutes after the last activity in any of the corresponding motion sensors or doors.


# Room and floor presence v1

{% hint style="warning" %}
In 2020, I decided to rework the logic to handle room and floor presence.

This page represent the "old" way of doing it. The reason why I had to reconsider this model, was the large number of flows and timers involved.

I'd reccomend to instead use the updated [room and floor presence logic v2](/aggregated-state/room-and-floor-presence-v2).
{% endhint %}

I use logic boolean variables to track if there is anyone present in the room or not. For some areas I use the same technique, but track the present in a complete floor.

### Simple room presence with door sensor

The simplest setup I have to track a room, is to add a window/door sensor and a timer.

I currently use the [chronograph timer](https://homey.app/en-us/app/nl.fellownet.chronograph/Chronograph/).

When the door opens or closes, I set the presence to `true`, restart a timer from 50 minutes. When the timer fires, I flip the presence back to `false`.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-Ltofx0VBbfzrQyjVYmY%2FSkjermbilde%202019-11-16%20kl.%2015.48.26.png?alt=media\&token=caddbc03-246a-4b84-967f-ef16fb4843c6)

For a door sensor, there is no available triggers that covers both open and close. The result is that I duplicate the flow above and have an nearly identical flow **Bad1 trigger active2,** that triggers when the **contact alarm turned off** instead of on.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-LtogLXy6lSahrG7QTnW%2FSkjermbilde%202019-11-16%20kl.%2015.50.10.png?alt=media\&token=61c9362f-2a70-4e36-ae3b-7e4e97f52ad4)

The flow above fires when the door has not been opened or clsoed for 50 minutes.

I use this setup for rooms where you do not stay for very long at a time, such as bathroom and a booth. For the childrens room and other room where you can for stay longer, I've added a motion sensor as well.

### Simple floor presence

I have a floor with multiple motion sensors. I track the presence in this floor by using the zone feature. I have no door sensors (nor doors) on this floor.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-LtohE-8zfzvGo-4AjnL%2FSkjermbilde%202019-11-16%20kl.%2015.54.01.png?alt=media\&token=4369ebc8-b986-4644-98c8-1a7fcd21e4ab)

Then a second flow for setting presence to false.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-LtohQQs5dGkDOa3TK0E%2FSkjermbilde%202019-11-16%20kl.%2015.54.54.png?alt=media\&token=77c58d85-a5b1-4599-bce1-2f0e278689fc)

### More complex presence

It gets messy when you have an area with multiple motion sensor that does not match a floor, and combine them with multiple door sensors.

I have these flows:

* One **setActive** flow, that is executed from the trigger cards. This flow is execturd when motion is detected.
  * The setActive flow sets presence to `true`,&#x20;
  * and stops the timer.
* One card for **each of the motion sensors**/zones when **motion is detected**, starting the **setActive** flow above.
* One flow called **trigger**, that checks if there is no motion in any of the motion sensors, and if so, it:
  * starts a timer with a duration of 20 minutes
  * sets presence to `true`
* One flow for each of the motion sensor when motion alarm is turned off, that:
  * starts the **trigger** flow
* Two flows for each of the door sensors (one for open and one for close), that
  * starts the **trigger** flow
* One flow that fires when the timer finishes
  * Sets presence to `false`

#### setActive flow

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-Ltoja1yah0wVoADy5WC%2FSkjermbilde%202019-11-16%20kl.%2016.04.21.png?alt=media\&token=a9a76765-6da5-4cef-9835-88e652255860)

#### trigger flow

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-Ltojw-TqAs40-_4i70h%2FSkjermbilde%202019-11-16%20kl.%2016.05.48.png?alt=media\&token=cb4155e3-7236-451a-ba40-0c2063b0a471)

#### inactive flow (timer finishes)

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-LtojztxAmub15nZlyhB%2FSkjermbilde%202019-11-16%20kl.%2016.06.05.png?alt=media\&token=c28d2737-bd47-4d6e-9324-cbc03cda4927)

#### example of a motion alarm turns on

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-Ltok4E-5DIjv6ItKQqT%2FSkjermbilde%202019-11-16%20kl.%2016.06.29.png?alt=media\&token=2de8006a-6ada-474c-9670-b4e1e2e8702c)

#### example of a motion alarm turns off

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-Ltok8eQnU1K4_tT4hcP%2FSkjermbilde%202019-11-16%20kl.%2016.06.45.png?alt=media\&token=19e9ce01-9550-40c8-82e9-269d8d4d1e9b)

#### example of a door opens

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtoelnXgkZRF9oFIK8-%2F-LtokDq1IJKSzprX2Eq8%2FSkjermbilde%202019-11-16%20kl.%2016.07.06.png?alt=media\&token=63a4ca65-ae80-4846-ae22-4115c208ab69)


# Time table

Many of my automations depend on the time of day. In example, I’d prefer cold temperature of the light in the morning, and warm in the evening. I also dim the light in the evening.

I’ve created a logic string variable named `timeperiod`

| Value      | Meaning                                  | Triggers                                                                |
| ---------- | ---------------------------------------- | ----------------------------------------------------------------------- |
| `morning` | At least someone has gotten out of bed   | Motion after 06:50                                                      |
| `day`     | Most of the day                          | <p>09:00 weekday</p><p>11:00 weekday</p>                                |
| `evening` | After dinner, before the kids go to bed. | 18:30                                                                   |
| `late`     | Late in the evening                      | 22:00                                                                   |
| `night`    | Everyone has to bed                      | <p>Button triggers good night mode</p><p>Auto if no motion at 01:30</p> |

I use time triggers to set the variable, and it is pretty straight forward.

![I collect all the timeperiod flows in a timetable folder. Currently it also contains a few sun event based flows.](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtnY7EkyORLEMvTsxB-%2F-LtnYHOm1JpvRAIB7D1r%2FSkjermbilde%202019-11-16%20kl.%2010.30.58.png?alt=media\&token=66e36a6e-5b7a-4082-b3df-dcab69ea67f3)

Many of the values I set at the same time all week, and some I differ in weekend and on hollidays.

The flows are usually trigged by date and time, with the condition of weekday or weekend. I've not fully introduced the holliday concept, but there are apps for the official hollidays, and probably would use that in combination with a integration with our family calendar.&#x20;

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtnVagIWVKh4ZP1ZJAI%2F-LtnX5q1ynLK42M9qD9v%2FSkjermbilde%202019-11-16%20kl.%2010.25.47.png?alt=media\&token=5dbe6662-df9f-4858-9297-12cea78f4889)

### Good night

We have a manual button in the master bedroom triggering the good night flow, which sets the `timeperiod` variable to `night`. In addition we have an auto good night flow, that runs at 1:30, if there is motion in basement and on first floor.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtnVagIWVKh4ZP1ZJAI%2F-LtnY4ruaTKAVIo4BI-V%2FSkjermbilde%202019-11-16%20kl.%2010.30.06.png?alt=media\&token=1b867e46-0344-4356-94c1-4f23393ebc4e)


# Dark or bright

I've added two boolean logic variables, `dark` and `bright`.

### Dark

As simple as if is dark outside or not. Turn on at sunset, and turn off when sun rise.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtnfUp3wq7ooDkLQCy9%2F-LtnfmGNjlWEImyK1RVy%2FSkjermbilde%202019-11-16%20kl.%2011.08.03.png?alt=media\&token=2192e1a9-b8d1-49ce-b503-6b8daf681768)

### Bright

I want a boolean variable to flag when there is bright light outside. Typically I use this to for some of the rooms with a lot of windows, turn off the lights in the middle of the day in the summer.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtnfUp3wq7ooDkLQCy9%2F-LtngFag4t3h87rFCjGT%2FSkjermbilde%202019-11-16%20kl.%2011.10.09.png?alt=media\&token=a57fe5b3-93c5-436a-af43-1698731d2ac0)


# TV

Using the on/off state of the TV to control lighting is very satisfying. Automatically dim the lights nearby the TV that otherwise give reflection etc.

Currently there is no apps that supports my philips TV. Then instead I added a power plug to detect the power usage. Tracking when the TV passes 5W seems to be reliable.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtngI-dvzbPLf9woKup%2F-LtoeA-B5X6CxI_HcX_7%2FSkjermbilde%202019-11-16%20kl.%2015.40.40.png?alt=media\&token=8e8c67cb-cfb8-4500-b0cb-1483c874bf6d)

With home assistant I had support for apple TV, and was able to increase the lighting when the movie was set on pause, and then dim down when I hit play. I'm not able to do that with just the TV state, and I hope someone adds an Apple TV app anytime soon.


# Ventilation

I have a logic boolean variable `vent` which should result in ventilation running on **high** if `true`, and **medium** if `false`.

Then I use logic like:

```javascript
if (home) {
    if (vent) {
        fanmode = "High"
    } else {
        fanmode = "Medium"
    }
} else {
    fanmode = "Low"
}
```

The vent variable should be true if either there is a high CO2 level is high or there is high humidity in the bathroom.

The flow for updating the aggregated `vent` variable goes like this (and there is a duplicated trigger flow for each of the vent-.. variables.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-Ltp8676QsUAu5rat8YS%2F-Ltp8z_-VKg9tU3tRY6Q%2FSkjermbilde%202019-11-16%20kl.%2017.59.42.png?alt=media\&token=5ad56804-74ad-46cf-abee-283185251ad4)


# CO2

For each of the rooms where I measure CO2, I update an vent-co2-room logic boolean variable, to indicate whether the co2 level is too high.

Here is an example:

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-Ltp9ACp6_sdPj0LB3zL%2F-Ltp9MCa6VKorF7TPG7x%2FSkjermbilde%202019-11-16%20kl.%2018.01.18.png?alt=media\&token=03b88ec3-a744-4680-8e7e-2b06da1c95a7)


# Humidity bathrooms

I use a [Xiaomi Aqara temperature and humidity sensor](https://www.gearbest.com/access-control/pp_626702.html) in each bathroom, and I also have a Netatmo indoor station that I use to correlate.

I keep an humdiff variable updated for each bathroom, which should be the humidity in the bathroom minus the humidity in the living room.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-Ltp9NvghVGYg9_8YcLx%2F-Ltp9lWIeVMfy72P4tVl%2FSkjermbilde%202019-11-16%20kl.%2018.03.03.png?alt=media\&token=b9b95fd4-f04f-4d75-a0d2-967c6bd37e6d)

Then for each bathroom, I update a vent-hum-bad1 boolean variable to indicate whether the humidity level is sufficiently higher than the reference in order to trigger ventilation:

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-Ltp9NvghVGYg9_8YcLx%2F-LtpA5X6grIYivvLsyOY%2FSkjermbilde%202019-11-16%20kl.%2018.04.32.png?alt=media\&token=4b6dbe68-1ac6-4ccb-82aa-c939d4683caf)

Here is an example of insight of the humdiff variable after a shower. It triggered ventilation to run on high for about an hour.

![Humidity in bathroom1 compared to a reference, after a shower](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtpA7f0gPA_Ovpo2toM%2F-LtpATvf9ala5ebcHf7K%2FSkjermbilde%202019-11-16%20kl.%2018.06.08.png?alt=media\&token=113cd600-b00e-4a9a-858c-5c9d28c166e4)


# Scenes

I create a string variable to set the wanted scene in an area, typically one for each room but sometimes for a whole floor.

In example I have a `scene-2etg` for a scene for the second floor, including kitchen and living room.

| Value    | Meaning                                                                                                        |
| -------- | -------------------------------------------------------------------------------------------------------------- |
| `normal` | Someone present, normal lighting                                                                               |
| `soft`   | Slightly dimmed                                                                                                |
| `dim`    | Dimmed lighting, typically in the morning or evening                                                           |
| `flood`  | Flood lighting. Typically when we clean the house                                                              |
| `night`  | Night lighting. Most lights turned off.                                                                        |
| `nightp` | Night lighting with presence detected. A few dimmed lights turned on to be able to navigate through the house. |

I decided that these scene variables are the only way to control the lighting of that room. I'm fine with a few preset scenes for simplicity.

In some rooms, I'm happy with 2-3 scens, in others I have a lot more.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtndIyKNGxnxc1Vh46e%2F-Ltnf-yC3fjsXV7zi_BQ%2FSkjermbilde%202019-11-16%20kl.%2011.04.44.png?alt=media\&token=7bb41de6-2414-4936-840a-df3b1df925fb)

I need one flow for each scene. It is triggered when the scene variables changes. It has a condition of the scene variable to match a specific string value. And the then cards all set the different lighting conditions for that scene. Typically wither **turn off**, or **dim to**.

Collect all the scenes in a folder, and start with one and use duplicate to simplify the setup.

*I do not use scenes to set color temperature, I have a parallell setup for that independently from scenes.*


# Scene activations

Logic deciding which scene to activate depending on a set of aggregated state variables.

{% hint style="warning" %}
Be aware: In order to use scene activations, basic knowledge of javascript is required.
{% endhint %}

{% hint style="info" %}
This page was updated July 2021 reflecting the change from using *Logic* to [Better Logic](https://homey.app/en-us/app/net.i-dev.betterlogic/Better-Logic/). Not all pages reflect this update, and there might be examples using logic variables.
{% endhint %}

I have one scene activation logic for each scene variable. A scene variable is a text value representing the different [available scenes](/lighting/scenes) for a room or area.

The scene activation is responsible for translating a set of input variables (aggregated state) into one of the available scenes for a room.

### A very basic example: bedroom

Bedroom has a scene variable `scene-bedroom`, a text string that always contains one of the [defined scenes](/lighting/scenes) for that room. In a simple setup a room can have `off`, `normal`, `dimmed`.

The logic to decide the scene, may be `bright`, `presence-bedroom` and `timeperiod`. Both bright and presence-bedroom is boolean, indicating whether there is [bright daylight](/aggregated-state/dark-or-bright) and whether there is [someone present](/aggregated-state/room-and-floor-presence-v2) in the bedroom (motion and door sensors). The [timeperiod variable](/aggregated-state/time-table) is a string that can contain one of a few values indicating which part of the day.

A scene activation consists of a homeyscript and a flow that triggers the homeyscript. I use homeyscript because it allows more advanced logic than what is available in the flow editor (which is far too limited).

The flow *Scene activation bedroom*, triggers when one of aggregated state variables that are involved changes.

![Example of a scene activation flow](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-MexYfAp0MNaYY8IsiMJ%2F-MexeIdnzxMVbmEBUKtD%2Fimage.png?alt=media\&token=2816e81d-85b8-49de-88f8-6723f489c47d)

### The scene activation flow

Use the better logic "On of these variables changed" card, as seen in the illustration above.

The card takes a comma-separated list of better logic variables – the aggregated state variables that is needed for this flow.

### The scene activation **homeyscript**

The scene activation homeyscript will run when one of the aggregated state variables changes. The script can be split into three sections:

* Fetching the aggregated state variables
* Logic calculating the output scene depending on the aggregated state.
* Setting the output scene variable

#### Fetching the aggregated state variables

The first section with helper functions is the same for all scene activation homeyscript, and can be copied as part of a boiler plate template.

```javascript
// Helper functions to be able to fetch variables from the better logic app
let betterlogic = await Homey.apps.getApp({id: "net.i-dev.betterlogic"})
let getVar = async (name) => {
    let x = await betterlogic.apiGet(name)
    if (!x)  throw new Error("Could not find variable [" + name + "]")
    return x.value
}

// Fetch the aggregated state variables that is needed to do the logic.
let timeperiod = await getVar('timeperiod')
let presence = await getVar('presence-bedroom')
let bright = await getVar('bright')
```

In the example above we fetch three better logic variables into local javascript variables available to perform the logic.

{% hint style="info" %}
Notice that there is a slight delay in fetching each of the logic variables of a few milliseconds.
{% endhint %}

Output the variables to the console for debugging is useful:

```javascript
console.log("Timeperiod " + timeperiod)
console.log("Bedroom presence " + presence)
console.log("Bright daylight " + bright)
```

#### Scene logic

We use the aggregated state variables fetched above to decide which scene to activate for this room. It must always end up with one of the [defined scenes](/lighting/scenes) for that room.

```javascript
// timeperiod values: morning, day, evening, late, night
let scene = "off"
if (presence) {
  if (bright && timperiod !== 'morning')  {
    scene = "normal"
  } else if (timeperiod !== "night") {
    scene = "dimmed"
  } else {
    scene = "off"
  }
}
```

#### Setting the scene variable

Notice the trailing slash needed to be added to the name of the better logic scene variable.

```javascript
console.log("Setting scene to [" + scene + "]")
betterlogic.apiPut("scene-bedroom/" + scene)
return true
```

#### Complete example

The complete homeyscript as described above:

{% code title="sa-bedroom.js" %}

```javascript
// Helper functions to be able to fetch variables from the better logic app
let betterlogic = await Homey.apps.getApp({id: "net.i-dev.betterlogic"})
let getVar = async (name) => {
    let x = await betterlogic.apiGet(name)
    if (!x)  throw new Error("Could not find variable [" + name + "]")
    return x.value
}

// Fetch the aggregated state variables that is needed to do the logic.
let timeperiod = await getVar('timeperiod')
let presence = await getVar('presence-bedroom')
let bright = await getVar('bright')

// timeperiod values: morning, day, evening, late, night
let scene = "off"
if (presence) {
  if (bright && timperiod !== 'morning')  {
    scene = "normal"
  } else if (timeperiod !== "night") {
    scene = "dimmed"
  } else {
    scene = "off"
  }
}

console.log("Setting scene to [" + scene + "]")
betterlogic.apiPut("scene-bedroom/" + scene)
return true
```

{% endcode %}

### Testing the scene activation

Use the better logic app settings and manually edit each of the aggregate states, and verify that the scene variable are updated correctly, and also verify how the lighting, music or temperature changes accordingly to the scene.

One common mistake is the have typos in the "On of these variables changed"-card.

### Alternative way of fetching variables

If you run into issues with delays adding up when fetching a large number of variables, you may fetch data in parallell like this:

```javascript
let [
    home,
    timeperiod,
    presence1,
    presence0,
    dark
] = await Promise.all([getVar('home'), getVar('timeperiod'), 
    getVar('presence-1etg'), getVar('presence-0etg'), 
    getVar('dark')]
)
```

### More examples

#### Living room lights

Here is the homescript for kjellerstua:

{% code title="sa-0etg.js" %}

```javascript
let sceneid = "off"
if (presence) {
    if (tv)  {
        sceneid = "tv"
    } else if (timeperiod === 'day') {
        sceneid = "normal"
    } else {
        sceneid = "dim"
    }
    
} 
```

{% endcode %}

#### Outdoor lights

Example for outdoor lighting.

{% code title="sa-outdoor.js" %}

```javascript
let sceneid = "off"
if (dark) {
    if (timeperiod === 'morning' && presence) {
        sceneid = 'normal'
    } else if (timeperiod === 'day' && presence) {
        sceneid = 'boost'
    } else if (timeperiod === 'day' && home) {
        sceneid = 'normal'
    } else if (timeperiod === 'evening' && presence) {
        sceneid = 'boost'
    } else if (timeperiod === 'evening') {
        sceneid = 'normal'
    } else if (timeperiod === 'late' && presence) {
        sceneid = 'dim'
    }
}
```

{% endcode %}

#### 2nd floor lights

{% code title="sa-floor2.js" %}

```javascript
let sceneid = "off"

if (inputScene === "off") {
    sceneid = "night"
} else if (timeperiod === 'night' && presence) {
    sceneid = "nightp"
} else if (timeperiod === 'night') {
    sceneid = "night"
} else if (!home) {
    sceneid = "away"
} else if (!presence) {
    sceneid = "night"
} else if (inputScene === "flood") {
    sceneid = "flood"
} else if (inputScene === "middag") {
    sceneid = "middag"
} else if (bright) {
    sceneid = "bright"
} else if (timeperiod === "morning") {
    sceneid = (inputScene === "dim") ? "dim" : "soft"
} else if (timeperiod === "evening") {
    sceneid = (inputScene === "dim") ? "dim" : "soft"
} else if (timeperiod === "late") {
    sceneid = "dim"
} else {
    sceneid = (inputScene === "dim") ? "soft" : "normal"
}

```

{% endcode %}

Here is another older example for the second floor:

{% code title="sa-floor2-old.js" %}

```javascript
let sceneid = "off"
if (timeperiod === 'night' && presence) {
    sceneid = "nightp"
} else if (timeperiod === 'night') {
    sceneid = "night"
} else if (!home) {
    sceneid = "away"
} else if (!presence) {
    sceneid = "night"
} else if (bright) {
    sceneid = "bright"
} else if (timeperiod === "morning") {
    sceneid = "soft"
} else if (timeperiod === "evening") {
    sceneid = "soft"
} else if (timeperiod === "late") {
    sceneid = "dim"
} else {
    sceneid = "normal"
}
```

{% endcode %}

#### Children's room lights

Example from a childrens room. `inputScene` is a variable controlled by a person using a set of buttons for choosing modus.

```javascript
let sceneid = null
if (inputScene === 'flood') {
    sceneid = "flood"
} else if (!presence) {
    sceneid = "off"
} else if (scene === 'night') {
    sceneid = 'night'
} else if (scene === 'off') {
    sceneid = 'off'
} else {

    if (timeperiod === 'day') {
        sceneid = "normal"
    } else if (timeperiod === 'morning') {
//        sceneid = "morning"
    } else if (timeperiod === 'evening') {
        if (leggetid) {
            sceneid = "off"
        } else {
            sceneid = "leselys"
        }
    } else if (timeperiod === 'late') {
        sceneid = "night"
    } else if (timeperiod === 'night') {
        sceneid = "night"
    }
}
if (inputScene !== null) {
    console.log("Set new scene " + sceneid)
    betterlogic.apiPut("scene-1linus/" + sceneid)
} else {
    console.log("No change. Keep current scene")
}

```

#### Music 2nd floor lights

Here is an example controlling a music scene, including a variable for music volume:

```javascript
let d = new Date()
let m = d.getMonth()+1
let day = d.getDate()
let christmas = (m === 12) && day > 10

// console.log("Is christmas", christmas)
// Timeperiods: morning, day, evening, late, night

let music = "off"
let volume = 12

if (presenceAndreas && !presenceVigdis) {
    volume = 16
}

if (!presence) {
    music = "off"
} else if (!home) {
    music = "off"
} else if (timeperiod === 'night') {
    music = "off"
} else if (timeperiod === 'day' && !presenceAndreas && !presenceVigdis) {
    music = "nrksuper"
} else if (timeperiod === 'day' && presenceAndreas && !presenceVigdis) {
    music = "p4"
} else {

} 

await setTagValue("music-2etg", {type: 'string', title:'Music 2etg'}, music)
await setTagValue("music-2etg-volume", {type: 'number', title:'Music 2etg volume'}, volume/100)
```

#### Music bathroom

Here is another logic for music, controlling music on the bathroom:

```javascript
let d = new Date()
let m = d.getMonth()+1
let day = d.getDate()
let christmas = (m === 12) && day > 10

// console.log("Is christmas", christmas)
// Timeperiods: morning, day, evening, late, night

let music = "off"
let volume = 0.08

if (presenceAndreasBad) {
    volume = 0.10
}
console.log("Christmas " + christmas)

if (!presence) {
    music = "off"
} else if (timeperiod === 'night') {
    music = "off"
} else {

    music = "chill"

    if (inputMusic !== 'auto') {
        music = inputMusic

        if(inputMusic === 'rock' && timeperiod !== 'late' && timeperiod !== 'evening') {
            volume += 0.05
        }

    } else if ( timeperiod === 'morning') {
        music = "spa"
    } else if (christmas) {
        music = "jul"
    } else if (timeperiod === 'day') {
        music = "pop"
    }

}
if (timeperiod !== 'late') {
  volume -= 0.03
} 
if (timeperiod !== 'evening') {
  volume -= 0.01
}

```


# Childrens room


# Guest room


# Bathrooms


# Getting a bearer token

Getting an Athom Homey bearer token is not straight forward

The following guide, describes a setup that automatically fetches an updated bearer token for usage with your homey API. It consists of two main parts:

* A homeyscript that returns an updated bearer token as a return tag value
* An example flow that runs the homeyscript regularly and stores an updated bearer token in a logic variable

### The homeyscript

First you need to update the configuration section to your homey.

* The `email` and `password` is your Homey account.
* A `client_id` and `client_secret` can be found at <http://developer.athom.com/api/projects>
* The `redirect_url` can stay untouched
* Your `cloudid` can be found here: <http://developer.athom.com/tools/system>

```javascript
// -------- o - Configure these parameters -------- o -------- o 
let email = 'xxxxx@gmail.com'
let password = 'skdjhf987s8d76fsd'
let client_id = 's8d67chdg36d8d6f6d'
let client_secret = 'dsdfghjkdfjghkdfjhgkdfjhgkdfjhgkdfjhgkfd'
let redirect_url = 'http://localhost'
let cloudid = '8sd76f87sd6f876sd8f76sd'
// -------- o -------- o -------- o -------- o -------- o 

const between = function(str, strf, strt) {
    return str.split(strf).pop().split(strt)[0].trim();
}

const authurl = 'https://accounts.athom.com/login'
console.log("POST authentication " + authurl)
const response2 = await fetch(authurl, {
  "headers": {
    "accept": "application/json, text/javascript, */*; q=0.01",
    "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  },
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": 'email=' +encodeURIComponent(email) + '&password=' + encodeURIComponent(password) + '&otptoken=',
  "method": "POST",
  "mode": "cors",
  "credentials": "omit"
})
const body2 = await response2.text()
const token = JSON.parse(body2)

const authorizeurl = 'https://accounts.athom.com/oauth2/authorise?client_id=' + client_id + 
    '&redirect_uri=' + encodeURIComponent(redirect_url) + '&response_type=code&user_token=' + token.token


console.log(" Response from accounts.athom.com/login ", body2)
console.log("GET Authorization " + authorizeurl)

const response3 = await fetch(authorizeurl, {
  "headers": {
  },
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
})
const body3 = await response3.text()
let csrf = between(body3, 'name="_csrf" value="', '">')



let raw = response3.headers.raw()['set-cookie']
//let rawd = raw[0].split(';')
let cookiecsrf = null
raw.forEach(el => {
    let dc = el.split('=')
    if (dc[0] === '_csrf') {
        cookiecsrf = dc[1]
    }
})

let cookie4 = '_csrf=' + cookiecsrf
// console.log("Cookie4", cookie4)
console.log(" CSRF input parameter", csrf)
console.log(" CSRF cookie", cookiecsrf)

let authorizeurl2 = 'https://accounts.athom.com/authorise?client_id=' + client_id +   '&redirect_uri=' + encodeURIComponent(redirect_url) + '&response_type=code&user_token=' + token.token
console.log("GET Authorization", authorizeurl2)
const response4 = await fetch(authorizeurl2, {
  "headers": {
    "content-type": "application/x-www-form-urlencoded",
    "cookie": cookie4
  },
  "redirect": "manual",
  "body": "resource=resource.homey." + cloudid + "&_csrf=" + csrf + "&allow=Allow",
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});

const body4 = await response4.text()
let code = response4.headers['_headers'].location[0].split('=')[1]

console.log(" Response from authorization. Redirect to ", response4.headers['_headers'].location[0])
console.log(" Response content ", body4)
console.log(" Parsed the following code ", code)



let tokenendpoint = 'https://api.athom.com/oauth2/token'
console.log("POST token (resolve code to token) " + tokenendpoint)
const response5 = await fetch(tokenendpoint, {
  "headers": {
    "content-type": "application/x-www-form-urlencoded",
  },
  "body": 'client_id=' + encodeURIComponent(client_id) +  '&client_secret=' + encodeURIComponent(client_secret) + 
    '&grant_type=authorization_code&code=' + encodeURIComponent(code),
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});


//console.log("Response5", response5)
const body5 = await response5.text()
let accesstoken = JSON.parse(body5)





let delegationEndpoint = 'https://api.athom.com/delegation/token?audience=homey'
const response6 = await fetch(delegationEndpoint, {
  "headers": {
    "content-type": "application/x-www-form-urlencoded",
    "authorization": "Bearer " + accesstoken.access_token
  },
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": "client_id=" + client_id + " &client_secret=" + client_secret + "&grant_type=refresh_token&refresh_token=" + accesstoken.refresh_token,
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});



const body6 = await response6.json()
console.log(" JWT token is " + body6)

let endpoint7 = 'https://' + cloudid + '.connect.athom.com/api/manager/users/login'
console.log("POST login endpoint " + endpoint7)
const response7 = await fetch(endpoint7, {
  "headers": {
    "content-type": "application/json",
    //"authorization": "Bearer " + accesstoken.access_token
  },
  "body": JSON.stringify({"token": body6}),
  "method": "POST"
});

const body7 = await response7.json()
console.log(" Response status " + response7.status)
console.log(" Response: " + body7)

await setTagValue("accesstoken", {type: 'string', title:'Access token'}, body7)
return true
```

### Updating a logic variable

Run the homeyscript above regularly to update an logic variable using the flow below.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-MIEAz7qY8mqr0-B5bRs%2F-MIEDA8OpDvefv1phG7U%2FSkjermbilde%202020-09-27%20kl.%2013.00.14.png?alt=media\&token=c1eb7ece-4a2e-4849-8cff-c0f2a4b4e44a)

### Using the bearer token with the API

Here is an example using the Homey API with the bearer token:

```javascript
// -------- o - Configure these parameters -------- o -------- o 
let cloudid = 'sd87f68ds76f87sd6f8sd76f'
// -------- o -------- o -------- o -------- o -------- o 

let variables = await Homey.logic.getVariables()
let getVar = (name) => {
    let x = _.find(variables, (val, key) => {return (val.name === name) })
    if (typeof x === 'undefined') throw new Error("Could not find variable [" + name + "]")
    return x
}
let accesstoken = getVar('accesstoken').value
console.log("Access token is " + accesstoken)
let baseapi = 'https://' + cloudid + '.connect.athom.com/api/'
let apiendpoint = baseapi + 'manager/devices/device/'
console.log("GET " + apiendpoint)
const response = await fetch(apiendpoint, {
  "headers": {
    "accept": "application/json",
    "Authorization": "Bearer " + accesstoken
  },
  "method": "GET"
});
console.log(" Response status " + response.status + " " + response.statusText)
const responseBody = await response.text()
const data = JSON.parse(responseBody)
console.log(" Result ", data)

```

### Homey API reference

Thanks to Johan Bendz for providing a list of API endpoints: <https://github.com/JohanBendz/Homey-Endpoints/blob/master/Homey-REST-endpoints>

The base URL is:&#x20;

```javascript
https://<cloudid>.connect.athom.com/api/
```

[https://apps.developer.athom.com/tutorial-Web Api.html](https://apps.developer.athom.com/tutorial-Web%20Api.html)

Use the bearer token to make an authorized call:

```javascript
GET /api/manager/sessions/session/ HTTP/1.1
Host: <cloudid>.connect.athom.com
Authorization: Bearer <ACCESS TOKEN>
```

```javascript
# Alarms
GET /api/manager/alarms/alarm/

# Apps
GET /api/manager/apps/app/
GET /api/manager/apps/app/<APP_ID>/ (eg. com.fibaro)

# Cloud state
GET /api/manager/cloud/state/

# Devices
GET /api/manager/devices/device/
GET /api/manager/devices/device/<DEVICE_ID>/

# Drivers
GET /api/manager/devices/drivers/
GET /api/manager/drivers/pairsession/

# Experiments
GET /api/manager/experiments/experiment/

# Flow
GET /api/manager/flow/flow/
GET /api/manager/flow/flow/<FLOW_ID>/

# Flow folders
GET /api/manager/flow/flowfolder/

# Flow tokens
GET /api/manager/flowtoken/flowtoken/

# Images
GET /api/manager/images/image/

# Insights
GET /api/manager/insights/log/

# Language and unit settings
GET /api/manager/i18n/

# LED ring
GET /api/manager/ledring/screensaver/
GET /api/manager/ledring/state/

# Location
GET /api/manager/geolocation/

# Logic
GET /api/logic/variable/
GET /api/logic/variable/<VARIABLE_ID>/
PUT { "value": value } TO /api/manager/logic/variable/<VARIABLE_ID>/ 

# Mobile
GET /api/manager/mobile/

# Notifications
GET /api/manager/notifications/notification/
GET /api/manager/notifications/owner/

# Presence
GET /api/manager/presence/

# Reminders
GET /api/manager/reminder/reminder/

# Sessions
GET /api/manager/sessions/session/

# Switch on:
PUT { "value": true } TO /api/manager/devices/device/<DEVICE_ID>/capability/onoff/

# Switch off:
PUT { "value": false } TO /api/manager/devices/device/<DEVICE_ID>/capability/onoff/

# System
GET https://<HOMEY_IP>/api/manager/system/

# System Reboot // Use with caution!
POST https://<HOMEY_IP>/api/manager/system/reboot/

# Users
GET /api/manager/users/user/
GET /api/manager/users/user/<USER_ID>/
GET /api/manager/users/state/

# Weather
GET /api/manager/weather/weather/

# Zigbee
GET /api/manager/zigbee/state/

# Z-Wave
GET /api/manager/zwave/state/
GET /api/manager/zwave/log/
POST /api/manager/zwave/command/
- heal: payload {command: "heal", opts: {nodeId: <NODE_ID>}}
- test: payload {command: "sendData", opts: "<NODE_ID>,0x20,0x00"} 

# Zones
GET /api/manager/zones/
GET /api/manager/zones/<ZONE_ID>/
```


# Battery tracking

Stay updated on low battery and devices that do not report.

Every hour I run a homeyscript to check battery status, and look for devices that have not reported last 24 hours. If the list of affected devices do differ from previous run, it will send a battery status report to a Telegram group like this:

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-MU4ZSGG0lZnZkEBSCQc%2F-MU4_dZJIa1xUJpOoSeB%2FIMG_0514.jpg?alt=media\&token=a2b2ec96-aee0-42ba-9aa2-28a2ea523449)

### Preparing better logic variables

First create two better logic string variables, `batterytext` and `batterystatus`.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-MU4ZSGG0lZnZkEBSCQc%2F-MU4aId0G-w9YdyVzFUX%2FIMG_967A9D9DB0BE-1.jpeg?alt=media\&token=f5effd22-4e98-4aa5-9a34-6c2f872b0ce1)

It does not matter what value you set.&#x20;

### Create a homeyscript

{% code title="batteryStatus2.js" %}

```javascript
let betterlogic = await Homey.apps.getApp({id: "net.i-dev.betterlogic"})
let getVar = async (name) => {
    let x = await betterlogic.apiGet(name)
    if (!x)  throw new Error("Could not find variable [" + name + "]")
    return x.value
}

let currentStatus = await getVar('batterystatus')

const devicesX = await Homey.devices.getDevices();
const devices = []
_.forEach(devicesX, device => {
    devices.push(device)
})

const excludes = ['75602d03-9c02-4ad4-a29f-119313982878']

const isBattery = (x) => {
    if (!x.hasOwnProperty('capabilities')) return false
    if (_.includes(excludes, x.id) ) return false
    return _.includes(x['capabilities'], 'measure_battery')
}
const lowBattery = (x) => {
    if (x.up > (24*3600)) return true
    if (x.battery < 35) return true
    return false
}

const sf = (str) => {
    return str.replace(/ /g, "\\ ")
}

const getIDstring = (entities) => {
    let ids = _.map(entities, (x) => { return x.id })
    ids = _.sortBy(ids)
    console.log(ids)
    return ids.join(',')
}


const batteryInfo = (x) => {
    let info = {
        name: x.name,
        id: x.id,
        battery: null,
        driverUri: x.driverUri,
        driverId: x.driverId,
        batteries: x.energyObj.batteries
    }


    if (x.capabilitiesObj && x.capabilitiesObj.hasOwnProperty('measure_battery')) {
        //console.log(x)
        info.battery = x.capabilitiesObj.measure_battery.value

        let updates = []
        for (let key in x.capabilitiesObj) {
            if (x.capabilitiesObj[key].lastUpdated) updates.push(Date.parse(x.capabilitiesObj[key].lastUpdated))
        }
        let mostRecent = _.max(updates)
        // Seconds since last updated measurement from this entity
        info.up = Math.floor((Date.now() - mostRecent)/1000.0)

        
    }
    info.lowBattery = (info.battery < 50)
    info.noUpdateToday = (info.up > (24*3600))
    info.alarm = (info.up > (24*3600)) || (info.battery < 35)

    info.body = "battery,id=" + sf(info.id) + ",name=" + sf(info.name) + " battery=" + info.battery + ",upd=" + info.up + 
        ",lowbatt=" + info.lowBattery + ",noupd=" + info.noUpdateToday + ",alarm=" + info.alarm + "\n"

    return info
}


let batteryentities = _.filter(devices, isBattery)
batteryentities = _.map(batteryentities, batteryInfo)
batteryentities = _.filter(batteryentities, (x) => { return x.alarm })

// console.log(batteryentities)

let batterytext = ''
_.forEach(batteryentities, batt => {

    if (batt.lowBattery) {
        batterytext += batt.name + ' has low battery ' + batt.battery + '%' + "\n"
        console.log(batt.name + ' has low battery ' + batt.battery + '%')
    } else if (batt.noUpdateToday) {
      batterytext += batt.name + ' has not reported today. Not in ' + Math.floor(batt.up/3600) + ' hours' + "\n"
      console.log(batt.name + ' has not reported today. Not in ' + Math.floor(batt.up/3600) + ' hours')
    }

    
})


let batterystatus = getIDstring(batteryentities)
if (currentStatus !== batterystatus) {

  console.log(" ----- REPORTING -----")
  betterlogic.apiPut("batterytext/" + encodeURIComponent(batterytext))

} else {
  console.log(" - no report - ")
}


console.log("Store batterystatus: " + batterystatus)
betterlogic.apiPut("batterystatus/" + batterystatus)

return true

```

{% endcode %}

### Create the neccessary flows

You will need two flows. One flow that runs the battery script regularly:

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-MU4ZSGG0lZnZkEBSCQc%2F-MU4bK-dkpmFAn7LGFrn%2FSkjermbilde%202021-02-21%20kl.%2018.22.31.png?alt=media\&token=9363f6b6-a02d-4f66-964c-e31d53f873ba)

And one script that sends the prepared battery status message to telegram when it is updated:

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-MU4ZSGG0lZnZkEBSCQc%2F-MU4bfFn4tSsqEwBHGIE%2FSkjermbilde%202021-02-21%20kl.%2018.24.05.png?alt=media\&token=2d216c8c-1387-4263-a3d2-197080893806)

The second action of the script above is to update a dakboard status display with the same message.


# Influxdb and Grafana

Short description of how to export data from Homey to influxdb using homeyscript.

There is no available apps for exporting to Influxdb at the moment. Luckily the influxdb API is a very simple HTTP API.

If you are able to use prometheus.io instead of influxdb, you should consider that, as there is an app supporting export to prometheus.io.

What I did was to create a homeyscript that takes a few argument separated with `;`. The parameters are:

* Name of the measurement. I use names like `temperature`, `motion`, `logic`&#x20;
* Name of the device
* A group name (to group devices, like room)
* The measured value.&#x20;

The `influxdb.js` script goes like this:

```javascript
let a = args[0].split(',')

let measurement = a[0].trim()
let device = a[1].trim()
let zone = a[2].trim()

let value = a[3].trim()
if (isNaN(value)) {
    value = '"' + value + '"'
}

fetch("http://influx.yourhostname.com:30369/write?db=homey", {
    headers: {
      'Accept': 'application/json',
      'Authorization': 'Basic xxxxxxxxxxxxx' // fill in with your credentials
    },
    method: "POST",
    body: body
}).then((response) => {
    console.log(response)
})

return true;
```

{% hint style="info" %}
Remember to update the credential parts. Your username, colon, your password, base64 encoded.
{% endhint %}

I use one flow for temperature change, power change, humidity change. For logic variables, its not that easy. I have to use one flow for each Logic variable. If you use better logic, you would not need that and one flow to catch all logic would be sufficient.

Here is an example of a flow:

![A flow to update influxes with motion measurements that turns active.There is a corresponding flow for motion sensors that turns off.](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OP3Ht3S1h30CvYveY%2F-M6OPMQmPg4H5oZzKgSo%2FSkjermbilde%202020%E2%80%9305%E2%80%9303%20kl.%C2%A009.03.29.png?alt=media\&token=64508e35-5aa5-42c8-b060-419402f3705e)

As the flow has generated data over some time, you may start to create grafana dashboards.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OP3Ht3S1h30CvYveY%2F-M6OPTMxqQhkGflfq8G2%2FSolberg%20-%20Homey%20-%20Grafana.png?alt=media\&token=eb64169e-b076-439c-8077-06794480f735)

For keeping track of logic variables that changes over time, check out the [Grafana discrete panel](https://grafana.com/grafana/plugins/natel-discrete-panel).

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6OP3Ht3S1h30CvYveY%2F-M6OQU7ucbaVnfxO6Qcm%2FSkjermbilde%202020-05-03%20kl.%2009.25.20.png?alt=media\&token=c3410acb-25b4-4c86-8476-b7ab1fa3e216)

### Questions for you.

1. As you can see in the discrete panels above, it will not know the "start" value until the first value changes. Any ideas how to fix that? May be tweak the query to fetch data for a few more hours back in time? I've not managed to get this working yet, let me know if you make it work.


# Flexit ventilation

**Very briefly described:**

* Flexit Uni 3
* Flexit modbus adapter CI66
* Intel NUC running Home assistant and with an USB RS485 adapter
* Homeyscript interacting directly with the home assistant API

Here is the homeyscript interacting with the API:

{% code title="vent.js" %}

```javascript
let variables = await Homey.logic.getVariables()
let getVar = (name) => {
    let x = _.find(variables, (val, key) => {return (val.name === name) })
    if (typeof x === 'undefined') throw new Error("Could not find variable [" + name + "]")
    return x
}
// Variables: home,bright,timeperiod,presence-2etg
let home = getVar('home').value
let vent = getVar('vent').value

// home: boolean
// vent: boolean

let fanmode = "Low"
if (home) {
    if (vent) {
        fanmode = "High"
    } else {
        fanmode = "Medium"
    }
}

let body = {
    "entity_id": "climate.flexit",
    "fan_mode": fanmode
}
let bodyencoded = JSON.stringify(body)
let token = '-----removed------' // get a token from the home assistant user settings

let response = await fetch("https://home.solweb.no/api/services/climate/set_fan_mode",
{
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token
    },
    method: "POST",
    body: bodyencoded
})
let json = await response.json()
// console.log("Reponse: ", json)

if (json.length <= 0) {
    throw new Error("Empty response....")
}

return true;

```

{% endcode %}


# Water metering

**Works in progress! I'm not done with migrating this setup to Homey yet!**

This article describes the setup of digitally measuring water usage, using a tessel.io microcontroller.

### Choosing the correct water meter <a href="#choosingthecorrectwatermeter" id="choosingthecorrectwatermeter"></a>

You are not allowed to install a water meter yourself. In dialog with the waterworks, ro replace or install a water meter you need to ask for a water meter that can support digital readings.

In Trondheim, Norway, [Øwre-Johnsen as](https://owre-johnsen.no/) delivers the water meters allowed to be installed.

In dialoge with Øwre-Johnsen as, I ended up installing [Elster V200 (KVM-T)](https://owre-johnsen.no/produkt/ringstempelmaler-v200-kvm-t/). In addition I ordered and installed the [Falcon PR6 pulse module](https://owre-johnsen.no/produkt/signalgiver-falcon-pr6/). This one you can install your self.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtokHvCItp5hBZHutp_%2F-LtolRCFVRVdk0v6Hn12%2Fimage.png?alt=media\&token=d7ecb50b-94a0-40b9-94e5-5536226b7884)

### Reading the pulse <a href="#readingthepulse" id="readingthepulse"></a>

I chose to use a [tessel.io](https://tessel.io/) microcontroller, because it has both analog and digital gpio ports that is very easy to interact with using javascript.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtokHvCItp5hBZHutp_%2F-LtolZPls47hPBcsjBo1%2Fimage.png?alt=media\&token=ad4b2f5e-05de-4742-b57f-4591dfe22de8)

The Falcon PR6 contains a battery and gives a digital impulse every liter user detected. Connect the black wire to GND and the yellow to one of the numbered PINs.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtokHvCItp5hBZHutp_%2F-LtoleqMw0zS-gUgLLuR%2Fimage.png?alt=media\&token=de770162-a678-44ab-803e-8b51e2b2e4d0)

And when/if you order a Tessel 2, remember to also order a set of [jumper wires](https://www.sparkfun.com/products/11709)that fits.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-LtokHvCItp5hBZHutp_%2F-Ltom6OR6LKlqNMgg4i-%2FIMG_1177.jpg?alt=media\&token=499a78c4-ff27-437f-810c-9d5a549b3691)

Here is a class to read the pulse indicating one liter used.

```javascript
const  
  EventEmitter = require('events'),
  tessel = require('tessel')

const pin = tessel.port.A.pin[4]

class PulseReader extends EventEmitter {  
  constructor() {
    super()
    this.previous = false
    setInterval(() => {
      this.read()
    }, 50) // Read impulse every 50 ms
  }

  // Give a short blue LED blink to signal that an pulse is detected.
  blink() {
    blue.on()
    setTimeout(function () {
      blue.off()
    }, 100);
  }

  read() {
    pin.read((error, value) => {
      if (error) {
        console.error("Error reading analog input: " + error)
        return
      }
      let val = (value === 0)

      // Emit a value event every time a digital pulse is detected
      // Will discard all subsequent readins from the same pulse
      if (val && val !== this.previous) {
        this.emit('value', true)
        this.blink()
      }
      this.previous = val
    })
  }
}

module.exports = PulseReader
```

It can be used like this:

```javascript
let d = new Database()  
let p = new PulseReader()

p.on('value', (value) => {  
  d.post(1)
})
```


# Telia data plan

Fetching total and remaining data on a Telia mobile subscription.

The following homeyscript will login and parse the total GB and remaining GB of your Telia mobile subscription. Name it `telia.js`, or similar:

```javascript
const between = function(str, strf, strt) {
    return str.split(strf).pop().split(strt)[0].trim();
}

if (args.length === 0) {
    throw new Error("Cannot run without arguments. Format is:  mobilenumber;password")
}

let a = args[0].split(';')
let mobile = a[0].trim()
let password = a[1].trim()

let postbody = {
    "Username": mobile,
    "Password": password
}
let cookie = "cookieConsentLevel=3; ROUTEID_CSS=.NCNO-1; ROUTEID_RECSS=.NCNO-1; _gcl_au=1.1.1071439771.1588404619; _gid=GA1.2.693156927.1588404620; _fbp=fb.1.1588404619754.1570612504; _dc_gtm_UA-3214365-1=1; _hjid=8ca343c8-c972-4794-8ff6-7156d0adb5d5; nmstat=1588404710200; _scid=bb633357-5e50-42b9-b694-ba82d0a707ac; _ga=GA1.1.1888154566.1588404619; _ga_75H3PZJWHY=GS1.1.1588404619.1.0.1588404621.58"
const response = await fetch("https://min-side.telia.no/re/api/mssa-proxy/no/rs/auth/basic?goToMinbedrift=false", {
  "headers": {
    "accept": "application/json",
    "accept-language": "nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5",
    "authorization": "Basic bXNzYS1wcm94eS11c2VyOnJmVGV0VzVQblA=",
    "cache-control": "no-cache",
    "client-id": "css",
    "content-type": "application/json",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin",
    "cookie": cookie
  },
  "referrer": "https://min-side.telia.no/re/login?CT_ORIG_URL=https%3A%2F%2Fwww.telia.no%2Fmin-side",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": JSON.stringify(postbody),
  "method": "POST",
  "mode": "cors"
});

//console.log("Response", response)

let raw = response.headers.raw()['set-cookie']
let rawd = raw[0].split(';')
let session = null
rawd.forEach(el => {
    let dc = el.split('=')
    if (dc[0] === 'CTSESSION') {
        session = dc[1]
    }
})

cookie += '; CTSESSION=' + session


const response2 = await fetch("https://www.telia.no/forbruk/?ts=1588404892199", {
  "headers": {
    "accept": "*/*",
    "accept-language": "nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin",
    "x-requested-with": "XMLHttpRequest",
    "cookie": cookie
  },
  "referrer": "https://www.telia.no/forbruk/",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": null,
  "method": "GET",
  "mode": "cors"
});
const body = await response2.text()

let total = between(body, '<p class="donut-chart__value">', '</p><p class="donut-chart__value-caption">igjen av totalt</p>')
let remaining = between(body, '<p class="donut-chart__value-caption">', '</p></div></div></div></div></div><div class="chart-legend__container">')
// console.log(body)

let tot = parseFloat(total.split(' ')[0] )
let rem = parseFloat(remaining.split(' ')[0])

console.log("total "+ tot)
console.log("remaining " + rem)

await setTagValue("telia_" + mobile + "_tot", {type: 'number', title:'Telia [' + mobile + '] total'}, tot)
await setTagValue("telia_" + mobile + "_rem", {type: 'number', title:'Telia [' + mobile + '] remaining'}, rem)

return true
```

{% hint style="warning" %}
The script ignores unit, only reads the value. If your plan is 200MB, the unit that will be presented in Telia minside is in MB and not GB.

The script uses the minside, which is not a good way to fetch the data. The reason is that I did not find any proper API by Telia.
{% endhint %}

In order to update data, create a flow like this:

![Example flow fetching subscription data for three mobile subscriptions](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6LLYbE8TwLM-RbdXWi%2F-M6LMoILV9Mia2dFdPwk%2FSkjermbilde%202020-05-02%20kl.%2019.06.48.png?alt=media\&token=efe70700-ae83-498c-888d-79f4a880cca5)

The homeyscript above takes one paramter in this format:   `12345678;password`

The number is your mobile number, and the password is your password in Telia minside. If you enter wrong password a few subsequent attempts, Telia will block your password, and you will need to reset the password.

The homeyscript will create a tag for the total available data for this month, and another tag for the remaining data for this month.

The flow above add a Logic **Set numeric variable** to store the results.

![Logic view showing total and remaining data plans in GB.](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-M6LNXPx2tKsZ0bvn7ti%2F-M6LPg76W--63Z5nmAu1%2FIMG_3032.jpg?alt=media\&token=5fbf4151-11f2-48b6-afcf-d9b367563aca)


# Trondheim folkebibliotek

Getting overview of due loan dates for loaned books at the library.

Use this homeyscript, and replace the username and pin code:

```javascript
let user = 'N001234567'
let pin = '1234'

const response = await fetch("https://trhbib1.bib.no/cgi-bin/oauthlogin", {
  "headers": {
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "accept-language": "nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5",
    "cache-control": "max-age=0",
    "content-type": "application/x-www-form-urlencoded",
    "sec-fetch-dest": "document",
    "sec-fetch-mode": "navigate",
    "sec-fetch-site": "same-origin",
    "sec-fetch-user": "?1",
    "upgrade-insecure-requests": "1"
  },
  redirect: 'manual',
  "referrer": "https://trhbib1.bib.no/cgi-bin/oauthlogin?response_type=token&client_id=56d09e37acec1895f6fdeffb29a628aff8814410a04aec2fc58f5d32cd9256f9&redirect_uri=https://biblioteket.trondheim.kommune.no/mine-sider/mine-lan-og-reservasjoner/",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": "username=" + user + "&password=" + pin + "&client_id=56d09e37acec1895f6fdeffb29a628aff8814410a04aec2fc58f5d32cd9256f9&redirect_uri=https%3A%2F%2Fbiblioteket.trondheim.kommune.no%2Fmine-sider%2Fmine-lan-og-reservasjoner%2F&response_type=token&type=sjekkinnlogging",
  "method": "POST",
  "mode": "cors"
});


let rawcookies = response.headers.raw()['set-cookie']
let cookie = _.map(rawcookies, (x) => {
    let rawd = x.split(';')
    return rawd[0]
}).join('; ')


const loanresponse = await fetch("https://trhbib1.bib.no/cgi-bin/rest_service/oawebapi_getLoans/1.0/data", {
  "headers": {
    "accept": "*/*",
    "accept-language": "nb-NO,nb;q=0.9,no;q=0.8,nn;q=0.7,en-US;q=0.6,en;q=0.5",
    "authorization": "Bearer null",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "cross-site",
    "cookie": cookie
  },
  "referrer": "https://biblioteket.trondheim.kommune.no/mine-sider/mine-lan-og-reservasjoner/",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
});

let loanjson = await loanresponse.json()
let l = loanjson.loans

let today = new Date() 
let daysFromNow = (x) => {
  let diff = Math.floor((x.getTime() - today.getTime()) / (1000*60*60*24));
  return diff
}

let edc = (x) => {
  let y = _.unescape(x)
  return y.replace(/&oslash;/g, 'ø')
    .replace(/&aring;/g, 'å')
    .replace(/&aelig;/g, 'æ')
    .replace(/&Oslash;/g, 'Ø')
    .replace(/&Aring;/g, 'Å')
    .replace(/&AElig;/g, 'Æ')
}

let firstDue = 999
let summary = {}
_.each(l, (x) => {
  let ld = new Date(x.dueDate)
  let days = daysFromNow(ld)
  if (!summary.hasOwnProperty(days)) {
    summary[days] = {
      "days": days,
      "count": 0,
      "titles": [],
      "renew": true
    }
  }
  if (days < firstDue) {
    firstDue = days
  }
  summary[days].count++
  summary[days].titles.push(edc(x.title))
  if (!x.loanIsRenewable || x.renewedToday) {
    summary[days].renew = false
  }
  // console.log("dato [" + x.loanDate + "] " + daysFromNow(ld))
})




let st = _.map(Object.values(summary), (x) => {
  return x.count + ' bøker må leveres innnen ' + x.days + ' dager. ' +
    (x.renew ? 'Kan forlenges. ' : 'Kan IKKE forlenges. ') + '(' + x.titles.join(', ') +  ')'
}).join("-- \n")



// console.log("Set cookie", cookie)
// console.log(st)
// console.log(summary)
// console.log("Mine lån")
// console.log(loanjson.loans)


await setTagValue("bibtext", {type: 'string', title:'Bibliotek tekst'}, st)
await setTagValue("bibdue", {type: 'number', title:'Bibliotek dager igjen'}, firstDue)

return true
```

Then use the tags to send notifications on mobile og telegram, and / or set logic variables depending on preferences.

![](https://1587494504-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ltn3vQOSB-YGC6qLZu_%2F-MAKqq4QI3ht5cKa_vW4%2F-MAKs3evAnVF77I5dMi_%2FIMG_3431.jpg?alt=media\&token=fb2ffbfb-ebad-4fb4-b00a-79d8bcac503c)

The genereated bibtext value has a format like this:

> 9 bøker må leveres innnen 21 dager. Kan forlenges. (Pyse, Ali ser rødt, Kom rundt omkring, Hvor bor dyrene?, Kryptalportalen, Vikinger på tokt, Ann, Max og Kim får ål, Snørrunger, Flink drage)\
> 3 bøker må leveres innnen 56 dager. Kan IKKE forlenges. (En hemmelig venn, Et monster i mattetimen, Vi lager fest!)


# SBanken

Få kontostatus for dine kontoer i Sbanken. Spesielt aktuelt for barnekontoer, hvor det er mangelfull mulighet for å hente ut saldo.

Dette homeyscriptet vil bruke sbankens API for å hente ut kontostatus.

Det du må gjøre er å registrere en klient i sbanken beta utvikler portal. Sette fødselsnummer i koden under som customerID. Så må du bruke API-et for å hente ut en account ID for den kontoen du vil ha status for.

{% code title="sbanken.js" %}

```javascript
let customerId = 'xxxfødselsnummerxxx'
let b = xxx base64encoded clientid : secret ....'
let tokenEndpoint = 'https://auth.sbanken.no/identityserver/connect/token'

// var basicAuth = btoa(encodeURIComponent(clientid) + ":" + encodeURIComponent(secret));
// console.log("basic ", basicAuth)

const response = await fetch(tokenEndpoint, {
  "headers": {
    "accept": "application/json",
    "authorization": "Basic " + b,
    "content-type": "application/x-www-form-urlencoded; charset=utf-8"
  },
  "body": 'grant_type=client_credentials',
  "method": "POST"
});
let data = await response.json()
var token = data['access_token']

async function getAPI(path) {
    const baseURL = 'https://api.sbanken.no/exec.bank/api/v1'
    let resp = await fetch(baseURL + path, {
        "headers": {
            "accept": "application/json",
            "authorization": "Bearer " + token,
            "content-type": "application/json",
            "customerId": customerId
        },
        "method": "GET"
    });
    let data = await resp.json()
    return data
}

// Note.. You have to play with the API in postman to get the account ID that you would like to get status from

let r = await getAPI('/Accounts/xxx_accountid_xxx')
console.log("=== Results ===")
console.log(r)

await setTagValue("sbanken_linnea", {type: 'number', title:'Linnea konto'}, r.item.available)

//console.log("response", data)

```

{% endcode %}


# Badetemperatur

Using Altibox Badetassen APIs to get current temperature

Using data from <https://badetassen.no>

{% code title="badetassen.js" %}

```javascript
// Fetching temperature from Altibox API. Badetassen.
// For use with Athom Homey as a homeyscript.

const apiurl = 'https://prdl-apimgmt.lyse.no/apis/t/prod.altibox.lyse.no/temp/1.0/api/location/'

async function getData() {
    let resp = await fetch(apiurl, {
        "headers": {
            "accept": "application/json",
            "content-type": "application/json",
            "Authorization": "Bearer 9df43895-3d09-30d5-afe4-db2bf92a86f0",
        },
        "method": "GET"
    });
    let data = await resp.json()

    let el = null
    for (let i = 0; i < data.length; i++) {
        if (data[i].Name === 'Sjøbadet') {
            el = data[i]
        }
    }

    return el
}

let r = await getData()
console.log("=== Results ===")
console.log(r)

if (r === null) return;

console.log("Tag " + "badetassen_" + r.id)
console.log({type: 'number', title:'Badetemp [' + r.Name + ']'})
console.log("Temperature: ", r.lastTemperature)

await setTagValue("badetemp_" + r.id, {type: 'number', title:'Badetemp [' + r.Name + ']'}, parseFloat(r.lastTemperature))
```

{% endcode %}


# Is it rainy? (yr.no)

A simple homeyscript fetching the yr.no APIs hourly and keeping a few betterlogic varaibles updated with information about the rain condition the last four hours and the next hour.

```javascript
// Please update lat and lot to match your location!
const lat = '63.75'
const lon = '10.2638'
// ---- o ---- o ---- o ---- o ---- o ----

const apifurl = 'https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=' + lat + '&lon=' + lon
const apinurl = 'https://api.met.no/weatherapi/nowcast/2.0/complete?lat=' + lat + '&lon=' + lon

const betterlogic = await Homey.apps.getApp({id: "net.i-dev.betterlogic"})
const getVar = async (name) => {
    let x = await betterlogic.apiGet(name)
    if (!x)  throw new Error("Could not find variable [" + name + "]")
    return x.value
}

async function getForecast() {
  console.log("Fetching " + apifurl)
    let resp = await fetch(apifurl, {
        "method": "GET",
        "headers": {
          "Accept": "application/json",
          "User-Agent": "AthomHomey Smart home github.com/andreassolberg"
        }
    });
    //console.log("Status: " + resp.status + " " + resp.statusText)
    if (!resp.ok) {
      return null
    } 
    let data = await resp.json()
    return data
}

async function getNow() {
  console.log("Fetching " + apinurl)
  let resp = await fetch(apinurl, {
    "method": "GET",
    "headers": {
      "Accept": "application/json",
      "User-Agent": "AthomHomey Smart home github.com/andreassolberg"
    }
  });
  //console.log("Status: " + resp.status + " " + resp.statusText)
  if (!resp.ok) {
    return null
  } 
  let data = await resp.json()
  let precip_hour = Number.parseFloat(data.properties.timeseries[0].data.next_1_hours.details.precipitation_amount)
  let precip_now = Number.parseFloat(data.properties.timeseries[0].data.instant.details.precipitation_rate)

  return [precip_hour, precip_now]
}

let nowdata = await getNow()
if (nowdata === null) return false;
//let forecast = await getForecast()

console.log("Data: ")
//console.log(nowdata)
//console.log(forecast)

let history = JSON.parse(await getVar('yr_history'))
if (!_.isArray(history)) {
  history = []
}


if (history.length >= 5) {
  history.shift()
}
history.push(nowdata[1])
let rainsum = _.sum(history)
let rainy = (rainsum >= 1.5)

console.log(history)
console.log("Sum is " + rainsum)


await betterlogic.apiPut("yr_precip_hour/" + encodeURIComponent(nowdata[0]))
//betterlogic.apiPut("yr_precip_now/" + encodeURIComponent(nowdata[1]))
await betterlogic.apiPut("yr_precip/" + encodeURIComponent(rainsum))
await betterlogic.apiPut("yr_rainy/" + encodeURIComponent(JSON.stringify(rainy)))
await betterlogic.apiPut("yr_history/" + encodeURIComponent(JSON.stringify(history)))


return true
```

You need to prepare and create three variables in better logic. Install better logic app, if you do not have it already. These are the variables:

| Variable name    | type    | Initial value |
| ---------------- | ------- | ------------- |
| `yr_precip_hour` | number  | 0             |
| `yr_rainy`       | boolean | false         |
| `yr_history`     | string  | `null`        |
| `yr_precip`      | number  | 0             |

Next, you need to setup a flow that runs once ever hour that executes the homeyscript above.

Now, you can create flows that triggers when the variables `yr_precip_hour` or `yr_rainy` changes.

`yr_rainy` is true if the precipation sum of the last four hours and the next hour is more than 1.5 mm.


# Music

For each individual music zone (room), I’ve setup a logic string variable.

* `music-bad1`
* `music-bad0`
* `music-linnea`
* `music-2etg`

| Value    | Corresponding playlist/radio           |
| -------- | -------------------------------------- |
| off      | Music off                              |
| spa      | Spa                                    |
| chill    | Chill                                  |
| jul      | Christmas tunes                        |
| 80s      | Music 80s                              |
| nrksuper | NRK Super (Radio channel for children) |
| rock     | Rock music                             |
| pop      | Pop music                              |
| svensk   | Swedish pop                            |

Bad0 has these configured: off,spa,jul

Bad1: off, spa, jul, nrksuper, 80s, pop, svensk, p4

2etg: off, jul, 80s, pop, p4, nrksuper, svensk


# Setup

Create an iOS shortcut with the voicecommand you'd like as a name.

Add URL, and open url webpage including the webhook URL including the cloudid.&#x20;

You need to decide a event name.

Then create a flow in homey, triggered by logic › webhook, and type in the same event name.


# Overview

| Voice command | Event       | Action                                        |
| ------------- | ----------- | --------------------------------------------- |
| God natt siri | `goodnight` | Set mode night, and turn off lights and more. |
| Dim lys       | `dim`       | Set `input-scene-2etg` to `dim`               |
| Normalt lys   | `normal`    | Set `input-scene-2etg` to `auto`              |
| Spill pop     | `pop`       | Set `music-2etg` to `pop`                     |
| Spill radio   | `radio`     | Set `music-2etg` to `p4`                      |
| Stopp lyd     | `stopp`     | Set `music-2etg` to `off`                     |


