- Добавлен процесс для чтения iio-датчиков
authorRoman Bazalevsky <rvb@rvb.name>
Mon, 12 Nov 2018 17:18:30 +0000 (20:18 +0300)
committerRoman Bazalevsky <rvb@rvb.name>
Mon, 12 Nov 2018 17:18:30 +0000 (20:18 +0300)
- Добавлено сохранение неотосланных данных в очереди
- Добавлено локальное хранилище данных и легковесный веб-интерфейс
- Добавлен автоматический бэкап (регулярно и при перезапуске)

34 files changed:
bin/dump-current [new file with mode: 0755]
bin/dump-yesterday [new file with mode: 0755]
bin/weather-backlog [new file with mode: 0755]
bin/weather-backup [new file with mode: 0755]
bin/weather-display [new file with mode: 0755]
bin/weather-filter [new file with mode: 0755]
bin/weather-init [new file with mode: 0755]
bin/weathermon [new file with mode: 0755]
bin/weathermon-iio [new file with mode: 0755]
config/weathermon.uci [new file with mode: 0644]
filter_meteo.py [deleted file]
init.d/weather-display [new file with mode: 0755]
init.d/weather-init [new file with mode: 0755]
init.d/weathermon [new file with mode: 0755]
lib/wm_util.lua [new file with mode: 0644]
openwrt-web/cgi-bin/meteo.lua [new file with mode: 0755]
openwrt-web/meteo/archive.html [new file with mode: 0644]
openwrt-web/meteo/archive.js [new file with mode: 0644]
openwrt-web/meteo/graph.html [new file with mode: 0644]
openwrt-web/meteo/graph.js [new file with mode: 0644]
openwrt-web/meteo/index.html [new file with mode: 0644]
openwrt-web/meteo/meteo.css [new file with mode: 0644]
openwrt-web/meteo/meteo.js [new file with mode: 0644]
openwrt-web/meteo/properties.json [new file with mode: 0644]
openwrt-web/meteo/weather.svg [new file with mode: 0644]
server/filter_meteo.py [new file with mode: 0755]
server/weathermon-mqtt [new file with mode: 0755]
server/weathermon_cron [new file with mode: 0755]
weathermon-iio [deleted file]
weathermon-mqtt [deleted file]
weathermon.conf [deleted file]
weathermon.lua [deleted file]
weathermon.uci [deleted file]
weathermon_cron [deleted file]

diff --git a/bin/dump-current b/bin/dump-current
new file mode 100755 (executable)
index 0000000..c7670b9
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+logdb=`uci get weathermon.process.logdb`
+dumpdir=/var/weather/www
+mkdir -p $dumpdir
+/usr/bin/weather-filter $logdb dump-compacted - $dumpdir
+
diff --git a/bin/dump-yesterday b/bin/dump-yesterday
new file mode 100755 (executable)
index 0000000..457792f
--- /dev/null
@@ -0,0 +1,13 @@
+#!/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')"
diff --git a/bin/weather-backlog b/bin/weather-backlog
new file mode 100755 (executable)
index 0000000..84844cc
--- /dev/null
@@ -0,0 +1,77 @@
+#!/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).."&param="..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
diff --git a/bin/weather-backup b/bin/weather-backup
new file mode 100755 (executable)
index 0000000..fd219f7
--- /dev/null
@@ -0,0 +1,15 @@
+#!/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
diff --git a/bin/weather-display b/bin/weather-display
new file mode 100755 (executable)
index 0000000..1e0993b
--- /dev/null
@@ -0,0 +1,262 @@
+#!/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
diff --git a/bin/weather-filter b/bin/weather-filter
new file mode 100755 (executable)
index 0000000..1fc798f
--- /dev/null
@@ -0,0 +1,242 @@
+#!/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
diff --git a/bin/weather-init b/bin/weather-init
new file mode 100755 (executable)
index 0000000..d009c67
--- /dev/null
@@ -0,0 +1,33 @@
+# 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
diff --git a/bin/weathermon b/bin/weathermon
new file mode 100755 (executable)
index 0000000..03fad42
--- /dev/null
@@ -0,0 +1,408 @@
+#!/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).."&param="..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
diff --git a/bin/weathermon-iio b/bin/weathermon-iio
new file mode 100755 (executable)
index 0000000..7fe5f68
--- /dev/null
@@ -0,0 +1,205 @@
+#!/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
diff --git a/config/weathermon.uci b/config/weathermon.uci
new file mode 100644 (file)
index 0000000..3f97040
--- /dev/null
@@ -0,0 +1,158 @@
+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"
diff --git a/filter_meteo.py b/filter_meteo.py
deleted file mode 100755 (executable)
index fc497db..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/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
diff --git a/init.d/weather-display b/init.d/weather-display
new file mode 100755 (executable)
index 0000000..ed9c44f
--- /dev/null
@@ -0,0 +1,28 @@
+#!/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
+}
diff --git a/init.d/weather-init b/init.d/weather-init
new file mode 100755 (executable)
index 0000000..fd61a78
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2007-2014 OpenWrt.org
+
+START=45
+
+boot() {
+
+       /usr/bin/weather-init
+
+}
diff --git a/init.d/weathermon b/init.d/weathermon
new file mode 100755 (executable)
index 0000000..cb1822a
--- /dev/null
@@ -0,0 +1,47 @@
+#!/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
+}
diff --git a/lib/wm_util.lua b/lib/wm_util.lua
new file mode 100644 (file)
index 0000000..bb5d8ee
--- /dev/null
@@ -0,0 +1,106 @@
+#!/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
diff --git a/openwrt-web/cgi-bin/meteo.lua b/openwrt-web/cgi-bin/meteo.lua
new file mode 100755 (executable)
index 0000000..8e38a49
--- /dev/null
@@ -0,0 +1,110 @@
+#!/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
diff --git a/openwrt-web/meteo/archive.html b/openwrt-web/meteo/archive.html
new file mode 100644 (file)
index 0000000..129bb18
--- /dev/null
@@ -0,0 +1,48 @@
+<?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>
diff --git a/openwrt-web/meteo/archive.js b/openwrt-web/meteo/archive.js
new file mode 100644 (file)
index 0000000..20aa7aa
--- /dev/null
@@ -0,0 +1,322 @@
+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);
diff --git a/openwrt-web/meteo/graph.html b/openwrt-web/meteo/graph.html
new file mode 100644 (file)
index 0000000..da1f863
--- /dev/null
@@ -0,0 +1,38 @@
+<?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>
diff --git a/openwrt-web/meteo/graph.js b/openwrt-web/meteo/graph.js
new file mode 100644 (file)
index 0000000..ed74328
--- /dev/null
@@ -0,0 +1,206 @@
+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)
diff --git a/openwrt-web/meteo/index.html b/openwrt-web/meteo/index.html
new file mode 100644 (file)
index 0000000..fbd7ce0
--- /dev/null
@@ -0,0 +1,47 @@
+<?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>
diff --git a/openwrt-web/meteo/meteo.css b/openwrt-web/meteo/meteo.css
new file mode 100644 (file)
index 0000000..ff3fe2f
--- /dev/null
@@ -0,0 +1,200 @@
+* {
+    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; 
+}
diff --git a/openwrt-web/meteo/meteo.js b/openwrt-web/meteo/meteo.js
new file mode 100644 (file)
index 0000000..f56bc2d
--- /dev/null
@@ -0,0 +1,85 @@
+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)
diff --git a/openwrt-web/meteo/properties.json b/openwrt-web/meteo/properties.json
new file mode 100644 (file)
index 0000000..54c0e66
--- /dev/null
@@ -0,0 +1,44 @@
+{
+  "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" }
+  }
+}
diff --git a/openwrt-web/meteo/weather.svg b/openwrt-web/meteo/weather.svg
new file mode 100644 (file)
index 0000000..dc49122
--- /dev/null
@@ -0,0 +1,39 @@
+<?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
diff --git a/server/filter_meteo.py b/server/filter_meteo.py
new file mode 100755 (executable)
index 0000000..fc497db
--- /dev/null
@@ -0,0 +1,144 @@
+#!/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
diff --git a/server/weathermon-mqtt b/server/weathermon-mqtt
new file mode 100755 (executable)
index 0000000..c6b6a82
--- /dev/null
@@ -0,0 +1,94 @@
+#!/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()   
diff --git a/server/weathermon_cron b/server/weathermon_cron
new file mode 100755 (executable)
index 0000000..bc4eb88
--- /dev/null
@@ -0,0 +1,209 @@
+#!/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()
diff --git a/weathermon-iio b/weathermon-iio
deleted file mode 100755 (executable)
index 93b9cb5..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-#!/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
diff --git a/weathermon-mqtt b/weathermon-mqtt
deleted file mode 100755 (executable)
index c6b6a82..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/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()   
diff --git a/weathermon.conf b/weathermon.conf
deleted file mode 100644 (file)
index 9018a24..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-[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
diff --git a/weathermon.lua b/weathermon.lua
deleted file mode 100755 (executable)
index 3208e30..0000000
+++ /dev/null
@@ -1,388 +0,0 @@
-#!/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).."&param="..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
diff --git a/weathermon.uci b/weathermon.uci
deleted file mode 100644 (file)
index 7a4162f..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-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"
diff --git a/weathermon_cron b/weathermon_cron
deleted file mode 100755 (executable)
index bc4eb88..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-#!/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()