pyweb.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #!/usr/bin/env python3
  2. from bottle import get,post,request,Bottle,run,template,static_file
  3. import requests,threading,time,json,zlib,gnupg,socket,psutil,os,sys,pymysql,queue,_thread,matplotlib.pyplot as plt,matplotlib.cm as cm,numpy
  4. #gpg=gnupg.GPG('/usr/bin/gpg',gnupghome='/home/pi/.gnupg')
  5. gpg=gnupg.GPG()
  6. pathname = os.path.dirname(sys.argv[0])
  7. abspath=os.path.abspath(pathname)
  8. configfile=abspath+"/config.json"
  9. try:
  10. cf=open(configfile,"r")
  11. except:
  12. cf=open(configfile+".template","r")
  13. log_conf=json.load(cf)
  14. cf.close()
  15. parameter={"device":socket.gethostname(),"allowed_ip":{"127.0.0.1":"25A4CF79414F10FD"},"gpg_keyid":"25A4CF79414F10FD"}
  16. for n in parameter:
  17. if n in log_conf:
  18. parameter[n]=log_conf[n]
  19. if "sqlserver" in log_conf:
  20. hostname="banana"
  21. if "host" in log_conf['sqlserver']:
  22. hostname=log_conf['sqlserver']['host']
  23. port=24049
  24. if "port" in log_conf['sqlserver']:
  25. port=int(log_conf['sqlserver']['port'])
  26. clientlist=parameter['allowed_ip']
  27. try:
  28. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  29. except:
  30. clientlist=parameter['allowed_ip']
  31. else:
  32. mycursor=mydb.cursor(pymysql.cursors.DictCursor)
  33. mycursor.execute("select ip,keyid from clients;")
  34. myclientlist=mycursor.fetchall()
  35. if len(myclientlist)>0:
  36. tcl={}
  37. for i in myclientlist:
  38. if len(gpg.list_keys(i['keyid']))>0:
  39. tcl[i['ip']]=i['keyid']
  40. if len(tcl)>0:
  41. clientlist=tcl
  42. #sqlinsert="insert into measures (time,id,value) values ({0:d},{1:d},{2:d})"
  43. sqlinsert="insert into measures (time,id,value) values "
  44. hsqlinsert="insert into hourly_measures (time,id,value) values "
  45. dsqlinsert="insert into daily_measures (time,id,value) values "
  46. asqlinsert="insert into last_measures (time,id,value) values "
  47. measdata={}
  48. _HASH="hash"
  49. _SIGNEDGPG="signed_gpg"
  50. _PAYLOAD="payload"
  51. _MEASURES="measure"
  52. _BEGINSIGNATURE="-----BEGIN PGP SIGNATURE-----"
  53. _BEGINMESSAGE="-----BEGIN PGP SIGNED MESSAGE-----"
  54. _BEGINHASH="Hash:"
  55. _JSONDATA="data"
  56. _JSONSIGNEDDATA="signed_data"
  57. _JSONENCRYPTDATA="encrypted_data"
  58. def analyse_jsonin(json_in,hash_id):
  59. measdata={}
  60. if _JSONDATA in json_in:
  61. measdata=json_in[_JSONDATA]
  62. if _JSONSIGNEDDATA in json_in:
  63. vpgp=gpg.verify(json_in[_JSONSIGNEDDATA])
  64. if hash_id != vpgp.key_id:
  65. print("signature does not fit hash id")
  66. else:
  67. signed_in=json_in[_JSONSIGNEDDATA].split("\n")
  68. signed_in[signed_in.index(_BEGINSIGNATURE):]=""
  69. del signed_in[signed_in.index(_BEGINMESSAGE)]
  70. del signed_in[signed_in.index("")]
  71. for h in signed_in:
  72. if _BEGINHASH in h:
  73. del signed_in[signed_in.index(h)]
  74. if len(signed_in)>0:
  75. measdata=json.loads(signed_in[0])
  76. if _JSONENCRYPTDATA in json_in:
  77. dpgp=gpg.decrypt(json_in[_JSONENCRYPTDATA])
  78. if hash_id != dpgp.key_id:
  79. print("signature of encrypted data does not fit hash id")
  80. else:
  81. measdata=json.loads(dpgp.data)
  82. if len(measdata)==0:
  83. print("no data available")
  84. else:
  85. _thread.start_new_thread(insert_sql,(measdata,))
  86. def insert_sql(measdata):
  87. # tsi=sqlinsert
  88. sqlu=[]
  89. tsi=""
  90. tsi_count=0
  91. atsi=""
  92. for h in measdata: # iterate over each variable stored in json
  93. md=measdata[h]
  94. sqlu.append("delete from last_measures where id = '"+str(h)+"';")
  95. mmax=max(md['measures'])
  96. atsi=atsi+'('+str(mmax)+','+h+','+str(md['measures'][mmax])+'),'
  97. for m in md['measures']: # iterate over each measurement for given variable
  98. try:
  99. stime=int(m)
  100. except:
  101. print("wrong entry")
  102. else:
  103. ttsi='('+str(m)+','+h+','+str(md['measures'][m])+'),'
  104. tsi=tsi+ttsi
  105. tsi_count=tsi_count+1
  106. tsi=tsi[:-1]+';'
  107. atsi=atsi[:-1]+';'
  108. try:
  109. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  110. except:
  111. print("could not connect to mysql")
  112. with open("missed.sql","a") as sqlstore:
  113. sqlstore.write(tsi)
  114. else:
  115. mycursor=mydb.cursor(pymysql.cursors.DictCursor)
  116. for h in measdata:
  117. mycursor.execute('select id from ids where id='+h+';')
  118. t=mycursor.fetchall()
  119. if len(t)==0:
  120. hsql='insert into ids (id,device,varname,sensor,sensorsub,i2c) values ('+h+','
  121. hm=measdata[h]
  122. ht={}
  123. for i in ['device','varname','sensor','sensorsub']:
  124. if i in hm:
  125. hsql=hsql+"'"+hm[i]+"'"+','
  126. else:
  127. hsql=hsql+"\'\',"
  128. if 'i2c' in hm:
  129. try:
  130. hsql=hsql+"'"+str(hm['i2c'])+"'"
  131. except:
  132. hsql=hsql+"'0'"
  133. else:
  134. hsql=hsql+0
  135. hsql=hsql+');'
  136. try:
  137. mycursor.execute(hsql)
  138. except:
  139. print("could not insert new var_id")
  140. print(hsql)
  141. for dsql in sqlu:
  142. mycursor.execute(dsql)
  143. mycursor.execute(sqlinsert+tsi)
  144. print(sqlinsert+tsi)
  145. mycursor.execute(hsqlinsert+tsi)
  146. mycursor.execute(dsqlinsert+tsi)
  147. mycursor.execute(asqlinsert+atsi)
  148. delhtime=int((time.time()-3600)*1000)
  149. deldtime=int((time.time()-86400)*1000)
  150. mycursor.execute("delete from hourly_measures where time < "+str(delhtime))
  151. mycursor.execute("delete from daily_measures where time < "+str(deldtime))
  152. print(str(tsi_count)+" new measures inserted")
  153. mycursor.close()
  154. mydb.commit()
  155. mydb.close()
  156. # print(tsi)
  157. # print(measdata)
  158. app=Bottle()
  159. #bottlesqp=bottle_mysql.Plugin(read_default_file='~/.my.cnf',db="rasolar")
  160. #app.install(plugin)
  161. @app.get('/')
  162. def approot():
  163. return '''
  164. <a href="/ids">Uebersicht</a></br>
  165. <a href="/solar">Solarübersicht</a></br>
  166. <a href="/cpu">CPU-Temperaturen</a></br>
  167. '''
  168. @app.get('/ids')
  169. def show_ids():
  170. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  171. mycursor=mydb.cursor(pymysql.cursors.DictCursor)
  172. starttime=time.time()
  173. mycursor.execute('select gm.*,last_measures.value,ids.varname,ids.device,ids.sensor,ids.i2c,ids.sensorsub from (select id, max(time) as time,count(time) as count from last_measures group by id) gm join last_measures on last_measures.id=gm.id and last_measures.time=gm.time join ids on ids.id=gm.id;')
  174. print("ids sql duration:"+str(time.time()-starttime))
  175. row=mycursor.fetchall()
  176. mycursor.close()
  177. mydb.close()
  178. return template('ids.tpl',measdata=row)
  179. @app.get('/solar')
  180. def show_solar():
  181. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  182. myc=mydb.cursor()
  183. myc.execute("select gm.time,last_measures.value/1000,ids.varname from (select id, max(time) as time from last_measures group by id) gm join last_measures on last_measures.id=gm.id and last_measures.time=gm.time join ids on ids.id=gm.id where ids.sensor='tristar';")
  184. ts=myc.fetchall()
  185. myc.close()
  186. mydb.close()
  187. tt={"kwh_tot":0,"volt_bat":0,"volt_bat_sens":0,"volt_sweep_mc":0,"volt_sweep_oc":0,"amp_bat":0,"temp_heatsink":0,"temp_bat":0}
  188. zs=0
  189. for tims,value,varname in ts:
  190. tt[varname]=value
  191. if int(tims)>zs:
  192. zs=int(tims)
  193. return template('solar.tpl',solardata=tt,zeitstempel=time.strftime('%H:%M:%S %Y-%m-%d', time.localtime(zs/1000)))
  194. @app.get('/temp')
  195. def show_temperature():
  196. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  197. myc=mydb.cursor()
  198. myc.execute("select gm.time,last_measures.value/1000,ids.varname,ids.device,short_names.short from (select id, max(time) as time from last_measures group by id) gm join last_measures on last_measures.id=gm.id and last_measures.time=gm.time join ids on ids.id=gm.id join short_names on short_names.id=gm.id;")
  199. ts=myc.fetchall()
  200. myc.close()
  201. mydb.close()
  202. tt={"temp_garten":0,"temp_dach":0,"temp_carport":0,"hum_garten":0,"hum_dach":0,"hum_carport":0,"press_garten":0,"press_dach":0,"press_carport":0,"lux_dach":0,"uv_dach":0,"temp_solar":0,"temp_bat":0,"temp_wohnen":0,"hum_wohnen":0,"press_wohnen":0}
  203. zs=0
  204. thp={"rasolar":{"temperature":0,"humidity":0,"pressure":0,"time":0},"ragps":{"temperature":0,"humidity":0,"pressure":0,"time":0},"ragarden":{"temperature":0,"humidity":0,"pressure":0,"time":0}}
  205. for tims,value,varname,device,short in ts:
  206. tt[short]=value
  207. try:
  208. thp[device][varname]=value
  209. except:
  210. thp[device]={varname:value}
  211. try:
  212. if thp[device]['time']<tims:
  213. thp[device]['time']=tims
  214. except:
  215. thp[device]['time']=tims
  216. if int(tims)>zs:
  217. zs=int(tims)
  218. return template('temperature.tpl',tempdata=tt,zeitstempel=time.strftime('%H:%M:%S %Y-%m-%d', time.localtime(zs/1000)))
  219. @app.get('/clients')
  220. def show_clients():
  221. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  222. myc=mydb.cursor()
  223. no_clients=myc.execute('select distinct ids.device from ids join hourly_measures on hourly_measures.id=ids.id;')
  224. if no_clients > 0:
  225. t_cl=myc.fetchall()
  226. client_list=[]
  227. for cl in t_cl:
  228. client_list.append(cl[0])
  229. return template('clients.tpl',clientdata=client_list)
  230. else:
  231. return '''
  232. <!DOCTYPE html><html><head>
  233. <title>Übersicht Tristar</title>
  234. </head><body>
  235. <p>Aktuell keine Rechner verbunden</p>
  236. </body></html>
  237. '''
  238. @app.get('/client/<client>')
  239. def forward_client(client):
  240. response=requests.get('http://'+client+':8080/')
  241. print(requests.get('http://'+client+':8080/'))
  242. if response.status_code == 200:
  243. return response.content
  244. else:
  245. return '''
  246. <!DOCTYPE html><html><head>
  247. <title>Keine Verbindung</title>
  248. </head><body>
  249. <p>Kein Anschluss unter dieser Himbeere</p>
  250. </body></html>
  251. '''
  252. @app.get('/cpu')
  253. def show_ids():
  254. starttime=time.time()
  255. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  256. myc=mydb.cursor(pymysql.cursors.DictCursor)
  257. myc.execute('select * from ids where sensor="CPU";')
  258. channels=myc.fetchall()
  259. myc.close()
  260. cpu_col=cm.rainbow(numpy.linspace(0,1,len(channels)))
  261. plt.figure(figsize=[6,8])
  262. mycursor=mydb.cursor()
  263. for j in range(len(channels)):
  264. mycursor.execute('select ((select max(time) from hourly_measures)-hourly_measures.time)/1000 as time,hourly_measures.value/1000 as value from hourly_measures join ids on ids.id=hourly_measures.id where ids.sensor="CPU" and ids.id='+str(channels[j]['id'])+';')
  265. row=numpy.array(mycursor.fetchall())
  266. plt.plot(row[:,0],row[:,1],color=cpu_col[j],label=channels[j]['varname']+'; '+channels[j]['device'])
  267. plt.legend()
  268. print(str(time.time()-starttime)+"s for fetching and display")
  269. plt.title('CPU Verlauf')
  270. plt.savefig("svg/cpu.svg")
  271. mycursor.close()
  272. mydb.close()
  273. if len(row)>0:
  274. return template('cputemp.tpl',measdata=row)
  275. @app.get('/svg/<svgfile>')
  276. def show_svg(svgfile):
  277. return static_file("svg/"+svgfile,root=abspath)
  278. @app.get('/graph/<mid>/<kind>')
  279. def show_graph(mid,kind):
  280. sqltable="hourly_measures"
  281. if(kind=="daily"):
  282. sqltable="daily_measures"
  283. starttime=time.time()
  284. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  285. mycursor=mydb.cursor()
  286. print(str(time.time()-starttime)+' sql open')
  287. nrow=mycursor.execute('select ((select max(time) from '+sqltable+')-'+sqltable+'.time)/3600000 as time,'+sqltable+'.value/1000 as value from '+sqltable+' where id=%s order by time',str(mid))
  288. if (nrow>0):
  289. row=numpy.array(mycursor.fetchall())
  290. print(str(time.time()-starttime)+' sql fetched')
  291. nirow=mycursor.execute('select id,varname,device,sensor,i2c from ids where id='+str(mid))
  292. gdata=mycursor.fetchall()
  293. mycursor.close()
  294. mydb.close()
  295. kanal_info={"varname":"","device":"","sensor":"","i2c":0,"count":nrow}
  296. for ids,varname,device,sensor,i2c in gdata:
  297. kanal_info["varname"]=varname
  298. kanal_info["device"]=device
  299. kanal_info["sensor"]=sensor
  300. kanal_info["i2c"]=int(i2c)
  301. print(str(time.time()-starttime)+' sql closed')
  302. plt.figure(figsize=[6,8])
  303. plt.plot(row[:,0],row[:,1])
  304. print(str(time.time()-starttime)+' picture')
  305. plt.savefig("svg/"+mid+".svg")
  306. print(str(time.time()-starttime)+' saved')
  307. if len(row)>0:
  308. return template('verlauf.tpl',measdata=row,mid=mid,kanal_info=kanal_info)
  309. else:
  310. return template('''
  311. <!DOCTYPE html><html><head>
  312. <title>Keine Daten</title>
  313. </head><body>
  314. <p>Die Daten verstecken sich.</p>
  315. <a href="/graph/{{mid}}/hourly">Letzte Stunde</a></br>
  316. <a href="/graph/{{mid}}/daily">Letzter Tag</a></br>
  317. </body></html>
  318. ''',mid=mid)
  319. @app.get('/graph/<mid>')
  320. def show_graph_short(mid):
  321. show_graph(mid,'hourly')
  322. @app.post('/data/<hash_id>')
  323. def dataimport(hash_id):
  324. # print(hash_id)
  325. timestart=time.time()
  326. # check if request comes from allowed ip
  327. if (request.remote_addr in clientlist):
  328. # hash must be the used gpg key id
  329. if (hash_id in clientlist[request.remote_addr]):
  330. # print("correct id")
  331. # check, if json is transmitted
  332. try:
  333. json_in=json.loads(request.json)
  334. # print(json_in)
  335. except:
  336. print("no json")
  337. else:
  338. print(time.time()-timestart)
  339. _thread.start_new_thread(analyse_jsonin,(json_in,hash_id,))
  340. else:
  341. print("wrong id")
  342. else:
  343. print("not allowed client address")
  344. run(app,host="",port=8081)
  345. mydb=pymysql.connect(read_default_file="~/.my.cnf",database="rasolar")
  346. #mycursor=mydb.cursor(pymysql.cursors.DictCursor)
  347. mycursor=mydb.cursor(pymysql.cursors.DictCursor)
  348. mycursor.execute('select gm.*,measures.value,ids.varname,ids.device,ids.sensor,ids.i2c,ids.sensorsub from (select id, max(time) as time,count(time) as count from measures group by id) gm join measures on measures.id=gm.id and measures.time=gm.time join ids on ids.id=gm.id where gm.time>= ((select max(time) from measures)-24*6400000);')
  349. row=mycursor.fetchall()
  350. mycursor.close()
  351. mydb.close()