ESP32 e-Paper info screen (client part).
[weathermon.git] / bin / weather-lcd
1 #!/usr/bin/lua
2
3 socket = require "socket"
4 json = require "json"
5 require "uci"
6 cur = uci.cursor()
7 require "wm_util"
8
9 config_name = arg[1]
10 if not config_name then
11   config_name = "weathermon"
12 end  
13
14 weather_file = uci.get(config_name,"process","dump_file")
15 weather_db = uci.get(config_name,"process","logdb")
16
17 graph_duration = uci.get(config_name,"display","graph_duration")
18
19 if graph_duration and not(weather_db == "") then
20   pcall(function ()
21       local dbdriver = require "luasql.sqlite3"
22       env = assert(dbdriver.sqlite3())
23       log_con = assert(env:connect(weather_db))
24     end)  
25 end
26
27 function round(num, numDecimalPlaces)
28   local mult = 10^(numDecimalPlaces or 0)
29   return math.floor(num * mult + 0.5) / mult
30 end
31
32 function trim(s)
33   return (s:gsub("^%s*(.-)%s*$", "%1"))
34 end
35
36 function lpad(s, l, c)
37   local res = string.rep(c or ' ', l - #s) .. s
38   return res, res ~= s
39 end
40
41 function process_file()
42   txt = get_file_content(weather_file)
43   data = json.decode(txt)
44   for k,v in pairs(data) do
45     return v
46   end
47 end
48
49 function get_display_config()
50
51   width  = tonumber(uci.get(config_name,"display","width"))
52   height = tonumber(uci.get(config_name,"display","height"))
53   charwidth  = tonumber(uci.get(config_name,"display","charwidth"))
54   charheight = tonumber(uci.get(config_name,"display","charheight"))
55   title  = uci.get(config_name,"display","title")
56   
57   pages = {}
58   pagetitles = {}
59   pagedurations = {}
60   pagecolumns = {}
61   pagecolwidth = {}
62   
63   local page, def, line, col, pos
64   
65   cur.foreach(config_name,"display", function(s) 
66       page = {}
67       local columns = s["columns"]
68       if not columns or columns == "" then
69         columns = 1
70       else 
71         columns = tonumber(columns)
72       end
73       local pagetitle = s["title"]
74       if not pagetitle then
75         pagetitle = title
76       end
77       local pageduration = s["duration"]
78       if not (pagetitle=="") then
79         line = 2
80       else
81         line = 1
82       end
83       col = 1
84       pos = 0
85       for k,v in pairs(s["parameter"]) do
86         def = {}
87         if not(v == "") then
88           v = split(v,":")
89           def["sensor"] = v[1]
90           def["param"] = v[2]
91           def["label"] = v[3]
92           def["unit"] = v[4]
93           def["scale"] = tonumber(v[5])
94           def["round"] = tonumber(v[6])
95           def["pos"] = tonumber(v[7])
96           def["line"] = line
97           def["col"] = col
98           page[#page+1] = def
99         end
100         col = col + 1
101         if col > columns then
102           col = 1
103           line = line + 1
104         end
105       end
106       pages[#pages+1] = page
107       pagetitles[#pages] = pagetitle
108       pagedurations[#pages] = pageduration
109       pagecolumns[#pages] = columns
110       pagecolwidth[#pages] = math.floor(width/columns)
111     end)
112
113 end
114
115 function connect_server()
116   local ip = uci.get(config_name,"display","server")
117   local port = uci.get(config_name,"display","port")
118   conn = socket.connect(ip,port)
119   return conn
120 end
121
122 function write_command(conn,command)
123   conn:send(command.."\n")
124   local str=conn:receive()
125   return str
126 end
127
128 function setup_pages(conn)
129   write_command(conn,"hello")
130   write_command(conn,"client_set -name weathermon")
131   for i,page in pairs(pages) do
132     local pageid = "page"..trim(tostring(i))
133     write_command(conn,"screen_add "..pageid)
134     local pagetitle = pagetitles[i]
135     write_command(conn,"screen_set "..pageid.." -cursor off")
136     if not(pagetitle == "") then
137       write_command(conn,"screen_set "..pageid.." -name "..pagetitle)
138       write_command(conn,"widget_add "..pageid.." "..pageid..".title title")
139       write_command(conn,"widget_set "..pageid.." "..pageid..".title \"".. pagetitle.."\"")
140     end
141     local pageduration = pagedurations[i]
142     if pageduration and not(pageduration == "") then
143       write_command(conn,"screen_set "..pageid.." -duration "..pageduration)
144     end
145     for j,def in pairs(page) do
146       defid = def["sensor"].."."..def["param"]
147       write_command(conn,"widget_add "..pageid.." "..defid.." string")
148     end
149   end
150   if log_con then
151     for i,page in pairs(pages) do
152       for j,def in pairs(page) do
153         pageid = def["sensor"].."."..def["param"]
154         pagetitle = trim(def["label"])..", "..def["unit"]
155         write_command(conn,"screen_add "..pageid)
156         write_command(conn,"screen_set "..pageid.." -cursor off -name "..pagetitle.." -duration "..graph_duration)
157         write_command(conn,"widget_add "..pageid.." "..pageid..".title title")
158         if height>2 then
159           write_command(conn,"widget_add "..pageid.." "..pageid..".max string")
160           write_command(conn,"widget_add "..pageid.." "..pageid..".min string")
161           for k = 3,height-1 do
162             write_command(conn,"widget_add "..pageid.." "..pageid..".place"..trim(tostring(k)).." string")
163           end
164         end  
165         write_command(conn,"widget_set "..pageid.." "..pageid..".title \"".. pagetitle.."\"")
166       end
167     end
168   end
169 end
170
171 function process_vals(vals)
172   for i,page in pairs(pages) do
173     col_width = pagecolwidth[i]
174     local pageid = "page"..trim(tostring(i))
175     for j,def in pairs(page) do
176       val = vals[def["sensor"]][def["param"]]
177       if val then
178         val = round(val * def["scale"], def["round"])
179       end
180       defid = def["sensor"].."."..def["param"]
181       val = lpad(tostring(val),def["pos"]).." "..def["unit"]
182       if not(def["label"] == "") then
183         val = def["label"]..": "..val
184       end
185       write_command(conn,"widget_set "..pageid.." "..defid.." "..trim(tostring((def["col"]-1)*col_width+1)).." "..def["line"].." \""..val.."\"")
186     end
187   end
188 end  
189
190 function process_graphs()
191   for i,page in pairs(pages) do
192     for j,def in pairs(page) do
193       local sensor = def["sensor"]
194       local param  = def["param"]
195       local pageid = sensor.."."..param
196       local sqlcur = assert (log_con:execute(string.format("select hour,avg(value) val from (select strftime('%%Y-%%m-%%d %%H',time_stamp)||':00' hour,value from log where sensor='%s' and param='%s') group by hour order by hour desc limit 24",sensor,param)))
197       local row = sqlcur:fetch ({}, "a")
198       local vals = {}
199       local maxval = -99999999
200       local minval =  99999999
201       local len
202       for k=width,1,-1 do
203         if row then
204           val = row["val"] * def["scale"]
205           row = sqlcur:fetch ({}, "a")
206         else
207           val = nil  
208         end  
209         vals[k] = val
210         if val and (val > maxval) then maxval = val; end
211         if val and (val < minval) then minval = val; end
212       end          
213       sqlcur:close()
214       minval = math.floor(minval)
215       maxval = math.ceil(maxval)
216       if height>2 then
217         local minvalstr = trim(tostring(minval))
218         local maxvalstr = trim(tostring(maxval))
219         len = math.max(string.len(minvalstr),string.len(maxvalstr))
220         write_command(conn,"widget_set "..pageid.." "..pageid..".max "..trim(tostring(width-string.len(maxvalstr)+1)).." 2 "..maxvalstr)
221         write_command(conn,"widget_set "..pageid.." "..pageid..".min "..trim(tostring(width-string.len(minvalstr)+1)).." "..height.." "..minvalstr)
222         for k = 3,height-1 do
223           write_command(conn,"widget_set "..pageid.." "..pageid..".place"..trim(tostring(k)).." "..trim(tostring(width-len+1)).." "..k.." \" "..string.rep("-",len-1).."\"")
224         end
225       else
226         len = 0
227       end    
228       local m = width
229       for k=1,width do
230         write_command(conn,"widget_del "..pageid.." "..pageid..".bar"..trim(tostring(k)))
231       end
232       for k = width-len,1,-1 do
233         val = vals[m]
234         if val then
235           h = math.floor(0.5+(val-minval)/(maxval-minval)*(height-1)*charheight)
236         else
237           h = 0
238         end  
239         write_command(conn,"widget_add "..pageid.." "..pageid..".bar"..trim(tostring(k)).." vbar")
240         write_command(conn,"widget_set "..pageid.." "..pageid..".bar"..trim(tostring(k)).." "..k.." "..height.." "..h)
241         m = m - 1
242       end
243     end
244   end
245 end
246
247 get_display_config()
248
249 conn = connect_server()
250
251 setup_pages(conn)
252
253 while true do
254
255 --  pcall( function ()
256
257       vals = process_file()
258       process_vals(vals)
259
260       if weather_db then
261         process_graphs()
262       end  
263       
264       os.execute("inotifywait -e MODIFY \""..weather_file.."\"")
265   
266 --    end)
267
268     socket.sleep(3)
269   
270 end