Kotinäyttö

Tätä on nyt muutamina viimeviikkoina herätelty henkiin taas. Olen hukannut viimeisimmän uusimman version, joten liikkeelle lähdetään hyvin varhaisista alkeista.
Tällä kertaa grafiikkaa ei piirretä minkään kirjaston avustuksella, vaan natiiveilla html:n canvas-työkaluilla (tai sitten ei ollenkaan).
Nykyään osaan myös paremmin jaotella mitä tehdään palvelinpuolella ja mitä front-endin puolella selaimen javascriptillä, aikaisempaan sekasotkuun verrattuna.
Hankkeen tila
01/12/2025
Tämä on edistynyt nyt taas vaihteeksi. Kaikki aiemmin grafiikan piirtoon käytettävät kirjastot on heivattu helvettiin ja grafiikka piirretään täysin puhtaan VanillaJS:n & SVG toimesta. Erityisen hyvin lämpötilagraafi toimii nyt (KUVA PUUTTUU VIELÄ), sähkön hinnan ja kulutettujen eurojen esittämisestä olen luonnostellut ASCII-pohjaista palkkigrafiikkaa, mutta en tiedä onko se lopullinen ratkaisu.
Ehkä suurin ja tärkein läpimurto on ILP:n ohjaus. Sain aikaiseksi tilata IR-ledejä ja vastaanottimia, joiden avulla voidaan nauhoittaa kaukosäätimestä IR-signaalia ja lähettää sitä pumpulle. Sain nauhoitettua kaukosäätimestä oleellisimmat toiminnot, joita voidaan lähettää pumpulle funktion laheta_ir_komento() avulla (tässä ei vielä käytössä mutta toimivaksi trimmattu jo. HOX! Idea tai lähetysmekanismi ei ole oma ideani, mutta se on niin yksinkertainen ja nerokas ettei mikään muu ratkaisu voisi tulla kyseeseen!) TODO: Kirjoitetaan tarkempi kuvaus tuosta IR-hommasta + nauhoituksesta LIRCiä tai mitään muutakaan paskaa ei tarvita, vaan kaikki hoituu rasbianissa valmiina olevilla työkaluilla!
Tämä pumpun ohjaus on erityisesti sydämmemme asialla, koska asentaessani ilmanlämpöpumpun ilman pätevyyttä tai lupia, asensin sen niin vittumaiseen paikkaan ettei alhaalta tuvasta sisäyksikkö näe kaukosäädintä ja siksi parvelle nouseminen sitä operoimaan on aina inhottava velvoite. Toisaalta näin pienessä huoneen kokoisessa talossakaan ei kauheasti paikkoja ollutkaan valittavana.
Tällä hetkellä haasteellisinta on päättää käyttöliittymän tulevia linjauksia, miten vesi esitetään, teho, varaajan suunnitelma ja nyttemmin myös ilmanlämpöpumpun ohjaus. Sen lisäksi, että tämän avulla saadaan nyt etäkäyttö, saadaan se himmaamaan pyyntiä korkean sähkön hinnan aikaan. Pumpulle ja muulle on väkerretty textboxi, josta syötetään komentoja ja ajastuksia kaikkiin tämän eri toimintoihin. (parseri tekemättä !), olihan sekin oma työmaansa saada tuolta sivulta POST metodilla dataa palvelinpuolelle, vaan onnistui lopulta silti.
Myös ajastusten käsittelyssä on työnnetty kädet kunnolla paskaan ja luovuttu niistä timereistä joita edellisessä mallissa oli käytössä, kaikki pyritään hoitamaan itse. Myös HTTP-palvelimen kirjoittaminen on aloitettu, koska tuo pythonin moduulipalvelin vain jostain syystä vituttaa. Itse tekemällä saisin siitä sellaisen, joka avaa uuden säikeen vain sse-livestriimiä varten, tuossa nykyisessä joka requesti avataan omassa säikeessä. Lisäksi en edes ymmärrä täysin kuinka se [moduulipalvelin] toimii, joka aiheuttaa teknistä pahoinvointia.
###
- Molemmat vesimittarit toimivat
- ILP tehon mittaus toimii
- Ohjelmarakenne tehdään kivuttomasti taipumaan tulevaan varttimittaukseen
- Sähkömittarista tolpan kyljestä ei saavu enään pulsseja tänne
- Tätä varten on maastouduttava pöpelikköön etsimään katkeaako signaali tai 5V syöttö mittauskeskukseen, jossa LDR-vastus lukee sähkömittarin lediä
- 1-wire Lämpötila-anturit ovat aivan kadotuksissa mikä on mikäkin ja mitkä ovat niiden johdot
- Veden esilämmitys pihalla on osittain purettu + kuopattu kiinnostuksen loputtua
Palvelimen puoli
HUOMHUOM! Näitä ei kannata edes testata, koska toimivuus on spesifisesti tässä ympäristössä ja näillä antureilla sen lisäksi että kaikki on vielä kesken + levällään. Jääkäämme seuraamaan kehitystä.
import datetime import requests, json import time, sys import _thread import RPi.GPIO as GPIO from socketserver import ThreadingMixIn from http.server import BaseHTTPRequestHandler, HTTPServer import pprint import pigpio data = { 'sahkonKulutus' : {}, 'sahkonALV' : 1.255, 'sahkoVero' : 2.827515, 'sahkoSiirto' : 4.65, 'sahko' : { 'pulssien_vali' : 0, 'pulsseja_per_kwh' : 10000, 'lastTimestamp' : 0, 'nordPoolWaitTime' : time.time(), 'kokonaisPulssit' : 0, 'kokonaisHinta' : 0 }, 'pvmTanaan' : datetime.date.today().strftime('%Y-%m-%d'), 'nykyinenTunti' : 0, 'pvmHuomenna' : (datetime.datetime.today()+datetime.timedelta(days=+1)).strftime('%Y-%m-%d'), 'currTimestamp' : time.time(), 'cliKeskeytys' : False, 'meta' : { 'httpPyyntojaSaatu' : 0, 'broadcastedBytes' : 0, 'asiakkaitaPaikalla' : 0 }, 'restartTimes' : 0, 'ajoaikaSec' : 0, 'saikeetElossa' : { 'hengetarLastTimestamp' : time.time(), 'mittariLastTimestamp' : time.time() }, 'talletustiedosto' : "/home/tumple/tmpfs_CACHE/55talletustiedosto.txt", 'firstRun' : time.time(), 'dataStructVersion' : 24332724, # Jos tämän taulukon skeema muuttuu, ei vanha tiedosto ole enään käypä --> hylätään 'viivapiirturi' : [], 'lammotTmp' : {}, 'komentoTXTarea' : "", 'ilp' : { 'pulssien_vali' : 0, 'pulsseja_per_kwh' : 1000, 'lastTimestamp' : 0, 'kokonaisPulssit' : 0, 'kokonaisPulssitLast' : 0, 'kokonaisPulssitLastTimestamp' : time.time(), 'kokonaisHinta' : 0, 'tavoitelampo' : 21, 'defaultIRmap' : 'lammitys_fan4', 'currIRmap' : '' }, 'kvesi' : { 'pulssien_vali' : 0, 'lastTimestamp' : -1, 'kokonaisPulssit' : 0, 'kokonaisPulssitLast' : 0 }, 'lvesi' : { 'pulssien_vali' : 0, 'lastTimestamp' : 0, 'varaajanKap' : 99000, # 99000 pulssia = 150 litraa 'varaajanHavioteho' : 0.15, # kW 'varaajanTeho' : 1.8, # kW 'varaajanStatus' : 99000, 'kylma_vesi_c' : 4, 'kokonaisPulssit' : 0, 'kokonaisPulssitLast' : 0, 'lampimanVedenArvo' : 0, 'varaajanSuunnitelma' : {} }, 'vedenKayttokerrat' : [], 'sahkonHinta' : {}, 'lastLogs' : [], 'errorLogs' : [] } #28-3c05f6482521 28-3c12e381c7a4 28-3ca9f648c262 28-3cf5f648648b devices = { #/sys/bus/w1/devices 'huone' : '/sys/bus/w1/devices/28-3cf5f648648b/w1_slave', 'parvi' : '/sys/bus/w1/devices/28-3c05f6482521/w1_slave', 'ulko' : '/sys/bus/w1/devices/28-3c12e381c7a4/w1_slave', 'sisa_puhallus' : '/sys/bus/w1/devices/28-3ca9f648c262/w1_slave', 'kierron_meno' : '/sys/bus/w1/devices/28-3ce10457c47b/w1_slave', 'kierron_paluu' : '/sys/bus/w1/devices/28-3ce1045723c3/w1_slave', 'ponton_lampotila' : '/sys/bus/w1/devices/28-3ce10457e032/w1_slave' } irCodes = { 'lammitys_fan1' : {}, 'lammitys_fan2' : {}, 'lammitys_fan3' : {}, 'lammitys_fan4' : {}, 'lammitys_fan5' : {}, 'lammitys_fan6' : {}, 'viilennys_fan5' : {}, 'viilennys_fan4' : {}, 'viilennys_fan3' : {}, 'viilennys_fan2' : {}, 'viilennys_fan1' : {}, 'sammutus' : {}, 'kosteuden_poisto' : {} } def carrier(gpio, frequency, micros): """ Generate carrier square wave. """ wf = [] cycle = 1000.0 / frequency cycles = int(round(micros/cycle)) on = int(round(cycle / 2.0)) sofar = 0 for c in range(cycles): target = int(round((c+1)*cycle)) sofar += on off = target - sofar sofar += off wf.append(pigpio.pulse(1<<gpio, 0, on)) wf.append(pigpio.pulse(0, 1<<gpio, off)) return wf def laheta_ir_komento(code): GAP_MS = 100 GAP_S = GAP_MS / 1000.0 pi = pigpio.pi() # Connect to Pi. if not pi.connected: exit(0) pi.set_mode(pinnit['irTx'], pigpio.OUTPUT) # IR TX connected to this GPIO. pi.wave_add_new() emit_time = time.time() #code = str marks_wid = {} spaces_wid = {} wave = [0]*len(code) for i in range(0, len(code)): ci = code[i] if i & 1: # Space if ci not in spaces_wid: pi.wave_add_generic([pigpio.pulse(0, 0, ci)]) spaces_wid[ci] = pi.wave_create() wave[i] = spaces_wid[ci] else: # Mark if ci not in marks_wid: wf = carrier(pinnit['irTx'], 38, ci) pi.wave_add_generic(wf) marks_wid[ci] = pi.wave_create() wave[i] = marks_wid[ci] delay = emit_time - time.time() if delay > 0.0: time.sleep(delay) pi.wave_chain(wave) while pi.wave_tx_busy(): time.sleep(0.002) emit_time = time.time() + GAP_S for i in marks_wid: pi.wave_delete(marks_wid[i]) marks_wid = {} for i in spaces_wid: pi.wave_delete(spaces_wid[i]) spaces_wid = {} pi.stop() # Disconnect from Pi. def lue_ir_codet_muistiin(): for tiedosto in irCodes: with open(tiedosto, "r") as file: irCodes[tiedosto]['data'] = json.loads(file.read()) file.close() # LÄMPÖMITTARIN SÄIE KUTSUU MINUUTIN VÄLEIN TÄTÄ FUNKTIOTA # YRITETÄÄN LUKEA USEAMPAAN KERTAAN JOS EI ONNISTU def lampomittari_saie_1min(): data['saikeetElossa']['mittariLastTimestamp'] = data['currTimestamp'] retries = 6 while lue_anturi('huone') == -999 and retries > 0: retries -= 1 retries = 6 while lue_anturi('parvi') == -999 and retries > 0: retries -= 1 retries = 6 while lue_anturi('sisa_puhallus') == -999 and retries > 0: retries -= 1 retries = 6 while lue_anturi('ulko') == -999 and retries > 0: retries -= 1 return -1 # LÄMPÖMITTAREIDEN LUKUUN VARATTU OMA SÄIE def lue_lampomittareita(): logi("Lämpömittareiden luku säie käynnistyy!") lastTimestamp = 0 #time.time() while True: if data['cliKeskeytys']: return 0 time.sleep(1) if lastTimestamp + 60 < time.time(): lastTimestamp = time.time() lampomittari_saie_1min() return 0 def lue_anturi (device): ##logi("AVATAAN LAITE: " + device) # huone, parvi, sisa_puhallus #time.sleep(1) try: with open(devices[device], 'r') as onewire: time.sleep(0.5) lines = onewire.readlines() onewire.close() except: logi("lue_anturi(): EI LÖYDY LAITETIEDOSTOA | device: " + device, 'err') #logi("lue_anturi: " + device) time.sleep(1) data['lammotTmp'][device] = -999 return -999 if lines == False or len(lines) != 2: logi("parsi_lampotila: EI PARSITTAVAA") print(lines) data['lammotTmp'][device] = -999 time.sleep(1) return -999 logi(str(lines)) if (lines[0][-4 : len(lines[0])-1]) != 'YES': logi("parsi_lampotila: TEMP IS NOT VALID") time.sleep(1) data['lammotTmp'][device] = -999 return -999 #print(lines[0][-3 : len(lines[0])]) lampotila = round(int(lines[1][lines[1].find("t=")+2:len(lines[1])]) / 1000, 1) data['lammotTmp'][device] = lampotila return 1 class handler(BaseHTTPRequestHandler): global data #def do_OPTIONS(self): #self.send_response(200) #self.send_header("Access-Control-Allow-Origin", "*") #self.send_header("Access-Control-Allow-Credentials", "true") #self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') #self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept") #self.end_headers() def do_GET(self): data['meta']['httpPyyntojaSaatu'] += 1 #logi("GEttiä kutsutaan!") if self.path.endswith('/kaikki_data'): #Lähettää koko data arrayn json dumppina self.send_response(200) #self.send_header("Access-Control-Allow-Origin", "*") #self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Origin, Accept") self.send_header('Content-type', 'application/json; charset=UTF-8') self.end_headers() output = str.encode(json.dumps(data)) data['meta']['broadcastedBytes'] += len(output) self.wfile.write(output) logi("/kaikki_data tarjoiltu") return logi("yhteys ei katkea") elif self.path.endswith('/debug'): self.send_response(200) self.send_header('Content-type', 'text/plain; charset=UTF-8') #self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() output = "<pre>" output += pprint.pformat(data, indent=1,sort_dicts=False) output += pprint.pformat(irCodes, indent=1,sort_dicts=False) #output += data output = str.encode(output) data['meta']['broadcastedBytes'] += len(output) # ei toimi self.wfile.write(output) return elif self.path.endswith('/etusivu.html'): self.send_response(200) self.send_header('Content-type', 'text/html; charset=UTF-8') self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() f = open("etusivu.html", "r") output = str.encode(f.read()) f.close() data['meta']['broadcastedBytes'] += len(output) self.wfile.write(output) return elif self.path.endswith('/livestream'): data['meta']['asiakkaitaPaikalla'] += 1 self.send_response(200) self.send_header('Content-type', 'text/event-stream') self.end_headers() prevLastLogTimestamp = time.time() fullOutput = {'aiemminLahetetyt' : {}} firstRun = True while True: #time.sleep(1) if data['cliKeskeytys']: return lahetettavastLastlogit = "" for x in data['lastLogs']: if prevLastLogTimestamp < x['timestamp']: lahetettavastLastlogit += "\n" + x['str'] prevLastLogTimestamp = x['timestamp'] fullOutput = muodosta_sse_data({'lastLogStr' : lahetettavastLastlogit, 'aiemminLahetetyt' : fullOutput['aiemminLahetetyt'], 'firstRun' : firstRun}) output = str.encode(fullOutput['outputStr']) data['meta']['broadcastedBytes'] += len(output) try: self.wfile.write(output) except: data['meta']['asiakkaitaPaikalla'] -= 0 return time.sleep(1) firstRun = False data['meta']['asiakkaitaPaikalla'] -= 0 return else: self.send_response(200) self.send_header('Content-type', 'text/html; charset=UTF-8') self.end_headers() output = "<h1>VÄÄRÄ YHTEYSPISTE</h1>" output = str.encode(output) data['meta']['broadcastedBytes'] += len(output) self.wfile.write(output) return def do_POST(self): if self.path.endswith('/post'): content_length = int(self.headers['Content-Length']) # <--- Gets the size of data post_data = self.rfile.read(content_length) # <--- Gets the data itself data['komentoTXTarea'] = post_data.decode("utf-8") #logi(post_data) print(post_data) logi("Vastaanotettiin POST-DATA: " + str(content_length) + " merkkiä") def muodosta_sse_data(argumentit): # ls lp lh lastLogStr = argumentit['lastLogStr'] sseArr = { 'sahkoDelay' : data['sahko']['pulssien_vali'], 'dataaLahetetty' : data['meta']['broadcastedBytes'], 'asiakkaitaPaikalla': data['meta']['asiakkaitaPaikalla'], 'httpPyyntojaSaatu' : data['meta']['httpPyyntojaSaatu'] } if lastLogStr != "": sseArr.update({'lastLogs' : lastLogStr}) if data['kvesi']['pulssien_vali'] > 0 or data['lvesi']['pulssien_vali'] > 0 or argumentit['firstRun']: sseArr.update({'kvesiDelay' : data['kvesi']['pulssien_vali']}) sseArr.update({'kPulssit' : data['kvesi']['kokonaisPulssit']}) sseArr.update({'lvesiDelay' : data['lvesi']['pulssien_vali']}) sseArr.update({'varaajanStatus' : data['lvesi']['varaajanStatus']}) sseArr.update({'varaajanKap' : data['lvesi']['varaajanKap']}) sseArr.update({'lPulssit' : data['lvesi']['kokonaisPulssit']}) if data['ilp']['pulssien_vali'] > 0: sseArr.update({'ilpDelay' : data['ilp']['pulssien_vali']}) sseArr.update({'ilpPulssit' : data['ilp']['kokonaisPulssit']}) #if 'ls' in argumentit['aiemminLahetetyt']: #if data['lammotTmp']['sisa_puhallus'] != argumentit['aiemminLaheteyt']['ls']: sseArr.update({'ls' : data['lammotTmp']['sisa_puhallus']}) #if 'lp' in argumentit['aiemminLahetetyt']: #if data['lammotTmp']['parvi'] != argumentit['aiemminLaheteyt']['lp']: sseArr.update({'lp' : data['lammotTmp']['parvi']}) #if 'lh' in argumentit['aiemminLahetetyt']: #if data['lammotTmp']['huone'] != argumentit['aiemminLaheteyt']['lh']: sseArr.update({'lh' : data['lammotTmp']['huone']}) sseArr.update({'ul' : data['lammotTmp']['ulko']}) #if 'komentoTXTarea' not in argumentit['aiemminLahetetyt']: # if argumentit['aiemminLahetetyt']['komentoTXTarea'] != data['komentoTXTarea']: sseArr.update({'komentoTXTarea' : data['komentoTXTarea']}) #sseArr.update({'lp' : data['lammotTmp']['parvi']}) #sseArr.update({'lh' : data['lammotTmp']['huone']}) output = 'data: ' output += json.dumps(sseArr) output += "\n\n" return {'outputStr' : output, 'aiemminLahetetyt' : sseArr} def paivita_aika(): global data now = datetime.datetime.now() data['pvmTanaan'] = datetime.date.today().strftime('%Y-%m-%d') data['nykyinenTunti'] = now.hour data['currTimestamp'] = time.time() def hae_sahkonhinta(paiva = datetime.date.today().strftime('%Y-%m-%d')): if paiva in data["sahkonHinta"]: logi(paiva + " HINTA ON JO LISTALLA", 'err') return -1 if time.time() < data['sahko']['nordPoolWaitTime']: logi("ESTETTY HINTOJEN HAKU LIIAN USEIN", "err") return -1 data['sahko']['nordPoolWaitTime'] = time.time() + (60 * 5) headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36'} osoite = "https://dataportal-api.nordpoolgroup.com/api/DayAheadPrices?date=" + paiva + "&market=DayAhead&deliveryArea=FI¤cy=EUR" try: url = requests.get(osoite, headers = headers) except: logi("SÄHKÖN HINTAA EI SAA HAETTUA: requests.get()", 'err') return -1 try: vastaus = json.loads(url.text) except: logi("SÄHKÖN HINTAA EI SAA HAETTUA: json.loads()", 'err') return -1 if 'deliveryDateCET' not in vastaus: logi("deliveryDateCET ei löydy vastauksesta", 'err') return -1 if vastaus["deliveryDateCET"] != paiva: logi("deliveryDateCET ei täsmää päivämäärään", 'err') return -1 data['sahkonHinta'][paiva] = {'hinta' : [], 'kulutus' : [], 'ilpKulutus' : [], 'kylmavesi' : [], 'kuumavesi' : [] } tunti = 0 for x in vastaus["multiAreaEntries"]: hinta = x["entryPerArea"]["FI"] hinta = round((((hinta / 10) + data['sahkoVero']) * data["sahkonALV"]) + data["sahkoSiirto"], 2) #print(hinta) data['sahkonHinta'][paiva]['hinta'].append(hinta) data['sahkonHinta'][paiva]['kulutus'].append(0) data['sahkonHinta'][paiva]['ilpKulutus'].append(0) data['sahkonHinta'][paiva]['kylmavesi'].append(0) data['sahkonHinta'][paiva]['kuumavesi'].append(0) return 1 def saatana(e): return e['hinta'] def suunnittele_varaaja(): #hinnat.append({'tunti' : x, 'hinta' : random.randrange(3, 49)}) hinnat = [] suunnitelma = {} #Jos edes tämän päiväisiä sähkönhintoja ei ole, niin turha tehdä mitään if data['pvmTanaan'] not in data["sahkonHinta"]: return 0 for x in range(data['nykyinenTunti'], 24): hinnat.append({'tunti' : x, 'hinta' : data['sahkonHinta'][data['pvmTanaan']]['hinta'][x]}) print(x) huominen = (datetime.datetime.today()+datetime.timedelta(days=+1)).strftime('%Y-%m-%d') if huominen in data["sahkonHinta"]: for i in range(0, len(data["sahkonHinta"][huominen]['hinta'])): hinnat.append({'tunti' : i + x, 'hinta' : data['sahkonHinta'][huominen]['hinta'][i]}) #print(hinnat) myMin = min(d['hinta'] for d in hinnat) #print(myMin) kapasiteetti = data['lvesi']['varaajanKap'] lammitysteho = data['lvesi']['varaajanTeho'] havioteho = 0.5 #data['lvesi']['varaajanHavioteho'] haviotehoPulssia = ((havioteho * 60) / (85 - 4) / 4.2) * 11 * 60 * 60 print(haviotehoPulssia) time.sleep(1) status = data['lvesi']['varaajanStatus'] heatPulsesPerHour = ((lammitysteho * 60) / (85 - 4) / 4.2) * 11 * 60 * 60 lammitysTarvePulssia = kapasiteetti - status lammitysTarveTuntia = round(lammitysTarvePulssia / heatPulsesPerHour)+1 hinnat = sorted(hinnat, key=saatana) i = 0 for x, val in enumerate(hinnat): print(i) tila = False status = data['lvesi']['varaajanStatus'] - haviotehoPulssia * (val['tunti'] - data['nykyinenTunti']) #status -= 500 #or x['hinta'] < myMin * 1.1 if i <= lammitysTarveTuntia or val['hinta'] < myMin * 1.2: tila = True status += heatPulsesPerHour #i += 1 if status > kapasiteetti: status = kapasiteetti tmpArr = {'hinta' : val['hinta'], 'tunti' : val['tunti'], 'status' : round(status), 'tila' : tila} suunnitelma[val['tunti']] = tmpArr i += 1 data['lvesi']['varaajanSuunnitelma'] = suunnitelma #print(pprint.pformat(suunnitelma, indent=1,sort_dicts=False)) def vesimittari(channel): tmpAika = time.time() # Keskeytysten ajankohta on tarkka, joten paivita_aika() tarjoamia lukuja ei voi täällä käyttää if channel == pinnit['sahko']: # SÄHKÖMITTARI logi("SÄHKÖMITTARIPULSSI") data['sahko']['pulssien_vali'] = tmpAika - data['sahko']['lastTimestamp'] data['sahko']['lastTimestamp'] = tmpAika data['sahkonHinta'][data['pvmTanaan']]['kulutus'][data['nykyinenTunti']] += 1 data['sahko']['kokonaisPulssit'] += 1 return 0 if channel == pinnit['ilpSahko']: # ILP MITTARI data['ilp']['pulssien_vali'] = tmpAika - data['ilp']['lastTimestamp'] data['ilp']['lastTimestamp'] = tmpAika data['sahkonHinta'][data['pvmTanaan']]['ilpKulutus'][data['nykyinenTunti']] += 1 data['ilp']['kokonaisPulssit'] += 1 #logi("ILPPULSSI") return 0 if channel == pinnit['kvesi']: # KYLMÄN VEDEN MITTARI kokonaisPulssit data['kvesi']['pulssien_vali'] = tmpAika - data['kvesi']['lastTimestamp'] data['kvesi']['lastTimestamp'] = tmpAika data['sahkonHinta'][data['pvmTanaan']]['kylmavesi'][data['nykyinenTunti']] += 1 data['kvesi']['kokonaisPulssit'] += 1 #logi("VESIPULSSI") return 0 if channel == pinnit['lvesi']: # KUUMAN VEDEN MITTARI data['lvesi']['pulssien_vali'] = tmpAika - data['lvesi']['lastTimestamp'] data['lvesi']['lastTimestamp'] = tmpAika data['sahkonHinta'][data['pvmTanaan']]['kuumavesi'][data['nykyinenTunti']] += 1 data['lvesi']['varaajanStatus'] -= 1 data['lvesi']['kokonaisPulssit'] += 1 #logi("KUUMAVESIPULSSI") return 0 #Kun virtaus pysähtyy, ei keskeytystä enään tule = ei jätetä edellistä taajuuslukemaa päälle def pysayta_mittari(asap = False): global data # Huolehditaan käynnistettäessä luvut nollille varmasti if asap: data['kvesi']['pulssien_vali'] = -1 data['lvesi']['pulssien_vali'] = -1 if data['currTimestamp'] - data['kvesi']['lastTimestamp'] > 1: data['kvesi']['pulssien_vali'] = -1 if data['currTimestamp'] - data['lvesi']['lastTimestamp'] > 1: data['lvesi']['pulssien_vali'] = -1 if data['currTimestamp'] - data['ilp']['lastTimestamp'] > 360: data['ilp']['pulssien_vali'] = -1 if data['currTimestamp'] - data['sahko']['lastTimestamp'] > 1: data['sahko']['pulssien_vali'] = 0 #if time.time() - data['sahko']['lastTimestamp'] > 1: # data['sahko']['pulssien_vali'] = False def siivoa(toiminto): if toiminto == "siivoaLastlog": if len(data['lastLogs']) < 30: #print("ALLE 10") return 0 #print("yli 10") while len(data['lastLogs']) > 20: data['lastLogs'].pop(0) return 0 if toiminto == "siivoaViivapiirturi": if len(data['viivapiirturi']) < 60 * 24: return 0 while len(data['viivapiirturi']) > 60 * 24: data['viivapiirturi'].pop(0) return 0 if toiminto == "siivoaVedenKayttokerrat": if len(data['vedenKayttokerrat']) < 50: return 0 while len(data['vedenKayttokerrat']) > 50: data['vedenKayttokerrat'].pop(0) return 0 def kodin_hengetar(): global data logi("Kodin hengetär täällä!") data['saikeetElossa']['hengetarLastTimestamp'] = data['currTimestamp'] tt = {'1minWait': 0, '5minWait' : 0, '15minWait': 0} while True: if data['cliKeskeytys']: return 0 paivita_aika() #logi("Kodin hengetär täällä!") pysayta_mittari() if tt['1minWait'] < data['currTimestamp']: #logi("1 min sykli aktivoitui!") ajastus_1min() tt['1minWait'] = data['currTimestamp'] + 60 continue if tt['5minWait'] < data['currTimestamp']: logi("5 min sykli aktivoitui!") ajastus_5min() tt['5minWait'] = data['currTimestamp'] + 60 * 5 continue if tt['15minWait'] < data['currTimestamp']: logi("15 min sykli aktivoitui!") ajastus_15min() tt['15minWait'] = data['currTimestamp'] + 60 * 15 continue kirjaa_veden_kayttokerrat() time.sleep(0.005) return 0 def kirjaa_veden_kayttokerrat(): if data['kvesi']['pulssien_vali'] > 0: #Jos vettä kulutetaan if 'vedenKayttokerratTmp' not in data: #ja tmpmuuttujaa ei ole vielä olemassa data['vedenKayttokerratTmp'] = {'alku' : data['currTimestamp'], 'kpulssitStart' : data['kvesi']['kokonaisPulssit'], 'lpulssitStart' : data['lvesi']['kokonaisPulssit']} return 0 #Tähän päästään vain jos mittarit eivät rekisteröi virtausta # Eli hana on jo suljettu, tmpmuuttuja on luotu ylempänä if 'vedenKayttokerratTmp' in data: # Alle 10 pulssin häiriöitä ei rekisteröidä ollenkaan if data['kvesi']['kokonaisPulssit'] - data['vedenKayttokerratTmp']['kpulssitStart'] < 10: del data['vedenKayttokerratTmp'] logi("HARHA VEDENKÄYTÖSSÄ") return 0 #Kirjataan veden käyttökerta lopulliseen listaan data['vedenKayttokerrat'].append({ 'alku' : round(data['vedenKayttokerratTmp']['alku']), 'loppu' : round(data['currTimestamp']), 'kpulssit' : data['kvesi']['kokonaisPulssit'] - data['vedenKayttokerratTmp']['kpulssitStart'], 'lpulssit' : data['lvesi']['kokonaisPulssit'] - data['vedenKayttokerratTmp']['lpulssitStart']}) del data['vedenKayttokerratTmp'] #Poistetaan tmpmuuttuja, josta seuraavalla kerralla havaitaan uusi vedenkäyttösessio def toimeenpane_komento_txtarea(): if len(data['komentoTXTarea']) < 1: return 0 arr = data['komentoTXTarea'].split("\n") return 0 for k in arr: if arr[k][0] == "#": continue return 1 def ajastus_1min(): global data # Otetaan huominen päivä palvelimen päästä, koska en nyt tähän hänään keksi miten tuo hoidetaan JS:llä data['pvmHuomenna'] = (datetime.datetime.today()+datetime.timedelta(days=+1)).strftime('%Y-%m-%d') data['saikeetElossa']['hengetarLastTimestamp'] = data['currTimestamp'] #HINNAT TÄNÄÄN: if data['pvmTanaan'] not in data['sahkonHinta']: if hae_sahkonhinta(data['pvmTanaan']) > 0: logi("Haettiin tämän päivän hinnat!") #HINNAT HUOMENNA: if data['nykyinenTunti'] > 13: huominen = (datetime.datetime.today()+datetime.timedelta(days=+1)).strftime('%Y-%m-%d') if huominen not in data['sahkonHinta']: if (hae_sahkonhinta(huominen) > 0): logi("Haettiin huomisen hinnat!") # Lisätään varaajaan joka minuutti lämmitystehon mukainen määrä pulsseja # Ellei varaaja ole jo täynnä if data['lvesi']['varaajanStatus'] < data['lvesi']['varaajanKap']: heatDiff = 85 - data['lvesi']['kylma_vesi_c'] heatPulsesPerMin = ((data['lvesi']['varaajanTeho'] * 60) / heatDiff / 4.2) * 11 * 60 data['lvesi']['varaajanStatus'] += round(heatPulsesPerMin) #Kuluvan minuutin keskimääräinen virtaus tai teho # Lisätään vain jos muutoksia on tapahtunut tmpArr = {} if data['ilp']['kokonaisPulssit'] != data['ilp']['kokonaisPulssitLast']: tmpArr['ilp'] = data['ilp']['kokonaisPulssit'] - data['ilp']['kokonaisPulssitLast'] data['ilp']['kokonaisPulssitLast'] = data['ilp']['kokonaisPulssit'] #Summataan nykyisen tunnin sähkön hinnalla kokonaishintaa myös tällä tavalla: data['ilp']['kokonaisHinta'] += round((tmpArr['ilp'] / 1000) * data['sahkonHinta'][data['pvmTanaan']]['hinta'][data['nykyinenTunti']]) if data['kvesi']['kokonaisPulssit'] != data['kvesi']['kokonaisPulssitLast']: tmpArr['k'] = data['kvesi']['kokonaisPulssit'] - data['kvesi']['kokonaisPulssitLast'] data['kvesi']['kokonaisPulssitLast'] = data['kvesi']['kokonaisPulssit'] if data['lvesi']['kokonaisPulssit'] != data['lvesi']['kokonaisPulssitLast']: tmpArr['l'] = data['lvesi']['kokonaisPulssit'] - data['lvesi']['kokonaisPulssitLast'] data['lvesi']['kokonaisPulssitLast'] = data['lvesi']['kokonaisPulssit'] # data['lvesi']['lampimanVedenArvo'] += Nyut ei pää toimi näin hankalille hommille # LÄMPÖMITTARIN LUKEMAT MYÖS VIIVAPIIRTURIIN if 'sisa_puhallus' in data['lammotTmp']: if data['lammotTmp']['sisa_puhallus'] != -999: tmpArr['ls'] = data['lammotTmp']['sisa_puhallus'] if 'parvi' in data['lammotTmp']: if data['lammotTmp']['parvi'] != -999: tmpArr['lp'] = data['lammotTmp']['parvi'] if 'huone' in data['lammotTmp']: if data['lammotTmp']['huone'] != -999: tmpArr['lh'] = data['lammotTmp']['huone'] if 'ulko' in data['lammotTmp']: if data['lammotTmp']['ulko'] != -999: tmpArr['ul'] = data['lammotTmp']['ulko'] #if len(tmpArr) < 1: # tmpArr = False data['viivapiirturi'].append(tmpArr) suunnittele_varaaja() toimeenpane_komento_txtarea() return 0 def ajastus_5min(): siivoa("siivoaLastlog") siivoa("siivoaViivapiirturi") siivoa("siivoaVedenKayttokerrat") return 0 def ajastus_15min(): talleta_tiedostoon() return 0 def logi(txt, error = ""): logStr = "[" + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "] " + txt logArr = {"timestamp" : round(time.time()), "str" : logStr } print(logStr) data['lastLogs'].append(logArr) if error == "err": data['errorLogs'].append(logArr) time.sleep(1/1000) def talleta_tiedostoon(): data['ajoaikaSec'] += time.time() - start logi("tallennetaan data") file = open(data['talletustiedosto'], "w") file.write(json.dumps(data)) file.close() logi("Tallennettiin tiedosto") class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): logi("ThreadedHTTPServer käynnistyy") """Handle requests in a separate thread.""" def create_server(): port = 8080 global data httpd = ThreadedHTTPServer(("0.0.0.0", port), handler) logi("HTTP KUUNTELEE PORTISSA " + str(port)) httpd.serve_forever() # TÄTÄ KÄYTETÄÄN VAIN DEBUGGAUKSEEN/SIMULOINTIIN, JOS TODELLISTA SIGNAALIA EI TULE def keskeytyksien_simulointi(): logi("Simulointisäie käynnistyi!") time.sleep(5) while True: if data['cliKeskeytys']: return 0 vesimittari(17) time.sleep(0.1) #print(hae_sahkonhinta()) #hae_sahkonhinta("2024-11-30") pinnit = {'sahko' : 23, # sähkömittari 'ilpSahko' : 27, # ILP mittari 'lvesi' : 26, # KUUMAVESI 'kvesi' : 24, # KYLMÄVESI 'irTx' : 19 # IR LÄHETYS } GPIO.setmode(GPIO.BCM) # SÄHKÖMITTARI GPIO.setup(pinnit['sahko'], GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(pinnit['sahko'], GPIO.FALLING, bouncetime=2) GPIO.add_event_callback(pinnit['sahko'], vesimittari) # ILP MITTARI GPIO.setup(pinnit['ilpSahko'], GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(pinnit['ilpSahko'], GPIO.FALLING, bouncetime=30) GPIO.add_event_callback(pinnit['ilpSahko'], vesimittari) # KUUMAN VEDEN MITTARI GPIO.setup(pinnit['lvesi'], GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(pinnit['lvesi'], GPIO.FALLING, bouncetime=2) GPIO.add_event_callback(pinnit['lvesi'], vesimittari) # KYLMÄN VEDEN MITTARI GPIO.setup(pinnit['kvesi'], GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(pinnit['kvesi'], GPIO.FALLING, bouncetime=2) GPIO.add_event_callback(pinnit['kvesi'], vesimittari) #sys.exit() if 'skip_file' in sys.argv: data['dataStructVersion'] = time.time() try: #Jos data array on aiemmin dumpattu tiedostoon talteen with open(data['talletustiedosto'], "r") as file: logi("TIEDOSTO LÖYTYI") dataTmp = json.loads(file.read()) file.close() if dataTmp['dataStructVersion'] == data['dataStructVersion']: data = dataTmp del dataTmp logi("TIEDOSTO ON VALIDI") else: del dataTmp logi("----- TIEDOSTO ON EPÄVALIDI --------") except: logi("Ei tiedostoa") data['cliKeskeytys'] = False data['restartTimes'] += 1 paivita_aika() pysayta_mittari(True) #pysayta mittari ilman timeouttia == asap data['saikeetElossa']['hengetarLastTimestamp'] = data['currTimestamp'] _thread.start_new_thread(kodin_hengetar, tuple()) data['saikeetElossa']['mittariLastTimestamp'] = data['currTimestamp'] _thread.start_new_thread(lue_lampomittareita, tuple()) _thread.start_new_thread(create_server, tuple()) #_thread.start_new_thread(keskeytyksien_simulointi, tuple()) #print(data) lue_ir_codet_muistiin() start = time.time() try: while True: paivita_aika() time.sleep(1) if ((data['saikeetElossa']['hengetarLastTimestamp'] + (60 * 2)) < data['currTimestamp']) or (data['saikeetElossa']['mittariLastTimestamp'] + (60 * 2) < data['currTimestamp'] ): data['cliKeskeytys'] = True time.sleep(4) logi("--------- JOMPI KUMPI SÄIE ON KUOLLUT ------- ", "err") sys.exit() except KeyboardInterrupt: data['cliKeskeytys'] = True time.sleep(3) talleta_tiedostoon() logi("SAMMUTETAAN")
Front-end puoli
<!DOCTYPE html> <html lang="fi"> <head> <title>aaa</title> <style> .sin { color: blue; } .pun { color: red; } .vih { color: green; } .mag { color: magenta; } .pystyyn { transform: rotate(-180deg); writing-mode: vertical-rl; font-size: xx-small; /*font-family: Arial, monospace;*/ } </style> </head> <body> <div style="text-align: center;" id="tehoJaHinta"></div> <div style="border: 1ox solid black;"> <span id="ilp"></span> <span id="ilpKokonaisKulutus"></span> </div> <div style="border: 1px dotted pink; background-color: #e8cce0;"> Lämmöt: <span id="ls"></span><span id="lp"></span><span id="lh"></span><span id="ul"></span><br> <details><summary>Näytä ajastukset ja komennot</summary> <form method="post" id="form-propt" name="form-propt" onsubmit="submitFormFetch(); return false;" > <textarea rows="10" cols="50" id="komentoTXTarea" name="komentoTXTarea" style="width: 85%; height: 100px;"></textarea><br> <input type="submit" id="submit" name="submit" value="submit"> </form> </details> </div> <div style="float: right;"> <div style="border: 1px dotted blue;"> <h3>Veden kokonaiskulutus</h3> <div id="vedenKokonaiskulutus"></div> <canvas id="varaajan_tila" width="200" height="30" style="border:1px solid #d3d3d3; "></canvas> </div> <div style="border: 1px solid black;" id="ajonaikaisetTiedot"></div> <!-- overflow: auto; display: block; --> </div> <div id="kulutusASCII" style=" padding-bottom: 20px;flex-direction: row;height: 400px; width: 95%; display: flex; font-size: xx-small;overflow: auto; font-family: monospace; white-space: pre;"></div> <div id="kulutusASCIIpaivittain" style=" padding-bottom: 20px;flex-direction: row;height: 400px; width: 95%; display: flex; font-size: xx-small;overflow: auto; font-family: monospace; white-space: pre;"></div> <!-- <div id="sahkonHintaSVG" style="border: 1px solid black; width: 700px; height: 300px;"></div> --> <div> <button onclick="lampoSVGrange(60)">60 min</button> <button onclick="lampoSVGrange(60*8)">8 h</button> <button onclick="lampoSVGrange(60*24)">24 h</button> </div> <div id="viivapiirturiSVG" style="border: 1px solid black; width: 700px; height: 300px;"></div> <!--<div> <canvas id="lampotilaCanvas" width="600" height="150" style="border:1px solid #000000;"></canvas></div>--> <div> <canvas id="sahkonHintaCanvas" width="600" height="150" style="border:1px solid #000000;"></canvas></div> <div style="border: 1px solid black; overflow: scroll; height: 150px; width: 600px;"><h3>Veden käyttökerrat</h3><p id="vedenKayttokerrat"></p></div> <div> <canvas id="vedenKayttokerratCanvas" width="600" height="150" style="border:1px solid #000000;"></canvas></div> <div style="margin: 10px; padding: 10px; border: 1px solid black;"><textarea style="width: 80%; height: 400px;" rows="20" cols="20" id="lastLogsArea"></textarea></div> </body> </html> <script> "use strict"; var sets = {'pulsseja_per_kwh' : 0, 'ilp_pulsseja_per_kwh' : 0, 'nykyinenHinta' : 0, 'pvmTanaan' : 0, 'nykyinenTunti' : 0}; var pubs = {'nykyinenTunti' : 0, 'lampoSVGslice' : 60 }; var allData = {}; function lampoSVGrange(mins) { pubs['lampoSVGslice'] = mins; piirra_lampotila_svg(allData['viivapiirturi']); } function piirra_sahkon_hinta_svg(dataArr) { let str = ""; str += "<svg width='700' height='300' xmlns='http://www.w3.org/2000/svg'>\ṇ"; let i = 0, pituus = 0, lMargin = 40, rSet = []; console.log(dataArr); dataArr = dataArr[0]; pituus = dataArr.length; let width = 700; let step = (width - lMargin) / pituus; let keskihinta = dataArr.reduce((a, b) => a + b) / pituus; let hMax = Math.max(...dataArr); let hMin = Math.min(...dataArr); let hFact = 270 / (hMax); let yMax = 280; rSet['keskihinta'] = (yMax - (hFact * keskihinta)).toFixed(0); for (i = 0; i < pituus; i++) { rSet['xPos'] = i*step + lMargin; rSet['value'] = ((hFact * dataArr[i])).toFixed(0); rSet['yPos'] = yMax - rSet['value']; str += `<text x="${rSet['xPos']}" y="${300}" fill="red">${i}</text>\n`; str += `<rect width="15" height="${rSet['value']}" x="${rSet['xPos']}" y="${rSet['yPos']}" fill="blue" />\n`; } str += `<line x1="${0}" y1="${rSet['keskihinta']}" x2="${700}" y2="${rSet['keskihinta']}" style="stroke:red;stroke-width:2" />\n`; str += "</svg>\n\n"; //console.log(str); document.getElementById("sahkonHintaSVG").innerHTML = str; } function luo_X_label(labelArr) { labelArr.reverse(); let i; let out = `<svg x="10" y="10">\n`; let stepSize = 700 / labelArr.length; } function get_minMax(arr) { const min = Math.min(...arr); const max = Math.max(...arr); const index_min = arr.indexOf(min); const index_max = arr.indexOf(max); return [arr[index_min], arr[index_max], (arr[index_max] - arr[index_min]) ]; } function luo_Y_label(labelArr) { labelArr.reverse(); //let step = 300 / labelArr.length; let i; let out = `<svg y="0" >\n`; let stepSize = 300 / (labelArr.length - 1); let yOffset = stepSize; yOffset = 0; for (i = 0; i < labelArr.length; i++) { out += `<line x1="${0}" y1="${yOffset + (i * stepSize)}" x2="${700}" y2="${yOffset + i * stepSize}" style="stroke:gray;stroke-width:1" />\n`; out += `<text x="${10}" y="${yOffset + i * stepSize}" fill="red">${labelArr[i]}</text>\n`; } out += "</svg>\n"; console.log(out); return out; } function piirra_svg_viiva(dataArr, yLabel, color) { let i; let out = `<svg >\n`; let cords = ""; let stepSize = 700 / (dataArr.length ); let mm = get_minMax(dataArr); let tt = get_minMax(yLabel); let uPerPix = 300 / (tt[2]); let offset = (tt[0]) * uPerPix; if (offset < 0) { offset = offset * -1; } if (tt[0] > 0) { //tt[2] = tt[1] - tt[0]; offset = 0 - offset; } //offset = ; for (i = 0; i < dataArr.length; i++) { //cords += (i * stepSize).toFixed(0) + "," + (300-offset-(dataArr[i] * uPerPix).toFixed(0)) + " "; cords += (i * stepSize) + "," + (300-offset-(dataArr[i] * uPerPix)) + " "; } out += `<polyline style=\"fill:none;stroke:${color};stroke-width:1\" points="${cords}" />\n`; console.log("OFFSET: " + offset); console.log("uPerPix: " + uPerPix); return out; } function piirra_lampotila_svg(piirtoArr) { let str = Array(); let i; piirtoArr = piirtoArr.slice(0 - pubs['lampoSVGslice']); str['out'] = "<svg width='700' height='300' xmlns='http://www.w3.org/2000/svg'>\n"; let yLabel = [-10,0,10,20,30,40]; console.log(yLabel); let lh = Array(); let ls = Array(); let lp = Array(); let ul = Array(); //(20,20,20,20); //[14.5,14,15,13,15,12,10,0,5,7,8,15]; for ( i = 0; i < piirtoArr.length; i++) { if (!piirtoArr[i]['lh']) { piirtoArr[i]['lh'] = 0; } if (!piirtoArr[i]['ls']) { piirtoArr[i]['ls'] = 0; } if (!piirtoArr[i]['lp']) { piirtoArr[i]['lp'] = 0; } if (!piirtoArr[i]['ul']) { piirtoArr[i]['ul'] = 0; } lh.push(piirtoArr[i]['lh']); lp.push(piirtoArr[i]['lp']); ls.push(piirtoArr[i]['ls']); ul.push(piirtoArr[i]['ul']); } console.log(lh); str['out'] += luo_Y_label(yLabel); str['out'] += piirra_svg_viiva(lh, yLabel, "green"); str['out'] += piirra_svg_viiva(lp, yLabel, "blue"); str['out'] += piirra_svg_viiva(ls, yLabel, "magenta"); str['out'] += piirra_svg_viiva(ul, yLabel, "purple"); //console.log(str['out']); str['out'] += "</svg>\n"; document.getElementById("viivapiirturiSVG").innerHTML = str['out']; return 0; } function piirra_sahkon_hinta(hintaArr) { var c = document.getElementById("sahkonHintaCanvas"); var ctx = c.getContext("2d"); //var hintaArr = [10,5,5,0,5]; let i = 0, pituus = 0, lMargin = 40; hintaArr = hintaArr[0]; pituus = hintaArr.length let width = 600; let step = (width - lMargin) / pituus; let keskihinta = hintaArr.reduce((a, b) => a + b) / pituus; for (i = 0; i < pituus; i++) { ctx.fillText(i + " ", i * step + lMargin , 150); } let hMax = Math.max(...hintaArr); let hMin = Math.min(...hintaArr); let hFact = 130 / (hMax); console.log("max hinta: " + hMax); let yMax = 140; //Keskihinnan viiva ctx.moveTo(0, hFact * keskihinta); ctx.lineTo(width, hFact * keskihinta); ctx.stroke(); for (i = 0; i < pituus; i++) { ctx.fillRect(i * step + lMargin, yMax, 15, 0 - (hFact * hintaArr[i])); } } /* function piirra_lampotilat(viivapiirturi) { var c = document.getElementById("vedenKayttokerratCanvas"); var ctx = c.getContext("2d"); let i = 0; for (i = 0; i < viivapiirturi.length; i++) { */ function piirra_veden_kayttokerrat(piirtoArr) { var c = document.getElementById("vedenKayttokerratCanvas"); var ctx = c.getContext("2d"); let nyt = Math.round(Date.now() / 1000); let i; let arr = Array(); let sc = piirtoArr.length, mc = 3; //for (i = nyt; i > (nyt - sc); i--) { // arr[i] = ctx.fillRect((i * step), yMax, step-1, 0 - (Math.floor(Math.random() * 40) + 20)); //console.log(piirtoArr); let step = (600 / sc); let yMax = 150; let ilp = Array(), ls = Array(); ilp['scaleY'] = (140 / 2.5); ilp['num'] = 0; ls['scaleY'] = 1; //(140 / 2.5); ls['num'] = 0; for (i = 0; i < piirtoArr.length; i++) { /*if (piirtoArr[i]['ilp']) { ilp['num'] = (((1000/1000)*3600)/piirtoArr[i]['ilp'])/1000; //console.log(ilp); ctx.fillRect((i * step), yMax, step-1, 0 - (ilp['num'] * ilp['scaleY'])); }*/ if (piirtoArr[i]['ls']) { ls['num'] = piirtoArr[i]['ls']; //console.log(ilp); //ctx.fillRect((i * step), yMax, step-1, 0 - (ls['num'] * ls['scaleY'])); ctx.moveTo(((i -1) * step), (ls['num'] * ls['scaleY'])); ctx.lineTo((i * step), (ls['num'] * ls['scaleY'])); ctx.stroke(); } } } function vedenVirtausJaKokonaiskulutus(obj) { let str = ""; if (!obj['kDelay']) { obj['kDelay'] = 0; } if (!obj['lDelay']) { obj['lDelay'] = 0; } str += "<span style='color: blue;'>" + precise(obj['kPulssit'] / 6.6 / 60, 5) + " L ( " + (obj['kDelay'] / 11) + " L/s) </span><br>"; str += "<span style='color: red;'>" + precise(obj['lPulssit'] / 11 / 60, 5) + " L ( " + (obj['lDelay'] / 6.6) + " L/s) </span><br>"; str += "Varaajan status:" document.getElementById("vedenKokonaiskulutus").innerHTML = str; varaaja_canvas(obj['varaajanStatus'] / obj['varaajanKap']); } function nayta_veden_kayttokerrat(vesiArr) { //console.log(vesiArr); //piirra_veden_kayttokerrat(vesiArr); let i = 0; let aika, kesto, str = "", kylma = 0, kuuma = 0, prec = 0, yht = 0; for (i = 0; i < vesiArr.length; i++){ kesto = precise(vesiArr[i]['loppu'] - vesiArr[i]['alku']); kylma = (vesiArr[i]['kpulssit'] / 6.6 / 60); kuuma = (vesiArr[i]['lpulssit'] / 11 / 60); yht = precise(kylma + kuuma); prec = kuuma / kylma * 100; //str += "<span class='sin'>" + kylma + "</span> L / <span class='pun'>" + kuuma + "</span> L " + kesto + " s <br>"; str += " " + yht + " L " + kesto + " s <br>"; str += "<div style='width: 100px; height: 20px; background-color: blue;'><div style='background-color: red; width: " + prec + "%;'> s</div></div>"; //console.log(kylma); } document.getElementById("vedenKayttokerrat").innerHTML = str; } function varaaja_canvas(lataus) { //console.log("Varaajan lataus:" + lataus); var c = document.getElementById("varaajan_tila"); var ctx = c.getContext("2d"); var grd = ctx.createLinearGradient(0, 0, 200, 0); grd.addColorStop(0, "blue"); grd.addColorStop(1, "red"); // Fill with gradient ctx.fillStyle = grd; ctx.fillRect(0, 0, 200 * lataus, 20); } function piirra_sahkon_kulutus_ASCII(dataArr, varaajanSuunnitelma = null) { let arvot = Array(); let i; let out = ""; let out2 = ""; let yAxis; let v; let k; let mm; let w; let cFact; let style; let vari; const sets = []; let row = []; let hinta; let vals = []; //arvot = [2,2,5.2,2,2,1,2,4,2,2,2,5.2,2,2,1,2,4,2,5,9,2,1]; arvot = dataArr['hinta']; console.log(varaajanSuunnitelma); //return 0; mm = get_minMax(dataArr['hinta']); let keskihinta = arvot.reduce((a, b) => a + b) / 24; w = 77; sets['kwScale'] = 20; sets['hintaScale'] = 2; sets['varaajanStatusScale'] = 0.2; sets['nykyinenTuntiStyleStr'] = ''; cFact = w / mm[2]; //out += "\n\n\n\n\n\n\n\n\n\n"; yAxis = Array.from({length: 24}, (v, k) => k++ + ":00"); //out += "<div style='border: 1px solid pink;align-self: end; display: flex;'>"; for (i = 0; i < yAxis.length; i++) { vals['varaajanTila'] = "X"; sets['nykyinenTuntiStyleStr'] = ''; if (varaajanSuunnitelma[i]) { vals['varaajanTila'] = "POIS"; if (varaajanSuunnitelma[i]['tila']) { vals['varaajanTila'] = "PÄÄLLÄ"; } vals['varaajanStatus'] = ((varaajanSuunnitelma[i]['status'] / 99000) * 100).toFixed(0); // 99000 = täysi varaaja } out2 += " " + yAxis[i] + ""; console.log(pubs['nykyinenTunti']); if (pubs['nykyinenTunti'] == i) { sets['nykyinenTuntiStyleStr'] = 'background-color: yellow;'; } out += "<div style='" + sets['nykyinenTuntiStyleStr'] + " margin-right: 15px; padding-right: 5px; bottom: 10px; align-self: end; border-left: 1px solid #dee0e3; '>"; if (arvot[i] < keskihinta) { vari = "vih"; } else { vari = "pun"; } out += "<div>"; vals['sahkonHinta'] = dataArr['hinta'][i]; vals['sahkonHintaRepeat'] = dataArr['hinta'][i] - (mm[0] * 0.9); vals['ilpHinta'] = ((dataArr['ilpKulutus'][i] / 1000) * dataArr['hinta'][i]) ; vals['ilpKulutus'] = dataArr['ilpKulutus'][i] / 1000; out += "" + `<span class='pystyyn ${vari}'><b>` + vals['sahkonHinta'] + "</b> " + "€".repeat(vals['sahkonHintaRepeat'] * sets['hintaScale']) + "</span>"; if (vals['ilpKulutus'] != 0) { out += "" + `<span class='pystyyn mag'><b>` + (vals['ilpKulutus']).toFixed(2) + "</b> " + "#".repeat((vals['ilpKulutus']) * sets['kwScale']) + "</span>"; } if (vals['ilpHinta'] != 0) { out += "" + `<span class='pystyyn '><b>` + (vals['ilpHinta']).toFixed(2) + "</b> " + "€".repeat((vals['ilpHinta']) * sets['hintaScale']) + "</span>"; } if (vals['varaajanStatus']) { out += "" + `<span class='pystyyn '>` + vals['varaajanStatus'] + " " + ".".repeat((vals['varaajanStatus']) * sets['varaajanStatusScale']) + "</span>"; } out += "</div>"; out += "<div>" + yAxis[i] + "</div>"; out += "<div>" + vals['varaajanTila'] + "</div>"; out += "</div>"; //out += } //out += "<div style='border: 1px solid pink; '>"; //out += "<br>PÄIVÄMÄÄRÄ"; //out += "</div>"; //out = out + "\n" + out2; //console.log("OUT: " + out); document.getElementById("kulutusASCII").innerHTML = out; } function piirra_kulutustiedot_paivittain_ASCII() { let i, pvm, out = "", yAxis, sum; let paivia = allData['sahkonHinta'].length; //yAxis = Array.from({length: 24}, (v, k) => k++ + ":00"); for (var key in allData['sahkonHinta']) { //m = key['ilpKulutus'].reduce((acc, o) => acc + parseInt(o.value), 0); //out += "<div style='" + sets['nykyinenTuntiStyleStr'] + " margin-right: 15px; padding-right: 5px; bottom: 10px; align-self: end; border-left: 1px solid #dee0e3; '>"; //nsole.log(sum); } return 0; document.getElementById("kulutusASCIIpaivittain").innerHTML = out; } function kasittele_kaikki_data(data) { //console.log(data); sets['pulsseja_per_kwh'] = data['sahko']['pulsseja_per_kwh']; sets['ilp_pulsseja_per_kwh'] = data['ilp']['pulsseja_per_kwh']; sets['nykyinenHinta'] = data['sahkonHinta'][data['pvmTanaan']]['hinta'][data['nykyinenTunti']]; //console.log("nykyinen hinta: " + sets['nykyinenHinta']); if (data['vedenKayttokerrat']) { nayta_veden_kayttokerrat(data['vedenKayttokerrat']); } //data['pvmTanaan'] = '2025-01-03'; pubs['pvmTanaan'] = data['pvmTanaan']; pubs['nykyinenTunti'] = data['nykyinenTunti']; const hintaASCII = data['sahkonHinta'][data['pvmTanaan']]; /*if (data['sahkonHinta'][data['pvmHuomenna']]) { console.log("HUOMINEN LÖYTYY"); hintaASCII = (data['sahkonHinta'][data['pvmTanaan']]).concat(data['sahkonHinta'][data['pvmHuomenna']]); }*/ piirra_sahkon_kulutus_ASCII(hintaASCII, data['lvesi']['varaajanSuunnitelma']); //piirra_sahkon_hinta_svg([data['sahkonHinta'][data['pvmTanaan']]['hinta'], data['lvesi']['varaajanSuunnitelma']]); allData['viivapiirturi'] = data['viivapiirturi']; allData['sahkonHinta'] = data['sahkonHinta']; piirra_kulutustiedot_paivittain_ASCII(); piirra_lampotila_svg(allData['viivapiirturi']); //vedenVirtausJaKokonaiskulutus(data); //piirra_sahkon_hinta([data['sahkonHinta'][data['pvmTanaan']]['hinta']]); //piirra_veden_kayttokerrat(data['viivapiirturi']); document.getElementById("komentoTXTarea").value = data['komentoTXTarea']; } function hae_kaikki_data() { let data = fetch('/kaikki_data') .then(response => response.json()) .then(data => kasittele_kaikki_data(data)); } function precise(x, m = 3) { return x.toPrecision(m); } hae_kaikki_data(); var source = new EventSource("/livestream"); source.onmessage = function(event) { const obj = JSON.parse(event.data); //console.log(obj); let teho = precise((((1000/10000)*3600)/obj['sahkoDelay'])/1000); let hinta = precise(teho * (sets['nykyinenHinta'] / 100)); document.getElementById("tehoJaHinta").innerHTML = "<h1>" + teho + " kw (" + hinta + " eur/h) </h1>"; if (obj['ilpDelay']) { let ilpTeho = precise((((1000/1000)*3600)/obj['ilpDelay'])/1000); let ilpHinta = precise(ilpTeho * (sets['nykyinenHinta'] / 100), 1); document.getElementById("ilp").innerHTML = "Ottoteho: " + ilpTeho + " kW (" + ilpHinta + " eur/h )"; document.getElementById("ilpKokonaisKulutus").innerHTML = "Yhteensä " + (obj['ilpPulssit'] / 1000) + " kWh"; } if (obj['lastLogs']) { document.getElementById("lastLogsArea").innerHTML += obj['lastLogs']; } if (obj['kvesiDelay'] || obj['lvesiDelay']) { vedenVirtausJaKokonaiskulutus(obj); } if (obj['lh']) { document.getElementById("lh").innerHTML = "Huone: <b>" + obj['lh'] + "</b> c, "; } if (obj['lp']) { document.getElementById("lp").innerHTML = "Parvi: <b>" + obj['lp'] + "</b> c, "; } if (obj['ls']) { document.getElementById("ls").innerHTML = "Ilp puhalluslämpö: <b>" + obj['ls'] + "</b> c, "; } if (obj['ul']) { document.getElementById("ul").innerHTML = "Ulkolämpö: <b>" + obj['ul'] + "</b> c, "; } /*if (obj['komentoTXTarea']) { // PURKKARATKAISU VÄLIAIKAISESTI if (document.getElementById("komentoTXTarea").value != obj['komentoTXTarea']) { document.getElementById("komentoTXTarea").value = obj['komentoTXTarea']; } }*/ } function submitFormFetch(event) { var fetchForm = document.getElementById("form-propt"); var data = new FormData(fetchForm); var plainFormData = Object.fromEntries(data.entries()); //scrollToEnd(); //tyhjaaTxt(); //console.log(JSON.stringify(plainFormData)); fetch("/post", { method: "POST", body: plainFormData["komentoTXTarea"] //JSON.stringify(plainFormData) }) .then((res) => { //console.log(res); //return res.text(); }) .then((txt) => { /*alert("Submit Success");*/ }) .catch((err) => { //alert(err); }); return false; } </script>
Katso myös
Aiheesta muualla
- http://penasaatiot.org/keskustelu/index.php?aihe=265 Edellinen, kuopattu ja kadonnut versio