I got the Zemismart motorised curtains some time ago (review) and ever since I wanted to open it not based on a schedule, but a time offset created by the Android alarm. I don’t think I have to explain the benefits of that over a fixed schedule. The curtains would open a couple of minutes before your alarm rather than at the fixed schedule. How to sync Android alarm with NodeRED? With Tasker!
From Android Alarm to NodeRED timer
My first prototype used Test Next Alarm action to get the information about the pending alarm, but since the method picks up all sorts of alarms from different apps (clock, calendar etc) it was proven unreliable.
To add the insult to injury, the information about the alarm was inefficient and I wanted to have all alarms, labels, dates and days the alarm is active. Apart from that, I needed to know when the alarm had been updated, disabled or deleted. All this for each alarm instance, for multiple phones. You had no idea, how difficult this task was at first.
I’m pleased to say that I did all that in about 2 days, and now you can obtain the following information in NodeRED:
- alarm time
- alarm date
- repeat schedule
- alarm state (enabled or not)
- alarm label*
- support for unlimited alarms
- support for multiple phones
- values ready for NodeRED timers
- update and delete actions
- dynamic dashboard
*it’s impossible to obtain the label unless the notification is issued so the closest thing I could extract was how many characters the label has.
If you think the list is impressive and you feel particularly grateful that I got all these grey hair not you, consider my Patreon page, or you can top up my Coffee Jar (PayPal). I would like to thank Jim from Hackspace for helping me out with troubleshooting one nasty issue and vastly improving one of the scripts.
Turns out that LogCat function is pretty handy for that. The new event let me extract the information about the alarms on Android phones and use it to whatever I want to. While the Tasker part of this tutorial is easy, the NodeRED part is probably aimed at advanced users. The good news is, you can download it all, and use it with only minor modifications.
How to sync Android alarm with NodeRED with Tasker
The new, magical option in Tasker is the ability to spy on LogCat entries in Android, these messages reveal what happens in the Android system-wise and we can use it as triggers.
The principle applies to alarms. When an alarm is created, modified or deleted appropriate messages are shared system-wise. The problem with LogCat messages is, that they don’t look even remotely useful in the raw shape.
LogCat – create alarm
1578328399.047 4795 4822 I AlarmClock: Created new alarm instance: AlarmInstance{alarmId=1, id=1, state=SCHEDULED, time=01-07-2020 16:33, vibrate=true, ringtone=content://settings/system/alarm_alert, labelLength=0}
LogCat – update alarm
1578399781.660 23509 23532 I AlarmClock: Updated alarm from: Alarm {id=1, enabled=true, hour=9, minute=20, daysOfWeek=[M T Th F Sa Su], vibrate=true, ringtone=content://settings/system/alarm_alert, labelLength=0, hasWorkflow=false, externalUuid=null, deleteAfterUse=false, instanceIds=[1]} to: Alarm {id=1, enabled=true, hour=9, minute=20, daysOfWeek=[M T Th Sa Su], vibrate=true, ringtone=content://settings/system/alarm_alert, labelLength=0, hasWorkflow=false, externalUuid=null, deleteAfterUse=false, instanceIds=[1]}
LogCat – delete alarm
1578328392.061 4795 4822 I AlarmClock: Removed alarm: Alarm {id=1, enabled=false, hour=16, minute=12, daysOfWeek=[], vibrate=true, ringtone=content://settings/system/alarm_alert, labelLength=0, hasWorkflow=false, externalUuid=null, deleteAfterUse=false, instanceIds=[]}
While the data is there, there is a long way to go before we can actually use it. The exact LogCat entry filer will depend on what app is used to create the alarm. I used Google Clock app, but with small modifications, this would work with 3rd party apps too!
Also if you want to intercept alarm voice commands, double-check the entries too, as my filters work on Pixel 3 but not on Xiaomi Mi9 without modifications.
Proposed Filters
#Alarm Created AlarmClock: Created new alarm instance #Alarm Updated AlarmClock: Updated alarm from: Alarm #Alarm Deleted AlarmClock: Removed alarm: Alarm
These filters will trigger 3 corresponding actions in Tasker (I could use one task and set of IF filters but it’s more transparent this way). This profile aims to process the raw data on NodeRED, so I’m passing unprocessed data stored in %lc_text
.
To sync Android alarm with NodeRED I will send an HTTP POST request. I have avoided hardcoding my server information thanks to my credentials project and this variable method. I would also strongly recommend you to check the 5 min guide to NodeRED security so you don’t leave your server vulnerable to attacks.
https://%HTTPuser:%HTTPpass@%HOMESERVER:1880/alarm
Each action will have a slightly different body, as I want to let my NodeRED know what action took place on my phone, and which phone is reporting:
#Alarm Created {"time":"%lc_text", "phone": "%PHONE", "type": "create"} #Alarm Updated {"time":"%lc_text", "phone": "%PHONE", "type": "update"} #Alarm Deleted {"time":"%lc_text", "phone": "%PHONE", "type": "delete"}
NodeRED
This is where the advanced section starts. I learned new things in JavaScript thanks to this tutorial. I hope you will too! If you are new to NodeRED, consider reading this guide to beginners first. Otherwise, things will get confusing.
Because of the %lc_text
data is different for each action, I need 3 flows to process the raw input into a useful format. To sync Android alarm with NodeRED easier, I want the values to be also available in separate variables so I can use it any way I want, not just for the dashboard.
To make things complicated, Android will change and update the id
of existing alarms, which made my initial approach buggy. There is another issue I had to address. When an alarm id
gets deleted, and the id
of that deleted alarm was smaller than the biggest id
in the array, Android system not only reshuffles the array assigning new id
, but creates a new alarm when the id
is updated. Long story short, this is a bit of a mess to track.
LogCat text sent contains different data then update, which is confusing, but I can extract and format a timestamp of the alarm. This is very useful, as I can get a date of the next alarm and make the conversions across the timezones. You can see how much “fun” dealing with time is in my sun tracker and personal take on alarm.
Other fields are the alarm ID which I will use to organise the data in my array and label length. The label name is not available, but I can still use the count of the character to distinguish alarms.
A custom object is sent as JSON (learn more about JSON) to be added to the array. Each phone will have a separated array in which the alarms are stored. This array is also stored as a flow variable, so make sure to preserve the variables if you want that data to survive the reboot. Tasker is also passing information about the phone.
Lastly, I had to hardcode the status of the alarm to "enabled": true
as this method doesn’t set that option (something that is done in the update).
A completely formatted payload is sent as JSON to the next node. Before I can store the alarm in a corresponding array, I have to check which phone is sending the data and if the alarm id
already exists (remember when I said that a new alarm can be created during the update?). I also used a pad()
function to provide a human-readable time (as string) with leading zeros without affecting the integers in minutes
and hours
. If you are dealing with time in seconds, I already have you covered in this article.
In order to add the alarm to the array, I check the index of the id
of my alarm (var pos = alarmsMi9.map(function(e) { return e.id; }).indexOf(id)
) and I push the alarm object to the alarm array.
When changes are made to the alarm, the update call is made and a new set of details are sent via LogCat. These contain more detailed information about the alarm and need to be processed differently.
This is the part where Jim was super helpful and reduced my regex searches with... a reduce function (that pun, right!?). This very short regex function captures both states of the alarm (I need before and after as id
of the alarm changes in specific circumstances) and saves it as JSON formatted object.
//capture 2 update outcomes in regex groups var data = x.match(/[^{]{(.?)}[^{]{(.?)}/); var extractJson = x => x .split(', ') .map(x => x.split('=')) .reduce((p,c) => { p[c[0]]=c[1]; return p; }, {});
I asked Jim, to also create a separate array for daysofweek
, so you could check programmatically what days the alarm should go off just by testing its bool value.
I'm ending up with string type values, so to make the data usable I will pass the relevant details as integers. Because this update doesn't have information about the date, I'm saving the date before I will override the element of the array: var date1 = alarmsMi9[arraystart].alarm.date
. The same functions are in use as in the previous paragraph to add leading 0 and display the time in a better format.
Bear in mind that deleting the alarm is different from deactivating it in the Clock app. The message issued by the LogCat is different, but I only need the phone type to access the correct array and the id
to delete the correct entry.
Before I can delete the right entry, I have to search my array for the correct alarm id and save the index. Once the alarm is identified, I can remove the element from the array and update the array. This array will load again when a new alarm instance is added.
Process Array & Template node
I found a table template online which I can update with my array, but before I can feed the data to the template node, I have to make sure there are no empty elements and then pass the array as msg.object (I kept as much as I could from the original template).
A neat way to clear any elements that are not needed is to filer the array with this: var arr = array.filter(function(e){return e});
- just be aware "0" elements will fall into that filter too. I'm not planning on having any zeros there so I'm good to go.
The actual template node is:
Conclusion
What's not to like? I learned a lot from this project and I'm really happy I was able to overcome all the issues. The process is instant, gives you relevant information to create timed events in NodeRED! What's next? I will consider making the same subset of data available locally to Tasker, as there is no simple way to obtain the alarm information, but that's the subject for the next tutorial. In the meantime, you can check this article out, it's about tracking who is home. Consider following me on social media if you want to know when this happens. If you have any questions about this project, feel free to leave a comment in this Reddit thread.
Project Download
Download project files here. Bear in mind that Patreon supporters have early access to project files and videos.