Showing posts with label Prayer Times. Show all posts
Showing posts with label Prayer Times. Show all posts

Monday, April 6, 2020

Creating a prayer times action on Google Home

Saving a lot of time during the COVID-19 shutdown gave me the chance to do something I wanted to try for a while, getting my Google Home Mini to tell me the prayer time instead of looking at a screen. So this weekend I got to play with a dummy piece of code I made during the week and finalize my test version of my own prayer times assistant.

So far, I can only ask about the next prayer or name a specific prayer to know its time.

Important note: you cannot replicate the following project without a premium account on Firebase to enable calls to external APIs.



Here is what I had to do in simple words:

1- Create a new Dialogflow agent


Dialogflow is where I started my project with creating a blank agent with nothing but a Welcome intent.



2 - Modify the default welcome intent


For the sake of having fun, I changed the way of addressing the user to say the magic word: "Salam! How can I help you?" plus accepting similar magic words like "Assalm Alaykom".



3 - Create a new intent for the next prayer time


This intent is meant to looks for the next prayer time in the array of today's prayers. But first, in which city or location?

Circumvent the location access step


To get the user's location, I need to ask for permissions. I didn't want one extra step to ask for permission. I actually don't know if I will have to do this every time or I can add checks once and then I have the location for always. But to cut the working time, I decided to let the user simply mention the city. This can make it faster to develop my agent for now. So I added some training phrases with and without the city parameter. But notice that I added the city parameter as required and added some prompts to ask the user if the parameter is not found in the phrase. I also helped the training by double-clicking the word "Berlin" and chose my city parameter from that popup list which appeared.



Use fulfillment for fetching prayer times


To get to write some code to get the answer I need, I should enable fulfillments (enable webhooks checkbox). This is a special section where you can programmatically manipulate your agent. I also marked this intent as an end of conversation. This means that once I respond, the agent stops and does not listen to any more input from the user.



I will get to the fulfillments section a bit later.

4 - Create a new intent for a specific prayer time


If I already can fetch and scan through some prayer times, creating the next intent was pretty easy in comparison to the previous one. That's because I includes no comparisons or calculations, but as simple as looking for a specific key in the prayers key-value object.

Create a Prayer entity


To detect the prayer in the user's phrase, I had to create a special entity which does not exist by default, which is the prayer entity.

This entity can be created from the left side panel. In the entity, I define the possible entries, along with any synonyms which can mean the same thing. So I added the names of the five prayers and added some synonyms for any different pronunciation.



Create the intent with the phrases and responses like before


Just like the previous intent, I created a new one and added some training phrases and responses. And this time I added one more parameter: prayer. and I set its entity as Prayer, which I just created. And then I marked the prayer in each training phrase by marking the text and choosing the correct parameter from the list appearing.




Enable fulfillment and end of conversation


This step is similar to the previous entity. I enabled fulfillments and marked the intent as end of conversation.




5 - Fulfillment section


Now comes the programmatic part. In this section, we should enable the inline editor. This is where we write handlers to do what we want. There is another option to have webhooks written with some standards. But lets stick to the quick win of scripting something. Remember that you need a premium Firebase account to do this step to be able to call external APIs.



The code here is as simple as writing a handler and registering this handler to a specific intent by matching its name. So the skeleton of my code looks like this:

function nextPrayerTimeHandler(agent) {
    // some code here
    agent.add(`This is something the agent is going to say to the user!`);
}

function specificPrayerTimeHandler(agent) {
    // some code here
    agent.add(`This is something the agent is going to say to the user!`);
}

intentMap.set('NextPrayerIntent', nextPrayerTimeHandler);
intentMap.set('SpecificPrayerIntent', specificPrayerTimeHandler);


This skeleton blends with the existing boilerplate in the inline editor. The content of my functions is where magic happens. I wrote some quick and dirty code based on the first API I found in my search for prayer times APIs. And spent a good amount of time fiddling with the rabbit hole of JS date objects and local vs UTC mess.

Here is my code if you are curious or want to make your own cleaner code :)

const https = require("https");
const apiBaseTodayPrayers = 'https://api.pray.zone/v2/times/today.json?higher-latitudes=3&school=3&city=';
const prayerKeys = ['Fajr', 'Dhuhr', 'Asr', 'Maghrib', 'Isha'];
var city = '';
var prayer = '';

function constructTimestampFromData(date, time, offset) {
    var offsetSign = (offset > 0) ? '+' : '-';

    var fullTimeString = `${date} ${time} GMT${offsetSign}${offset}`;

    return (new Date(fullTimeString).getTime());
}

function findNextPrayerTimeFromJsonResponse(jsonData) {
    let allPrayerTimes = jsonData.results.datetime[0].times;
    let date = jsonData.results.datetime[0].date.gregorian;
    let offset = jsonData.results.location.local_offset;

    // Filter prayer times and convert from string to timestamp
    var filteredPrayerTimes = {};

    for (var timeKey in allPrayerTimes) {
      if (prayerKeys.includes(timeKey)) {
        filteredPrayerTimes[timeKey] = constructTimestampFromData(date, allPrayerTimes[timeKey], offset);
      }
    }

    // Get local time
    let now = new Date().getTime();

    // Find the next prayer
    var nextPrayerName = '';
    var nextPrayerTime = '';

    for (var key in filteredPrayerTimes) {
      if (filteredPrayerTimes[key] > now) {
        return [key, allPrayerTimes[key]];
      }
    }
}
 
function nextPrayerTimeHandler(agent) {   
    // Get the current city
    city = agent.parameters.city;
   
    // Make API call to fetch prayer times
    const todayPrayersUrl = apiBaseTodayPrayers + city;
    return new Promise((resolve, reject) => {
      https.get(todayPrayersUrl, function(resp) {
        var json = "";
        resp.on("data", function(chunk) {
          json += chunk;
        });

        resp.on("end", function() {
          let jsonData = JSON.parse(json);
          let nextPrayerNameAndTime = findNextPrayerTimeFromJsonResponse(jsonData);

          // Respond
          if (nextPrayerNameAndTime[0] === '') {
            agent.add(`There are no more prayers in ${city} today.`);
          } else {
            agent.add(`The next prayer in ${city} is ${nextPrayerNameAndTime[0]} at ${nextPrayerNameAndTime[1]}.`);
          }

          resolve();
        });
      });
    });
}
 
function specificPrayerTimeHandler(agent) {   
    // Get the current city
    city = agent.parameters.city;
    
    // Get the prayer name
    prayer = agent.parameters.prayer;
    
    const todayPrayersUrl = apiBaseTodayPrayers + city;
    return new Promise((resolve, reject) => {
      https.get(todayPrayersUrl, function(resp) {
        var json = "";
        resp.on("data", function(chunk) {
          json += chunk;
        });

        resp.on("end", function() {
          let jsonData = JSON.parse(json);
          let prayerTime = jsonData.results.datetime[0].times[prayer];

          // Respond
          if (prayerTime === undefined) {
            agent.add(`Sorry, I cound not find ${prayer} time for ${city}.`);
          } else {
            agent.add(`${prayer} in ${city} is at ${prayerTime}.`);
          }

          resolve();
        });
      });
    });
}
 

intentMap.set('NextPrayerIntent', nextPrayerTimeHandler);
intentMap.set('SpecificPrayerIntent', specificPrayerTimeHandler);


6 - Testing with Google Assistant


On the right side of the screen, there is a message saying;
See how it works in Google Assistant.
Click it and you will be directed to Actions Console where you can test against a simulator for Google Assistant as well as other Google devices. You can also test on your own Google Home if you are connected to the same account from Google Home.



And from the Actions Console you can modify some settings and deploy your app to alpha and beta versions, or even submit it to be reviewed and publicly published.

Live demo


That's it! I created a simple app to help me with my daily need. And I learned something new along the way. I have more plans to improve my project to my needs and maybe publish it later to others.

I hope you learned something from my post. :)




Resources


Friday, August 6, 2010

Prayer Times Calculations: Pure C++ Code

[25-March-2017] Update: For brothers and sisters in Indonesia or any country below the equator, thanks to Reza's comment,  the code now works correctly.
[18-January-2012 Update: I refined the code to make it easier and more elegant to use]
[06-July-2011 Update: I found this page http://praytimes.org/calculation#Implementation ]

This idea came to me when a friend of mine was asking for an 'offline database' for prayer times. I searched for algorithms to make my own database and almost all search results lead to the same algorithm. And all codes are divided into two categories: a simple but approximate one, and a complicated but precise one. I tried with the complicated one to understand it but it needs more time because of its nested functions and -unfortunately- it is not a priority right now. So I went with the easy one as a start.

One thing I noticed is that every person is trying to make the ultimate use of the framework he is working on (mostly dotNet). But come on, it is all about calculations. Why don't you make a code that can be easily converted from one language/platform to another? That's why I wanted to make that code. It has a major problem till now as it produces times later than my Egyptian calendar up to 5 minutes !!! Only later not earlier.


Anyways, here is the code, and I hope I can reach better results in the near future (in-sha'a Allah).


 The parameters needed are:
  • Year/Month/Day of the desired day.
  • Longitude/Latitude/Time Zone of the desired place.
  • Fajr Twilight/ Esha Twilight which differ in calculations from one country to another.


Organization
Angle of the sun under the Horizon (Fajr)
Angle of the sun under the Horizon (Isha)
Region
University Of Islamic Sciences, Karachi
18 Degrees
18 Degrees
Pakistan, Bangladesh,
India, Afghanistan, Parts of Europe
North America
15 Degrees
15 Degrees
Parts of the USA, Canada, Parts of the UK
Muslim World League
18 Degrees
17 Degrees
Europe, The Far East, Parts of the USA
Umm Al-Qura Committee
19 Degrees
90 minutes after the Sunset Prayer
120 minutes (in Ramadan only)
The Arabian Peninsula
Egyptian General Authority of Survey
19.5 Degrees
17.5 Degrees
Africa, Syria, Iraq, Lebanon, Malaysia, Parts of the USA


The only library used in this function was
#include <math.h>
to use the trigonometric functions (sin, cos,...)


The function takes the data of Year/Month/Day/Longitude/Latitude/TimeZone/FajrTwilight/IshaTwilight plus 6 references to double variables (Fajr/SunRise/Zuhr/Asr/Maghrib/Isha). These 6 variables are the ones to return data into. I also added some supporting functions to help in some number conversions (for example, Radians to Degrees and vise versa).

//convert Degree to Radian
double degToRad(double degree)
{
    return ((3.1415926 / 180) * degree);
}

//convert Radian to Degree
double radToDeg(double radian)
{
    return (radian * (180/3.1415926));
}

//make sure a value is between 0 and 360
double moreLess360(double value)
{
    while(value > 360 || value < 0)
    {
        if(value > 360)
            value -= 360;

        else if (value <0)
            value += 360;
    }

    return value;
}

//make sure a value is between 0 and 24
double moreLess24(double value)
{
    while(value > 24 || value < 0)
    {
        if(value > 24)
            value -= 24;

        else if (value <0)
            value += 24;
    }

    return value;
}

//convert the double number to Hours and Minutes
void doubleToHrMin(double number, int &hours, int &minutes)
{
    hours = floor(moreLess24(number));
    minutes = floor(moreLess24(number - hours) * 60);
}

void calcPrayerTimes(int year, int month, int day,
                     double longitude, double latitude, int timeZone,
                     double fajrTwilight, double ishaTwilight,
                     double &fajrTime, double &sunRiseTime, double &zuhrTime,
                     double &asrTime, double &maghribTime, double &ishaTime)
{
    double D = (367 * year) - ((year + (int)((month + 9) / 12)) * 7 / 4) + (((int)(275 * month / 9)) + day - 730531.5);

    double L = 280.461 + 0.9856474 * D;
    L = moreLess360(L);

    double M = 357.528 + (0.9856003) * D;
    M = moreLess360(M);

    double Lambda = L + 1.915 * sin(degToRad(M)) + 0.02 * sin(degToRad(2 * M));
    Lambda = moreLess360(Lambda);

    double Obliquity = 23.439 - 0.0000004 * D;
    double Alpha = radToDeg(atan((cos(degToRad(Obliquity)) * tan(degToRad(Lambda)))));
    Alpha = moreLess360(Alpha);

    Alpha = Alpha - (360 * (int)(Alpha / 360));
    Alpha = Alpha + 90 * (floor(Lambda / 90) - floor(Alpha / 90));

    double ST = 100.46 + 0.985647352 * D;
    double Dec = radToDeg(asin(sin(degToRad(Obliquity)) * sin(degToRad(Lambda))));
    double Durinal_Arc = radToDeg(acos((sin(degToRad(-0.8333)) - sin(degToRad(Dec)) * sin(degToRad(latitude))) / (cos(degToRad(Dec)) * cos(degToRad(latitude)))));

    double Noon = Alpha - ST;
    Noon = moreLess360(Noon);


    double UT_Noon = Noon - longitude;


    ////////////////////////////////////////////
    // Calculating Prayer Times Arcs & Times //
    //////////////////////////////////////////

    // 2) Zuhr Time [Local noon]
    zuhrTime = UT_Noon / 15 + timeZone;

    // Asr Hanafi
    //double Asr_Alt =radToDeg(atan(2 + tan(degToRad(abs(latitude - Dec)))));

    // Asr Shafii
    double Asr_Alt = radToDeg(atan(1 + tan(degToRad(abs(latitude - Dec)))));
    double Asr_Arc = radToDeg(acos((sin(degToRad(90 - Asr_Alt)) - sin(degToRad(Dec)) * sin(degToRad(latitude))) / (cos(degToRad(Dec)) * cos(degToRad(latitude)))));
    Asr_Arc = Asr_Arc / 15;
    // 3) Asr Time
    asrTime = zuhrTime + Asr_Arc;

    // 1) Shorouq Time
    sunRiseTime = zuhrTime - (Durinal_Arc / 15);

    // 4) Maghrib Time
    maghribTime = zuhrTime + (Durinal_Arc / 15);


    double Esha_Arc = radToDeg(acos((sin(degToRad(ishaTwilight)) - sin(degToRad(Dec)) * sin(degToRad(latitude))) / (cos(degToRad(Dec)) * cos(degToRad(latitude)))));
    // 5) Isha Time
    ishaTime = zuhrTime + (Esha_Arc / 15);

    // 0) Fajr Time
    double Fajr_Arc = radToDeg(acos((sin(degToRad(fajrTwilight)) - sin(degToRad(Dec)) * sin(degToRad(latitude))) / (cos(degToRad(Dec)) * cos(degToRad(latitude)))));
    fajrTime = zuhrTime - (Fajr_Arc / 15);

    return;
}



So, if I take Cairo as an example:
  • Date: 18-1-2012
  • Longitude: 30.2
  • Latitude: 30
  • Time Zone: +2 
  • Fajr Twilight: -19.5
  • Esha Twilight: -17.5
the function will be used like this:
double fajr, sunRise, zuhr, asr, maghrib, isha;
calcPrayerTimes(2012,1,18, 30.2, 30, 2, -19.5, -17.5, fajr, sunRise, zuhr, asr, maghrib, isha);

Note that these prayer times are still "double" and should be converted to a time format. I made the doubleToHrMin function (you can find it before the calcPrayerTimes fnction) which splits the number into Hours and Minutes. It takes the double and two references to int variables. Here is how to use it:

int hours, minutes;
doubleToHrMin(fajr, hours, minutes);

Now you have the prayer time as Hour/Minute to use it however you want.

So, in brief:
- I first add the above code block to my code, then use it whenever I want like this example:

double fajr, sunRise, zuhr, asr, maghrib, isha;
calcPrayerTimes(2012,1,18, 30.2, 30, 2, -19.5, -17.5,
                fajr, sunRise, zuhr, asr, maghrib, isha);

int hours, minutes;

doubleToHrMin(fajr, hours, minutes);
std::cout << "Fajr: " << hours << ":" << minutes << std::endl;

doubleToHrMin(sunRise, hours, minutes);
std::cout << "Sunrise: " << hours << ":" << minutes << std::endl;

doubleToHrMin(zuhr, hours, minutes);
std::cout << "Zuhr: " << hours << ":" << minutes << std::endl;

doubleToHrMin(asr, hours, minutes);
std::cout << "Asr: " << hours << ":" << minutes << std::endl;

doubleToHrMin(maghrib, hours, minutes);
std::cout << "Maghrib: " << hours << ":" << minutes << std::endl;

doubleToHrMin(isha, hours, minutes);
std::cout << "Isha: " << hours << ":" << minutes << std::endl;


(view the full prayer times example in this online IDE)

I hope it is useful to someone looking for the pure algorithm with no framework dependencies, and I hope I come back to it one more time to see the problem of these extra resulting minutes.


Resources:

جمعية الفلك بالقطيف
Muslim Programmers & Designers Community
Tanzil.info (1),(2)
PrayTime.info
IslamCity.com
IslamWare.com