Tuesday, May 15, 2007
Bluetooth HandsFree Profile Support for Windows Mobile 5 - The Series (part 3)
In the last two articles, we talked about the different ways to interact with Microsoft's Audio Gateway (AG). We then focused on how to start writing your own light-weight Audio Gateway, since some versions of the PPC and Smartphone SDK do not include mechanisms to support control of the AG. In the second article, we took a system level view of the AG core and gave enough detail to create a Parser and Interface Module.
Today, we'll get to the real guts, and this is what heroes are made of: the Handler Module. However, be warned that this is not for the timid, nor the faint of heart. We'll dive into the deep end right away!
This third and last installment will discuss how to create the Handler Module, which includes processing the services and network commands from the Interface module; processing the AT Commands from the Parser Module, starting the service/control level socket connection with the Bluetooth Device; and routing audio to the Bluetooth chip.
Yeah, that's right--what you've been waiting for! With this information, you can do loads of cool things like streaming mp3 music from your mobile device to your bluetooth headset. Wow! Imagine what else you can do with a little imagination!
The guts of the Handler Module contains four small sub-modules, the connection processing, the AT command processing, network notification processing and the services processing. We'll break down these four sub-modules in this article.
First, we assume that the Bluetooth device has already been paired with WM5 device, if not then do so right now! Start->Settings->Connections->Bluetooth... you know the rest, if not consult your WM5 Device User's Manual on how to pair with a Bluetooth device.
Once the pair has been completed and Microsoft's AG has been turned off (See the previous article), we're ready to create a service level connection with the Bluetooth device.
The pairing of the Bluetooth device will create an entry in the registry, under [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Bluetooth\\AudioGateway\\Devices]. In this registry folder will be numbered folders for the different Bluetooth devices that have been paired with the WM5 device. These folders consist of a socket address and the associated service value which the connection offers, in our case, the Handsfree profile service. To create a service/control-level socket connection, first iterate through the numbered folders to get the right address and service that corresponds to the device that you want to create a connection with. Then, simply connect with the Bluetooth device by using the standard winsock socket connection scheme as shown in the code below:
So, we've created a service/control level connection with the Bluetooth device, now what? Well, once the Bluetooth device has detected a connection, it will send initialization codes as AT Commands. It's the Parser's job to parse these initialization codes and send them to us to send to the Handler Module for processing.
To receive these commands, we'll need to receive, from the socket, i.e.
And subsequently send out responses through the same socket the AT Command responses, i.e.
The initialization hand-shaking is outlined below. However, for a detailed overview of this call flow go to the Bluetooth website .
Note that the OK command send string as follows: "\r\nOK\r\n"
1. The Headset first sends the AG the features that it supports and in turn the AG sends back features that are supported.
Listed below are constants to define the end of the AT command (after the "=" sign)
These values can be OR'ed together to create the support value, e.g. if we only want to support three-way calling and reject a call features the value would be 0x21. Subsequently, if the headset only supports voice recognition activation and remote volume control, it would send to the AG 0x18. Hence, the string which would be sent out from the AG to the headset would be "\r\n+BRSF:%d\r\n", where %d is equal to the features supported.
Note the new line and carriage return before and after the command. These are classic AT Command delimiters. See the Bluetooth Spec for details.
2. The next message sent from the Headset to the AG is a request by the Headset to the AG to indicate which indicators are supported by the AG. An example of the response string is as follows: "\r\n+CIND:
(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n"
3. The headset then requests the status of those indicators. In our example we respond with the string "\r\n+CIND: %d,%d,%d\r\n", where the %d are defined:
first %d indicates that we have cellular service (from the listed values this could be 0-1)
second %d indicates that we're in a call: 0 for false (from the listed values this could be 0-1)
third %d indicates the current call set-up: 0 for idle (from the listed values this could be 0-3)
4. Finally, the headset sends a request to enable the indicator status update in the AG.
NOTE: From time to time, in my experience, what follows with some devices that the microphone volume information is sent after step 4.
ALSO NOTE: In the last article, we discussed the different AT commands which can be expected to be received by the AG, the additional commands not mentioned in this article are out of scope, i.e. "AT+BLDN" - last number dialed, which requires the developer to have some grasp of the RIL layer. However, from a purely simple view, sending back an "\r\nOK\r\n" to the headset for these commands will be sufficient. But, I've given enough information that if the developer does understand the RIL and has implemented telephony functionality, they will be able to piece together how processing of these AT commands can be done. Keep a look out for additional articles about RIL if interested.
OK, we're done with the initialization! WOW! that was a lot of information. Let's move on to network signaling.
As mentioned in the second article from the Interface layer, there are a few messages which will be signaled by the Interface Module to the Handler module that need to be supported: Incoming Call, Outgoing Call, Call Connected, Call Disconnected, Call Rejected, Call Busy, Call Waiting and Call Ringing. Listed below are the AT Commands sent to the headset for the corresponding Network event to signal the Headset into a different state
Incoming Call
Outgoing Call
Call Connected
Call Disconnected and Call Rejected
Call Waiting
Call Ringing
Almost done! Let’s move to the final section--voice routing.
In the last article, we talked about the Interface Module and how the Handler Module needs to support method calls from the Interface Layer. In this section, we'll go deeper into these calls to see how we can accurately process them. The commands are listed as:
Opening Audio
Closing audio
Opening a control channel
Closing a control channel
Getting/setting the mic volume
Getting/setting the speaker volume
Getting/setting the power mode
I leave it up to the reader to be able to handle the start and stop of your own AG service and also the mic/speaker and power getting and setting methods, since they are trivial.
Open/Close Control Channel: from the previous section, we learned how to connect to the Bluetooth socket to create a service level connection with the remote Bluetooth device. Hence, to handle the Open Control Channel command, we'll need to create some reliability and error checking around this socket connection. To close the control channel, all that is needed is to close the connected socket with a Winsock call. I leave it up to the reader to choose an appropriate method to shut down the socket connection.
Finally, the last piece is to open and close the audio, and this is not a trivial task.
First, we need to create a synchronous connection with the Bluetooth device. If you're lucky enough, you'll already have the libraries for creating this connection. Unfortunately, this is an article that describes a work-around if you don't have this library.
In the Windows directory, you'll find the DLL "btdrt.dll." You'll need to dynamically link to a few methods to get the audio connection. Listed below is example code to call the create synchronous connection and shut down the synchronous connection (which we'll need to route audio):
Once we have a service level connection, a SCO connection with the Bluetooth device,
we can call the audio driver to route to the Bluetooth chip.
To close the audio channel, first signal the audio driver to stop routing to the Bluetooth chip and close the SCO connection.
And that's it!
I hope that this has been an adventure. Play around with what has been shown; contact me if there is anything missing. I've omitted the getting and setting of the registry values with regards to different call states, volume settings and feature supports. However, if you're interested, take a look on the MSDN websites for registry settings related to the Audio Gateway and apply them into your architecture (and AT Command processing).
Best of luck in your Bluetooth Handsfree Adventures!
If you've read this series in its entirety drop me a line, I would love to hear what you've thought about the article.
Look for more articles related to Bluetooth and Telephony in the near feature by me!
Today, we'll get to the real guts, and this is what heroes are made of: the Handler Module. However, be warned that this is not for the timid, nor the faint of heart. We'll dive into the deep end right away!
This third and last installment will discuss how to create the Handler Module, which includes processing the services and network commands from the Interface module; processing the AT Commands from the Parser Module, starting the service/control level socket connection with the Bluetooth Device; and routing audio to the Bluetooth chip.
Yeah, that's right--what you've been waiting for! With this information, you can do loads of cool things like streaming mp3 music from your mobile device to your bluetooth headset. Wow! Imagine what else you can do with a little imagination!
The Guts of the Handler Module
The guts of the Handler Module contains four small sub-modules, the connection processing, the AT command processing, network notification processing and the services processing. We'll break down these four sub-modules in this article.
Creating a Socket Connection with the Bluetooth HandsFree device:
First, we assume that the Bluetooth device has already been paired with WM5 device, if not then do so right now! Start->Settings->Connections->Bluetooth... you know the rest, if not consult your WM5 Device User's Manual on how to pair with a Bluetooth device.
Once the pair has been completed and Microsoft's AG has been turned off (See the previous article), we're ready to create a service level connection with the Bluetooth device.
The pairing of the Bluetooth device will create an entry in the registry, under [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Bluetooth\\AudioGateway\\Devices]. In this registry folder will be numbered folders for the different Bluetooth devices that have been paired with the WM5 device. These folders consist of a socket address and the associated service value which the connection offers, in our case, the Handsfree profile service. To create a service/control-level socket connection, first iterate through the numbered folders to get the right address and service that corresponds to the device that you want to create a connection with. Then, simply connect with the Bluetooth device by using the standard winsock socket connection scheme as shown in the code below:
// make sure to include theseSimply put, get the registry value, create the socket, fill the SOCKADDR_BTH structure with the registry value and try to connect to the Bluetooth device.
#include <Winsock2.h>
#include <ws2bth.h>
// in a function to create the connection, do this
// create a Socket
m_socket = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if (INVALID_SOCKET == m_socket)
{
// Failed to create socket: Log something here with GetLastError();
return;
}
// Fill the socket addr structure with the bluetooth device information
SOCKADDR_BTH socketAddr = {0};
socketAddr.serviceClassId = /*Service value from the registry goes here*/;
socketAddr.addressFamily = AF_BTH;
socketAddr.btAddr = /*Address from the registry goes here*/;
// connect to the device
if( SOCKET_ERROR == connect(m_socket, (SOCKADDR *)&m_addr, sizeof(m_addr)) )
{
// Failed to connect: Log something here with GetLastError();
return;
}
Parse AT Command Processing and Initialization Hand-shaking
So, we've created a service/control level connection with the Bluetooth device, now what? Well, once the Bluetooth device has detected a connection, it will send initialization codes as AT Commands. It's the Parser's job to parse these initialization codes and send them to us to send to the Handler Module for processing.
To receive these commands, we'll need to receive, from the socket, i.e.
// create an array of char with a size of RECEIVE_BUFFER_SIZE (that's your define)
// with s(the socket that was just connected)
int result = recv(s, buffer, RECEIVE_BUFFER_SIZE, 0);
And subsequently send out responses through the same socket the AT Command responses, i.e.
// "command" is a std::string and "s" is the socket
// that has the connection to the Bluetooth device
::send(s, command.c_str(), command.size(), 0);
The initialization hand-shaking is outlined below. However, for a detailed overview of this call flow go to the Bluetooth website .
Headset AG
| |
| AT+BRSF=<supported> |
|--------------------->| 1.
| |
| +BRSF:<AG supported> |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CIND=? |
|--------------------->| 2.
| |
| +CIND:... |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CIND |
|--------------------->| 3.
| |
| +CIND:... |
|<---------------------|
| OK |
|<---------------------|
| |
| AT+CMER= |
|--------------------->| 4.
| OK |
|<---------------------|
| |
| |
Note that the OK command send string as follows: "\r\nOK\r\n"
1. The Headset first sends the AG the features that it supports and in turn the AG sends back features that are supported.
Listed below are constants to define the end of the AT command (after the "=" sign)
#define HF_FEATURE_EC_ANDOR_NR 0x01
#define HF_FEATURE_CALL_WAITING_THREEWAY_CALLING 0x02
#define HF_FEATURE_CLI_PRESENTATION 0x04
#define HF_FEATURE_VOICE_RECOGNITION_ACTIVATION 0x08
#define HF_FEATURE_REMOTE_VOLUME_CONTROL 0x10
#define AG_FEATURE_THREE_WAY_CALLING 0x01
#define AG_FEATURE_EC_ANDOR_NR 0x02
#define AG_FEATURE_VOICE_RECOGNITION 0x04
#define AG_FEATURE_INBAND_RINGTONE 0x08
#define AG_FEATURE_ATTACH_NUMBER_TO_VOICETAG 0x10
#define AG_FEATURE_REJECT_A_CALL 0x20
These values can be OR'ed together to create the support value, e.g. if we only want to support three-way calling and reject a call features the value would be 0x21. Subsequently, if the headset only supports voice recognition activation and remote volume control, it would send to the AG 0x18. Hence, the string which would be sent out from the AG to the headset would be "\r\n+BRSF:%d\r\n", where %d is equal to the features supported.
Note the new line and carriage return before and after the command. These are classic AT Command delimiters. See the Bluetooth Spec for details.
2. The next message sent from the Headset to the AG is a request by the Headset to the AG to indicate which indicators are supported by the AG. An example of the response string is as follows: "\r\n+CIND:
(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))\r\n"
3. The headset then requests the status of those indicators. In our example we respond with the string "\r\n+CIND: %d,%d,%d\r\n", where the %d are defined:
first %d indicates that we have cellular service (from the listed values this could be 0-1)
second %d indicates that we're in a call: 0 for false (from the listed values this could be 0-1)
third %d indicates the current call set-up: 0 for idle (from the listed values this could be 0-3)
4. Finally, the headset sends a request to enable the indicator status update in the AG.
NOTE: From time to time, in my experience, what follows with some devices that the microphone volume information is sent after step 4.
ALSO NOTE: In the last article, we discussed the different AT commands which can be expected to be received by the AG, the additional commands not mentioned in this article are out of scope, i.e. "AT+BLDN" - last number dialed, which requires the developer to have some grasp of the RIL layer. However, from a purely simple view, sending back an "\r\nOK\r\n" to the headset for these commands will be sufficient. But, I've given enough information that if the developer does understand the RIL and has implemented telephony functionality, they will be able to piece together how processing of these AT commands can be done. Keep a look out for additional articles about RIL if interested.
OK, we're done with the initialization! WOW! that was a lot of information. Let's move on to network signaling.
Network Signaling of In-call States:
As mentioned in the second article from the Interface layer, there are a few messages which will be signaled by the Interface Module to the Handler module that need to be supported: Incoming Call, Outgoing Call, Call Connected, Call Disconnected, Call Rejected, Call Busy, Call Waiting and Call Ringing. Listed below are the AT Commands sent to the headset for the corresponding Network event to signal the Headset into a different state
Incoming Call
"\r\n+BSIR:0\r\n"
"\r\n+CIEV:3,1\r\n"
Outgoing Call
"\r\n+CIEV:3,3\r\n"
Call Connected
"\r\n+CIEV:2,1\r\n"
"\r\n+CIEV:3,0\r\n"
Call Disconnected and Call Rejected
"\r\n+CIEV:2,0\r\n"
Call Waiting
"\r\n+CCWA:\"0\",0,1\r\n"
Call Ringing
"\r\nRING\r\n"
Almost done! Let’s move to the final section--voice routing.
Services Processing:
In the last article, we talked about the Interface Module and how the Handler Module needs to support method calls from the Interface Layer. In this section, we'll go deeper into these calls to see how we can accurately process them. The commands are listed as:
Opening Audio
Closing audio
Opening a control channel
Closing a control channel
Getting/setting the mic volume
Getting/setting the speaker volume
Getting/setting the power mode
I leave it up to the reader to be able to handle the start and stop of your own AG service and also the mic/speaker and power getting and setting methods, since they are trivial.
Open/Close Control Channel: from the previous section, we learned how to connect to the Bluetooth socket to create a service level connection with the remote Bluetooth device. Hence, to handle the Open Control Channel command, we'll need to create some reliability and error checking around this socket connection. To close the control channel, all that is needed is to close the connected socket with a Winsock call. I leave it up to the reader to choose an appropriate method to shut down the socket connection.
Finally, the last piece is to open and close the audio, and this is not a trivial task.
First, we need to create a synchronous connection with the Bluetooth device. If you're lucky enough, you'll already have the libraries for creating this connection. Unfortunately, this is an article that describes a work-around if you don't have this library.
In the Windows directory, you'll find the DLL "btdrt.dll." You'll need to dynamically link to a few methods to get the audio connection. Listed below is example code to call the create synchronous connection and shut down the synchronous connection (which we'll need to route audio):
//******Note that the developer needs to call
//before these calls call ::LoadLibrary(...)
//once finished with these calls call ::FreeLibrary(...)
// Code for creating a SCO connection
typedef int (*BthCreateSCOConnectionWrapper)( BT_ADDR*, unsigned short* );
BthCreateSCOConnectionWrapper proc;
HMODULE hMod;
if(NULL == (hMod = ::GetModuleHandle(L"\\Windows\\btdrt.dll")))
{
// Log something since we can't find the module
return;
}
if(NULL == (proc = (BthCreateSCOConnectionWrapper)::GetProcAddress(hMod,
L"BthCreateSCOConnection")))
{
// Log something since we can't find the method
return;
}
//ptrBTAddr is a pointer to the Bluetooth Address--the same address we created the service level connection with
//ptrHandle is a pointer to an unsigned short which will be filled with the handle to the SCO connection
proc(ptrBTAddr, ptrHandle);
// Code for closing the SCO connection
typedef int (*BthCloseConnectionWrapper)( unsigned short );
BthCloseConnectionWrapper proc;
HMODULE hMod;
if(NULL == (hMod = ::GetModuleHandle(L"\\Windows\\btdrt.dll")))
{
// Log something since we can't find the module
return;
}
if(NULL == (proc = (BthCloseConnectionWrapper)::GetProcAddress(hMod,
L"BthCloseConnection")))
{
// Log something since we can't find the method
return;
}
// handle is the pointer to the unsigned short value that was passed in to create the SCO connection
return proc( handle );
Once we have a service level connection, a SCO connection with the Bluetooth device,
we can call the audio driver to route to the Bluetooth chip.
#define WODM_BT_SCO_AUDIO_CONTROL 500
waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, TRUE);
To close the audio channel, first signal the audio driver to stop routing to the Bluetooth chip and close the SCO connection.
waveOutMessage(0, WODM_BT_SCO_AUDIO_CONTROL, 0, FALSE);
And that's it!
I hope that this has been an adventure. Play around with what has been shown; contact me if there is anything missing. I've omitted the getting and setting of the registry values with regards to different call states, volume settings and feature supports. However, if you're interested, take a look on the MSDN websites for registry settings related to the Audio Gateway and apply them into your architecture (and AT Command processing).
Best of luck in your Bluetooth Handsfree Adventures!
If you've read this series in its entirety drop me a line, I would love to hear what you've thought about the article.
Look for more articles related to Bluetooth and Telephony in the near feature by me!
Labels: audio gateway, Bluetooth, c++
Comments:
Awesome article, it was exceptionally helpful! I simply began in this and I'm becoming more acquainted with it better! Cheers, keep doing awesome!
Try to visit my page: 강남오피
(jk)
Thanks for sharing this.,
Leanpitch provides online training in Scrum Master, everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
agile scrum master certification
certified scrum master
Thanks for sharing this.,
Leanpitch provides online training in Scrum Master, everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
Scrum master
Best Scrum master certification
Thanks for sharing this.,
Leanpitch provides online training in Scrum Master, everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
scrum master certification cost
csm certification cost
<< Home
Hello Quan,
great article :dumpup:
I've enjoyed reading a lot :)
Could you give a hint how to redirect a2dp to wiress stereo headset?
great article :dumpup:
I've enjoyed reading a lot :)
Could you give a hint how to redirect a2dp to wiress stereo headset?
Hi,
Thanks for the comments. A2DP is quite involved and possibly would require an additional article(s) ;-) to describe. I would like to note that A2DP streaming audio is not done through the narrow band voice on the SCO channel. In any case it's not a trivial task to undertake. What exactly are you having trouble with? Have you referred to the bluetooth spec for A2DP profile? A2DP Profile Link
Thanks for the comments. A2DP is quite involved and possibly would require an additional article(s) ;-) to describe. I would like to note that A2DP streaming audio is not done through the narrow band voice on the SCO channel. In any case it's not a trivial task to undertake. What exactly are you having trouble with? Have you referred to the bluetooth spec for A2DP profile? A2DP Profile Link
Hi Quan,
thanks for the link. Great info.
I tried to set up a a2dp connection and wasn't successful so far.
I would love to read an a2dp article by you :)
thanks for the link. Great info.
I tried to set up a a2dp connection and wasn't successful so far.
I would love to read an a2dp article by you :)
Hi Quan,
It is very useful to understand , how to switch audio between device and bluetooth hands free. I am facing issue here in DOPOD 810 is I am able to route audio from device to bluetooth hands free as your suggested way, but routing from hands free to device is something funny. I just close SCO connection and send 500 (FALSE) to driver and I loose audio from either side, what it require is stop/start audio after starting it again I am able to route. Same thing is done in default phone hands free usage, it can route audio anywhere without stop/start. Only difference I am getting here is I am able to route audio from hands free to device for cell call only but If I try same for music or audio played with WaveOut API then above problem occurs. Any idea will help me lot I stuck with it.
It is very useful to understand , how to switch audio between device and bluetooth hands free. I am facing issue here in DOPOD 810 is I am able to route audio from device to bluetooth hands free as your suggested way, but routing from hands free to device is something funny. I just close SCO connection and send 500 (FALSE) to driver and I loose audio from either side, what it require is stop/start audio after starting it again I am able to route. Same thing is done in default phone hands free usage, it can route audio anywhere without stop/start. Only difference I am getting here is I am able to route audio from hands free to device for cell call only but If I try same for music or audio played with WaveOut API then above problem occurs. Any idea will help me lot I stuck with it.
Hi Paresh,
So after closing the SCO conneciton calling
waveOutMessage(0, 500, 0, FALSE); gives you no audio?
Does the call fail or is it successful, waveOutMessage(...) usually comes with a return code. If it is successful quite possibly you need to call the RIL audio set audio method, i.e. call RIL_SetAudioDevices(HRIL, RILAUDIODEVICEINFO* ); which is from the ril.dll within the Windows system folder.
If you're having a hard time finding the definition of this method try finding the ril.h file on google.
Basically this translates to a IOControl call to the WAV driver to route audio
So after closing the SCO conneciton calling
waveOutMessage(0, 500, 0, FALSE); gives you no audio?
Does the call fail or is it successful, waveOutMessage(...) usually comes with a return code. If it is successful quite possibly you need to call the RIL audio set audio method, i.e. call RIL_SetAudioDevices(HRIL, RILAUDIODEVICEINFO* ); which is from the ril.dll within the Windows system folder.
If you're having a hard time finding the definition of this method try finding the ril.h file on google.
Basically this translates to a IOControl call to the WAV driver to route audio
Hello Quan,
I am trying to switch audio from HF to device with DeviceIOControl() with IOCTL IOCTL_WAV_MESSAGE and
suggested msg value 500 with dwParam2 -> TRUE/FALSE in MMDRV_MESSAGE_PARAMS structure. I am able to route cell call audio only without STOP/START but not other audio.
I am trying to switch audio from HF to device with DeviceIOControl() with IOCTL IOCTL_WAV_MESSAGE and
suggested msg value 500 with dwParam2 -> TRUE/FALSE in MMDRV_MESSAGE_PARAMS structure. I am able to route cell call audio only without STOP/START but not other audio.
hi this is good article but it would have been better for me if i could have it for linux. such as handfree profile implememntation on linuc system..
thanks but..
good one
thanks a lot
thanks but..
good one
thanks a lot
Hello QUAN,
It is really very good article.
I am quite n ew to this Bluetooth technology. However I am in need of Bluetooth API's for Application level on Linux so that with those API's I should be able to call driver level functions.Can you suggest me something.
Regards
Nisha
It is really very good article.
I am quite n ew to this Bluetooth technology. However I am in need of Bluetooth API's for Application level on Linux so that with those API's I should be able to call driver level functions.Can you suggest me something.
Regards
Nisha
I am sure that this useful info will help you to get into Harvard. It was useful to read for me and my friends
In a daily basis, technologies bringing vital changes and updations in our life. Day by day this is the world is going to move on towards the more than more smartness. Like if we take an example of a computer we can see that the technicians and the experts change the physics of a huge computer to a pocket-size mobile phone and like the discussion of Bluetooth is also an example of such blessings.
If you want to buy PPT presentation completed by the most knowledgeable, professional and experienced writers, Top-Papers.com is always at your disposal! Urgent and quality academic writing service, 24/7 support and assistance with PowerPoint presentation!
Travelling by cab in Agra is a safe and secure way. With a wide network and convenient way of hiring cabs in Agra we provide quick and fast service. For years Agra is quite famous as a travel destination in India. It is not only famous for Taj Mahal but it also have other places which are a masterpiece of architectural beauty as well. Agra being near from Delhi ,the best way to explore all the architectural buildings of the city is to travel by a cab.
Cheap Taxi Service in Agra | Cheap Taxi Service in Haridwar | Cheap Taxi Service in Jodhpur | Cheap Taxi Service in Goa | Cheap Taxi Service in Udaipur | Cheap Taxi Service in Ajmer | Cheap Taxi Service in Nainital | Cheap Taxi Service in Mussoorie
Cheap Taxi Service in Agra | Cheap Taxi Service in Haridwar | Cheap Taxi Service in Jodhpur | Cheap Taxi Service in Goa | Cheap Taxi Service in Udaipur | Cheap Taxi Service in Ajmer | Cheap Taxi Service in Nainital | Cheap Taxi Service in Mussoorie
Cheap Taxi Service in Dehradun | Cheap Taxi Service in Varanasi | Cheap Taxi Service in Noida | Cheap Taxi Service in Ghaziabad | Cheap Taxi Service in Shimla | Cheap Taxi Service in Bangalore
Cheap Taxi Service in Mumbai | Cheap Taxi Service in Pune | Cheap Taxi Service in Gurgaon | Cheap Taxi Service in Delhi | Cheap Taxi Service in Ahmedabad | Cheap Taxi Service in Mumbai
I admire this article for the well-researched content and excellent wording. I am impressed with your work and skill. Thank you so much.Spy Bluetooth Device
Buy Coffee Maker Grinder In USA, UK With Best Price Available At Home Shopping & Get Free Home Delivery.Buy Coffee Maker deals at low prices in USA coffee maker. New Coffee Maker for Sale on Instalments with Specifications & Features in United State..
wireless service, remaining balance on device becomes due. Availability and amount of EIP financing subject to credit approval. Buy wireless earbuds deals at low prices in United State, UK. New Wireless Headphones for Sale on Installments with Specifications & Features .
WiFi router, modem & mesh WiFi deals for 2020 are underway, explore the latest Black Friday & Cyber Monday Google WiFi, If you ever wonder what's the best wifi router most popular name in the performance router market then it's probably Netgear.
However, you shouldn’t worry any more when Same Day Short Terms Loans Unsecured face some unexpected need of money now as online payday advance lenders are always ready to support you compared to the traditional banks, which provide their banking services only to those borrowers, who have payday loans with no credit check good credit score.
Hey, if you use Quickbooks for most of your business, then you might want to know more about this useful Quickbooks tool called Quickbooks Install Diagnostic Tool it is the support system for QB desktop which scans the system, recognizes the error and resolves any kind of errors that occur during installation of QUickbooks desktop
Awesome article, it was exceptionally helpful! I simply began in this and I'm becoming more acquainted with it better! Cheers, keep doing awesome!
Try to visit my page: 강남오피
(jk)
Thanks for sharing this.,
Leanpitch provides online training in Scrum Master, everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
agile scrum master certification
certified scrum master
Thanks for sharing this.,
Leanpitch provides online training in Scrum Master, everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
Scrum master
Best Scrum master certification
Thanks for sharing this.,
Leanpitch provides online training in Scrum Master, everyone can use it wisely.
Join Leanpitch 2 Days CSM Certification Workshop in different cities.
scrum master certification cost
csm certification cost
Quickbooks file doctor is a good tool that can help you to fix damaged corporate files in QuickBooks. It's also helpful for diagnosing QuickBooks network problems.
Hey friend, it is very well written article, thank you for the valuable and useful information you provide in this post. Keep up the good work! FYI, Pet Care adda
Credit card processing, wimpy kid books free
,science a blessing or curse essay
Credit card processing, wimpy kid books free
,science a blessing or curse essay
Quickbooks is accounting software and it is used to manage income and expenses and keep track of the financial health of their business.QuickBooks error 3371 is a common error it takes place when the user tries to activate QB on a different computer with a previously activated license.
Mostly error arises in printer because of printer not activated error code 30 in windows 8,10. And due to this error users are not able to print the file because the drivers are old or faulty and printer is not specified
Bibliography or references This section will include the citations that help to prevent plagiarism. Click here for - cmos citation generator
I was finding the answer to this question but I was unable to get the answer until I found Digital Gravity. There people told me about this problem's solution
Awesome blog!! Thank for providing excellent information. if you have any issues with QuickBooks error message 62107, you can go through the detailed steps mentioned in this article
Lol. I need to read this blog again as I didn't understand a thing but I need to focus more on this blog but I will give it a read after I am done creating web design company in the USA digital marketing strategies.
Wonderful blog post! This content on your site is incredibly valuable and can be of great benefit to everyone. I came across your website while searching for information on various topics, and I must say that you consistently share amazing things with your readers. Your commitment to providing insightful and helpful content is truly commendable. Thank you for your efforts in educating and engaging your audience. I'm excited to explore more of your blog and continue learning from your expertise. Keep up the fantastic work!
Abogado de Conducción Imprudente Condado de Monmouth
Abogado de Conducción Imprudente Condado de Monmouth
Global Assignment Help is an assignment writing service provider, who helps the students across the world by providing them the best quality assignments on time. The 500+ Phd writers of the firm assist the students by sharing their expertise in the respective fields. They are well-known for enhancing the quality of the assignments through precise and correct conceptual information. We provide unique plagiarism-free content by maintaining the academic standards and required customization.
Students can get advantage from us through proper experts’ guidance, manage their time appropriately, unlock their potential to score high and be successful in their future academic career. Moreover, the 24/7 customer services provided by us, and the attractive price range makes us one of the best assignment help service providers in the world.
Students can get advantage from us through proper experts’ guidance, manage their time appropriately, unlock their potential to score high and be successful in their future academic career. Moreover, the 24/7 customer services provided by us, and the attractive price range makes us one of the best assignment help service providers in the world.
The $500 Cash Advance application in Illinois is the same as in other states. Here are the simple steps to follow when applying for online cash advance loans
Thank you for sharing this informative blog post.
Global Assignment Help makes homework help affordable and effective. With prices that are among the best in the industry and up to 35% discounts available, students on a budget still get the top quality plagiarism-free papers that help boost their grades. No matter how urgent the turnaround time needed, the expert writers and researchers deliver prompt, customized assistance that meets all professor requirements. Whether its a business assignment help, case study, essay, report or thesis, students get the help they need at prices that fit their budget.
Global Assignment Help makes homework help affordable and effective. With prices that are among the best in the industry and up to 35% discounts available, students on a budget still get the top quality plagiarism-free papers that help boost their grades. No matter how urgent the turnaround time needed, the expert writers and researchers deliver prompt, customized assistance that meets all professor requirements. Whether its a business assignment help, case study, essay, report or thesis, students get the help they need at prices that fit their budget.
I'm truly impressed by the depth and quality of your blog's content. Your dedication to sharing valuable information shines through in every piece. Your blog stands as a reliable fountain of knowledge and inspiration. Thank you for consistently producing such insightful content.Middlesex County Driving Without a License Lawyer and Middlesex County Reckless Driving Attorney
Looking for a top-tier Social media agency Dubai? Discover expert strategies, tailored solutions, and unparalleled results for your brand's digital presence. Let's elevate your online presence together
I look forward to reading more articles from you on many different topics that pique my interest. Your ability to convey complex ideas in a clear and concise manner is highly appreciable. boats for sale abu dhabi
The way you break down intricate subjects into accessible, actionable information is truly impressive. Keep up the good work! boat rental abu dhabi
I admire how you transform complicated ideas into straightforward, actionable steps. Your efforts are greatly appreciated! Cisco distributors in dubai
This post on Bluetooth Hands-Free Profile support is incredibly informative. The detailed explanation of how this profile enhances communication and usability for hands-free devices is quite helpful.
DigiTroan combines IT expertise with marketing knowledge to craft strategic digital solutions for businesses. We design and develop software, and create custom online strategies to boost engagement and elevate brand presence
Dubai IT is the best IT company in Dubai. Dubai IT offer the best digital marketing solutions for individuals and businesses in Dubai and beyond. Strengthen your online presence and business growth, and reach your business goals with Dubai IT.
Qatar IT Solution is a leading IT solution provider in Qatar, specializing in diversified IT services. We are a full-service IT company, with a team of highly skilled and experienced industry experts. Qatar IT Solution is your ultimate destination for ensuring your desired online presence and business growth!
Thank you for creating such a supportive space where everyone can learn and grow together. sell usdt in india AngelX Super is designed with simplicity in mind, and with its no-fee, no-KYC policies and allowing to sell USDT to INR instantly, it eliminates many of the typical barriers people may feel when they plan trading.
Welcome to Website Development Company Dubai Dedicated to transforming your digital presence into a powerful business tool. With years of expertise in the industry, we specialize in creating innovative, user-friendly, and aesthetically appealing websites that drive results.
Post a Comment
<< Home