Automating with Hue motion sensor, Applescript, Geektool and Keyboard Maestro

Been having a bit of fun the past couple of days learning how to get temperature data from the Philips Hue motion sensors and use it in (semi-)useful ways. I wrote up everything at my blog. Undoubtedly, some parts could be made more efficient, which I would welcome hearing about, but I’d also love to hear how others are using this sort of data you can get from Hue.

Very cool stuff. I just got started with Hue, but I’ve bookmarked this for the inevitable day when I get there :slight_smile:

From what I can tell, your round_truncate() AppleScript handler outputs a number to a specified number of decimal places. So, passing it number 123.456543 and specifying the number of decimal places as 3 returns 123.457, whilst specifying the number of decimal places as 4 returns 123.4565.

Here’s an alternative method that’s shorter, faster, and less likely to error:

to setDecimalPlaces for N to d as integer : 2
		local N, d
	
		set k to 10 ^ d
		set x to (round N * k rounding as taught in school) / k
		if d ≠ 0 then return x
		x as integer
end setDecimalPlaces

It can be called in any of the following ways:

setDecimalPlaces for 123.456543 to "2"
setDecimalPlaces for "123.456543"
setDecimalPlaces for "12.3456543E+1" to 2

You’ll notice that both parameters can accept a number or a string representation of a number, including one in scientific notation. Additionally, the "to" parameter is optional, and current is set to use 2 as the default value if omitted.

Your number_to_string() handler, which seems to be there to cater for numbers expressed using scientific notation, is, therefore, superfluous, since AppleScript understands scientific notation already.

You have a line in your script that converts a temperature value from Celsius to Fahrenheit:

set officeTempF to (officeTemp / 100 * 9 / 5 + 32)

To make the arithmetic less ambiguous to a casual reader, I would have written it like this:

set officeTempF to (officeTemp / 100) * (9 / 5) + 32

However, AppleScript is also familiar with various temperature scales, so it can perform the conversion for you if you prefer:

set officeTempF to (officeTemp / 100) as degrees Celsius as degrees Fahrenheit as number

It’s not necessarily the best example of AppleScript’s so-called “natural language syntax”, so you might prefer to stick with the arithmetic.

Lastly, I’d suggest changing this:

tell application "JSON Helper"
set result to (fetch JSON from "http://...")
tell state of result
       ...
end tell

to this:

tell application "JSON Helper" to fetch JSON from "http://"
tell state of result
        ...
end tell

As you will see, assigning something to the variable result is, at best, unnecessary, and in other cases, not a good idea, as result is a property in AppleScript that will always contain the output of the most recently executed line of a script. You should be able to tell it’s a special word because, when you compile the script in Script Editor, it will appear in a specific font and colour that differentiates it as a being a pre-defined term. This doesn’t mean you can’t use these terms or assign things to them; but you need to know when you can do this, and how to do it appropriately.


To summarise all of this, here’s one possible re-working of your script that incorporates the stuff above:

to setDecimalPlaces for N to d as integer : 2
		local N, d
	
		set k to 10 ^ d
		set x to (round N * k rounding as taught in school) / k
		if d ≠ 0 then return x
		x as integer
end setDecimalPlaces

tell application "JSON Helper" to fetch JSON from "http://[YOUR_BRIDGE_URL]/api/[YOUR_API_KEY]"
set officeTemp to the result's state's temperature / 100
set officeTempF to officeTemp as degrees Celcius as degrees Fahrenheit as number
setDecimalPlaces for officeTempF to 2

Chri.sk, thank you for taking so much time to go through the script so thoroughly. On the round_truncate() handler, I got that word for word from Sal Saghoian’s MacAutomation site so I assumed that was the most efficient way to perform that function. Kudos for finding something quicker and shorter.

In any case, half the reason I do this is to learn more and I appreciate your assistance in that.

You’re absolutely welcome.

Sal’s example AppleScripts have been around the internet for years and some were written before AppleScript had the extra commands bestowed upon it through its Standard Additions (round for example). In this case, that’s not the reason his script was more roundabout than it could have been (instead of round, one could instead coerce to an integer, but you have to take into account floating point precision which a simple coercion will succumb to when the numbers get smaller).

Having seen those scripts, they look like they were written purposely the way they are in order to get first-time scripters thinking about how problems should initially be broken down into a logical series of smaller steps; to showcase the different things AppleScript can do by doing them all in a few inefficient scripts rather than writing a ton of smaller, more opaque examples; and, of course, to increase the exposure of AppleScript’s syntax to novices and experts alike, as it takes getting used to in a way that experienced software developers sometimes can’t get their heads around.

The scripts will work, of course. But if you asked Sal, I doubt he’d use them himself in practice.

If you want, I can show you how to retrieve and parse the JSON from your API call without the need to call out to an external application. I chose not to include that before in case I overstepped by criticising every line of your script (which isn’t my goal).

Thank you, I’d love to learn more about JSON, which I know even less about than Applescript.

If you want to learn JSON very quickly, try this.

https://learnxinyminutes.com/docs/json/

If you want to spend a lot longer, this 10 minute Learn JSON - Full Crash Course for Beginners video might be worth a watch.

It might also be worth noting that the Jayson app by @simonbs will be landing on Mac before too long by the looks of things.

I’m not so much about to impart knowledge about JSON, rather than demonstrate that one can use AppleScript’s objective-C bridge to make the API call and obtain the relevant values from the JSON that gets returned. So, specifically, this part of the script where you call the JSON Helper:

tell application "JSON Helper" to fetch JSON from "http://[YOUR_BRIDGE_URL]/api/[YOUR_API_KEY]"

can instead be handled internally by importing the Foundation framework (see below) and using this sexy API call:

NSJSONSerialization's JSONObjectWithData:(NSData's ¬
		dataWithContentsOfURL:(NSURL's ¬
		URLWithString:"<your API URL>")) ¬
		options:0 |error|:(missing value) ¬
		as {record, list}

But I’m going to put that inside a handler and use a script object, largely just to help neaten the code up and break up that call into individual steps to make it a little more digestible. Here’s the full script:

use framework "Foundation"
use scripting additions

property URL : "http://[YOUR_BRIDGE_URL]/api/[YOUR_API_KEY]"

to setDecimalPlaces for N to d as integer : 2
		local N, d
	
		set k to 10 ^ d
		set x to (round N * k rounding as taught in school) / k
		if d ≠ 0 then return x
		x as integer
end setDecimalPlaces

on temperature from api
		local api
	
		script
				property a : my (NSURL's URLWithString:(api))
				property b : my (NSData's dataWithContentsOfURL:a)
				property c : my (NSJSONSerialization's JSONObjectWithData:b ¬
								options:0 |error|:(missing value)) as {record, list}
				property s : a reference to c's state
				property t : a reference to temperature of s
				property |°C| : (t / 100) as degrees Celsius
				property |°F| : |°C| as degrees Fahrenheit
				property value : setDecimalPlaces for (|°F| as number)
		end script
end temperature

get value of temperature from my URL

Now it’s no longer reliant on an external application to do the heavy lifting, the script will be faster, use fewer system resources overall, and you won’t need the JSON Helper running constantly in the background.

1 Like