Controlling the Entire Database via HTML commands
The JSON Server can control Units assigned to it, but does not by default have access to the entire database. You may wish to control other units from an incoming HTML command. You could do this by creating proxy units on the JSON server and forwarding the command on from their ON and OFF scripts, but if you want to allow control of more than just a couple of Units this way that would become very tedious. Luckily we can actually process form variables in the ON scripts of JSON server units and so it is possible to make a universal command that can control any unit in the database.
In spite of it’s name the “JSON” server doesn’t actually require any JSON to be a part of the connection. Our first use for it was receiving JSON requests from a specific device and the name stuck. It will happily handle normal web requests and form variables that have nothing to do with JSON at all.
First create an accessor Unit assigned to the JSON server. The name of the unit can be anything that makes sense to you, but make the address something easy to access via the path in the JSON server. In this example the Unit will be addressed as “ACCESS”.
In the ON script use the “Insert” toolbar button to add the “JSONRequest” handler. The form of the handler in AppleScript is like this:
on JSONRequest( formData, JSONData)
In this example we’re not sending any JSON data, so that record passed to us will be empty. If you have a device that can send an actual JSON data structure then you can alter this example to use that and pass the data that way. This example will use simple GET variables, but the exact same code will work for POST variables as well.
Paste the following code into the ON script of the accessor unit. Whats happening is explained in the code comments.
(note: sometimes cutting and pasting from a browser adds multibyte spacing characters to the code which AppleScript can choke on during compiling. If you find that the script will not compile after pasting it into the script editor window try deleting the white space in front of the lines)
on JSONRequest(formData, JSONData) -- 2 or 3 form variables are required for this to work. -- name=the unit name (which must be properly URL escaped so really “the%20unit%20name”) -- command=on (can be on, off or setvalue. You can add more commands to the code below to do anything else.) -- if the command is setvalue then a value must also be specified -- value=50 -- get the form variables out of the formData record -- since terms like “name” and “command” and “value” are reserved words in AppleScript -- or included in our dictionary, in order to make them function as keys into the record -- we have to put them inside pipe characters “|” like “|name|" -- the send reply command is necessary to have a response sent to the browser or other requestor. Without that -- the connection will sit and wait for it for 15 seconds or so and then send a timeout error. -- using the send reply command sends whatever data you supply to the requestor. You could even return -- a full JSON item here with any data you wished. try set WorkUnit to |name| of formData set WorkCommand to |command| of formData if WorkCommand is equal to "setvalue" then set WorkValue to |value| of formData end if on error write log "no unit name specified" send reply "invalid parameters" & return return code 400 return end try -- based on what the command variable is we do different actions -- the whole thing is wrapped in a try/onerror block so that we can return an error -- to the server if the unit name is not found. try if WorkCommand is equal to "on" then write log "turning on " & WorkUnit & " from JSON server command" turnon WorkUnit else if WorkCommand is equal to "off" then write log "turning off " & WorkUnit & " from JSON server command" turnoff WorkUnit else if WorkCommand is equal to "setvalue" then write log "dim " & WorkUnit & " to " & WorkValue & " from JSON server command" dim WorkUnit to WorkValue else write log "unknown command from JSON accessor: (" & WorkCommand & ")" send reply "unknown command (" & WorkCommand & ")" & return return code 400 return end if on error send reply "unit not found (" & WorkUnit & ")" & return return code 400 return end try send reply "OK" & return return code 200 end JSONRequest
now you can control any unit in your database via a simple html request. For example to control a unit with curl you could do something like:
curl “http://your.address:port/[server token]/access?name=the%20unit%20name&command=on” curl “http://your.address:port/[server token]/access?name=the%20unit%20name&command=off” curl “http://your.address:port/[server token]/access?name=the%20unit%20name&command=setvalue&value=50”
The [server token] in the above should be replaced with a valid token setup on your JSON Server. See the JSON Server page for more info. The port should be the same port you setup for the JSON server. The address portion can be the static IP of the server itself if your devices are only communicating on your local network. If you want to be able to control some units from the internet itself then you will need a dynamic DNS service and a router passthrough on the port of the server. See the networking for home automation page for more info on that.
Limiting Access To Specific Units
Perhaps you don’t want to expose the entire database in this way but rather limit it to just specific units. In this example we’ll add some code to check and see if the requested unit is in a specific list.
Create a new List in XTension called something like “HTML Access Allowed” and drag any units you want to be able to control from the HTML/JSON server into it.
In the ON script first create a property at the top of the ON script, not inside the on JSONRequest handler that holds the name of the List that you want to check against:
property allowedUnitsList:”HTML Access Allowed”
This block of code goes after the first try/onerror block, but before the second one. The entire script will be posted again below.
-- make sure unit is in the list of allowed units if WorkUnit is not in (all of list allowedUnitsList) then write log "HTML access for restricted unit (" & WorkUnit & ") not in allowed list." color red send reply "access not allowed" & return return code 403 return end if
The all of list verb returns an AppleScript list of all the units in that list. Then you can use the simple AppleScript “is in” or “is not in” commands to know if the unit is in the list or not. If you wanted you could use a Group rather than a List. Place the units in a group and use the all of group verb instead to get the list of it’s Units.
If control is requested for a unit not in that list a “403: Forbidden” error will be returned to the browser or other device.
You can have multiple accessor Units pointed to different control lists for doing different things or from different devices if that makes it easier to keep them straight. Just give the other units unique addresses and change the name of the access control list.
Here is the On script code in it’s entirety with the access control code inserted:
property allowedUnitsList:”HTML Access Allowed” on JSONRequest(formData, JSONData) -- 2 or 3 form variables are required for this to work. -- name=the unit name (which must be properly URL escaped so really “the%20unit%20name”) -- command=on (can be on, off or setvalue) -- if the command is setvalue then a value must also be specified -- value=50 -- get the form variables out of the formData record -- since terms like “name” and “command” and “value” are reserved words in AppleScript -- or included in our dictionary, in order to make them function as keys into the record -- we have to put them inside pipe characters “|”, “|name|" -- the send reply command is necessary to have a response sent to the browser or other requestor. Without that -- the connection will sit and wait for it for 15 seconds or so and then send a timeout error. -- using the send reply command sends whatever data you supply to the requestor. You could even return -- a full JSON item here with any data you wished. try set WorkUnit to |name| of formData set WorkCommand to |command| of formData if WorkCommand is equal to "setvalue" then set WorkValue to |value| of formData end if on error write log "no unit name specified" send reply "invalid parameters" & return return code 400 return end try -- make sure unit is in the list of allowed units if WorkUnit is not in (all of list allowedUnitsList) then write log "HTML access for restricted unit (" & WorkUnit & ") not in allowed list." color red send reply "access not allowed" & return return code 403 return end if -- based on what the command variable is we do different actions -- the whole thing wrapped in a try/onerror block so that we can return an error -- to the server if the unit name is not found. try if WorkCommand is equal to "on" then write log "turning on " & WorkUnit & " from JSON server command" turnon WorkUnit else if WorkCommand is equal to "off" then write log "turning off " & WorkUnit & " from JSON server command" turnoff WorkUnit else if WorkCommand is equal to "setvalue" then write log "dim " & WorkUnit & " to " & WorkValue & " from JSON server command" dim WorkUnit to WorkValue else write log "unknown command from JSON accessor: (" & WorkCommand & ")" send reply "unknown command (" & WorkCommand & ")" & return return code 400 return end if on error send reply "unit not found (" & WorkUnit & ")" & return return code 400 return end try send reply "OK" & return return code 200 end JSONRequest