Ferdinand Keil's

electronic notes

Aug 07, 2013

Connect V-USB devices to the Internet with the TL-WR703N – Part 2

In the first part of this mini-series I established a basic communication between my device and the TL-WR703N. In this part I will request data from my device over USB and then push it to Cosm. Cosm is a service that can store and display sensor data, which is exactly what I want to do. Cosm recently became Xively (the third name-change in the history of the service...). The good news is, that the old API still works.

You can talk to Cosm over a REST API. I searched the web for a Lua implementation of this API and finally found some code by a guy named Shadock. To simplify usage I created a small Lua library:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- save as 'luacosm.lua'

-- load libs
require("ltn12")
socket = require("socket")
http = require("socket.http")

-- cosm_put(apikey, feed, datastream, value)
-- Push's a value to the specified Cosm feed.
-- Has to be given an API-Key with the necessary permissions.
--
-- based on Shadock's code as published on
-- http://forum.micasaverde.com/index.php?topic=7396.0
function cosm_put(apikey, feed, datastream, value)
    local base_url = "http://api.cosm.com/v2/feeds/"
    local method = "PUT"

    local json_data = '{ "version":"1.0.0","datastreams":[ {"id":"' .. datastream .. '", "current_value":"' .. value .. '"}]}'
    local response_body = {}
    local response, status, header = http.request{
        method = method,
        url = base_url .. feed .. ".json",
        headers = {
            ["Content-Type"] = "application/json",
            ["Content-Length"] = string.len(json_data),
            ["X-ApiKey"] = apikey
        },
        source = ltn12.source.string(json_data),
        sink = ltn12.sink.table(response_body)
    }
    return response, status, header
end

A short example shows how to use the library:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
require("math")
require("luacosm")

local apikey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
local feedid = 123456
math.randomseed( os.time() )
local humidity = math.random(60, 80)
print("Humidity: " .. humidity .. "%relH")

local response, status, header = cosm_put(apikey, feedid, "Humidity", humidity)

To start sending data, you first have to create so called feed in Cosm. This gives you the feed ID. You also need your personal API key, which can also be found on the Cosm website. Then all that is left is pushing some data to your feed. The code shown above runs on the firmware image Madox generated for the TL-WR703N. It ships with all necessary Lua libraries, so no need to compile anything.

After verifying that I can upload random data to Cosm, I still need to retrieve some real sensor readings from my device. Again I used V-USB and lualibusb1 to connect my device to the router. I adapted the script presented in the first part of the series for this purpose.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
require("luacosm")

usb = require("libusb1")

PSCMD_STATUS =  1
PSCMD_ON =      2
PSCMD_OFF =     3

local apikey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
local feedid = 123456

local handle = usb.open_device_with_vid_pid(0x16C0, 0x05DC);
if ((usb.get_string_descriptor_ascii(handle, 1) ~= "www.ferdinandkeil.com")
    and (usb.get_string_descriptor_ascii(handle, 2) ~= "Humidor Steuerung")) then
    handle = nil;
end


if (handle~=nil) then
    print("Device successfully initialised!")

    local type = usb.LIBUSB_REQUEST_TYPE_VENDOR + usb.LIBUSB_RECIPIENT_DEVICE + usb.LIBUSB_ENDPOINT_IN
    local request = PSCMD_STATUS

    print("Request data from device...")
    local response = usb.control_transfer(handle, type, request, 0, 0, 8, 5000)

    local hi = string.byte(response, 2)
    local lo = string.byte(response, 1)
    local humidity = ( (hi*255) + lo ) / 100
    print("Measured Humidity: " .. humidity .. "%relH")

    print("Try to upload to Cosm...")
    local response, status, header = cosm_put(apikey, feedid, "Feuchtigkeit", humidity)
    print("Finished")
else
    print("Device initialisation failed!")
end

Now let's go through some of the important parts of the code:

12
13
14
15
16
local handle = usb.open_device_with_vid_pid(0x16C0, 0x05DC);
if ((usb.get_string_descriptor_ascii(handle, 1) ~= "www.ferdinandkeil.com")
    and (usb.get_string_descriptor_ascii(handle, 2) ~= "Humidor Steuerung")) then
    handle = nil;
end

I still use the VID/PID supplied by Objective Development, but this time I tried to honour their license terms. They basically say you can use their VID/PID freely, but you have to give your device a unique descriptor. So I ask the device for its descriptor and only accept the one I have chosen for my device.

local hi = string.byte(response, 2)
local lo = string.byte(response, 1)
local humidity = ( (hi*255) + lo ) / 100

On the micro-controller side the humidity is a 16 bit value, so it is send as two bytes. On the router side I have to reassemble these two bytes into one value. That is done with the code shown above. string.byte(s, i) gives me the raw byte value for the i-th byte in string s. I then shift the high byte to the left (hi*255) and then add the low byte. It took me some time to figure this one out, as I am accustomed to the way C handles data. But you can't easily typecast variables in Lua, so you have to use some helper functions. Also there seems to be no shift operator, so you have to rely on multiplication and division.