--- /dev/null
+#!/bin/sh
+
+logdb=`uci get weathermon.process.logdb`
+dumpdir=/var/weather/www
+mkdir -p $dumpdir
+/usr/bin/weather-filter $logdb dump-compacted - $dumpdir
+
--- /dev/null
+#!/bin/sh
+
+yesterday=`echo "print(os.date(\"%Y-%m-%d\",os.time()-24*60*60))" | /usr/bin/lua`
+
+logdb=`uci get weathermon.process.logdb`
+
+dumpdir=`uci get weathermon.process.archive_dir`/`echo "print(os.date(\"%Y/%m/%d\",os.time()-22*60*60))" | /usr/bin/lua`
+
+mkdir -p $dumpdir
+
+/usr/bin/weather-filter $logdb dump-compacted $yesterday $dumpdir
+
+sqlite3 $logdb "delete from log where time_stamp<date('now','-1 day')"
--- /dev/null
+#!/usr/bin/lua
+
+local json = require("json")
+local socket = require("socket")
+local http = require("socket.http")
+
+require "wm_util"
+
+function getConfig(configname)
+
+ local command,f
+
+ local uci=require("uci")
+ local cur=uci.cursor()
+ local config
+ if configname then
+ config=configname
+ else
+ config="weathermon"
+ end
+
+ web_url = cur.get(config,"web","url")
+ web_user = cur.get(config,"web","user")
+ web_timeout = cur.get(config,"web","timeout")
+ web_pass = cur.get(config,"web","password")
+
+ if not web_timeout then
+ web_timeout = 10
+ end
+
+ backlogdb = cur.get(config,"process","backlogdb")
+
+end
+
+function submitValue(timestamp,type,id,param,val)
+
+ local url = web_url.."?stype="..url_encode(type).."&sid="..url_encode(id).."¶m="..url_encode(param).."&value="..url_encode(val).."&time="..url_encode(timestamp)
+
+ if web_user then
+ url = url:gsub("//","//"..web_user..":"..web_pass.."@",1)
+ end
+
+ local result,code = http.request ({
+ url=url, create=function()
+ local req_sock = socket.tcp()
+ req_sock:settimeout(web_timeout)
+ return req_sock
+ end})
+
+ if code ~= 200 then
+ return false
+ end
+
+ return true
+
+end
+
+getConfig(arg[1])
+
+if backlogdb then
+
+ local dbdriver = require "luasql.sqlite3"
+ env = assert(dbdriver.sqlite3())
+ if file_exists(backlogdb) then
+ backlog_con = assert(env:connect(backlogdb))
+ end
+
+ cursor = assert(backlog_con:execute("SELECT rowid,time_stamp,sensor_id,sensor,param,value FROM queue LIMIT 400"))
+ row = cursor:fetch ({}, "a")
+ while row do
+ if submitValue(row.time_stamp,row.sensor,row.sensor_id,row.param,row.value) then
+ backlog_con:execute(string.format("DELETE FROM queue WHERE rowid='%s'",row.rowid))
+ end
+ row = cursor:fetch (row, "a")
+ end
+
+end
--- /dev/null
+#!/bin/sh
+
+BACKUP_DIR=`uci get weathermon.process.backup_dir`
+BACKLOGDB=`uci get weathermon.process.backlogdb`
+LOGDB=`uci get weathermon.process.logdb`
+
+if [ ! -z "$BACKLOGDB" ]; then
+ BACKLOG_BASE=$(basename "$BACKLOGDB")
+ sqlite3 "$BACKLOGDB" ".backup $BACKUP_DIR/$BACKLOG_BASE"
+fi
+
+if [ ! -z "$LOGDB" ]; then
+ LOG_BASE=$(basename "$LOGDB")
+ sqlite3 "$LOGDB" ".backup $BACKUP_DIR/$LOG_BASE"
+fi
--- /dev/null
+#!/usr/bin/lua
+
+require "uci"
+local cur = uci.cursor()
+local socket = require "socket"
+local lfs = require "lfs"
+local json = require "json"
+
+require "wm_util"
+
+function get_levels_list(config_name)
+ local levels, engage, engage_mode, disengage_mode, levels_idx
+ levels = {}
+ levels_idx = {}
+ engage = {}
+ engage_mode = {}
+ disengage_mode = {}
+ cur.foreach(config_name, "alarm", function(s)
+ local idx = #levels+1
+ levels[idx] = s["name"]
+ engage[idx] = s["engage"]
+ engage_mode[idx] = s["engage_mode"]
+ disengage_mode[idx] = s["disengage_mode"]
+ levels_idx[s[".name"]] = idx
+ end)
+ return levels, engage, engage_mode, disengage_mode, levels_idx
+end
+
+function get_params(config_name,levels_idx)
+ local a_names, a_formats, a_limits
+ a_names = {}
+ a_formats = {}
+ a_limits = {}
+ cur.foreach(config_name,"params", function(s)
+ if s["name"] then
+ a_names[s["param"]] = s["name"]
+ end
+ if s["format"] or s["scale"] then
+ local format, scale
+ if s["scale"] then
+ scale = s["scale"]
+ else
+ scale = 1
+ end
+ if s["format"] then
+ format = s["format"]
+ else
+ format = "4s"
+ end
+ a_formats[s["param"]] = {format,scale}
+ end
+ if s["limits"] then
+ for i,record in pairs(s["limits"]) do
+ rec = split(record,":")
+ idx = levels_idx[rec[1]]
+ low = tonumber(rec[2])
+ high = tonumber(rec[3])
+ if not a_limits[s["param"]] then
+ a_limits[s["param"]] = {}
+ end
+ if not a_limits[s["param"]][idx] then
+ a_limits[s["param"]][idx] = {}
+ end
+ rec = a_limits[s["param"]][idx]
+ a_limits[s["param"]][idx][#rec+1] = {low,high}
+ end
+ end
+ end)
+ return a_names,a_formats,a_limits;
+end
+
+function check_limit(param,value,limits)
+
+ limit = limits[param]
+ if limit then
+
+ for level,ranges in pairs(limit) do
+ for key,range in pairs(ranges) do
+ if value>=range[1] and value<range[2] then
+ return level
+ end
+ end
+ end
+
+ return 0
+
+ else
+
+ return 0
+
+ end
+
+end
+
+local config_name = arg[1]
+
+if not config_name then
+ config_name = "weathermon"
+end
+
+print("loading config...")
+
+local a_levels, a_engage, a_engage_mode, a_disengage_mode, levels_idx = get_levels_list(config_name)
+
+a_leds = {}
+
+for level,leds in pairs(a_engage) do
+
+ for key,led in pairs(leds) do
+
+ a_leds[led] = a_disengage_mode[level]
+
+ end
+
+end
+
+local a_names,a_formats,limits = get_params(config_name,levels_idx)
+
+local w_led = cur.get(config_name, "process", "engage")
+local w_engage = cur.get(config_name, "process", "engage_mode")
+local w_disengage = cur.get(config_name, "process", "disengage_mode")
+
+local senstemplate = string.gsub(cur.get(config_name, "display", "formatstr"),"~",string.char(223))
+local out_file = cur.get(config_name, "display", "file")
+
+local watch_file = cur.get(config_name,"process","dump_file")
+
+if not watch_file then
+ return
+end
+
+local last = 0
+
+local data = ""
+local old_data = ""
+local sensor_data
+local res
+
+local mute_file=cur.get(config_name, "process", "mute_file")
+local mute_time=tonumber(cur.get(config_name, "process", "mute_time"))
+local alarm_raise = 2
+local onoff=true
+
+local timestr_template = cur.get(config_name, "display", "timestr")
+if not timestr_template then
+ timestr_template = " %d.%m.%y %H:%M "
+end
+
+local alarmstr_len = cur.get(config_name, "display", "strlen")
+if not alarmstr_len then
+ alarmstr_len = 20
+end
+alarmstr_len=tonumber(alarmstr_len)
+
+web_id = uci.get(config_name,"web","devid")
+
+while true do
+
+ onoff = not onoff
+
+ local timestr = os.date(timestr_template)
+
+ local mod = lfs.attributes(watch_file,"modification")
+ local muted_mod = lfs.attributes(mute_file,"modification")
+ local muted_beep = muted_mod and (muted_mod > (os.time() - mute_time))
+
+ if mod then
+
+ if last < mod then
+ last = mod
+ res,sensor_data = pcall(function ()
+ local f = io.open(watch_file,"r")
+ content = f:read("*all")
+ io.close(f)
+ return json.decode(content)
+ end)
+
+ write_file(w_led,w_engage)
+
+ else
+
+ write_file(w_led,w_disengage)
+
+ end
+
+ local values = {}
+ local printable = {}
+
+ for sensor,sensor_params in pairs(sensor_data[web_id]) do
+ if sensor ~= "timestamp" then
+ for param,value in pairs(sensor_params) do
+ local name = sensor.."."..param
+ values[name]=value
+ if a_formats[name] then
+ printable[name]=string.format("%"..a_formats[name][1],value*a_formats[name][2])
+ end
+ end
+ end
+ end
+
+ level = 1
+ alarms = ""
+
+ if not muted_beep then
+ for key,value in pairs(values) do
+ value_level = check_limit(key,value,limits)
+ if value_level > level then
+ level = value_level
+ alarms = a_names[key]
+ elseif value_level == level then
+ alarms = alarms.." "..a_names[key]
+ end
+ end
+ end
+
+ leds_engage = {}
+
+ for key,value in pairs(a_engage[level]) do
+ leds_engage[value] = a_engage_mode[level]
+ end
+
+ for led,mode in pairs(a_leds) do
+ if leds_engage[led] then
+ write_file(led,leds_engage[led])
+ else
+ write_file(led,mode)
+ end
+ end
+
+ alarms = trim(alarms)
+ alarmstr = a_levels[level]..": "..alarms
+
+ if alarmstr:len()>alarmstr_len then
+ alarmstr=alarmstr:sub(1,alarmstr_len)
+ elseif alarmstr:len()<alarmstr_len then
+ local delta = alarmstr_len - alarmstr:len()
+ local before = math.floor(delta/2)
+ local after = delta - before
+ alarmstr = string.rep(" ",before)..alarmstr..string.rep(" ",after)
+ end
+
+ sensstr = string.gsub(senstemplate,"%{.-%}", function (s) return printable[s:sub(2,s:len()-1)]; end)
+
+ if onoff and (level>=alarm_raise) then
+ data = alarmstr..sensstr
+ else
+ data = timestr..sensstr
+ end
+
+ if old_data ~= data then
+
+ old_data = data
+ print(data)
+ write_file(out_file,data)
+
+ end
+
+ end
+
+ socket.sleep(1)
+
+end
--- /dev/null
+#!/usr/bin/lua
+
+local uci = require("uci")
+local cur = uci.cursor()
+local json = require "json"
+
+local logdb = arg[1]
+
+require "wm_util"
+
+if not logdb then
+
+ print("no sqlite log defined!")
+ return
+
+end
+
+function shallowcopy(orig)
+ local orig_type = type(orig)
+ local copy
+ if orig_type == 'table' then
+ copy = {}
+ for orig_key, orig_value in pairs(orig) do
+ copy[orig_key] = orig_value
+ end
+ else -- number, string, boolean, etc
+ copy = orig
+ end
+ return copy
+end
+
+function median(dataset)
+
+ table.sort(shallowcopy(dataset))
+ return dataset[math.floor(#dataset/2)]
+
+end
+
+function filter_data(dataset,width)
+
+ if not width then
+ width = 7
+ end
+
+ local result = {}
+
+ local window_spread = math.floor(width/2)
+ local window = {}
+
+ for i = 1,window_spread do
+ window[#window+1] = dataset[i]["y"]
+ end
+
+ for key,value in pairs(dataset) do
+ nextelem = dataset[key+window_spread]
+ if nextelem then
+ window[#window+1] = nextelem["y"]
+ end
+ if not nextelem or #window>width then
+ table.remove(window,1)
+ end
+ row = {}
+ row["t"]=value["t"]
+ row["y"]=median(window)
+ result[#result+1] = row
+ end
+
+ return result
+
+end
+
+function average_results(dataset,con)
+ local name = os.tmpname()
+ touch(name)
+ local tmpcon = assert(env:connect(name))
+ assert(tmpcon:execute("create table series(time_stamp datetime,value float)"))
+ for key,value in pairs(dataset) do
+ assert(tmpcon:execute(string.format("INSERT INTO series(time_stamp,value) VALUES ('%s','%s')",value["t"],value["y"])))
+ end
+ local sql = "select rounded as t,avg(value) as y from (select substr(strftime('%Y-%m-%dT%H:%M',time_stamp),1,15)||'5:00' rounded,value from series) group by rounded order by rounded"
+ results = run_sql(sql,tmpcon)
+ tmpcon:close()
+ os.remove(name)
+ return results
+end
+
+function run_sql(sql,con)
+ local result = {}
+
+ cursor = assert(con:execute(sql))
+ row = cursor:fetch ({}, "a")
+ while row do
+ result[#result+1] = row
+ row = cursor:fetch ({}, "a")
+ end
+
+ return result
+end
+
+function get_list(day,con)
+ if day == "-" then
+ sql = string.format("SELECT DISTINCT sensor_id,sensor,param FROM log WHERE time_stamp>=datetime('now','-1 day','localtime') ORDER BY sensor_id,sensor,param")
+ else
+ sql = string.format("SELECT DISTINCT sensor_id,sensor,param FROM log WHERE time_stamp>='%s' and time_stamp<date('%s','+1 day') ORDER BY sensor_id,sensor,param",day,day)
+ end
+ return run_sql(sql,con)
+end
+
+function get_raw(day,con,sensor_id,sensor_type,param)
+ format = '%Y-%m-%dT%H:%M:%S'
+ if day == "-" then
+ sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>=datetime('now','-1 day','localtime') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,sensor_id,sensor_type,param)
+ else
+ sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>='%s' and time_stamp<date('%s','+1 day') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,day,day,sensor_id,sensor_type,param)
+ end
+ return run_sql(sql,con)
+end
+
+function get_filtered(day,con,sensor_id,sensor_type,param,width)
+ format = '%Y-%m-%dT%H:%M:%S'
+ if day == "-" then
+ sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>=datetime('now','-1 day','localtime') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,sensor_id,sensor_type,param)
+ else
+ sql = string.format("SELECT strftime('%s',time_stamp) as t,value as y FROM log WHERE time_stamp>='%s' and time_stamp<date('%s','+1 day') and sensor_id='%s' and sensor='%s' and param='%s' ORDER BY time_stamp",format,day,day,sensor_id,sensor_type,param)
+ end
+ return filter_data(run_sql(sql,con),width)
+end
+
+function dump_json(dataset,file)
+ local f
+ if file then
+ f = io.open(file,"w")
+ io.output(f)
+ end
+ io.write(json.encode(dataset))
+ if f then
+ io.close(f)
+ end
+end
+
+function dump_txt(dataset,file)
+ local f
+ if file then
+ f = io.open(file,"w")
+ io.output(f)
+ end
+ for key,row in pairs(dataset) do
+ io.write(row["t"].." "..row["y"].."\n")
+ end
+ if f then
+ io.close(f)
+ end
+end
+
+function dump_list(dataset,file)
+ local f
+ if file then
+ f = io.open(file,"w")
+ io.output(f)
+ end
+ for key,row in pairs(dataset) do
+ io.write(row["sensor_id"].." "..row["sensor"].." "..row["param"].."\n")
+ end
+ if f then
+ io.close(f)
+ end
+end
+
+local command = arg[2]
+local day = arg[3]
+
+local dbdriver = require "luasql.sqlite3"
+env = assert(dbdriver.sqlite3())
+con = assert(env:connect(logdb))
+
+if command == "list" then
+
+ dump_list(get_list(day,con))
+
+elseif command == "get" then
+
+ sensor_id = arg[4]
+ sensor_type = arg[5]
+ param = arg[6]
+
+ dump_txt(get_raw(day,con,sensor_id,sensor_type,param))
+
+elseif command == "get-filtered" then
+
+ sensor_id = arg[4]
+ sensor_type = arg[5]
+ param = arg[6]
+
+ width = arg[7]
+ if not width then
+ width = 5
+ end
+
+ dataset =get_filtered(day,con,sensor_id,sensor_type,param)
+ dump_txt(dataset)
+
+elseif command == "get-compacted" then
+
+ sensor_id = arg[4]
+ sensor_type = arg[5]
+ param = arg[6]
+
+ width = arg[7]
+ if not width then
+ width = 5
+ end
+
+ dataset =get_filtered(day,con,sensor_id,sensor_type,param)
+ dump_txt(average_results(dataset))
+
+elseif command == "dump" then
+
+ local directory = arg[4]
+ if not directory then
+ directory = "."
+ end
+
+ list = get_list(day,con)
+ dump_json(list,directory.."/sensors.json")
+ for key,value in pairs(list) do
+ dump_json(get_filtered(day,con,value["sensor_id"],value["sensor"],value["param"]),directory.."/"..value["sensor_id"].."."..value["sensor"].."."..value["param"]..".json")
+ end
+
+elseif command == "dump-compacted" then
+
+ local directory = arg[4]
+ if not directory then
+ directory = "."
+ end
+
+ list = get_list(day,con)
+ dump_json(list,directory.."/sensors.json")
+ for key,value in pairs(list) do
+ dump_json(average_results(get_filtered(day,con,value["sensor_id"],value["sensor"],value["param"])),directory.."/"..value["sensor_id"].."."..value["sensor"].."."..value["param"]..".json")
+ end
+
+end
--- /dev/null
+# Put your custom commands here that should be executed once
+# the system init finished. By default this file does nothing.
+
+I2C_BUS=0
+DS3221=68
+
+echo heartbeat > /sys/class/leds/linkit-smart-7688:orange\:wifi/trigger
+
+if [ ! -e /dev/lcdi2c ]; then
+ insmod lcdi2c busno=0 address=0x27 cursor=0 blink=0 topo=1
+fi
+
+echo 1 > /sys/class/alphalcd/lcdi2c/clear
+echo \00\00 > /sys/class/alphalcd/lcdi2c/position
+
+if [ ! -e /dev/rtc0 ]; then
+
+ modprobe rtc-ds1307
+ echo ds1307 0x$DS3221 > /sys/class/i2c-dev/i2c-$I2C_BUS/device/new_device
+ sleep 1
+ if [ -e /dev/rtc0 ]; then
+ ln -sf /dev/rtc0 /dev/rtc
+ logger -t hardware RTC initialized
+ fi
+
+fi
+
+if [ -e /dev/rtc0 ]; then
+ /sbin/hwclock -u --rtc=/dev/rtc0 --hctosys
+ echo "Hardware RTC OK" > /dev/lcdi2c
+else
+ echo "Hardware RTC FAIL" > /dev/lcdi2c
+fi
--- /dev/null
+#!/usr/bin/lua
+
+local json = require("json")
+local socket = require("socket")
+
+local http = require("socket.http")
+
+require "wm_util"
+
+function getConfig(configname)
+
+ local command,f
+
+ local uci=require("uci")
+ local cur=uci.cursor()
+ local config
+ if configname then
+ config=configname
+ else
+ config="weathermon"
+ end
+
+ web_url = cur.get(config,"web","url")
+ web_user = cur.get(config,"web","user")
+ web_timeout = cur.get(config,"web","timeout")
+ web_pass = cur.get(config,"web","password")
+ web_devid = cur.get(config,"web","devid")
+
+ web_iface = cur.get(config,"web","iface")
+
+ if not web_timeout then
+ web_timeout = 10
+ end
+
+ if web_iface then
+
+ command = '/sbin/ifconfig '..web_iface..' | grep \'\\<inet\\>\' | sed -n \'1p\' | tr -s \' \' | cut -d \' \' -f3 | cut -d \':\' -f2'
+ f=io.popen(command,'r')
+ ip_addr=f:read()
+
+ end
+
+ if not web_devid then
+
+ if web_iface then
+ io.input("/sys/class/net/"..web_iface.."/address")
+ else
+ io.input("/sys/class/net/eth0/address")
+ end
+
+ local mac = io.read("*line")
+ mac = mac:gsub(":","")
+ mac = mac:upper()
+
+ web_devid = mac
+
+ end
+
+ logging = cur.get(config,"logging","enabled")
+ touch_file = cur.get(config,"logging","touch_file")
+
+ backlogdb = cur.get(config,"process","backlogdb")
+ logdb = cur.get(config,"process","logdb")
+
+ serial_port = cur.get(config,"serial","port")
+ serial_baud = cur.get(config,"serial","baud")
+
+ input_file = cur.get(config,"input","file")
+ input_exec = cur.get(config,"input","exec")
+ alarm_exec = cur.get(config,"alarm","exec")
+
+ if serial_port then
+
+ command = "stty -F "..serial_port.." "..serial_baud
+ capture(command)
+
+ end
+
+ mqtt_host = cur.get(config,"mqtt","host")
+ mqtt_port = cur.get(config,"mqtt","port")
+ mqtt_id = cur.get(config,"mqtt","id")
+ mqtt_topic = cur.get(config,"mqtt","topic")
+ mqtt_alarm_topic = cur.get(config,"mqtt","alarm_topic")
+
+ mqtt_user = cur.get(config,"mqtt","user")
+ mqtt_passwd = cur.get(config,"mqtt","password")
+
+ if mqtt_host and not mqtt_id then
+ mqtt_id="weather-"..web_devid
+ end
+
+ if mqtt_host and not mqtt_port then
+ mqtt_port = 1883
+ end
+
+ if mqtt_host and not mqtt_topic then
+ mqtt_topic = 'weathermon/{dev}/{type}/{id}/{param}'
+ end
+
+ if mqtt_host and not mqtt_alarm_topic then
+ mqtt_alarm_topic = 'alarm/{dev}/{type}/{id}'
+ end
+
+ dump_file = cur.get(config,"process","dump_file")
+
+end
+
+function printLog(str)
+ if logging=="on" then
+ capture("logger -t weathermon "..str)
+ print(str)
+ elseif logging=="syslog" then
+ capture("logger -t weathermon "..str)
+ elseif logging=="stdout" then
+ print(str)
+ end
+end
+
+function submitValue(type,id,param,val)
+
+ if web_url then
+
+ local url = web_url.."?stype="..url_encode(type).."&sid="..url_encode(id).."¶m="..url_encode(param).."&value="..url_encode(val)
+
+ if web_user then
+ url = url:gsub("//","//"..web_user..":"..web_pass.."@",1)
+ end
+
+ local result,code = http.request ({
+ url=url, create=function()
+ local req_sock = socket.tcp()
+ req_sock:settimeout(web_timeout)
+ return req_sock
+ end})
+
+ if code ~= 200 then
+ print("writing record to backlog...")
+ backlog_con:execute(string.format("INSERT INTO queue(time_stamp,sensor_id,sensor,param,value) VALUES (datetime('now','localtime'),'%s','%s','%s',%f)",id,type,param,val))
+ end
+
+ end
+
+ if logdb then
+ log_con:execute(string.format("INSERT INTO log(time_stamp,sensor_id,sensor,param,value) VALUES (datetime('now','localtime'),'%s','%s','%s',%f)",id,type,param,val))
+ end
+
+ if touch_file then
+ touch(touch_file)
+ end
+
+end
+
+function storeRecord(id,sensor,param,value)
+
+ if not records[id] then
+ records[id] = {}
+ end
+
+ records[id]["timestamp"] = os.date("%Y-%m-%dT%H:%M:%S")
+
+ if not records[id][sensor] then
+ records[id][sensor] = {}
+ end
+
+ records[id][sensor][param] = value
+
+end
+
+function processJson(str)
+
+ msg=json.decode(str)
+
+ sensor={}
+
+ for key,value in pairs(msg) do
+ if value then
+ if key=="model" or key=="device" then
+ sensor_type=value
+ elseif key=="id" then
+ sensor_id=value
+ elseif key=='time' then
+ sensor_time=value
+ else
+ sensor[key]=value
+ end
+ end
+ end
+
+ if not sensor_id then
+ sensor_id = web_devid
+ end
+
+ if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
+ if next(sensor)==nil then
+ sensor["command"]="alarm"
+ end
+ local record = {}
+ for k,v in pairs(sensor) do
+ storeRecord(sensor_id,sensor_type,k,v)
+ printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = \""..v.."\"")
+ submitValue(sensor_type,sensor_id,k,v)
+ if mqtt_client then
+ mqtt_path=string.gsub(mqtt_topic,"{(.-)}",
+ function (name)
+ if name=="dev" then
+ return mqtt_encode(web_devid)
+ elseif name=="type" then
+ return mqtt_encode(sensor_type)
+ elseif name=="id" then
+ return mqtt_encode(sensor_id)
+ elseif name=="param" then
+ return k
+ else
+ return '{'..name..'}'
+ end
+ end)
+ mqtt_client:connect(mqtt_host,mqtt_port)
+ mqtt_client:publish(mqtt_path,v)
+ mqtt_client:disconnect()
+ end
+ end
+ else
+ printLog("Cannot parse sensor input: "..str)
+ end
+
+end
+
+function processLine(str)
+
+ msg=split(line,':')
+ msg_type=msg[1] or ''
+ msg_body=msg[2] or ''
+ if msg_type=="STATUS" then
+ printLog("Status: "..msg_body)
+ elseif msg_type=="ERROR" then
+ printLog("Error: "..msg_body)
+ elseif msg_type=="SENSOR" then
+ printLog("SENSOR: "..msg_body)
+ sens = split(msg_body,",")
+ sensor = {}
+ idx = 1
+ sensor_type = nil
+ sensor_id = web_devid
+ for i,rec in ipairs(sens) do
+ recrd=split(rec,'=')
+ key=recrd[1] or ''
+ value=recrd[2] or ''
+ if value then
+ if key=="TYPE" then
+ sensor_type=value
+ elseif key=="ID" then
+ sensor_id=value
+ else
+ sensor[key]=value
+ end
+ end
+ end
+ if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
+ local record = {}
+ for k,v in pairs(sensor) do
+ storeRecord(sensor_id,sensor_type,k,v)
+ printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = "..v)
+ submitValue(sensor_type,sensor_id,k,v)
+ if mqtt_client then
+ mqtt_path=string.gsub(mqtt_topic,"{(.-)}",
+ function (name)
+ if name=="dev" then
+ return web_devid
+ elseif name=="type" then
+ return sensor_type
+ elseif name=="id" then
+ return sensor_id
+ elseif name=="param" then
+ return k
+ else
+ return '{'..name..'}'
+ end
+ end)
+ mqtt_client:connect(mqtt_host,mqtt_port)
+ mqtt_client:publish(mqtt_path,v)
+ mqtt_client:disconnect()
+ end
+ end
+ else
+ printLog("Cannot parse sensor input: "..msg_body)
+ end
+ elseif msg_type=="ALARM" then
+ printLog("ALARM: "..msg_body)
+ sens = split(msg_body,",")
+ sensor = {}
+ idx = 1
+ sensor_type = nil
+ sensor_id = web_devid
+ mqtt_param = {}
+ for i,rec in ipairs(sens) do
+ recrd=split(rec,'=')
+ key=recrd[1] or ''
+ value=recrd[2] or ''
+ if value then
+ if key=="TYPE" then
+ alarm_type=value
+ elseif key=="ID" then
+ alarm_id=value
+ end
+ end
+ end
+ if not (alarm_type==nil or alarm_id==nil or alarm_type=='' or alarm_id=='') then
+ if mqtt_client then
+ mqtt_path=string.gsub(mqtt_alarm_topic,"{(.-)}",
+ function (name)
+ if name=="dev" then
+ return web_devid
+ elseif name=="type" then
+ return sensor_type
+ elseif name=="id" then
+ return sensor_id
+ else
+ return '{'..name..'}'
+ end
+ end)
+ mqtt_client:connect(mqtt_host,mqtt_port)
+ mqtt_client:publish(mqtt_path,msg_body)
+ mqtt_client:disconnect()
+ end
+ if alarm_exec then
+ command=alarm_exec..
+ " \""..string.gsub(alarm_type,"\"","\\\"")..
+ "\" \""..string.gsub(alarm_id,"\"","\\\"")..
+ "\" \""..string.gsub(msg_body,"\"","\\\"").."\""
+ capture(command)
+ end
+ else
+ printLog("Cannot parse alarm input: "..msg_body)
+ end
+ end
+
+end
+
+getConfig(arg[1])
+
+if backlogdb or logdb then
+ local dbdriver = require "luasql.sqlite3"
+ env = assert(dbdriver.sqlite3())
+end
+
+if backlogdb then
+ if not file_exists(backlogdb) then
+ touch(backlogdb)
+ backlog_con = assert(env:connect(backlogdb))
+ backlog_con:execute("CREATE TABLE queue(time_stamp datetime,sensor_id varchar(16),sensor varchar(16),param varchar(16),value float)")
+ else
+ backlog_con = assert(env:connect(backlogdb))
+ end
+end
+
+if logdb then
+ if not file_exists(logdb) then
+ touch(logdb)
+ log_con = assert(env:connect(logdb))
+ log_con:execute("CREATE TABLE log(time_stamp datetime,sensor_id varchar(16),sensor varchar(16),param varchar(16),value float)")
+ log_con:execute("CREATE INDEX log_idx ON log(sensor_id,sensor,param,time_stamp)")
+ else
+ log_con = assert(env:connect(logdb))
+ end
+end
+
+if mqtt_host then
+ MQTT = require "mosquitto"
+ mqtt_client = MQTT.new(mqtt_id)
+ if mqtt_user then
+ mqtt_client:login_set(mqtt_user, mqtt_passwd)
+ end
+end
+
+if serial_port then
+ serialin=io.open(serial_port,"r")
+elseif input_file == "-" then
+ serialin=io.stdin;
+elseif input_file then
+ serialin=io.open(input_file,"r")
+elseif input_exec then
+ serialin=io.popen(input_exec,"r")
+else
+ printLog("No input selected")
+ return
+end
+
+records = {}
+
+while 1 do
+ line=serialin:read("*l")
+ if line == nil then
+ break
+ end
+ printLog("Received: "..line);
+ if startswith(line,'{') then
+ processJson(line)
+ else
+ processLine(line)
+ end
+
+ if dump_file then
+ local f = io.open(dump_file,"w")
+ io.output(f)
+ io.write(json.encode(records))
+ io.close(f)
+ end
+end
--- /dev/null
+#!/usr/bin/lua
+
+require "uci"
+cur = uci.cursor()
+
+lfs = require "lfs"
+json = require "json"
+socket = require "socket"
+
+require "wm_util"
+
+function get_device_list(config_name)
+
+ local devices
+ devices = {}
+
+ cur.foreach(config_name, "device", function(s)
+ devices[#devices+1] = s[".name"]
+ end)
+
+ return devices
+
+end
+
+function get_device(config_name,device_name)
+
+ local device
+ device = {}
+
+ cur.foreach(config_name, "device", function(s)
+ if s[".name"] == device_name then
+ device = s
+ end
+ end)
+
+ return device
+
+end
+
+function find_device(name,subsystem)
+
+ local search_base
+
+ if subsystem == "iio" then
+ search_base = "/sys/bus/iio/devices"
+ for file in lfs.dir(search_base) do
+ if get_file_content(search_base.."/"..file.."/name") == name then
+ return search_base.."/"..file
+ end
+ end
+ elseif subsystem == "hwmon" then
+ search_base = "/sys/class/hwmon"
+ for file in lfs.dir(search_base) do
+ if get_file_content(search_base.."/"..file.."/device/name") == name then
+ return search_base.."/"..file.."/device"
+ end
+ end
+ end
+
+ return nil
+
+end
+
+function init_device(device,parameters,i2c_bus)
+
+ if not i2c_bus then
+ i2c_bus = 0
+ end
+
+ if device["module"] then
+ os.execute("modprobe "..device["module"])
+ end
+
+ if device["type"] then
+
+ devtype = split(device["type"],":")
+ bus = devtype[1]
+ subsystem = devtype[2]
+
+ if (bus == "i2c") and device["address"] and device["name"] then
+ pcall(function ()
+ local f = io.open("/sys/class/i2c-dev/i2c-"..i2c_bus.."/device/new_device","w")
+ io.output(f)
+ io.write(device["name"].." "..device["address"])
+ io.close(f)
+ end)
+ end
+
+ device_path=find_device(device["name"],subsystem)
+
+ if device_path and device["set_param"] then
+ for key,record in pairs(device["set_param"]) do
+ setparam = split(record,":")
+ setpath = device_path.."/"..setparam[1]
+ pcall(function ()
+ local f = io.open(setpath,"w")
+ io.output(f)
+ io.write(setparam[2])
+ io.close(f)
+ end)
+ end
+ end
+
+ if device_path and device["parameter"] then
+
+ for key,record in pairs(device["parameter"]) do
+
+ getparam = split(record,":")
+ getparameter = {}
+ getparameter["path"] = device_path.."/"..getparam[1]
+ getparameter["name"] = getparam[2]
+ getscale = getparam[3]
+ getcorrection = getparam[4]
+ if not getscale then
+ getscale = 1
+ end
+ if not getcorrection then
+ getcorrection = 0
+ end
+ getparameter["scale"] = tonumber(getscale)
+ getparameter["sensor"] = device["name"]:upper()
+ getparameter["correction"] = tonumber(getcorrection)
+
+ parameters[#parameters+1] = getparameter
+
+ end
+
+ end
+
+ end
+
+end
+
+function init(config_name)
+
+ local parameters= {}
+
+ i2c_bus = uci.get(config_name,"hardware","i2c_bus")
+
+ local devices = get_device_list(config_name)
+
+ for key,devname in pairs(devices) do
+
+ device = get_device(config_name,devname)
+
+ if device then
+ init_device(device,parameters,i2c_bus)
+ end
+
+ end
+
+ return parameters
+
+end
+
+function get_parameter(parameter)
+ return tonumber(get_file_content(parameter["path"])) * parameter["scale"] + parameter["correction"]
+end
+
+function get_parameters(parameters)
+ local results = {}
+ for key,record in pairs(parameters) do
+ if not results[record["sensor"]] then
+ results[record["sensor"]] = {}
+ end
+ results[record["sensor"]][record["name"]] = get_parameter(record)
+ end
+ return results
+end
+
+config_name = arg[1]
+if not config_name then
+ config_name = "weathermon"
+end
+
+web_id = uci.get(config_name,"web","devid")
+
+parameters = init(config_name)
+
+local delay = uci.get(config_name,"process","delay")
+
+local working_dir = uci.get(config_name,"process","working_dir")
+if working_dir then
+ lfs.mkdir(working_dir)
+end
+
+if not delay then
+ delay = 60
+end
+
+while true do
+ values = get_parameters(parameters)
+ records = {}
+ records[web_id] = {}
+ for key,record in pairs(values) do
+ records[web_id][key] = record
+ records[web_id]["timestamp"] = os.date("%Y-%m-%dT%H:%M:%S")
+ end
+ for key,value in pairs(values) do
+ value["device"] = key
+ print(json.encode(value))
+ end
+ socket.sleep(delay)
+end
+
\ No newline at end of file
--- /dev/null
+config internal 'web'
+ option url http://host-to-send/meteo/send.php
+ option user meteo
+ option password some-strong-pwd
+ option iface eth0
+ option timeout 5
+ option devid "9C65F920F34E" # mac-addr usually
+
+config internal 'input'
+ option exec "/usr/bin/stdbuf -o0 /usr/bin/weathermon-iio"
+
+# config internal 'serial'
+# option port /dev/ttyATH0
+# option timeout 100
+# option baud 57600
+
+config internal 'logging'
+ option enabled stdout
+# option touch_file /var/run/weathermon/weathermon.last
+
+config internal 'mqtt'
+ option host mqtt-host-to-snd
+ option user mqtt-user
+ option password some-strong-pwd
+
+config internal 'alarm'
+# option exec /usr/local/bin/alarm_received
+
+config internal 'hardware'
+ option i2c_bus 0
+
+config internal 'process'
+ option delay 48
+ option working_dir "/var/weather/"
+ option dump_file "/var/weather/weather.state"
+ option logdb "/var/weather/weather.db"
+ option backlogdb "/var/weather/backlog.db"
+ option engage "/sys/class/leds/some-led"
+ option engage_mode "default-on"
+ option disengage_mode "none"
+ option mute_file "/var/weather/mute" # for mute button
+ option mute_time 900
+ option backup_dir "/srv/backup"
+ option archive_dir "/srv/history"
+
+config device "bme280"
+ option module "bmp280_i2c"
+ option address "0x76"
+ option type "i2c:iio"
+ option name "bme280"
+ list set_param "in_humidityrelative_oversampling_ratio:4"
+ list set_param "in_temp_oversampling_ratio:8"
+ list set_param "in_pressure_oversampling_ratio:8"
+ list parameter "in_temp_input:TEMPERATURE:0.001:-4"
+ list parameter "in_pressure_input:PRESSURE:10"
+ list parameter "in_humidityrelative_input:HUMIDITY:0.001"
+
+config device "ads1115"
+ option module "ads1015"
+ option address "0x48"
+ option type "i2c:hwmon"
+ option name "ads1115"
+ list parameter "in4_input:CO"
+ list parameter "in5_input:CH4"
+ list parameter "in6_input:AIR"
+
+config alarm "green"
+ option name "OK"
+ list engage "/sys/class/leds/led:green/trigger"
+ option engage_mode "heartbeat"
+ option disengage_mode "none"
+
+config alarm "yellow"
+ option name "Warning"
+ list engage "/sys/class/leds/led:yellow/trigger"
+ option engage_mode "heartbeat"
+ option disengage_mode "none"
+
+config alarm "red"
+ option name "High"
+ list engage "/sys/class/leds/led:red/trigger"
+ option engage_mode "heartbeat"
+ option disengage_mode "none"
+
+config alarm "beep"
+ option name "ALARM!"
+ list engage "/sys/class/leds/led:red/trigger"
+ list engage "/sys/class/leds/led:beep/trigger"
+ option engage_mode "heartbeat"
+ option disengage_mode "none"
+
+config internal "display"
+ option timestr " %d.%m.%Y %H:%M "
+ option formatstr " {BME280.TEMPERATURE}~C {BME280.HUMIDITY}% {BME280.PRESSURE}mm CO:{ADS1115.CO} CH:{ADS1115.CH4} AI:{ADS1115.AIR}"
+ option file "/sys/class/alphalcd/lcdi2c/data"
+
+config params
+ option param "BME280.TEMPERATURE"
+ option name "Temp."
+ option format "4.1f"
+ option scale 1
+ list limits "green:12:27"
+ list limits "yellow:8:12"
+ list limits "yellow:27:35"
+ list limits "red:-50:8"
+ list limits "red:35:50"
+
+config params
+ option param "BME280.HUMIDITY"
+ option name "Hum."
+ option format "2.0f"
+ option scale 1
+ list limits "green:20:60"
+ list limits "yellow:10:20"
+ list limits "yellow:60:80"
+ list limits "red:0:20"
+ list limits "red:80:100"
+
+config params
+ option param "BME280.PRESSURE"
+ option name "Press."
+ option format "3.0f"
+ option scale 0.75
+ list limits "green:970:1030"
+ list limits "yellow:950:970"
+ list limits "yellow:1030:1050"
+ list limits "red:800:950"
+ list limits "red:1050:1100"
+
+config params
+ option param "ADS1115.CO"
+ option name "CO"
+ option format "3.0f"
+ option scale 0.1
+ list limits "green:0:1400"
+ list limits "yellow:1400:1800"
+ list limits "red:1800:2000"
+ list limits "beep:2000:10000"
+
+config params
+ option param "ADS1115.CH4"
+ option name "Metane"
+ option format "3.0f"
+ option scale 0.1
+ list limits "green:0:800"
+ list limits "yellow:800:1000"
+ list limits "red:1000:1200"
+ list limits "beep:1200:10000"
+
+config params
+ option param "ADS1115.AIR"
+ option name "Air"
+ option format "3.0f"
+ option scale 0.1
+ list limits "green:0:1100"
+ list limits "yellow:1100:1500"
+ list limits "red:1500:2000"
+ list limits "beep:2000:10000"
+++ /dev/null
-#!/usr/bin/python
-
-import MySQLdb
-import ConfigParser
-import sys
-
-from pprint import pprint
-import datetime
-
-import numpy as np
-
-import scipy.signal
-
-global database
-
-def GetTables():
- if database:
- c = database.cursor()
- c.execute("SELECT s.id sid,p.id pid FROM sensors s,st_parameters p where s.st_id=p.st_id and p.id>=0")
- return c.fetchall()
- else:
- print "No connection to DB"
- exit()
-
-def Today():
- dt = datetime.datetime.now()
- d_truncated = datetime.date(dt.year, dt.month, dt.day)
- return d_truncated
-
-def Tomorrow():
- dt = Today()
- return dt + datetime.timedelta(days=1)
-
-def Yesterday():
- dt = Today()
- return dt - datetime.timedelta(days=1)
-
-def Prehistoric():
- dt = datetime.date(2000,01,01)
- return dt
-
-def GetData(sid,pid,fromDate=Yesterday(),toDate=Today()):
- if database:
- c = database.cursor()
- c.execute("SELECT id,timestamp,value FROM sensor_values WHERE sensor_id=%s and parameter_id=%s and timestamp>=%s AND timestamp<%s",[sid,pid,fromDate.strftime('%Y-%m-%d %H:%M:%S'),toDate.strftime('%Y-%m-%d %H:%M:%S')])
- return c.fetchall()
- else:
- print "No connection to DB"
- exit()
-
-def FixRecord(id,value):
- if database:
- c = database.cursor()
- command="UPDATE sensor_values SET value={} WHERE id='{}'".format(value,id)
- c.execute(command)
- else:
- print "No connection to DB"
- exit()
-
-def ProcessTable(sid,pid):
-
- if process_all:
- data=GetData(sid,pid,Prehistoric(),Today())
- elif not current:
- data=GetData(sid,pid)
- else:
- data=GetData(sid,pid,Today(),Tomorrow())
-
- if not data:
- return
-
- sID=[]
- sTime=[]
- sValue=[]
- for rec in data:
- sID.append(rec[0])
- sTime.append(rec[1])
- sValue.append(rec[2])
- sValue=np.array(sValue)
-
- sValueFilt=scipy.signal.medfilt(sValue,5)
-
- sValueDiff=abs(sValue-sValueFilt)
-
- avg=np.mean(sValueDiff)
-
- for i in range(0,len(sTime)-1):
- if sValueDiff[i]>avg*filterThreshold:
- print "fixing %s : %5.2f %5.2f %5.2f" % (sTime[i],sValue[i],sValueFilt[i],sValueDiff[i])
- FixRecord(sID[i],sValueFilt[i])
-
- database.commit()
-
-if len(sys.argv)==2 and sys.argv[1]=='current':
- current=True
-else:
- current=False
-
-if len(sys.argv)==2 and sys.argv[1]=='all':
- process_all=True
-else:
- process_all=False
-
-try:
-
- cfg = ConfigParser.RawConfigParser(allow_no_value=True)
- cfg.readfp(open('/etc/weathermon.conf'))
- dbhost = cfg.get("mysql","host")
- dbuser = cfg.get("mysql","user")
- dbpasswd = cfg.get("mysql","passwd")
- dbdb = cfg.get("mysql","db")
-
- filterWindow = int(cfg.get("filter","window"))
- filterThreshold = float(cfg.get("filter","threshold"))
-
-except:
-
- print "Error reading configuration file"
- exit()
-
-try:
-
- database = MySQLdb.connect(host=dbhost,user=dbuser,passwd=dbpasswd,db=dbdb,use_unicode=True)
- database.set_character_set('utf8')
- c = database.cursor()
- c.execute('SET NAMES utf8;')
-
- print "Connected..."
-
-except:
-
- print "Error connecting database"
- exit()
-
-tables = GetTables()
-
-for sid,pid in tables:
-
- print "Processing sensor %d, parameter %d " % (sid,pid)
-
- ProcessTable(sid,pid)
-
-print "Processed "
-
\ No newline at end of file
--- /dev/null
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2007-2014 OpenWrt.org
+
+START=99
+
+USE_PROCD=1
+
+PROG=/usr/bin/weather-display
+NICEPRIO=3
+
+start_service() {
+
+ sleep 2
+ echo 1 > /sys/class/alphalcd/lcdi2c/clear
+ printf '\x00\x00' > /sys/class/alphalcd/lcdi2c/position
+
+ procd_open_instance
+ procd_set_param command "$PROG"
+ procd_set_param nice "$NICEPRIO"
+ procd_set_param respawn ${respawn_threshold:-600} ${respawn_timeout:-5} ${respawn_retry:-5}
+ procd_close_instance
+}
+
+reload_service()
+{
+ stop
+ start
+}
--- /dev/null
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2007-2014 OpenWrt.org
+
+START=45
+
+boot() {
+
+ /usr/bin/weather-init
+
+}
--- /dev/null
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2007-2014 OpenWrt.org
+
+START=93
+STOP=45
+
+USE_PROCD=1
+
+PROG=/usr/bin/weathermon
+NICEPRIO=-1
+
+BACKUP_DIR=`uci get weathermon.process.backup_dir`
+BACKLOGDB=`uci get weathermon.process.backlogdb`
+LOGDB=`uci get weathermon.process.logdb`
+WORK_DIR=`uci get weathermon.process.working_dir`
+
+start_service() {
+
+ mkdir -p "$WORK_DIR"
+
+ if [ ! -z "$BACKLOGDB" ] && [ ! -f "$BACKLOGDB" ]; then
+ BACKLOG_BASE=$(basename "$BACKLOGDB")
+ cp "$BACKUP_DIR/$BACKLOG_BASE" "$BACKLOGDB"
+ fi
+
+ if [ ! -z "$LOGDB" ] && [ ! -f "$LOGDB" ]; then
+ LOG_BASE=$(basename "$LOGDB")
+ cp "$BACKUP_DIR/$LOG_BASE" "$LOGDB"
+ fi
+
+ procd_open_instance
+ procd_set_param command "$PROG"
+ procd_set_param nice "$NICEPRIO"
+ procd_set_param respawn ${respawn_threshold:-600} ${respawn_timeout:-5} ${respawn_retry:-5}
+ procd_close_instance
+}
+
+stop_service() {
+ killall weathermon
+ killall weathermon-iio
+ if [ ! -z "$BACKLOGDB" ] && [ -f "$BACKLOGDB" ]; then
+ cp "$BACKLOGDB" "$BACKUP_DIR"/
+ fi
+ if [ ! -z "$LOGDB" ] && [ -f "$BACKLOGDB" ]; then
+ cp "$LOGDB" "$BACKUP_DIR"/
+ fi
+}
--- /dev/null
+#!/usr/bin/lua
+
+local socket = require "socket"
+
+function startswith(String,Start)
+ if String then
+ return string.sub(String,1,string.len(Start))==Start
+ else
+ return False
+ end
+end
+
+function trim(s)
+ return (s:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+function url_decode(str)
+ if not str then return nil end
+ str = string.gsub (str, "+", " ")
+ str = string.gsub (str, "%%(%x%x)", function(h) return
+ string.char(tonumber(h,16)) end)
+ str = string.gsub (str, "\r\n", "\n")
+ return str
+end
+
+function url_encode(str)
+ if (str) then
+ str = string.gsub (str, "\n", "\r\n")
+ str = string.gsub (str, "([^%w %-%_%.%~])",
+ function (c) return string.format ("%%%02X", string.byte(c)) end)
+ str = string.gsub (str, " ", "+")
+ end
+ return str
+end
+
+function capture(cmd, raw)
+ local f = assert(io.popen(cmd, 'r'))
+ local s = assert(f:read('*a'))
+ f:close()
+ if raw then return s end
+ s = string.gsub(s, '^%s+', '')
+ s = string.gsub(s, '%s+$', '')
+ s = string.gsub(s, '[\n\r]+', ' ')
+ return s
+end
+
+function mqtt_encode(str)
+ if (str) then
+ str = string.gsub (str, "\n", "")
+ str = string.gsub (str, "/", "-")
+ end
+ return str
+end
+
+function touch(name)
+ local file = io.open(name, 'a')
+ file:close()
+end
+
+function write_file(name,data)
+ pcall(function ()
+ local f = io.open(name,"w")
+ io.output(f)
+ io.write(data)
+ io.close(f)
+ end)
+end
+
+function file_exists(name)
+ local f=io.open(name,"r")
+ if f~=nil then io.close(f) return true else return false end
+end
+
+function get_file_content(name)
+ local f = io.open(name,"r")
+ if f ~= nil then
+ local content = trim(f:read("*all"))
+ io.close(f)
+ return content
+ else
+ return false
+ end
+end
+
+function sleep(sec)
+ socket.sleep(sec)
+end
+
+function split(s, delimiter)
+ local result = {};
+ for match in (s..delimiter):gmatch("(.-)"..delimiter) do
+ result[#result+1] = match
+ end
+ return result;
+end
+
+function list_dir(name)
+ local lfs = require "lfs"
+ local result = {}
+ for name in lfs.dir(name) do
+ if not startswith(name,".") then
+ result[#result+1] = name
+ end
+ end
+ return result
+end
--- /dev/null
+#!/usr/bin/lua
+
+require "uci"
+
+cursor = uci.cursor()
+
+archive_dir = uci.get("weathermon","process","archive_dir")
+
+require "wm_util"
+
+command = url_decode(os.getenv('QUERY_STRING'))
+
+print("Content-Type: text/plain\r\n")
+
+if command == "state" or command == "" or not command then
+ print(get_file_content(uci.get("weathermon","process","dump_file")))
+
+elseif command == "props" then
+ print(get_file_content("/www/meteo/properties.json"))
+
+elseif command == "years" then
+ json = require "json"
+ local result = list_dir(archive_dir)
+ table.sort(result, function(a, b) return a > b; end)
+ print(json.encode(result))
+
+elseif startswith(command,"months/") then
+ json = require "json"
+ local year = string.match(command,"months/(%d+)")
+ local result = list_dir(archive_dir.."/"..year)
+ table.sort(result, function(a, b) return a > b; end)
+ print(json.encode(result))
+
+elseif startswith(command,"days/") then
+ json = require "json"
+ local year,month = string.match(command,"days/(%d+)/(%d+)")
+ local result = list_dir(archive_dir.."/"..year.."/"..month)
+ table.sort(result, function(a, b) return a > b; end)
+ print(json.encode(result))
+
+elseif startswith(command,"sensors/") then
+ json = require "json"
+ local year,month,day = string.match(command,"sensors/(%d+)/(%d+)/(%d+)")
+ local files = list_dir(archive_dir.."/"..year.."/"..month.."/"..day)
+ result = {}
+ for key,value in pairs(files) do
+ local id, sensor, param = string.match(value,"(%w+)%.(%w+)%.(%w+).json")
+ if id then
+ result[#result+1] = id.."."..sensor.."."..param
+ end
+ end
+ table.sort(result)
+ print(json.encode(result))
+
+elseif startswith(command,"list/") then
+
+ sensor = string.sub(command,string.len("list/")+1)
+ lfs = require "lfs"
+ json = require "json"
+ dir = uci.get("weathermon","process","working_dir").."/www"
+
+ params = {}
+
+ for name in lfs.dir(dir) do
+ param=name:match(devid.."\."..sensor.."\.(%w+)\.json")
+ if param then
+ params[#params+1] = param
+ end
+ end
+
+ print(json.encode(params))
+
+elseif startswith(command,"get/") then
+
+ sensor_path=string.sub(command,string.len("get/")+1)
+ devid,sensor,param = sensor_path:match("(%w+)/(%w+)/(.+)")
+
+ if param=="*" then
+
+ lfs = require "lfs"
+ dir = uci.get("weathermon","process","working_dir").."/www"
+
+ result = ""
+
+ for name in lfs.dir(dir) do
+ param=name:match(devid.."\."..sensor.."\.(%w+)\.json")
+ if param then
+ content = "\""..param.."\":"..get_file_content(dir.."/"..name)
+ if result=="" then
+ result = content
+ else
+ result = result..","..content
+ end
+ end
+ end
+
+ print("{"..result.."}")
+
+ else
+ print(get_file_content(uci.get("weathermon","process","working_dir").."/www/"..devid.."."..sensor.."."..param..".json"))
+ end
+
+elseif startswith(command,"get-archive/") then
+
+ local sensor_path=string.sub(command,string.len("get-archive/")+1)
+ local year,month,day,devid,sensor,param = sensor_path:match("(%d+)/(%d+)/(%d+)/(%w+)/(%w+)/(%w+)")
+
+ print(get_file_content(archive_dir.."/"..year.."/"..month.."/"..day.."/"..devid.."."..sensor.."."..param..".json"))
+
+end
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta http-equiv="Cache-Control" content="no-cache" />
+ <link rel="shortcut icon" href="/luci-static/material/favicon.ico">
+ <link rel="stylesheet" href="/meteo/meteo.css" type="text/css" />
+ <script type="text/javascript" src="/js/Chart.bundle.min.js"></script>
+ <script type="text/javascript" src="/meteo/archive.js"></script>
+</head>
+<body>
+
+<header>
+ <div class="fill">
+ <div class="container">
+ <a class="brand" href="/"><img width="48" src="/meteo/weather.svg"/></a>
+ <a class="brand" href="/meteo">Метеостанция</a>
+ <a class="brand" id="current" href="/meteo">Последние сутки</a>
+ </div>
+ </div>
+</header>
+
+<div class="main">
+ <div id="maincontent">
+ <div class="container">
+ <div class="wide-section">
+ <div class="selector-header">Год</div>
+ <div class="selector"><select id="year" onchange="selectChange();"></select></div>
+ <div class="selector-header">Месяц</div>
+ <div class="selector"><select id="month" onchange="selectChange();"></select></div>
+ <div class="selector-header">День</div>
+ <div class="selector"><select id="day" onchange="selectChange();"></select></div>
+ <div class="selector-header">Параметр</div>
+ <div class="selector"><select id="sensor" onchange="selectChange();"></select></div>
+ </div>
+ <div class="bottom-section" id="chartdiv">
+ <canvas id="chart">
+ </canvas>
+ </div>
+ </div>
+ </div>
+</div>
+
+<footer>Powered by OpenWRT</footer>
+
+</body>
+</html>
--- /dev/null
+urlbase="/meteo/cgi?";
+
+archive_base="/meteo/archive";
+current_base = "/meteo/graph";
+
+var url = window.location.pathname;
+
+if (url.startsWith(archive_base)) {
+ url = url.substr(archive_base.length);
+}
+
+var params = url.split('/');
+
+var year = params[1];
+var month = params[2];
+var day = params[3];
+var devid = params[4];
+var sensor = params[5];
+var param = params[6];
+
+var properties;
+var years;
+var months;
+var days;
+var sensors;
+
+function composeUrl() {
+ url = window.location.protocol+"//"+window.location.host+archive_base+"/";
+ if (year) {
+ url = url + year;
+ if (month) {
+ url = url + "/" + month;
+ if (day) {
+ url = url + "/" + day;
+ if (devid) {
+ url = url + "/" + devid;
+ if (sensor) {
+ url = url + "/" + sensor;
+ if (param) {
+ url = url + "/" + param;
+ }
+ }
+ }
+ }
+ }
+ }
+ history.pushState({}, null, url);
+}
+
+function selectChange() {
+ new_year = document.getElementById("year").value;
+ new_month = document.getElementById("month").value;
+ new_day = document.getElementById("day").value;
+ sensor_id = document.getElementById("sensor").value.split(".");
+ new_devid = sensor_id[0];
+ new_sensor = sensor_id[1];
+ new_param = sensor_id[2];
+ if (year != new_year) {
+ year = new_year;
+ GetMonths();
+ } else if (month != new_month) {
+ month = new_month;
+ GetDays();
+ } else if (day != new_day) {
+ day = new_day;
+ GetSensors();
+ } else if ((new_devid != devid) || (new_sensor != sensor) || (new_param != param)) {
+ devid = new_devid;
+ sensor = new_sensor;
+ param = new_param;
+ RefreshGraph();
+ }
+ composeUrl();
+}
+
+function processDataset(dataset,devid,sensorname,paramname) {
+ var scale = properties["scale"][devid+"."+sensorname+"."+paramname]
+ if (scale) {
+ var result=[];
+ for (idx in dataset) {
+ newRec = {};
+ newRec.t = dataset[idx].t
+ newRec.y = dataset[idx].y * scale[0];
+ result.push(newRec);
+ }
+ return result;
+ } else {
+ return dataset;
+ }
+}
+
+function drawGraph(graphData) {
+
+ document.getElementById("current").href = current_base+"/"+devid + "/" + sensor + "/" + param;
+
+ var div = document.getElementById("chartdiv");
+ var canvas = document.getElementById("chart");
+
+ canvas.width = div.style.width;
+ canvas.height = div.style.height;
+
+ var ctx = canvas.getContext('2d');
+ var color = Chart.helpers.color;
+
+ var cfg = {
+ type: 'bar',
+ data: {
+ datasets: [
+ {
+ label: properties["names"][devid+"."+sensor+"."+param],
+ backgroundColor: color(properties["colors"][devid+"."+sensor+"."+param]).alpha(0.5).rgbString(),
+ borderColor: properties["colors"][devid+"."+sensor+"."+param],
+ data: processDataset(graphData,devid,sensor,param),
+ type: 'line',
+ pointRadius: 0,
+ fill: true,
+ borderWidth: 2
+ }
+ ]
+ },
+ options: {
+ animation: {
+ duration: 0,
+ },
+ hover: {
+ animationDuration: 0,
+ },
+ responsiveAnimationDuration: 0,
+ legend: {
+ labels: {
+ fontColor: properties["fonts"]["legend"]["color"],
+ fontSize: properties["fonts"]["legend"]["size"],
+ fontStyle: properties["fonts"]["legend"]["style"],
+ }
+ },
+ scales: {
+ xAxes: [{
+ type: 'time',
+ distribution: 'series',
+ scaleLabel: {
+ fontColor: properties["fonts"]["axes"]["color"],
+ fontSize: properties["fonts"]["axes"]["size"],
+ fontStyle: properties["fonts"]["axes"]["style"],
+ }
+ }],
+ yAxes: [{
+ scaleLabel: {
+ display: true,
+ labelString: properties["names"][sensor+"."+param],
+ fontColor: properties["fonts"]["axes"]["color"],
+ fontSize: properties["fonts"]["axes"]["size"],
+ fontStyle: properties["fonts"]["axes"]["style"],
+ }
+ }]
+ }
+ }
+
+ }
+ var chart = new Chart(ctx, cfg);
+}
+
+function RefreshGraph() {
+
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(RefreshGraph,30000);
+ return;
+ }
+ var graphData = JSON.parse(this.responseText);
+ drawGraph(graphData);
+ };
+
+ req.open("GET", urlbase+"get-archive/"+year+"/"+month+"/"+day+"/"+devid+"/"+sensor+"/"+param, true);
+ req.send();
+
+}
+
+function GetProperties() {
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(GetProperties,30000);
+ return;
+ }
+ properties = JSON.parse(this.responseText);
+ GetYears();
+ };
+
+ req.open("GET", urlbase+"props", true);
+ req.send();
+
+}
+
+function fillSelector(id,data,value,nameFunc) {
+
+ var element = document.getElementById(id);
+ var html = "";
+ var line;
+
+ for (i in data) {
+ if (nameFunc) {
+ line = "<option value=\""+data[i]+"\">"+nameFunc(data[i])+"</option>"
+ } else {
+ line = "<option value=\""+data[i]+"\">"+data[i]+"</option>"
+ }
+ html = html + line;
+ }
+
+ element.innerHTML = html;
+ if (value) {
+ element.value = value;
+ }
+ return element.value;
+
+}
+
+function GetYears() {
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(GetYears,30000);
+ return;
+ }
+ years = JSON.parse(this.responseText);
+ year = fillSelector("year", years, year);
+ composeUrl();
+ if (year) {
+ GetMonths();
+ }
+ };
+
+ req.open("GET", urlbase+"years", true);
+ req.send();
+
+}
+
+function GetMonths() {
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(GetMonths,30000);
+ return;
+ }
+ months = JSON.parse(this.responseText);
+ month = fillSelector("month", months, month);
+ composeUrl();
+ if (month) {
+ GetDays();
+ }
+ };
+
+ req.open("GET", urlbase+"months/"+year, true);
+ req.send();
+
+}
+
+function GetDays() {
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(GetDays,30000);
+ return;
+ }
+ days = JSON.parse(this.responseText);
+ day = fillSelector("day", days, day);
+ composeUrl();
+ if (day) {
+ GetSensors();
+ }
+ };
+
+ req.open("GET", urlbase+"days/"+year+"/"+month, true);
+ req.send();
+
+}
+
+function SensorName(id) {
+ return properties["names"][id];
+}
+
+function GetSensors() {
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(GetSensors,30000);
+ return;
+ }
+ sensors = JSON.parse(this.responseText);
+ if (devid && sensor && param) {
+ sensor_id = devid+"."+sensor+"."+param;
+ } else {
+ sensor_id = null;
+ }
+ sensor_id = fillSelector("sensor", sensors, sensor_id, SensorName).split(".");
+ devid = sensor_id[0];
+ sensor = sensor_id[1];
+ param = sensor_id[2];
+ composeUrl();
+ if (sensor_id) {
+ RefreshGraph();
+ }
+ };
+
+ req.open("GET", urlbase+"sensors/"+year+"/"+month+"/"+day, true);
+ req.send();
+
+}
+
+setTimeout(GetProperties,100);
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta http-equiv="Cache-Control" content="no-cache" />
+ <link rel="shortcut icon" href="/luci-static/material/favicon.ico">
+ <link rel="stylesheet" href="/meteo/meteo.css" type="text/css" />
+ <script type="text/javascript" src="/js/Chart.bundle.min.js"></script>
+ <script type="text/javascript" src="/meteo/graph.js"></script>
+</head>
+<body>
+
+<header>
+ <div class="fill">
+ <div class="container">
+ <a class="brand" href="/"><img width="48" src="/meteo/weather.svg"/></a>
+ <a class="brand" href="/meteo">Метеостанция</a>
+ <a class="brand" id="archive" href="/meteo">Архивы</a>
+ </div>
+ </div>
+</header>
+
+<div class="main">
+ <div id="maincontent">
+ <div class="container">
+ <div class="large-section" id="chartdiv">
+ <canvas id="chart">
+ </canvas>
+ </div>
+ </div>
+ </div>
+</div>
+
+<footer>Powered by OpenWRT</footer>
+
+</body>
+</html>
--- /dev/null
+urlbase="/meteo/cgi?"
+archive_base="/meteo/archive";
+
+var params = window.location.pathname.split('/').slice(-4);
+var sensor_id = params[1];
+var sensor = params[2];
+var param = params[3];
+
+var sensor_path = sensor_id + "." + sensor + "." + param;
+
+var properties;
+
+function processDataset(dataset,sensorid,sensorname,paramname) {
+ var scale = properties["scale"][sensorid+"."+sensorname+"."+paramname]
+ if (scale) {
+ var result=[];
+ for (idx in dataset) {
+ newRec = {};
+ newRec.t = dataset[idx].t
+ newRec.y = dataset[idx].y * scale[0];
+ result.push(newRec);
+ }
+ return result;
+ } else {
+ return dataset;
+ }
+}
+
+function drawGraph(graphData) {
+
+ document.getElementById("archive").href = archive_base+"////"+sensor_id + "/" + sensor + "/" + param;
+
+ var div = document.getElementById("chartdiv");
+ var canvas = document.getElementById("chart");
+
+ canvas.width = div.style.width;
+ canvas.height = div.style.height;
+
+ var ctx = canvas.getContext('2d');
+ var color = Chart.helpers.color;
+
+ if (param=="*") {
+
+ var cfg = {
+ type: 'bar',
+ data: {
+ datasets: [
+ ]
+ },
+ options: {
+ animation: {
+ duration: 0,
+ },
+ hover: {
+ animationDuration: 0,
+ },
+ responsiveAnimationDuration: 0,
+ legend: {
+ labels: {
+ fontColor: properties["fonts"]["legend"]["color"],
+ fontSize: properties["fonts"]["legend"]["size"],
+ fontStyle: properties["fonts"]["legend"]["style"],
+ }
+ },
+ scales: {
+ xAxes: [{
+ type: 'time',
+ distribution: 'series',
+ scaleLabel: {
+ fontColor: properties["fonts"]["axes"]["color"],
+ fontSize: properties["fonts"]["axes"]["size"],
+ fontStyle: properties["fonts"]["axes"]["style"],
+ }
+ }],
+ yAxes: [{
+ scaleLabel: {
+ display: true,
+ fontColor: properties["fonts"]["axes"]["color"],
+ fontSize: properties["fonts"]["axes"]["size"],
+ fontStyle: properties["fonts"]["axes"]["style"],
+ }
+ }]
+ }
+ }
+ }
+
+ for (paramname in graphData) {
+
+ cfg.data.datasets.push(
+ {
+ label: properties["names"][sensor_id+"."+sensor+"."+paramname],
+ data: processDataset(graphData[paramname],sensor_id,sensor,paramname),
+ borderColor: properties["colors"][sensor_id+"."+sensor+"."+paramname],
+ type: 'line',
+ fill: false,
+ pointRadius: 0,
+ borderWidth: 2
+ }
+ )
+
+ }
+
+ } else {
+
+ var y_label = properties["names"][sensor_path];
+ if (properties["units"][sensor_path]) {
+ y_label = y_label + ", " + properties["units"][sensor_path];
+ }
+
+ var cfg = {
+ type: 'bar',
+ data: {
+ datasets: [
+ {
+ label: properties["names"][sensor_path],
+ backgroundColor: color(properties["colors"][sensor_path]).alpha(0.5).rgbString(),
+ borderColor: properties["colors"][sensor_path],
+ data: processDataset(graphData,sensor_id,sensor,param),
+ type: 'line',
+ pointRadius: 0,
+ fill: true,
+ borderWidth: 2
+ }
+ ]
+ },
+ options: {
+ animation: {
+ duration: 0,
+ },
+ hover: {
+ animationDuration: 0,
+ },
+ responsiveAnimationDuration: 0,
+ legend: {
+ labels: {
+ fontColor: properties["fonts"]["legend"]["color"],
+ fontSize: properties["fonts"]["legend"]["size"],
+ fontStyle: properties["fonts"]["legend"]["style"],
+ }
+ },
+ scales: {
+ xAxes: [{
+ type: 'time',
+ distribution: 'series',
+ scaleLabel: {
+ fontColor: properties["fonts"]["axes"]["color"],
+ fontSize: properties["fonts"]["axes"]["size"],
+ fontStyle: properties["fonts"]["axes"]["style"],
+ }
+ }],
+ yAxes: [{
+ scaleLabel: {
+ display: true,
+ labelString: y_label,
+ fontColor: properties["fonts"]["axes"]["color"],
+ fontSize: properties["fonts"]["axes"]["size"],
+ fontStyle: properties["fonts"]["axes"]["style"],
+ }
+ }]
+ }
+ }
+ }
+
+ }
+ var chart = new Chart(ctx, cfg);
+}
+
+function RefreshGraph() {
+
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(RefreshGraph,30000);
+ return;
+ }
+ var graphData = JSON.parse(this.responseText);
+ drawGraph(graphData);
+ setTimeout(RefreshGraph,30000)
+ };
+
+ req.open("GET", urlbase+"get/"+sensor_id+"/"+sensor+"/"+param, true);
+ req.send();
+
+}
+
+function GetProperties() {
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(GetProperties,30000);
+ return;
+ }
+ properties = JSON.parse(this.responseText);
+ setTimeout(RefreshGraph,100)
+ };
+
+ req.open("GET", urlbase+"props", true);
+ req.send();
+
+}
+
+setTimeout(GetProperties,100)
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta http-equiv="Cache-Control" content="no-cache" />
+ <link rel="shortcut icon" href="/luci-static/material/favicon.ico">
+ <link rel="stylesheet" href="/meteo/meteo.css" type="text/css" />
+ <script type="text/javascript" src="/meteo/meteo.js"></script>
+</head>
+<body>
+
+<header>
+ <div class="fill">
+ <div class="container">
+ <a class="brand" href="/"><img width="48" src="weather.svg"/></a>
+ <a class="brand" href="archive">Архивы метеонаблюдений</a>
+ </div>
+</header>
+
+
+<div style="display: none;" id="template">
+<a href="graph/$SENSOR_ID/$SENSOR/$PARAM">
+<div class="section" title="$TIMESTAMP">
+ <div class="reference-header" style="color: $COLOR;">$NAME</div>
+ <div class="reference" id="value">$VALUE</div>
+ <div class="reference-unit">$UNITS</div>
+</div>
+</a>
+</div>
+
+<div class="main">
+
+ <div id="maincontent">
+
+ <div class="container" id="meteo">
+
+ </div>
+
+ </div>
+
+</div>
+
+<footer>Powered by OpenWRT</footer>
+
+</body>
+</html>
--- /dev/null
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-size: 0.8rem;
+ background-color: #EEE;
+}
+
+html, body {
+ margin: 0px;
+ padding: 0px;
+ height: 100%;
+ font-family: Microsoft Yahei, WenQuanYi Micro Hei, sans-serif, "Helvetica Neue", Helvetica, Hiragino Sans GB;
+}
+
+header {
+ background: darkgreen;
+ color: white;
+}
+
+header, .main {
+ width: 100%;
+ position: absolute;
+}
+
+header {
+ height: 5rem;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, .26);
+ transition: box-shadow .2s;
+ float: left;
+ position: fixed;
+ top: 0px;
+ z-index: 2000;
+}
+
+header > .fill > .container {
+ padding-top: 0.25rem;
+ padding-right: 1rem;
+ padding-bottom: 0.25rem;
+ display: flex;
+ align-items: center;
+ height: 5rem;
+}
+
+header > .fill > .container > img{
+ max-height: 4.5rem;
+ margin: 1rem;
+ padding: 0.5rem;
+}
+
+header > .fill > .container > .brand {
+ font-size: 1.4rem;
+ color: white;
+ text-decoration: none;
+ margin-left: 1rem;
+}
+
+.main {
+ top: 4rem;
+ bottom: 0rem;
+ position: relative;
+ height: calc(100% - 5rem);
+}
+
+.main > #maincontent {
+ background-color: #EEE;
+ height: calc(100% - 5rem);
+}
+
+#maincontent > .container {
+ margin: 1rem 2rem 1rem 2rem;
+ padding-top: 1px;
+ height: 100%;
+}
+
+.section {
+ margin: 1rem 1rem 1rem 1rem;
+ padding: 2rem;
+ border: 0;
+ font-weight: normal;
+ font-style: normal;
+ line-height: 1;
+ font-family: inherit;
+ float: left;
+ min-width: inherit;
+ border-radius: 0;
+ color: #404040;
+ background-color: #FFF;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
+ -webkit-overflow-scrolling: touch;
+}
+
+.wide-section {
+ margin: 1rem 1rem 1rem 1rem;
+ padding: 2rem;
+ border: 0;
+ font-weight: normal;
+ font-style: normal;
+ line-height: 1;
+ font-family: inherit;
+ float: left;
+ min-width: calc(100% - 2rem);
+ border-radius: 0;
+ color: #404040;
+ background-color: #FFF;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
+ -webkit-overflow-scrolling: touch;
+}
+
+.large-section {
+ margin: 1rem 1rem 1rem 1rem;
+ padding: 2rem;
+ border: 0;
+ font-weight: normal;
+ font-style: normal;
+ line-height: 1;
+ font-family: inherit;
+ min-width: inherit;
+ border-radius: 0;
+ height: 100%;
+ background-color: #FFF;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
+ -webkit-overflow-scrolling: touch;
+}
+
+.bottom-section {
+ margin: 1rem 1rem 1rem 1rem;
+ padding: 2rem;
+ border: 0;
+ font-weight: normal;
+ font-style: normal;
+ line-height: 1;
+ font-family: inherit;
+ float: left;
+ min-width: calc(100% - 2rem);
+ border-radius: 0;
+ height: 80%;
+ background-color: #FFF;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
+ -webkit-overflow-scrolling: touch;
+}
+
+footer {
+ text-align: right;
+ padding: 1rem;
+ color: #aaa;
+ font-size: 0.8rem;
+ text-shadow: 0px 0px 2px #BBB;
+}
+
+footer > a {
+ color: #aaa;
+ text-decoration: none;
+}
+
+.reference {
+ padding: 1rem 1rem;
+ text-decoration: bold;
+ float: left;
+ font-size: 6rem;
+}
+
+.reference-unit {
+ padding: 0.5rem 1rem;
+ text-decoration: bold;
+ float: left;
+ font-size: 4rem;
+}
+
+.section:hover {
+ color: white;
+ background: darkgreen;
+}
+
+.reference-header {
+ padding: 0.5rem 0.2rem;
+ text-decoration: none;
+ font-size: 2rem;
+ text-align: left;
+}
+
+.selector-header {
+ padding: 1rem 1rem;
+ text-decoration: bold;
+ float: left;
+ font-size: 1.5rem;
+}
+
+.selector {
+ padding: 1rem 1rem;
+ text-decoration: bold;
+ float: left;
+}
+
+option {
+ font-size: 1.2rem;
+}
--- /dev/null
+urlbase="/meteo/cgi?"
+
+currentState=""
+
+function RefreshPageState() {
+
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+
+ if (this.readyState != 4) return;
+ if (this.status == 200) {
+
+ var returnedData = JSON.parse(this.responseText);
+
+ var template = document.getElementById("template").innerHTML;
+ var value_color = document.getElementById("value").style.color;
+
+ var html = "";
+
+ for (var sensor_id in returnedData) {
+ var timestamp = returnedData[sensor_id]["timestamp"];
+ for (var sensor in returnedData[sensor_id]) {
+ if (sensor != "timestamp") {
+ for (var param in returnedData[sensor_id][sensor]) {
+ sensor_path = sensor_id+"."+sensor+"."+param;
+ value = returnedData[sensor_id][sensor][param];
+ name = properties["names"][sensor_path];
+ if (! name) { name = sensor_path; }
+ units = properties["units"][sensor_path];
+ scale = properties["scale"][sensor_path];
+ color = properties["colors"][sensor_path];
+ if (scale) {
+ value = (scale[0] * value).toFixed(scale[1]);
+ }
+ if (! color) {
+ color = value_color;
+ }
+ var section = template.replace(/\$SENSOR_ID/g,sensor_id);
+ section = section.replace(/\$SENSOR/g,sensor);
+ section = section.replace(/\$PARAM/g,param);
+ section = section.replace(/\$NAME/g,name);
+ section = section.replace(/\$UNITS/g,units);
+ section = section.replace(/\$VALUE/g,value);
+ section = section.replace(/\$COLOR/g,color);
+ section = section.replace(/\$TIMESTAMP/g,timestamp);
+ html = html + section;
+ }
+ }
+ }
+ }
+
+ document.getElementById("meteo").innerHTML = html;
+
+ setTimeout(RefreshPageState,5000)
+ } else {
+ setTimeout(RefreshPageState,15000)
+ }
+ };
+
+ req.open("GET", urlbase+"state", true);
+ req.send();
+
+}
+
+function GetProperties() {
+ var req = new XMLHttpRequest();
+
+ req.onreadystatechange = function () {
+ if (this.readyState != 4) return;
+ if (this.status != 200) {
+ setTimeout(GetProperties,30000);
+ return;
+ }
+ properties = JSON.parse(this.responseText);
+ RefreshPageState();
+ };
+
+ req.open("GET", urlbase+"props", true);
+ req.send();
+
+}
+
+
+setTimeout(GetProperties,100)
--- /dev/null
+{
+ "names":{
+ "9C65F920F34E.ADS1115.CO":"Угарный газ",
+ "9C65F920F34E.ADS1115.CH4":"Горючие газы",
+ "9C65F920F34E.ADS1115.AIR":"Загрязнения воздуха",
+ "9C65F920F34E.BME280.TEMPERATURE":"Температура",
+ "9C65F920F34E.BME280.HUMIDITY":"Влажность",
+ "9C65F920F34E.BME280.PRESSURE":"Давление",
+ "9C65F920F34E.HMC5843.MX":"Магнитное поле (X)",
+ "9C65F920F34E.HMC5843.MY":"Магнитное поле (Y)",
+ "9C65F920F34E.HMC5843.MZ":"Магнитное поле (Z)"
+ },
+ "colors":{
+ "9C65F920F34E.ADS1115.CO":"black",
+ "9C65F920F34E.ADS1115.CH4":"darkgray",
+ "9C65F920F34E.ADS1115.AIR":"teal",
+ "9C65F920F34E.BME280.TEMPERATURE":"red",
+ "9C65F920F34E.BME280.HUMIDITY":"blue",
+ "9C65F920F34E.BME280.PRESSURE":"green",
+ "9C65F920F34E.HMC5843.MX":"orange",
+ "9C65F920F34E.HMC5843.MY":"cyan",
+ "9C65F920F34E.HMC5843.MZ":"magenta"
+ },
+ "units":{
+ "9C65F920F34E.ADS1115.CO":"Ед.",
+ "9C65F920F34E.ADS1115.CH4":"Ед.",
+ "9C65F920F34E.ADS1115.AIR":"Ед.",
+ "9C65F920F34E.BME280.TEMPERATURE":"°C",
+ "9C65F920F34E.BME280.HUMIDITY":"%",
+ "9C65F920F34E.BME280.PRESSURE":"мм",
+ "9C65F920F34E.HMC5843.MX":"Ед.",
+ "9C65F920F34E.HMC5843.MY":"Ед.",
+ "9C65F920F34E.HMC5843.MZ":"Ед."
+ },
+ "scale": {
+ "9C65F920F34E.BME280.PRESSURE": [ 0.75, 0 ],
+ "9C65F920F34E.BME280.TEMPERATURE": [ 1, 1 ],
+ "9C65F920F34E.BME280.HUMIDITY": [ 1, 0 ]
+ },
+ "fonts": {
+ "axes": { "color": "black", "size": 16, "style": "normal" },
+ "legend": { "color": "black", "size": 16, "style": "normal" }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\r
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"\r
+ viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">\r
+<style type="text/css">\r
+ .st0{fill:#4F5D73;}\r
+ .st1{opacity:0.2;}\r
+ .st2{fill:#231F20;}\r
+ .st3{fill:#E0995E;}\r
+ .st4{fill:#FFFFFF;}\r
+</style>\r
+<g id="Layer_1">\r
+ <g>\r
+ <circle class="st0" cx="32" cy="32" r="32"/>\r
+ </g>\r
+ <g class="st1">\r
+ <circle class="st2" cx="22" cy="24" r="10"/>\r
+ </g>\r
+ <g>\r
+ <circle class="st3" cx="22" cy="22" r="10"/>\r
+ </g>\r
+ <g class="st1">\r
+ <path class="st2" d="M48.7,36c0-7.7-6.6-14-14.7-14c-6.9,0-12.6,4.5-14.2,10.6c-4.4,0.6-7.8,4.3-7.8,8.6c0,4.8,4.1,8.8,9.2,8.8\r
+ h27.5c4.1,0,7.3-3.1,7.3-7S52.7,36,48.7,36z"/>\r
+ </g>\r
+ <g>\r
+ <g class="st1">\r
+ <path class="st2" d="M32,22c0-1-0.2-2-0.4-2.9c-6.2,0.6-11.3,4.9-12.8,10.5c-0.8,0.1-1.6,0.4-2.4,0.7C18,31.4,19.9,32,22,32\r
+ C27.5,32,32,27.5,32,22z"/>\r
+ </g>\r
+ </g>\r
+ <g>\r
+ <path class="st4" d="M48.7,34c0-7.7-6.6-14-14.7-14c-6.9,0-12.6,4.5-14.2,10.6c-4.4,0.6-7.8,4.3-7.8,8.6c0,4.8,4.1,8.8,9.2,8.8\r
+ h27.5c4.1,0,7.3-3.1,7.3-7S52.7,34,48.7,34z"/>\r
+ </g>\r
+</g>\r
+<g id="Layer_2">\r
+</g>\r
+</svg>\r
--- /dev/null
+#!/usr/bin/python
+
+import MySQLdb
+import ConfigParser
+import sys
+
+from pprint import pprint
+import datetime
+
+import numpy as np
+
+import scipy.signal
+
+global database
+
+def GetTables():
+ if database:
+ c = database.cursor()
+ c.execute("SELECT s.id sid,p.id pid FROM sensors s,st_parameters p where s.st_id=p.st_id and p.id>=0")
+ return c.fetchall()
+ else:
+ print "No connection to DB"
+ exit()
+
+def Today():
+ dt = datetime.datetime.now()
+ d_truncated = datetime.date(dt.year, dt.month, dt.day)
+ return d_truncated
+
+def Tomorrow():
+ dt = Today()
+ return dt + datetime.timedelta(days=1)
+
+def Yesterday():
+ dt = Today()
+ return dt - datetime.timedelta(days=1)
+
+def Prehistoric():
+ dt = datetime.date(2000,01,01)
+ return dt
+
+def GetData(sid,pid,fromDate=Yesterday(),toDate=Today()):
+ if database:
+ c = database.cursor()
+ c.execute("SELECT id,timestamp,value FROM sensor_values WHERE sensor_id=%s and parameter_id=%s and timestamp>=%s AND timestamp<%s",[sid,pid,fromDate.strftime('%Y-%m-%d %H:%M:%S'),toDate.strftime('%Y-%m-%d %H:%M:%S')])
+ return c.fetchall()
+ else:
+ print "No connection to DB"
+ exit()
+
+def FixRecord(id,value):
+ if database:
+ c = database.cursor()
+ command="UPDATE sensor_values SET value={} WHERE id='{}'".format(value,id)
+ c.execute(command)
+ else:
+ print "No connection to DB"
+ exit()
+
+def ProcessTable(sid,pid):
+
+ if process_all:
+ data=GetData(sid,pid,Prehistoric(),Today())
+ elif not current:
+ data=GetData(sid,pid)
+ else:
+ data=GetData(sid,pid,Today(),Tomorrow())
+
+ if not data:
+ return
+
+ sID=[]
+ sTime=[]
+ sValue=[]
+ for rec in data:
+ sID.append(rec[0])
+ sTime.append(rec[1])
+ sValue.append(rec[2])
+ sValue=np.array(sValue)
+
+ sValueFilt=scipy.signal.medfilt(sValue,5)
+
+ sValueDiff=abs(sValue-sValueFilt)
+
+ avg=np.mean(sValueDiff)
+
+ for i in range(0,len(sTime)-1):
+ if sValueDiff[i]>avg*filterThreshold:
+ print "fixing %s : %5.2f %5.2f %5.2f" % (sTime[i],sValue[i],sValueFilt[i],sValueDiff[i])
+ FixRecord(sID[i],sValueFilt[i])
+
+ database.commit()
+
+if len(sys.argv)==2 and sys.argv[1]=='current':
+ current=True
+else:
+ current=False
+
+if len(sys.argv)==2 and sys.argv[1]=='all':
+ process_all=True
+else:
+ process_all=False
+
+try:
+
+ cfg = ConfigParser.RawConfigParser(allow_no_value=True)
+ cfg.readfp(open('/etc/weathermon.conf'))
+ dbhost = cfg.get("mysql","host")
+ dbuser = cfg.get("mysql","user")
+ dbpasswd = cfg.get("mysql","passwd")
+ dbdb = cfg.get("mysql","db")
+
+ filterWindow = int(cfg.get("filter","window"))
+ filterThreshold = float(cfg.get("filter","threshold"))
+
+except:
+
+ print "Error reading configuration file"
+ exit()
+
+try:
+
+ database = MySQLdb.connect(host=dbhost,user=dbuser,passwd=dbpasswd,db=dbdb,use_unicode=True)
+ database.set_character_set('utf8')
+ c = database.cursor()
+ c.execute('SET NAMES utf8;')
+
+ print "Connected..."
+
+except:
+
+ print "Error connecting database"
+ exit()
+
+tables = GetTables()
+
+for sid,pid in tables:
+
+ print "Processing sensor %d, parameter %d " % (sid,pid)
+
+ ProcessTable(sid,pid)
+
+print "Processed "
+
\ No newline at end of file
--- /dev/null
+#!/usr/bin/python -u
+
+import paho.mqtt.client as paho
+import sys
+from ConfigParser import ConfigParser
+import MySQLdb
+from pprint import pprint
+import json
+from dateutil.parser import parser
+tparser=parser()
+
+def on_message(mosq, obj, msg):
+ topic=msg.topic
+ payload=json.loads(msg.payload)
+ timestamp=tparser.parse(payload['Time'])
+ for sensor_type in payload:
+ if sensor_type != 'Time' and sensor_type != 'TempUnit':
+ sensor_data=payload[sensor_type]
+ for param in sensor_data:
+ try:
+ value=sensor_data[param]
+ try:
+ c = database.cursor()
+ c.execute('CALL meteo.submit_mqtt(%s,%s,%s,%s,NULL)', (topic,sensor_type,param,value))
+ database.commit()
+ print topic,sensor_type,param,value
+ except:
+ print "Failed to submit data"
+ except:
+ None
+
+def Topics():
+
+ c = database.cursor()
+ c.execute(
+ '''
+ select topic from mqtt_topics where topic<>""
+ '''
+ )
+
+ topics=c.fetchall()
+ return topics
+
+
+def Init():
+
+ global client,database
+
+ conffile = sys.argv[1]
+
+ config = ConfigParser()
+ config.add_section('mqtt')
+ # set defaults for anonymous auth
+ config.set('mqtt', 'username', '')
+ config.set('mqtt', 'password', '')
+ config.set('mqtt', 'port', '1883')
+ config.read(conffile)
+
+ mqtt_server = config.get('mqtt', 'server')
+ mqtt_port = config.getint('mqtt', 'port')
+ mqtt_username = config.get('mqtt', 'username')
+ mqtt_password = config.get('mqtt', 'password')
+
+ mysql_server = config.get('mysql', 'server')
+ mysql_username = config.get('mysql','username')
+ mysql_password = config.get('mysql','password')
+ mysql_db = config.get('mysql','db')
+
+ client = paho.Client('weather')
+ client.username_pw_set(mqtt_username, mqtt_password)
+ client.on_message=on_message
+ client.connect(mqtt_server, port=mqtt_port)
+
+ database = MySQLdb.connect(host=mysql_server,user=mysql_username,passwd=mysql_password,db=mysql_db,use_unicode=True,connect_timeout=10)
+ database.set_character_set('utf8')
+ c = database.cursor()
+ c.execute('SET NAMES utf8;')
+
+ topics=[]
+ for topic in Topics():
+ topics.append((topic[0].encode('UTF-8'),1))
+
+ client.subscribe(topics)
+
+Init()
+
+try:
+ while True:
+ try:
+ client.loop()
+ except:
+ break
+finally:
+ exit()
--- /dev/null
+#!/usr/bin/python
+
+from pprint import pprint
+from os import system;
+
+import pycurl
+from urllib import urlencode
+from StringIO import StringIO
+
+import MySQLdb
+import ConfigParser
+
+import uuid
+
+debug = True
+
+def print_log(str):
+ global logging,debug
+ if debug>0:
+ print str
+ if logging == "on":
+ system("logger -t weathermon \""+str+"\"")
+
+def submit_narodmon():
+
+ global debug
+
+ param = { 'ID':devid }
+
+ c = database.cursor()
+ c.execute(
+ '''
+select nm_id,avg(value) from meteo.ext_sensors e,meteo.sensor_values v
+where
+e.sensor_id=v.sensor_id and e.param_id=v.parameter_id
+and v.timestamp>DATE_SUB(CURRENT_TIMESTAMP(),INTERVAL 15 MINUTE)
+and e.nm_id is not null
+group by e.sensor_id,e.param_id,e.nm_id,e.owm_id
+ '''
+ )
+
+ queue=c.fetchall()
+
+ if debug>1:
+ pprint(queue)
+
+ for (sensor,value) in queue:
+ param[sensor] = value
+
+ if debug>1:
+ pprint (param)
+
+ url = "http://narodmon.ru/post.php"
+
+ try:
+
+ response_buffer = StringIO()
+ curl = pycurl.Curl()
+
+ curl.setopt(curl.URL, url)
+ curl.setopt(curl.WRITEFUNCTION, response_buffer.write)
+ curl.setopt(curl.POST, 1)
+ curl.setopt(curl.POSTFIELDS, urlencode(param))
+
+ curl.perform()
+ curl.close()
+
+ response_value = response_buffer.getvalue()
+
+ print_log('Narodmon response: '+response_value)
+
+ return True
+
+ except:
+
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
+ traceback.print_exception(exc_type, exc_value, exc_traceback,
+ limit=2, file=sys.stdout)
+ return False
+
+def submit_owm():
+
+ global debug
+
+ url = "http://openweathermap.org/data/post"
+ params = {'name':owm_station, 'lat':owm_lat, 'long':owm_lon}
+
+ c = database.cursor()
+ c.execute(
+ '''
+ select owm_id,value from
+ (
+ SELECT sensor_id,parameter_id,avg(timestamp) timestamp,round(avg(value),1) value FROM meteo.sensor_values
+ where
+ timestamp>=date_sub(current_timestamp(), INTERVAL 15 MINUTE)
+ group by sensor_id,parameter_id
+ ) v,meteo.ext_sensors e
+ where v.sensor_id=e.sensor_id and v.parameter_id=e.param_id
+ and owm_id is not null
+ '''
+ )
+
+ queue=c.fetchall()
+
+ if debug>1:
+ pprint(queue)
+
+ for (sensor,value) in queue:
+ params[sensor]=value
+ if debug>1:
+ pprint (params)
+
+ try:
+
+ response_buffer = StringIO()
+ curl = pycurl.Curl()
+
+ curl.setopt(curl.URL, url)
+ curl.setopt(curl.USERPWD, '%s:%s' % (owmuser, owmpasswd))
+ curl.setopt(curl.WRITEFUNCTION, response_buffer.write)
+ curl.setopt(curl.POST, 1)
+ curl.setopt(curl.POSTFIELDS, urlencode(params))
+
+ curl.perform()
+ curl.close()
+
+ response_value = response_buffer.getvalue()
+
+ print_log('Openweathermap response: '+response_value)
+
+ return True
+
+ except:
+
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
+ traceback.print_exception(exc_type, exc_value, exc_traceback,
+ limit=2, file=sys.stdout)
+ return False
+
+def main():
+ submit_narodmon()
+ submit_owm()
+
+def init():
+
+ global logging,debug;
+ global devid;
+ global dbhost,dbuser,dbpasswd;
+ global owmuser,owmpasswd,owm_temp,owm_pres,owm_humi,owm_lat,owm_lon,owm_station;
+
+ try:
+
+ cfg = ConfigParser.RawConfigParser(allow_no_value=True)
+ cfg.readfp(open('/etc/weathermon.conf'))
+
+ try:
+ logging = cfg.get("logging","enabled")
+ except:
+ logging = None
+ try:
+ debug = int(cfg.get("logging","debug"))
+ except:
+ debug = 0
+
+ dbhost = cfg.get("mysql","host")
+ dbuser = cfg.get("mysql","user")
+ dbpasswd = cfg.get("mysql","passwd")
+ try:
+ narmon = cfg.get("narodmon","enable")
+ except:
+ narmon = 'off'
+ try:
+ devid = cfg.get("narodmon","devid")
+ except:
+ devid = "{:0>12X}".format(getnode())
+ try:
+ owmuser = cfg.get("openweathermap","user")
+ owmpasswd = cfg.get("openweathermap",'passwd')
+ except:
+ owmuser = None
+ if owmuser:
+ try:
+ owm_station=cfg.get("openweathermap","station")
+ owm_lat=cfg.get("openweathermap","lat")
+ owm_lon=cfg.get("openweathermap","lon")
+ except:
+ None
+
+ global database
+ database = MySQLdb.connect(host=dbhost,user=dbuser,passwd=dbpasswd,use_unicode=True,connect_timeout=10)
+ database.set_character_set('utf8')
+ c = database.cursor()
+ c.execute('SET NAMES utf8;')
+ print_log("Database connected...")
+ connected = True
+
+ except:
+
+ print_log("Cannot intialize system")
+ exit()
+
+if __name__ == "__main__":
+ import sys
+ reload(sys)
+ sys.setdefaultencoding('utf-8')
+ init()
+ main()
+++ /dev/null
-#!/usr/bin/lua
-
-require "uci"
-cur = uci.cursor()
-
-lfs = require "lfs"
-json = require "json"
-socket = require "socket"
-
-function split(str, pat)
- local t = {} -- NOTE: use {n = 0} in Lua-5.0
- local fpat = "(.-)" .. pat
- local last_end = 1
- local s, e, cap = str:find(fpat, 1)
- while s do
- if s ~= 1 or cap ~= "" then
- table.insert(t,cap)
- end
- last_end = e+1
- s, e, cap = str:find(fpat, last_end)
- end
- if last_end <= #str then
- cap = str:sub(last_end)
- table.insert(t, cap)
- end
- return t
-end
-
-function trim(s)
- return (s:gsub("^%s*(.-)%s*$", "%1"))
-end
-
-function get_device_list(config_name)
-
- local devices
- devices = {}
-
- cur.foreach(config_name, "device", function(s)
- devices[#devices+1] = s[".name"]
- end)
-
- return devices
-
-end
-
-function get_device(config_name,device_name)
-
- local device
- device = {}
-
- cur.foreach(config_name, "device", function(s)
- if s[".name"] == device_name then
- device = s
- end
- end)
-
- return device
-
-end
-
-function get_file_content(name)
- local f = io.open(name,"r")
- if f ~= nil then
- local content = trim(f:read("*all"))
- io.close(f)
- return content
- else
- return false
- end
-end
-
-function find_device(name,subsystem)
-
- local search_base
-
- if subsystem == "iio" then
- search_base = "/sys/bus/iio/devices"
- for file in lfs.dir(search_base) do
- if get_file_content(search_base.."/"..file.."/name") == name then
- return search_base.."/"..file
- end
- end
- elseif subsystem == "hwmon" then
- search_base = "/sys/class/hwmon"
- for file in lfs.dir(search_base) do
- if get_file_content(search_base.."/"..file.."/device/name") == name then
- return search_base.."/"..file.."/device"
- end
- end
- end
-
- return nil
-
-end
-
-function init_device(device,parameters,i2c_bus)
-
- if not i2c_bus then
- i2c_bus = 0
- end
-
- if device["module"] then
- os.execute("modprobe "..device["module"])
- end
-
- if device["type"] then
-
- devtype = split(device["type"],":")
- bus = devtype[1]
- subsystem = devtype[2]
-
- if (bus == "i2c") and device["address"] and device["name"] then
- pcall(function ()
- local f = io.open("/sys/class/i2c-dev/i2c-"..i2c_bus.."/device/new_device","w")
- io.output(f)
- io.write(device["name"].." "..device["address"])
- io.close(f)
- end)
- end
-
- device_path=find_device(device["name"],subsystem)
-
- if device_path and device["set_param"] then
- for key,record in pairs(device["set_param"]) do
- setparam = split(record,":")
- setpath = device_path.."/"..setparam[1]
- pcall(function ()
- local f = io.open(setpath,"w")
- io.output(f)
- io.write(setparam[2])
- io.close(f)
- end)
- end
- end
-
- if device_path and device["parameter"] then
-
- for key,record in pairs(device["parameter"]) do
-
- getparam = split(record,":")
- getparameter = {}
- getparameter["path"] = device_path.."/"..getparam[1]
- getparameter["name"] = getparam[2]
- getscale = getparam[3]
- getcorrection = getparam[4]
- if not getscale then
- getscale = 1
- end
- if not getcorrection then
- getcorrection = 0
- end
- getparameter["scale"] = tonumber(getscale)
- getparameter["sensor"] = device["name"]:upper()
- getparameter["correction"] = tonumber(getcorrection)
-
- parameters[#parameters+1] = getparameter
-
- end
-
- end
-
- end
-
-end
-
-function init(config_name)
-
- local parameters= {}
-
- i2c_bus = uci.get(config_name,"hardware","i2c_bus")
-
- local devices = get_device_list(config_name)
-
- for key,devname in pairs(devices) do
-
- device = get_device(config_name,devname)
-
- if device then
- init_device(device,parameters,i2c_bus)
- end
-
- end
-
- return parameters
-
-end
-
-function get_parameter(parameter)
- return tonumber(get_file_content(parameter["path"])) * parameter["scale"] + parameter["correction"]
-end
-
-function get_parameters(parameters)
- local results = {}
- for key,record in pairs(parameters) do
- if not results[record["sensor"]] then
- results[record["sensor"]] = {}
- end
- results[record["sensor"]][record["name"]] = get_parameter(record)
- end
- return results
-end
-
-config_name = arg[1]
-if not config_name then
- config_name = "weathermon"
-end
-
-parameters = init(config_name)
-
-local delay = uci.get(config_name,"process","delay")
-
-local working_dir = uci.get(config_name,"process","working_dir")
-local dump_file = uci.get(config_name,"process","dump_file")
-if working_dir then
- lfs.mkdir(working_dir)
-end
-
-if not delay then
- delay = 60
-end
-
-while true do
- values = get_parameters(parameters)
- for key,record in pairs(values) do
- dump = record
- dump["device"] = key
- print(json.encode(dump))
- end
- if dump_file then
- local f = io.open(dump_file,"w")
- io.output(f)
- io.write(json.encode(values))
- io.close(f)
- end
- socket.sleep(delay)
-end
-
\ No newline at end of file
+++ /dev/null
-#!/usr/bin/python -u
-
-import paho.mqtt.client as paho
-import sys
-from ConfigParser import ConfigParser
-import MySQLdb
-from pprint import pprint
-import json
-from dateutil.parser import parser
-tparser=parser()
-
-def on_message(mosq, obj, msg):
- topic=msg.topic
- payload=json.loads(msg.payload)
- timestamp=tparser.parse(payload['Time'])
- for sensor_type in payload:
- if sensor_type != 'Time' and sensor_type != 'TempUnit':
- sensor_data=payload[sensor_type]
- for param in sensor_data:
- try:
- value=sensor_data[param]
- try:
- c = database.cursor()
- c.execute('CALL meteo.submit_mqtt(%s,%s,%s,%s,NULL)', (topic,sensor_type,param,value))
- database.commit()
- print topic,sensor_type,param,value
- except:
- print "Failed to submit data"
- except:
- None
-
-def Topics():
-
- c = database.cursor()
- c.execute(
- '''
- select topic from mqtt_topics where topic<>""
- '''
- )
-
- topics=c.fetchall()
- return topics
-
-
-def Init():
-
- global client,database
-
- conffile = sys.argv[1]
-
- config = ConfigParser()
- config.add_section('mqtt')
- # set defaults for anonymous auth
- config.set('mqtt', 'username', '')
- config.set('mqtt', 'password', '')
- config.set('mqtt', 'port', '1883')
- config.read(conffile)
-
- mqtt_server = config.get('mqtt', 'server')
- mqtt_port = config.getint('mqtt', 'port')
- mqtt_username = config.get('mqtt', 'username')
- mqtt_password = config.get('mqtt', 'password')
-
- mysql_server = config.get('mysql', 'server')
- mysql_username = config.get('mysql','username')
- mysql_password = config.get('mysql','password')
- mysql_db = config.get('mysql','db')
-
- client = paho.Client('weather')
- client.username_pw_set(mqtt_username, mqtt_password)
- client.on_message=on_message
- client.connect(mqtt_server, port=mqtt_port)
-
- database = MySQLdb.connect(host=mysql_server,user=mysql_username,passwd=mysql_password,db=mysql_db,use_unicode=True,connect_timeout=10)
- database.set_character_set('utf8')
- c = database.cursor()
- c.execute('SET NAMES utf8;')
-
- topics=[]
- for topic in Topics():
- topics.append((topic[0].encode('UTF-8'),1))
-
- client.subscribe(topics)
-
-Init()
-
-try:
- while True:
- try:
- client.loop()
- except:
- break
-finally:
- exit()
+++ /dev/null
-[mysql]
-host = dbhost
-user = meteo
-passwd = password
-db = meteo
-[filter]
-window=5
-threshold=10
-[serial]
-port = /dev/ttyATH0
-[openweathermap]
-user = user
-passwd = password
-temp = OUTDOOR.44F.TEMPERATURE
-pres = BARO.DEFAULT.PRESSURE
-humi = OUTDOOR.44F.HUMIDITY
-lat = 55.403266
-lon = 37.561651
-station = station.name
-[logging]
-enabled=on
+++ /dev/null
-#!/usr/bin/lua
-
-json = require("json")
-socket = require("socket")
-
-function startswith(String,Start)
- if String then
- return string.sub(String,1,string.len(Start))==Start
- else
- return False
- end
-end
-
-function url_encode(str)
- if (str) then
- str = string.gsub (str, "\n", "\r\n")
- str = string.gsub (str, "([^%w %-%_%.%~])",
- function (c) return string.format ("%%%02X", string.byte(c)) end)
- str = string.gsub (str, " ", "+")
- end
- return str
-end
-
-function capture(cmd, raw)
- local f = assert(io.popen(cmd, 'r'))
- local s = assert(f:read('*a'))
- f:close()
- if raw then return s end
- s = string.gsub(s, '^%s+', '')
- s = string.gsub(s, '%s+$', '')
- s = string.gsub(s, '[\n\r]+', ' ')
- return s
-end
-
-function mqtt_encode(str)
- if (str) then
- str = string.gsub (str, "\n", "")
- str = string.gsub (str, "/", "-")
- end
- return str
-end
-
-function getConfig(configname)
-
- local uci=require("uci")
- local cur=uci.cursor()
- local config
- if configname then
- config=configname
- else
- config="weathermon"
- end
-
- web_url = cur.get(config,"web","url")
- web_user = cur.get(config,"web","user")
- web_pass = cur.get(config,"web","password")
- web_devid = cur.get(config,"web","devid")
-
- web_iface = cur.get(config,"web","iface")
-
- if web_iface then
-
- command = '/sbin/ifconfig '..web_iface..' | grep \'\\<inet\\>\' | sed -n \'1p\' | tr -s \' \' | cut -d \' \' -f3 | cut -d \':\' -f2'
- f=io.popen(command,'r')
- ip_addr=f:read()
-
- end
-
- if not web_devid then
-
- if web_iface then
- io.input("/sys/class/net/"..web_iface.."/address")
- else
- io.input("/sys/class/net/eth0/address")
- end
-
- mac = io.read("*line")
- mac = mac:gsub(":","")
- mac = mac:upper()
-
- web_devid = mac
-
- end
-
- logging = cur.get(config,"logging","enabled")
- touch_file = cur.get(config,"logging","touch_file")
-
- serial_port = cur.get(config,"serial","port")
- serial_baud = cur.get(config,"serial","baud")
-
- input_file = cur.get(config,"input","file")
- input_exec = cur.get(config,"input","exec")
- alarm_exec = cur.get(config,"alarm","exec")
-
- if serial_port then
-
- command = "stty -F "..serial_port.." "..serial_baud
- capture(command)
-
- end
-
- mqtt_host = cur.get(config,"mqtt","host")
- mqtt_port = cur.get(config,"mqtt","port")
- mqtt_id = cur.get(config,"mqtt","id")
- mqtt_topic = cur.get(config,"mqtt","topic")
- mqtt_alarm_topic = cur.get(config,"mqtt","alarm_topic")
-
- mqtt_user = cur.get(config,"mqtt","user")
- mqtt_passwd = cur.get(config,"mqtt","password")
-
- if mqtt_host and not mqtt_id then
- mqtt_id="weather-"..web_devid
- end
-
- if mqtt_host and not mqtt_port then
- mqtt_port = 1883
- end
-
- if mqtt_host and not mqtt_topic then
- mqtt_topic = 'weathermon/{dev}/{type}/{id}/{param}'
- end
-
- if mqtt_host and not mqtt_alarm_topic then
- mqtt_alarm_topic = 'alarm/{dev}/{type}/{id}'
- end
-
-end
-
-function touch()
- if touch_file then
- local file = io.open(touch_file, 'w')
- file:close()
- end
-end
-
-function sleep(sec)
- socket.select(nil, nil, sec)
-end
-
-function splitStr(str,char)
-
- local res = {}
- local idx = 1
-
- while str:len()>0 do
- pos = str:find(char);
- if pos == nil then
- res[idx]=str
- str=""
- else
- res[idx]=str:sub(1,pos-1)
- idx=idx+1
- str=str:sub(pos+1)
- end
- end
-
- return res
-
-end
-
-function printLog(str)
- if logging=="on" then
- capture("logger -t weathermon "..str)
- print(str)
- elseif logging=="syslog" then
- capture("logger -t weathermon "..str)
- elseif logging=="stdout" then
- print(str)
- end
-end
-
-function submitValue(type,id,param,val)
-
- url = web_url.."?stype="..url_encode(type).."&sid="..url_encode(id).."¶m="..url_encode(param).."&value="..url_encode(val)
-
- command = "curl"
-
- if web_iface then
- command = command.." --interface "..ip_addr
- end
-
- if web_user then
- command = command.." -u "..web_user..":"..web_pass
- end
-
- command = command.." \""..url.."\" 2>&1"
-
- result = capture(command)
-
- touch()
-
-end
-
-function processJson(str)
-
- msg=json.decode(str)
-
- sensor={}
-
- for key,value in pairs(msg) do
- if value then
- if key=="model" or key=="device" then
- sensor_type=value
- elseif key=="id" then
- sensor_id=value
- elseif key=='time' then
- sensor_time=value
- else
- sensor[key]=value
- end
- end
- end
-
- if not sensor_id then
- sensor_id = web_devid
- end
-
- if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
- if next(sensor)==nil then
- sensor["command"]="alarm"
- end
- for k,v in pairs(sensor) do
- printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = \""..v.."\"")
- submitValue(sensor_type,sensor_id,k,v)
- if mqtt_client then
- mqtt_path=string.gsub(mqtt_topic,"{(.-)}",
- function (name)
- if name=="dev" then
- return mqtt_encode(web_devid)
- elseif name=="type" then
- return mqtt_encode(sensor_type)
- elseif name=="id" then
- return mqtt_encode(sensor_id)
- elseif name=="param" then
- return k
- else
- return '{'..name..'}'
- end
- end)
- mqtt_client:publish(mqtt_path,v)
- end
- end
- else
- printLog("Cannot parse sensor input: "..str)
- end
-
-end
-
-function processLine(str)
-
- msg=splitStr(line,':')
- msg_type=msg[1] or ''
- msg_body=msg[2] or ''
- if msg_type=="STATUS" then
- printLog("Status: "..msg_body)
- elseif msg_type=="ERROR" then
- printLog("Error: "..msg_body)
- elseif msg_type=="SENSOR" then
- printLog("SENSOR: "..msg_body)
- sens = splitStr(msg_body,",")
- sensor = {}
- idx = 1
- sensor_type = nil
- sensor_id = web_devid
- for i,rec in ipairs(sens) do
- recrd=splitStr(rec,'=')
- key=recrd[1] or ''
- value=recrd[2] or ''
- if value then
- if key=="TYPE" then
- sensor_type=value
- elseif key=="ID" then
- sensor_id=value
- else
- sensor[key]=value
- end
- end
- end
- if not (sensor_type==nil or sensor_id==nil or sensor_type=='' or sensor_id=='') then
- for k,v in pairs(sensor) do
- printLog("Type = "..sensor_type..", ID = "..sensor_id..", Param = "..k..", Value = "..v)
- submitValue(sensor_type,sensor_id,k,v)
- if mqtt_client then
- mqtt_path=string.gsub(mqtt_topic,"{(.-)}",
- function (name)
- if name=="dev" then
- return web_devid
- elseif name=="type" then
- return sensor_type
- elseif name=="id" then
- return sensor_id
- elseif name=="param" then
- return k
- else
- return '{'..name..'}'
- end
- end)
- mqtt_client:publish(mqtt_path,v)
- end
- end
- else
- printLog("Cannot parse sensor input: "..msg_body)
- end
- elseif msg_type=="ALARM" then
- printLog("ALARM: "..msg_body)
- sens = splitStr(msg_body,",")
- sensor = {}
- idx = 1
- sensor_type = nil
- sensor_id = web_devid
- mqtt_param = {}
- for i,rec in ipairs(sens) do
- recrd=splitStr(rec,'=')
- key=recrd[1] or ''
- value=recrd[2] or ''
- if value then
- if key=="TYPE" then
- alarm_type=value
- elseif key=="ID" then
- alarm_id=value
- end
- end
- end
- if not (alarm_type==nil or alarm_id==nil or alarm_type=='' or alarm_id=='') then
- if mqtt_client then
- mqtt_path=string.gsub(mqtt_alarm_topic,"{(.-)}",
- function (name)
- if name=="dev" then
- return web_devid
- elseif name=="type" then
- return sensor_type
- elseif name=="id" then
- return sensor_id
- else
- return '{'..name..'}'
- end
- end)
- mqtt_client:publish(mqtt_path,msg_body)
- end
- if alarm_exec then
- command=alarm_exec..
- " \""..string.gsub(alarm_type,"\"","\\\"")..
- "\" \""..string.gsub(alarm_id,"\"","\\\"")..
- "\" \""..string.gsub(msg_body,"\"","\\\"").."\""
- capture(command)
- end
- else
- printLog("Cannot parse alarm input: "..msg_body)
- end
- end
-
-end
-
-getConfig(arg[1])
-
-if mqtt_host then
- MQTT = require "mosquitto"
- mqtt_client = MQTT.new(mqtt_id)
- if mqtt_user then
- mqtt_client:login_set(mqtt_user, mqtt_passwd)
- end
- mqtt_client:connect(mqtt_host,mqtt_port)
-end
-
-if serial_port then
- serialin=io.open(serial_port,"r")
-elseif input_file == "-" then
- serialin=io.stdin;
-elseif input_file then
- serialin=io.open(input_file,"r")
-elseif input_exec then
- serialin=io.popen(input_exec,"r")
-else
- printLog("No input selected")
- return
-end
-while 1 do
- line=serialin:read("*l")
- if line == nil then
- break
- end
- printLog("Received: "..line);
- if startswith(line,'{') then
- processJson(line)
- else
- processLine(line)
- end
-end
+++ /dev/null
-config internal 'web'
- option url http://url-to-submit-meteo-data
- option user meteo
- option password some-password
- option iface wlan0
-
-config internal 'input'
- option exec "/usr/bin/stdbuf -o0 /usr/bin/lua /usr/bin/weathermon-iio"
-# option port /dev/ttyATH0
-# option timeout 100
-# option baud 57600
-
-config internal 'logging'
- option enabled off # on/stdout/syslog
-# option touch_file /var/run/weathermon/weathermon.last
-
-config internal 'mqtt'
- option host mqtt.host.name
- option user meteo-user
- option password some-password
-
-config internal 'alarm'
-# option exec /usr/local/bin/alarm_received
-
-config internal 'hardware'
- option i2c_bus 0
-
-config internal 'process'
- option delay 48
- option working_dir "/var/weather/"
- option dump_file "/var/weather/weather.state"
-
-config device "bme280"
- option module "bmp280_i2c"
- option address "0x76"
- option type "i2c:iio"
- option name "bme280"
- list set_param "in_humidityrelative_oversampling_ratio:4"
- list set_param "in_temp_oversampling_ratio:8"
- list set_param "in_pressure_oversampling_ratio:8"
- list parameter "in_temp_input:T:0.001:-4" # source, name, scale, correction
- list parameter "in_pressure_input:P:10"
- list parameter "in_humidityrelative_input:H:0.001"
-
-config device "hmc5843"
- option module "hmc5843_i2c"
- option address "0x1e"
- option type "i2c:iio"
- option name "hmc5843"
- list set_param "in_magn_meas_conf:normal"
- list set_param "in_magn_sampling_frequency:2"
- list set_param "in_magn_scale:0.000007692"
- list parameter "in_magn_x_raw:MX"
- list parameter "in_magn_y_raw:MY"
- list parameter "in_magn_z_raw:MZ"
-
-config device "ads1115"
- option module "ads1015"
- option address "0x48"
- option type "i2c:hwmon"
- option name "ads1115"
- list parameter "in4_input:CO"
- list parameter "in5_input:CH4"
- list parameter "in6_input:AIR"
+++ /dev/null
-#!/usr/bin/python
-
-from pprint import pprint
-from os import system;
-
-import pycurl
-from urllib import urlencode
-from StringIO import StringIO
-
-import MySQLdb
-import ConfigParser
-
-import uuid
-
-debug = True
-
-def print_log(str):
- global logging,debug
- if debug>0:
- print str
- if logging == "on":
- system("logger -t weathermon \""+str+"\"")
-
-def submit_narodmon():
-
- global debug
-
- param = { 'ID':devid }
-
- c = database.cursor()
- c.execute(
- '''
-select nm_id,avg(value) from meteo.ext_sensors e,meteo.sensor_values v
-where
-e.sensor_id=v.sensor_id and e.param_id=v.parameter_id
-and v.timestamp>DATE_SUB(CURRENT_TIMESTAMP(),INTERVAL 15 MINUTE)
-and e.nm_id is not null
-group by e.sensor_id,e.param_id,e.nm_id,e.owm_id
- '''
- )
-
- queue=c.fetchall()
-
- if debug>1:
- pprint(queue)
-
- for (sensor,value) in queue:
- param[sensor] = value
-
- if debug>1:
- pprint (param)
-
- url = "http://narodmon.ru/post.php"
-
- try:
-
- response_buffer = StringIO()
- curl = pycurl.Curl()
-
- curl.setopt(curl.URL, url)
- curl.setopt(curl.WRITEFUNCTION, response_buffer.write)
- curl.setopt(curl.POST, 1)
- curl.setopt(curl.POSTFIELDS, urlencode(param))
-
- curl.perform()
- curl.close()
-
- response_value = response_buffer.getvalue()
-
- print_log('Narodmon response: '+response_value)
-
- return True
-
- except:
-
- exc_type, exc_value, exc_traceback = sys.exc_info()
- traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
- traceback.print_exception(exc_type, exc_value, exc_traceback,
- limit=2, file=sys.stdout)
- return False
-
-def submit_owm():
-
- global debug
-
- url = "http://openweathermap.org/data/post"
- params = {'name':owm_station, 'lat':owm_lat, 'long':owm_lon}
-
- c = database.cursor()
- c.execute(
- '''
- select owm_id,value from
- (
- SELECT sensor_id,parameter_id,avg(timestamp) timestamp,round(avg(value),1) value FROM meteo.sensor_values
- where
- timestamp>=date_sub(current_timestamp(), INTERVAL 15 MINUTE)
- group by sensor_id,parameter_id
- ) v,meteo.ext_sensors e
- where v.sensor_id=e.sensor_id and v.parameter_id=e.param_id
- and owm_id is not null
- '''
- )
-
- queue=c.fetchall()
-
- if debug>1:
- pprint(queue)
-
- for (sensor,value) in queue:
- params[sensor]=value
- if debug>1:
- pprint (params)
-
- try:
-
- response_buffer = StringIO()
- curl = pycurl.Curl()
-
- curl.setopt(curl.URL, url)
- curl.setopt(curl.USERPWD, '%s:%s' % (owmuser, owmpasswd))
- curl.setopt(curl.WRITEFUNCTION, response_buffer.write)
- curl.setopt(curl.POST, 1)
- curl.setopt(curl.POSTFIELDS, urlencode(params))
-
- curl.perform()
- curl.close()
-
- response_value = response_buffer.getvalue()
-
- print_log('Openweathermap response: '+response_value)
-
- return True
-
- except:
-
- exc_type, exc_value, exc_traceback = sys.exc_info()
- traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
- traceback.print_exception(exc_type, exc_value, exc_traceback,
- limit=2, file=sys.stdout)
- return False
-
-def main():
- submit_narodmon()
- submit_owm()
-
-def init():
-
- global logging,debug;
- global devid;
- global dbhost,dbuser,dbpasswd;
- global owmuser,owmpasswd,owm_temp,owm_pres,owm_humi,owm_lat,owm_lon,owm_station;
-
- try:
-
- cfg = ConfigParser.RawConfigParser(allow_no_value=True)
- cfg.readfp(open('/etc/weathermon.conf'))
-
- try:
- logging = cfg.get("logging","enabled")
- except:
- logging = None
- try:
- debug = int(cfg.get("logging","debug"))
- except:
- debug = 0
-
- dbhost = cfg.get("mysql","host")
- dbuser = cfg.get("mysql","user")
- dbpasswd = cfg.get("mysql","passwd")
- try:
- narmon = cfg.get("narodmon","enable")
- except:
- narmon = 'off'
- try:
- devid = cfg.get("narodmon","devid")
- except:
- devid = "{:0>12X}".format(getnode())
- try:
- owmuser = cfg.get("openweathermap","user")
- owmpasswd = cfg.get("openweathermap",'passwd')
- except:
- owmuser = None
- if owmuser:
- try:
- owm_station=cfg.get("openweathermap","station")
- owm_lat=cfg.get("openweathermap","lat")
- owm_lon=cfg.get("openweathermap","lon")
- except:
- None
-
- global database
- database = MySQLdb.connect(host=dbhost,user=dbuser,passwd=dbpasswd,use_unicode=True,connect_timeout=10)
- database.set_character_set('utf8')
- c = database.cursor()
- c.execute('SET NAMES utf8;')
- print_log("Database connected...")
- connected = True
-
- except:
-
- print_log("Cannot intialize system")
- exit()
-
-if __name__ == "__main__":
- import sys
- reload(sys)
- sys.setdefaultencoding('utf-8')
- init()
- main()