New v14 REST API for application developers


 

Bryan Mayland

TVWBB Hall of Fame
Spent today getting together the framework of an actual REST API for interacting with LinkMeter from external applications. Documentation is filling out as I add functions on the wiki Accessing Raw Data Remotely.

The gist is that there is now a collection of URIs /cgi-bin/luci/lm/api/* which are designed for better interaction with 3rd party apps, specifically when it comes to being able to change parameters and do all the things the webui does when a user is logged in. Most important is the change from a username / password and sysauth and cookies and tokens and all that authentication to an API key-based authorization parameter. LinkMeter generates an API key on install and adding that to a POST request to any of the API functions will allow changes to be made. Quick example:
Code:
curl --data "sp=250&apikey=XXXX" http://heatermeter.local/cgi-bin/luci/lm/set
or
curl --data "sp=250" http://heatermeter.local/cgi-bin/luci/lm/set?apikey=XXXX
or
curl --data "value=250" http://heatermeter.local/cgi-bin/luci/lm/set/sp?apikey=XXXX

Right now all I have is current status, configuration, and version. I plan on adding archive features (list, stash, restore, reset current database), reboot avr, alarm test, possibly outputting history data in JSON? And other wizardry I do behind the scenes that maybe someone wants exposed to use in their app as well, just ask me about it! The old API is still there, but I believe people are having problems with doing SET commands on v14 snapshots and it would be pretty hacky for me to fix what is going wrong in a backward compatible way so this is the future.

Application developers are encouraged to query /cgi-bin/luci/lm/api/version to determine support. 404 Not found response? Old v13 and below API. 403 Forbidden response? User has disabled the external API. The API version number is returned in a JSON object on a 200 (success) return code. Other API requests return 403 if the supplied API key isn't valid or 405 Method Not Allowed if attempting to do a write operation and you did not use a POST HTTP method or 401 if supplied arguments are invalid.
 
Very nice! I installed the snapshot and tried it out.

In the config gui, I would suggest masking the api key, adding a "click to view" option. Reason being is that when we ask people to post screen shots of their config screen, we don't want them to expose their API key.

For new mobile apps that will need to use the api, you would need to manually copy / paste the API key into the app. I can see this being a bit of a hassle due to the string length. Having a URI that allowed the app to request the API key and then you click on "approve" in the config UI from a desktop browser might make this easier.
 
Here's my $0.02 for generating a 32 hex character key

Code:
</dev/urandom tr -dc '0-9a-f' | head -c32

This will include the whole alphabet:

Code:
</dev/urandom tr -dc '0-9a-z' | head -c32
 
I like your previous ideas but what makes you think that I need a way to generate a 32 character hex key? Did it not create one for you when you upgraded firmware? I used dd if=/dev/urandom bs=16 count=1 | hexdump -f '"%02x"'
 
I like your previous ideas but what makes you think that I need a way to generate a 32 character hex key? Did it not create one for you when you upgraded firmware? I used dd if=/dev/urandom bs=16 count=1 | hexdump -f '"%02x"'

Oh, it absolutely did. I just have a bit of an irrational dislike to the dd command. It's a long story involving a well meaning jr admin that mixed up the if= and of= statements. On 3 separate servers!
 
Oh, it absolutely did. I just have a bit of an irrational dislike to the dd command. It's a long story involving a well meaning jr admin that mixed up the if= and of= statements. On 3 separate servers!
Oh gotcha! I was worried that the upgrade process didn't work and left you with a blank API key for some reason.

I also have a fun story of using dd. `dd if=/dev/zero of=/dev/sda bs=512 count=1` when I meant to zero out the first block of the second partition because we were doing something fun with a raw filesystem, instead I zeroed out the MBR and partition table of the whole drive. Nobody reboot this server until we can figure out the partition sizes enough to recreate the table and cat in a new MBR!

I really like your idea of the whole go to a browser to authorize idea. I hate apps on tvs that are like "enter your full email address and complicated password on a keyboard with a random layout you have to navigate with arrows"... "sorry login incorrect (clears both fields)". What do you think of this interaction:

3rd Party App: GET /lm/api/provision?name=PitDroid
  • returns JSON object { status: PROVISION, user_url: http://.../admin/lm/authorize, token: QS04P, name: PitDroid, ip: yyy, timeout: 1492108297 }
  • name in the query is the name that will be displayed to the user on the authorize page
  • user_url is a url that can be provided to the user as to where to go to accept authorization
  • token is a code that should be displayed to the user to verify that which instance of "name" is requesting authorization
  • IP is the IP address of the 3rd party client which might be useful somehow. I also think there should be a lockout function if a single IP requests provisioning too many times in a certain amount of time
  • timeout - The timeout is when the provisioning request will time out on the HeaterMeter, the user must authorize in this window. 15 minutes I think? Provisioning requests are not persistent across Pi reboots, it must be completed before a reboot or timeout or else a new provision request must be started

After a status PROVISION response, the 3rd party app should start polling /lm/api/provision?token=QS04P (which must be from the same IP address) every N seconds which will keep returning the above JSON. The user will go to the URL and authorize or deny the request. When this is complete, the server will return the above JSON except it will contain status: OK and apikey on success, or status: ERROR and an error key with any additional information.
 
I did look at adding a QR code to scan because a lot of people do that with this webapi stuff. It would add another 12kb of minified javascript that would be loaded with every page (because all the webui script is bundled) which seemed like a waste. So many web apps have the kitchen sink of javascript load when you hit the page every time and it really bloats up the place and slows that page load down. Despite the web people having qr codes, I've never seen a qrcode scanner in an app before to take advantage of it either. Plus, if you're setting up an app on your phone, how are you going to shoot the QR code anyway? It would require you to go to a second device to bring the code up which seems like more of a hassle than just long-pressing the field and copy pasting it in.

I thought about demand-loading it but I thought about it a long time and decided it just wasn't worth it. I do like this API-based provisioning though.
 
The QR code would be on the desktop browser, and the mobile device would scan it. This is how new codes are added to the Google Authenticator app. I'm just throwin out ideas.
 
I have recenlty succesfully build my HeaterMeter and I am very happy with how it performs so far. I am fiddling around with the REST API to see if I can integrate it easily in my home automation setup. I have some issues with the API, probably I am doing something wrong.

If I make a HTTP Post request and want to set the PIT temp, this works, I get the following response:

User api_write setting 1 values...
sp to 130 = OK
Done!


I use the following URL: HOST/luci/lm/api/config/sp?apikey=xxx&sp=130

If I want to change for example the Lid open state I use the same URL except for the last parameter:

HOST/luci/lm/api/config/sp?apikey=xxx&ld=,,1

But then the request is not successful and I get the following error:

"No values specified" -> ("status":400,"statusText":"Bad Request")

I have tried to set alarms also, can't get them to work either. Gave me the same response.

Any ideas?
 
HOST/luci/lm/api/config/sp?apikey=xxx&ld=,,1
The base URL is /luci/lm/api/config, you've got an extra part on there (/sp). The /sp part in the documentation is an example for GET request to just get "sp" and should be omitted from set POST requests. The first request you sent actually shouldn't work but might just work due to a bug in the API side that doesn't expect that.

So like this:
Code:
http://heatermeter.local/luci/lm/api/config?apikey=XXXX&ld=,,1
I can't remember if the parameters have to be in the POST body or not
 
just a single parameter curl --data "value=250" http://heatermeter.local/luci/lm/api/config/sp?apikey=XXXX
Ah so I guess that behavior is intentional and I don't have anything to fix! See that "single parameter" is sp, and you were using the sp URI and trying to set ld with it. The single parameter URI for ld is ... ld, not sp. It would be:
curl --data "value=,,1" http://heatermeter.local/luci/lm/api/config/ld?apikey=XXXX

The example is correct. I've updated the documentation to be more clear and changed the example to not be sp (setpoint) due to maybe there being some confusion if sp meant "single parameter".
 
Last edited:

 

Back
Top