1#!/usr/bin/lua
2--
3-- weatherstation.lua - a simple web server intended to be run from tcpd to
4-- expose sensors from the BeagleBone Weather cape and serve the web app.
5--
6
7-- List of files shared by this service.
8distfiles={}
9distfiles["/index.html"] = true
10distfiles["/style.css"] = true
11distfiles["/jquery.js"] = true
12distfiles["/processing.js"] = true
13distfiles["/spin.js"] = true
14distfiles["/weatherstation.js"] = true
15
16-- Base path for distfiles
17prefix="/usr/share/beaglebone/weather"
18
19-- Check that the filename is part of this demo.
20function filename_is_valid(filename)
21	return distfiles[filename] ~= nil
22end
23
24-- Check if the string 's' starts with 'starting'
25function starts_with(s, starting)
26	return string.sub(s, 1, string.len(starting)) == starting
27end
28
29-- Check if the string 's' ends with 'ending'
30function ends_with(s, ending)
31	return string.sub(s, -string.len(ending)) == ending
32end
33
34-- Return the content type of the file based only on the file extension.
35function get_content_type(filename)
36
37	if ends_with(filename, ".js") then
38		return "application/javascript"
39	elseif ends_with(filename, ".css") then
40		return "text/css"
41	elseif ends_with(filename, ".html") then
42		return "text/html"
43	elseif ends_with(filename, ".json") then
44		return "application/json"
45	else
46		return "text/plain"
47	end
48end
49
50-- Reads from STDIN until an empty or nil line is received.
51-- Returns the first line of the request.
52function read_request()
53
54	local i = 0
55	local lines = {}
56
57	repeat
58		local line = io.read("*line")
59		if line == nil or line == "\r" then
60			break
61		end
62		lines[i] = line
63		i = i + 1
64	until false
65
66	return lines[0]
67end
68
69-- Return the entire contents of a file
70function read_file(filename)
71	local f = io.open(filename, "rb")
72
73	if f == nil then
74		return nil
75	end
76	local content = f:read("*all")
77	f:close()
78
79	return content
80end
81
82-- Extract the value of the LABEL:VALUE pairs read from the device files.
83function extract_value(data, pat)
84
85	local x = string.match(data, pat)
86
87	if x == nil then
88		return "0"
89	end
90	return x
91end
92
93-- Read the sensor values and generate json output
94function generate_json()
95	local json = ""
96
97	local tsl2550 = read_file("/dev/tsl2550b3s39")
98	if tsl2550 == nil then
99		return nil
100	end
101	local illuminance = extract_value(tsl2550, "ILLUMINANCE: (%d+)")
102
103	local sht21 = read_file("/dev/sht21b3s40")
104	if sht21 == nil then
105		return nil
106	end
107	local temperature = extract_value(sht21, "TEMPERATURE: (%d+.%d+)")
108	local humidity = extract_value(sht21, "HUMIDITY: (%d+.%d+)")
109
110	local bmp085 = read_file("/dev/bmp085b3s77")
111	if bmp085 == nil then
112		return nil
113	end
114	local pressure = extract_value(bmp085, "PRESSURE: (%d+)")
115
116	json = json .. "{\n"
117	json = json .. "\"temperature\": " .. temperature .. ",\n"
118	json = json .. "\"humidity\": " .. humidity .. ",\n"
119	json = json .. "\"illuminance\": " .. illuminance .. ",\n"
120	json = json .. "\"pressure\": " .. pressure .. "\n"
121	json = json .. "}\n"
122
123	return json
124end
125
126function handle_request(req)
127
128	if req == nil then
129		response("404 Not Found", "text/plain", "Unknown Request")
130		return
131	end
132
133	-- Parse filename out of HTTP request
134	local filename = (string.match(req, " %S+ "):gsub("^%s+", ""):gsub("%s+$", ""))
135	if filename == "" or filename == "/" then
136		filename = "/index.html"
137	end
138
139	-- Check if the filename is known (i.e. it's a file in the web app)
140	if filename_is_valid(filename) then
141		local contents = read_file(prefix .. filename)
142
143		if contents ~= nil then
144			response("200 OK", get_content_type(filename), contents)
145		else
146			response("404 Not Found", "text/plain", "Failed to load known file")
147		end
148	-- Else maybe the user is requesting the dynamic json data
149	elseif starts_with(filename, "/weather.json") then
150		local json = generate_json()
151		if json == nil then
152			response("500 Internal Server Error", "text/plain", "Could not get sensor values")
153		else
154			response("200 OK", get_content_type("/weather.json"), json)
155		end
156	-- Else, the user is requesting something not part of the app
157	else
158		response("403 Forbidden", "text/plain", "File not allowed.")
159	end
160
161	return
162end
163
164-- Send the response to the HTTP request.
165function response(err_code, content_type, content)
166	io.write("HTTP/1.1 " .. err_code .. "\r\n")
167	io.write("Content-Type: " .. content_type .. "\r\n")
168	io.write("\r\n")
169	io.write(content)
170	io.write("\r\n")
171end
172
173-- Read the request and then handle it.
174handle_request(read_request())
175
176