Webshop
Min side
Tilmeld Nyhedsbreve
Du kan afmelde nyhedsbreve igen ved logge ind på "Min side" med det kodeord vi sender dig.
Bemærk: For at vi ikke får spam tilmeldinger, skal du i kontrolfeltet skrive dit navn bagfra.
f.eks. at huske indholdet af din indkøbskurv.
Vores Raspberry Pi projekter |
Herunder finder du lidt (IT-) nørderi.
Hjemmeriet gør sig grundlæggende i fermentering af forskellig karakter.
IT-nørderi som disse sider, er derfor lidt udenfor vores egentlige fokus. Men ingen regler uden undtagelser.
Hvis lidt IT-nørderi kan inspirere nogle til at fermentere i den ene eller anden retning, giver det lidt mening alligevel.
-----
Kode på disse sider opdateres fortløbende, og kan benyttet kvit og frit
- med forbehold for fejl og mangler -
Vores kode kan ses i de forskellige afsnit herunder.
Som udgangspunkt er koden skjult, men ved at klikke på det lyserøde link gøres koden synlig.
Du kan hente/downloade koden ved at klikke på linket til højre for vis/skjul linket.
-----
Vi gør os umage for, at gøre systemet så praktisk og struktureret som vi evner.
Vores Raspberry Pi projekter er ikke tænkt som undervisning i god praksis indenfor programmering.
Forslag til ændringer og gode råd modtages meget gerne.
Baggrund
Intro
Raspberry Pi - Intro |
En Raspberry Pi (RPi) er en lille computer på størrelse med et kreditkort. Man kan koble skærm, mus, tastatur, kamera m.m. til denne lille enhed og bruge den som en PC. Operativsystemet er Linux-baseret, som for mange nok er lidt af en udfordring. Kender man ikke til Linux/Unix, men har man mod på at udvide sin horisont indenfor PC-verdenen, så er der uanede mængder af hjælp at finde på nettet - og lidt på disse sider. En af de ting der adskiller en Raspberry Pi fra en "almindelig" computer/PC er, at en Raspberry Pi har en række pins, kaldet GPIO for Generel Purpose Input/Output, som kan bruges til alverdens former for styring. Man kan for eksempel registrere når der trykkes på en kontakt, eller tænde/slukke for et relæ. En Raspberry Pi kan derfor bruges til rigtig mange formål som styring af robotter, varmepumper, opsamling af data fra sensorer, som enkelte eksempler. Vores kendskab til Raspberry Pi udspringer af, at Hjemmeriet forhandler en enhed til overvågning af temperatur og densitet i hjemmebrygget øl - Tilt enheden. Tilt enheden kan kommunikere via Bluetooth til en app på en smartphone, men tillige til en Raspberry Pi, hvor man så kan følge udviklingen af gæringsprocessen. Når vi forhandler Tilt-enheder, med disse muligheder, skal de naturligvis afprøves. Som sagt så gjort: Vi skaffede en RPi via nettet, brugte en del tid på at lære hvordan og hvorledes, og har lavet en udførlig vejledning så andre kan få glæde af dette. |
|
En andet aktuel udnyttelse af RPi er, at vi har udviklet et system til overvågning af vores el-forbrug. Det er i skrivende stund et meget omtalt emne, set i lyset af elprisernes periodevise himmelflugt. Vi har udviklet et elforbrug/pris-system, som henter vores registrerede el-forbrug, og priserne på el - inklusiv spotpriser, dvs. prisen på el det kommende døgn. Det bruger vi til at planlægge el-forbrugende aktiviteter til de billige perioder og holde styr på vores generelle el-forbrug, så vi dermed kan gøre tiltag til at minimere vores elforbrug. Vi har nu vores projekter liggende her på hjemmesiden, så alle med mod på lidt IT-nørderi kan lade sig inspirere. Projekterne kan hentes / downloades til fri inspiration / afbenyttelse.
|
Python | Programmeringssproget |
PyQT-5 | Grafiske brugerflade |
MySQL | Database |
MatPlotLib | Visualisering |
bluepy | Bluetooth |
MQTT | WiFi beskedsystem |
requests | Internet opslag |
smtplib | |
... | med mere |
Vi deler gerne ud af vores erfaringer, men noterne er også en huskeseddel for os selv.
+ USB kamera med motion-detection - "Der er kunder i butikken" alarm
+ Vejrstation
+ Ringeklokke ved bevægelser på gårdspladsen - Der er gæster / kunder
+ Switches til manuel styring
- Send mail modul
- Kamerarobot i naturen - Hvor bor pindsvinet - Bevægelig/Natkamera
- pH overvågning af fermentering
- Styring af flaskefylder - magnetventilstyring
Noter til "Kamerarobot i naturen - Hvor bor pindsvinet - Bevægelig/Natkamera"
Målet er at opsætte en RPi i den "vilde natur" (på vores ejendom), med tilkoblet kamera.
Det hele skal drives med strøm fra et bilbatteri, som oplades med et solcellepanel.
Når der registreres bevægelse, skal der optages video, som gemmes på RPi, og der skal sendes et billede via WiFi/telefon til en PC/server med besked om bevægelse. Der skal oprettes en PC/server med fast IP-adresse. Fra denne PC skal man kunne tilgå RPi med VNC eller lign. VNC kan også bruges til at tilgå PC/server udefra, så man kan følge online video, 24-7, overalt.
Vores RPi's
FORUDSÆTNINGER
Basis
Kommandoer
InkBird IBS-THx
Raspberry Pi - InkBird IBS-THx |
Herunder er et forslag til Python kode, som frit kan bruges som udgangspunkt i forbindelse med aflæsning af IBS-THx enhederne via Bluetooth på en Raspberry Pi.
Se iøvrigt også vores "Temperaturkontrol" applikation, hvor vi i udpræget grad gør brug af InkBird IBS-THx enhederne til registrering af temperaturer i vores køleskabe og frysere.
Kontakt Hjemmeriet hvis du skal have hjælp.
Temperaturkontrol
Raspberry Pi - Temperaturkontrol |
Herunder vores kode til at overvåge temperaturer i vores køleskabe og frysere.
Desuden skal MySQL projektet bruges... og andre ...
| |||
|
Elpriser
Raspberry Pi - Elpriser |
Herunder vores kode til at holde styr på vores elforbrug og -priser.
Det startede som et "mest for sjov" projekt, men nu har det absolut tjent udviklingstiden ind:
Sammenlignet med 1 år tilbage, har vi reduceret vores forbrug af og udgift til el med cirka 50%.
Det viser nok, at vi har været rigeligt ødsle - før i tiden. Vi er blevet klogere, og smartere.
---
For at forstå mulighederne bag Elpriser, kan du studere rækken af hjælpetekster og se billederne nederst på siden.
---
Bemærk: Udover python koden er der 5 hjælpe-tekst filer og et icon.
Desuden skal MySQL projektet bruges... og andre ...
Et par billeder fra Elpriser er vist nederst.
Hjælpe-tekst-1:
| |||
Elpriser kan holde dig ajour med aktuelle timepriser for el og du kan følge
dit registrerede elforbrug og den beregnede elpris på timebasis.
Elpriser viser de aktuelle elpriser ('spotpriser') for igår, idag og tillige, normalt fra kl 13, elpriserne den følgende dag. Dette kan med fordel indgå i planlægningen af elforbrug som tøjvask, tørretumbler, opvask og ovn til de billige perioder i det kommende døgn. For at bruge Elpriser, skal Elpriser konfigureres til dig personligt. Se informationen om, hvordan du konfigurerer Elpriser i fanebladet Konfiguration. Bemærk: Elpriser kan ikke aflæse dit 'her og nu' forbrug. Det er udelukkende forbrug med 1 - 2 dages forsinkelse, som du kan se i Elpriser. Er du ikke bevidst om aktuelle el-priser og dit forbrugsmønster af el, lægges større elforbrug ofte i spidsbelastningsperioder - netop hvor elprisen er høj. Er du bevidst om de aktuelle el-priser, og har du timeafregnet aftale med dit elselskab, kan du reducere dine udgifter til el i BETRAGTELIG omfang ved at være bevidst om, at lægge dine forbrug af el på tidspunkter, hvor elprisen er lav. Vi anbefaler derfor, at du sikrer dig - eller sørger for at du har - timeafregnet aftale med dit elselskab og har fingeren på pulsen mht. at lægge dine strømslugende aktiviteter i de el-pris-billige perioder. Elpriser er primært udviklet med det mål at minimere forbrug og dermed udgifterne til el i vores private dagligdag men tillige i vores virksomhed Hjemmeriet. Vi har - skiftet fra kvartalsaffregnet fastsat til timebaseret afregningspris - det krævede blot en kort opringning til el-selskabet - reduceret antallet af køleskabe og frysere (4 køleskabe og 1 fryser kunne undværes) - flyttet elkrævende opgaver som vask, tørretumbling, opvask, bagning o.l. til perioder med billig elpris - sørget for at opvaskemaskine og vaskemaskine fyldes helt inden brug - dvs. vi ofte springer en opvaskedag over - skruet ned for varmen - tykke sokker og håndledsvarmere kan anbefales... - reduceret antal lamper - udskiftet pærer til LED - slukket for PC, TV o.l. når vi ikke bruger det - ... - derved opnået en betragtelig reduktion af elregningen - ~50% Det er lidt af en sport - men også lærerigt - at være mere bevidst om forbruget (nogle gange misbruget) af forskellige ressourcer. Det er et mål her, at holde fast i de gode vaner som vi opnår ved at følge vores elforbrug. Som udgangspunkt af hensyn til økonomien, men også for klimaet... |
Hjælpe-tekst-2:
| |||
Elpriser skal konfigureres til dig personligt, så du dermed giver Elpriser
mulighed for at hente data om dit elforbrug.
Så vidt vi ved, bliver alle elmålere i Danmark aflæst elektronisk, og data fra elmålerne indrapporteres til Energinet. Energinet er en selvstændig offentlig virksomhed under Klima-, Energi- og Forsyningsministeriet. Data fra elmålerne kan tilgås via hjemmesiden Eloverblik.dk, som drives af Energinet. Energinet giver på denne måde elkunder i Danmark gratis adgang til deres egne data via Eloverblik.dk, hvor man skal logge på med MitID for at få adgang til at se sine data. Data som kan ses på Eloverblik.dk er tilgængelig med 1 - 2 dages forsinkelse. Du kan således ikke se dit her-og-nu forbrug. @!Eloverblik token For at Elpriser skal kunne tilgå dine data over elforbrug, skal du konfigurere Elpriser til dette. Dette gøres ved at du henter en nøgle (kaldet datadelings token) fra Eloverblik.dk, og gemmer denne nøgle i Elpriser. Elpriser bruger så denne nøgle i forbindelse med forespørgsel på dit elforbrug. Når Elpriser er konfigureret med din datadelings token, vil Elpriser programmet, via eloverblik.dk, hente oplysninger om dit elforbrug og alle priser og tariffer, som indgår i beregningen af din elpris. Det datadelings token som gemmes i Elpriser, kan UDELUKKENDE bruges til at hente dine registreringer af el-forbrug fra Eloverblik.dk. Der er INGEN risiko for at Elpriser programmet kan misbruge datadelings tokenet til at rekvirere andre personlige oplysninger eller data eller at det kan blive misbrugt af tredjepart. Sådan opretter du et datadelings token på Eloverblik.dk: Gå ind på adressen https://eloverblik.dk og log ind med MitID. Klik derefter på den lille person i øverste højre hjørne. Derefter klikker du på 'Datadeling' og så på 'Opret token'. Du kan oprette flere tokens til forskellige formål, så systemet beder dig give det oprettede token et navn - skriv for eksempel Elpriser. Dette giver dig nu et token bestående af (RIGTIG MANGE) bogstaver og tal - ikke lige noget skriver ned for at taste det ind senere! Dette token bliver kun vist én gang og er derfor kun tilgængelig via det aktuelle vindue. Ved glemt eller mistet token, skal der oprettes et nyt token. Kopier derfor det oprettede token ved at trykke på "Kopier til udklipsholder" og sæt det ind i token konfigurationsfeltet i Elpriser / Konfiguration / 'Eloverblik token' med Ctrl-V. !@Eloverblik token Det oprettede token fra eloverblik.dk er aktivt/gyldigt i 1 år. Elpriser programmet vil oplyse når dit token er cirka 1 år gammelt - så skal du oprette et nyt token via eloverblik.dk / Lille mand / Datadeling / Opret token og kopiere denne til Elpriser Konfiguration igen. I Elpriser konfigurationen er der desuden følgende ting, som kan justeres: Antal måneder i Opgørelse Antal dage i historik graf Minimum antal aktive timer per dag Lavt prisniveau (kr/kWh) Elafgift ændringsdato Elafgift efter ændringsdato @!Antal måneder i Opgørelse I fanebladet Opgørelse kan du se elforbrug og elpriser per time, dag, måned og kvartal og du kan studere de enkelte måneders største dagsforbrug og priser m.m. Antallet af måneder som gemmes i Elpriser angives i fanebladet Konfiguration. !@Antal måneder i Opgørelse @!Antal dage i historik graf I fanebladet Aktuelt kan du se elpriser for igår, idag og, efter kl. 13, tillige imorgen. Desuden vises elforbrug og elpriser for historiske dage. Antallet af historiske dage kan ændres dynamisk - det initielle antal historiske dage angives i fanebladet Konfiguration. Det anbefales ikke at angive mere end 7 dage af hensyn til visningen af data. !@Antal dage i historik graf @!Minimum antal aktive timer per dag @!Lavt prisniveau Værdierne for Minimum antal aktive timer per dag og Lavt prisniveau (kr/kWh) bruges til at udpege de timer i det aktuelle døgn og dagen imorgen, hvor det vil være billigst at placere elkrævende forbrug. Disse timer vises ved orange firkanter i de øvre grafer. Det er desuden tanken at disse oplysninger kan indgå i en styring af vores varmepumpe og/eller gulvvarmesystem så gulvvarmen kun cirkulerer i de billige perioder og varmepumpen holdes slukket i de dyre perioder. Med erfaringen ses de billigste timer typisk at falde fra kl. 22 - 06, og man kunne derfor med fordel istedet sætter en tænd/sluk-ur til at aktivere styringen af gulvarmen i disse timer. !@Lavt prisniveau !@Minimum antal aktive timer per dag @!Elafgift ændringsdato @!Elafgift efter ændringsdato Elpriser henter de aktuelle tariffer, som indgår i den samlede elpris, inklusiv den statslige elafgift. For spotpriserne, som er elpriserne igår, idag og imorgen, kendes de korrekte tariffer ikke. Der anvendes tariffer, som er aktuelt gældende - dog kan du angive ændringer i den statslige elafgift, og datoen for ændringen. På denne måde kan spotpriserne gøre lidt mere korrekte i de dage, hvor den statslige elafgift ændres. Du kan se de officielle statslige afgifter på el via dette link: https://www.skm.dk/skattetal/satser/satser-og-beloebsgraenser-i-lovgivningen/elafgiftsloven/ !@Elafgift efter ændringsdato !@Elafgift ændringsdato |
Hjælpe-tekst-3:
| |||
I fanen Aktuelt vises aktuelle grafer.
Øverste grafer vises "spotpriser", dvs. elpriserne for igår, idag og imorgen (efter kl 13). Brug disse til at planlægge, hvornår du skal vaske eller gøre andre el-krævede opgaver. Nederste grafer viser historiske data over dit elforbrug og resulterende priser på timebasis. Hvis du ikke har en timebaseret afregningsaftale med dit elselskab, så vil de viste priser IKKE være gældende for, hvad du vil blive afregnet med. De viste priser vil KUN være korrekt, såfremt du har en timebaseret afregningsaftale med dit elselskab. Læs mere om dette under fanen 'Opgørelse'. Øverste grafer I den øverste gruppe af grafer, giver den mørkeblå kurve timeprisen for el, inkl. alle afgifter, moms o.l. Der skelnes mellem Vest- og Øst-Danmark, med skillelinie i Storebælt. Prisen i din landsdel er vist i mørkeblå, mens den anden landsdel vises i orange farve. Oftest vil priserne være identiske for Øst og Vest, og så vil du ikke se den orange kurve, fordi den ligger under den mørkeblå kurve. Det hænder ikke sjældent at der er afvigelser over døgnet imellem landsdelene som følge af forskellige forhold mht. forsyning (sol/vind/vand) fra Tyskland, Norge og Sverige, som vi ikke nøjere skal kloge os på her. Kurverne som er farvet lilla (din landsdel) hhv. lyseblå (anden landsdel) viser den rå elpris (inkl. moms), dvs. prisen hos elproducenten. Denne er en del lavere end den samlede elpris (blå og orange), som inkluderer elafgifter, transport, håndtering af elnettet o.l. Den orange lodrette streg i grafområdet angiver 'nu' - og er suppleret med tidspunkt og elprisen for den nuværende time. De øverste grafer strækker sig over 2 - 3 dage, som dækker igår, idag og eventuelt imorgen. Graferne for imorgen vises efter kl. 13 - så er morgendagens el-priser nemlig lagt fast (fastsættes af nordpool, som er en 'el-børs' som styrer fordelingen af tilgængelig el til landene omkring os - se mere her: https://en.wikipedia.org/wiki/Nord_Pool og https://www.nordpoolgroup.com/) I bunden af det øvre grafområde, vises en række orange firkanter. Dette er de timer i det aktuelle døgn og dagen imorgen, hvor det vil være billigst at placere elkrævende forbrug. Konfigurering af disse timer forklares under fanen Konfiguration. Endelig kan der vises en række røde firkanter i det øvre grafområde - i så fald indikerer dette at de viste grafer er skønnede, i det tilfælde at disse oplysninger ikke har kunne hentes. Nederste grafer De nederste grafer viser historiske data for dit elforbrug og de resulterende elpriser, FORUDSAT at du har en timeafregnet aftale med dit elselskab. Den grønne graf viser dit elforbrug (kWh). Den lilla og mørkeblå viser ren el-time-pris hhv. samlet el-time-pris. Den røde kurve viser prisen for dit elforbrug (kr) ( dvs. elforbrug x el-time-pris ). For hver dag vises det summerede elforbrug og den summerede pris for dagen. Med felterne under den nederste graf, kan du styre hvilke perioder, som vises i den historiske graf. Vælg antal dage, start dato og tryk på 'Vis'. Du kan på denne måde nærstudere, hvordan dit forbrug har været før der kom øget fokus på elpriserne. Det kan godt være lidt af en øjenåbner. Knapperne <<, >> og >>| flytter graferne én periode tilbage, én periode frem hhv. så langt frem som muligt, dvs. med slut i seneste hentede data. |
Hjælpe-tekst-4:
| |||
Gennem Opgørelse kan du se elforbrug og elpriser per time, dag, måned og kvartal og du kan studere
de enkelte måneders største dagsforbrug og priser m.m. Antallet af måneder som gemmes i Elpriser konfigureres i fanebladet Konfiguration.
Data præsenteres øverst i en månedsoversigt, derunder en dagsoversigt for en valgt måned (venstre) og en timeoversigt i en valgt dag (højre). Placerer du musen hen over en graf, vises oplysninger om grafdata i en tooltip. Klikker du på en måned i månedsoversigten, vises dagene i denne måned og timerne i den sidste dag i denne måned. Klikker du på en dag i dagsoversigten, vises timerne for denne dag. For at beregne månedernes forbrug og priser skal månedernes data være hentet fra Eloverblik. Dette er en rimelig stor mængde data, som tager tid at hente fra Eloverblik. Data hentes derfor en måned af gangen, startende med de ældste måneder først. Data hentes automatisk, med 1 måned for hver 2. minut, cirka. Data vil fortsætte med at blive hentet automatisk, indtil alle måneder er hentet. Når data for en måned er hentet beregnes månedens største time/dagforbrug og priser, som vises i Opgørelse. Alle hentede data gemmes i en database, og denne proces kan tage lidt tid. Der kan være begrænsninger mht. hvor ofte du kan hente dine data fra Eloverblik - det kan betyde at det tager lidt længere tid at hente data. For de måneder, som er hentet ind og gemt i Elpriser, vil der ud for hver måned være en grøn MinMax knap. For måneder som endnu ikke er hentet, er knappen inaktiv med teksten Afventer. Når du klikker på MinMax knappen, åbnes en oversigtsliste, som udpeger 10 værdier for måneden: - Dagen med mindste hhv. største dagspris - Dagen med mindste hhv. største dagsforbrug - Timen med mindste hhv. største timepris - Timen med mindste hhv. største timeforbrug - Timen med mindste hhv. største elpris De enkelte værdier er vist i hver sin knap - et tryk på denne knap vil sætte den historiske graf til den periode hvor værdien optræder. For at se grafen, skal vinduerne som vises, flyttes eller minimeres. Hvis du under Konfigurer har sat antallet af måneder i Opgørelse til et stort antal, vil hentningen af alle data naturligvis tage tid derefter - så må du lade systemet arbejde i den tid det tager. Opgørelse vil vise dig summerede forbrug og priser på månedsbasis og opsummerede kvartalstal, så du kan holde dette op mod dine aktuelle regninger. Det skulle gerne stemme nogenlunde, men mindre forskelle kan skyldes forskellige gebyrer, abonnementer o.l. som Elpriser ikke har indregnet korrekt - men det skulle være i småtingsafdelingen. Opgørelse viser forbrug og priser opgjort på timebasis. Hvis der er store forskelle mellem din reelle elregning og hvad Opgørelse viser, kan dette skyldes, at du IKKE afregnes på timebasis hos dit elselskab. De viste priser er UDELUKKENDE beregnet på basis af timeafregning. Har du eksempelvis måneds- eller kvartalsafregnet, fastpris-fastsat elpris, kan forskellen mellem priserne i Opgørelse og dine fysiske regninger være af både positiv som negativ karakter. |
Hjælpe-tekst-5:
| |||
Elpriser er udviklet af Jan Jochimsen i forbindelse med optimering af
forbrug af el i vores private hjem og forbruget af el til
drift af Hjemmeriets (mange) køleskabe og frysere, samt optimering af
driftperioder af større elforbrug som opvaskemaskinen o.l.
Vær opmærksom på.... 1) Der er visse forhold som betinger, at Elpriser kan virke optimalt.Elpriser henter data fra eloverblik.dk og api.energidataservice.dk. Maskinen som Elpriser kører på, skal derfor kunne hente data over internettet med jævne mellemrum. Er dette ikke tilfældet, eller er forbindelsen til internettet ikke stabil, vil Elpriser ikke kunne hente data som ønsket, hvilket vil kunne ses i de grafer, opgørelser m.m. som Elpriser viser. 2) Der kan forekomme perioder, hvor kilderne til data (eloverblik.dk og api.energidataservice.dk) lægger begrænsninger på hentningen af data. Dette vil kunne ses i de grafer, opgørelser m.m. som Elpriser viser. Begrænsningerne er dog ofte kun midlertidige. Elpriser sørger for at gentage forepørgsler på data i tilfælde af begrænsninger - derfor vil "huller" i data oftest forsvinde i løbet af kort tid. 3) Elpriser er udviklet på en Raspberry Pi 4B, kodet i programmeringssproget Python med brug af databaseværktøjet MySQL. Er du selv en anelse IT-kyndig, kan du læse mere om Elpriser og hente vores Python kode, kvit og frit, her: https://Hjemmeriet.com/Raspberry-Pi Er du interesseret i tekniske detaljer om de services som Elpriser bruger til at hente forbrugsdata og priser på el over internettet, kan du studere dette via følgende links: Forbrugsdata fra Eloverblik.dk Priser på el fra Energidataservice.dk 4) Elpriser er udviklet for "sjov" men drevet af ønsket om at få styr på vores forbrug af el i en tid, hvor der er behov for at være bevidst om såvel økonomi som klima. 5) Elpriser deler IKKE data med nogen som helst - data hentes fra offentlige services og gemmes lokalt. Ingen data sendes ud af Elpriser, udover hvad der visuelt vises i grafer, opgørelser o.l. 6) Forslag/kommentarer er altid velkommen - send gerne dette per mail til Hjemmeriet@Hjemmeriet.com 7) Ansvarsfraskrivelse Anvendelse af Elpriser er uden ansvar for udvikleren. |
Programikon:
|
Selve koden:
Klik for at hente filen Elpriser.py
Version: 09-Jul-2023 07:28#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Start by:
#
# python3 Elpriser.py
#
# Coding standards: https://peps.python.org/pep-0008/
'''
Krav til installerede moduler:
- Python3, numpy, ...
- PyQt5
- MatPlotLib
- MySQL - sudo apt-get install python-mysqldb
- environment variables in /etc/environment
'''
# Standard imports
import logging
import os, sys, subprocess, re, uuid, random, time, copy, traceback
from datetime import datetime, timedelta
import numpy as np
import requests
import json
from functools import partial
from dateutil import parser
# PyQt5
from PyQt5.QtWidgets import QDialog,QStyledItemDelegate,QDialogButtonBox,QFormLayout,QHeaderView,QWidget,QPushButton,QLineEdit,QDateTimeEdit,QApplication,QTableWidgetItem,QTableWidget,QComboBox,QCheckBox,QLabel
from PyQt5.QtWidgets import QStyle,QMenu,QAction,QFrame,QTextBrowser,QVBoxLayout,QScrollArea,QWidget,QPushButton,QSpinBox,QDateTimeEdit,QApplication,QListWidget,QMessageBox,QGridLayout,QLabel,QProgressBar,QScrollBar,QListWidgetItem,QDesktopWidget,QTabWidget
from PyQt5.QtCore import QTimer,QDateTime, Qt
from PyQt5.QtWidgets import QToolTip, QGraphicsOpacityEffect
from PyQt5 import QtWidgets, QtGui, QtCore
# MatPlotLib
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
import matplotlib.dates as mdates
import matplotlib.ticker as ticker
import matplotlib.widgets as mwidgets
# MySQLdb
import RPiMySQL
# "Globals"
global area_other
global area_primary
global meterId
global childMeterId
global postnummer
global adresse
global eloverblikstatus
global bSpotEstimate
global bSpotMissing
global bSpotWaiting
global bPricesMissing
global balanceSupplierName
global gridOperatorName
global taxReduction
global firstConsumerPartyName
global secondConsumerPartyName
global token0
global token1
global nDays_Init
global nMonths_Bills
global nActiveHours
global lowPriceLevel
global bStepCurves
global dateTime_NextUpdate
global elAfgiftAndet
global Elafgift_now
global elAfgift_datoskift
global elAfgift_after
global elAfgiftOver4000kWh
global Nettarif_C_time
global foretagOpslag
global iToggle
global dotdotdots
global helpHelpTexts
global mainForm
global labelTooltip
global timerTooltipShow
global timerTooltipHide
global eventTooltip
global widgetTooltip
global filedebug
# MySQL
try:
rpiHost = "localhost" ## os.environ[ "SQLHOST" ] ## "localhost" ## "192.168.1.224"
rpiUser = os.environ[ "SQLUSER" ] ## "pi"
rpiPass = os.environ[ "SQLPASS" ] ## "mysqlpsw"
except Exception as e:
print( "Elpriser *** Exception *** {}".format(e) )
print( "environment not correctly defined" )
print( "If started with sudo, use sudo -E" )
exit( 0 )
rpiDb = "elpriser"
mainForm = None
filedebug = open( "filedebug.txt", "a", buffering = 1 )
print( "elpriser start", datetime.now(), file = filedebug )
filedebug.flush()
logging.basicConfig( filename='elpriserlog.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
helpHelpTexts = {}
thisRPiMySQL = RPiMySQL.RPiMySQL( rpiHost, rpiUser, rpiPass, rpiDb )
bSpotEstimate = False
bSpotMissing = False
bPricesMissing = False
bSpotWaiting = False
iToggle = 0
meterId = ""
childMeterId = ""
Elafgift_now = 0.722
elAfgift_datoskift = "2023-01-01"
elAfgift_after = 0.008
bStepCurves = True
nDays_Init = 4
bVerbose = True
area_primary = "DK2"
area_other = "DK1"
dotdotdots = ""
adresse = "-- Ingen opslag --"
eloverblikstatus = ""
dateFormat = "%Y-%m-%d" # Gives f.ex. "2022-06-23"
timeFormat = "%H:%M:%S" # Gives f.ex. "17:23:15"
datetimeFormat = dateFormat + " " + timeFormat
# Define empty list/timeseries to store data
dataTime_1_A = [] # Aktuelle
dataTime_2_A = []
dataPrice_1_A = []
dataPrice_2_A = []
dataHeating_2_A = []
dataElPrice_1_A = []
dataElPrice_2_A = []
dateTime_NextUpdate = None
dataTime_1_H = [] # Historiske
dataTime_2_H = []
dataPrice_1_H = []
dataPrice_2_H = []
dataPrice_3_H = []
dataElPrice_2_H = []
#
# Structure of table elpriser.data
#
dataTable = { \
'id': { 'Type': 'int(11)', 'Null': 'NO', 'Key': 'PRI', 'Default': 'None', 'Extra': 'auto_increment' }, \
'time': { 'Type': 'timestamp', 'Null': 'NO', 'Key': '', 'Default': 'current_timestamp()', 'Extra': 'on update current_timestamp()' }, \
'meterId': { 'Type': 'text', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'datetime': { 'Type': 'datetime', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'ident': { 'Type': 'varchar(20)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'value': { 'Type': 'float(10,5)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
}
#
# Structure of table elpriser.setup
#
setupTable = { \
'id': { 'Type': 'int(11)', 'Null': 'NO', 'Key': 'MUL', 'Default': 'None', 'Extra': 'auto_increment' }, \
'datetime0': { 'Type': 'datetime', 'Null': 'NO', 'Key': 'PRI', 'Default': 'None', 'Extra': '' }, \
'token0': { 'Type': 'text', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'datetime1': { 'Type': 'datetime', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'token1': { 'Type': 'text', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'nDays_Init': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'nMonths_Bills': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'elAfgift_datoskift': { 'Type': 'text', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'elAfgift_after': { 'Type': 'float(6,3)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'meterId': { 'Type': 'text', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'area_primary': { 'Type': 'text', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'nActiveHours': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '24', 'Extra': '' }, \
'lowPriceLevel': { 'Type': 'float', 'Null': 'NO', 'Key': '', 'Default': '1.5', 'Extra': '' }, \
}
#
# Structure of table elpriser.regninger
#
regningerTable = { \
'id': { 'Type': 'int(11)', 'Null': 'NO', 'Key': 'PRI', 'Default': 'None', 'Extra': 'auto_increment' }, \
'time': { 'Type': 'timestamp', 'Null': 'NO', 'Key': '', 'Default': 'current_timestamp()', 'Extra': 'on update current_timestamp()' }, \
'meterId': { 'Type': 'text', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'year': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'month': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'sum_kwh': { 'Type': 'float(10,2)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'sum_price': { 'Type': 'float(10,2)', 'Null': 'NO', 'Key': '', 'Default': 'None', 'Extra': '' }, \
'krperkwhstd': { 'Type': 'float(10,5)', 'Null': 'NO', 'Key': '', 'Default': '0.00000', 'Extra': '' }, \
'sumprismin': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'sumprisminday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'sumkwhmin': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'sumkwhminday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'pristimemin': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'pristimeminday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'pristimeminhour': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'kwhtimemin': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'kwhtimeminday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'kwhtimeminhour': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'priskwhtimemin': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'priskwhtimeminday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'priskwhtimeminhour': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'sumprismax': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'sumprismaxday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'sumkwhmax': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'sumkwhmaxday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'pristimemax': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'pristimemaxday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'pristimemaxhour': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'kwhtimemax': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'kwhtimemaxday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'kwhtimemaxhour': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'priskwhtimemax': { 'Type': 'float(6,2)', 'Null': 'NO', 'Key': '', 'Default': '0.00', 'Extra': '' }, \
'priskwhtimemaxday': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'priskwhtimemaxhour': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
'nDays': { 'Type': 'int(11)', 'Null': 'NO', 'Key': '', 'Default': '0', 'Extra': '' }, \
}
# https://realpython.com/python-logging/
def mylogging( iSeverity, text ):
timeNow = ( datetime.now() ).strftime( datetimeFormat )
if iSeverity <= 0:
# info / debug
printY( timeNow + ": "+ text )
elif iSeverity == 1:
# warning
logging.warning( timeNow + ": "+ text )
elif iSeverity == 2:
# error
logging.error( timeNow + ": "+ text )
else:
logging.critical( timeNow + ": - CRITICAL - " + text )
def printY( str ):
global bVerbose
if bVerbose:
print( str )
def hourPriceExtra( thisTime, thisConsumption ):
global taxReduction
thisTaxReduction = ( taxReduction == "True" )
# Consumption above 4000 kWh per year will have reduced elafgift
elVarmeLimitYear = 4000 # kWh
elVarmeLimitHour = elVarmeLimitYear / ( 365 * 24 ) # = 0.4566 kW (ElVarme) ellers 0
# Elforbrug i 24 timer med 0,4566 kW = 10,96 kWh
if thisTaxReduction:
# Elvarme
forbrugIkkeElVarme = elVarmeLimitHour
forbrugElVarme = thisConsumption - elVarmeLimitHour # Kan blive negativ, men det udligner sig...
else:
# Ikke elvarme
forbrugIkkeElVarme = thisConsumption
forbrugElVarme = 0
thisDate = thisTime.strftime( dateFormat )
if elAfgift_datoskift.strip() == "" or thisDate < elAfgift_datoskift:
thisElAfgift = Elafgift_now
else:
thisElAfgift = elAfgift_after
'''
if thisTime.year == 2022:
opslagsArray = Cerius2022V
elif thisTime.month >= 4 and thisTime.month <= 9:
opslagsArray = Cerius2023S
else:
opslagsArray = Cerius2023V
'''
thisCeriusTarif = Nettarif_C_time[ thisTime.hour ]
extraPrice = forbrugIkkeElVarme * thisElAfgift + \
forbrugElVarme * elAfgiftOver4000kWh + \
thisConsumption * thisCeriusTarif + \
thisConsumption * elAfgiftAndet
printY( ">>>>>> hourPriceExtra <<<<<<<<<<<<<<<<" )
printY( ">>> thisTime " + str( thisTime ) )
printY( ">>> thisConsumption " + str( thisConsumption ) )
printY( ">>> thisElAfgift " + str( thisElAfgift ) )
printY( ">>> thisCeriusTarif " + str( thisCeriusTarif ) )
printY( ">>> forbrugElVarme " + str( forbrugElVarme ) )
printY( ">>> forbrugIkkeElVarme " + str( forbrugIkkeElVarme ) )
printY( ">>> elAfgiftOver4000kWh" + str( elAfgiftOver4000kWh ) )
printY( ">>> elAfgiftAndet " + str( elAfgiftAndet ) )
printY( ">>> extraPrice =======>" + str( extraPrice ) )
return 1.25 * extraPrice
'''
class Progress( QWidget ):
def __init__( self ):
super( Progress, self ).__init__()
def start( self ):
self.setWindowTitle('Finder Max...')
self.pbar = QProgressBar(self)
self.pbar.setValue(0)
self.resize(300, 100)
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.pbar)
self.setLayout(self.vbox)
self.show()
def set( self, i ):
self.pbar.setValue( i )
self.update()
def stop( self ):
self.hide()
'''
def nDaysInMonth( year, month ):
if month == 2:
leap = 0
if year % 400 == 0:
leap = 1
elif year % 100 == 0:
leap = 0
elif year % 4 == 0:
leap = 1
return 28 + leap
if month in [1,3,5,7,8,10,12]:
return 31
return 30
def updateCharges():
global Nettarif_C_time
global Elafgift_now
global elAfgiftAndet
global elAfgiftOver4000kWh
global postnummer
global adresse
global eloverblikstatus
global balanceSupplierName
global gridOperatorName
global taxReduction
global firstConsumerPartyName
global secondConsumerPartyName
global area_primary
global area_other
global token0
global meterId
global childMeterId
'''
Cerius2022V = [ 0.3028, 0.3028, 0.3028, 0.3028, 0.3028, 0.3028,
0.3028, 0.3028, 0.3028, 0.3028, 0.3028, 0.3028,
0.3028, 0.3028, 0.3028, 0.3028, 0.3028,
0.7835, 0.7835, 0.7835,
0.3028, 0.3028, 0.3028, 0.3028 ]
Cerius2023V = [ 0.2038, 0.2038, 0.2038, 0.2038, 0.2038, 0.2038,
0.6113, 0.6113, 0.6113, 0.6113, 0.6113, 0.6113,
0.6113, 0.6113, 0.6113, 0.6113, 0.6113,
1.8336, 1.8336, 1.8336, 1.8336,
0.6113, 0.6113, 0.6113 ]
Cerius2023S = [ 0.2038, 0.2038, 0.2038, 0.2038, 0.2038, 0.2038,
0.3057, 0.3057, 0.3057, 0.3057, 0.3057, 0.3057,
0.3057, 0.3057, 0.3057, 0.3057, 0.3057,
0.7945, 0.7945, 0.7945, 0.7945,
0.3057, 0.3057, 0.3057 ]
'''
printY( "updateCharges" )
results = GetMeteringPoints()
if results is not None:
result = results[ 'result' ][0]
meterId = result[ "meteringPointId" ]
printY( chr(10) + "- GetMeteringPoints meterid: " + meterId + chr(10) + chr(10) )
results = GetDetails()
if results is not None:
eloverblikstatus = ""
result = results[ 'result' ][0]
succes = result[ 'success' ]
resultlist = result[ 'result' ]
balanceSupplierName = resultlist[ "balanceSupplierName" ] # "Jysk Energi A/S"
gridOperatorName = resultlist[ "gridOperatorName" ] # "Cerius A/S"
taxReduction = resultlist[ "taxReduction" ] # "True"
firstConsumerPartyName = resultlist[ "firstConsumerPartyName" ] # "Jan Jochimsen"
secondConsumerPartyName = resultlist[ "secondConsumerPartyName" ] # "Eva Marie Jochimsen"
streetName = resultlist[ "streetName" ] # "Nyvangsvej"
buildingNumber = resultlist[ "buildingNumber" ] # "93"
floorId = resultlist[ "floorId" ] # ""
roomId = resultlist[ "roomId" ] # ""
postcode = resultlist[ "postcode" ] # "4100"
cityName = resultlist[ "cityName" ] # "Ringsted"
childMeterId = ""
if "childMeteringPoints" in resultlist and taxReduction == "True":
childMeteringPoints = resultlist[ "childMeteringPoints" ]
nChilds = len( childMeteringPoints )
for iChild in range( nChilds ):
childMeteringPoint = childMeteringPoints[ iChild ]
if "typeOfMP" in childMeteringPoint and \
"meteringPointId" in childMeteringPoint and \
"parentMeteringPointId" in childMeteringPoint:
if childMeteringPoint[ "typeOfMP" ] == "D14" and \
childMeteringPoint[ "parentMeteringPointId" ] == meterId:
childMeterId = childMeteringPoint[ "meteringPointId" ]
if childMeterId != "":
break
'''
{
"parentMeteringPointId": "571313181100121195",
"meteringPointId": "571313174001112942",
"typeOfMP": "D14",
"meterReadingOccurrence": "PT1H",
"meterNumber": ""
}
'''
postnummer = int( postcode )
if len( buildingNumber.strip() ) > 0:
buildingNumber = " " + buildingNumber.strip()
if len( floorId.strip() ) > 0:
floorId = " " + floorId.strip()
if len( roomId.strip() ) > 0:
roomId = " " + roomId.strip()
adresse = streetName + buildingNumber + floorId + roomId + ", " + postcode + " " + cityName + " - MÃ¥ler Id: " + meterId
adresse = adresse + chr(10) + "El-selskab: " + balanceSupplierName + " - El-leverandør: " + gridOperatorName
if bVerbose:
printY( "El-selskab: " + balanceSupplierName )
printY( "El-leverandør: " + gridOperatorName )
printY( "Adresse: " + adresse )
# "DK1" = Danmark Vest ( Jylland, Fyn + Øer )
# "DK2" = Danmark Øst ( Sjælland + Øer, Bornholm )
if postnummer < 5000:
area_primary = "DK2"
area_other = "DK1"
else:
area_primary = "DK1"
area_other = "DK2"
CheckConfigChange( token0, nActiveHours, lowPriceLevel, nMonths_Bills, nDays_Init, elAfgift_datoskift, elAfgift_after, meterId, area_primary )
elif foretagOpslag:
adresse = eloverblikstatus
mylogging( 1, "GetDetails - ?? - Results " + str( results ) )
results = GetCharges( meterId )
if results is not None:
result = results[ 'result' ][0]
succes = result[ 'success' ]
resultlist = result[ 'result' ]
subscriptions = resultlist[ 'subscriptions' ][ 0 ]
abonnement = subscriptions[ 'price' ]
tariffs = resultlist[ 'tariffs' ]
nTariffs = len( tariffs )
thisTransmissions_nettarif = 0
thisSystemtarif = 0
thisBalancetarif_for_forbrug = 0
elAfgiftOver4000kWh = 0
Nettarif_C_time = []
for iTariff in range( nTariffs ):
thisTariff = tariffs[ iTariff ]
nameTariff = thisTariff[ 'name' ]
pricesTariff = thisTariff[ 'prices' ]
nPrices = len( pricesTariff )
bUsed = True
if nPrices == 1:
if nameTariff == "Transmissions nettarif":
thisTransmissions_nettarif = pricesTariff[ 0 ][ 'price' ]
elif nameTariff == "Systemtarif":
thisSystemtarif = pricesTariff[ 0 ][ 'price' ]
elif nameTariff == "Balancetarif for forbrug":
thisBalancetarif_for_forbrug = pricesTariff[ 0 ][ 'price' ]
elif nameTariff == "Reduceret elafgift":
elAfgiftOver4000kWh = pricesTariff[ 0 ][ 'price' ]
elif nameTariff == "Elafgift":
Elafgift_now = pricesTariff[ 0 ][ 'price' ]
else:
bUsed = False
else:
if nPrices == 24:
if nameTariff == "Nettarif C time":
for iTariff in range( 24 ):
thisPrice = pricesTariff[ iTariff ][ 'price' ]
Nettarif_C_time.append( thisPrice )
elif nameTariff == "Rabat på Cerius' nettarif":
# Bruges pt ikke
iDummy = 1
else:
bUsed = False
if not bUsed:
mylogging( 1, "GetCharges - ?? - Tariff name: " + str( nameTariff ) + " Antal: " + str( nPrices ) )
elAfgiftAndet = thisTransmissions_nettarif + thisSystemtarif + thisBalancetarif_for_forbrug
elif foretagOpslag:
mylogging( 1, "GetCharges - meterId - ??- Results " + str( results ) )
if childMeterId != "":
results = GetCharges( childMeterId )
if results is not None:
result = results[ 'result' ][0]
succes = result[ 'success' ]
resultlist = result[ 'result' ]
tariffs = resultlist[ 'tariffs' ]
nTariffs = len( tariffs )
for iTariff in range( nTariffs ):
thisTariff = tariffs[ iTariff ]
nameTariff = thisTariff[ 'name' ]
pricesTariff = thisTariff[ 'prices' ]
nPrices = len( pricesTariff )
if nPrices == 1:
if nameTariff == "Elafgift":
Elafgift_now = pricesTariff[ 0 ][ 'price' ]
else:
mylogging( 1, "GetCharges - childMeterId - ??- Results " + str( results ) )
'''
results ::
{
"result": [
{
"success": true,
"errorCode": 10000,
"errorText": "NoError",
"id": "571313181100121195",
"stackTrace": null,
"result": {
"fees": [],
"meteringPointId": "571313181100121195",
"subscriptions": [
{
"subscriptionId": null,
"name": "Net abo C forbrug time",
"description": "Abonnement, hvor aftagepunktet typisk er i 0,4 kV-nettet med en timeaflæst måler",
"owner": "5790000705184",
"validFromDate": "2018-04-30T22:00:00.000Z",
"validToDate": null,
"price": 46.583333,
"quantity": 1
}
],
"tariffs": [
{
"tariffId": null,
"name": "Transmissions nettarif",
"description": "Netafgiften, for både forbrugere og producenter, dækker omkostninger til drift og vedligehold af det overordnede elnet (132/150 og 400 kv nettet) og drift og vedligehold af udlandsforbindelserne.",
"owner": "5790000432752",
"periodType": "P1D",
"validFromDate": "2014-12-31T23:00:00.000Z",
"validToDate": null,
"prices": [
{
"position": "1",
"price": 0.049
}
]
},
{
"tariffId": null,
"name": "Systemtarif",
"description": "Systemafgiften dækker omkostninger til forsyningssikkerhed og elforsyningens kvalitet.",
"owner": "5790000432752",
"periodType": "P1D",
"validFromDate": "2014-12-31T23:00:00.000Z",
"validToDate": null,
"prices": [
{
"position": "1",
"price": 0.061
}
]
},
{
"tariffId": null,
"name": "Balancetarif for forbrug",
"description": "Balancetarif for forbrug",
"owner": "5790000432752",
"periodType": "P1D",
"validFromDate": "2014-12-31T23:00:00.000Z",
"validToDate": null,
"prices": [
{
"position": "1",
"price": 0.00229
}
]
},
{
"tariffId": null,
"name": "Reduceret elafgift",
"description": "Reduceret elafgift for elvarmekunder",
"owner": "5790000432752",
"periodType": "P1D",
"validFromDate": "2015-05-31T22:00:00.000Z",
"validToDate": null,
"prices": [
{
"position": "1",
"price": 0.008
}
]
},
{
"tariffId": null,
"name": "Rabat på Cerius' nettarif",
"description": "Bindende midlertidig tarifnedsættelse, hvor aftagepunktet typisk er i 0,4 kV-nettet med timeaflæst måler.",
"owner": "5790000705184",
"periodType": "PT1H",
"validFromDate": "2021-01-31T23:00:00.000Z",
"validToDate": null,
"prices": [
{
"position": "1",
"price": 0
},
{
"position": "2",
"price": 0
},
{
"position": "3",
"price": 0
},
{
"position": "4",
"price": 0
},
{
"position": "5",
"price": 0
},
{
"position": "6",
"price": 0
},
{
"position": "7",
"price": 0
},
{
"position": "8",
"price": 0
},
{
"position": "9",
"price": 0
},
{
"position": "10",
"price": 0
},
{
"position": "11",
"price": 0
},
{
"position": "12",
"price": 0
},
{
"position": "13",
"price": 0
},
{
"position": "14",
"price": 0
},
{
"position": "15",
"price": 0
},
{
"position": "16",
"price": 0
},
{
"position": "17",
"price": 0
},
{
"position": "18",
"price": 0
},
{
"position": "19",
"price": 0
},
{
"position": "20",
"price": 0
},
{
"position": "21",
"price": 0
},
{
"position": "22",
"price": 0
},
{
"position": "23",
"price": 0
},
{
"position": "24",
"price": 0
}
]
},
{
"tariffId": null,
"name": "Nettarif C time",
"description": "Tarif, hvor aftagepunktet typisk er i 0,4 kV-nettet med timeaflæst måler",
"owner": "5790000705184",
"periodType": "PT1H",
"validFromDate": "2018-04-30T22:00:00.000Z",
"validToDate": null,
"prices": [
{
"position": "1",
"price": 0.3028
},
{
"position": "2",
"price": 0.3028
},
{
"position": "3",
"price": 0.3028
},
{
"position": "4",
"price": 0.3028
},
{
"position": "5",
"price": 0.3028
},
{
"position": "6",
"price": 0.3028
},
{
"position": "7",
"price": 0.3028
},
{
"position": "8",
"price": 0.3028
},
{
"position": "9",
"price": 0.3028
},
{
"position": "10",
"price": 0.3028
},
{
"position": "11",
"price": 0.3028
},
{
"position": "12",
"price": 0.3028
},
{
"position": "13",
"price": 0.3028
},
{
"position": "14",
"price": 0.3028
},
{
"position": "15",
"price": 0.3028
},
{
"position": "16",
"price": 0.3028
},
{
"position": "17",
"price": 0.3028
},
{
"position": "18",
"price": 0.7885
},
{
"position": "19",
"price": 0.7885
},
{
"position": "20",
"price": 0.7885
},
{
"position": "21",
"price": 0.3028
},
{
"position": "22",
"price": 0.3028
},
{
"position": "23",
"price": 0.3028
},
{
"position": "24",
"price": 0.3028
}
]
}
]
}
}
]
}
'''
'''
Details
{
"result": [
{
"success": true,
"errorCode": 10000,
"errorText": "NoError",
"id": "571313181100121195",
"stackTrace": null,
"result": {
"balanceSupplierName": "Jysk Energi A/S",
"balanceSupplierStartDate": "2020-06-30T22:00:00.000Z",
"meteringPointId": "571313181100121195",
"parentMeteringPointId": "",
"typeOfMP": "E17",
"energyTimeSeriesMeasureUnit": "KWH",
"estimatedAnnualVolume": "15281",
"settlementMethod": "D01",
"meterNumber": "312343",
"gridOperatorName": "Cerius A/S",
"meteringGridAreaIdentification": "740",
"netSettlementGroup": "0",
"physicalStatusOfMP": "E22",
"consumerCategory": "212",
"powerLimitKW": "",
"powerLimitA": "",
"subTypeOfMP": "D01",
"productionObligation": "",
"mpCapacity": "",
"mpConnectionType": "",
"disconnectionType": "D01",
"product": "Item8716867000030",
"consumerCVR": "",
"dataAccessCVR": "",
"consumerStartDate": "2020-06-30T22:00:00.000Z",
"meterReadingOccurrence": "PT1H",
"mpReadingCharacteristics": "",
"meterCounterDigits": "6.0",
"meterCounterMultiplyFactor": "1.0",
"meterCounterUnit": "KWH",
"meterCounterType": "D01",
"taxReduction": "True",
"taxSettlementDate": "2016-02-29T23:00:00.000Z",
"mpRelationType": "",
"firstConsumerPartyName": "Jan Jochimsen",
"secondConsumerPartyName": "Eva Marie Jochimsen",
"streetCode": "0188",
"streetName": "Nyvangsvej",
"buildingNumber": "93",
"floorId": "",
"roomId": "",
"postcode": "4100",
"cityName": "Ringsted",
"citySubDivisionName": "",
"municipalityCode": "259",
"locationDescription": "",
"contactAddresses": [
{
"contactName1": "Jan Jochimsen",
"contactName2": "",
"addressCode": "D01",
"streetName": "Nyvangsvej",
"buildingNumber": "93",
"floorId": "",
"roomId": "",
"citySubDivisionName": "",
"postcode": "4100",
"cityName": "Ringsted",
"countryName": "DK",
"contactPhoneNumber": "",
"contactMobileNumber": "23244800",
"contactEmailAddress": "jan.jochimsen@hjemmeriet.com",
"contactType": null
},
{
"contactName1": "Jan Jochimsen",
"contactName2": "",
"addressCode": "D04",
"streetName": "Nyvangsvej",
"buildingNumber": "93",
"floorId": "",
"roomId": "",
"citySubDivisionName": "",
"postcode": "4100",
"cityName": "Ringsted",
"countryName": "DK",
"contactPhoneNumber": "",
"contactMobileNumber": "23244800",
"contactEmailAddress": "jan.jochimsen@hjemmeriet.com",
"contactType": null
}
],
"childMeteringPoints": [
{
"parentMeteringPointId": "571313181100121195",
"meteringPointId": "571313174001112942",
"typeOfMP": "D14",
"meterReadingOccurrence": "PT1H",
"meterNumber": ""
}
]
}
}
]
}
'''
## https://energinet.dk/Energidata/DataHub/Adgang-til-data
def GetMeteringPoints( mTry = 10 ):
## eloverblik swagger: https://api.eloverblik.dk/customerapi/index.html
## https://energinet.dk/Energidata/DataHub
'''
curl -X 'GET' \
'https://api.eloverblik.dk/customerapi/api/meteringpoints/meteringpoints?includeAll=false' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOiJDdXN0b21lckFQSV9EYXRhQWNjZXNzIiwidG9rZW5pZCI6ImU1M2UxZmUxLWI2MWItNGY1Yy1iOWI5LTYyODZiZWY2MjZjMCIsIndlYkFwcCI6WyJDdXN0b21lckFwaSIsIkN1c3RvbWVyQXBpIiwiQ3VzdG9tZXJBcHBBcGkiXSwianRpIjoiZTUzZTFmZTEtYjYxYi00ZjVjLWI5YjktNjI4NmJlZjYyNmMwIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiJQSUQ6OTIwOC0yMDAyLTItMzc1Nzg1MjEwMDkxIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZ2l2ZW5uYW1lIjoiSmFuIEpvY2hpbXNlbiIsImxvZ2luVHlwZSI6IktleUNhcmQiLCJwaWQiOiI5MjA4LTIwMDItMi0zNzU3ODUyMTAwOTEiLCJ0eXAiOiJQT0NFUyIsInVzZXJJZCI6IjE0MTkxMiIsImV4cCI6MTY2NzU1NDYxMiwiaXNzIjoiRW5lcmdpbmV0IiwidG9rZW5OYW1lIjoiZWxvdmVyYmxpazIwMjIxMDI2IiwiYXVkIjoiRW5lcmdpbmV0In0.fjFeTZqObfrhDmesQ3Of3FPZnS14FboEhkRi6L5ARyI'
'''
global bVerbose
global filedebug
global token1
if bVerbose:
printY( "GetMeteringPoints" )
if not CheckElOverblikToken( mTry ):
return None
try:
headers = { "accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer " + token1 }
url = "https://api.eloverblik.dk/customerapi/api/meteringpoints/meteringpoints?includeAll=false"
if bVerbose:
printY( "url: " + url )
print( "headers: ", headers )
nTry = 0
while True:
print( datetime.now(), "GetMeteringPoints/eloverblik", file = filedebug )
print( " - url: ", url, file = filedebug )
print( " - headers:", headers, file = filedebug )
response = requests.get( url = url, headers = headers )
print( " - Status code: ", str( response.status_code ), file = filedebug )
print( " - Status text:", response.text, file = filedebug )
if bVerbose:
printY( "Status code: " + str( response.status_code ) )
printY( "Text: " + response.text )
if ( response.status_code == 503 or response.status_code == 429 ) and nTry < mTry: # Service not available
print( " - Fails - return...", file = filedebug )
print( "Eloverblik/meteringpoints - (", response.status_code, ") ..." )
return None
'''
print( "Eloverblik/meteringpoints - Kø (", response.status_code, ") ... venter i 60 sec" )
nTry = nTry + 1
time.sleep( 60 ) # Wait 60 seconds and try again
print( "Prøver igen..." )
'''
elif response.status_code == 200:
print( " - Success...", file = filedebug )
results = response.json()
if bVerbose:
printY( chr(10) + chr(10) )
print( json.dumps( results, indent = 4 ) )
printY( chr(10) + chr(10) )
return results
else:
return None
except Exception as e:
print( " - >>> Exception <<<" + "{}".format(e), file = filedebug )
sMessage = ">>> GetMeteringPoints - Exception <<<" + chr(10) + "{}".format(e)
mylogging( 2, sMessage ) # ShowMessageBox
return None
def GetDetails( mTry = 10 ):
## eloverblik swagger: https://api.eloverblik.dk/customerapi/index.html
'''
curl -X 'POST' \
'https://api.eloverblik.dk/customerapi/api/meteringpoints/meteringpoint/getdetails' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOiJDdXN0b21lckFQSV9EYXRhQWNjZXNzIiwidG9rZW5pZCI6IjQ5YmFmYWUyLWFiNmYtNGVmMy1hMWEyLWFkYWMwNzMyODYxZCIsIndlYkFwcCI6WyJDdXN0b21lckFwaSIsIkN1c3RvbWVyQXBpIiwiQ3VzdG9tZXJBcHBBcGkiXSwianRpIjoiNDliYWZhZTItYWI2Zi00ZWYzLWExYTItYWRhYzA3MzI4NjFkIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiJQSUQ6OTIwOC0yMDAyLTItMzc1Nzg1MjEwMDkxIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZ2l2ZW5uYW1lIjoiSmFuIEpvY2hpbXNlbiIsImxvZ2luVHlwZSI6IktleUNhcmQiLCJwaWQiOiI5MjA4LTIwMDItMi0zNzU3ODUyMTAwOTEiLCJ0eXAiOiJQT0NFUyIsInVzZXJJZCI6IjE0MTkxMiIsImV4cCI6MTY2NzIzNDAxNiwiaXNzIjoiRW5lcmdpbmV0IiwidG9rZW5OYW1lIjoiZWxvdmVyYmxpazIwMjIxMDI2IiwiYXVkIjoiRW5lcmdpbmV0In0.nlvkH5PFxZKISbWiy9AZah_vHV7Y39RgIpzEdMrsZu0' \
-H 'Content-Type: application/json' \
-d '{
"meteringPoints": {
"meteringPoint": [
"571313181100121195"
]
}
}'
'''
global bVerbose
global filedebug
global token1
global eloverblikstatus
if bVerbose:
printY( "GetDetails" )
eloverblikstatus = ""
if not CheckElOverblikToken( mTry ):
return None
headers = { "accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer " + token1 }
data = { "meteringPoints": { "meteringPoint": [ meterId ] } }
url = "https://api.eloverblik.dk/customerapi/api/meteringpoints/meteringpoint/getdetails"
if bVerbose:
printY( "url: " + url )
printY( "headers: " + str( headers ) )
printY( "data: " + str( data ) )
nTry = 0
try:
while True:
print( datetime.now(), "GetDetails/eloverblik", file = filedebug )
print( " - url: ", url, file = filedebug )
print( " - headers:", headers, file = filedebug )
print( " - data:", str( data ), file = filedebug )
response = requests.post( url = url, headers = headers, json = data ) # or json = data replaced by data = json.dumps( data )
print( " - Status code: ", str( response.status_code ), file = filedebug )
print( " - Status text:", response.text, file = filedebug )
if bVerbose:
printY( "Status code: " + str( response.status_code ) )
printY( "Text: " + response.text )
if ( response.status_code == 503 or response.status_code == 429 ) and nTry < mTry: # Service not available
print( " - Fails - return...", file = filedebug )
print( "Eloverblik/getdetails - (", response.status_code, ") ..." )
eloverblikstatus = response.text + " - (" + str( response.status_code ) + ") - GetDetails"
return None
'''
print( "Eloverblik/getdetails - Kø (", response.status_code, ") ... venter i 60 sec" )
nTry = nTry + 1
time.sleep( 60 ) # Wait 60 seconds and try again
'''
elif response.status_code == 200:
print( " - Success...", file = filedebug )
results = response.json()
if bVerbose:
printY( chr(10) + chr(10) )
print( json.dumps( results, indent = 4 ) )
printY( chr(10) + chr(10) )
return results
else:
return None
except Exception as e:
print( " - >>> Exception <<<{}".format(e), file = filedebug )
sMessage = ">>> GetDetails - Exception <<<" + chr(10) + "{}".format(e)
mylogging( 2, sMessage ) # ShowMessageBox
return None
def GetCharges( thisMeterId, mTry = 10 ):
'''
curl -X 'POST' \
'https://api.eloverblik.dk/customerapi/api/meteringpoints/meteringpoint/getcharges' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOiJDdXN0b21lckFQSV9EYXRhQWNjZXNzIiwidG9rZW5pZCI6ImEwYTdhZTczLTZmMjItNDFlNy1iNDMxLTI5M2Y2YTM1YTFlMCIsIndlYkFwcCI6WyJDdXN0b21lckFwaSIsIkN1c3RvbWVyQXBpIiwiQ3VzdG9tZXJBcHBBcGkiXSwianRpIjoiYTBhN2FlNzMtNmYyMi00MWU3LWI0MzEtMjkzZjZhMzVhMWUwIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiJQSUQ6OTIwOC0yMDAyLTItMzc1Nzg1MjEwMDkxIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZ2l2ZW5uYW1lIjoiSmFuIEpvY2hpbXNlbiIsImxvZ2luVHlwZSI6IktleUNhcmQiLCJwaWQiOiI5MjA4LTIwMDItMi0zNzU3ODUyMTAwOTEiLCJ0eXAiOiJQT0NFUyIsInVzZXJJZCI6IjE0MTkxMiIsImV4cCI6MTY2NzEyMzA1MywiaXNzIjoiRW5lcmdpbmV0IiwidG9rZW5OYW1lIjoiZWxvdmVyYmxpazIwMjIxMDI2IiwiYXVkIjoiRW5lcmdpbmV0In0.XsyTYwcB0plqxkJJACVLVz-gVPF9Ik4R5NNWAXdQfgg' \
-H 'Content-Type: application/json' \
-d '{
"meteringPoints": {
"meteringPoint": [
"571313181100121195"
]
}
}'
'''
global bVerbose
global filedebug
global token1
global eloverblikstatus
printY( "GetCharges" )
if not CheckElOverblikToken( mTry ):
return None
headers = { "accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer " + token1 }
data = { "meteringPoints": { "meteringPoint": [ thisMeterId ] } }
url = "https://api.eloverblik.dk/customerapi/api/meteringpoints/meteringpoint/getcharges"
printY( "url: " + url )
printY( "headers: " + str( headers ) )
printY( "data: " + str( data ) )
nTry = 0
try:
while True:
print( datetime.now(), "GetCharges/eloverblik", file = filedebug )
print( " - url: ", url, file = filedebug )
print( " - headers:", headers, file = filedebug )
print( " - data:", str( data ), file = filedebug )
response = requests.post( url = url, headers = headers, json = data ) # or json = data replaced by data = json.dumps( data )
print( " - Status code: ", str( response.status_code ), file = filedebug )
print( " - Status text:", response.text, file = filedebug )
printY( "Status code: " + str( response.status_code ) )
printY( "Text: " + response.text )
if ( response.status_code == 503 or response.status_code == 429 ) and nTry < mTry: # Service not available
print( " - Fails - return...", file = filedebug )
print( "Eloverblik/getcharges - (", response.status_code, ") ..." )
eloverblikstatus = response.text + " - (" + str( response.status_code ) + ") - GetCharges"
return None
'''
print( "Eloverblik/getcharges - Kø (", response.status_code, ") ... venter i 60 sec" )
nTry = nTry + 1
time.sleep( 60 ) # Wait 60 seconds and try again
'''
elif response.status_code == 200:
print( " - Success...", file = filedebug )
results = response.json()
if bVerbose:
printY( chr(10) + chr(10) )
print( json.dumps( results, indent = 4 ) )
printY( chr(10) + chr(10) )
return results
else:
return None
except Exception as e:
print( " - >>> Exception <<<{}".format(e), file = filedebug )
sMessage = ">>> GetCharges - Exception <<<" + chr(10) + "{}".format(e)
mylogging( 2, sMessage ) # ShowMessageBox
return None
# Hent forbrugsdata for en tidsperiode
def GetEnergyPricesX( dateFrom, thisArea, dateTo = None ):
global bSpotEstimate
global bSpotMissing
if dateTo is None:
printY( "GetEnergyPricesX - Get spot prices..." )
# Spot pricer
dataTime, dataElPrisT, dataElPris = GetEnergyPrices( dateFrom, thisArea, dateTo )
bSpotMissing = False
if len( dataTime ) > 0:
# Data OK - Gem i db...
printY( "GetEnergyPricesX - Got spot prices... Store..." )
StoreTimeseries( "ElPrisT_" + thisArea, dataTime, dataElPrisT )
StoreTimeseries( "ElPris_" + thisArea, dataTime, dataElPris )
# We SHOULD always get minimum 48 hours - after 13:00 also 1 day ahead, total 72 hours
# Some time the system fails, and only returns 24 hours = yesterday...
# Patch data = copy yesterday to today, in that case
bSpotEstimate = False
if len( dataTime ) == 24:
bSpotEstimate = True
for iHour in range( 24 ):
datetimeval = dataTime[ iHour ]
dv1 = dataElPrisT[ iHour ]
dv2 = dataElPris[ iHour ]
dataTime.append( datetimeval + timedelta( hours = 24 ) )
dataElPrisT.append( dv1 )
dataElPris.append( dv2 )
else:
# Hent hvad der er i db...
recordsT = GetData( "ElprisT_" + thisArea, dateFrom, "" )
records = GetData( "Elpris_" + thisArea, dateFrom, "" )
if records is not None and recordsT is not None:
printY( "GetEnergyPricesX - No spot prices... Get from db..." )
# Der var data gemt i db
nHours = len( records )
bSpotMissing = ( nHours == 0 )
for i in range( nHours ):
row = records[ i ]
rowT = recordsT[ i ]
dataTime.append( row[ 0 ] )
dataElPrisT.append( rowT[ 1 ] )
dataElPris.append( row[ 1 ] )
if len( dataTime ) == 24:
bSpotEstimate = True
for iHour in range( 24 ):
datetimeval = dataTime[ iHour ]
dv1 = dataElPrisT[ iHour ]
dv2 = dataElPris[ iHour ]
dataTime.append( datetimeval + timedelta( hours = 24 ) )
dataElPrisT.append( dv1 )
dataElPris.append( dv2 )
else:
bSpotMissing = True
# Empty arrays will be returned, as returned from GetEnergyPrices...
printY( "GetEnergyPricesX - No spot prices... No data from db..." )
if bSpotMissing:
fromDate = datetime.strptime( dateFrom, dateFormat )
dataTime = []
dataElPrisT = []
dataElPris = []
ll = len( dataTime_2_H )
if ll >= 24:
l0 = ll - 24
for iDay in range( 2 ):
for iHour in range( 24 ):
datetimeval = fromDate + timedelta( days = iDay, hours = iHour )
dv1 = dataPrice_2_H[ l0 + iHour ]
dv2 = dataElPrice_2_H[ l0 + iHour ]
dataTime.append( datetimeval )
dataElPrisT.append( dv1 )
dataElPris.append( dv2 )
else:
# Hent historiske data fra db...
printY( "GetGetEnergyPricesX - Historisk - dateFrom, thisArea, dateto: " + str( dateFrom ) + " - " + thisArea + " - " + str( dateTo ) )
recordsT = GetData( "ElprisT_" + thisArea, dateFrom, dateTo )
records = GetData( "Elpris_" + thisArea, dateFrom, dateTo )
dataTime = []
dataElPrisT = []
dataElPris = []
bOK = False
if records is not None and recordsT is not None:
# Der var data gemt i db
fromDate = datetime.strptime( dateFrom, dateFormat )
toDate = datetime.strptime( dateTo, dateFormat )
timePeriod = toDate - fromDate
nHours = int( timePeriod.total_seconds() / ( 60 * 60 ) )
dateInMarts = datetime( fromDate.year, 3, 31 )
idx = ( dateInMarts.weekday() + 1 ) % 7 # MON = 0, SUN = 6 -> SUN = 0 .. SAT = 6
dateInMarts = dateInMarts - timedelta( days = idx )
if fromDate <= dateInMarts and toDate > dateInMarts:
nHours = nHours - 1
nRecords = len( records )
nRecordsT = len( recordsT )
if nHours == nRecords and nHours == nRecordsT:
printY( "GetGetEnergyPricesX - data i db - nHours, nRecords, nRecordsT: " + str( nHours ) + ", " + str( nRecords ) + ", " + str( nRecordsT ) )
# Alt er hentet perfekt fra db
for i in range( nHours ):
row = records[ i ]
rowT = recordsT[ i ]
dataTime.append( row[ 0 ] )
dataElPrisT.append( rowT[ 1 ] )
dataElPris.append( row[ 1 ] )
bOK = True
else:
printY( "GetGetEnergyPricesX - Ingen data i db - nHours, nRecords, nRecordsT: " + str( nHours ) + ", " + str( nRecords ) + ", " + str( nRecordsT ) )
if not bOK:
printY( "GetGetEnergyPricesX - Hent nye data..." )
# Hent nye data
dataTime, dataElPrisT, dataElPris = GetEnergyPrices( dateFrom, thisArea, dateTo )
if len( dataTime ) > 0:
# Data OK - Gem i db...
StoreTimeseries( "ElPrisT_" + thisArea, dataTime, dataElPrisT )
StoreTimeseries( "ElPris_" + thisArea, dataTime, dataElPris )
return dataTime, dataElPrisT, dataElPris
def GetEnergyPrices( thisDate, thisArea, thisEndDate = None ):
global dateTime_NextUpdate
global bVerbose
global dotdotdots
global filedebug
global bSpotWaiting
bSpotWaiting = False
#
# Elspotprics: https://www.energidataservice.dk/guides/api-guides
## url = 'https://api.energidataservice.dk/v2/dataset/Elspotprices?limit=2000&start=' + thisDate + '&filter={%22PriceArea%22:%22' + thisArea + '%22}&sort=HourDK'
url = 'https://api.energidataservice.dk/dataset/Elspotprices?limit=0&start=' + thisDate + '&filter={%22PriceArea%22:%22' + thisArea + '%22}&sort=HourDK'
if thisEndDate is not None:
# Historical data
url = url + '&end=' + thisEndDate
else:
# Yesterday, Today and Day-ahead
timeNow = datetime.now()
if timeNow.hour < 13:
# Current time is before 13:00
# Next update at 13:00
dateTime_NextUpdate = timeNow.replace( hour = 13, minute = 0, second = 0, microsecond = 0 )
if False: # This following code will not be relevant - Day-Ahead prices are ALWAYS at 13:00
# Try to request day-ahead data earlier than 13:00
delta = 5 # 10, 20, 40, 1:20, 2:40, 5:20
# 13:00 - 0:05 = 12:55
# 12:55 - 0:10 = 12:45
# 12:45 - 0:20 = 12:25
# 12:25 - 0:40 = 11:45
# 11:45 - 1:20 = 10:25
# 10:25 - 2:40 = 07:45
# 07:45 - 5:20 = 02:25
dateTime_NextUpdate_Try = dateTime_NextUpdate - timedelta( minutes = delta )
while dateTime_NextUpdate_Try > timeNow:
dateTime_NextUpdate = dateTime_NextUpdate_Try
delta = 2 * delta
dateTime_NextUpdate_Try = dateTime_NextUpdate - timedelta( minutes = delta )
if bVerbose:
print( timeNow, "GetEnergyPrices - NextUpdate:", dateTime_NextUpdate )
else:
# Current time is past 13:00
# Between 13:00 and 24:00 we should have data for yesterday, today and tomorrow, ie. 3 x 24 hours
# In case the retrieval of tomorrow fails (after 13:00 hour) then keep trying to
# retrieve Day-ahead data...
if len( dataTime_2_A ) >= 3 * 24 - 2: # -2 to be sure...
# We got Day-ahead data... Next update at midnight...
timeNow = timeNow + timedelta( days = 1 )
dateTime_NextUpdate = timeNow.replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
else:
bSpotWaiting = ( len( dataTime_2_A ) > 0 )
if bSpotWaiting:
## Missing Day-ahead data - Keep trying
printY( "---> Missing Day-ahead data - will retry..." )
else:
dotdotdots = ""
dateTime_NextUpdate = timeNow
if bVerbose:
printY( "GetEnergyPrices - url " + url )
nTry = 0
mTry = 10
if not foretagOpslag:
return [], [], []
while True:
try:
print( datetime.now(), "GetEnergyPrices/energidataservice", file = filedebug )
print( " - url: ", url, file = filedebug )
response = requests.get( url = url )
print( " - Status code: ", str( response.status_code ), file = filedebug )
print( " - Status text:", response.text, file = filedebug )
if bVerbose:
print( "GetEnergyPrices - Response", response )
if ( response.status_code == 503 or response.status_code == 429 ) and nTry < mTry: # Service not available
print( " - Fails - return...", file = filedebug )
dataTime = []
dataPrice = []
dataElPrice = []
print( "GetEnergyPrices - Energidataservice/Elspotprices - (", response.status_code, ") ..." )
return dataTime, dataPrice, dataElPrice
'''
print( "GetEnergyPrices - Energidataservice/Elspotprices - Kø (", response.status_code, ") ... venter i 60 sec" )
nTry = nTry + 1
time.sleep( 60 ) # Wait 60 seconds and try again
'''
else:
if response.status_code == 200:
print( " - Success...", file = filedebug )
result = response.json()
if bVerbose:
printY( "GetEnergyPrices - response" + chr(10) + chr(10) )
print( json.dumps( result, indent = 4 ) )
printY( chr(10) + chr(10) )
records = result.get( 'records', [] )
dataTime, dataPrice, dataElPrice = makets( records )
else:
sMessage = "GetEnergyPrices - Could not get energy prices for period starting at " + startDate + " and ending at " + endDate
sMessage = sMessage + chr(10) + "Response code: " + str( response.status_code )
sMessage = sMessage + chr(10) + "Response text: " + response.text
sMessage = sMessage + chr(10) + "Url: " + url
mylogging( 1, sMessage ) # ShowMessageBox( sMessage )
dataTime = []
dataPrice = []
dataElPrice = []
return dataTime, dataPrice, dataElPrice
except Exception as e:
print( " - >>> Exception <<<{}".format(e), file = filedebug )
mylogging( 2, "GetEnergyPrices - Problem: {}".format( e ) )
return [], [], []
# Define classes to handle a graph area with one or two axes
class MplCanvas( FigureCanvas ):
def __init__( self, parent = None, width = 5, height = 2, dpi = 120, adjustleft = 0.06, adjustright = 0.94, adjustbottom = 0.1, adjusttop = 0.9 ):
fig = Figure( figsize = ( width, height ), dpi = dpi )
self.axes = fig.add_subplot( 111 )
## fig.tight_layout()
fig.subplots_adjust( left = adjustleft, bottom = adjustbottom, right = adjustright, top = adjusttop )
super( MplCanvas, self ).__init__( fig )
class MplCanvas2( FigureCanvas ):
def __init__( self, parent=None, width = 5, height = 2, dpi = 120, adjustleft = 0.06, adjustright = 0.94, adjustbottom = 0.1, adjusttop = 0.9 ):
self.fig = Figure( figsize = ( width, height ), dpi = dpi )
self.axes = self.fig.add_subplot( 111 )
self.ax2 = self.axes.twinx()
## fig.tight_layout()
self.fig.subplots_adjust( left = adjustleft, bottom = adjustbottom, right = adjustright, top = adjusttop )
super( MplCanvas2, self ).__init__( self.fig )
# GUI-form based on PyQt5 - QWidget
def multicolor_ylabel(ax,list_of_strings,list_of_colors,axis='x',anchorpad=0, box_to_anchor=( 0.5, 0.5 ),**kw):
"""this function creates axes labels with multiple colors
ax specifies the axes object where the labels should be drawn
list_of_strings is a list of all of the text items
list_if_colors is a corresponding list of colors for the strings
axis='x', 'y', or 'both' and specifies which label(s) should be drawn"""
from matplotlib.offsetbox import AnchoredOffsetbox, TextArea, HPacker, VPacker
# x-axis label
if axis=='x' or axis=='both':
boxes = [TextArea(text, textprops=dict(color=color, ha='left',va='bottom',**kw))
for text,color in zip(list_of_strings,list_of_colors) ]
xbox = HPacker(children=boxes,align="center",pad=0, sep=5)
anchored_xbox = AnchoredOffsetbox(loc=10, child=xbox, pad=anchorpad,frameon=False,bbox_to_anchor=(0.2, -0.09),
bbox_transform=ax.transAxes, borderpad=0.)
ax.add_artist( anchored_xbox )
# y-axis label
if axis=='y' or axis=='both':
boxes = [TextArea(text, textprops=dict(color=color, ha='left',va='bottom',rotation=90,**kw))
for text,color in zip(list_of_strings[::-1],list_of_colors) ]
ybox = VPacker(children=boxes,align="center", pad=0, sep=5)
anchored_ybox = AnchoredOffsetbox(loc=10, child=ybox, pad=anchorpad, frameon=False, bbox_to_anchor=box_to_anchor,
bbox_transform=ax.transAxes, borderpad=0.)
ax.add_artist( anchored_ybox )
## multicolor_ylabel(ax,('Line1','and','Line2','with','extra','colors!'),('r','k','b','k','m','g'),axis='both',size=15,weight='bold')
## https://stackoverflow.com/questions/33159134/matplotlib-y-axis-label-with-multiple-colors
class WinForm( QWidget ):
def __init__( self, parent = None ):
super( WinForm, self ).__init__( parent )
global labelTooltip
global timerTooltipShow
global timerTooltipHide
self.helpID = "Elpriser"
# Tooltip label
labelTooltip = QLabel( self )
labelTooltip.setWindowFlags( QtCore.Qt.ToolTip )
labelTooltip.setTextFormat( Qt.RichText )
labelTooltip.setWindowTitle( "Forbrugstal" )
# Use a border + light yellow background + rounded corners
tooltipStyle = "border: 1px solid black; padding:5px; background-color:#ffffe0; opacity:0.5; border-radius:5px;"
labelTooltip.setStyleSheet( tooltipStyle )
timerTooltipShow = QTimer()
timerTooltipShow.timeout.connect( showTooltip ) # Timer to show tooltip
timerTooltipHide = QTimer()
timerTooltipHide.timeout.connect( hideTooltip ) # Timer to hide tooltip
# Create tab-page widget
tabwidget = QTabWidget()
self.tabWidget = tabwidget
self.tabWidget.currentChanged.connect( self.tabChanged )
self.tab1 = QWidget()
tabwidget.addTab( self.tab1, "Aktuelt" )
self.tab1.helpID = "Aktuelt"
self.tab2 = QWidget()
tabwidget.addTab( self.tab2, "Opgørelse" )
self.tab2.helpID = "Opgørelse"
self.tab3 = QWidget()
tabwidget.addTab( self.tab3, "Konfiguration" )
self.tab3.helpID = "Konfiguration"
self.tab4 = QWidget()
tabwidget.addTab( self.tab4, "Hjælp" )
self.tab4.helpID = "Hjælp"
# Main form layout
layout = QGridLayout()
self.setLayout( layout )
layout.addWidget( tabwidget, 0, 0, 1, 1 )
# Build grid layouts
layout1 = QGridLayout()
self.tab1.setLayout( layout1 )
layout2 = QGridLayout()
self.layout2 = layout2
self.tab2.setLayout( layout2 )
layout3 = QGridLayout()
self.tab3.setLayout( layout3 )
layout4 = QGridLayout()
self.tab4.setLayout( layout4 )
# Minimum window size...
self.setMinimumSize( 900, 700 )
buttonsize = 80
# Window title
self.setWindowTitle( 'Elpriser' )
self.label = QLabel( adresse )
self.label.setAlignment( Qt.AlignCenter )
# Define various items in window
toTime = datetime.now()
self.labelDays = QLabel( 'Dage:' )
self.labelDays.setAlignment( Qt.AlignVCenter | Qt.AlignCenter )
self.labelFrom = QLabel( 'Fra:' )
self.labelFrom.setAlignment( Qt.AlignVCenter | Qt.AlignCenter )
self.spinDays = QSpinBox( self )
self.spinDays.setRange( 1, 14 )
self.spinDays.setValue( nDays_Init )
self.spinDays.setAlignment( Qt.AlignCenter )
self.spinDays.setFixedWidth( buttonsize )
self.datetimeFrom = QDateTimeEdit( calendarPopup = True )
self.datetimeFrom.setDisplayFormat("dd/MM/yyyy")
self.datetimeFrom.setAlignment( Qt.AlignCenter )
self.datetimeFrom.setFixedWidth( 2 * buttonsize )
fromTime = toTime - timedelta( days = 4 )
self.datetimeFrom.setDateTime( fromTime )
self.btnShow = QPushButton( 'Vis' )
self.btnShowPrv = QPushButton( '<<' )
self.btnShowNxt = QPushButton( '>>' )
self.btnShowNow = QPushButton( '>>|' )
self.btnShow.setFixedWidth( buttonsize )
self.btnShowPrv.setFixedWidth( buttonsize )
self.btnShowNxt.setFixedWidth( buttonsize )
self.btnShowNow.setFixedWidth( buttonsize )
self.graphWidget_A = MplCanvas2( self, width = 5, height = 4, dpi = 65, adjustleft = 0.06, adjustright = 0.94 )
self.graphWidget_A.AllowAddActivity = 1
self.graphWidget_H = MplCanvas2( self, width = 5, height = 4, dpi = 65, adjustleft = 0.06, adjustright = 0.94 )
self.graphWidget_H.AllowAddActivity = 2
# Define hover event on the graph areas - used to display tooltip, implemented as a QLabel
self.graphWidget_A.fig.canvas.mpl_connect( "motion_notify_event", self.hoverA )
self.graphWidget_A.fig.canvas.mpl_connect( "button_press_event", self.hoverA )
self.graphWidget_H.fig.canvas.mpl_connect( "motion_notify_event", self.hoverH )
self.graphWidget_H.fig.canvas.mpl_connect( "button_press_event", self.hoverH )
# Define items in grid layout
# y-position
# | x-position
# | | row span (default 1)
# | | | column span (default 1)
# | | | |
# v v v v
#
# y, x, r, c
#
layout1.addWidget( self.label, 0, 0, 1, 6 )
layout1.addWidget( self.graphWidget_A, 1, 0, 1, 6 )
layout1.addWidget( self.graphWidget_H, 2, 0, 1, 6 )
## layout1.addWidget( self.UpdateSpot, 3, 0, 1, 2 )
layout1.addWidget( self.labelDays, 3, 0 )
layout1.addWidget( self.labelFrom, 3, 1 )
layout1.addWidget( self.spinDays, 4, 0 )
layout1.addWidget( self.datetimeFrom, 4, 1 )
layout1.addWidget( self.btnShow, 4, 2 )
layout1.addWidget( self.btnShowPrv, 4, 3 )
layout1.addWidget( self.btnShowNxt, 4, 4 )
layout1.addWidget( self.btnShowNow, 4, 5 )
# Setup button click events
self.btnShow.clicked.connect( partial( self.btnShowClk, action = 0 ) )
self.btnShowNxt.clicked.connect( partial( self.btnShowClk, action = 1 ) )
self.btnShowPrv.clicked.connect( partial( self.btnShowClk, action = -1 ) )
self.btnShowNow.clicked.connect( partial( self.btnShowClk, action = 2 ) )
# Disable some buttons at start up
## self.UpdateSpot.setEnabled( False )
## self.btnShow.setEnabled( False )
self.timerUpdateSpot = QTimer()
self.timerUpdateSpot.timeout.connect( self.updateSpotPrices ) # Timer to update spot prices
timeNow = datetime.now()
secs = 60 - timeNow.second
if secs < 5:
secs = secs + 60
self.timerUpdateSpot.start( ( secs + 1 ) * 1000 ) # Update after 60 sec
self.timerUpdateShow = QTimer()
self.timerUpdateShow.timeout.connect( self.updateShowBtn ) # Timer to update show button
self.timerUpdateShow.start( 10 * 1000 ) # Update after 10 sec
# Center form on desktop
qtRectangle = self.frameGeometry()
centerPoint = QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
# Define graphs...
self.update_plot_A()
self.update_plot_X()
# Activate...
self.show()
# After main tab load - define content in other tabs...
self.defineResultsTab( self.layout2 )
self.defineConfigTab( layout3 )
self.defineHelpTab( layout4 )
''' Følgende kode centrerer ramme...
qtRectangle = self.frameGeometry()
centerPoint = QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter( centerPoint )
self.move( qtRectangle.topLeft() )
'''
# Done here ...
def contextMenuEvent( self, event ):
## https://www.pythonguis.com/faq/built-in-qicons-pyqt/
pixmapi = QStyle.SP_TitleBarContextHelpButton
hIcon = self.style().standardIcon(pixmapi)
child = self.childAt( event.pos() )
## child = self.childAt( event.globalPos() )
print( "event: ", event )
print( "child: ", child )
bAllowAddActivity = 0
try:
bAllowAddActivity = child.AllowAddActivity
print( "++ AllowAddActivity", bAllowAddActivity )
print( "++ ", event.pos(), event.globalPos() )
print( "++ ", eventTooltip.xdata, eventTooltip.ydata )
dt = mdates.num2date( eventTooltip.xdata )
# Fjern tzinfo
dt = datetime( dt.year, dt.month, dt.day, dt.hour, dt.minute ) # Er offset-naive
dtformat = dt.strftime( datetimeFormat )
except Exception as e:
print( "- AllowAddActivity" )
####
'''
xdata = eventTooltip.xdata
ydata = eventTooltip.ydata
ix = int( xdata + 0.4999 )
if ix < 0:
ix = 0
timerTooltipShow.stop()
if widgetTooltip == mainForm.graphWidgetMonths:
# Months
nn = len( mainForm.resultskWh )
if ix >= nn:
ix = nn - 1
vMonth = mainForm.resultsMonths[ ix ]
vkWh = mainForm.resultskWh[ ix ]
vPrice = mainForm.resultsPrice[ ix ]
vStd = mainForm.resultsStd[ ix ] # monthsstd.append( (365.25 / 12 ) * thiskrperkwhstd * 24 * 1 )
vkWh = "{:.2f} kWh".format( vkWh )
vPrice = "{:.2f} kr".format( vPrice )
vStd = "{:.2f} /".format( vStd ) + " {:.2f} kr/kWh".format( vStd / ( 24 * 1 * ( 365.25 / 12 ) ) )
text = "MÃ¥ned: " + vMonth + "<br>" + \
"Forbrugstal:" + \
"<table>" + \
"<tr><td>Forbrug</td><td align='center'><span style='color:#32CD32;text-align:center;'>▮</span></td><td>" + vkWh + "</td></tr>" + \
"<tr><td>Pris</td><td><span style='color:red'><b>―</b></span></td><td>" + vPrice + "</td></tr>" + \
"<tr><td>Elpris</td><td><span style='color:blue'><b>―</b></span></td><td>" + vStd + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidgetMonth:
# Month
if mainForm.resultsDaysForbrug is None or ix > len( mainForm.resultsDaysForbrug ) or mainForm.resultsDaysPris[ ix - 1 ] <= 0:
text = ""
else:
if ix < 1:
ix = 1
vDay = ix
vkWh = mainForm.resultsDaysForbrug[ ix - 1 ]
vPrice = mainForm.resultsDaysPris[ ix - 1 ]
vStd = mainForm.resultsDaysElpris[ ix - 1 ] # monthsstd.append( (365.25 / 12 ) * thiskrperkwhstd * 24 * 1 )
vkWh = "{:.2f} kWh".format( vkWh )
vPrice = "{:.2f} kr".format( vPrice )
vStd = "{:.2f} /".format( vStd ) + " {:.2f} kr/kWh".format( vStd / 24 )
text = "Dag: " + str( vDay ) + "<br>" + \
"Forbrugstal:" + \
"<table>" + \
"<tr><td>Forbrug</td><td align='center'><span style='color:#32CD32;text-align:center;'>▮</span></td><td>" + vkWh + "</td></tr>" + \
"<tr><td>Pris</td><td><span style='color:red'><b>―</b></span></td><td>" + vPrice + "</td></tr>" + \
"<tr><td>Elpris</td><td><span style='color:blue'><b>―</b></span></td><td>" + vStd + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidgetDay:
# Days
if mainForm.resultsHoursForbrug is None or ix >= len( mainForm.resultsHoursForbrug ):
text = ""
else:
vHour = ix
vkWh = mainForm.resultsHoursForbrug[ ix ]
vPrice = mainForm.resultsHoursPris[ ix ]
vStd = mainForm.resultsHoursElpris[ ix ]
vkWh = "{:.2f} kWh".format( vkWh )
vPrice = "{:.2f} kr".format( vPrice )
vStd = "{:.2f} kr/kWh".format( vStd )
text = "Time: " + str( vHour ) + "<br>" + \
"Forbrugstal:" + \
"<table>" + \
"<tr><td>Forbrug</td><td align='center'><span style='color:#32CD32;text-align:center;'>▮</span></td><td>" + vkWh + "</td></tr>" + \
"<tr><td>Pris</td><td><span style='color:red'><b>―</b></span></td><td>" + vPrice + "</td></tr>" + \
"<tr><td>Elpris</td><td><span style='color:blue'><b>―</b></span></td><td>" + vStd + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidget_A:
dt = mdates.num2date( xdata )
# Fjern tzinfo
dt = datetime( dt.year, dt.month, dt.day, dt.hour, dt.minute ) # Er offset-naive
dtformat = dt.strftime( datetimeFormat )
# Look up graph values at cursor position
vmin = 1000
vmax = -1000
v1 = findInTimeseries( dataTime_1_A, dataPrice_1_A, dt )
if v1 is None:
v1 = "Afventer"
else:
vmin = min( vmin, v1 )
vmax = max( vmax, v1 )
v1 = "{:.2f} kr/kWh".format( v1 )
v1El = findInTimeseries( dataTime_1_A, dataElPrice_1_A, dt )
if v1El is None:
v1El = "Afventer"
else:
vmin = min( vmin, v1El )
vmax = max( vmax, v1El )
v1El = "{:.2f} kr/kWh".format( v1El )
v2 = findInTimeseries( dataTime_2_A, dataPrice_2_A, dt )
if v2 is None:
v2 = "Afventer"
else:
vmin = min( vmin, v2 )
vmax = max( vmax, v2 )
v2 = "{:.2f} kr/kWh".format( v2 )
v2El = findInTimeseries( dataTime_2_A, dataElPrice_2_A, dt )
if v2El is None:
v2El = "Afventer"
else:
vmin = min( vmin, v2El )
vmax = max( vmax, v2El )
v2El = "{:.2f} kr/kWh".format( v2El )
if vmin == 1000 or ydata < vmin - ( vmax - vmin ) / 10 or ydata > vmax + ( vmax - vmin ) / 10:
# Current position is too far from graphs - do not show tooltip
text = ""
else:
sUpdateTime = ""
recordUpdateTime1 = GetUpdateTime( "ElPrisT_" + area_primary, dtformat )
if recordUpdateTime1 is not None:
nRecordsUpdateTime1 = len( recordUpdateTime1 )
for i in range( nRecordsUpdateTime1 ):
row = recordUpdateTime1[ i ]
dataUpdateTime = row[ 0 ]
dtUpdateTime = dataUpdateTime.strftime( datetimeFormat )
sUpdateTime = sUpdateTime + "<tr><td>Opdateret (E):</td><td colspan=2>" + dtUpdateTime + "</td></tr>"
if v1 == v2 and v1El == v2El:
# Graphs (DK1 and DK2) have same values - show...
# Glypts, like 8213; ---> https://www.ee.ucl.ac.uk/~mflanaga/java/HTMLandASCIItableC1.html
text = "<table>" + \
"<tr><td>Tidspunkt:</td><td colspan=2>" + dtformat + "</td></tr>" + sUpdateTime + \
"<tr><td colspan=3>Timepriser:</td></tr>" + \
"<tr><td>" + area_primary + "/" + area_other + "</span></td><td><span style='color:blue'><b>―</b></span> / <span style='color:pink'><b>―</b></span></td><td> " + v2 + "</td></tr>" + \
"<tr><td>- RÃ¥:</td><td><span style='color:magenta'><b>―</b></span> / <span style='color:cyan'><b>―</b></span></td><td>" + v2El + "</td></tr>" + \
"</table>"
else:
# Graphs (DK1 and DK2) have different values - show...
text = "<table>" + \
"<tr><td>Tidspunkt:</td><td colspan=2>" + dtformat + "</td></tr>" + sUpdateTime + \
"<tr><td colspan=3>Timepriser:</td></tr>" + \
"<tr><td>" + area_primary + "</td><td><span style='color:blue'><b>―</b></span></td><td>" + v2 + "</td></tr>" + \
u"<tr><td>- RÃ¥:</td><td><span style='color:magenta'><b>―</b></span></td><td>" + v2El + "</td></tr>" + \
"<tr><td>" + area_other + "</td><td><span style='color:pink'><b>―</b></span></td><td>" + v1 + "</td></tr>" + \
u"<tr><td>- RÃ¥:</td><td><span style='color:cyan'><b>―</b></span></td><td>" + v1El + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidget_H:
dt = mdates.num2date( xdata ) # Er offset-aware
# Fjern tzinfo
dt = datetime( dt.year, dt.month, dt.day, dt.hour, dt.minute ) # Er offset-naive
dtformat = dt.strftime( datetimeFormat )
ymin1, ymax1 = mainForm.graphWidget_H.axes.get_ylim()
ymin2, ymax2 = mainForm.graphWidget_H.ax2.get_ylim()
frac = ( ydata - ymin2 ) / ( ymax2 - ymin2 )
ydata1 = ymin1 + frac * ( ymax1 - ymin1 )
# Look up graph values at cursor position
vmin = 1000
vmax = -1000
vForbrug = findInTimeseries( dataTime_1_H, dataPrice_1_H, dt )
if vForbrug is None:
vForbrug = "Afventer"
else:
frac = ( vForbrug - ymin1 ) / ( ymax1 - ymin1 )
vForbrug2 = ymin2 + frac* ( ymax2 - ymin2 )
vmin = min( vmin, vForbrug2 )
vmax = max( vmax, vForbrug2 )
vForbrug = "{:.2f} kWh".format( vForbrug )
vTimepris = findInTimeseries( dataTime_2_H, dataPrice_3_H, dt )
if vTimepris is None:
vTimepris = "Afventer"
else:
vmin = min( vmin, vTimepris )
'''
####
## print( qt_object_properties( child ) )
## child = self.childAt( self.sender().mapTo( self, event ) )
contextMenu = QMenu( self )
# Creating a separator action
separator = QAction(self)
separator.setSeparator(True)
try:
helpID = child.helpID
if helpID in helpHelpTexts:
helpAct = contextMenu.addAction( "Hjælp: " + helpID )
else:
helpAct = contextMenu.addAction( "Hjælp MANGLER: " + helpID )
helpAct.setIcon( hIcon )
noAct = contextMenu.addAction( separator )
except Exception as e:
helpID = ">>>>> helpID Exception <<<<<" + chr(10) + "{}".format( e )
helpAct = None
## ShowMessageBox( "helpID: " + helpID )
'''
try:
cText = child.text()
except:
cText = "? NoText"
nameAct = contextMenu.addAction( cName )
textAct = contextMenu.addAction( cText )
'''
# Adding the separator to the menu
if bAllowAddActivity == 1:
add1Act = contextMenu.addAction( "Add Activity @ " + dtformat )
if bAllowAddActivity == 2:
add2Act = contextMenu.addAction( "Add Activity @ " + dtformat )
noAct = contextMenu.addAction( separator )
quitAct = contextMenu.addAction( "Quit" )
action = contextMenu.exec( event.globalPos() )
if action == quitAct:
self.close()
elif action == helpAct and action is not None and helpID in helpHelpTexts:
ShowMessageBox( "Hjælp: " + helpID + "<br><br>" + helpHelpTexts[ helpID ] )
def defineConfigTab( self, layout ):
try:
iSpace = 50
# left top right bottom
layout.setContentsMargins( iSpace, iSpace, iSpace, iSpace )
# inbetween
layout.setSpacing( 10 )
fformat = "%.3f"
ConfigBtn = QPushButton( 'Konfigurer' )
ConfigBtn.helpID = 'Konfigurer'
self.edit_token0 = QLineEdit( token0 )
self.edit_nActiveHours = QLineEdit( str( nActiveHours ) )
self.edit_lowPriceLevel = QLineEdit( str( lowPriceLevel ) )
self.edit_nMonths = QLineEdit( str( nMonths_Bills ) )
self.edit_nDays = QLineEdit( str( nDays_Init ) )
self.edit_elAfgift_datoskift = QLineEdit( elAfgift_datoskift )
self.edit_elAfgift_after = QLineEdit( fformat % elAfgift_after )
self.edit_token0.setAlignment( Qt.AlignCenter )
self.edit_nActiveHours.setAlignment( Qt.AlignCenter )
self.edit_lowPriceLevel.setAlignment( Qt.AlignCenter )
self.edit_nMonths.setAlignment( Qt.AlignCenter )
self.edit_nDays.setAlignment( Qt.AlignCenter )
self.edit_elAfgift_datoskift.setAlignment( Qt.AlignCenter )
self.edit_elAfgift_after.setAlignment( Qt.AlignCenter )
label_token0 = QLabel( "Eloverblik token" )
label_token0.helpID = "Eloverblik token"
label_nActiveHours = QLabel( "Minimum antal aktive timer per dag" )
label_nActiveHours.helpID = "Minimum antal aktive timer per dag"
label_lowPriceLevel = QLabel( "Lavt prisniveau (kr/kWh)" )
label_lowPriceLevel.helpID = "Lavt prisniveau"
label_nMonths = QLabel( u"Antal måneder i Opgørelse" )
label_nMonths.helpID = u"Antal måneder i Opgørelse"
label_nDays = QLabel( "Antal dage i historik graf" )
label_nDays.helpID = "Antal dage i historik graf"
label_elAfgift_datoskift = QLabel( u"Elafgift ændringsdato" )
label_elAfgift_datoskift.helpID = u"Elafgift ændringsdato"
label_elAfgift_after = QLabel( u"Elafgift efter ændringsdato (kr/kWh)" )
label_elAfgift_after.helpID = u"Elafgift efter ændringsdato"
layout.addWidget( label_token0, 0, 0 )
layout.addWidget( self.edit_token0, 0, 1 )
layout.addWidget( label_nActiveHours, 1, 0 )
layout.addWidget( self.edit_nActiveHours, 1, 1 )
layout.addWidget( label_lowPriceLevel, 2, 0 )
layout.addWidget( self.edit_lowPriceLevel, 2, 1 )
layout.addWidget( label_nMonths, 3, 0 )
layout.addWidget( self.edit_nMonths, 3, 1 )
layout.addWidget( label_nDays, 4, 0 )
layout.addWidget( self.edit_nDays, 4, 1 )
layout.addWidget( label_elAfgift_datoskift, 5, 0 )
layout.addWidget( self.edit_elAfgift_datoskift, 5, 1 )
layout.addWidget( label_elAfgift_after, 6, 0 )
layout.addWidget( self.edit_elAfgift_after, 6, 1 )
layout.addWidget( ConfigBtn, 7, 0, 1, 2 )
except Exception as e:
mylogging( 2, ">>>>> Config Exception <<<<<" + chr(10) + "{}".format( e ) )
'''
if dialog.exec():
if nDays_Init != self.config_nDays or \
nActiveHours != self.config_nActiveHours or \
lowPriceLevel != self.config_lowPriceLevel or \
nMonths_Bills != self.config_nMonths or \
token0 != self.config_token0 or \
elAfgift_datoskift != self.config_elAfgift_datoskift or \
elAfgift_after != self.config_elAfgift_after:
self.spinDays.setValue( nDays_Init )
updateCharges()
self.btnShowClk( 2 )
'''
ConfigBtn.clicked.connect( self.configValidate )
def configValidate( self ):
global nDays_Init
global nMonths_Bills
global token0
global nActiveHours
global lowPriceLevel
global elAfgift_datoskift
global elAfgift_after
sMessage = ""
try:
newnActiveHours = int( self.edit_nActiveHours.text() )
if newnActiveHours < 0 or newnActiveHours > 24:
sMessage = "Minimum antal aktive timer per dag SKAL være mellem 0 og 24"
except:
sMessage = "Minimum antal aktive timer per dag SKAL være mellem 0 og 24"
try:
newlowPriceLevel = float( self.edit_lowPriceLevel.text() )
if newlowPriceLevel < 0:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Lavt prisniveau SKAL være større eller lig 0"
except:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Lavt prisniveau SKAL være et tal større eller lig 0"
try:
newnMonths_Bills = int( self.edit_nMonths.text() )
if newnMonths_Bills < 3 or newnMonths_Bills > 40:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Antal måneder i opgørelse SKAL være mellem 3 og 40"
except:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Antal måneder i opgørelse SKAL være et tal mellem 3 og 40"
try:
newnDays_Init = int( self.edit_nDays.text() )
if newnDays_Init < 1 or newnDays_Init > 14:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Antal dage i graf SKAL være mellem 1 og 14"
except:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Antal dage i graf SKAL være et tal mellem 1 og 14"
newToken0 = self.edit_token0.text()
if len( newToken0 ) < 100:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Ugyldigt token"
newElAfgift_datoskift = ( self.edit_elAfgift_datoskift.text() ).strip()
if newElAfgift_datoskift != "":
try:
newElAfgift_datoskift = datetime.strptime( newElAfgift_datoskift, dateFormat )
timeNow = datetime.now()
timeNow = timeNow.replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
if newElAfgift_datoskift <= timeNow:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Ugyldig dato for elafgift ændring - skal være tom eller i fremtiden"
else:
newElAfgift_datoskift = ( self.edit_elAfgift_datoskift.text() ).strip()
except:
if sMessage != "":
sMessage = sMessage + chr(10)
timeX = datetime.now() + timedelta( days = 180 )
sTimeX = timeX.strftime( dateFormat )
sMessage = sMessage + "Ugyldig dato for elafgift ændring - skal være i format yyyy-mm-dd, f.eks. " + sTimeX
try:
newElAfgift_after = float( self.edit_elAfgift_after.text() )
if newElAfgift_after <= 0 or newElAfgift_after > 10:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Ugyldig værdi af elafgift ny - skal være mellem 0 og 10"
except:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Forkert format af elafgift ny..."
if sMessage != "":
ShowMessageBox( sMessage )
else:
bOK = False
if newToken0 != token0 or \
newnActiveHours != nActiveHours or \
newlowPriceLevel != lowPriceLevel or \
newnMonths_Bills != nMonths_Bills or \
newnDays_Init != nDays_Init or \
newElAfgift_datoskift != elAfgift_datoskift or \
newElAfgift_after != elAfgift_after:
bOK = CheckConfigChange( newToken0, newnActiveHours, newlowPriceLevel, newnMonths_Bills, newnDays_Init, newElAfgift_datoskift, newElAfgift_after, meterId, area_primary )
if bOK:
self.spinDays.setValue( nDays_Init )
updateCharges()
self.btnShowClk( 2 )
def defineHelpTab( self, layout ):
try:
helpText1 = "-"
helpText2 = "-"
helpText3 = "-"
helpText4 = "-"
helpText5 = "-"
try:
helpText1 = readHelpText( 'Elpriser1.hlp' )
helpText2 = readHelpText( 'Elpriser2.hlp' )
helpText3 = readHelpText( 'Elpriser3.hlp' )
helpText4 = readHelpText( 'Elpriser4.hlp' )
helpText5 = readHelpText( 'Elpriser5.hlp' )
except:
iDummy = 1 # Ignore errors...
layout1 = QGridLayout() # Will hold help-1
layout2 = QGridLayout() # Will hold help-2
layout3 = QGridLayout() # Will hold help-3
layout4 = QGridLayout() # Will hold help-4
layout5 = QGridLayout() # Will hold help-4
layout1.setContentsMargins( 5, 5, 5, 5 )
layout2.setContentsMargins( 5, 5, 5, 5 )
layout3.setContentsMargins( 5, 5, 5, 5 )
layout4.setContentsMargins( 5, 5, 5, 5 )
layout5.setContentsMargins( 5, 5, 5, 5 )
#
# Using grid layout
# Define items in grid layout
#
# y-position, starting at 0
# | x-position, starting at 0
# | | row span, default 1
# | | | column span, default 1
# | | | |
# v v v v
#
# layout.addWidget( item, y, x, r, c )
#
# Layout 1
self.help1 = QTextBrowser()
self.help1.setHtml( helpText1 )
self.help1.setOpenExternalLinks( True )
self.help1.setReadOnly( True )
layout1.addWidget( self.help1, 0, 0 )
# Layout 2
self.help2 = QTextBrowser()
self.help2.setHtml( helpText2 )
self.help2.setOpenExternalLinks( True )
self.help2.setReadOnly( True )
layout2.addWidget( self.help2, 0, 0 )
# Layout 3
self.help3 = QTextBrowser()
self.help3.setHtml( helpText3 )
self.help3.setOpenExternalLinks( True )
self.help3.setReadOnly( True )
layout3.addWidget( self.help3, 0, 0 )
# Layout 4
self.help4 = QTextBrowser()
self.help4.setHtml( helpText4 )
self.help4.setOpenExternalLinks( True )
self.help4.setReadOnly( True )
layout4.addWidget( self.help4, 0, 0 )
# Layout 5
self.help5 = QTextBrowser()
self.help5.setHtml( helpText5 )
self.help5.setOpenExternalLinks( True )
self.help5.setReadOnly( True )
layout5.addWidget( self.help5, 0, 0 )
self.tabWidgetH = QTabWidget()
self.tabWidgetH.currentChanged.connect( self.tabHelpChanged )
self.tab1H = QWidget()
self.tab2H = QWidget()
self.tab3H = QWidget()
self.tab4H = QWidget()
self.tab5H = QWidget()
self.tabWidgetH.addTab( self.tab1H, appText( "Generelt" ) )
self.tabWidgetH.addTab( self.tab2H, appText( "Konfiguration" ) )
self.tabWidgetH.addTab( self.tab3H, appText( "Aktuelt" ) )
self.tabWidgetH.addTab( self.tab4H, appText( "Opgørelse" ) )
self.tabWidgetH.addTab( self.tab5H, appText( "Om" ) )
# Place the three tabs in the main window grid layout
layout.addWidget( self.tabWidgetH, 0, 0 )
self.tab1H.setLayout( layout1 )
self.tab2H.setLayout( layout2 )
self.tab3H.setLayout( layout3 )
self.tab4H.setLayout( layout4 )
self.tab5H.setLayout( layout5 )
# Done here ...
except Exception as e:
mylogging( 1, ">>>>> HelpTab Exception <<<<<" + chr(10) + "{}".format( e ) )
def defineResultsTab( self, layout ):
# Clear current layout items
for i in reversed( range( layout.count() ) ):
layout.itemAt(i).widget().setParent(None)
iWindowWidth = self.tab1.width()
iSpace = 2
# left top right bottom
layout.setContentsMargins( iSpace, iSpace, iSpace, iSpace )
# inbetween
layout.setSpacing( iSpace )
## Subtract 1 day from endTime as the meter readings are 2 days delayed
timeNow = datetime.now()
timeEnd = timeNow - timedelta( days = 1 )
thisYear = timeEnd.year
thisMonth = timeEnd.month
thisDay = timeEnd.day
thisStartMonth = thisMonth - nMonths_Bills
thisStartYear = thisYear
## ShowMessageBox( "nMonths_Bills: " + str( nMonths_Bills ) + chr(10) + \
## "now..." + str( thisYear ) + "-" + str( thisMonth ) )
while thisStartMonth <= 0:
thisStartMonth = thisStartMonth + 12
thisStartYear = thisStartYear - 1
# Udvid opgørelse til at starte på startmåneden i et kvartal...
# dvs. hvis startmåned er 2 eller 3 ændres det til 1...
thisStartMonth = 1 + 3 * int( ( thisStartMonth - 1 ) / 3 )
nMonths = thisMonth - thisStartMonth + 1 + 12 * ( thisYear - thisStartYear )
nQuarters = int( nMonths / 3 )
iFixedWidth1 = 90 # Width of 1.st and last column
# Width of month column data
nMonthsx = ( iWindowWidth - 2 * iFixedWidth1 ) / 50
iFixedWidth = ( iWindowWidth - 2 * ( iFixedWidth1 + iSpace ) ) / nMonthsx - iSpace
## ShowMessageBox( "thisStartMonth: " + str( thisStartMonth ) + chr(10) + \
## "thisStartYear: " + str( thisStartYear ) + chr(10) + \
## "nMonths: " + str( nMonths ) )
nRowsTotal = nMonths + nQuarters
labels = {}
buttons = {}
self.labels = labels
self.buttons = buttons
thisLabelDummy1 = QLabel( "" )
thisLabelDummy2 = QLabel( "" )
thisLabelDummy3 = QLabel( "" )
thisLabelDummy4 = QLabel( "" )
thisLabelDummy5 = QLabel( "" )
thisLabelDummy6 = QLabel( "" )
thisLabelDummy7 = QLabel( "" )
thisLabelDummy8 = QLabel( "" )
thisLabelDummy9 = QLabel( "" )
thisLabelTmonth = QLabel( "" )
thisLabelTkWh = QLabel( "kWh" )
thisLabelTPrice = QLabel( "Kr" )
thisLabelTkWhAv = QLabel( "kWh/Dag" )
thisLabelTPriceAv = QLabel( "Kr/Dag" )
thisLabelTPricekWh = QLabel( "Kr/kWh" )
thisLabelTPricekWhStd = QLabel( "Kr/kWh" )
thisLabelTKvartal = QLabel( "" )
thisLabelTTkWh = QLabel( " <span style='color:#32CD32;text-align:center;'>▮</span> Forbrug" )
thisLabelTTPrice = QLabel( "<span style='color:red'><b>―</b></span> Pris" )
thisLabelTTPricekWhStd = QLabel( "<span style='color:blue'><b>―</b></span> Elpris Std" )
thisLabelTTPricekWh = QLabel( "Elpris" )
thisLabelTTkWhAv = QLabel( "Forbrug/Dag" )
thisLabelTTPriceAv = QLabel( "Pris/Dag" )
thisLabelTTKvartal = QLabel( "Kvartal" )
## Scroll area for data info
dataGridLayout = QGridLayout()
# left top right bottom
dataGridLayout.setContentsMargins( iSpace, 0, iSpace, iSpace )
# inbetween
dataGridLayout.setSpacing( iSpace - 1 )
dataWidget = QWidget()
dataWidget.setLayout( dataGridLayout )
scrollArea = QScrollArea()
self.scrollArea = scrollArea
scrollArea.setWidgetResizable( True )
## scrollArea.setFixedWidth( 800 )
scrollArea.setFixedHeight( 12 * 19 + 1 ) # 12 rows x 19 + 1 (!?)
#
scrollArea.setWidget( dataWidget )
vkWh = 0
vPrice = 0
iMonth = thisStartMonth
iYear = thisStartYear
thisLabelDummy1.setAlignment( Qt.AlignLeft )
thisLabelDummy2.setAlignment( Qt.AlignLeft )
thisLabelDummy3.setAlignment( Qt.AlignLeft )
thisLabelDummy4.setAlignment( Qt.AlignLeft )
thisLabelDummy5.setAlignment( Qt.AlignLeft )
thisLabelDummy6.setAlignment( Qt.AlignLeft )
thisLabelDummy7.setAlignment( Qt.AlignLeft )
thisLabelDummy8.setAlignment( Qt.AlignLeft )
thisLabelDummy9.setAlignment( Qt.AlignLeft )
thisLabelTmonth.setAlignment( Qt.AlignLeft )
thisLabelTkWh.setAlignment( Qt.AlignLeft )
thisLabelTPrice.setAlignment( Qt.AlignLeft )
thisLabelTkWhAv.setAlignment( Qt.AlignLeft )
thisLabelTPriceAv.setAlignment( Qt.AlignLeft )
thisLabelTPricekWh.setAlignment( Qt.AlignLeft )
thisLabelTPricekWhStd.setAlignment( Qt.AlignLeft )
thisLabelTKvartal.setAlignment( Qt.AlignLeft )
thisLabelTTkWh.setAlignment( Qt.AlignLeft )
thisLabelTTPrice.setAlignment( Qt.AlignLeft )
thisLabelTTkWhAv.setAlignment( Qt.AlignLeft )
thisLabelTTPriceAv.setAlignment( Qt.AlignLeft )
thisLabelTTPricekWh.setAlignment( Qt.AlignLeft )
thisLabelTTPricekWhStd.setAlignment( Qt.AlignLeft )
thisLabelTTKvartal.setAlignment( Qt.AlignLeft )
iCol = 0
nVal = 0
bOKVal3 = True
fformat = "%.0f"
fformat1 = "%.1f"
fformat2 = "%.2f"
# Update current month results
thisYear = timeEnd.year
thisMonth = timeEnd.month
thisYearEnd = thisYear
thisMonthEnd = thisMonth
# Load existing results
resultsyear, resultsmonth, resultskWh, resultsPrice, resultskrperkwhstd, resultsnDays = loadResultsData( thisStartMonth, thisStartYear )
thisMonth = thisStartMonth
thisYear = thisStartYear
self.thisStartYear = thisStartYear
self.thisStartMonth = thisStartMonth
iData = 0
nData = len( resultsyear )
## print( "nData", nData )
thisAlign = Qt.AlignRight | Qt.AlignCenter
months = []
monthscolor = []
monthsstd = []
lastMonthN = ""
lastColWid = None
for iMonth in range( nMonths ):
thisLabelIdText = monthname( thisMonth ) + "-" + str( thisYear % 100 )
thisLabelIdTextx = monthname( thisMonth ) + "\n" + str( thisYear % 100 )
months.append( thisLabelIdTextx )
if iMonth == nMonths - 1:
monthscolor.append( '#60ff60' )
else:
monthscolor.append( '#32CD32' ) # Lime green
thisLabelYear = QLabel( str( thisYear ) )
thisLabelYear.setFixedWidth( iFixedWidth )
thisLabelMonth = QLabel( monthname( thisMonth ) + "<span> </span>" )
thisLabelMonth.setFixedWidth( iFixedWidth )
labels[ thisLabelIdText ] = thisLabelMonth
thisLabelYear.setAlignment( Qt.AlignRight )
thisLabelMonth.setAlignment( Qt.AlignRight )
lastColWid = thisLabelMonth
## print( "Label", thisLabelIdText )
thiskWh = None
thisPrice = None
thiskrperkwhstd = None
thiskWhAv = None
thisPriceAv = None
thisnDays = None
## print( "iData", iData )
if iData < nData:
if resultsyear[ iData ] == thisYear and resultsmonth[ iData ] == thisMonth:
thiskWh = resultskWh [ iData ]
thisPrice = resultsPrice[ iData ]
thiskrperkwhstd = resultskrperkwhstd[ iData ]
thisnDays = resultsnDays[ iData ]
if thiskrperkwhstd <= 0:
thiskrperkwhstd = Fixthiskrperkwhstd( thisYear, thisMonth )
iData = iData + 1
## print( "Data OK", thiskWh, thisPrice )
## else:
## print( "Data mangler 1 -", resultsyear[ iData ], resultsmonth[ iData ] )
## else:
## print( "Data mangler 2" )
bOKVal = True
if iMonth == nMonths - 1:
# Current month...
daysInMonth = timeNow.day - 2
else:
daysInMonth = nDaysInMonth( thisYear, thisMonth )
if thisnDays == None:
lastMonthN = "(?)"
elif thisnDays != daysInMonth:
lastMonthN = "(*" + str( thisnDays ) + ")"
elif iMonth == nMonths - 1:
lastMonthN = "(" + str( thisnDays ) + ")"
if thiskWh is not None:
thiskWhAv = thiskWh / daysInMonth
sLabelkWh = fformat % thiskWh
sLabelkWhAv = fformat1 % thiskWhAv
if vkWh is not None:
vkWh = vkWh + thiskWh
else:
sLabelkWh = "-"
sLabelkWhAv = "-"
bOKVal = False
bOKVal3 = False
vkWh = None
thisLabelkWh = QLabelValue( sLabelkWh, thiskWh )
thisLabelkWh.setFixedWidth( iFixedWidth )
thisLabelkWh.setAlignment( thisAlign )
labels[ thisLabelIdText + "kWh" ] = thisLabelkWh
thisLabelkWhAv = QLabelValue( sLabelkWhAv, thiskWhAv )
thisLabelkWhAv.setFixedWidth( iFixedWidth )
thisLabelkWhAv.setAlignment( thisAlign )
labels[ thisLabelIdText + "kWhAv" ] = thisLabelkWhAv
if thisPrice is not None:
thisPriceAv = thisPrice / daysInMonth
sLabelPrice = fformat % thisPrice
sLabelPriceAv = fformat1 % thisPriceAv
if vPrice is not None:
vPrice = vPrice + thisPrice
else:
sLabelPrice = "-"
sLabelPriceAv = "-"
bOKVal = False
bOKVal3 = False
vPrice = None
thisLabelPrice = QLabelValue( sLabelPrice, thisPrice )
thisLabelPrice.setFixedWidth( iFixedWidth )
thisLabelPrice.setAlignment( thisAlign )
labels[ thisLabelIdText + "Price" ] = thisLabelPrice
thisLabelPriceAv = QLabelValue( sLabelPriceAv, thisPriceAv )
thisLabelPriceAv.setFixedWidth( iFixedWidth )
thisLabelPriceAv.setAlignment( thisAlign )
labels[ thisLabelIdText + "PriceAv" ] = thisLabelPriceAv
nVal = nVal + 1
dataGridLayout.addWidget( thisLabelYear, 2, iCol )
dataGridLayout.addWidget( thisLabelMonth, 3, iCol )
dataGridLayout.addWidget( thisLabelkWh, 4, iCol )
dataGridLayout.addWidget( thisLabelPrice, 5, iCol )
dataGridLayout.addWidget( thisLabelkWhAv, 8, iCol )
dataGridLayout.addWidget( thisLabelPriceAv, 9, iCol )
thisLabelPricekWh = QLabelValue( "", None )
thisLabelPricekWh.setFixedWidth( iFixedWidth )
thisLabelPricekWh.setAlignment( thisAlign )
if thiskWh is not None and thisPrice is not None:
thisPricekWh = thisPrice / thiskWh
sLabelPricekWh = fformat2 % thisPricekWh
thisLabelPricekWh.setText( sLabelPricekWh )
thisLabelPricekWh.valueLabel = thisPricekWh
labels[ thisLabelIdText + "PricekWh" ] = thisLabelPricekWh
dataGridLayout.addWidget( thisLabelPricekWh, 7, iCol )
thisLabelPriceStd = QLabelValue( "", thiskrperkwhstd )
thisLabelPriceStd.setFixedWidth( iFixedWidth )
thisLabelPriceStd.setAlignment( thisAlign )
if thiskrperkwhstd is not None:
sLabelPriceStd = fformat2 % thiskrperkwhstd
thisLabelPriceStd.setText( sLabelPriceStd )
labels[ thisLabelIdText + "PriceStd" ] = thisLabelPriceStd
dataGridLayout.addWidget( thisLabelPriceStd, 6, iCol )
## print( "bOKVal", bOKVal )
thisButtonPush = QPushButton( "" )
thisButtonPush.setFixedWidth( iFixedWidth )
buttons[ thisLabelIdText ] = thisButtonPush
dataGridLayout.addWidget( thisButtonPush, 11, iCol )
self.setButtonState( thisYear, thisMonth, thisButtonPush )
if thisnDays is not None and thiskrperkwhstd is not None:
monthsstd.append( (365.25 / 12 ) * thiskrperkwhstd * 24 * 1 )
else:
monthsstd.append( 0.0 )
if nVal == 3:
'''
iRow = iRow + 1
thisLabelIdText = str( thisYear ) + "-" + monthname( thisMonth - 2 ) + ":" + monthname( thisMonth )
thisLabelId = QLabel( thisLabelIdText )
pal = thisLabelId.palette()
pal.setColor( QtGui.QPalette.WindowText, QtGui.QColor("red") )
thisLabelId.setPalette( pal )
layout.addWidget( thisLabelId, iRow, 0 )
'''
if bOKVal3:
sLabelkWh = fformat % vkWh + " kWh"
sLabelPrice = fformat % vPrice + " kr"
else:
sLabelkWh = "-"
sLabelPrice = "-"
## thisKvartalText = thisLabelIdText + " - " + sLabelkWh + " / " + sLabelPrice
thisKvartalText = "<span style='color:#32CD32;'>" + sLabelkWh + "</span>, <span style='color:red'>" + sLabelPrice + "</span>"
thisLabelKvartal = QLabel( thisKvartalText )
'''
pal = thisLabelKvartal.palette()
pal.setColor( QtGui.QPalette.WindowText, QtGui.QColor("blue") )
thisLabelKvartal.setPalette( pal )
'''
thisLabelKvartal.setAlignment( Qt.AlignCenter )
dataGridLayout.addWidget( thisLabelKvartal, 10, iCol - 2, 1, 3 )
'''
thisLabelkWh = QLabelValue( sLabelkWh, vkWh )
thisLabelPrice = QLabelValue( sLabelPrice, vPrice )
pal = thisLabelkWh.palette()
pal.setColor( QtGui.QPalette.WindowText, QtGui.QColor("red") )
thisLabelkWh.setPalette( pal )
pal = thisLabelPrice.palette()
pal.setColor( QtGui.QPalette.WindowText, QtGui.QColor("red") )
thisLabelPrice.setPalette( pal )
thisLabelkWh.setAlignment( thisAlign )
thisLabelPrice.setAlignment( thisAlign )
labels[ thisLabelIdText + "kWh" ] = thisLabelkWh
labels[ thisLabelIdText + "Price" ] = thisLabelPrice
layout.addWidget( thisLabelkWh, iRow, 1 )
layout.addWidget( thisLabelPrice, iRow, 2 )
'''
nVal = 0
bOKVal3 = True
vkWh = 0
vPrice = 0
iCol = iCol + 1
thisMonth = thisMonth + 1
if thisMonth > 12:
thisMonth = thisMonth - 12
thisYear = thisYear + 1
layout.addWidget( thisLabelDummy1, 2, 0 )
layout.addWidget( thisLabelDummy2, 3, 0 )
layout.addWidget( thisLabelTTkWh, 4, 0 )
layout.addWidget( thisLabelTTPrice, 5, 0 )
layout.addWidget( thisLabelTTPricekWhStd, 6, 0 )
layout.addWidget( thisLabelTTPricekWh, 7, 0 )
layout.addWidget( thisLabelTTkWhAv, 8, 0 )
layout.addWidget( thisLabelTTPriceAv, 9, 0 )
layout.addWidget( thisLabelTTKvartal, 10, 0 )
sNow = datetime.now().strftime( "%H:%M" )
thisLabelNow = QLabel( sNow )
thisLabelNow.setAlignment( Qt.AlignCenter )
layout.addWidget( thisLabelDummy3, 11, 0 )
layout.addWidget( thisLabelNow, 12, 0 )
layout.addWidget( thisLabelDummy4, 13, 0 )
thisLabelTmonth.setText( lastMonthN )
labels[ "lastMonthN" ] = thisLabelTmonth
# Save labels for later update of values...
self.labels = labels
self.buttons = buttons
layout.addWidget( thisLabelDummy5, 2, iCol )
layout.addWidget( thisLabelTmonth, 3, iCol )
layout.addWidget( thisLabelTkWh, 4, iCol )
layout.addWidget( thisLabelTPrice, 5, iCol )
layout.addWidget( thisLabelTPricekWhStd, 6, iCol )
layout.addWidget( thisLabelTPricekWh, 7, iCol )
layout.addWidget( thisLabelTkWhAv, 8, iCol )
layout.addWidget( thisLabelTPriceAv, 9, iCol )
layout.addWidget( thisLabelTKvartal, 10, iCol )
layout.addWidget( thisLabelDummy6, 11, iCol )
layout.addWidget( thisLabelDummy7, 12, iCol )
layout.addWidget( thisLabelDummy8, 13, iCol )
'''
for iiCol in range( iCol - 1 ):
dataGridLayout.setColumnMinimumWidth( iiCol, 40 )
dataGridLayout.setColumnStretch( iiCol, 60 )
dataGridLayout.setColumnMinimumWidth( iCol-1, 20 )
dataGridLayout.setColumnStretch( iCol-1, 80 )
dataGridLayout.setColumnMinimumWidth( iCol, 30 )
dataGridLayout.setColumnStretch( 0, 120 )
dataGridLayout.setColumnStretch( iCol, 120 )
'''
iValid = isMinMaxDataValid( thisYearEnd, thisMonthEnd )
if iValid < 1:
updateResults( thisYearEnd, thisMonthEnd )
findMinMax( thisYearEnd, thisMonthEnd )
nCol = iCol + 1
nDataRows = 12
nDataCols = nCol - 2
layout.addWidget( scrollArea, 2, 1, nDataRows, nDataCols )
# Months graph
self.graphWidgetMonths = MplCanvas2( self, width = 5, height = 4, dpi = 65, adjustbottom = 0.15 )
layout.addWidget( self.graphWidgetMonths, 0, 0, 1, nCol )
self.graphWidgetMonths.axes.set_title( "Forbrug & priser - Oversigt" )
self.graphWidgetMonths.axes.set_ylabel( "Forbrug - kWh", color = 'green' )
## self.graphWidgetMonths.ax2.set_ylabel( "Pris - kr", color = 'blue' )
multicolor_ylabel( self.graphWidgetMonths.ax2, ('Pris - Kr :','Middel','og','Summeret'), ('r','k','b','k'), axis='y', box_to_anchor = ( 1.05, 0.5 ), size = 11 ) ##, weight = 'bold' )
nn = len( months )
monthsi = []
for im in range( nn ):
monthsi.append( im )
ymax = max( resultsPrice )
self.graphWidgetMonths.ax2.set_ylim( bottom = 0, top = ymax * 1.05 )
self.graphWidgetMonths.axes.set_xlim( left = -0.75, right = nn - 0.25 )
self.resultsMonths = months
self.resultskWh = resultskWh
self.resultsPrice = resultsPrice
self.resultsStd = monthsstd # monthsstd.append( (365.25 / 12 ) * thiskrperkwhstd * 24 * 1 )
# When current date is in the start of the month, results may not be available yet
# - in that case shorten arrays
npop = len( months ) - len( resultskWh )
if npop > 0:
'''
print( "===== npop", npop )
print( "months", months )
print( "monthsi", monthsi )
print( "resultskWh", resultskWh )
print( "resultsPrice", resultsPrice )
print( "monthsstd", monthsstd )
print( "monthscolor", monthscolor )
print( "=====" )
'''
resultskWh.append( 0.0 )
resultsPrice.append( 0.0 )
'''
for ipop in range( npop ):
months.pop()
monthscolor.pop()
monthsi.pop()
monthsstd.pop()
'''
'''
print( "months", months )
print( "monthsi", monthsi )
print( "resultskWh", resultskWh )
print( "resultsPrice", resultsPrice )
print( "monthsstd", monthsstd )
print( "monthscolor", monthscolor )
print( "=====" )
'''
self.graphWidgetMonths.axes.bar( months, resultskWh, width = 0.3, color = monthscolor )
self.stepGraph( monthsi, resultsPrice, self.graphWidgetMonths.ax2, "red" )
self.stepGraph( monthsi, monthsstd, self.graphWidgetMonths.ax2, "blue" )
self.graphWidgetMonths.axes.yaxis.grid()
self.graphWidgetMonths.fig.canvas.mpl_connect( "button_release_event", self.selectMonth )
# Month graph
self.graphWidgetMonth = MplCanvas2( self, width = 5, height = 4, dpi = 65, adjustleft = 0.1, adjustright = 0.93 )
self.graphWidgetMonth.fig.canvas.mpl_connect( "button_release_event", self.selectDay )
self.hasMonthGraph = False
nCol1 = ( 3 * nCol ) / 5
layout.addWidget( self.graphWidgetMonth, 1, 0, 1, nCol1 )
# Day graph
self.graphWidgetDay = MplCanvas2( self, width = 5, height = 4, dpi = 65, adjustleft = 0.1, adjustright = 0.86 )
## self.graphWidgetDay.fig.subplots_adjust( left = 0.06 * 5 / 3 * 0.9, bottom = 0.1, right = 1 - 0.06 * 5 / 3 * 0.9, top = 0.9 )
self.thisticker = ticker.MaxNLocator( integer = True, min_n_ticks = 1 )
self.graphWidgetDay.axes.yaxis.set_major_locator( self.thisticker )
self.hasDayGraph = False
nCol2 = nCol + 1 - nCol1
layout.addWidget( self.graphWidgetDay, 1, nCol1, 1, nCol2 )
# Define hover event on the graph areas - used to display tooltip, implemented as a QLabel
self.graphWidgetMonths.fig.canvas.mpl_connect( "motion_notify_event", self.hoverMonths )
self.graphWidgetMonths.fig.canvas.mpl_connect( "button_press_event", self.hoverMonths )
self.graphWidgetMonth.fig.canvas.mpl_connect( "motion_notify_event", self.hoverMonth )
self.graphWidgetMonth.fig.canvas.mpl_connect( "button_press_event", self.hoverMonth )
self.graphWidgetDay.fig.canvas.mpl_connect( "motion_notify_event", self.hoverDay )
self.graphWidgetDay.fig.canvas.mpl_connect( "button_press_event", self.hoverDay )
self.graphWidgetMonths.draw()
self.resultsHoursForbrug = None
self.resultsHoursPris = None
self.resultsHoursElpris = None
self.resultsDaysForbrug = None
self.resultsDaysPris = None
self.resultsDaysElpris = None
self.showResultMonth()
self.showResultDay()
self.scrollToMax()
def hoverMonths( self, event ):
global eventTooltip
global widgetTooltip
set_tooltip( "" )
if event.inaxes is not None:
# Start timer which will show tooltip if mouse is not moved within .25 sec
timerTooltipShow.start( 250 )
eventTooltip = event
widgetTooltip = self.graphWidgetMonths
def hoverMonth( self, event ):
global eventTooltip
global widgetTooltip
set_tooltip( "" )
if event.inaxes is not None:
# Start timer which will show tooltip if mouse is not moved within .25 sec
timerTooltipShow.start( 250 )
eventTooltip = event
widgetTooltip = self.graphWidgetMonth
def hoverDay( self, event ):
global eventTooltip
global widgetTooltip
set_tooltip( "" )
if event.inaxes is not None:
# Start timer which will show tooltip if mouse is not moved within .25 sec
timerTooltipShow.start( 250 )
eventTooltip = event
widgetTooltip = self.graphWidgetDay
def selectMonth( self, event ):
if event.inaxes is not None:
ix = int( event.xdata + 0.4999 )
if ix < 0:
ix = 0
thisYear = self.thisStartYear
thisMonth = self.thisStartMonth + ix
while thisMonth > 12:
thisYear = thisYear + 1
thisMonth = thisMonth - 12
self.showResultMonth( thisYear, thisMonth )
self.showResultDay( thisYear, thisMonth, 0 )
def showResultMonth( self, thisYeari = 0, thisMonthi = 0 ):
thisYear = thisYeari
thisMonth = thisMonthi
timeNow = datetime.now()
if thisMonthi <= 0: # Start up by showing latest...
thisMonth = timeNow.month
thisYear = timeNow.year
if timeNow.day <= 2:
thisMonth = thisMonth - 1
if thisMonth == 0:
thisYear = thisYear - 1
thisMonth = 12
daysInMonth = nDaysInMonth( thisYear, thisMonth )
if thisYear == timeNow.year and thisMonth == timeNow.month:
# Current month...
if timeNow.day <= 2:
return # Nothing to show...
## daysInMonth = timeNow.day - 2
dateFrom = str( thisYear ) + "-" + "%02d" % ( thisMonth ) + "-01"
nextYear = thisYear
nextMonth = thisMonth + 1
if nextMonth > 12:
nextYear = nextYear + 1
nextMonth = nextMonth - 12
dateTo = str( nextYear ) + "-" + "%02d" % ( nextMonth ) + "-01"
recordsForbrug = GetData( "Forbrug", dateFrom, dateTo )
recordsPris = GetData( "ElprisT_" + area_primary, dateFrom, dateTo )
nF = len( recordsForbrug )
nP = len( recordsPris )
times = []
forbrug = []
elpris = []
pris = []
nval = []
for iDay in range( daysInMonth ):
times.append( iDay + 1 )
forbrug.append( 0 )
pris.append( 0 )
elpris.append( 0 )
nval.append( 0 )
nDataVal = min( nF, nP )
if nDataVal < daysInMonth:
print( "Problem - Click in Months: " + dateFrom + " - " + dateTo + " - " + str( nF ) + " - " + str( nP ) )
else:
self.hasMonthGraph = True
self.thisMonthYear = thisYear
self.thisMonthMonth = thisMonth
it = 0
itd = 0
while it < nDataVal:
thisT = recordsForbrug[ it ][ 0 ]
iDay = thisT.day - 1
## ShowMessageBox( "thisT " + str( thisT ) + " - iDay " + str( iDay ) )
thisF = recordsForbrug[ it ][ 1 ]
thisP = recordsPris[ it ][ 1 ]
it = it + 1
forbrug[ iDay ] = forbrug[ iDay ] + thisF
pris[ iDay ] = pris[ iDay ] + thisP * thisF
elpris[ iDay ] = elpris[ iDay ] + thisP
nval[ iDay ] = nval[ iDay ] + 1
## Correct for 25 / 23 hours in case of change between summer/winter time...
for iDay in range( daysInMonth ):
nnval = nval[ iDay ]
if nnval > 0 and nnval != 24:
forbrug[ iDay ] = 24 * forbrug[ iDay ] / nnval
pris[ iDay ] = 24 * pris[ iDay ] / nnval
elpris[ iDay ] = 24 * elpris[ iDay ] / nnval
self.graphWidgetMonth.axes.cla() # Clear the canvas
self.graphWidgetMonth.ax2.cla() # Clear the canvas
self.graphWidgetMonth.axes.set_title( "Forbrug & priser - " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
self.graphWidgetMonth.axes.set_ylabel( "Forbrug - kWh", color = 'green' )
self.graphWidgetMonth.ax2.set_ylabel( "Pris - kr", color = 'blue' )
ymax1 = max( pris )
ymax2 = max( elpris )
ymax = max( ymax1, ymax2 )
self.graphWidgetMonth.ax2.set_ylim( bottom = 0, top = ymax * 1.05 )
self.graphWidgetMonth.axes.set_xlim( left = 0.25, right = daysInMonth + 0.75 )
self.graphWidgetMonth.ax2.set_xlim( left = 0.25, right = daysInMonth + 0.75 )
self.graphWidgetMonth.axes.yaxis.grid()
self.graphWidgetMonth.axes.set_xticks( times )
self.graphWidgetMonth.ax2.set_xticks( times )
self.resultsDaysForbrug = forbrug
self.resultsDaysPris = pris
self.resultsDaysElpris = elpris
self.graphWidgetMonth.axes.bar( times, forbrug, width = 0.5, color = '#32CD32' )
d = datetime( thisYear, thisMonth, 1 )
nDays = nDaysInMonth( thisYear, thisMonth )
x = 7.5 - d.weekday() # 0 means monday
while x <= nDays:
self.graphWidgetMonth.axes.axvline( x, color="black", linestyle="--")
x = x + 7
## self.graphWidgetMonth.ax2.plot( times, pris, color = "blue" )
self.stepGraph( times, pris, self.graphWidgetMonth.ax2, color = "red" )
self.stepGraph( times, elpris, self.graphWidgetMonth.ax2, 'blue' )
self.graphWidgetMonth.draw()
def stepGraph( self, xval, yval, ax, color ):
if len( xval ) > 0:
xval2 = xval[:] # Make a clone - instead of just another reference
# to original array
nxval = len( xval )
for ixval in range( nxval ):
xval2[ ixval ] = xval2[ ixval ] - 0.5
lastxval = xval2[ nxval - 1 ]
xval2.append( lastxval + 1 )
yval2 = yval[:]
lastyval = yval[ len( yval ) - 1 ]
yval2.append( lastyval )
ax.step( xval2, yval2, where = 'post', color = color )
def selectDay( self, event ):
if self.hasMonthGraph and event.inaxes is not None:
thisYear = self.thisMonthYear
thisMonth = self.thisMonthMonth
idt = int( event.xdata + 0.4999 )
if idt < 1:
idt = 1
mdt = nDaysInMonth( thisYear, thisMonth )
if idt > mdt:
idt = mdt
self.showResultDay( thisYear, thisMonth, idt )
def showResultDay( self, thisYeari = 0, thisMonthi = 0, thisDayi = 0 ):
thisYear = thisYeari
thisMonth = thisMonthi
thisDay = thisDayi
timeNow = datetime.now()
if thisYeari <= 0 or thisYeari == timeNow.year and thisMonthi == timeNow.month and thisDayi == 0: # Start up by showing latest...
thisMonth = timeNow.month
thisYear = timeNow.year
thisDay = timeNow.day
if thisDay <= 2:
thisMonth = thisMonth - 1
if thisMonth == 0:
thisYear = thisYear - 1
thisMonth = 12
thisDay = nDaysInMonth( thisYear, thisMonth ) - ( 2 - thisDay )
else:
thisDay = thisDay - 2
elif thisDayi <= 0: # Show latest day in month...
thisDay = nDaysInMonth( thisYear, thisMonth )
if thisYear == timeNow.year and thisMonth == timeNow.month:
# Current month...
if timeNow.day <= 2:
return # Nothing to show...
checkProblem = True
while checkProblem:
checkProblem = False
dateFrom = str( thisYear ) + "-" + "%02d" % ( thisMonth ) + "-" + "%02d" % ( thisDay )
dt = datetime.strptime( dateFrom, dateFormat )
dt1 = dt + timedelta( days = 1 )
dateTo = str( dt1.year ) + "-" + "%02d" % ( dt1.month ) + "-" + "%02d" % ( dt1.day )
recordsForbrug = GetData( "Forbrug", dateFrom, dateTo )
recordsPris = GetData( "ElprisT_" + area_primary, dateFrom, dateTo )
nF = len( recordsForbrug )
nP = len( recordsPris )
if nF != nP or nF <= 0:
print( "Problem - Click in Month: " + dateFrom + " - " + dateTo + " - " + str( nF ) + " - " + str( nP ) )
thisDay = thisDay - 1
if thisDay == 0:
return
checkProblem = True #
times = []
timesT = []
forbrug = []
pris = []
elpris = []
it = 0
thistime = datetime.strptime( dateFrom, dateFormat )
while it < nF:
thisT = recordsForbrug[ it ][ 0 ]
thisF = recordsForbrug[ it ][ 1 ]
thisP = recordsPris[ it ][ 1 ]
times.append( it )
it = it + 1
## times.append( thistime )
## timesT.append( thisT )
## thistime = thistime + timedelta( hours = 1 )
forbrug.append( thisF )
pris.append( thisF * thisP )
elpris.append( thisP )
## ShowMessageBox( "nF " + str( nF ) + " - " + str( times ) )
## ShowMessageBox( "nF " + str( nF ) + " - " + str( timesT ) )
self.graphWidgetDay.axes.cla() # Clear the canvas
self.graphWidgetDay.ax2.cla() # Clear the canvas
sDay = dayname( datetime( thisYear, thisMonth, thisDay ) )
self.graphWidgetDay.axes.set_title( "Forbrug & priser - " + sDay + ", %02d" % ( thisDay ) + "-" + monthname( thisMonth ) + "-" + str( thisYear ) )
self.graphWidgetDay.axes.set_ylabel( "Forbrug - kWh", color = 'green' )
self.graphWidgetDay.ax2.set_ylabel( "Pris - kr", color = 'blue' )
ymax1 = max( pris )
ymax2 = max( elpris )
ymax = max( ymax1, ymax2 )
self.graphWidgetDay.ax2.set_ylim( bottom = 0, top = ymax * 1.05 )
self.graphWidgetDay.axes.set_xlim( left = -0.75, right = 23.75 )
self.graphWidgetDay.ax2.set_xlim( left = -0.75, right = 23.75 )
self.graphWidgetDay.axes.yaxis.grid()
self.graphWidgetDay.axes.set_xticks( times )
self.graphWidgetDay.ax2.set_xticks( times )
self.resultsHoursForbrug = forbrug
self.resultsHoursPris = pris
self.resultsHoursElpris = elpris
self.graphWidgetDay.axes.bar( times, forbrug, width = 0.5, color = '#32CD32' )
## self.graphWidgetDay.ax2.plot( times, pris, color = "blue" )
self.stepGraph( times, pris, self.graphWidgetDay.ax2, color = "red" )
self.stepGraph( times, elpris, self.graphWidgetDay.ax2, 'blue' )
self.graphWidgetDay.draw()
def setButtonState( self, thisYear, thisMonth, initButton = None ):
thisLabelIdText = monthname( thisMonth ) + "-" + str( thisYear % 100 )
try:
thisButton = self.buttons[ thisLabelIdText ]
except Exception:
if initButton is not None:
thisButton = initButton
else:
return # Just in case - Should not happen...
iValid = isMinMaxDataValid( thisYear, thisMonth )
if iValid >= 0:
thisButton.setText( "Min" + chr(10) + "Max" )
if iValid == 1:
thisButton.setStyleSheet("background-color: green") # Green - All data is loaded...
elif iValid == 0:
thisButton.setStyleSheet("background-color: #32CD32") # Lime green - Need to fetch data - secondary
thisButton.clicked.connect( partial( self.visMinMax, thisYear = thisYear, thisMonth = thisMonth ) )
thisButton.setEnabled( True )
else:
thisButton.setText( "Afventer" )
thisButton.setStyleSheet("background-color: #D0D3D4" ) # Grey...
thisButton.setEnabled( False ) # Disable - Data will be loaded little by little
# #C1E1C1 Pastel green - Need to fetch data - primary
## thisButton.clicked.connect( partial( self.updateResults, thisYear = thisYear, thisMonth = thisMonth ) )
thisButton.validState = iValid
def visMinMax( self, thisYear, thisMonth ):
minmaxDialog = MinMaxDialog( thisYear, thisMonth )
bValid = isMinMaxDataValid( thisYear, thisMonth )
thisLabelIdText = monthname( thisMonth ) + "-" + str( thisYear % 100 )
thisButtonPush = self.buttons[ thisLabelIdText ]
if bValid:
thisButtonPush.setStyleSheet("background-color: green")
else:
thisButtonPush.setStyleSheet("background-color: #32CD32") # lime green
minmaxDialog.exec()
# Definer tooltip i "Aktuelle" grafer
def hoverA( self, event ):
global eventTooltip
global widgetTooltip
set_tooltip( "" )
if event.inaxes is not None:
# Start timer which will show tooltip if mouse is not moved within .25 sec
timerTooltipShow.start( 250 )
eventTooltip = event
widgetTooltip = self.graphWidget_A
# Definer tooltip i "Historiske" grafer
def hoverH( self, event ):
global eventTooltip
global widgetTooltip
set_tooltip( "" )
if event.inaxes is not None:
# Start timer which will show tooltip if mouse is not moved within .25 sec
timerTooltipShow.start( 250 )
eventTooltip = event
widgetTooltip = self.graphWidget_H
def updateSpotPrices( self ):
## self.UpdateSpot.setEnabled( False )
bNewDay = ( dateTime_NextUpdate.hour == 0 )
if bNewDay: # Get new charges as we have passed midnight
updateCharges()
bUpdated = refreshSpotPrices()
bNewDay = ( bNewDay and bUpdated )
# bNewDay: Time just passed midnight and new spot prices are loaded
# moving spot graph one day ahead
# Force historical graph to show show up till now
self.update_plot_A()
## Update Min,Max for a single month...
## Subtract 1 day as the meter readings are 1 day delayed
timeEnd = datetime.now() - timedelta( days = 1 )
bNewDay = ( timeEnd.hour == 0 )
thisYear = timeEnd.year
thisMonth = timeEnd.month
thisDay = timeEnd.day
thisStartMonth = thisMonth - nMonths_Bills
thisStartYear = thisYear
## ShowMessageBox( "nMonths_Bills: " + str( nMonths_Bills ) + chr(10) + \
## "now..." + str( thisYear ) + "-" + str( thisMonth ) )
while thisStartMonth <= 0:
thisStartMonth = thisStartMonth + 12
thisStartYear = thisStartYear - 1
# Udvid opgørelse til at starte på startmåneden i et kvartal...
# dvs. hvis startmåned er 2 eller 3 ændres det til 1...
thisStartMonth = 1 + 3 * int( ( thisStartMonth - 1 ) / 3 )
nMonths = thisMonth - thisStartMonth + 1 + 12 * ( thisYear - thisStartYear )
thisMonth = thisStartMonth
thisYear = thisStartYear
for iMonth in range( nMonths ):
printY( "Check update Opdaterer Min,Max for " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
iValid = isMinMaxDataValid( thisYear, thisMonth )
printY( "Check update - iValid: " + str( iValid ) )
if iValid < 1:
printY( "Start: Opdaterer Min,Max for " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
if iValid <= 0:
updateResults( thisYear, thisMonth )
printY( "... Results loaded for " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
findMinMax( thisYear, thisMonth )
printY( "Slut: Opdaterer Min,Max for " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
mainForm.setButtonState( thisYear, thisMonth )
break # Just operate once per time ( = minute )
thisMonth = thisMonth + 1
if thisMonth > 12:
thisMonth = thisMonth - 12
thisYear = thisYear + 1
timeNow = datetime.now()
secs = 60 - timeNow.second
if secs < 5:
secs = secs + 60
self.timerUpdateSpot.start( ( secs + 1 ) * 1000 ) # Update after 60 sec
# Show just loaded day
if bNewDay or bPricesMissing:
if bNewDay:
print( "New day... update historical graph" )
self.btnShowClk( 2 )
else:
print( "Missing data... update historical graph" )
self.btnShowClk( 3 )
def updateShowBtn( self ):
self.btnShow.setEnabled( True )
def btnShowClk( self, action = 0 ):
global dataTime_1_H
global dataTime_2_H
global dataPrice_1_H
global dataPrice_2_H
global dataElPrice_2_H
global adresse
self.label.setText( adresse )
## self.btnShow.setEnabled( False )
nDays = self.spinDays.value()
if nDays <= 0:
nDays = 1
# When getting historical consumption, this is only possible
# with up to 2 days delay (currently)
timeNow = datetime.now()
timeMax = timeNow.replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
timeMax = timeMax - timedelta( days = 1 )
tStart = self.datetimeFrom.dateTime().toPyDateTime()
tStart.replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
if action == 1:
tStart = tStart + timedelta( days = nDays )
elif action >= 2:
tStart = timeMax
elif action == -1:
tStart = tStart - timedelta( days = nDays )
tEnd = tStart + timedelta( days = nDays )
if tEnd > timeMax:
tStart = tStart - ( tEnd - timeMax )
tEnd = timeMax
self.datetimeFrom.setDateTime( tStart )
startDate = tStart.strftime( dateFormat )
endDate = tEnd.strftime( dateFormat )
printY( "Vis - Start: " + str( startDate ) + ", Slut: " + str( endDate ) )
thisColor = "background-color: #FFD580" # Light orange...
if action == 3:
thisColor = "background-color: #80D580" # Greenish when updating (missing) historical data
self.btnShowNow.setStyleSheet( thisColor )
elif action == 2:
self.btnShowNow.setStyleSheet( thisColor )
elif action == 1:
self.btnShowNxt.setStyleSheet( thisColor )
elif action == -1:
self.btnShowPrv.setStyleSheet( thisColor )
else:
self.btnShow.setStyleSheet( thisColor )
# Update GUI
QtCore.QCoreApplication.processEvents()
dataTime_1_H, dataPrice_1_H, bUpdate = GetElOverblikX( startDate, endDate )
dataTime_2_H, dataPrice_2_H, dataElPrice_2_H = GetEnergyPricesX( startDate, area_primary, endDate )
if bUpdate or True:
thisYear = tStart.year
thisMonth = tStart.month
printY( "Start: Opdaterer Min,Max for " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
updateResults( thisYear, thisMonth )
printY( "... Results loaded for " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
findMinMax( thisYear, thisMonth )
printY( "Slut: Opdaterer Min,Max for " + monthnameLong( thisMonth ) + " - " + str( thisYear ) )
mainForm.setButtonState( thisYear, thisMonth )
self.defineResultsTab( self.layout2 )
if action == 2: # action 3 is for missing historical data only
self.UpdateSpotClk()
self.update_plot_X()
thisColor = "background-color: None"
if action >= 2:
self.btnShowNow.setStyleSheet( thisColor )
elif action == 1:
self.btnShowNxt.setStyleSheet( thisColor )
elif action == -1:
self.btnShowPrv.setStyleSheet( thisColor )
else:
self.btnShow.setStyleSheet( thisColor )
self.timerUpdateShow.start( 10 * 1000 ) # Update after 10 sec
def tabChanged( self, tabIndex ):
print( "tabChanged - ", tabIndex )
if tabIndex == 1:
self.scrollToMax()
def scrollToMax( self ):
scrollBar = self.scrollArea.horizontalScrollBar()
maxPos = scrollBar.maximum()
printY( "================= maxPos - 1 - " + str( maxPos ) )
scrollBar.setSliderPosition( maxPos )
printY( "================= maxPos - 2 - " + str( maxPos ) )
def tabHelpChanged( self, tabIndex ):
print( "tabHelpChanged - ", tabIndex )
try:
helpText = readHelpText( 'Elpriser' + str( tabIndex + 1 ) + '.hlp' )
except Exception as e:
helpText = "???" # Ignore errors...
print( ">>>>> readHelpText Exception <<<<<" + chr(10) + "{}".format( e ) )
if tabIndex == 0:
self.help1.setHtml( helpText )
elif tabIndex == 1:
self.help2.setHtml( helpText )
elif tabIndex == 2:
self.help3.setHtml( helpText )
elif tabIndex == 3:
self.help4.setHtml( helpText )
elif tabIndex == 4:
self.help5.setHtml( helpText )
else:
print( "Wrong help tab index..." )
return
def update_plot_X( self ):
global bVerbose
global bPricesMissing
global dataTime_1_H
global dataPrice_1_H
global dataTime_2_H
global dataPrice_2_H
global dataPrice_3_H
l1 = len( dataTime_1_H )
l2 = len( dataTime_2_H )
if l1 <= 0 and l2 <= 0:
return
if l1 <= 0:
return
# In case the two arrays have different size, extend...
# Happens if Eloverblik cannot deliver data...
bPricesMissing = False
if l1 < l2 and l1 > 0:
printY( "!!! l1 < l2: " + str( l1 ) + ", " + str( l2 ) )
starttime = dataTime_1_H[ l1 - 1 ]
bPricesMissing = True
iTime = 1
for i in range( l1, l2 ):
dataTime_1_H.append( starttime + timedelta( hours = iTime ) )
dataPrice_1_H.append( 0.0 )
iTime = iTime + 1
l1 = l2
elif l2 < l1 and l2 > 0:
printY( "!!! l1 > l2: " + str( l1 ) + ", " + str( l2 ) )
starttime = dataTime_2_H[ l2 - 1 ]
bPricesMissing = True
iTime = 1
for i in range( l2, l1 ):
dataTime_2_H.append( starttime + timedelta( hours = iTime ) )
dataPrice_2_H.append( 0.0 )
iTime = iTime + 1
l2 = l1
if False:
for i in range( l1 ):
dt1 = dataTime_1_H[ i ]
dt2 = dataTime_2_H[ i ]
dt1tzinfo = dt1.tzinfo
dt1IsAware = False
dt1utcoffset = None
if dt1tzinfo is not None:
dt1utcoffset = dt1tzinfo.utcoffset( dt1 )
dt1IsAware = ( dt1utcoffset is not None )
dt2tzinfo = dt2.tzinfo
dt2IsAware = False
dt2utcoffset = None
if dt2tzinfo is not None:
dt2utcoffset = dt2tzinfo.utcoffset( dt2 )
dt2IsAware = ( dt2utcoffset is not None )
print( "dt1, txInfo, utcoffset, isAware", dt1, dt1tzinfo, dt1utcoffset, dt1IsAware )
print( "dt2, txInfo, utcoffset, isAware", dt2, dt2tzinfo, dt2utcoffset, dt2IsAware )
if l1 > 0:
timeFirst = dataTime_1_H[ 0 ]
timeLast = dataTime_1_H[ l1 - 1 ]
minTime = min( dataTime_1_H )
maxTime = max( dataTime_1_H )
else:
timeFirst = dataTime_2_H[ 0 ]
timeLast = dataTime_2_H[ l2 - 1 ]
minTime = min( dataTime_2_H )
maxTime = max( dataTime_2_H )
timeDelta = timeLast - timeFirst
nHours = 1 + int( ( timeDelta.total_seconds() - 1 ) / ( 60 * 60 ) )
if bVerbose:
printY( "update_plot_X - From: " + str( timeFirst ) + " - To: " + str( timeLast ) + " - nHours: " + str( nHours ) )
if bVerbose:
printY( " - minTime: " + str( minTime ) + " - maxTime: " + str( maxTime ) )
mInterval = 61
if nHours <= 12:
nInterval = 1
if nHours <= 1:
mInterval = 5
elif nHours <= 4:
mInterval = 10
elif nHours <= 8:
mInterval = 15
else:
mInterval = 30
elif nHours <= 24:
nInterval = 2
mInterval = 30
elif nHours <= 2*24:
nInterval = 4
mInterval = 60
elif nHours <= 5*24:
nInterval = 6
mInterval = 60
elif nHours <= 10*24:
nInterval = 12
mInterval = 120
elif nHours <= 14*24:
nInterval = 24
mInterval = 240
# Graphs
self.graphWidget_H.axes.cla() # Clear the canvas
self.graphWidget_H.ax2.cla() # Clear the canvas
self.graphWidget_H.axes.set_xlim( left = minTime, right = maxTime + timedelta( hours = 1 ) )
self.graphWidget_H.ax2.set_xlim( left = minTime, right = maxTime + timedelta( hours = 1 ) )
dataPrice_3_H = []
ymin1 = min( dataPrice_2_H )
ymin2 = ymin1
ymax1 = max( dataPrice_2_H )
ymax2 = ymax1
sumall = []
sumkwh = []
sumpris = []
prismin = []
prismax = []
forbrugmin = []
midnight = dataTime_1_H[ 0 ].replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
midnight = midnight + timedelta( days = 1 )
time2 = dataTime_1_H[ len( dataTime_1_H ) - 1 ]
bFirst = True
nDays = 1
while midnight < time2:
nDays = nDays + 1
midnight = midnight + timedelta( days = 1 )
iDay = 0
sumthis = 0
sumthiskwh = 0
sumthispris = 0
thisprismin = 10000
thisprismax = 0
thisforbrugmin = 10000
midnight = dataTime_1_H[ 0 ].replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
midnight = midnight + timedelta( days = 1 )
## printY( "##################################################" )
## printY( "midnight " + str( midnight ) )
## printY( "##################################################" )
for i in range( l1 ):
if dataPrice_1_H[ i ] is None or dataPrice_2_H[ i ] is None:
v = None
else:
vkwh = dataPrice_1_H[ i ]
## printY( "t,kwh " + str( dataTime_1_H[ i ] ) + ", " + str( vkwh ) )
vpris = dataPrice_2_H[ i ]
v = vkwh * vpris
if bVerbose:
print( v, dataTime_1_H[ i ], dataPrice_1_H[ i ], dataPrice_2_H[ i ] )
if v is not None:
dataPrice_3_H.append( v )
else:
dataPrice_3_H.append( 0.0 )
if dataTime_1_H[ i ] >= midnight:
sumall.append( sumthis )
sumkwh.append( sumthiskwh )
sumpris.append( sumthispris )
prismin.append( thisprismin )
prismax.append( thisprismax )
forbrugmin.append( thisforbrugmin )
## printY( "#### " + str( sumthiskwh ) + ", " + str( sumthis ) )
sumthis = 0
sumthiskwh = 0
sumthispris = 0
thisprismin = 10000
thisprismax = 0
thisforbrugmin = 10000
iDay = iDay + 1
midnight = midnight + timedelta( days = 1 )
if v is not None:
sumthis = sumthis + v # Including tax (moms)
sumthiskwh = sumthiskwh + vkwh
sumthispris = sumthispris + vpris
thisprismin = min( thisprismin, vpris )
thisprismax = max( thisprismax, vpris )
thisforbrugmin = min( thisforbrugmin, vkwh )
## printY( "#### " + str( sumthiskwh ) + ", " + str( sumthis ) )
## printY( "##################################################" )
sumall.append( sumthis )
sumkwh.append( sumthiskwh )
sumpris.append( sumthispris )
prismin.append( thisprismin )
prismax.append( thisprismax )
forbrugmin.append( thisforbrugmin )
ymax1 = max( dataPrice_1_H )
ymax2A = max( dataPrice_2_H )
ymax2B = max( dataPrice_3_H )
ymax2 = max( ymax2A, ymax2B )
self.graphWidget_H.axes.set_ylim( bottom = 0, top = ymax1 * 1.05 )
self.graphWidget_H.ax2.set_ylim( bottom = 0, top = ymax2 * 1.05 )
hours = mdates.HourLocator( byhour=None, interval=nInterval )
h_fmt = mdates.DateFormatter('%H:%M')
self.graphWidget_H.axes.tick_params(which='major', length=7)
self.graphWidget_H.axes.yaxis.set_major_locator( ticker.MaxNLocator( integer = True ) )
self.graphWidget_H.axes.xaxis.set_major_locator( hours )
self.graphWidget_H.axes.xaxis.set_major_formatter( h_fmt )
if mInterval <= 60:
minutes = mdates.MinuteLocator( byminute = range( 0, 60, mInterval ) )
self.graphWidget_H.axes.xaxis.set_minor_locator( minutes )
self.graphWidget_H.axes.tick_params('x', labelrotation=0)
self.graphWidget_H.axes.set_title( "Historisk forbrug & priser" )
## self.graphWidget_H.axes.set_xlabel( "Tid" )
self.graphWidget_H.axes.set_ylabel( "Forbrug - kWh", color = 'green' )
self.graphWidget_H.axes.grid()
curve11 = self.axesplot( bStepCurves, self.graphWidget_H.axes, dataTime_1_H, dataPrice_1_H, area_primary, '#40ff40', True )
self.graphWidget_H.ax2.set_ylabel( "Pris - kr / kWh", color = 'blue' )
curve12 = self.axesplot( bStepCurves, self.graphWidget_H.ax2, dataTime_2_H, dataPrice_2_H, area_primary, 'blue' )
curveEl12 = self.axesplot( bStepCurves, self.graphWidget_H.ax2, dataTime_2_H, dataElPrice_2_H, area_primary, 'magenta' )
curve13 = self.axesplot( bStepCurves, self.graphWidget_H.ax2, dataTime_2_H, dataPrice_3_H, area_primary + "X", 'red' )
ymin2 = min( dataPrice_3_H )
ymax2 = max( dataPrice_3_H )
ymin = min( ymin1, ymin2 )
ymax = max( ymax1, ymax2 )
ymin, ymax = self.graphWidget_H.ax2.get_ylim()
yoff = ( ymax - ymin ) / 20
xmin = min( dataTime_1_H )
xmax = max( dataTime_1_H )
xoff = ( xmax - xmin ) / 40
midnight = dataTime_1_H[ 0 ].replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
midnight = midnight + timedelta( days = 1 )
time2 = dataTime_1_H[ len( dataTime_1_H ) - 1 ]
bFirst = True
iDay = 0
sumthis = -999.999
sumthiskwh = 0
sumthispris = 0
thisprismin = 10000
thisprismax = 0
thisforbrugmin = 10000
if iDay < len( sumall ):
sumthis = sumall[ iDay ]
sumthiskwh = sumkwh[ iDay ]
sumthispris = sumpris[ iDay ]
thisprismin = prismin[ iDay ]
thisprismax = prismax[ iDay ]
thisforbrugmin = forbrugmin[ iDay ]
fformat = "%.2f"
fformat0 = "%.0f"
while midnight < time2:
self.graphWidget_H.axes.axvline( midnight, color="black", linestyle="--")
if bFirst:
thisDate = midnight - timedelta( days = 1 )
sText = dayname( thisDate ) + chr(10) + ( thisDate ).strftime( "%d/%m" )
if sumthis != -999.999:
if sumthis <= 0:
if sumthispris != 0:
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh"
sText = sText + chr(10) + "Data mangler"
bPricesMissing = True
else:
if sumthispris != 0:
# fdmin = minimumsforbrug på dagen === forbrugmin
# ptmin = minimumspris på dagen === prismin
# ptmax = maximumspris på dagen === prismax
# pdag = pris for dagen === sumpris
# pdmin = pris dag minimum
# = sum( fdmin * pristime.i + ( forbrugtime.i - fdmin ) * ptmin )
# = fdmin * sum( pristime.i ) + ptmin * sum( forbrugtime.i ) - 24 * fdmin * ptmin
# = forbrugmin * sumthispris + ptmin * ( sumthiskwh - 24 *forbrugmin )
# pdmax = pris dag maximum
# = sum( fdmin * pristime.i + ( forbrugtime.i - fdmin ) * ptmax )
# = fdmin * sum( pristime.i ) + ptmax * sum( forbrugtime.i ) - 24 * fdmin * ptmax
# = forbrugmin * sumthispris + ptmax * ( sumthiskwh - 24 *forbrugmin )
# index = 2 * ( pdag - pdmin ) / ( pdmax - pdmin )
pdmin = thisforbrugmin * sumthispris + thisprismin * ( sumthiskwh - 24 * thisforbrugmin )
pdmax = thisforbrugmin * sumthispris + thisprismax * ( sumthiskwh - 24 * thisforbrugmin )
indexx = 100 - 200 * ( sumthis - pdmin ) / ( pdmax - pdmin )
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh" + chr(10) + fformat % thisforbrugmin + " kWh" + chr(10) + fformat0 % indexx
sText = sText + chr(10) + fformat % sumthiskwh + " kWh" + chr(10) + fformat % sumthis + " kr"
iDay = iDay + 1
sumthis = -999.999
sumthiskwh = 0
sumthispris = 0
thisprismin = 10000
thisprismax = 0
thisforbrugmin = 10000
if iDay < len( sumall ):
sumthis = sumall[ iDay ]
sumthiskwh = sumkwh[ iDay ]
sumthispris = sumpris[ iDay ]
thisprismin = prismin[ iDay ]
thisprismax = prismax[ iDay ]
thisforbrugmin = forbrugmin[ iDay ]
self.graphWidget_H.ax2.text( midnight - timedelta( hours = 12 ), ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='black', fontsize = 10 )
sText = dayname( midnight ) + chr(10) + midnight.strftime( "%d/%m" )
if sumthis != -999.999:
if sumthis <= 0:
if sumthispris != 0:
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh"
sText = sText + chr(10) + "Data mangler"
bPricesMissing = True
else:
if sumthispris != 0:
pdmin = thisforbrugmin * sumthispris + thisprismin * ( sumthiskwh - 24 * thisforbrugmin )
pdmax = thisforbrugmin * sumthispris + thisprismax * ( sumthiskwh - 24 * thisforbrugmin )
indexx = 100 - 200 * ( sumthis - pdmin ) / ( pdmax - pdmin )
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh" + chr(10) + fformat % thisforbrugmin + " kWh" + chr(10) + fformat0 % indexx
sText = sText + chr(10) + fformat % sumthiskwh + " kWh" + chr(10)+ fformat % sumthis + " kr"
iDay = iDay + 1
sumthis = -999.999
sumthiskwh = 0
sumthispris = 0
thisprismin = 10000
thisprismax = 0
thisforbrugmin = 10000
if iDay < len( sumall ):
sumthis = sumall[ iDay ]
sumthiskwh = sumkwh[ iDay ]
sumthispris = sumpris[ iDay ]
thisprismin = prismin[ iDay ]
thisprismax = prismax[ iDay ]
thisforbrugmin = forbrugmin[ iDay ]
if midnight + timedelta( days = 1 ) <= xmax:
self.graphWidget_H.ax2.text( midnight + timedelta( hours = 12 ), ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='black', fontsize = 10 )
else:
self.graphWidget_H.ax2.text( midnight + timedelta( hours = 12 ), ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='black', fontsize = 10 )
bFirst = False
midnight = midnight + timedelta( days = 1 )
if bFirst:
# just a single day...
timex = xmin + ( xmax - xmin ) / 2
sText = dayname( timex ) + chr(10) + timex.strftime( "%d/%m" )
if sumthis != -999.999:
if sumthis <= 0:
if sumthispris != 0:
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh"
sText = sText + chr(10) + "Data mangler"
bPricesMissing = True
else:
if sumthispris != 0:
pdmin = thisforbrugmin * sumthispris + thisprismin * ( sumthiskwh - 24 * thisforbrugmin )
pdmax = thisforbrugmin * sumthispris + thisprismax * ( sumthiskwh - 24 * thisforbrugmin )
indexx = 100 - 200 * ( sumthis - pdmin ) / ( pdmax - pdmin )
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh" + chr(10) + fformat % thisforbrugmin + " kWh" + chr(10) + fformat0 % indexx
sText = sText + chr(10) + fformat % sumthiskwh + " kWh" + chr(10) + + fformat % sumthis + " kr"
self.graphWidget_H.ax2.text( timex, ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='black', fontsize = 10 )
# Trigger the canvas to update and redraw.
self.graphWidget_H.draw()
if bVerbose:
printY( "End ...plot_X" )
# Done here...
def axesplot( self, thisStepCurve, thisGraphWidgetaxes, thisDataTime, thisDataPrice, thisLabel, thisColor, fillit = False ):
thisCurve = None
if len( thisDataTime ) > 0:
if thisStepCurve:
newDataTime = thisDataTime[:] # Make a clone - instead of just another reference
# to original array
lastTime = thisDataTime[ len( thisDataTime ) - 1 ]
newDataTime.append( lastTime + timedelta( hours = 1 ) )
newDataPrice = thisDataPrice[:]
if not fillit:
lastPrice = thisDataPrice[ len( thisDataPrice ) - 1 ]
newDataPrice.append( lastPrice )
if fillit:
thisCurve = thisGraphWidgetaxes.stairs( newDataPrice, newDataTime, label = thisLabel, color = thisColor, fill = fillit )
else:
thisCurve = thisGraphWidgetaxes.step( newDataTime, newDataPrice, where = 'post', label = thisLabel, color = thisColor )
else:
thisCurve = thisGraphWidgetaxes.plot( thisDataTime, thisDataPrice, label = thisLabel, color = thisColor )
return thisCurve
def update_plot_A( self ):
global bVerbose
global dotdotdots
nDatas = len( dataTime_1_A )
nDatas2 = len( dataTime_2_A )
if nDatas <= 0 or nDatas2 <= 0:
return
timeFirst = dataTime_1_A[ 0 ]
timeLast = dataTime_1_A[ nDatas - 1 ]
minTime = min( min( dataTime_1_A ), min( dataTime_2_A ) )
maxTime = max( max( dataTime_1_A ), max( dataTime_2_A ) )
timeDelta = maxTime - minTime # timeLast - timeFirst
nHours = 1 + int( ( timeDelta.total_seconds() - 1 ) / ( 60 * 60 ) )
if bVerbose:
printY( "update_plot_A - From: " + str( minTime ) + " - To: " + str( maxTime ) + " - nHours: " + str( nHours ) )
mInterval = 61
if nHours <= 12:
nInterval = 1
if nHours <= 1:
mInterval = 5
elif nHours <= 4:
mInterval = 10
elif nHours <= 8:
mInterval = 15
else:
mInterval = 30
elif nHours <= 24:
nInterval = 2
mInterval = 30
elif nHours <= 2*24:
nInterval = 4
mInterval = 60
elif nHours <= 5*24:
nInterval = 6
mInterval = 60
elif nHours <= 10*24:
nInterval = 12
mInterval = 120
elif nHours <= 14*24:
nInterval = 24
mInterval = 240
sumpris = []
iDay = 0
sumthis = 0
sumthiskwh = 0
sumthispris = 0
thisprismin = 10000
thisprismax = 0
thisforbrugmin = 10000
midnight = dataTime_1_A[ 0 ].replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
midnight = midnight + timedelta( days = 1 )
## printY( "##################################################" )
## printY( "midnight " + str( midnight ) )
## printY( "##################################################" )
for i in range( nDatas ):
if dataPrice_1_A[ i ] is None:
vpris = None
else:
vpris = dataPrice_1_A[ i ]
if dataTime_1_A[ i ] >= midnight:
sumpris.append( sumthispris )
sumthispris = 0
iDay = iDay + 1
midnight = midnight + timedelta( days = 1 )
if vpris is not None:
sumthispris = sumthispris + vpris
sumpris.append( sumthispris )
# Graphs
self.graphWidget_A.axes.cla() # Clear the canvas
self.graphWidget_A.ax2.cla() # Clear the canvas
ymax1 = max( dataPrice_1_A )
ymax2 = max( dataPrice_2_A )
ymax = 1.05 * max( ymax1, ymax2 )
printY( "- ymax: " + str( ymax ) )
self.graphWidget_A.axes.set_xlim( left = minTime, right = maxTime + timedelta( hours = 1 ) )
self.graphWidget_A.axes.set_ylim( bottom = 0, top = ymax )
hours = mdates.HourLocator( byhour=None, interval=nInterval )
h_fmt = mdates.DateFormatter('%H:%M')
self.graphWidget_A.axes.tick_params(which='major', length=7)
self.graphWidget_A.axes.yaxis.set_major_locator( ticker.MaxNLocator( integer = True ) )
self.graphWidget_A.ax2.yaxis.set_major_locator( ticker.MaxNLocator( integer = True ) )
self.graphWidget_A.axes.xaxis.set_major_locator( hours )
self.graphWidget_A.axes.xaxis.set_major_formatter( h_fmt )
if mInterval <= 60:
minutes = mdates.MinuteLocator( byminute = range( 0, 60, mInterval ) )
self.graphWidget_A.axes.xaxis.set_minor_locator( minutes )
self.graphWidget_A.axes.tick_params('x', labelrotation=0)
if bSpotMissing or bSpotEstimate:
self.graphWidget_A.axes.set_title( "Aktuelle priser - BEMÆRK: Spotpriser kan p.t. ikke hentes - Viser estimat..." )
elif bSpotWaiting and nDatas <= 50:
if dotdotdots == "":
dotdotdots = "..."
else:
dotdotdots = dotdotdots + "."
if dotdotdots == "...........":
dotdotdots = "..."
self.graphWidget_A.axes.set_title( "Aktuelle priser - Afventer morgendagens priser" + dotdotdots )
else:
self.graphWidget_A.axes.set_title( "Aktuelle priser" )
## self.graphWidget_A.axes.set_xlabel( "Tid" )
self.graphWidget_A.axes.set_ylabel( "Pris - kr / kWh", color = 'blue' )
self.graphWidget_A.ax2.set_ylim( bottom = 0, top = ymax )
self.graphWidget_A.ax2.set_ylabel( "Pris - kr / kWh", color = 'blue' )
self.graphWidget_A.axes.grid()
timeNow = datetime.now()
self.graphWidget_A.axes.axvline( timeNow, color="orange", linestyle="-", linewidth = 3 )
curve11 = self.axesplot( bStepCurves, self.graphWidget_A.axes, dataTime_1_A, dataPrice_1_A, area_other, 'pink' )
curveEl11 = self.axesplot( bStepCurves, self.graphWidget_A.axes, dataTime_1_A, dataElPrice_1_A, area_other, 'cyan' )
curve12 = self.axesplot( bStepCurves, self.graphWidget_A.axes, dataTime_2_A, dataPrice_2_A, area_primary, 'blue' )
curveEl12 = self.axesplot( bStepCurves, self.graphWidget_A.axes, dataTime_2_A, dataElPrice_2_A, area_primary, 'magenta' )
# Add rectangle to plot to indicate heating hours
nHeatingHours = len( dataHeating_2_A )
for iHour in range( nHeatingHours ):
if dataHeating_2_A[ iHour ] > 0:
xpos = dataTime_2_A[ iHour ] + timedelta( minutes = 5 )
ypos = ymax / 50
width = timedelta( minutes = 50 )
height = ymax / 20
self.graphWidget_A.axes.add_patch( Rectangle( ( xpos, ypos ), width, height, edgecolor = 'red', facecolor = 'orange', fill = True, lw = 1 ) )
if bSpotMissing:
xpos = dataTime_2_A[ 0 ] + timedelta( minutes = 5 )
ypos = ymax - ymax / 50 - ymax / 40
width = timedelta( minutes = 50 )
height = ymax / 40
for iHour in range( 48 ):
self.graphWidget_A.axes.add_patch( Rectangle( ( xpos, ypos ), width, height, edgecolor = 'yellow', facecolor = 'red', fill = True, lw = 1 ) )
xpos = xpos + timedelta( hours = 1 )
elif bSpotEstimate:
xpos = dataTime_2_A[ len( dataTime_2_A ) - 1 ] + timedelta( minutes = 5 ) - timedelta( hours = 23 )
ypos = ymax - ymax / 50 - ymax / 40
width = timedelta( minutes = 50 )
height = ymax / 40
for iHour in range( 24 ):
self.graphWidget_A.axes.add_patch( Rectangle( ( xpos, ypos ), width, height, edgecolor = 'orange', facecolor = 'yellow', fill = True, lw = 1 ) )
xpos = xpos + timedelta( hours = 1 )
yoff = ( ymax - 0 ) / 20
xmin = min( dataTime_1_A )
xmax = max( dataTime_1_A )
xoff = ( xmax - xmin ) / 40
l2 = len( dataTime_2_A )
vNow = None
for i in range( 1, l2 ):
if dataTime_2_A[ i ] >= timeNow:
iPos = i - 1
vNow = dataPrice_2_A[ i - 1 ]
break
if vNow is not None:
sNow = timeNow.strftime( "%H:%M" )
### printY( "-------> timeNow: ", timeNow, iPos, vNow )
### printY( "-------> data : ", dataTime_2_A[ iPos ], dataPrice_2_A[ iPos-1 ], dataPrice_2_A[ iPos ], dataPrice_2_A[ iPos + 1 ] )
self.graphWidget_A.axes.text( timeNow - timedelta( minutes = 10 ), vNow + yoff/20, sNow + chr(10) + "%.2f" % vNow,
verticalalignment = 'bottom', horizontalalignment='right',
color='orange', fontsize = 12 )
midnight = dataTime_1_A[ 0 ].replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
midnight = midnight + timedelta( days = 1 )
time2 = dataTime_1_A[ len( dataTime_1_A ) - 1 ]
bFirst = True
iDay = 0
sumthispris = 0
if iDay < len( sumpris ):
sumthispris = sumpris[ iDay ]
fformat = "%.2f"
while midnight < time2:
self.graphWidget_A.axes.axvline( midnight, color="black", linestyle="--")
if bFirst:
thisDate = midnight - timedelta( days = 1 )
sText = dayname( thisDate ) + chr(10) + ( thisDate ).strftime( "%d/%m" )
if sumthispris <= 0:
sText = sText + chr(10) + "Data mangler"
else:
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh"
self.graphWidget_A.axes.text( midnight - timedelta( hours = 12 ), ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='green', fontsize = 10 )
iDay = iDay + 1
sumthispris = 0
if iDay < len( sumpris ):
sumthispris = sumpris[ iDay ]
sText = dayname( midnight ) + chr(10) + midnight.strftime( "%d/%m" )
if sumthispris <= 0:
sText = sText + chr(10) + "Data mangler"
else:
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh"
if midnight + timedelta( days = 1 ) <= xmax:
self.graphWidget_A.axes.text( midnight + timedelta( hours = 12 ), ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='green', fontsize = 10 )
else:
self.graphWidget_A.axes.text( midnight + timedelta( hours = 12 ), ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='green', fontsize = 10 )
bFirst = False
midnight = midnight + timedelta( days = 1 )
iDay = iDay + 1
sumthispris = 0
if iDay < len( sumpris ):
sumthispris = sumpris[ iDay ]
if bFirst:
# just a single day...
midnight = midnight + timedelta( days = 1 )
time1 = dataTime_1_A[ 0 ]
timex = time1 + ( time2 - time1 ) / 2
sText = dayname( timex ) + chr(10) + timex.strftime( "%d/%m" )
if sumthispris <= 0:
sText = sText + chr(10) + "Data mangler"
else:
sText = sText + chr(10) + fformat % ( sumthispris / 24 ) + " kr/kWh"
self.graphWidget_A.axes.text( timex, ymax - yoff, sText,
verticalalignment = 'top', horizontalalignment='center',
color='green', fontsize = 10 )
# Trigger the canvas to update and redraw.
self.graphWidget_A.draw()
# Done here...
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Afslut...', 'Er du sikker på at du vil afslutte?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
dbClose()
print('Bye - Elpriser')
else:
event.ignore()
def UpdateSpotClk( self ):
refreshSpotPrices( True )
timeNow = datetime.now()
secs = 60 - timeNow.second
if secs < 5:
secs = secs + 60
self.timerUpdateSpot.start( ( secs + 1 ) * 1000 ) # Update after 60 sec
self.update_plot_A()
def configClicked( self ):
self.config_nActiveHours = copy.copy( nActiveHours )
self.config_lowPriceLevel = copy.copy( lowPriceLevel )
self.config_nDays = copy.copy( nDays_Init )
self.config_nMonths = copy.copy( nMonths_Bills )
self.config_token0 = token0[:]
self.config_elAfgift_datoskift = elAfgift_datoskift[:]
self.config_elAfgift_after = copy.copy( elAfgift_after )
try:
fformat = "%.3f"
labels = [ u"Eloverblik token", u"Minimum antal aktive timer per dag", u"Lavt prisniveau (kr/kWh)", u"Antal måneder i opgørelse", u"Antal dage i graf", u"Elafgift ændringsdato", u"Elafgift ny (kr/kWh)" ]
values = [ token0, str( nActiveHours ), str( lowPriceLevel ), str( nMonths_Bills ), str( nDays_Init ), elAfgift_datoskift, fformat % elAfgift_after ]
dialog = ConfigDialog( labels, values )
except Exception as e:
mylogging( 2, ">>>>> Config Exception <<<<<" + chr(10)+ "{}".format( e ) )
if dialog.exec():
if nDays_Init != self.config_nDays or \
nActiveHours != self.config_nActiveHours or \
lowPriceLevel != self.config_lowPriceLevel or \
nMonths_Bills != self.config_nMonths or \
token0 != self.config_token0 or \
elAfgift_datoskift != self.config_elAfgift_datoskift or \
elAfgift_after != self.config_elAfgift_after:
self.spinDays.setValue( nDays_Init )
updateCharges()
self.btnShowClk( 2 )
def qt_object_properties(qt_object: object) -> dict:
"""
Create a dictionary of property names and values from a QObject.
:param qt_object: The QObject to retrieve properties from.
:type qt_object: object
:return: Dictionary with format
{'name': property_name, 'value': property_value}
:rtype: dict
"""
properties: list = []
# Returns a list of QByteArray.
button_properties: list = qt_object.dynamicPropertyNames()
for prop in button_properties:
# Decode the QByteArray into a string.
name: str = str(prop, 'utf-8')
# Get the property value from the button.
value: str = qt_object.property(name)
properties.append({'name': name, 'value': value})
return properties
def dbExecute( sql ):
return thisRPiMySQL.Execute( sql )
def dbFetch():
return thisRPiMySQL.Fetch()
def dbClose():
return thisRPiMySQL.Close()
def dbDumpDescribe( thisTable ):
return thisRPiMySQL.DumpDescribe( thisTable )
def dbUpdateSchema( thisTable, thisSchema ):
return thisRPiMySQL.UpdateSchema( thisTable, thisSchema )
#
# Store data into db
# Returns : -2: MySQL error
# -1: Exception error
# 0: Data stored
# 1: Data already exist - same value
# 2: Data already exist - new value
# 10: Multiple data existed (fixed)
#
def StoreTimeseries( thisIdent, theseDateTimes, theseValues ):
try:
nValues = len( theseDateTimes )
dateTime1 = theseDateTimes[ 0 ]
dateTime1 = dateTime1.strftime( datetimeFormat )
dateTime2 = theseDateTimes[ nValues - 1 ]
dateTime2 = dateTime2.strftime( datetimeFormat )
sql = "DELETE FROM data WHERE meterId = '" + meterId + "' AND datetime >= '" + dateTime1 + "' AND datetime <= '" + dateTime2 + "' AND ident = '" + thisIdent + "'"
printY( "StoreTimeseries - 1 - sql: " + sql )
if dbExecute( sql ) < 0:
mylogging( 1, "StoreTimeseries - 1 - FEJLER, sql: " + sql )
return -2
sql = "INSERT INTO data (meterId, datetime, ident, value) VALUES "
for i in range( nValues ):
dateTime = theseDateTimes[ i ]
dateTime = dateTime.strftime( datetimeFormat )
thisDateTime = theseDateTimes[ i ]
thisValue = theseValues[ i ]
sql1 = "('" + meterId + "','" + dateTime + "','" + thisIdent + "','" + str( thisValue ) + "')"
if i < nValues - 1:
sql1 = sql1 + ","
else:
sql1 = sql1 + ";"
sql = sql + sql1
printY( "StoreTimeseries - 2 - sql: " + sql )
if dbExecute( sql ) < 0:
mylogging( 1, "StoreTimeseries - 2 - FEJLER, sql: " + sql )
return -2
printY( "StoreTimeseries - Done" )
return 0
except Exception as err:
mylogging( 2, "StoreTimeseries - Exception: {}".format( err ) )
return -1
# Get data from db
#
# returns records with datetime, value for given ident
def GetUpdateTime( thisIdent, fromTime = "", toTime = "" ):
global meterId
# Build SQL statement to retrieve price data...
if fromTime == "":
if toTime == "": # Select ALL
sql = "SELECT time from data"
else: # Select ALL Before toTime
sql = "SELECT time from data where datetime < '" + toTime + "'"
elif toTime == "": # Select ALL After fromTime
sql = "SELECT time from data where datetime >= '" + fromTime + "'"
else: # Select Interval
sql = "SELECT time from data where datetime >= '" + fromTime + "' and datetime < '" + toTime + "'"
try:
sql = sql + " AND meterId = '" + meterId + "'" + " AND ident = '" + thisIdent + "'"
sql = sql + " LIMIT 1"
printY( "GetUpdateTime - sql: " + sql )
if dbExecute( sql ) < 0:
return None # -2
records = dbFetch()
printY( "GetUpdateTime - nrecords: " + str( len( records ) ) )
return records
except Exception as e:
mylogging( 2, "GetUpdateTime - Exception: {}".format( e ) )
return None
# Get data from db
#
# returns records with datetime, value for given ident
def GetData( thisIdent, fromTime = "", toTime = "" ):
global meterId
# Build SQL statement to retrieve price data...
if fromTime == "":
if toTime == "": # Select ALL
sql = "SELECT datetime, value from data"
else: # Select ALL Before toTime
sql = "SELECT datetime, value from data where datetime < '" + toTime + "'"
elif toTime == "": # Select ALL After fromTime
sql = "SELECT datetime, value from data where datetime >= '" + fromTime + "'"
else: # Select Interval
sql = "SELECT datetime, value from data where datetime >= '" + fromTime + "' and datetime < '" + toTime + "'"
try:
sql = sql + " AND meterId = '" + meterId + "'" + " AND ident = '" + thisIdent + "'"
sql = sql + " ORDER BY datetime"
printY( "GetData - sql: " + sql )
if dbExecute( sql ) < 0:
return None # -2
records = dbFetch()
printY( "GetData - nrecords: " + str( len( records ) ) )
return records
except Exception as e:
mylogging( 2, "GetData - Exception: {}".format( e ) )
return None
def dayname( thisDate ):
WEEKDAYS = [ 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', u'Lørdag', u'Søndag' ]
dayinweek = thisDate.weekday()
if dayinweek >= 0 and dayinweek <= 6:
return WEEKDAYS[ dayinweek ]
else:
return "-?-"
def monthname( thisMonth ):
MONTHS = [ 'Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec' ]
try:
if thisMonth >= 1 and thisMonth <= 12:
return MONTHS[ thisMonth - 1 ]
else:
return "-?-"
except:
return "-!-"
def monthnameLong( thisMonth ):
MONTHS = [ 'Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December' ]
try:
if thisMonth >= 1 and thisMonth <= 12:
return MONTHS[ thisMonth - 1 ]
else:
return "-?-"
except:
return "-!-"
class ConfigDialog( QDialog ):
def __init__(self, labels, values, parent = None ):
super().__init__(parent)
buttonBox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self )
layout = QFormLayout( self )
self.inputs = []
self.setWindowTitle( "Konfiguration" )
self.setMinimumWidth( 400 )
bFirst = True
for lab, val in zip( labels, values ):
thisEdit = QLineEdit( self )
thisEdit.setAlignment( Qt.AlignCenter )
thisEdit.setText( val )
self.inputs.append( thisEdit )
layout.addRow( lab, self.inputs[ -1 ] )
if bFirst:
iDummy = 1
## thisPush = QLabel( "PushButton
bFirst = False
layout.addRow( buttonBox )
buttonBox.accepted.connect( self.validate )
buttonBox.rejected.connect( self.reject )
def validate( self ):
global nDays_Init
global nMonths_Bills
global token0
global nActiveHours
global lowPriceLevel
global elAfgift_datoskift
global elAfgift_after
sMessage = ""
try:
newnActiveHours = int( self.inputs[ 1 ].text() )
if newnActiveHours < 0 or newnActiveHours > 24:
sMessage = "Minimum antal aktive timer per dag SKAL være mellem 0 og 24"
except:
sMessage = "Minimum antal aktive timer per dag SKAL være mellem 0 og 24"
try:
newlowPriceLevel = float( self.inputs[ 2 ].text() )
if newlowPriceLevel < 0:
sMessage = "Lavt prisniveau SKAL være større eller lig 0"
except:
sMessage = "Lavt prisniveau SKAL være et tal større eller lig 0"
try:
newnMonths_Bills = int( self.inputs[ 3 ].text() )
if newnMonths_Bills < 3 or newnMonths_Bills > 40:
sMessage = "Antal måneder i opgørelse SKAL være mellem 3 og 40"
except:
sMessage = "Antal måneder i opgørelse SKAL være et tal mellem 3 og 40"
try:
newnDays_Init = int( self.inputs[ 4 ].text() )
if newnDays_Init < 1 or newnDays_Init > 14:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = "Antal dage i graf SKAL være mellem 1 og 14"
except:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = "Antal dage i graf SKAL være et tal mellem 1 og 14"
newToken0 = self.inputs[ 0 ].text()
if len( newToken0 ) < 100:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Ugyldigt token"
newElAfgift_datoskift = ( self.inputs[ 5 ].text() ).strip()
if newElAfgift_datoskift != "":
try:
newElAfgift_datoskift = datetime.strptime( newElAfgift_datoskift, dateFormat )
timeNow = datetime.now()
timeNow = timeNow.replace( hour = 0, minute = 0, second = 0, microsecond = 0 )
if newElAfgift_datoskift <= timeNow:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Ugyldig dato for elafgift ændring - skal være tom eller i fremtiden"
else:
newElAfgift_datoskift = ( self.inputs[ 5 ].text() ).strip()
except:
if sMessage != "":
sMessage = sMessage + chr(10)
timeX = datetime.now() + timedelta( days = 180 )
sTimeX = timeX.strftime( dateFormat )
sMessage = sMessage + "Ugyldig dato for elafgift ændring - skal være i format yyyy-mm-dd, f.eks. " + sTimeX
try:
newElAfgift_after = float( self.inputs[ 6 ].text() )
if newElAfgift_after <= 0 or newElAfgift_after > 10:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Ugyldig værdi af elafgift ny - skal være mellem 0 og 10"
except:
if sMessage != "":
sMessage = sMessage + chr(10)
sMessage = sMessage + "Forkert format af elafgift ny..."
if sMessage != "":
ShowMessageBox( sMessage )
else:
bOK = True
if newToken0 != token0 or \
newnActiveHours != nActiveHours or \
newlowPriceLevel != lowPriceLevel or \
newnMonths_Bills != nMonths_Bills or \
newnDays_Init != nDays_Init or \
newElAfgift_datoskift != elAfgift_datoskift or \
newElAfgift_after != elAfgift_after:
bOK = CheckConfigChange( newToken0, newnActiveHours, newlowPriceLevel, newnMonths_Bills, newnDays_Init, newElAfgift_datoskift, newElAfgift_after, meterId, area_primary )
if bOK:
self.accept()
## self.label_1.setStyleSheet("background-color: lightgreen")
def makets( records ):
thisTime = []
thisData = []
thisBaseData = []
tFormat = '%Y-%m-%dT%H:%M:%S'
for row in records:
tVal = row[ 'HourDK' ]
ttVal = datetime.strptime( tVal, tFormat )
thisTime.append( ttVal )
pVal = 1.25 * row[ 'SpotPriceDKK' ]
pExtra = hourPriceExtra( ttVal, 1.0 )
thisData.append( pExtra + pVal / 1000 )
thisBaseData.append( pVal / 1000 )
return thisTime, thisData, thisBaseData
#
# https://energinet.dk/Energidata/DataHub/Eloverblik
# https://api.eloverblik.dk/customerapi/index.html
#
def CheckElOverblikToken( mTry = 10 ):
global bVerbose
global filedebug
global token0
global token1
global meterId
global nDays_Init
global nMonths_Bills
global nActiveHours
global lowPriceLevel
global Elafgift_now
global elAfgift_datoskift
global elAfgift_after
global adresse
global eloverblikstatus
try:
# Define validation dates
datetimenow = datetime.now()
datetimenowplus1day = datetimenow + timedelta( days = 1 )
datetimenowplus1yearminus7days = datetimenow + timedelta( days = 365 - 7 )
datetimenow = datetimenow.strftime( datetimeFormat )
datetimenowplus1day = datetimenowplus1day.strftime( datetimeFormat )
datetimenowplus1yearminus7days = datetimenowplus1yearminus7days.strftime( datetimeFormat )
# Get tokens and validation dates...
## sql = "SELECT datetime0, token0, datetime1, token1, meterId, nDays_Init, Elafgift_now, elAfgift_datoskift, elAfgift_after FROM setup"
sql = "SELECT datetime0, token0, datetime1, token1, nActiveHours, lowPriceLevel, nMonths_Bills, nDays_Init, elAfgift_datoskift, elAfgift_after, meterId, area_primary FROM setup"
if dbExecute( sql ) < 0:
eloverblikstatus = "CheckElOverblikToken - dbExecute 1 fejler"
return False
result = dbFetch()
needNewRefreshToken = False
needNewAccessToken = False
if len( result ) >= 1:
# Tokens exist in table... 1 row only...
row = result[ 0 ]
try:
datetimetoken0 = row[ 0 ].strftime( datetimeFormat )
token0 = row[ 1 ]
datetimetoken1 = row[ 2 ].strftime( datetimeFormat )
token1 = row[ 3 ]
nActiveHours = row[ 4 ]
lowPriceLevel = row[ 5 ]
nMonths_Bills = row[ 6 ]
nDays_Init = row[ 7 ]
elAfgift_datoskift = row[ 8 ]
elAfgift_after = row[ 9 ]
meterId = row[ 10 ]
area_primary = row[ 11 ]
if area_primary == "DK1":
area_other = "DK2"
else:
area_primary = "DK2"
area_other = "DK1"
if not foretagOpslag:
adresse = "Ingen opslag - Viser gemte data for:" + chr(10) + \
"MeterId: " + meterId + " - Område: " + area_primary
needNewRefreshToken = len( token0 ) < 100 or ( datetimenow >= datetimetoken0 )
needNewAccessToken = len( token1 ) < 100 or ( datetimenow >= datetimetoken1 )
except:
needNewRefreshToken = True
needNewAccessToken = True
else:
# First time - Create tokens in table
print( ">>>> Inserting Refresh & Access tokens into table..." )
sql = "INSERT INTO setup ( datetime0, token0, datetime1, token1 ) VALUES ('" + datetimenowplus1yearminus7days + "','" + token0 + "','" + datetimenowplus1day + "','" + token1 + "')"
if dbExecute( sql ) < 0:
eloverblikstatus = "CheckElOverblikToken - dbExecute 2 fejler"
return False
adresse = "-- Ingen opslag --"
needNewAccessToken = True
if needNewRefreshToken:
sMessage = ">>>>>>>> ElOverblik Token skal opdateres <<<<<<<<<"
ShowMessageBox( sMessage )
return False
elif needNewAccessToken:
if not foretagOpslag:
return False
print( ">>>> Requesting new Access-Token..." )
url = "https://api.eloverblik.dk/customerapi/api/token"
headers = { "accept": "application/json", "Authorization": "Bearer " + token0 }
if bVerbose:
print( "url:", url )
print( "headers:", headers )
nTry = 0
while True:
print( datetime.now(), "CheckElOverblikToken/eloverblik", file = filedebug )
print( " - url: ", url, file = filedebug )
print( " - headers: ", headers, file = filedebug )
response = requests.get( url = url, headers = headers )
print( " - Status code: ", str( response.status_code ), file = filedebug )
print( " - Status text:", response.text, file = filedebug )
if ( response.status_code == 503 or response.status_code == 429 ) and nTry < mTry: # Service not available
print( " - Fails - return...", file = filedebug )
print( "Eloverblik/token - (", response.status_code, ") ..." )
eloverblikstatus = "CheckElOverblikToken - request token fejler: " + response.text + " - (" + str( response.status_code ) + ")"
return False
'''
print( "Eloverblik/token - Kø (", response.status_code, ") ... venter i 60 sec" )
nTry = nTry + 1
time.sleep( 60 ) # Wait 60 seconds and try again
'''
elif response.status_code == 200:
print( " - Success...", file = filedebug )
result = response.json()
if bVerbose:
printY( chr(10) + chr(10) )
print( json.dumps( result, indent = 4 ) )
printY( chr(10) + chr(10) )
token1 = result[ "result" ]
print( "New token:", result )
sql = "UPDATE setup SET datetime1 = '" + datetimenowplus1day + "', token1 = '" + token1 + "'"
if dbExecute( sql ) < 0:
eloverblikstatus = "CheckElOverblikToken - dbExecute 3 fejler"
return False
return True
else:
sMessage = ">>>>>>>> Kunne ikke opfriske Access-Token <<<<<<<<<"
sMessage = sMessage + chr(10) + "Response: " + str( response )
eloverblikstatus = "CheckElOverblikToken - request token fejler: " + response.text + " - (" + str( response.status_code ) + ")"
mylogging( 1, sMessage ) # ShowMessageBox( sMessage )
return False
else:
printY( "Reusing Access-Token..." )
except Exception as e:
print( " - >>> Exception <<<{}".format(e), file = filedebug )
sMessage = ">>> CheckElOverblikToken - Exception <<<" + chr(10) + "{}".format(e)
mylogging( 2, sMessage ) # ShowMessageBox( sMessage )
return False
if not foretagOpslag:
return False
return True
def CheckConfigChange( token0x, nActiveHoursx, lowPriceLevelx, nMonths_Billsx, nDays_Initx, elAfgift_datoskiftX, elAfgift_afterX, meterIdx, area_primaryx ):
global bVerbose
global filedebug
global token0
global token1
global nDays_Init
global nActiveHours
global lowPriceLevel
global nMonths_Bills
global elAfgift_datoskift
global elAfgift_after
try:
# Define validation dates
datetimenow = datetime.now()
datetimenowplus1day = datetimenow + timedelta( days = 1 )
datetimenowplus1yearminus7days = datetimenow + timedelta( days = 365 - 7 )
datetimenow = datetimenow.strftime( datetimeFormat )
datetimenowplus1day = datetimenowplus1day.strftime( datetimeFormat )
datetimenowplus1yearminus7days = datetimenowplus1yearminus7days.strftime( datetimeFormat )
if token0x != token0:
if not foretagOpslag:
return False
print( ">>>> Requesting Access-Token to new Request-Token..." )
url = "https://api.eloverblik.dk/customerapi/api/token"
headers = { "accept": "application/json", "Authorization": "Bearer " + token0x }
print( "url:", url )
print( "headers:", headers )
nTry = 0
mTry = 100
while True:
print( datetime.now(), "CheckConfigChange/eloverblik", file = filedebug )
print( " - url: ", url, file = filedebug )
print( " - headers: ", headers, file = filedebug )
response = requests.get( url = url, headers = headers )
print( " - Status code: ", str( response.status_code ), file = filedebug )
print( " - Status text:", response.text, file = filedebug )
if ( response.status_code == 503 or response.status_code == 429 ) and nTry < mTry: # Service not available
print( " - Fails - return...", file = filedebug )
print( "Eloverblik/token - (", response.status_code, ") ..." )
return False
'''
print( "Eloverblik/token - Kø (", response.status_code, ") ... venter i 60 sec" )
nTry = nTry + 1
time.sleep( 60 ) # Wait 60 seconds and try again
'''
elif response.status_code == 200:
print( " - Success...", file = filedebug )
result = response.json()
if bVerbose:
printY( chr(10) + chr(10) )
print( json.dumps( result, indent = 4 ) )
printY( chr(10) + chr(10) )
token1 = result[ "result" ]
print( "New token:", result )
sql = "UPDATE setup SET datetime0 = '" + datetimenowplus1yearminus7days + "', token0 = '" + token0x + "', datetime1 = '" + datetimenowplus1day + "', token1 = '" + token1 + "', nMonths_Bills = " + str( nMonths_Billsx ) + ", nActiveHours = " + str( nActiveHoursx ) + ", lowPriceLevel = " + str( lowPriceLevelx ) + ", nDays_Init = " + str( nDays_Initx ) + ", elAfgift_datoskift = '" + str( elAfgift_datoskiftX ) + "', elAfgift_after = '" + str( elAfgift_afterX ) + "'"
if dbExecute( sql ) < 0:
return False
## Also, get meterId
results = GetMeteringPoints( 100 )
if results is not None:
result = results[ 'result' ][0]
meterId = result[ "meteringPointId" ]
printY( chr(10) + "- GetMeteringPoints meterid: " + meterId + chr(10) + chr(10) )
sql = "UPDATE setup SET meterId = '" + meterId + "'"
if dbExecute( sql ) < 0:
return False
else:
return False
# Success - Update current configuration
token0 = token0x[:]
nActiveHours = copy.copy( nActiveHoursx )
lowPriceLevel = copy.copy( lowPriceLevelx )
nMonths_Bills = copy.copy( nMonths_Billsx )
nDays_Init = copy.copy( nDays_Initx )
elAfgift_datoskift = elAfgift_datoskiftX[:]
elAfgift_after = copy.copy( elAfgift_afterX )
return True
else:
sMessage = ">>>>>>>> Kunne ikke opfriske Access-Token <<<<<<<<<"
sMessage = sMessage + chr(10) + "Response: " + str( response )
mylogging( 2, sMessage ) # ShowMessageBox( sMessage )
return False
else:
sql = "UPDATE setup SET nDays_Init = " + str( nDays_Initx )+ ", nActiveHours = " + str( nActiveHoursx ) + ", lowPriceLevel = " + str( lowPriceLevelx ) + ", nMonths_Bills = " + str( nMonths_Billsx ) + ", elAfgift_datoskift = '" + str( elAfgift_datoskiftX ) + "', elAfgift_after = '" + str( elAfgift_afterX ) + "', meterId = '" + meterIdx + "', area_primary = '" + area_primaryx + "'"
if dbExecute( sql ) < 0:
return False
nDays_Init = copy.copy( nDays_Initx )
nActiveHours = copy.copy( nActiveHoursx )
lowPriceLevel = copy.copy( lowPriceLevelx )
nMonths_Bills = copy.copy( nMonths_Billsx )
elAfgift_datoskift = elAfgift_datoskiftX[:]
elAfgift_after = copy.copy( elAfgift_afterX )
except Exception as e:
print( " - >>> Exception <<<{}".format(e), file = filedebug )
sMessage = ">>> CheckConfigChange - Exception <<<" + chr(10) + "{}".format(e)
mylogging( 2, sMessage ) # ShowMessageBox( sMessage )
return False
return True
class QLabelValue( QLabel ):
def __init__( self, textLabel, valueLabel ):
super().__init__()
self.setText( textLabel )
self.valueLabel = valueLabel
def readHelpText( thisFile ):
global helpHelpTexts
sHelpStart = "@!"
sHelpEnd = "!@"
with open( thisFile, encoding = "utf-8" ) as f:
helpText = f.readlines()
nLines = len( helpText )
iLine = 0
for i in range( nLines ):
line = helpText[ iLine ]
if line.startswith( sHelpStart ):
# Start of helpID section
helpText.pop( iLine ) # Skip line with helpID tag in help text
iLine = iLine - 1
if len( line ) > 2:
helpID = line[ 2: ]
helpID = helpID.replace( chr(10), '' ).replace( chr(13), '' )
helpHelpText = []
jLine = iLine + 1
# Scan until we fine HelpID end tag
while jLine < nLines:
linex = helpText[ jLine ]
if linex.startswith( sHelpEnd + helpID ):
break; # end of helpID Text
if not( linex.startswith( sHelpStart ) or linex.startswith( sHelpEnd ) ):
helpHelpText.append( linex ) # Buffer up HelpID text
jLine = jLine + 1
if len( helpHelpText ) > 0:
helpHelpText = ''.join( helpHelpText )
helpHelpText = helpHelpText.replace( chr(10), ' ' ).replace( chr(13), '' )
## if helpID in helpHelpTexts:
## helpHelpText = helpHelpTexts[ helpID ] + "<br>" + helpHelpText
helpHelpTexts[ helpID ] = helpHelpText
elif line.startswith( sHelpEnd ):
helpText.pop( iLine )
iLine = iLine - 1
iLine = iLine + 1
helpText = ''.join( helpText )
helpText = helpText.replace( chr(10), ' ' ).replace( chr(13), '' )
return helpText
def findMinMax( thisYear, thisMonth ):
daysInMonth = nDaysInMonth( thisYear, thisMonth )
timeNow = datetime.now()
if thisMonth > timeNow.month and thisYear >= timeNow.year:
print( "Traceback: " )
traceback.print_stack()
exit(0)
bFullMonth = True
if thisYear == timeNow.year and thisMonth == timeNow.month:
# Current month...
if timeNow.day <= 2:
return # Nothing to fetch...
bFullMonth = False
daysInMonth = timeNow.day - 2
## ShowMessageBox( "@1" )
sumprismin = 9999999999
sumprisminday = 0
sumkwhmin = 9999999999
sumkwhminday = 0
pristimemin = 9999999999
pristimeminday = 0
pristimeminhour = 0
kwhtimemin = 9999999999
kwhtimeminday = 0
kwhtimeminhour = 0
priskwhtimemin = 9999999999
priskwhtimeminday = 0
priskwhtimeminhour = 0
sumprismax = 0
sumprismaxday = 0
sumkwhmax = 0
sumkwhmaxday = 0
pristimemax = 0
pristimemaxday = 0
pristimemaxhour = 0
kwhtimemax = 0
kwhtimemaxday = 0
kwhtimemaxhour = 0
priskwhtimemax = 0
priskwhtimemaxday = 0
priskwhtimemaxhour = 0
sumsumkwh = 0
sumsumpris = 0
QApplication.setOverrideCursor( Qt.WaitCursor )
try:
daysInMonth1 = daysInMonth
for iDay in range( daysInMonth ):
date1 = str( thisYear ) + "-" + "%02d" % thisMonth + "-" + "%02d" % ( iDay + 1 )
if iDay < daysInMonth - 1 or not bFullMonth:
date2 = str( thisYear ) + "-" + "%02d" % thisMonth + "-" + "%02d" % ( iDay + 2 )
else:
thisMonth1 = thisMonth + 1
if thisMonth1 == 13:
thisMonth1 = 1
thisYear1 = thisYear + 1
else:
thisYear1 = thisYear
date2 = str( thisYear1 ) + "-" + str( thisMonth1 ) + "-01"
if bVerbose:
print( "----------------------" )
print( "findMinMax year:", thisYear, ", month:", thisMonth, ", from:", date1, ", to:", date2 )
sql1 = "SELECT MAX( value ) FROM data " + \
"WHERE ident = 'Forbrug' AND " \
"meterId = '" + meterId + "' AND " + \
"datetime >= '" + date1 + "' AND datetime < '" + date2 + "' " + \
"GROUP BY datetime ORDER BY datetime"
if dbExecute( sql1 ) < 0:
QApplication.restoreOverrideCursor()
mylogging( 1, "Locate Max - FEJLER - 1" )
return -2
records1 = dbFetch()
nRecords1 = len( records1 )
if nRecords1 <= 0:
mylogging( 1, "Locate Max - FEJLER - antal1: " + str( nRecords1 ) + chr(10) + "sql: " + sql1 )
daysInMonth1 = daysInMonth1 - 1
continue
sql2 = "SELECT MAX( value ) FROM data " + \
"WHERE ident = 'ElPrisT_" + area_primary + "' AND " + \
"meterId = '" + meterId + "' AND " + \
"datetime >= '" + date1 + "' AND datetime < '" + date2 + "' " + \
"GROUP BY datetime ORDER BY datetime"
if dbExecute( sql2 ) < 0:
QApplication.restoreOverrideCursor()
mylogging( 1, "Locate Max - FEJLER - 2" )
return -2
records2 = dbFetch()
nRecords2 = len( records2 )
if nRecords2 <= 0:
QApplication.restoreOverrideCursor()
mylogging( 1, "Locate Max - FEJLER - antal2: " + str( nRecords2 ) )
return -2
if nRecords1 != nRecords2:
QApplication.restoreOverrideCursor()
mylogging( 1, "Locate Max - FEJLER - antal 1 <> 2: " + str( nRecords1 ) + " <> " + str( nRecords2 ) )
mylogging( 1, "findMinMax year " + str( thisYear ) + ", month: " + str( thisMonth ) )
mylogging( 1, "sql1: " + sql1 )
mylogging( 1, "sql2: " + sql2 )
return -2
if nRecords1 != 24:
mylogging( 1, "Locate Max - WARNING - antal1,2 er ikke 24: " + str( nRecords1 ) )
sumpris = 0
sumkwh = 0
if bVerbose:
print( "findMinMax year", thisYear, ", month:", thisMonth )
for i in range( nRecords1 ):
kwh = records1[ i ][ 0 ]
pris = records2[ i ][ 0 ]
timepris = kwh * pris
if timepris < pristimemin:
pristimemin = timepris
pristimeminday = iDay
pristimeminhour = i
if kwh < kwhtimemin:
kwhtimemin = kwh
kwhtimeminday = iDay
kwhtimeminhour = i
if pris < priskwhtimemin:
priskwhtimemin = pris
priskwhtimeminday = iDay
priskwhtimeminhour = i
if timepris > pristimemax:
pristimemax = timepris
pristimemaxday = iDay
pristimemaxhour = i
if kwh > kwhtimemax:
kwhtimemax = kwh
kwhtimemaxday = iDay
kwhtimemaxhour = i
if pris > priskwhtimemax:
priskwhtimemax = pris
priskwhtimemaxday = iDay
priskwhtimemaxhour = i
sumpris = sumpris + timepris
sumkwh = sumkwh + kwh
sumsumkwh = sumsumkwh + sumkwh
sumsumpris = sumsumpris + sumpris
if sumpris < sumprismin:
sumprismin = sumpris
sumprisminday = iDay
if sumpris > sumprismax:
sumprismax = sumpris
sumprismaxday = iDay
if sumkwh < sumkwhmin:
sumkwhmin = sumkwh
sumkwhminday = iDay
if sumkwh > sumkwhmax:
sumkwhmax = sumkwh
sumkwhmaxday = iDay
sumsumkwhavg = sumsumkwh / daysInMonth
sumsumprisavg = sumsumpris / daysInMonth
vlist = "sumprismin = '" + str( sumprismin ) + "'," + \
"sumprisminday = '" + str( sumprisminday ) + "'," + \
"sumkwhmin = '" + str( sumkwhmin ) + "'," + \
"sumkwhminday = '" + str( sumkwhminday ) + "'," + \
"pristimemin = '" + str( pristimemin ) + "'," + \
"pristimeminday = '" + str( pristimeminday ) + "'," + \
"pristimeminhour = '" + str( pristimeminhour ) + "'," + \
"kwhtimemin = '" + str( kwhtimemin ) + "'," + \
"kwhtimeminday = '" + str( kwhtimeminday ) + "'," + \
"kwhtimeminhour = '" + str( kwhtimeminhour ) + "'," + \
"priskwhtimemin = '" + str( priskwhtimemin ) + "'," + \
"priskwhtimeminday = '" + str( priskwhtimeminday ) + "'," + \
"priskwhtimeminhour = '" + str( priskwhtimeminhour ) + "'," + \
"sumprismax = '" + str( sumprismax ) + "'," + \
"sumprismaxday = '" + str( sumprismaxday ) + "'," + \
"sumkwhmax = '" + str( sumkwhmax ) + "'," + \
"sumkwhmaxday = '" + str( sumkwhmaxday ) + "'," + \
"pristimemax = '" + str( pristimemax ) + "'," + \
"pristimemaxday = '" + str( pristimemaxday ) + "'," + \
"pristimemaxhour = '" + str( pristimemaxhour ) + "'," + \
"kwhtimemax = '" + str( kwhtimemax ) + "'," + \
"kwhtimemaxday = '" + str( kwhtimemaxday ) + "'," + \
"kwhtimemaxhour = '" + str( kwhtimemaxhour ) + "'," + \
"priskwhtimemax = '" + str( priskwhtimemax ) + "'," + \
"priskwhtimemaxday = '" + str( priskwhtimemaxday ) + "'," + \
"priskwhtimemaxhour = '" + str( priskwhtimemaxhour ) + "'," + \
"nDays = '" + str( daysInMonth1 ) + "'"
sql = "UPDATE regninger SET " + vlist + " WHERE meterId = '" + meterId + "' AND year = '" + str( thisYear ) + "' AND month = '" + str( thisMonth ) + "'"
printY( "findMinMax sql: " + sql )
QApplication.restoreOverrideCursor()
if dbExecute( sql ) < 0:
mylogging( 1, ">> Kunne ikke gemme minmax data..." ) # ShowMessageBox
return -3
return 1
except Exception as err:
QApplication.restoreOverrideCursor()
mylogging( 2, ">>> MinMax - Problem: {}".format( err ) ) # ShowMessageBox
return -1
def Fixthiskrperkwhstd( thisYear, thisMonth ):
date1 = str( thisYear ) + "-" + str( thisMonth ) + "-01"
thisMonth1 = thisMonth + 1
if thisMonth1 == 13:
thisMonth1 = 1
thisYear1 = thisYear + 1
else:
thisYear1 = thisYear
date2 = str( thisYear1 ) + "-" + str( thisMonth1 ) + "-01"
sql = "SELECT value FROM data WHERE datetime >= '"+date1+"' and datetime < '"+date2+"' AND meterId = '" + meterId + "' AND ident = 'ElPrisT_" + area_primary + "'"
printY( "Fixthiskrperkwhstd, sql: " + sql )
if dbExecute( sql ) < 0:
return None
records = dbFetch()
nRecords = len( records )
if nRecords <= 0:
return None
sumkr = 0
for i in range( nRecords ):
row = records[ i ]
sumkr = sumkr + row[ 0 ]
krperkwhstd = sumkr / nRecords
sql = "UPDATE regninger SET krperkwhstd = '" + str( krperkwhstd ) + "' WHERE meterId = '" + meterId + "' AND year = '" + str( thisYear ) + "' AND month = '" + str( thisMonth ) + "'"
if dbExecute( sql ) < 0:
mylogging( 1, ">> Kunne ikke gemme krperkwhstd" ) # ShowMessageBox
return krperkwhstd
def isMinMaxDataValid( thisYear, thisMonth ):
sql = "SELECT sumkwhmax, ndays FROM regninger WHERE meterId = '" + meterId + "' AND month = " + str( thisMonth ) + " AND year = " + str( thisYear )
if dbExecute( sql ) >= 0:
records = dbFetch()
if len( records) > 0:
row = records[ 0 ]
sumkwhmax = row[ 0 ]
nDays = row[ 1 ]
daysInMonth = nDaysInMonth( thisYear, thisMonth )
timeNow = datetime.now()
if bVerbose:
print( "isMinMaxValid...", thisYear, thisMonth, sumkwhmax, nDays, timeNow.day )
if timeNow.year == thisYear and \
timeNow.month == thisMonth:
# Current month
if nDays >= timeNow.day - 2:
# Current data still valid
printY( "...1.1" )
return 1
else:
# More days has passed and data must be updated
printY( "...0.1" )
return 0
elif sumkwhmax > 0 and ( nDays == 0 or nDays == daysInMonth ):
# Current month is up to date
printY( "...1.2" )
return 1
elif nDays == 0 or nDays == daysInMonth:
# Current month is up to date
printY( "...1.2" )
return 0
else:
# Current month need update - data not up to date
printY( "...-1.1" )
return -1
else:
# Current month need update - no data so far
printY( "...-1.2" )
return -1
else:
printY( "...-2" )
return -2
# Timer action to hide tooltips
def hideTooltip():
labelTooltip.hide()
timerTooltipShow.stop()
def showTooltip():
xdata = eventTooltip.xdata
ydata = eventTooltip.ydata
ix = int( xdata + 0.4999 )
if ix < 0:
ix = 0
timerTooltipShow.stop()
if widgetTooltip == mainForm.graphWidgetMonths:
# Months
nn = len( mainForm.resultskWh )
if ix >= nn:
ix = nn - 1
vMonth = mainForm.resultsMonths[ ix ]
vkWh = mainForm.resultskWh[ ix ]
vPrice = mainForm.resultsPrice[ ix ]
vStd = mainForm.resultsStd[ ix ] # monthsstd.append( (365.25 / 12 ) * thiskrperkwhstd * 24 * 1 )
vkWh = "{:.2f} kWh".format( vkWh )
vPrice = "{:.2f} kr".format( vPrice )
vStd = "{:.2f} /".format( vStd ) + " {:.2f} kr/kWh".format( vStd / ( 24 * 1 * ( 365.25 / 12 ) ) )
text = "MÃ¥ned: " + vMonth + "<br>" + \
"Forbrugstal:" + \
"<table>" + \
"<tr><td>Forbrug</td><td align='center'><span style='color:#32CD32;text-align:center;'>▮</span></td><td>" + vkWh + "</td></tr>" + \
"<tr><td>Pris</td><td><span style='color:red'><b>―</b></span></td><td>" + vPrice + "</td></tr>" + \
"<tr><td>Elpris</td><td><span style='color:blue'><b>―</b></span></td><td>" + vStd + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidgetMonth:
# Month
if mainForm.resultsDaysForbrug is None or ix > len( mainForm.resultsDaysForbrug ) or mainForm.resultsDaysPris[ ix - 1 ] <= 0:
text = ""
else:
if ix < 1:
ix = 1
vDay = ix
vkWh = mainForm.resultsDaysForbrug[ ix - 1 ]
vPrice = mainForm.resultsDaysPris[ ix - 1 ]
vStd = mainForm.resultsDaysElpris[ ix - 1 ] # monthsstd.append( (365.25 / 12 ) * thiskrperkwhstd * 24 * 1 )
vkWh = "{:.2f} kWh".format( vkWh )
vPrice = "{:.2f} kr".format( vPrice )
vStd = "{:.2f} /".format( vStd ) + " {:.2f} kr/kWh".format( vStd / 24 )
text = "Dag: " + str( vDay ) + "<br>" + \
"Forbrugstal:" + \
"<table>" + \
"<tr><td>Forbrug</td><td align='center'><span style='color:#32CD32;text-align:center;'>▮</span></td><td>" + vkWh + "</td></tr>" + \
"<tr><td>Pris</td><td><span style='color:red'><b>―</b></span></td><td>" + vPrice + "</td></tr>" + \
"<tr><td>Elpris</td><td><span style='color:blue'><b>―</b></span></td><td>" + vStd + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidgetDay:
# Days
if mainForm.resultsHoursForbrug is None or ix >= len( mainForm.resultsHoursForbrug ):
text = ""
else:
vHour = ix
vkWh = mainForm.resultsHoursForbrug[ ix ]
vPrice = mainForm.resultsHoursPris[ ix ]
vStd = mainForm.resultsHoursElpris[ ix ]
vkWh = "{:.2f} kWh".format( vkWh )
vPrice = "{:.2f} kr".format( vPrice )
vStd = "{:.2f} kr/kWh".format( vStd )
text = "Time: " + str( vHour ) + "<br>" + \
"Forbrugstal:" + \
"<table>" + \
"<tr><td>Forbrug</td><td align='center'><span style='color:#32CD32;text-align:center;'>▮</span></td><td>" + vkWh + "</td></tr>" + \
"<tr><td>Pris</td><td><span style='color:red'><b>―</b></span></td><td>" + vPrice + "</td></tr>" + \
"<tr><td>Elpris</td><td><span style='color:blue'><b>―</b></span></td><td>" + vStd + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidget_A:
dt = mdates.num2date( xdata )
# Fjern tzinfo
dt = datetime( dt.year, dt.month, dt.day, dt.hour, dt.minute ) # Er offset-naive
dtformat = dt.strftime( datetimeFormat )
# Look up graph values at cursor position
vmin = 1000
vmax = -1000
v1 = findInTimeseries( dataTime_1_A, dataPrice_1_A, dt )
if v1 is None:
v1 = "Afventer"
else:
vmin = min( vmin, v1 )
vmax = max( vmax, v1 )
v1 = "{:.2f} kr/kWh".format( v1 )
v1El = findInTimeseries( dataTime_1_A, dataElPrice_1_A, dt )
if v1El is None:
v1El = "Afventer"
else:
vmin = min( vmin, v1El )
vmax = max( vmax, v1El )
v1El = "{:.2f} kr/kWh".format( v1El )
v2 = findInTimeseries( dataTime_2_A, dataPrice_2_A, dt )
if v2 is None:
v2 = "Afventer"
else:
vmin = min( vmin, v2 )
vmax = max( vmax, v2 )
v2 = "{:.2f} kr/kWh".format( v2 )
v2El = findInTimeseries( dataTime_2_A, dataElPrice_2_A, dt )
if v2El is None:
v2El = "Afventer"
else:
vmin = min( vmin, v2El )
vmax = max( vmax, v2El )
v2El = "{:.2f} kr/kWh".format( v2El )
if vmin == 1000 or ydata < vmin - ( vmax - vmin ) / 10 or ydata > vmax + ( vmax - vmin ) / 10:
# Current position is too far from graphs - do not show tooltip
text = ""
else:
sUpdateTime = ""
recordUpdateTime1 = GetUpdateTime( "ElPrisT_" + area_primary, dtformat )
if recordUpdateTime1 is not None:
nRecordsUpdateTime1 = len( recordUpdateTime1 )
for i in range( nRecordsUpdateTime1 ):
row = recordUpdateTime1[ i ]
dataUpdateTime = row[ 0 ]
dtUpdateTime = dataUpdateTime.strftime( datetimeFormat )
sUpdateTime = sUpdateTime + "<tr><td>Opdateret (E):</td><td colspan=2>" + dtUpdateTime + "</td></tr>"
if v1 == v2 and v1El == v2El:
# Graphs (DK1 and DK2) have same values - show...
# Glypts, like 8213; ---> https://www.ee.ucl.ac.uk/~mflanaga/java/HTMLandASCIItableC1.html
text = "<table>" + \
"<tr><td>Tidspunkt:</td><td colspan=2>" + dtformat + "</td></tr>" + sUpdateTime + \
"<tr><td colspan=3>Timepriser:</td></tr>" + \
"<tr><td>" + area_primary + "/" + area_other + "</span></td><td><span style='color:blue'><b>―</b></span> / <span style='color:pink'><b>―</b></span></td><td> " + v2 + "</td></tr>" + \
"<tr><td>- RÃ¥:</td><td><span style='color:magenta'><b>―</b></span> / <span style='color:cyan'><b>―</b></span></td><td>" + v2El + "</td></tr>" + \
"</table>"
else:
# Graphs (DK1 and DK2) have different values - show...
text = "<table>" + \
"<tr><td>Tidspunkt:</td><td colspan=2>" + dtformat + "</td></tr>" + sUpdateTime + \
"<tr><td colspan=3>Timepriser:</td></tr>" + \
"<tr><td>" + area_primary + "</td><td><span style='color:blue'><b>―</b></span></td><td>" + v2 + "</td></tr>" + \
u"<tr><td>- RÃ¥:</td><td><span style='color:magenta'><b>―</b></span></td><td>" + v2El + "</td></tr>" + \
"<tr><td>" + area_other + "</td><td><span style='color:pink'><b>―</b></span></td><td>" + v1 + "</td></tr>" + \
u"<tr><td>- RÃ¥:</td><td><span style='color:cyan'><b>―</b></span></td><td>" + v1El + "</td></tr>" + \
"</table>"
elif widgetTooltip == mainForm.graphWidget_H:
dt = mdates.num2date( xdata ) # Er offset-aware
# Fjern tzinfo
dt = datetime( dt.year, dt.month, dt.day, dt.hour, dt.minute ) # Er offset-naive
dtformat = dt.strftime( datetimeFormat )
ymin1, ymax1 = mainForm.graphWidget_H.axes.get_ylim()
ymin2, ymax2 = mainForm.graphWidget_H.ax2.get_ylim()
frac = ( ydata - ymin2 ) / ( ymax2 - ymin2 )
ydata1 = ymin1 + frac * ( ymax1 - ymin1 )
# Look up graph values at cursor position
vmin = 1000
vmax = -1000
vForbrug = findInTimeseries( dataTime_1_H, dataPrice_1_H, dt )
if vForbrug is None:
vForbrug = "Afventer"
else:
frac = ( vForbrug - ymin1 ) / ( ymax1 - ymin1 )
vForbrug2 = ymin2 + frac* ( ymax2 - ymin2 )
vmin = min( vmin, vForbrug2 )
vmax = max( vmax, vForbrug2 )
vForbrug = "{:.2f} kWh".format( vForbrug )
vTimepris = findInTimeseries( dataTime_2_H, dataPrice_3_H, dt )
if vTimepris is None:
vTimepris = "Afventer"
else:
vmin = min( vmin, vTimepris )
vmax = max( vmax, vTimepris )
vTimepris = "{:.2f} kr".format( vTimepris )
vElprisMed = findInTimeseries( dataTime_2_H, dataPrice_2_H, dt )
if vElprisMed is None:
vElprisMed = "Afventer"
else:
vmin = min( vmin, vElprisMed )
vmax = max( vmax, vElprisMed )
vElprisMed = "{:.2f} kr/kWh".format( vElprisMed )
vElprisUden = findInTimeseries( dataTime_1_H, dataElPrice_2_H, dt )
if vElprisUden is None:
vElprisUden = "Afventer"
else:
vmin = min( vmin, vElprisUden )
vmax = max( vmax, vElprisUden )
vElprisUden = "{:.2f} kr/kWh".format( vElprisUden )
if vmin == 1000 or ydata > vmax + ( vmax - vmin ) / 10:
# Current position is too far from graphs - do not show tooltip
text = ""
else:
# Current position is on graphs - show tooltip
sUpdateTime = ""
recordUpdateTime1 = GetUpdateTime( "ElPrisT_" + area_primary, dtformat )
if recordUpdateTime1 is not None:
nRecordsUpdateTime1 = len( recordUpdateTime1 )
for i in range( nRecordsUpdateTime1 ):
row = recordUpdateTime1[ i ]
dataUpdateTime = row[ 0 ]
dtUpdateTime = dataUpdateTime.strftime( datetimeFormat )
sUpdateTime = sUpdateTime + "<tr><td>Opdateret (E):</td><td colspan=2>" + dtUpdateTime + "</td></tr>"
recordUpdateTime = GetUpdateTime( "Forbrug", dtformat )
if recordUpdateTime is not None:
nRecordsUpdateTime = len( recordUpdateTime )
for i in range( nRecordsUpdateTime ):
row = recordUpdateTime[ i ]
dataUpdateTime = row[ 0 ]
dtUpdateTime = dataUpdateTime.strftime( datetimeFormat )
sUpdateTime = sUpdateTime + "<tr><td>Opdateret (F):</td><td colspan=2>" + dtUpdateTime + "</td></tr>"
text = "<table>" + \
"<tr><td>Tidspunkt:</td><td colspan=2>" + dtformat + "</td></tr>" + sUpdateTime + \
"<tr><td>Forbrug</td><td align='center'><span style='color:#40ff40;text-align:center;'>▮</span></td><td>" + vForbrug + "</td></tr>" + \
"<tr><td>Timepris</td><td align='center'><span style='color:red;text-align:center;'><b>―</b></span></td><td>" + vTimepris + "</td></tr>" + \
"<tr><td>Elpris</td><td align='center'><span style='color:blue;text-align:center;'><b>―</b></span></td><td>" + vElprisMed + "</td></tr>" + \
u"<tr><td>Elpris rÃ¥</td><td align='center'><span style='color:magenta;text-align:center;'><b>―</b></span></td><td>" + vElprisUden + "</td></tr>" + \
"</table>"
else:
text = ""
# Show tooltip
set_tooltip( text )
def set_tooltip( text ):
if not text:
# Nothing to show - hide the tooltip
hideTooltip()
else:
## print( "In set_tooltip: " + text )
tooltipText = "<html><div style='white-space: nowrap;'>" + text + "</div></html>"
# Set tooltip content
labelTooltip.setText( tooltipText )
# When content changes (modified number of lines in tooltip) we must adjust the size
labelTooltip.adjustSize()
# Position tooltip a bit offset to the cursor
p = QtGui.QCursor.pos() + QtCore.QPoint( 10, 20 )
labelTooltip.move( p )
# ... and show the tooltip
labelTooltip.show()
# Start timer which will hide after 10 sec
timerTooltipHide.start( 10 * 1000 )
# Find value in timeseries - Used to display values in tooltips
def findInTimeseries( tsX, tsY, tValue ):
v = None
try:
n = len( tsX )
if n > 0:
v = tsY[ 0 ]
## print("Qt: v", QtCore.QT_VERSION_STR, " - PyQt: v", QtCore.PYQT_VERSION_STR)
print( "tValue", tValue )
## print( "t0", tsX[ 0 ] )
for i in range( n - 1 ):
if tValue >= tsX[ i + 1 ]:
v = tsY[ i + 1 ]
except Exception as e:
mylogging( 2, ">>>>> findInTimeseries Exception <<<<<" + chr(10) + "{}".format( e ) )
return v
class MinMaxDialog( QDialog ):
def __init__( self, thisYear, thisMonth ):
super().__init__()
vlist = "sumprismin, sumprisminday, sumkwhmin, sumkwhminday, pristimemin, pristimeminday, " + \
"pristimeminhour, kwhtimemin, kwhtimeminday, kwhtimeminhour, " + \
"priskwhtimemin, priskwhtimeminday, priskwhtimeminhour, " + \
"sumprismax, sumprismaxday, sumkwhmax, sumkwhmaxday, pristimemax, pristimemaxday, " + \
"pristimemaxhour, kwhtimemax, kwhtimemaxday, kwhtimemaxhour, " + \
"priskwhtimemax, priskwhtimemaxday, priskwhtimemaxhour, nDays"
daysInMonth = nDaysInMonth( thisYear, thisMonth )
for i in range( 2 ):
sql = "SELECT " + vlist + " FROM regninger WHERE meterId = '" + meterId + "' AND month = " + str( thisMonth ) + " AND year = " + str( thisYear )
if dbExecute( sql ) < 0:
mylogging( 1, ">> Kunne ikke hente data fra regninger" ) # ShowMessageBox
else:
records = dbFetch()
if len( records) <= 0:
mylogging( 1, "For få rækker i regninger..." ) # ShowMessageBox
else:
if len( records ) > 1:
mylogging( 1, "For mange rækker i regninger..." ) # ShowMessageBox
row = records[ 0 ]
sumprismin = row[ 0 ]
sumprisminday = row[ 1 ]
sumkwhmin = row[ 2 ]
sumkwhminday = row[ 3 ]
pristimemin = row[ 4 ]
pristimeminday = row[ 5 ]
pristimeminhour = row[ 6 ]
kwhtimemin = row[ 7 ]
kwhtimeminday = row[ 8 ]
kwhtimeminhour = row[ 9 ]
priskwhtimemin = row[ 10 ]
priskwhtimeminday = row[ 11 ]
priskwhtimeminhour = row[ 12 ]
sumprismax = row[ 13 ]
sumprismaxday = row[ 14 ]
sumkwhmax = row[ 15 ]
sumkwhmaxday = row[ 16 ]
pristimemax = row[ 17 ]
pristimemaxday = row[ 18 ]
pristimemaxhour = row[ 19 ]
kwhtimemax = row[ 20 ]
kwhtimemaxday = row[ 21 ]
kwhtimemaxhour = row[ 22 ]
priskwhtimemax = row[ 23 ]
priskwhtimemaxday = row[ 24 ]
priskwhtimemaxhour = row[ 25 ]
nDays = row[ 26 ]
timeNow = datetime.now()
if timeNow.year == thisYear and \
timeNow.month == thisMonth:
# Current month
if nDays >= timeNow.day - 2:
break # Current data still valid
# else:
# # More days has passed and data must be updated
elif sumkwhmax > 0 and ( nDays == 0 or nDays == daysInMonth ):
# Current month is up to date
break
# Recalculate... as more days has passed since last
## ShowMessageBox( "Recalculating month " + str( thisYear ) + "-" + str( thisMonth ) )
# Opret minmax data i resultater
if i == 0:
if findMinMax( thisYear, thisMonth ) < 0:
mylogging( 1, ">> Kunne ikke oprette minmax data (1)..." ) # ShowMessageBox
return
else:
mylogging( 1, ">> Kunne ikke oprette minmax data (2)..." ) # ShowMessageBox
return
self.setWindowTitle( "Min-Max" )
self.setMinimumWidth( 500 )
OKbutton = QDialogButtonBox( QDialogButtonBox.Ok ) # | QDialogButtonBox.Cancel
OKbutton.accepted.connect( self.accept )
## self.buttonBox.rejected.connect(self.reject)
# the following is if you need to interact with the other window
self.setWindowModality( QtCore.Qt.NonModal )
sTitle = "Min/Max forbrug og priser for " + monthnameLong( thisMonth ) + " måned - " + str( thisYear )
sTitle = sTitle + chr(10) + datetime.now().strftime( datetimeFormat )
## print( "Traceback: " )
## traceback.print_stack()
mesMinText1 = "Mindste dagspris" + chr(10) + "%.0f kr" % sumprismin + chr(10) + "%02d" % ( sumprisminday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear )
mesMinText2 = "Mindste dagsforbrug" + chr(10) + "%.1f kWh" % sumkwhmin + chr(10) + "%02d" % ( sumkwhminday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear )
mesMinText3 = "Mindste timepris" + chr(10) + "%.2f kr" % pristimemin + chr(10) + "%02d" % ( pristimeminday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear ) + " kl. " + "%02d" % pristimeminhour
mesMinText4 = "Mindste timeforbrug" + chr(10) + "%.2f kWh" % kwhtimemin + chr(10) + "%02d" % ( kwhtimeminday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear ) + " kl. " + "%02d" % kwhtimeminhour
mesMinText5 = "Mindste kWh pris" + chr(10) + "%.2f kr/kWh" % priskwhtimemin + chr(10) + "%02d" % ( priskwhtimeminday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear ) + " kl. " + "%02d" % priskwhtimeminhour
mesMaxText1 = "Største dagspris" + chr(10) + "%.0f kr" % sumprismax + chr(10) + "%02d" % ( sumprismaxday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear )
mesMaxText2 = "Største dagsforbrug" + chr(10) + "%.1f kWh" % sumkwhmax + chr(10) + "%02d" % ( sumkwhmaxday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear )
mesMaxText3 = "Største timepris" + chr(10) + "%.2f kr" % pristimemax + chr(10) + "%02d" % ( pristimemaxday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear ) + " kl. " + "%02d" % pristimemaxhour
mesMaxText4 = "Største timeforbrug" + chr(10) + "%.2f kWh" % kwhtimemax + chr(10) + "%02d" % ( kwhtimemaxday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear ) + " kl. " + "%02d" % kwhtimemaxhour
mesMaxText5 = "Største kWh pris" + chr(10) + "%.2f kr/kWh" % priskwhtimemax + chr(10) + "%02d" % ( priskwhtimemaxday + 1 ) + "-" + monthname( thisMonth ) + "-" + str( thisYear ) + " kl. " + "%02d" % priskwhtimemaxhour
titleLabel = QLabel( sTitle )
titleLabel.setAlignment( Qt.AlignCenter )
viewMin1Button = QPushButton( mesMinText1 )
viewMin2Button = QPushButton( mesMinText2 )
viewMin3Button = QPushButton( mesMinText3 )
viewMin4Button = QPushButton( mesMinText4 )
viewMin5Button = QPushButton( mesMinText5 )
viewMin1Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = sumprisminday + 1 ) )
viewMin2Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = sumkwhminday + 1 ) )
viewMin3Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = pristimeminday + 1 ) )
viewMin4Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = kwhtimeminday + 1 ) )
viewMin5Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = priskwhtimeminday + 1 ) )
viewMax1Button = QPushButton( mesMaxText1 )
viewMax2Button = QPushButton( mesMaxText2 )
viewMax3Button = QPushButton( mesMaxText3 )
viewMax4Button = QPushButton( mesMaxText4 )
viewMax5Button = QPushButton( mesMaxText5 )
viewMax1Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = sumprismaxday + 1 ) )
viewMax2Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = sumkwhmaxday + 1 ) )
viewMax3Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = pristimemaxday + 1 ) )
viewMax4Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = kwhtimemaxday + 1 ) )
viewMax5Button.clicked.connect( partial( self.viewx, thisYear = thisYear, thisMonth = thisMonth, thisDay = priskwhtimemaxday + 1 ) )
layout = QGridLayout()
layout.addWidget( titleLabel, 0, 0, 1, 2 )
layout.addWidget( viewMin1Button, 1, 0 )
layout.addWidget( viewMin2Button, 2, 0 )
layout.addWidget( viewMin3Button, 3, 0 )
layout.addWidget( viewMin4Button, 4, 0 )
layout.addWidget( viewMin5Button, 5, 0 )
layout.addWidget( viewMax1Button, 1, 1 )
layout.addWidget( viewMax2Button, 2, 1 )
layout.addWidget( viewMax3Button, 3, 1 )
layout.addWidget( viewMax4Button, 4, 1 )
layout.addWidget( viewMax5Button, 5, 1 )
# OK button
layout.addWidget( OKbutton, 6, 0, 1, 2 )
self.setLayout( layout )
def viewx( self, thisYear, thisMonth, thisDay ):
sDate = str( thisYear ) + "-%02d" % thisMonth + "-%02d" % thisDay
thisDateTime = datetime.strptime( sDate, dateFormat )
nDays = int( mainForm.spinDays.value() / 2 )
if nDays > 1:
thisDateTime = thisDateTime - timedelta( days = nDays )
mainForm.datetimeFrom.setDateTime( thisDateTime )
mainForm.btnShowClk( 0 )
mainForm.showResultMonth( thisYear, thisMonth )
mainForm.showResultDay( thisYear, thisMonth, thisDay )
def updateResults( thisYear, thisMonth ):
global meterId
global area_primary
global mainForm
global filedebug
timeEnd = datetime.now() - timedelta( days = 1 )
fromDate = str( thisYear ) + "-" + "%02d" % thisMonth + "-01"
dateFrom = datetime.strptime( fromDate, dateFormat )
daysInMonth = nDaysInMonth( thisYear, thisMonth )
bFullMonth = True
print( datetime.now(), "updateResults", thisYear, thisMonth, file = filedebug )
if thisYear == timeEnd.year and thisMonth == timeEnd.month:
# Current month...
if timeEnd.day <= 1:
return # Nothing to fetch...
bFullMonth = False
daysInMonth = timeEnd.day - 1
dateTo = timeEnd
else:
dateTo = datetime( dateFrom.year + int( dateFrom.month / 12 ), ( ( dateFrom.month % 12 ) + 1), 1)
toDate = datetime.strftime( dateTo, dateFormat )
## ShowMessageBox( "Ready to updateResults" + chr(10) + "- fromDate: " + fromDate + chr(10) + "- toDate: " + toDate )
QApplication.setOverrideCursor( Qt.WaitCursor )
dataTime1, datakWh, bUpdate = GetElOverblikX( fromDate, toDate )
dataTime2, dataPriceT, dataPrice = GetEnergyPricesX( fromDate, area_primary, toDate )
nData1 = len( dataTime1 )
nData2 = len( dataTime2 )
if nData1 > 0 and nData1 != nData2:
nData1x = nData1
nData2x = nData2
nDaysFix = 0
while nData2x > 24:
nData2x = nData2x - 24
nDaysFix = nDaysFix + 1
if nData1x == nData2x:
break
if nData1x == nData2x: # Fixed missing Eloverblik data by whole days...
nData1 = nData1x
nData2 = nData2x
daysInMonth = daysInMonth - nDaysFix
print( "Fixed: Data from Eloverblik is missing " + str( nDaysFix ) + " day(s)" )
else:
QApplication.restoreOverrideCursor()
mylogging( 1, "Problem: Length of returned data is inconsistent" + chr(10) + "- 1: " + str( nData1 ) + chr(10) + "- 2: " + str( nData2 ) )
return
if nData1 <= 0:
QApplication.restoreOverrideCursor()
mylogging( 1, "Problem: Length of returned data from Eloverblik is 0" )
return
sumkWh = 0
sumPrice = 0
sumPrice1 = 0
fformat1 = "%.1f"
ii = 0
daysInMonth = int( ( nData1 + 10 ) / 24 )
for iData in range( nData1 ):
vkWh = datakWh[ iData ]
vPrice = dataPriceT[ iData ]
sumkWh = sumkWh + vkWh
sumPrice = sumPrice + vkWh * vPrice
sumPrice1 = sumPrice1 + 1 * vPrice
ii = ii + 1
## if bVerbose:
## print( "ii, vkWh, vPrice, sumkWh, sumPrice, sumPrice1", ii, vkWh, vPrice, sumkWh, sumPrice, sumPrice1, file = filedebug )
if ii == 24:
ii = 0
kWhAv = sumkWh / daysInMonth
priceAv = sumPrice / daysInMonth
priceStd = sumPrice / sumkWh
priceStd1 = sumPrice1 / nData1
## if bVerbose:
## print( "kWhAv, priceAv, priceStd, priceStd1", kWhAv, priceAv, priceStd, priceStd1, file = filedebug )
## filedebug.flush()
try:
sql = "DELETE FROM regninger WHERE meterId = '" + meterId + "' AND month = " + str( thisMonth ) + " AND year = " + str( thisYear )
printY( "Store resultater - 1 - sql: " + sql )
if dbExecute( sql ) < 0:
QApplication.restoreOverrideCursor()
mylogging( 1, "Store resultater - FEJLER - 1" )
return -2
sql = "INSERT INTO regninger ( meterId, year, month, sum_kwh, sum_price ) VALUES ('"+meterId+"',"+str(thisYear)+","+str(thisMonth)+","+str(sumkWh)+","+str(sumPrice)+")"
printY( "Store resultater - 2 - sql: " + sql )
if dbExecute( sql ) < 0:
QApplication.restoreOverrideCursor()
mylogging( 1, "Store resultater - FEJLER - 2" )
return -2
printY( "Store resultater - 3 - Done" )
except Exception as err:
QApplication.restoreOverrideCursor()
print( " - >>> Exception <<<{}".format(e), file = filedebug )
mylogging( 2, "updateResults - Problem: {}".format( err ) )
if mainForm is not None:
labels = mainForm.labels
thisLabelIdText = monthname( thisMonth ) + "-" + str( thisYear % 100 )
maxDaysInMonth = nDaysInMonth( thisYear, thisMonth )
if not bFullMonth:
# Current month...
maxDaysInMonth = timeNow.day - 2
'''
thisLabelMonth = labels[ "lastMonthN" ]
if maxDaysInMonth != daysInMonth:
thisLabelMonth.setText( "(*" + str( daysInMonth ) + ")" )
elif not bFullMonth:
thisLabelMonth.setText( "(" + str( daysInMonth ) + ")" )
else:
thisLabelMonth.setText( "" )
'''
fformat = "%.0f"
fformat1 = "%.1f"
fformat2 = "%.2f"
thisLabel = labels[ thisLabelIdText + "kWh" ]
sLabel = fformat % sumkWh
thisLabel.setText( sLabel )
thisLabel = labels[ thisLabelIdText + "Price" ]
sLabel = fformat % sumPrice
thisLabel.setText( sLabel )
thisLabel = labels[ thisLabelIdText + "kWhAv" ]
sLabel = fformat1 % kWhAv
thisLabel.setText( sLabel )
thisLabel = labels[ thisLabelIdText + "PriceAv" ]
sLabel = fformat1 % priceAv
thisLabel.setText( sLabel )
thisLabel = labels[ thisLabelIdText + "PricekWh" ]
sLabel = fformat2 % priceStd
thisLabel.setText( sLabel )
thisLabel = labels[ thisLabelIdText + "PriceStd" ]
sLabel = fformat2 % priceStd1
thisLabel.setText( sLabel )
QApplication.restoreOverrideCursor()
def loadResultsData( thisStartMonth, thisStartYear ):
sql = "SELECT year, month, sum_kwh, sum_price, krperkwhstd, nDays from regninger where meterId = '" + meterId + "' AND ( year >= " + str( thisStartYear ) + " AND month >= " + str( thisStartMonth ) + " OR year >= " + str( thisStartYear + 1 ) + ") ORDER BY year, month"
try:
## ShowMessageBox( "loadResultData - sql: " + sql )
if dbExecute( sql ) < 0:
print( "*** loadResultsData, sql fejler: ", sql )
return [], [], [], [], [], [] # -2
records = dbFetch()
nRecords = len( records )
## ShowMessageBox( "loadResultData - nRecords: " + str( nRecords ) )
resultyear = []
resultmonth = []
resultkwh = []
resultprices = []
resultkrperkwhstd = []
resultnDays = []
for i in range( nRecords ):
row = records[ i ]
resultyear.append( row[ 0 ] )
resultmonth.append( row[ 1 ] )
resultkwh.append( row[ 2 ] )
resultprices.append( row[ 3 ] )
resultkrperkwhstd.append( row[ 4 ] )
resultnDays.append( row[ 5 ] )
return resultyear, resultmonth, resultkwh, resultprices, resultkrperkwhstd, resultnDays
except Exception as e:
print( "*** loadResultsData, exception: ", sql, " - Problem: {}".format( e ) )
mylogging( 2, "loadResultData - Problem: {}".format( e ) )
return [], [], [], [], [], []
# Hent forbrugsdata for en tidsperiode
def GetElOverblikX( dateFrom, dateTo ):
printY( "GetElOverblikX: " + str( dateFrom ) + " - " + str( dateTo ) )
# Hent data fra db...
print( datetime.now(), "GetElOverblikX", dateFrom, dateTo, file = filedebug )
recordsForbrug = GetData( "Forbrug", dateFrom, dateTo )
dataTime = []
dataForbrug = []
bOK = False
bMissingOneDay = False
bUpdate = False
if recordsForbrug is not None:
# Der var data gemt i db
fromDate = datetime.strptime( dateFrom, dateFormat )
toDate = datetime.strptime( dateTo, dateFormat )
timePeriod = toDate - fromDate
nHours = int( timePeriod.total_seconds() / ( 60 * 60 ) )
# Sommertid er perioden der løber fra
# sidste søndag i marts til
# sidste søndag i oktober.
# Ved sommertids start stilles uret en time frem kl. 02.00.
# Ved sommertids slut stilles uret en time tilbage kl. 03.00.
# In Marts we will get 1 data point less due to start of summertime
# This will mean 23 data point on the first day of summertime.
# in Oktober we should get 1 data point more due to end of summertime,
# but due to the wat data is stored the extra data point in october
# will be overwritten and we will have 24 datapoints on the first
# day of wintertime...
dateInMarts = datetime( fromDate.year, 3, 31 )
idx = ( dateInMarts.weekday() + 1 ) % 7 # MON = 0, SUN = 6 -> SUN = 0 .. SAT = 6
dateInMarts = dateInMarts - timedelta( days = idx )
if fromDate <= dateInMarts and toDate > dateInMarts:
nHours = nHours - 1
nRecordsForbrug = len( recordsForbrug )
printY( "GetElOverblikX - nHours, nRecordsForbrug: " + str( nHours ) + ", " + str( nRecordsForbrug ) )
print( " - data in db - nHours, nRecordsForbrug: " + str( nHours ) + ", " + str( nRecordsForbrug ), file = filedebug )
if nHours == nRecordsForbrug or nHours == nRecordsForbrug + 24:
# Alt er hentet perfekt fra db
for i in range( nRecordsForbrug ):
row = recordsForbrug[ i ]
dataTime.append( row[ 0 ] )
dataForbrug.append( row[ 1 ] )
bOK = True
bMissingOneDay = ( nHours == nRecordsForbrug + 24 )
print( " - bOK, bMissingOneDay: ", bOK, bMissingOneDay, file = filedebug )
if not bOK or bMissingOneDay:
# Hent nye data
if bMissingOneDay:
toDate = datetime.strptime( dateTo, dateFormat )
fromDate = toDate - timedelta( days = 1 )
fromDate = fromDate.strftime( dateFormat )
printY( "Hent 1 døgn..." + str( fromDate ) + " ... " + str( dateTo ) )
dataTime1, dataForbrug1 = GetElOverblik( fromDate, dateTo )
if len( dataTime1 ) > 0:
# Data OK - Gem i db...
StoreTimeseries( "Forbrug", dataTime1, dataForbrug1 )
# Hent igen - dateTime - Timezone issue...
recordsForbrug = GetData( "Forbrug", fromDate, dateTo )
nRecordsForbrug = len( recordsForbrug )
printY( "GetElOverblikXX - nHours, nRecordsForbrug: " + str( nHours ) + ", " + str( nRecordsForbrug ) )
for i in range( nRecordsForbrug ):
row = recordsForbrug[ i ]
dataTime.append( row[ 0 ] )
dataForbrug.append( row[ 1 ] )
printY( "Hent 1 døgn... OK" )
bUpdate = True
else:
printY( "Hent 1 døgn... fejler" )
else:
printY( "Hent flere døgn..." + str( dateFrom ) + " - " + str( dateTo ) )
dataTime, dataForbrug = GetElOverblik( dateFrom, dateTo )
if len( dataTime ) > 0:
# Data OK - Gem i db...
StoreTimeseries( "Forbrug", dataTime, dataForbrug )
printY( "Hent flere døgn... OK" )
bUpdate = True
else:
printY( "Hent flere døgn... fejler" )
return dataTime, dataForbrug, bUpdate
def GetElOverblik( dateFrom, dateTo ):
global bVerbose
global token1
global iToggle
if not CheckElOverblikToken( ):
return [], []
headers = { "accept": "application/json", "Content-Type": "application/json", "Authorization": "Bearer " + token1 }
data = { "meteringPoints": { "meteringPoint": [ meterId ] } }
url = "https://api.eloverblik.dk/customerapi/api/meterdata/gettimeseries/" + dateFrom + "/" + dateTo
# Eloverblik kan bruges med /Hour eller /Quarter - BEGGE giver timeværdier!
# Men det sker OFTE at Eloverblik ikke returnerer data ved datoskifte (midnat)
# Det er så observeret at hvis der skiftes på /xxxxx så returneres data oftere....
# Derfor toggles, og når så der ikke returneres data een gang, så kommer det måske næste gang...
if iToggle == 0:
url = url + "/Hour"
else:
url = url + "/Quarter"
iToggle = 1 - iToggle
## url = "https://api.eloverblik.dk/customerapi/api/meterdata/gettimeseries/" + dateFrom + "/" + dateTo + "/Quarter"
if bVerbose:
print( "GetEloverblik - url:", url )
print( "GetEloverblik - headers:", headers )
print( "GetEloverblik - data:", data )
try:
print( datetime.now(), "GetEloverblik/eloverblik", file = filedebug )
print( " - url: ", url, file = filedebug )
print( " - headers: ", headers, file = filedebug )
print( " - data: ", data, file = filedebug )
response = requests.post( url = url, headers = headers, json = data ) # or json = data replaced by data = json.dumps( data )
print( " - Status code: ", str( response.status_code ), file = filedebug )
print( " - Status text:", response.text, file = filedebug )
nTry = 0
mTry = 10
while True:
if bVerbose:
print( "GetEloverblik - Status code:", response.status_code )
print( "GetEloverblik - Text:", response.text )
if ( response.status_code == 503 or response.status_code == 429 ) and nTry < mTry: # Service not available
print( " - Fails - return...", file = filedebug )
print( "GetEloverblik/gettimeseries - ", response.status_code, ") ..." )
return [], []
'''
print( "GetEloverblik/gettimeseries - Kø (", response.status_code, ") ... venter i 60 sec" )
nTry = nTry + 1
time.sleep( 60 ) # Wait 60 seconds and try again
'''
if response.status_code == 200:
print( " - Success...", file = filedebug )
dataTime = []
dataValue = []
result = response.json()
if bVerbose:
printY( "GetEloverblik - result" + chr(10) + chr(10) )
print( json.dumps( result, indent = 4 ) )
printY( chr(10) + chr(10) )
nResult = len( result )
result = result[ "result" ]
rSuccess = result[0][ "success" ]
rErrorCode = result[0][ "errorCode" ]
rErrorText = result[0][ "errorText" ]
rId = result[0][ "id" ]
rStackTrace = result[0][ "stackTrace" ]
rDoc = result[0][ "MyEnergyData_MarketDocument" ]
rDocTime = rDoc[ "period.timeInterval" ]
if rDocTime is not None:
rStart = rDoc[ "period.timeInterval" ] [ "start" ]
rEnd = rDoc[ "period.timeInterval" ] [ "end" ]
rTS = rDoc[ "TimeSeries" ]
rTSn = len( rTS )
for iTS in range( rTSn ):
rTSi = rTS[ iTS ]
rPeriod = rTSi[ "Period" ]
nPeriods = len( rPeriod )
for iPeriod in range( nPeriods ):
rPeriodi = rPeriod[ iPeriod ]
rResolution = rPeriodi[ "resolution" ]
rTSStart = rPeriodi[ "timeInterval" ][ "start" ]
rTSEnd = rPeriodi[ "timeInterval" ][ "end" ]
tStart = parser.parse( rTSStart )
tEnd = parser.parse( rTSEnd )
rPoints = rPeriodi[ "Point" ]
rPointsn = len( rPoints )
timeStart = parser.parse( rTSStart )
if bVerbose:
print( "TS from:", tStart, "to:", tEnd, "Resolution:", rResolution,"n:", rPointsn )
print( "==============" )
thisYear = datetime.now().year
# Sommertid er perioden der løber fra
# sidste søndag i marts til
# sidste søndag i oktober.
# Ved sommertids start stilles uret en time frem kl. 02.00.
# Ved sommertids slut stilles uret en time tilbage kl. 03.00.
# First day in summer time day contains only 23 data hours
isFirstDayOfSummerTime = ( rPointsn == 23 )
# First day in winter time day contains 25 data hours
isFirstDayOfWinterTime = ( rPointsn == 25 )
# "start": "2022-10-24T22:00:00Z",
isSummerTime = isFirstDayOfSummerTime or \
not isFirstDayOfWinterTime and rTSStart[ 11 : 19 ] == "22:00:00"
iPosx = 0
## printY( "rTSStart[ 11 : 19 ]", rTSStart[ 11 : 19 ] )
if isSummerTime:
## printY( "SUMMER TIME" )
iHourOffset = 2
else:
## printY( "WINTER TIME" )
iHourOffset = 1
for iPoint in range( rPointsn ):
rPoint = rPoints[ iPoint ]
iPosition = rPoint[ "position" ]
sValue = rPoint[ "out_Quantity.quantity" ]
sStatus = rPoint[ "out_Quantity.quality" ]
iPos = int( iPosition )
rValue = float( sValue )
tValue = iPos - 1 + iPosx
'''
if isFirstDayOfWinterTime and iPos == 3:
# 25 data points - Skip one... @ 02:00
iPosx = -1
printY( "SKIP ONE!!" )
continue
'''
ttValue = timeStart + timedelta( hours = tValue + iHourOffset )
dataTime.append( ttValue )
dataValue.append( rValue )
'''
if isFirstDayOfSummerTime and iPos == 2:
# Only 23 data points - Add Extra point @ 02:00
printY( "ADD ONE!!" )
iPosx = 1
tValue = iPos
ttValue = timeStart + timedelta( hours = tValue + iHourOffset )
dataTime.append( ttValue )
dataValue.append( rValue )
'''
if bVerbose:
print( "Time:", ttValue, "Value:", rValue, "Status:", sStatus )
return dataTime, dataValue
else:
sMessage = "GetEloverblik - No timeseries returned for period starting at " + dateFrom + " and ending at " + dateTo
sMessage = sMessage + chr(10) + "Response code: " + str( response.status_code )
sMessage = sMessage + chr(10) + "Response text: " + response.text
sMessage = sMessage + chr(10) + "meterId: " + meterId
sMessage = sMessage + chr(10) + "token0: " + str(len(token0)) + " " + token0[ -20: ]
sMessage = sMessage + chr(10) + "Url: " + url
## sMessage = sMessage + chr(10) + "token1: " + str(len(token1)) + " " + token1[ -20: ]
mylogging( 0, sMessage )
## ShowMessageBox( sMessage )
return [], []
else:
sMessage = "GetEloverblik - Could not get meter data for period starting at " + dateFrom + " and ending at " + dateTo
sMessage = sMessage + chr(10) + "Response code: " + str( response.status_code )
sMessage = sMessage + chr(10) + "Response text: " + response.text
sMessage = sMessage + chr(10) + "meterId: " + meterId
sMessage = sMessage + chr(10) + "token0: " + str(len(token0)) + " " + token0[ -20: ]
sMessage = sMessage + chr(10) + "Url: " + url
## sMessage = sMessage + chr(10) + "token1: " + str(len(token1)) + " " + token1[ -20: ]
mylogging( 1, sMessage )
## ShowMessageBox( sMessage )
return [], []
'''
"MyEnergyData_MarketDocument"
"period.timeInterval":
"start": "2022-10-24T22:00:00Z"
"end": "2022-10-25T22:00:00Z"
"TimeSeries" [i]
"Period" [i]
"resolution": "PT1H"
"timeInterval":
"start": "2022-10-24T22:00:00Z",
"end": "2022-10-25T22:00:00Z"
"Point" [i]
"position": "1",
"out_Quantity.quantity": "1.341",
"out_Quantity.quality": "A04"
"success": true
"errorCode": 10000,
"errorText": "NoError",
"id": "571313181100121195",
"stackTrace": null
'''
except Exception as e:
print( " - >>> Exception <<<{}".format(e), file = filedebug )
sMessage = ">>> GetElOverblik - Exception <<<" + chr(10) + "{}".format(e)
mylogging( 2, sMessage ) # ShowMessageBox( sMessage )
return [], []
def ShowMessageBox( message, detailedMessage = "", bQuestion = False ):
msg = QMessageBox()
msg.setText( message )
if detailedMessage != "":
msg.setDetailedText( detailedMessage )
if bQuestion:
msg.setIcon( QMessageBox.Question )
msg.setWindowTitle( appText( "Question" ) )
msg.setStandardButtons( QMessageBox.Ok | QMessageBox.Cancel )
else:
msg.setIcon( QMessageBox.Information )
msg.setWindowTitle( appText( "Information" ) )
msg.setStandardButtons( QMessageBox.Ok )
ret = msg.exec()
return ret
# class for scrollable label
class ScrollText( QScrollArea ):
# constructor
def __init__(self, *args, **kwargs):
QScrollArea.__init__(self, *args, **kwargs)
# making widget resizable
self.setWidgetResizable(True)
# making qwidget object
content = QWidget(self)
self.setWidget(content)
# vertical box layout
lay = QVBoxLayout(content)
# creating label
self.label = QLabel(content)
# setting alignment to the text
self.label.setAlignment(Qt.AlignLeft | Qt.AlignTop)
# making label multi-line
self.label.setWordWrap(True)
# adding label to the layout
lay.addWidget(self.label)
self.show()
# the setText method
def setText(self, text):
# setting text to the label
self.label.setText(text)
def appText( sText ):
return sText
def refreshSpotPrices( bForceUpdate = False ):
global dataTime_1_A
global dataPrice_1_A
global dataElPrice_1_A
global dataTime_2_A
global dataPrice_2_A
global dataHeating_2_A
global dataElPrice_2_A
global dateTime_NextUpdate
timeNow = datetime.now()
if bVerbose:
print( timeNow, "refreshSpotPrices - Update Spot prices... NextUpdate:", dateTime_NextUpdate )
if dateTime_NextUpdate is None:
printY( "refreshSpotPrices - - Update as NextUpdate not defined..." )
elif not bForceUpdate:
# We get here once a minute...
if timeNow < dateTime_NextUpdate:
# Spot prices ARE up to date
printY( "refreshSpotPrices - - No update as Spot prices are up to date..." )
return False
printY( "refreshSpotPrices - - Update as NextUpdate reached..." )
else:
printY( "refreshSpotPrices - - Update - Forced..." )
timeYesterday = timeNow - timedelta( days = 1 )
thisDate = timeYesterday.strftime( dateFormat )
try:
dataTime_1_A, dataPrice_1_A, dataElPrice_1_A = GetEnergyPricesX( thisDate, area_other )
except Exception as e:
msg = chr(10) + ">>> Exception <<<" + chr(10) + "{}".format(e) + chr(10) + traceback.format_exc()
mylogging( 2, ">>> Could not get energy spot prices for area " + area_primary + msg ) # ShowMessageBox( sMessage )
dataTime_1_A = []
dataPrice_1_A = []
dataPrice_1_A = []
try:
dataTime_2_A, dataPrice_2_A, dataElPrice_2_A = GetEnergyPricesX( thisDate, area_primary )
findCheapestHoursInDay()
except Exception as e:
msg = chr(10) + ">>> Exception <<<" + chr(10) + "{}".format(e) + chr(10) + traceback.format_exc()
mylogging( 2, "Could not get energy spot prices for area " + area_primary + msg ) # ShowMessageBox( sMessage )
dataTime_2_A = []
dataPrice_2_A = []
dataHeating_2_A = []
dataElPrice_2_A = []
return True
# Find cheapest hours in day
def findCheapestHoursInDay():
global dataTime_2_A
global dataPrice_2_A
global dataHeating_2_A
global nActiveHours
global lowPriceLevel
dataHeating_2_A = []
nTimerSpot = len( dataTime_2_A )
for i in range( nTimerSpot ):
dataHeating_2_A.append( 0 )
nMinTimerPerDag = nActiveHours # Minimum number of (heating) hours
lavtPrisNiveau = lowPriceLevel # kr/kwh
nTimerMax = min( 48, nTimerSpot ) # idag og imorgen
if nTimerMax < 24:
return
nDays = int( nTimerMax / 24 )
for iDay in range( nDays ):
iTime = nTimerSpot - nTimerMax + iDay * 24
nTimerHeating = 0
iLoop = 0
jLoop = 1
while nTimerHeating < nMinTimerPerDag:
prisNiveau = lavtPrisNiveau + iLoop * 0.1
nTimerHeating0 = nTimerHeating
for iiTime in range( 24 ):
if dataHeating_2_A[ iTime + iiTime ] == 0 and dataPrice_2_A[ iTime + iiTime ] <= prisNiveau:
dataHeating_2_A[ iTime + iiTime ] = jLoop
nTimerHeating = nTimerHeating + 1
# If we found too many hours, remove the most expensive ones
if nTimerHeating > nMinTimerPerDag:
nRemove = nTimerHeating - nMinTimerPerDag
for iiRemove in range( nRemove ):
iRemove = 0
vRemove = 0
for iiTime in range( 24 ):
if dataHeating_2_A[ iTime + iiTime ] == jLoop:
if iRemove == 0 or dataPrice_2_A[ iTime + iiTime ] > vRemove:
iRemove = iiTime
vRemove = dataPrice_2_A[ iTime + iiTime ]
dataHeating_2_A[ iTime + iRemove ] = 0
iLoop = iLoop + 1
if nTimerHeating != nTimerHeating0:
jLoop = jLoop + 1
print( "dataHeating_2_A", dataHeating_2_A )
'''
# This code is no longer used - uses the "unofficial" nordpool service
# Kept here for potential future use...
# Initialize class for fetching Elspot prices (today and day-ahead)
prices_spot = elspot.Prices()
# Initialize class for fetching Elsbas prices (historical)
prices_bas = elbas.Prices()
#pprintY(prices_spot.hourly(areas=['FI']))
dk1dataspot = prices_spot.hourly(areas=['DK1'])
dk2dataspot = prices_spot.hourly(areas=['DK2'])
#pprintY(prices_bas.hourly(areas=['FI']))
dk1database = prices_bas.hourly(areas=['DK1'])
dk2database = prices_bas.hourly(areas=['DK2'])
dk1spotTime = []
dk2spotTime = []
dk1baseTime = []
dk2baseTime = []
dk1spotPrice = []
dk2spotPrice = []
dk1basePrice = []
dk2basePrice = []
printY( "dk1dataspot" )
pprintY( dk1dataspot )
printY( "dk2dataspot" )
pprintY( dk2dataspot )
printY( "dk1database" )
pprintY( dk1database )
printY( "dk2database" )
pprintY( dk2database )
printY( "-----------" )
nowat = ""
try:
nowat = "1spotA"
data = dk1dataspot['areas']['DK1']['values']
nowat = "1spotB"
dk1spotTime, dk1spotPrice = makets( data )
nowat = "2spotA"
data = dk2dataspot['areas']['DK2']['values']
nowat = "2spotB"
dk2spotTime, dk2spotPrice = makets( data )
nowat = "1baseA"
data = dk1database['areas']['DK1']['Avg']
nowat = "1baseB"
dk1baseTime, dk1basePrice = makets( data )
nowat = "2baseA"
data = dk2database['areas']['DK2']['Avg']
nowat = "2baseB"
dk2baseTime, dk2basePrice = makets( data )
except Exception as e:
printY( ">>> Exception <<<" + chr(10) + "{}".format(e))
printY( "@", nowat )
printY( 'dk1spotTime', dk1spotTime )
printY( 'dk2spotTime', dk2spotTime )
printY( 'dk1baseTime', dk1baseTime )
printY( 'dk2baseTime', dk2baseTime )
printY( 'dk1spotPrice', dk1spotPrice )
printY( 'dk2spotPrice', dk2spotPrice )
printY( 'dk1basePrice', dk1basePrice )
printY( 'dk2basePrice', dk2basePrice )
dataTime_1_A = dk1baseTime + dk1spotTime
dataTime_2_A = dk2baseTime + dk2spotTime
dataPrice_1_A = dk1basePrice + dk1spotPrice
dataPrice_2_A = dk2basePrice + dk2spotPrice
'''
mylogging( 1, "----- Start logging of elpriser -----" )
print( "=============================================" + chr(10) + "dbDumpDescribe" + chr(10) )
dbDumpDescribe( "data" )
dbDumpDescribe( "setup" )
dbDumpDescribe( "regninger" )
print( "=============================================" + chr(10) + "dbUpdateSchema" + chr(10) )
dbUpdateSchema( "data", dataTable )
dbUpdateSchema( "setup", setupTable )
dbUpdateSchema( "regninger", regningerTable )
## test dbUpdateSchema( "setupNew", setupTable )
print( "=============================================" )
foretagOpslag = True
if len(sys.argv) == 2:
foretagOpslag = False
app = QApplication( sys.argv )
try:
app.setWindowIcon( QtGui.QIcon( 'Elpriser.png' ) )
except Exception as e:
print( "Elpriser - Could not set application icon: {}".format(e) )
ft = app.font()
ft.setFamily('Segoe UI')
ft.setPointSize( 10 )
app.setFont( ft )
updateCharges()
timeNow = datetime.now()
thisDate = timeNow.strftime( dateFormat )
tStart = timeNow - timedelta( hours = ( nDays_Init + 1 ) * 24 )
tEnd = tStart + timedelta( hours = nDays_Init * 24 )
startDate = tStart.strftime( dateFormat )
endDate = tEnd.strftime( dateFormat )
# Get prices and consumption nDays_Init days before today
dataTime_1_H, dataPrice_1_H, bUpdate = GetElOverblikX( startDate, endDate )
dataTime_2_H, dataPrice_2_H, dataElPrice_2_H = GetEnergyPricesX( startDate, area_primary, endDate )
# Get prices today and potentially a day ahead (if hour >= 13)
refreshSpotPrices()
mainForm = None
mainForm = WinForm()
mainForm.show()
mainForm.btnShowClk( 2 )
sys.exit(app.exec_())
Diverse
Raspberry Pi - Diverse |
... Herunder nogle mindre projekter ...
Bemærk at flere af disse projekter bruges i de større projekter.
Hønsehus
Raspberry Pi - Hønsehus |
Herunder vores projekt til at åbne/lukke for lemmen til vores hønsehus
OG undervejs: Foderautomat.
Lemmen skal være åben i de lyse timer af dagen.
Til at gøre det fysiske arbejde, bruger vi en Elektrisk lineær aktuator
- denne: RS PRO Miniature Electric Linear Actuator, RS Stock No.: 177-4517
- og strømforsyning dertil: RS Stock No.: 121-7113
- og en lille motor - denne: DC-gearmotor
- med hjemmelavet snegl: Klik for tegning af snegl
- Klik for download af Excel regneark til beregning af snegl
- Inspirationskilde - 1 - Archimedes Screw - Conveyor
- Inspirationskilde - 2 - Archimedes Screw - Conveyor
Desuden bruger vi en dobbelt afbryder fra Harald Nyborg
og et relæ-kort til fra raspberrypi.dk
Yderligere beskrivelse i koden.
(Koden bliver opdateret med foder-automatik indenfor nærmeste fremtil)
| |||
|
Pumpekontrol
Raspberry Pi - Pumpekontrol |
Herunder vores kode til at styre 2 stk. pumper (regnvand/dræn).
Applikationen er desuden en vejrstation ved brug af BME680.
Desuden skal MySQL projektet bruges... og andre ...
| |||
|
Noter til Pumpekontrol applikationen:
Anvendelse | GPIO | Relæpin | Pin | Relæpin | GPIO | Anvendelse | ||
Pumpe 2 | 21 | P29 (Relæ 3) | 40 | 39 | GND | |||
Niveau Alarm (1 or 2) | 20 | P28 (Relæ 2) | 38 | 37 | P25 (Relæ 1) | 26 | Pumpe 1 | |
Niveau 2/Rød | 16 | P27 | 36 | 35 | P24 | 19 | Alarm 1 (->Or) | |
Niveau 2/Sort=GND | GND | 34 | 33 | P23 | 13 | Niveau 2/Gul | ||
xx ?? xx | 12 | P26 | 32 | 31 | P22 | 6 | Alarm 2 (->Or) | |
GND | 30 | 29 | P21 | 5 | Niveau 2/Blå | |||
IDSC | 28 | 27 | IDSD | |||||
Niveau 1/Rød | 7 | CE1 | 26 | 25 | GND | |||
Niveau 2/Grøn | 8 | CE0 | 24 | 23 | SCK | 11 | Niveau 1/Gul | |
Niveau 1/Grøn | 25 | P6 | 22 | 21 | MISO | 9 | ||
Niveau 1/Sort=GND | GND | 20 | 19 | MOSI | 10 | Niveau 1/Blå | ||
Alarm-Ored= | 24 | P5 | 18 | 17 | 3V3 | |||
23 | P4 | 16 | 15 | P3 | 22 | |||
GND | 14 | 13 | P2 | 27 | ||||
18 | P1 | 12 | 11 | P0 | 17 | |||
15 | RX | 10 | 9 | GND | ||||
14 | TX | 8 | 7 | P7 | 4 | |||
BME680 | GND | 6 | 5 | SCL | 3 | BME680 | ||
5V | 4 | 3 | SDA | 2 | BME680 | |||
5V | 2 | 1 | 3V3 | BME680 |
MySQL
Raspberry Pi - MySQL |
Herunder vores kode til at håndtere MySQL.
| |||
|
Raspberry Pi - Mail |
Herunder vores kode til at sende en mail.
| |||
|
Motion
Raspberry Pi - Motion |
Herunder vores kode til registrering af bevægelse
| |||
|
Bootmail
Raspberry Pi - Bootmail |
Herunder vores kode til at sende en mail ved boot - med oplysning om IP og MAC-adresse
Desuden skal RPiMail projektet bruges.
| |||
|
ProgramsOnBoot
Raspberry Pi - ProgramsOnBoot |
Herunder vores kode til opstart af applikationer ved boot.
| |||
|
Alive
Raspberry Pi - Alive |
Herunder "Alive" projektet
Registrer on applikationer kører - Sæt status i MySQL - Giv alarm
| |||
|
Backup
Raspberry Pi - Backup |
Herunder "Backup" ting
Backup af MySQL database, hver dag, gem seneste 7 dage
| |||
|
Summa Summarum
Raspberry Pi - Summa Summarum |
Vi ønsker med disse sider, at give inspiration til, at andre kan lære at bruge Raspbery Pi's til diverse mere eller mindre nørdede projekter. Det kan være lærerigt, sjovt, men samtidig en tidsrøver. Du kan bruge vores projekter som inspiration, kvit og frit. --- Vi køber vores Raspberry Pi ting over nettet, hvor der findes en række salgssteder. Der er desværre p.t. vanskeligheder med at skaffe enhederne. Det har stået på gennem længere tid, på grund af diverse omstændigheder i verden omkring os - vi kan kun bede til at verden omkring os snart finder tilbage i fornuftige spor. Vi bruger "Raspberry Pi 4B", bestykket med 4GB ram, men 1GB eller 2GB går også fint. Faktisk har vi ikke kunne mærke forskel, med vores brug, om det er en 1GB eller en 4GB udgave. Vi vælger at anvende et mikro-SD kort på 64GB, af bedste/dyreste kvalitet. Dette rækker rigeligt til vores formål. Herudover har vi købt en backup-USB-drive på 128 GB. Vi har tilkøbt RPi strømforsyning, case, tastatur, mus, HDMI kabel til skærm, adapter til HDMI/mikro-HDMI og bruger en "gammel" skærm som vi havde på loftet. Dette er vores udviklings-RPi. Vi har flere Raspberry PI's, kun med case og strømforsyning (kaldes headless, fordi det er uden skærm) og kan så tilgå disse RPi's ved hjælp af VNC, som gør at en RPi kan fjernstyres fra en anden computer (PC, RPi, ...) med skærm, tastatus og mus. Når vi udvikler, gør vi det typisk gennem VNC fra en Windows PC. Men det er vigtigt at have muligheden for at tilgå en RPi med fysisk skærm, tastatur og mus - skulle det ske at man af en eller anden årsag IKKE kan fjernstyre RPi'en. Så må man se hvad der sker på RPi'en ved at koble fysisk op. Vi har installeret en mængde ting på vores udviklings-RPi, i takt med at vi har fået brug for det. Skal vi lave en ændring til et projekt, f.eks. pumpekontrollen, som kører på en selvstændig RPi, laver vi ændringen på RPi'en hvor pumpekontrollen kører. Inden vi laver ændringen, laver vi en klon af vores SD-kort fra udviklings-RPI'en og bruger så denne klon i den anden RPi, retter, tester etc. Når ændringen er gennemført, kopierer vi rettelserne tilbage til udviklings-RPi'en - typisk ved brug af vores backup-USB-drive. På denne måde har vi én og kun én konfiguration af vores RPi's.
Vi kan dog ikke yde nogen videre "support" - det er typisk selvstudier - learning by doing.
Vi har god erfaring med https://raspberrypi.dk. Andre steder kunne være Amazon, eBay o.l.