yunbridge: add linux side python code

to make the bridge work we need to run an askfirst instance on the ttyS.
additionally add the lua scripts needed to make REST work. this is really ugly
code but it works. i already cleaned up to the original code, but there are still
issues such as a new luci session being created for each request.

Signed-off-by: John Crispin <blogic@openwrt.org>
This commit is contained in:
John Crispin 2015-08-02 08:27:09 +02:00
parent d313c7b182
commit d88a1494fd
8 changed files with 788 additions and 0 deletions

48
utils/yunbridge/Makefile Normal file
View File

@ -0,0 +1,48 @@
#
# Copyright (C) 2006-2011 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=yunbridge
PKG_VERSION:=160
PKG_RELEASE=$(PKG_SOURCE_VERSION)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/arduino/YunBridge.git
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_SOURCE_VERSION:=f2042052115e71ad2c91f77e78d21db8275fcdd6
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz
PKG_MAINTAINER:=John Crispin <blogic@openwrt.org>
PKG_LICENSE:=GPL-2.0
include $(INCLUDE_DIR)/package.mk
define Package/yunbridge
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Arduino YUN bridge library
URL:=http://arduino.cc/
DEPENDS:=+python
endef
define Package/yunbridge/description
Arduino YUN bridge library
endef
define Build/Compile
true
endef
define Package/yunbridge/install
mkdir -p $(1)/usr/lib/python2.7/bridge
$(CP) $(PKG_BUILD_DIR)/bridge/*.py $(1)/usr/lib/python2.7/bridge/
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,yunbridge))

View File

@ -0,0 +1,6 @@
config bridge config
option socket_timeout 5
option secure_rest_api false
# remove this line to activae the yunbridge
option disabled 1

View File

@ -0,0 +1,22 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2013 OpenWrt.org
# start after and stop before networking
START=20
STOP=89
USE_PROCD=1
service_triggers()
{
procd_add_reload_trigger "yunbridge"
}
start_service()
{
[ "$(uci -q get yunbridge.config.disabled)" = "1" ] && return 0
procd_open_instance
procd_set_param command "/sbin/yunbridge"
procd_set_param respawn
procd_close_instance
}

View File

@ -0,0 +1,6 @@
#!/bin/sh
stty -F /dev/ttyS0 2500000 clocal cread cs8 -cstopb -parenb
exec < /dev/ttyS0
exec > /dev/ttyS0
exec 2> /dev/ttyS0
askfirst bin/ash --login

View File

@ -0,0 +1,75 @@
#!/usr/bin/lua
local function get_basic_net_info(network, iface, accumulator)
local net = network:get_network(iface)
local device = net and net:get_interface()
if device then
accumulator["uptime"] = net:uptime()
accumulator["iface"] = device:name()
accumulator["mac"] = device:mac()
accumulator["rx_bytes"] = device:rx_bytes()
accumulator["tx_bytes"] = device:tx_bytes()
accumulator["ipaddrs"] = {}
for _, ipaddr in ipairs(device:ipaddrs()) do
accumulator.ipaddrs[#accumulator.ipaddrs + 1] = {
addr = ipaddr:host():string(),
netmask = ipaddr:mask():string()
}
end
end
end
local function get_wifi_info(network, iface, accumulator)
local net = network:get_wifinet(iface)
if net then
local dev = net:get_device()
if dev then
accumulator["mode"] = net:active_mode()
accumulator["ssid"] = net:active_ssid()
accumulator["encryption"] = net:active_encryption()
accumulator["quality"] = net:signal_percent()
end
end
end
local function collect_wifi_info()
local network = require"luci.model.network".init()
local accumulator = {}
get_basic_net_info(network, "lan", accumulator)
get_wifi_info(network, "wlan0", accumulator)
return accumulator
end
local info = collect_wifi_info()
print("Current WiFi configuration")
if info.ssid then
print("SSID: " .. info.ssid)
end
if info.mode then
print("Mode: " .. info.mode)
end
if info.quality then
print("Signal: " .. info.quality .. "%")
end
if info.encryption then
print("Encryption method: " .. info.encryption)
end
if info.iface then
print("Interface name: " .. info.iface)
end
if info.uptime then
print("Active for: " .. math.floor(info.uptime / 60) .. " minutes")
end
if #info.ipaddrs > 0 then
print("IP address: " .. info.ipaddrs[1].addr .. "/" .. info.ipaddrs[1].netmask)
end
if info.mac then
print("MAC address: " .. info.mac)
end
if info.rx_bytes and info.tx_bytes then
print("RX/TX: " .. math.floor(info.rx_bytes / 1024) .. "/" .. math.floor(info.tx_bytes / 1024) .. " KBs")
end

View File

@ -0,0 +1,414 @@
--[[
This file is part of YunWebUI.
YunWebUI is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
As a special exception, you may use this file as part of a free software
library without restriction. Specifically, if other files instantiate
templates or use macros or inline functions from this file, or you compile
this file and link it with other files to produce an executable, this
file does not by itself cause the resulting executable to be covered by
the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
Copyright 2013 Arduino LLC (http://www.arduino.cc/)
]]
module("luci.controller.arduino.index", package.seeall)
local function not_nil_or_empty(value)
return value and value ~= ""
end
local function get_first(cursor, config, type, option)
return cursor:get_first(config, type, option)
end
local function set_first(cursor, config, type, option, value)
cursor:foreach(config, type, function(s)
if s[".type"] == type then
cursor:set(config, s[".name"], option, value)
end
end)
end
local function to_key_value(s)
local parts = luci.util.split(s, ":")
parts[1] = luci.util.trim(parts[1])
parts[2] = luci.util.trim(parts[2])
return parts[1], parts[2]
end
function http_error(code, text)
luci.http.prepare_content("text/plain")
luci.http.status(code)
if text then
luci.http.write(text)
end
end
function index()
function luci.dispatcher.authenticator.arduinoauth(validator, accs, default)
require("luci.controller.arduino.index")
local user = luci.http.formvalue("username")
local pass = luci.http.formvalue("password")
local basic_auth = luci.http.getenv("HTTP_AUTHORIZATION")
if user and validator(user, pass) then
return user
end
if basic_auth and basic_auth ~= "" then
local decoded_basic_auth = nixio.bin.b64decode(string.sub(basic_auth, 7))
user = string.sub(decoded_basic_auth, 0, string.find(decoded_basic_auth, ":") - 1)
pass = string.sub(decoded_basic_auth, string.find(decoded_basic_auth, ":") + 1)
end
if user then
if #pass ~= 64 and validator(user, pass) then
return user
elseif #pass == 64 then
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local stored_encrypted_pass = uci:get_first("yunbridge", "bridge", "password")
if pass == stored_encrypted_pass then
return user
end
end
end
luci.http.header("WWW-Authenticate", "Basic realm=\"yunbridge\"")
luci.http.status(401)
return false
end
local function make_entry(path, target, title, order)
local page = entry(path, target, title, order)
page.leaf = true
return page
end
-- web panel
local webpanel = entry({ "webpanel" }, alias("webpanel", "go_to_homepage"), _("%s Web Panel") % luci.sys.hostname(), 10)
webpanel.sysauth = "root"
webpanel.sysauth_authenticator = "arduinoauth"
make_entry({ "webpanel", "go_to_homepage" }, call("go_to_homepage"), nil)
--api security level
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local secure_rest_api = uci:get_first("yunbridge", "bridge", "secure_rest_api")
local rest_api_sysauth = false
if secure_rest_api == "true" then
rest_api_sysauth = webpanel.sysauth
end
--storage api
local data_api = node("data")
data_api.sysauth = rest_api_sysauth
data_api.sysauth_authenticator = webpanel.sysauth_authenticator
make_entry({ "data", "get" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
make_entry({ "data", "put" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
make_entry({ "data", "delete" }, call("storage_send_request"), nil).sysauth = rest_api_sysauth
local mailbox_api = node("mailbox")
mailbox_api.sysauth = rest_api_sysauth
mailbox_api.sysauth_authenticator = webpanel.sysauth_authenticator
make_entry({ "mailbox" }, call("build_bridge_mailbox_request"), nil).sysauth = rest_api_sysauth
--plain socket endpoint
local plain_socket_endpoint = make_entry({ "arduino" }, call("board_plain_socket"), nil)
plain_socket_endpoint.sysauth = rest_api_sysauth
plain_socket_endpoint.sysauth_authenticator = webpanel.sysauth_authenticator
end
function go_to_homepage()
luci.http.redirect("/index.html")
end
local function build_bridge_request(command, params)
local bridge_request = {
command = command
}
if command == "raw" then
params = table.concat(params, "/")
if not_nil_or_empty(params) then
bridge_request["data"] = params
end
return bridge_request
end
if command == "get" then
if not_nil_or_empty(params[1]) then
bridge_request["key"] = params[1]
end
return bridge_request
end
if command == "put" and not_nil_or_empty(params[1]) and params[2] then
bridge_request["key"] = params[1]
bridge_request["value"] = params[2]
return bridge_request
end
if command == "delete" and not_nil_or_empty(params[1]) then
bridge_request["key"] = params[1]
return bridge_request
end
return nil
end
local function extract_jsonp_param(query_string)
if not not_nil_or_empty(query_string) then
return nil
end
local qs_parts = string.split(query_string, "&")
for idx, value in ipairs(qs_parts) do
if string.find(value, "jsonp") == 1 or string.find(value, "callback") == 1 then
return string.sub(value, string.find(value, "=") + 1)
end
end
end
local function parts_after(url_part)
local url = luci.http.getenv("PATH_INFO")
local url_after_part = string.find(url, "/", string.find(url, url_part) + 1)
if not url_after_part then
return {}
end
return luci.util.split(string.sub(url, url_after_part + 1), "/")
end
function storage_send_request()
local method = luci.http.getenv("REQUEST_METHOD")
local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
local parts = parts_after("data")
local command = parts[1]
if not command or command == "" then
luci.http.status(404)
return
end
local params = {}
for idx, param in ipairs(parts) do
if idx > 1 and not_nil_or_empty(param) then
table.insert(params, param)
end
end
-- TODO check method?
local bridge_request = build_bridge_request(command, params)
if not bridge_request then
luci.http.status(403)
return
end
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
local sock, code, msg = nixio.connect("127.0.0.1", 5700)
if not sock then
code = code or ""
msg = msg or ""
http_error(500, "nil socket, " .. code .. " " .. msg)
return
end
sock:setopt("socket", "sndtimeo", socket_timeout)
sock:setopt("socket", "rcvtimeo", socket_timeout)
sock:setopt("tcp", "nodelay", 1)
local json = require("luci.json")
sock:write(json.encode(bridge_request))
sock:writeall("\n")
local response_text = {}
while true do
local bytes = sock:recv(4096)
if bytes and #bytes > 0 then
table.insert(response_text, bytes)
end
local json_response = json.decode(table.concat(response_text))
if json_response then
sock:close()
luci.http.status(200)
if jsonp_callback then
luci.http.prepare_content("application/javascript")
luci.http.write(jsonp_callback)
luci.http.write("(")
luci.http.write_json(json_response)
luci.http.write(");")
else
luci.http.prepare_content("application/json")
luci.http.write(json.encode(json_response))
end
return
end
if not bytes or #response_text == 0 then
sock:close()
http_error(500, "Empty response")
return
end
end
sock:close()
end
function board_plain_socket()
local function send_response(response_text, jsonp_callback)
if not response_text then
luci.http.status(500)
return
end
local rows = luci.util.split(response_text, "\r\n")
if #rows == 1 or string.find(rows[1], "Status") ~= 1 then
luci.http.prepare_content("text/plain")
luci.http.status(200)
luci.http.write(response_text)
return
end
local body_start_at_idx = -1
local content_type = "text/plain"
for idx, row in ipairs(rows) do
if row == "" then
body_start_at_idx = idx
break
end
local key, value = to_key_value(row)
if string.lower(key) == "status" then
luci.http.status(tonumber(value))
elseif string.lower(key) == "content-type" then
content_type = value
else
luci.http.header(key, value)
end
end
local response_body = table.concat(rows, "\r\n", body_start_at_idx + 1)
if content_type == "application/json" and jsonp_callback then
local json = require("luci.json")
luci.http.prepare_content("application/javascript")
luci.http.write(jsonp_callback)
luci.http.write("(")
luci.http.write_json(json.decode(response_body))
luci.http.write(");")
else
luci.http.prepare_content(content_type)
luci.http.write(response_body)
end
end
local method = luci.http.getenv("REQUEST_METHOD")
local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
local parts = parts_after("arduino")
local params = {}
for idx, param in ipairs(parts) do
if not_nil_or_empty(param) then
table.insert(params, param)
end
end
if #params == 0 then
luci.http.status(404)
return
end
params = table.concat(params, "/")
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
local sock, code, msg = nixio.connect("127.0.0.1", 5555)
if not sock then
code = code or ""
msg = msg or ""
http_error(500, "Could not connect to YunServer " .. code .. " " .. msg)
return
end
sock:setopt("socket", "sndtimeo", socket_timeout)
sock:setopt("socket", "rcvtimeo", socket_timeout)
sock:setopt("tcp", "nodelay", 1)
sock:write(params)
sock:writeall("\r\n")
local response_text = sock:readall()
sock:close()
send_response(response_text, jsonp_callback)
end
function build_bridge_mailbox_request()
local method = luci.http.getenv("REQUEST_METHOD")
local jsonp_callback = extract_jsonp_param(luci.http.getenv("QUERY_STRING"))
local parts = parts_after("mailbox")
local params = {}
for idx, param in ipairs(parts) do
if not_nil_or_empty(param) then
table.insert(params, param)
end
end
if #params == 0 then
luci.http.status(400)
return
end
local bridge_request = build_bridge_request("raw", params)
if not bridge_request then
luci.http.status(403)
return
end
local uci = luci.model.uci.cursor()
uci:load("yunbridge")
local socket_timeout = uci:get_first("yunbridge", "bridge", "socket_timeout", 5)
local sock, code, msg = nixio.connect("127.0.0.1", 5700)
if not sock then
code = code or ""
msg = msg or ""
http_error(500, "nil socket, " .. code .. " " .. msg)
return
end
sock:setopt("socket", "sndtimeo", socket_timeout)
sock:setopt("socket", "rcvtimeo", socket_timeout)
sock:setopt("tcp", "nodelay", 1)
local json = require("luci.json")
sock:write(json.encode(bridge_request))
sock:writeall("\n")
sock:close()
luci.http.status(200)
end

View File

@ -0,0 +1,199 @@
--
-- Code merged by gravityscore at http://pastebin.com/gsFrNjbt
--
-- Adaptation of the Secure Hashing Algorithm (SHA-244/256)
-- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm
--
-- Using an adapted version of the bit library
-- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua
--
module("luci.sha256", package.seeall)
local MOD = 2 ^ 32
local MODM = MOD - 1
local function memoize(f)
local mt = {}
local t = setmetatable({}, mt)
function mt:__index(k)
local v = f(k)
t[k] = v
return v
end
return t
end
local function make_bitop_uncached(t, m)
local function bitop(a, b)
local res, p = 0, 1
while a ~= 0 and b ~= 0 do
local am, bm = a % m, b % m
res = res + t[am][bm] * p
a = (a - am) / m
b = (b - bm) / m
p = p * m
end
res = res + (a + b) * p
return res
end
return bitop
end
local function make_bitop(t)
local op1 = make_bitop_uncached(t, 2 ^ 1)
local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end)
return make_bitop_uncached(op2, 2 ^ (t.n or 1))
end
local bxor1 = make_bitop({ [0] = { [0] = 0, [1] = 1 }, [1] = { [0] = 1, [1] = 0 }, n = 4 })
local function bxor(a, b, c, ...)
local z = nil
if b then
a = a % MOD
b = b % MOD
z = bxor1(a, b)
if c then z = bxor(z, c, ...) end
return z
elseif a then return a % MOD
else return 0
end
end
local function band(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = ((a + b) - bxor1(a, b)) / 2
if c then z = bit32_band(z, c, ...) end
return z
elseif a then return a % MOD
else return MODM
end
end
local function bnot(x) return (-1 - x) % MOD end
local function rshift1(a, disp)
if disp < 0 then return lshift(a, -disp) end
return math.floor(a % 2 ^ 32 / 2 ^ disp)
end
local function rshift(x, disp)
if disp > 31 or disp < -31 then return 0 end
return rshift1(x % MOD, disp)
end
local function lshift(a, disp)
if disp < 0 then return rshift(a, -disp) end
return (a * 2 ^ disp) % 2 ^ 32
end
local function rrotate(x, disp)
x = x % MOD
disp = disp % 32
local low = band(x, 2 ^ disp - 1)
return rshift(x, disp) + lshift(low, 32 - disp)
end
local k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
local function str2hexa(s)
return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end))
end
local function num2s(l, n)
local s = ""
for i = 1, n do
local rem = l % 256
s = string.char(rem) .. s
l = (l - rem) / 256
end
return s
end
local function s232num(s, i)
local n = 0
for i = i, i + 3 do n = n * 256 + string.byte(s, i) end
return n
end
local function preproc(msg, len)
local extra = 64 - ((len + 9) % 64)
len = num2s(8 * len, 8)
msg = msg .. "\128" .. string.rep("\0", extra) .. len
assert(#msg % 64 == 0)
return msg
end
local function initH256(H)
H[1] = 0x6a09e667
H[2] = 0xbb67ae85
H[3] = 0x3c6ef372
H[4] = 0xa54ff53a
H[5] = 0x510e527f
H[6] = 0x9b05688c
H[7] = 0x1f83d9ab
H[8] = 0x5be0cd19
return H
end
local function digestblock(msg, i, H)
local w = {}
for j = 1, 16 do w[j] = s232num(msg, i + (j - 1) * 4) end
for j = 17, 64 do
local v = w[j - 15]
local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3))
v = w[j - 2]
w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10))
end
local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
for i = 1, 64 do
local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
local maj = bxor(band(a, b), band(a, c), band(b, c))
local t2 = s0 + maj
local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local t1 = h + s1 + ch + k[i] + w[i]
h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
end
H[1] = band(H[1] + a)
H[2] = band(H[2] + b)
H[3] = band(H[3] + c)
H[4] = band(H[4] + d)
H[5] = band(H[5] + e)
H[6] = band(H[6] + f)
H[7] = band(H[7] + g)
H[8] = band(H[8] + h)
end
function sha256(msg)
msg = preproc(msg, #msg)
local H = initH256({})
for i = 1, #msg, 64 do digestblock(msg, i, H) end
return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
end

View File

@ -0,0 +1,18 @@
--- a/bridge/packet.py
+++ b/bridge/packet.py
@@ -93,12 +93,12 @@
def run(self, data):
if data[0] != 'X':
- call(['/usr/bin/blink-start', '100'])
+ #call(['/usr/bin/blink-start', '100'])
return chr(1)
if data[1:4] != '100':
- call(['/usr/bin/blink-start', '100'])
+ #call(['/usr/bin/blink-start', '100'])
return chr(2)
- call(['/usr/bin/blink-stop'])
+ #call(['/usr/bin/blink-stop'])
return chr(0) + '160' # send the actual bridge version
class PacketReader: