Monday, January 11, 2016

Using Android's BATTERY_CHANGED Intent in Tasker

Monitoring the status of my tablet's battery has always been part of the Digital Dash project (http://mikesgeneralblog.blogspot.com/2015/06/digital-dash-documentation-part-4.html).  And later the function was expanded to bring in the phone's battery information as well.  That part of the project has always worked pretty well and I haven't changed the code in a long time.

Now, however, I've discovered a more efficient way to gather this information and have rewritten that part of the system to take advantage of it.  It allows me to replace what had been two profiles and three tasks, with a single profile with one task attached to it.

The key is another Android Broadcast Intent, similar to the TIME_TICK intent I wrote about in my last post. (http://mikesgeneralblog.blogspot.com/2016/01/using-androids-timetick-intent-in-tasker.html)  The main difference is that where the TIME_TICK intent didn't provide any real information (it was just a synchronizing pulse), the BATTERY_CHANGED intent that we'll be using has a payload that contains a lot of info about the device's battery.

Setup to monitor this intent is nearly identical to the TIME_TICK: Create a new Event profile and choose "Intent Received" from the "System" category.  In the "Action" field enter "android.intent.action.BATTERY_CHANGED" (without quotes).  Then link to the task you want to run from this profile.

Here's what mine looks like:

Profile: V3_ BatteryTracker (472)
        Cooldown: 10
        Event: Intent Received [ Action:android.intent.action.BATTERY_CHANGED Cat:None Cat:None Scheme:* Mime Type:* ]
        State: Variable Value [ %V3_DrivingMode Set ]
Enter: V3_BattMon (483)
        A1: Variable Set [ Name:%V3_BatteryDisplay To:%level% Do Maths:Off Append:Off ] If [ %plugged = 0 ]
        A2: Variable Set [ Name:%V3_BatteryDisplay To:<u>%level%</u> Do Maths:Off Append:Off ] If [ %plugged > 0 ]


(Note that, as usual, my profile has a second context, %V3_DrivingMode Set, to keep it from firing unless my Digital Dash system is running.  You don't need that context just to monitor the battery.)

Although this is pretty simple arrangement, there are a couple of points to keep in mind if you're thinking of using this intent.  First of all, notice that I've put a 10-second cooldown on the profile to limit it's maximum firing rate.  That's because this intent will change any time one of several battery conditions changes.  It's not quite like Tasker's built-in "Battery Changed" event that only fires when the battery level changes.  The BATTERY_CHANGED intent (which Tasker is undoubtedly monitoring behind the scenes) puts out new information not only when the battery level changes, but also when the powered status changes, when the battery health changes, and when the the battery temperature changes, along with several other triggers.  

The upshot is that this intent can be updated very frequently and since I don't need or want to have that kind of granularity, I've restricted the profile to firing only once every 10 seconds, at a maximum.

The second things to take note of are the names of the Tasker variables that I'm using: %plugged and %level.  They're obviously local variables, but I didn't make up the names; they are the ones created by Tasker and based on the names provided by the intent itself.  Since there are a lot of other variables associated with this intent (and no real documentation about how they translate to Tasker) it's probably worth a few minutes to lay it out. (You can't just choose these variables from Tasker's drop-down list because they are generated dynamically at runtime.) 

The Android system documentation contains a section on the "Battery Manager" class, which provides the details of this intent.  You can find it here: http://developer.android.com/reference/android/os/BatteryManager.html

There you'll find a list of all the constants used by this class and the information they can contain.  This document is the key to sorting out the Tasker names and knowing what values to look for.

For example, let's take the %plugged variable.  As you can see in the code above, if this variable is not 0, I wrap the battery level in HTML underline tags before displaying it.  This gives me a visual indicator of the power state on the main screen.

If you look at the Battery Manager documentation, you'll see this entry:

public static final String EXTRA_PLUGGED

Added in API level 5
Extra for ACTION_BATTERY_CHANGED: integer indicating whether the device is plugged in to a power source; 0 means it is on battery, other constants are different types of power sources.
Constant Value: "plugged"
The "Constant Value" gives you the name of the Tasker variable (once you add the leading %).  It also give you a hint about what the variable might contain, but there's more information available.
If you scroll up a bit, you'll find these entries, all with the word "plugged" in their names:

public static final int BATTERY_PLUGGED_AC

Added in API level 1
Power source is an AC charger.
Constant Value: 1 (0x00000001)

public static final int BATTERY_PLUGGED_USB

Added in API level 1
Power source is a USB port.
Constant Value: 2 (0x00000002)

public static final int BATTERY_PLUGGED_WIRELESS

Added in API level 17
Power source is wireless.
Constant Value: 4 (0x00000004)
These are the other values that might be present in the %plugged variable.  Knowing that you can test for any state and act accordingly.
Likewise, you can find the names for other variables, such as the ones for health, voltage, status, and so on.  By linking the names back to the constants, you can determine what type of information can be retrieved.
Some variables, like temperature, require a little more digging, however.  If you set up the profile and flash the %temperature value, you might see something like: 223  Don't worry; it doesn't mean your battery is at the boiling point.  The intent reports the battery temperature in tenths of a degree Celsius.  So, a value of 223 means 23.3 degrees Celsius or about 72.14 degrees Fahrenheit.  So, basically room temperature.
The fact that this intent contains the battery temperature is the main reason I put the cooldown period on the profile.  When my Digital Dash project begins ramping up, the battery temperature changes rapidly and I didn't want to have the profile firing every time the temperature changed a tenth of a degree.
This single intent could be a starting point for a pretty comprehensive Tasker-based battery monitoring system.  Take a look at the documentation and see just how much information you can gather with just one profile.





Friday, January 08, 2016

Using Android's TIME_TICK intent in Tasker

I keep coming back to the TimeAndTorque task.  Not because it's all that important, but because I keep finding better ways to execute it.

It started off as a looping task (http://mikesgeneralblog.blogspot.com/2015/06/digital-dash-documentation-part-9.html) and then moved to being triggered by a Schrödinger's Profile, (http://mikesgeneralblog.blogspot.com/2015/12/schrodingers-profile.html) and now it's being driven by Android's TIME_TICK Broadcast Intent.

If you don't know what a Broadcast Intent is, you can think of it as an olde tyme town crier.  The town crier would wander the streets calling out the time and any relevant news.  The Broadcast Intent is basically the same thing; it's just a message that the Android system sends out.  Like the town crier, the system doesn't really know or care it anyone is listening; it's job is just to send out the message.

There are a lot of Broadcast Intents that the system sends out, but the one we are interested in here is called the TIME_TICK.  This message is broadcast at the top of every minute and can be used for triggering events or synchronizing information.

Other apps can broadcast intents as well.  I use one generated by PowerAmp to grab and display music track and artist information (http://mikesgeneralblog.blogspot.com/2015/06/digital-dash-documentation-part-5-music.html).

Fortunately, Tasker makes it easy to listen for these intents and harness them for our own uses. To begin listening for the TIME_TICK, create a new Event profile and choose "Intent Received" from the System category. In the Action field enter "android.intent.action.TIME_TICK" (without the quotes).  You can leave all the other fields blank.  Then, just exit the event configuration and choose the task that you want to run when this intent is received.

Here's what the code looks like for my TimeAndTorque profile and task:

Profile: V3_TimeTick (484)
        Event: Intent Received [ Action:android.intent.action.TIME_TICK Cat:None Cat:None Scheme:* Mime Type:* ]
        State: Variable Value [ %V3_DrivingMode Set ]
Enter: V3_TimeAndTorque (336)
        A1: Variable Split [ Name:%TIME Splitter:. Delete Base:Off ]
        A2: Variable Subtract [ Name:%TIME1 Value:12 Wrap Around:0 ] If [ %TIME1 > 12 ]
        A3: Variable Set [ Name:%V3_DispTime To:%TIME1:%TIME2 Do Maths:Off Append:Off ]
        A4: Run Shell [ Command:/data/data/burrows.apps.busybox/app_busybox/tail -1 /storage/emulated/0/torqueLogs/trackLog.csv Timeout (Seconds):0 Use Root:Off Store Output In:%obd_log Store Errors In: Store Result In: Continue Task After Error:On ]
        A5: Variable Split [ Name:%obd_log Splitter:, Delete Base:Off ]
        A6: Test Element [ Scene Name:V3_LH Element:LowFuel Test:Element Visibility Store Result In:%lowdistanceindicator Continue Task After Error:On ]
        A7: If [ %obd_log6 < 60 & %lowdistanceindicator ~ false ]
        A8: Element Visibility [ Scene Name:V3_LH Element Match:LowFuel Set:True Animation Time (MS):0 ]
        A9: Say [ Text:Warning.  Range limit under sixty miles. Engine:Voice:default:default Stream:3 Pitch:5 Speed:4 Respect Audio Focus:On Network:Off Continue Task Immediately:Off Continue Task After Error:On ]
        A10: End If
        A11: If [ %obd_log6 > 60 & %lowdistanceindicator ~ true ]
        A12: Element Visibility [ Scene Name:V3_LH Element Match:LowFuel Set:False Animation Time (MS):0 ]
        A13: End If

Once every minute, the profile will become active and run the TimeAndTorque task, grabbing the time (and synchronizing my on-screen display) and running the low-fuel check (If you want more detail about the task itself, check the documentation in the first link, above.)

This method is simple, clean, and reliable.  I doubt that I'll find a better way to run this task, but who knows; I learn something new about Tasker amost every day.
       

Tuesday, December 22, 2015

Happy Holidays!



Merry FishMoose!

(See, because it's a fish bottle, with a moose candle...  Never mind.  Whatever you celebrate, enjoy the holidays.)

Physical Controls for the Digital Dash, Part 3

After careful thought and consideration (Yes, I'm serious.) I've decided to retire my gamepad side-arm controller in favor of my original scheme of using a Flic to control app access on the Digital Dash.  There are a couple of reasons for that:

1) The Flic is simply more reliable and easier to use.  It doesn't require recharging, connects automatically, and handles it's sleep mode more elegantly.  It also responds more quickly than the gamepad.

2) Many of the functions that the gamepad controls are either no longer necessary or are otherwise handled.  I don't need to download A-GPS data since I'm using the "Device Only" mode for GPS and I've brought the external temperature reading to the main screen, eliminating the need to pull up the Weather Channel app just to get that information.  And, of course, by installing a Flic dedicated to music control, I no longer need those functions on the gamepad.

3)  Finally, on our last few drives I kept closer track of just exactly how I was interacting with the system.  By far, the thing I did most often was to switch over to Google Maps to see the next turn information.  And that didn't happen very often.  I used to switch over more often to see the ETA information, but since bringing that to the main screen as well, it's available without any interaction.  The second most common thing was to check MyRadar to see incoming weather, but again, that was relatively rare.  Other things, such as switching music sources or setting a navigation destination simply didn't happen during the drive; I tended to do that at the beginning of a trip and never needed to change it en-route.

Taking all that into account, I decided to set up a second "App Control" Flic with the following functions.

When the main screen is showing:

  • Short Click - Launch Google Maps
  • Long Click - Launch MyRadar
  • Double Click - Launch GasBuddy

When on any screen EXCEPT the main screen:

  • Short Click - Return to the main screen

When on the Google Maps screen:

  • Long Click - Toggle Voice Navigation Guidance on or off

That seems like a pretty short list, but it represents all the things I'm likely to do while driving.  Of course, if I need to I can map a few other functions to the button, either by manipulating profiles (adding a triple click, for example) or by taking more contexts into account (such as a double click launching the navigation panel only when the Maps screen is showing).  I also have three other Flics that I could dedicate to controlling certain subsystems.

Time will tell what, if any, changes need to be made.



Wednesday, December 09, 2015

Schrödinger's Profile

How do you define when a Tasker profile is active?

One way, is to say it's active when all of its contexts are true.  In practice, though, that can sometimes be difficult to monitor directly, so we tend to use another method.  Commonly, we assume a profile is active when it runs its Entry task.

Normally, these two metrics align exactly and can be considered equivalent.  However, there are some profiles where that's not true; where the contexts are active, but the Entry task hasn't been run.

Since, depending on which definition we use, the profile can be considered both active and inactive, we call these Schrödinger's profiles.  (Yeah, okay.  Fine.  I'm the only one that calls them that, but I like it, and it's my blog.)

So what makes a profile behave like that, and what is it good for?

Creating a Schrödinger's profile is very easy; all you need to do is go into the profile properties and configure the Cooldown time.  Cooldown is a little-used parameter in Tasker that keeps a profile from running it's entry task until the the Cooldown time has elapsed.  If you set a Cooldown of 20 seconds, the profile can't activate any more often than that, even if all the explicit contexts for it are true.

One thing this type of profile is good for is ignoring some triggers, while allowing others of the same type to be acted on.

For example, in the Digital Dash project, I have a profile that watches for changes in the Google Maps navigation notification so that I can display ETA information on the main screen.
(As described here: http://mikesgeneralblog.blogspot.com/2015/10/navigation-eta-information-revisited.html.)

That notification can update many times a minute, particularly when there are a lot of navigation events happening close together, and when it starts counting down distance in tenths of a mile.  I'm not displaying the navigation instructions at all, and I don't really need second-by-second updates, so I've added a 30-second Cooldown to the profile that monitors the notification.  That way I still get data with the granularity I need, but don't waste a lot a system resources processing useless information; I get updates every 30 seconds even though the notification itself updates much more often than that.

The other thing you can do with a Schrödinger's profile is use it to replace a looping task.

I've never been conceptually happy with the Time and Torque task in the system.  I've previously described it as a "rogue task" because it's the only one that isn't triggered directly by some action. (http://mikesgeneralblog.blogspot.com/2015/06/digital-dash-documentation-part-9.html)  Instead, it gets kicked off by the Digital Dash startup task and simply loops about every 30 seconds until the system kills it via the shutdown task.  And that's always bothered me.  I'd prefer that it not run constantly in the background, and a profile with a Cooldown allows me trigger it to run regularly and on-demand without a loop.

Let's take a look at how to get that set up.  Let's say that, for some ungodly reason, you want Tasker to beep at you every five seconds.  We can make that happen.

The first thing to do is base a profile on a context that is always true.  For example, set up a global variable and Set it to something; doesn't matter what it is.  Then, you use a context of "Variable IS Set" in your profile.

Here's what that might look like (Assuming you've Set the %MyLoop variable elsewhere.)

Profile: Looper (468)
        Cooldown: 5
        State: Variable Value [ %MyLoop Set ]
Enter: Action (466)
        A1: Beep [ Frequency:8000 Duration:1000 Amplitude:12 Stream:3 ]


Enter this into Tasker and back all the way out.  After five seconds, you'll get a beep.  And after another five seconds you'll get...nothing.  And every five seconds after that, you'll get nothing.  You get one beep, and that's it.  Clearly, this isn't working.

That's because the Cooldown is really just a timer.  It gets triggered when the profile becomes active and starts counting down.  Once it reaches the end, the profile is allowed to run its Entry task.  The problem is, Cooldown doesn't automatically reset itself.  It only starts when the profile becomes active, and since, in the example above, the profile never becomes inactive, the Cooldown timer won't fire again.

So, we need to make the profile go inactive and then activate it again so the timer can restart itself.

We can easily add a line to the Entry task to accomplish the first part:

Profile: Looper (468)
        Cooldown: 5
        State: Variable Value [ %MyLoop Set ]
Enter: Action (466)
        A1: Variable Clear [ Name:%MyLoop Pattern Matching:Off ]
        A2: Beep [ Frequency:8000 Duration:1000 Amplitude:12 Stream:3 ]



By clearing the %MyLoop variable, we've made the profile go inactive because its context is no longer true.  Of course, we're still not going to get repeating beeps, because all we've done at this point is turn off the profile.  Now we need to activate it again.  We can do that by adding an Exit task.

Profile: Looper (468)
        Cooldown: 5
        State: Variable Value [ %MyLoop Set ]
Enter: Action (466)
        A1: Variable Clear [ Name:%MyLoop Pattern Matching:Off ]
        A2: Beep [ Frequency:8000 Duration:1000 Amplitude:12 Stream:3 ]

Exit: Reaction (467)
        A1: Variable Set [ Name:%MyLoop To:True Do Maths:Off Append:Off ]



So, what happens is, when the profile runs its Entry task (after the Cooldown period has expired) it gets turned off by line A1 in the Entry Task.  This causes it to immediately run its Exit task, which resets the %MyLoop variable, making the context True again, and restarting the Cooldown period.  Note that deactivating the profile does not stop the entry task from running to completion, so we get our beep.  And every five seconds after that, we'll get another beep until we go into Tasker and either disable the profile with its On/Off switch or explicitly clear the %MyLoop variable on the Var panel.

This is the technique I used to convert the Time and Torque loop to a triggered task rather than a loop, and it's working quite well.  I still get regular updates, but nothing is sitting in a Wait state.  And I'm happier with that.


Friday, November 20, 2015

Another Bluetooth Button Control Scheme

My post on adding button control to my Digital Dash project (http://mikesgeneralblog.blogspot.com/2015/08/physical-controls-for-digital-dash.html) has turned out to be one of the most popular ones that I've written. (Around here that only means about 550 views, but it's something.)  It's been linked to in a couple of places and over on the AutoApps forum someone asked me if there was a way to make a button continually perform a task as long as it was held down.  It turned out to be pretty easy, so here's an example that controls the Media Volume of a device.

I chose to use the "Media Rewind" keycode to lower the system's media volume because it made sense in the physical layout of the Bluetooth Gamepad I was using, but you can, of course, use any key you want. To make a companion way to increase the volume, just copy the profiles and tasks and rename them with "Up" instead of "Down" in the names and change the "-1" in line A1 of the first task to a "+1". You'll also need to modify the IF clause in line A3 so that it checks for a maximum value rather than a minimum.

Profile: Vo!umeDownStart (437)
Event: AutoInput Key [ Configuration:Keys: Media Rewind
Key Action: Key Down ]
Enter: VolDown (436)
A1: Media Volume [ Level:%VOLM-1 Display:Off Sound:Off ]
A2: Wait [ MS:250 Seconds:0 Minutes:0 Hours:0 Days:0 ]
A3: Goto [ Type:Action Number Number:1 Label: ] If [ %VOLM > 0 ]

Profile: VolumeDownStop (438)
Priority: 11
Event: AutoInput Key [ Configuration:Keys: Media Rewind
Key Action: Key Up ]
Enter: VolDownStop (439)
A1: Stop [ With Error:Off Task:VolDown ]

A couple of things to note: The first profile triggers on the Key Down event and the second one triggers on Key Up. Be sure you bump up the priority for launched tasks in the second profile; it needs to be greater than the the task it's trying to stop. You'll also need to run AutoInput's KeySupress function before you use these profiles and then disable key suppression when you're done. The 250 ms delay in line A2 seems to give a nice response ramp, but if you want something a little faster, just decrease that value.  Finally, you'll probably need to disable "Restore Settings" in the first profile to make the volume changes stick.

Hope this helps someone.

Wednesday, November 18, 2015

Another (Very) Minor Tweak

I have a control on the main screen that allows me to toggle the tablet's AutoBrightness on and off, but it doesn't get used very much.  99% of the time we're driving in the sun with the top down and I want the tablet at full brightness.  About the only time I use it is on those rare occasions  when we are traveling at night.

It occurred to me today, though, that if I do dim the screen on the tablet, the phone will remain at full brightness.  That would be annoying and since I don't really have a control interface on that unit, changing the brightness would be distracting and annoying, requiring me to go into settings and fumble around.

I don't really want to put controls on the phone, so instead I used AutoRemote.  Now when I toggle the screen brightness on the tablet, it sends a direct message to the phone, which causes it to toggle as well.

As I said, a very minor thing, but when I need to use it, it will be handy.

Wednesday, November 11, 2015

Torque and Tasker

If you're interested in getting data from the Torque app into Tasker, take a look at the video I posted on YouTube:

https://www.youtube.com/watch?v=bjFLbbYakBo

It begins by showing you how to set up Torque, then steps through gathering the information and tools that you need, and finishes up with some actual Tasker code.