Compare commits

..

11 Commits

Author SHA1 Message Date
DBras f5dfda57b7 question: D3 Part 2 Q2 2024-06-10 15:48:18 +02:00
DBras e6c47bac4e dev: plotting file without fluff 2024-06-10 14:54:10 +02:00
DBras d1e6cc19f6 file: setpoints for load test 2024-06-10 14:37:03 +02:00
DBras bb6ebbc9db question: D3 Part 2 2024-06-10 14:34:57 +02:00
DBras fa6eda5c16 question: D3 Q4 2024-06-10 14:15:17 +02:00
DBras 563be46da6 question: D3 Q3 2024-06-10 14:00:46 +02:00
DBras b10d5f623e question: D3 Q2 2024-06-10 13:49:08 +02:00
DBras 3207c3f51f question: D3 Q1 2024-06-10 11:45:01 +02:00
DBras 2a6fc39dc0 question: prep file for answers 2024-06-10 11:33:35 +02:00
DBras a200ff729a fix: env & paths 2024-06-10 11:01:28 +02:00
DBras 1d8fbd49d1 fix: syslab-lib regex escape 2024-06-10 10:51:35 +02:00
77 changed files with 10138 additions and 4669 deletions

Binary file not shown.

View File

@ -1,28 +0,0 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\providecommand\HyField@AuxAddToFields[1]{}
\providecommand\HyField@AuxAddToCoFields[2]{}
\abx@aux@refcontext{nty/global//global/global}
\providecommand \oddpage@label [2]{}
\@writefile{toc}{~\hfill \textbf {Page}\par }
\@writefile{toc}{\contentsline {section}{\numberline {1}Test Case}{2}{section.1}\protected@file@percent }
\newlabel{sec:test_case}{{1}{2}{Test Case}{section.1}{}}
\newlabel{sec:test_case@cref}{{[section][1][]1}{[1][2][]2}}
\@writefile{toc}{\contentsline {section}{\numberline {2}Test Specification}{2}{section.2}\protected@file@percent }
\newlabel{sec:test_spec}{{2}{2}{Test Specification}{section.2}{}}
\newlabel{sec:test_spec@cref}{{[section][2][]2}{[1][2][]2}}
\@writefile{toc}{\contentsline {section}{\numberline {3}Test Results}{2}{section.3}\protected@file@percent }
\newlabel{sec:test_results}{{3}{2}{Test Results}{section.3}{}}
\newlabel{sec:test_results@cref}{{[section][3][]3}{[1][2][]2}}
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Results from the dropout test}}{2}{figure.caption.1}\protected@file@percent }
\providecommand*\caption@xref[2]{\@setref\relax\@undefined{#1}}
\newlabel{fig:results}{{1}{2}{Results from the dropout test}{figure.caption.1}{}}
\newlabel{fig:results@cref}{{[figure][1][]1}{[1][2][]2}}
\@writefile{toc}{\contentsline {section}{\numberline {4}Discussion and Outlook}{3}{section.4}\protected@file@percent }
\newlabel{sec:discussion}{{4}{3}{Discussion and Outlook}{section.4}{}}
\newlabel{sec:discussion@cref}{{[section][4][]4}{[1][3][]3}}
\newlabel{LastPage}{{4}{3}{Discussion and Outlook}{page.3}{}}
\gdef\lastpage@lastpage{3}
\gdef\lastpage@lastpageHy{3}
\abx@aux@read@bbl@mdfivesum{D41D8CD98F00B204E9800998ECF8427E}
\gdef \@abspage@last{3}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
[0] Config.pm:307> INFO - This is Biber 2.19
[0] Config.pm:310> INFO - Logfile is 'build/main.blg'
[108] biber:340> INFO - === Mon Jun 24, 2024, 10:26:41
[129] Biber.pm:419> INFO - Reading 'build/main.bcf'

View File

@ -1,251 +0,0 @@
# Fdb version 4
["biber build/main"] 1719217600.0857 "build/main.bcf" "build/main.bbl" "build/main" 1719438278.8597 0
"build/main.bcf" 1719438278.66678 107256 bbb7563979c1f9afdf84ee4ba713182f "pdflatex"
(generated)
"build/main.bbl"
"build/main.blg"
(rewritten before read)
["pdflatex"] 1719438275.13617 "main.tex" "build/main.pdf" "main" 1719438278.86009 0
"/usr/share/texmf-dist/fonts/enc/dvips/cm-super/cm-super-t1.enc" 1716849630 2971 def0b6c1f0b107b3b936def894055589 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecbi0800.tfm" 1716849630 3072 356816d08702a080511d9b4663f7e425 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecbi1000.tfm" 1716849630 3072 e6fe53b666f9cbd66cb135c6fdea66d3 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecbx1000.tfm" 1716849630 3584 2d666ecf6d466d8b007246bc2f94d9da ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/eccc1440.tfm" 1716849630 3072 f2bcba5ec52607af3501d45108193ff5 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/eccc2488.tfm" 1716849630 3072 0a9bb7969372717e6b61ec14d5eee9b0 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1000.tfm" 1716849630 3584 adb004a0c8e7c46ee66cad73671f37b4 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1200.tfm" 1716849630 3584 f80ddd985bd00e29e9a6047ebd9d4781 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1440.tfm" 1716849630 3584 3169d30142b88a27d4ab0e3468e963a2 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1728.tfm" 1716849630 3584 3c76ccb63eda935a68ba65ba9da29f1a ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm2488.tfm" 1716849630 3584 406ad7b70d9a41f7833f92b6313150c8 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecti0800.tfm" 1716849630 3072 828ba0ea87cf5b727c4bfd6367195ec2 ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecti1000.tfm" 1716849630 3072 3bce340d4c075dffe6d4ec732b4c32fe ""
"/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ectt1000.tfm" 1716849630 1536 06717a2b50de47d4087ac0e6cd759455 ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1716849630 1004 54797486969f23fa377b128694d548df ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1716849630 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam10.tfm" 1716849630 916 f87d7c45f9c908e672703b83b72241a3 ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam5.tfm" 1716849630 924 9904cf1d39e9767e7a3622f2a125a565 ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam7.tfm" 1716849630 928 2dc8d444221b7a635bb58038579b861a ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm10.tfm" 1716849630 908 2921f8a10601f252058503cc6570e581 ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm5.tfm" 1716849630 940 75ac932a52f80982a9f8ea75d03a34cf ""
"/usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm7.tfm" 1716849630 940 228d6584342e91276bf566bcf9716b83 ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmex10.tfm" 1716849630 992 662f679a0b3d2d53c1b94050fdaa3f50 ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmmi12.tfm" 1716849630 1524 4414a8315f39513458b80dfc63bff03a ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmmi6.tfm" 1716849630 1512 f21f83efb36853c0b70002322c1ab3ad ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmmi8.tfm" 1716849630 1520 eccf95517727cb11801f4f1aee3a21b4 ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmr12.tfm" 1716849630 1288 655e228510b4c2a1abe905c368440826 ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmr6.tfm" 1716849630 1300 b62933e007d01cfd073f79b963c01526 ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmr8.tfm" 1716849630 1292 21c1c5bfeaebccffdb478fd231a0997d ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmsy10.tfm" 1716849630 1124 6c73e740cf17375f03eec0ee63599741 ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm" 1716849630 1116 933a60c408fc0a863a92debe84b2d294 ""
"/usr/share/texmf-dist/fonts/tfm/public/cm/cmsy8.tfm" 1716849630 1120 8b7d695260f3cff42e636090a8002094 ""
"/usr/share/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb" 1716849630 36299 5f9df58c2139e7edcf37c8fca4bd384d ""
"/usr/share/texmf-dist/fonts/type1/public/cm-super/sfbi0800.pfb" 1716849630 199864 78641615cf96b4cff2c8a97c4dcf53a2 ""
"/usr/share/texmf-dist/fonts/type1/public/cm-super/sfcc1440.pfb" 1716849630 110032 35e0196b03e20c9bc327f34ecd2026f9 ""
"/usr/share/texmf-dist/fonts/type1/public/cm-super/sfcc2488.pfb" 1716849630 107888 79f3c4bd5eeba2adb9fc0d3049dd8ef0 ""
"/usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm1000.pfb" 1716849630 138258 6525c253f16cededa14c7fd0da7f67b2 ""
"/usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm1200.pfb" 1716849630 136101 f533469f523533d38317ab5729d00c8a ""
"/usr/share/texmf-dist/fonts/type1/public/cm-super/sfti0800.pfb" 1716849630 187625 f02a8c2c788e6490af6fc4f5ed857a0c ""
"/usr/share/texmf-dist/fonts/type1/public/cm-super/sftt1000.pfb" 1716849630 169201 9ebf99020dde51a5086e186761a34e8f ""
"/usr/share/texmf-dist/tex/context/base/mkii/supp-pdf.mkii" 1716849630 71627 94eb9990bed73c364d7f53f960cc8c5b ""
"/usr/share/texmf-dist/tex/generic/atbegshi/atbegshi.sty" 1716849630 24708 5584a51a7101caf7e6bbf1fc27d8f7b1 ""
"/usr/share/texmf-dist/tex/generic/bigintcalc/bigintcalc.sty" 1716849630 40635 c40361e206be584d448876bba8a64a3b ""
"/usr/share/texmf-dist/tex/generic/bitset/bitset.sty" 1716849630 33961 6b5c75130e435b2bfdb9f480a09a39f9 ""
"/usr/share/texmf-dist/tex/generic/gettitlestring/gettitlestring.sty" 1716849630 8371 9d55b8bd010bc717624922fb3477d92e ""
"/usr/share/texmf-dist/tex/generic/iftex/iftex.sty" 1716849630 7237 bdd120a32c8fdb4b433cf9ca2e7cd98a ""
"/usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty" 1716849630 1057 525c2192b5febbd8c1f662c9468335bb ""
"/usr/share/texmf-dist/tex/generic/infwarerr/infwarerr.sty" 1716849630 8356 7bbb2c2373aa810be568c29e333da8ed ""
"/usr/share/texmf-dist/tex/generic/intcalc/intcalc.sty" 1716849630 31769 002a487f55041f8e805cfbf6385ffd97 ""
"/usr/share/texmf-dist/tex/generic/kvdefinekeys/kvdefinekeys.sty" 1716849630 5412 d5a2436094cd7be85769db90f29250a6 ""
"/usr/share/texmf-dist/tex/generic/ltxcmds/ltxcmds.sty" 1716849630 17865 1a9bd36b4f98178fa551aca822290953 ""
"/usr/share/texmf-dist/tex/generic/pdfescape/pdfescape.sty" 1716849630 19007 15924f7228aca6c6d184b115f4baa231 ""
"/usr/share/texmf-dist/tex/generic/pdftexcmds/pdftexcmds.sty" 1716849630 20089 80423eac55aa175305d35b49e04fe23b ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex" 1716849630 1016 1c2b89187d12a2768764b83b4945667c ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex" 1716849630 43820 1fef971b75380574ab35a0d37fd92608 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex" 1716849630 19324 f4e4c6403dd0f1605fd20ed22fa79dea ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex" 1716849630 6038 ccb406740cc3f03bbfb58ad504fe8c27 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex" 1716849630 6911 f6d4cf5a3fef5cc879d668b810e82868 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex" 1716849630 4883 42daaf41e27c3735286e23e48d2d7af9 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex" 1716849630 2544 8c06d2a7f0f469616ac9e13db6d2f842 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex" 1716849630 44195 5e390c414de027626ca5e2df888fa68d ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex" 1716849630 17311 2ef6b2e29e2fc6a2fc8d6d652176e257 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex" 1716849630 21302 788a79944eb22192a4929e46963a3067 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex" 1716849630 9691 3d42d89522f4650c2f3dc616ca2b925e ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex" 1716849630 33335 dd1fa4814d4e51f18be97d88bf0da60c ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex" 1716849630 2965 4c2b1f4e0826925746439038172e5d6f ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorerdf.code.tex" 1716849630 5196 2cc249e0ee7e03da5f5f6589257b1e5b ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex" 1716849630 20821 7579108c1e9363e61a0b1584778804aa ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex" 1716849630 35249 abd4adf948f960299a4b3d27c5dddf46 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex" 1716849630 22012 81b34a0aa8fa1a6158cc6220b00e4f10 ""
"/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex" 1716849630 8893 e851de2175338fdf7c17f3e091d94618 ""
"/usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex" 1716849630 3937 3f208572dd82c71103831da976d74f1a ""
"/usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex" 1716849630 11518 738408f795261b70ce8dd47459171309 ""
"/usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex" 1716849630 186782 af500404a9edec4d362912fe762ded92 ""
"/usr/share/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex" 1716849630 32995 ac577023e12c0e4bd8aa420b2e852d1a ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfint.code.tex" 1716849630 3063 8c415c68a0f3394e45cfeca0b65f6ee6 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex" 1716849630 949 cea70942e7b7eddabfb3186befada2e6 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex" 1716849630 13270 2e54f2ce7622437bf37e013d399743e3 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex" 1716849630 104717 9b2393fbf004a0ce7fa688dbce423848 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex" 1716849630 10165 cec5fa73d49da442e56efc2d605ef154 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex" 1716849630 28178 41c17713108e0795aac6fef3d275fbca ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex" 1716849630 9649 85779d3d8d573bfd2cd4137ba8202e60 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex" 1716849630 3865 ac538ab80c5cf82b345016e474786549 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex" 1716849630 3177 27d85c44fbfe09ff3b2cf2879e3ea434 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex" 1716849630 11024 0179538121bc2dba172013a3ef89519f ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex" 1716849630 7890 0a86dbf4edfd88d022e0d889ec78cc03 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex" 1716849630 3379 781797a101f647bab82741a99944a229 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex" 1716849630 92405 f515f31275db273f97b9d8f52e1b0736 ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex" 1716849630 37466 97b0a1ba732e306a1a2034f5a73e239f ""
"/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex" 1716849630 8471 c2883569d03f69e8e1cabfef4999cfd7 ""
"/usr/share/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex" 1716849630 21211 1e73ec76bd73964d84197cc3d2685b01 ""
"/usr/share/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex" 1716849630 16121 346f9013d34804439f7436ff6786cef7 ""
"/usr/share/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex" 1716849630 44792 271e2e1934f34c759f4dedb1e14a5015 ""
"/usr/share/texmf-dist/tex/generic/pgf/pgf.revision.tex" 1716849630 114 e6d443369d0673933b38834bf99e422d ""
"/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg" 1716849630 926 2963ea0dcf6cc6c0a770b69ec46a477b ""
"/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def" 1716849630 5542 32f75a31ea6c3a7e1148cd6d5e93dbb7 ""
"/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def" 1716849630 12612 7774ba67bfd72e593c4436c2de6201e3 ""
"/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex" 1716849630 61351 bc5f86e0355834391e736e97a61abced ""
"/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex" 1716849630 1896 b8e0ca0ac371d74c0ca05583f6313c91 ""
"/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex" 1716849630 7778 53c8b5623d80238f6a20aa1df1868e63 ""
"/usr/share/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex" 1716849630 24033 d8893a1ec4d1bfa101b172754743d340 ""
"/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex" 1716849630 39784 414c54e866ebab4b801e2ad81d9b21d8 ""
"/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeyslibraryfiltered.code.tex" 1716849630 37433 940bc6d409f1ffd298adfdcaf125dd86 ""
"/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex" 1716849630 4385 510565c2f07998c8a0e14f0ec07ff23c ""
"/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex" 1716849630 29239 22e8c7516012992a49873eff0d868fed ""
"/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def" 1716849630 6950 8524a062d82b7afdc4a88a57cb377784 ""
"/usr/share/texmf-dist/tex/generic/stringenc/se-pdfdoc.def" 1716849630 5108 8920602307ea1294ccbce2300c7c6ccb ""
"/usr/share/texmf-dist/tex/generic/stringenc/stringenc.sty" 1716849630 21514 b7557edcee22835ef6b03ede1802dad4 ""
"/usr/share/texmf-dist/tex/generic/uniquecounter/uniquecounter.sty" 1716849630 7008 f92eaa0a3872ed622bbf538217cd2ab7 ""
"/usr/share/texmf-dist/tex/generic/xkeyval/xkeyval.tex" 1716849630 19231 27205ee17aaa2902aea3e0c07a3cfc65 ""
"/usr/share/texmf-dist/tex/generic/xkeyval/xkvutils.tex" 1716849630 7677 9cb1a74d945bc9331f2181c0a59ff34a ""
"/usr/share/texmf-dist/tex/latex/adjustbox/adjcalc.sty" 1716849630 5598 c49b91713cbe5e50a1fabefb733eda0d ""
"/usr/share/texmf-dist/tex/latex/adjustbox/adjustbox.sty" 1716849630 56907 e3e515e490dbc35309a010b5bbe1bef5 ""
"/usr/share/texmf-dist/tex/latex/adjustbox/tc-pdftex.def" 1716849630 4070 1677cfee6374067b93f61cf57ecd7144 ""
"/usr/share/texmf-dist/tex/latex/adjustbox/trimclip.sty" 1716849630 7244 36558f478da08e083d7316a63ba4bcd6 ""
"/usr/share/texmf-dist/tex/latex/amsfonts/amsfonts.sty" 1716849630 5949 3f3fd50a8cc94c3d4cbf4fc66cd3df1c ""
"/usr/share/texmf-dist/tex/latex/amsfonts/amssymb.sty" 1716849630 13829 94730e64147574077f8ecfea9bb69af4 ""
"/usr/share/texmf-dist/tex/latex/amsfonts/umsa.fd" 1716849630 961 6518c6525a34feb5e8250ffa91731cff ""
"/usr/share/texmf-dist/tex/latex/amsfonts/umsb.fd" 1716849630 961 d02606146ba5601b5645f987c92e6193 ""
"/usr/share/texmf-dist/tex/latex/amsmath/amsbsy.sty" 1716849630 2222 499d61426192c39efd8f410ee1a52b9c ""
"/usr/share/texmf-dist/tex/latex/amsmath/amsgen.sty" 1716849630 4173 82ac04dfb1256038fad068287fbb4fe6 ""
"/usr/share/texmf-dist/tex/latex/amsmath/amsmath.sty" 1716849630 88371 d84032c0f422c3d1e282266c01bef237 ""
"/usr/share/texmf-dist/tex/latex/amsmath/amsopn.sty" 1716849630 4474 b811654f4bf125f11506d13d13647efb ""
"/usr/share/texmf-dist/tex/latex/amsmath/amstext.sty" 1716849630 2444 0d0c1ee65478277e8015d65b86983da2 ""
"/usr/share/texmf-dist/tex/latex/appendix/appendix.sty" 1716849630 8878 d9f65b39ca82f1d70030390eca653b1c ""
"/usr/share/texmf-dist/tex/latex/atveryend/atveryend.sty" 1716849630 19336 ce7ae9438967282886b3b036cfad1e4d ""
"/usr/share/texmf-dist/tex/latex/auxhook/auxhook.sty" 1716849630 3935 57aa3c3e203a5c2effb4d2bd2efbc323 ""
"/usr/share/texmf-dist/tex/latex/base/article.cls" 1716849630 20144 147463a6a579f4597269ef9565205cfe ""
"/usr/share/texmf-dist/tex/latex/base/atbegshi-ltx.sty" 1716849630 3045 273c666a54e60b9f730964f431a56c1b ""
"/usr/share/texmf-dist/tex/latex/base/atveryend-ltx.sty" 1716849630 2462 6bc53756156dbd71c1ad550d30a3b93f ""
"/usr/share/texmf-dist/tex/latex/base/fontenc.sty" 1716849630 5119 a04a8b68ab4f6ce800a41f7f8012a10e ""
"/usr/share/texmf-dist/tex/latex/base/ifthen.sty" 1716849630 5319 2b738d02ce36ada6dcdd9534940db0ee ""
"/usr/share/texmf-dist/tex/latex/base/inputenc.sty" 1716849630 5048 425739d70251273bf93e3d51f3c40048 ""
"/usr/share/texmf-dist/tex/latex/base/size10.clo" 1716849630 8448 dbc0dbf4156c0bb9ba01a1c685d3bad0 ""
"/usr/share/texmf-dist/tex/latex/base/t1cmtt.fd" 1716849630 2443 790016d75def8d3127df5c216a45abcc ""
"/usr/share/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx" 1716849630 1818 9ed166ac0a9204a8ebe450ca09db5dde ""
"/usr/share/texmf-dist/tex/latex/biblatex/bbx/standard.bbx" 1716849630 25680 409c3f3d570418bc545e8065bebd0688 ""
"/usr/share/texmf-dist/tex/latex/biblatex/biblatex.cfg" 1716849630 69 249fa6df04d948e51b6d5c67bea30c42 ""
"/usr/share/texmf-dist/tex/latex/biblatex/biblatex.def" 1716849630 92527 8f6b3a677f74ea525477a813f33c4e65 ""
"/usr/share/texmf-dist/tex/latex/biblatex/biblatex.sty" 1716849630 528517 7eed285c714f532e12ae48b360c080f8 ""
"/usr/share/texmf-dist/tex/latex/biblatex/blx-case-expl3.sty" 1716849630 8433 72f8188742e7214b7068f345cd0287ac ""
"/usr/share/texmf-dist/tex/latex/biblatex/blx-compat.def" 1716849630 13919 5426dbe90e723f089052b4e908b56ef9 ""
"/usr/share/texmf-dist/tex/latex/biblatex/blx-dm.def" 1716849630 32455 8d3e554836db11aab80a8e11be62e1b1 ""
"/usr/share/texmf-dist/tex/latex/biblatex/cbx/numeric.cbx" 1716849630 4629 cda468e8a0b1cfa0f61872e171037a4b ""
"/usr/share/texmf-dist/tex/latex/biblatex/lbx/english.lbx" 1716849630 39965 48ce9ce3350aba9457f1020b1deba5cf ""
"/usr/share/texmf-dist/tex/latex/caption/caption.sty" 1716849630 56128 c2ccf1a29d78c33bc553880402e4fb9a ""
"/usr/share/texmf-dist/tex/latex/caption/caption3.sty" 1716849630 72619 ee90b6612147680fd73c3b1406a74245 ""
"/usr/share/texmf-dist/tex/latex/caption/subcaption.sty" 1716849630 12494 0c0cdb824278a4d51cefeb2e79901315 ""
"/usr/share/texmf-dist/tex/latex/cleveref/cleveref.sty" 1716849630 329481 7fc6b003158402a4c694bc0a1b729308 ""
"/usr/share/texmf-dist/tex/latex/collectbox/collectbox.sty" 1716849630 9124 59c3b56f1a073de66e3eea35f9c173c8 ""
"/usr/share/texmf-dist/tex/latex/csquotes/csquotes.cfg" 1716849630 7068 06f8d141725d114847527a66439066b6 ""
"/usr/share/texmf-dist/tex/latex/csquotes/csquotes.def" 1716849630 21994 2bc2a882df92d97dbce35c06bb1d3541 ""
"/usr/share/texmf-dist/tex/latex/csquotes/csquotes.sty" 1716849630 62761 3c92d495a9e03d7254b9a3266aa17164 ""
"/usr/share/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty" 1716849630 13886 d1306dcf79a944f6988e688c1785f9ce ""
"/usr/share/texmf-dist/tex/latex/eso-pic/eso-pic.sty" 1716849630 11876 6ef493863ae0d7a984706973240c2237 ""
"/usr/share/texmf-dist/tex/latex/etoolbox/etoolbox.sty" 1716849630 46845 3b58f70c6e861a13d927bff09d35ecbc ""
"/usr/share/texmf-dist/tex/latex/fancyhdr/fancyhdr.sty" 1716849630 18450 88279bf67c81e69f8e3f1c1bad1a26c5 ""
"/usr/share/texmf-dist/tex/latex/float/float.sty" 1716849630 6749 16d2656a1984957e674b149555f1ea1d ""
"/usr/share/texmf-dist/tex/latex/footmisc/footmisc.sty" 1716849630 21399 e9fa1517a82f349507e998594ef20b82 ""
"/usr/share/texmf-dist/tex/latex/geometry/geometry.sty" 1716849630 41601 9cf6c5257b1bc7af01a58859749dd37a ""
"/usr/share/texmf-dist/tex/latex/graphics-cfg/color.cfg" 1716849630 1213 620bba36b25224fa9b7e1ccb4ecb76fd ""
"/usr/share/texmf-dist/tex/latex/graphics-cfg/graphics.cfg" 1716849630 1224 978390e9c2234eab29404bc21b268d1e ""
"/usr/share/texmf-dist/tex/latex/graphics-def/pdftex.def" 1716849630 19448 1e988b341dda20961a6b931bcde55519 ""
"/usr/share/texmf-dist/tex/latex/graphics/color.sty" 1716849630 7233 e46ce9241d2b2ca2a78155475fdd557a ""
"/usr/share/texmf-dist/tex/latex/graphics/graphics.sty" 1716849630 18387 8f900a490197ebaf93c02ae9476d4b09 ""
"/usr/share/texmf-dist/tex/latex/graphics/graphicx.sty" 1716849630 8010 a8d949cbdbc5c983593827c9eec252e1 ""
"/usr/share/texmf-dist/tex/latex/graphics/keyval.sty" 1716849630 2671 7e67d78d9b88c845599a85b2d41f2e39 ""
"/usr/share/texmf-dist/tex/latex/graphics/lscape.sty" 1716849630 1822 5e4f855a9ecb640f34881e4b457fa9aa ""
"/usr/share/texmf-dist/tex/latex/graphics/mathcolor.ltx" 1716849630 2885 9c645d672ae17285bba324998918efd8 ""
"/usr/share/texmf-dist/tex/latex/graphics/trig.sty" 1716849630 4023 293ea1c16429fc0c4cf605f4da1791a9 ""
"/usr/share/texmf-dist/tex/latex/hycolor/hycolor.sty" 1716849630 17914 4c28a13fc3d975e6e81c9bea1d697276 ""
"/usr/share/texmf-dist/tex/latex/hyperref/hpdftex.def" 1716849630 48154 e46bf8adeb936500541441171d61726d ""
"/usr/share/texmf-dist/tex/latex/hyperref/hyperref.sty" 1716849630 220920 fd3cbb5f1a2bc9b8f451b8b7d8171264 ""
"/usr/share/texmf-dist/tex/latex/hyperref/nameref.sty" 1716849630 11026 182c63f139a71afd30a28e5f1ed2cd1c ""
"/usr/share/texmf-dist/tex/latex/hyperref/pd1enc.def" 1716849630 14249 e67cb186717b7ab18d14a4875e7e98b5 ""
"/usr/share/texmf-dist/tex/latex/hyperref/psdextra.def" 1716849630 39335 60d0b1a7945c7036be4d77ccc6e266f3 ""
"/usr/share/texmf-dist/tex/latex/hyperref/puenc.def" 1716849630 117112 05831178ece2cad4d9629dcf65099b11 ""
"/usr/share/texmf-dist/tex/latex/ifoddpage/ifoddpage.sty" 1716849630 2142 eae42205b97b7a3ad0e58db5fe99e3e6 ""
"/usr/share/texmf-dist/tex/latex/kvoptions/kvoptions.sty" 1716849630 22555 6d8e155cfef6d82c3d5c742fea7c992e ""
"/usr/share/texmf-dist/tex/latex/kvsetkeys/kvsetkeys.sty" 1716849630 13815 760b0c02f691ea230f5359c4e1de23a7 ""
"/usr/share/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def" 1716849630 30006 3d512c0edd558928ddea1690180ef77e ""
"/usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty" 1716849630 6565 10e89ed128ccd59431746bbdd82129fc ""
"/usr/share/texmf-dist/tex/latex/l3packages/l3keys2e/l3keys2e.sty" 1716849630 4674 4240ebde863d19dc4d13bb280b78abb2 ""
"/usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty" 1716849630 9327 11bedad2ac38f92e405a38ed18489a03 ""
"/usr/share/texmf-dist/tex/latex/lastpage/lastpage.sty" 1716849630 2868 94ccb360e6e4443cad221f78a5cbb6a8 ""
"/usr/share/texmf-dist/tex/latex/lastpage/lastpage2e.sty" 1716849630 2720 a038de4136aa4a721c8ef0dc226ae6b5 ""
"/usr/share/texmf-dist/tex/latex/lastpage/lastpagemodern.sty" 1716849630 9166 74f0d39535736e850fd2f2796fd37674 ""
"/usr/share/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg" 1716849630 678 4792914a8f45be57bb98413425e4c7af ""
"/usr/share/texmf-dist/tex/latex/lipsum/lipsum.ltd.tex" 1716849630 95525 6fd0552101a6b1f9b7a84b402ec435ba ""
"/usr/share/texmf-dist/tex/latex/lipsum/lipsum.sty" 1716849630 14690 c2c754218a7108db7823a4839c1bc3cd ""
"/usr/share/texmf-dist/tex/latex/listings/listings.cfg" 1716849630 1830 20af84c556326f7c12b9202ebe363f56 ""
"/usr/share/texmf-dist/tex/latex/listings/listings.sty" 1716849630 81322 d02238bdeb305f2c9f9d0229f99371d0 ""
"/usr/share/texmf-dist/tex/latex/listings/lstmisc.sty" 1716849630 77022 5c8c440739265e7ba15b8379ece6ecd7 ""
"/usr/share/texmf-dist/tex/latex/listings/lstpatch.sty" 1716849630 329 f19f5da7234b51d16764e23d20999c73 ""
"/usr/share/texmf-dist/tex/latex/logreq/logreq.def" 1716849630 1620 fb1c32b818f2058eca187e5c41dfae77 ""
"/usr/share/texmf-dist/tex/latex/logreq/logreq.sty" 1716849630 6187 b27afc771af565d3a9ff1ca7d16d0d46 ""
"/usr/share/texmf-dist/tex/latex/mathtools/mathtools.sty" 1716849630 62672 9ff036bc89365461cc2bd482cc1e4879 ""
"/usr/share/texmf-dist/tex/latex/mathtools/mhsetup.sty" 1716849630 5582 a43dedf8e5ec418356f1e9dfe5d29fc3 ""
"/usr/share/texmf-dist/tex/latex/outlines/outlines.sty" 1716849630 4614 0e87f5d4bbe7b8f03b29c7941684b222 ""
"/usr/share/texmf-dist/tex/latex/pdflscape/pdflscape-nometadata.sty" 1716849630 6572 ea530fbbe537629fd97736d33babc07d ""
"/usr/share/texmf-dist/tex/latex/pdflscape/pdflscape.sty" 1716849630 2224 1230ab76aa62221ccbd90bca8c8c015e ""
"/usr/share/texmf-dist/tex/latex/pdfpages/pdfpages.sty" 1716849630 54914 ea9713532d0d0ae802ca2446650f9ded ""
"/usr/share/texmf-dist/tex/latex/pdfpages/pppdftex.def" 1716849630 6591 249ecc067cc3246c4ed39a577ded77e3 ""
"/usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty" 1716849630 1090 bae35ef70b3168089ef166db3e66f5b2 ""
"/usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty" 1716849630 373 00b204b1d7d095b892ad31a7494b0373 ""
"/usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty" 1716849630 21013 f4ff83d25bb56552493b030f27c075ae ""
"/usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty" 1716849630 989 c49c8ae06d96f8b15869da7428047b1e ""
"/usr/share/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty" 1716849630 339 c2e180022e3afdb99c7d0ea5ce469b7d ""
"/usr/share/texmf-dist/tex/latex/pgf/math/pgfmath.sty" 1716849630 306 c56a323ca5bf9242f54474ced10fca71 ""
"/usr/share/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty" 1716849630 443 8c872229db56122037e86bcda49e14f3 ""
"/usr/share/texmf-dist/tex/latex/pgf/utilities/pgffor.sty" 1716849630 348 ee405e64380c11319f0e249fed57e6c5 ""
"/usr/share/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty" 1716849630 274 5ae372b7df79135d240456a1c6f2cf9a ""
"/usr/share/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty" 1716849630 325 f9f16d12354225b7dd52a3321f085955 ""
"/usr/share/texmf-dist/tex/latex/refcount/refcount.sty" 1716849630 9878 9e94e8fa600d95f9c7731bb21dfb67a4 ""
"/usr/share/texmf-dist/tex/latex/rerunfilecheck/rerunfilecheck.sty" 1716849630 9714 ba3194bd52c8499b3f1e3eb91d409670 ""
"/usr/share/texmf-dist/tex/latex/silence/silence.sty" 1716849630 17540 5aad5dddd881dee43ad1a83adb336af9 ""
"/usr/share/texmf-dist/tex/latex/titlesec/titlesec.sty" 1716849630 48766 0b93839be28e9744a24c45075c75b2e2 ""
"/usr/share/texmf-dist/tex/latex/tocloft/tocloft.sty" 1716849630 36103 3e78d14f0f4b1a30560fea5e04de805d ""
"/usr/share/texmf-dist/tex/latex/todonotes/todonotes.sty" 1716849630 21404 916e19cbd009b6d289c8194b313d3895 ""
"/usr/share/texmf-dist/tex/latex/tools/calc.sty" 1716849630 10214 547fd4d29642cb7c80bf54b49d447f01 ""
"/usr/share/texmf-dist/tex/latex/url/url.sty" 1716849630 12796 8edb7d69a20b857904dd0ea757c14ec9 ""
"/usr/share/texmf-dist/tex/latex/varwidth/varwidth.sty" 1716849630 10894 d359a13923460b2a73d4312d613554c8 ""
"/usr/share/texmf-dist/tex/latex/xcolor/xcolor.sty" 1716849630 55487 80a65caedd3722f4c20a14a69e785d8f ""
"/usr/share/texmf-dist/tex/latex/xkeyval/xkeyval.sty" 1716849630 4937 4ce600ce9bd4ec84d0250eb6892fcf4f ""
"/usr/share/texmf-dist/web2c/texmf.cnf" 1716849630 41588 b43d3e860a4f94167ee1e725ff526a72 ""
"/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map" 1716921075.84382 5307366 7c8b2c5f25a30f46e808245b8e6cf269 ""
"/var/lib/texmf/web2c/pdftex/pdflatex.fmt" 1716921071 7112998 4ddc129a6d2bdf041fc05b3009008598 ""
"build/main.aux" 1719438278.66011 1774 837a1b39d05a8f7362965527e9cc902c "pdflatex"
"build/main.bbl" 1719432997.89041 0 d41d8cd98f00b204e9800998ecf8427e "biber build/main"
"build/main.out" 1719438278.66344 216 f02a5084175509c0e4a18d378d1398db "pdflatex"
"build/main.run.xml" 1719438278.67011 2224 bc6149f5caee4074188e1b81a66978d0 "pdflatex"
"img/DTU.pdf" 1719432997.96042 17695 ac89e3011a696efc58efb000e9d735c0 ""
"img/dropout.png" 1719437541.95942 597350 5db1ea9199b8e4019172fe7e8582ce8f ""
"main.tex" 1719438273.59686 6534 c6d0c4ffe6e83fc8936ddc67cc6c4fd0 ""
"preamble.tex" 1719432997.96042 3532 2629b8bd7c0c3e4ffd675fe12836beac ""
(generated)
"build/main.aux"
"build/main.bcf"
"build/main.log"
"build/main.out"
"build/main.pdf"
"build/main.run.xml"
(rewritten before read)

View File

@ -1,463 +0,0 @@
PWD /home/daniel/Dropbox/DTU/F24/46045/syslab
INPUT /usr/share/texmf-dist/web2c/texmf.cnf
INPUT /var/lib/texmf/web2c/pdftex/pdflatex.fmt
INPUT main.tex
OUTPUT build/main.log
INPUT ./preamble.tex
INPUT ./preamble.tex
INPUT preamble.tex
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/article.cls
INPUT /usr/share/texmf-dist/tex/latex/base/size10.clo
INPUT /usr/share/texmf-dist/tex/latex/base/size10.clo
INPUT /usr/share/texmf-dist/tex/latex/base/size10.clo
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/geometry/geometry.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/keyval.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/generic/iftex/iftex.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/fontenc.sty
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1000.tfm
INPUT /usr/share/texmf-dist/tex/latex/base/inputenc.sty
INPUT /usr/share/texmf-dist/tex/latex/base/inputenc.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/hyperref.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/hyperref.sty
INPUT /usr/share/texmf-dist/tex/latex/kvsetkeys/kvsetkeys.sty
INPUT /usr/share/texmf-dist/tex/latex/kvsetkeys/kvsetkeys.sty
INPUT /usr/share/texmf-dist/tex/generic/kvdefinekeys/kvdefinekeys.sty
INPUT /usr/share/texmf-dist/tex/generic/kvdefinekeys/kvdefinekeys.sty
INPUT /usr/share/texmf-dist/tex/generic/pdfescape/pdfescape.sty
INPUT /usr/share/texmf-dist/tex/generic/pdfescape/pdfescape.sty
INPUT /usr/share/texmf-dist/tex/generic/ltxcmds/ltxcmds.sty
INPUT /usr/share/texmf-dist/tex/generic/ltxcmds/ltxcmds.sty
INPUT /usr/share/texmf-dist/tex/generic/pdftexcmds/pdftexcmds.sty
INPUT /usr/share/texmf-dist/tex/generic/pdftexcmds/pdftexcmds.sty
INPUT /usr/share/texmf-dist/tex/generic/infwarerr/infwarerr.sty
INPUT /usr/share/texmf-dist/tex/generic/infwarerr/infwarerr.sty
INPUT /usr/share/texmf-dist/tex/latex/hycolor/hycolor.sty
INPUT /usr/share/texmf-dist/tex/latex/hycolor/hycolor.sty
INPUT /usr/share/texmf-dist/tex/latex/auxhook/auxhook.sty
INPUT /usr/share/texmf-dist/tex/latex/auxhook/auxhook.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/nameref.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/nameref.sty
INPUT /usr/share/texmf-dist/tex/latex/refcount/refcount.sty
INPUT /usr/share/texmf-dist/tex/latex/refcount/refcount.sty
INPUT /usr/share/texmf-dist/tex/generic/gettitlestring/gettitlestring.sty
INPUT /usr/share/texmf-dist/tex/generic/gettitlestring/gettitlestring.sty
INPUT /usr/share/texmf-dist/tex/latex/kvoptions/kvoptions.sty
INPUT /usr/share/texmf-dist/tex/latex/kvoptions/kvoptions.sty
INPUT /usr/share/texmf-dist/tex/latex/etoolbox/etoolbox.sty
INPUT /usr/share/texmf-dist/tex/latex/etoolbox/etoolbox.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/pd1enc.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/pd1enc.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/pd1enc.def
INPUT /usr/share/texmf-dist/tex/generic/intcalc/intcalc.sty
INPUT /usr/share/texmf-dist/tex/generic/intcalc/intcalc.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/puenc.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/puenc.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/puenc.def
INPUT /usr/share/texmf-dist/tex/generic/stringenc/stringenc.sty
INPUT /usr/share/texmf-dist/tex/generic/stringenc/stringenc.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/psdextra.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/psdextra.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/psdextra.def
INPUT /usr/share/texmf-dist/tex/latex/url/url.sty
INPUT /usr/share/texmf-dist/tex/latex/url/url.sty
INPUT /usr/share/texmf-dist/tex/generic/bitset/bitset.sty
INPUT /usr/share/texmf-dist/tex/generic/bitset/bitset.sty
INPUT /usr/share/texmf-dist/tex/generic/bigintcalc/bigintcalc.sty
INPUT /usr/share/texmf-dist/tex/generic/bigintcalc/bigintcalc.sty
INPUT /usr/share/texmf-dist/tex/generic/atbegshi/atbegshi.sty
INPUT /usr/share/texmf-dist/tex/latex/base/atbegshi-ltx.sty
INPUT /usr/share/texmf-dist/tex/latex/base/atbegshi-ltx.sty
INPUT /usr/share/texmf-dist/tex/latex/hyperref/hpdftex.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/hpdftex.def
INPUT /usr/share/texmf-dist/tex/latex/hyperref/hpdftex.def
INPUT /usr/share/texmf-dist/tex/latex/atveryend/atveryend.sty
INPUT /usr/share/texmf-dist/tex/latex/base/atveryend-ltx.sty
INPUT /usr/share/texmf-dist/tex/latex/base/atveryend-ltx.sty
INPUT /usr/share/texmf-dist/tex/latex/rerunfilecheck/rerunfilecheck.sty
INPUT /usr/share/texmf-dist/tex/latex/rerunfilecheck/rerunfilecheck.sty
INPUT /usr/share/texmf-dist/tex/generic/uniquecounter/uniquecounter.sty
INPUT /usr/share/texmf-dist/tex/generic/uniquecounter/uniquecounter.sty
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/adjustbox.sty
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/adjustbox.sty
INPUT /usr/share/texmf-dist/tex/latex/xkeyval/xkeyval.sty
INPUT /usr/share/texmf-dist/tex/latex/xkeyval/xkeyval.sty
INPUT /usr/share/texmf-dist/tex/generic/xkeyval/xkeyval.tex
INPUT /usr/share/texmf-dist/tex/generic/xkeyval/xkvutils.tex
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/adjcalc.sty
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/adjcalc.sty
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/trimclip.sty
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/trimclip.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/graphicx.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/graphicx.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/graphics.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/graphics.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/trig.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/trig.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
INPUT /usr/share/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
INPUT /usr/share/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
INPUT /usr/share/texmf-dist/tex/latex/graphics-def/pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/graphics-def/pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/graphics-def/pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/collectbox/collectbox.sty
INPUT /usr/share/texmf-dist/tex/latex/collectbox/collectbox.sty
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/tc-pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/tc-pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/adjustbox/tc-pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/ifoddpage/ifoddpage.sty
INPUT /usr/share/texmf-dist/tex/latex/ifoddpage/ifoddpage.sty
INPUT /usr/share/texmf-dist/tex/latex/ifoddpage/ifoddpage.sty
INPUT /usr/share/texmf-dist/tex/latex/varwidth/varwidth.sty
INPUT /usr/share/texmf-dist/tex/latex/varwidth/varwidth.sty
INPUT /usr/share/texmf-dist/tex/latex/varwidth/varwidth.sty
INPUT /usr/share/texmf-dist/tex/latex/footmisc/footmisc.sty
INPUT /usr/share/texmf-dist/tex/latex/footmisc/footmisc.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsmath.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsmath.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsopn.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amstext.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amstext.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsgen.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsgen.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsbsy.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsbsy.sty
INPUT /usr/share/texmf-dist/tex/latex/amsmath/amsopn.sty
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/amssymb.sty
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/amssymb.sty
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/amsfonts.sty
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/amsfonts.sty
INPUT /usr/share/texmf-dist/tex/latex/mathtools/mathtools.sty
INPUT /usr/share/texmf-dist/tex/latex/mathtools/mathtools.sty
INPUT /usr/share/texmf-dist/tex/latex/tools/calc.sty
INPUT /usr/share/texmf-dist/tex/latex/tools/calc.sty
INPUT /usr/share/texmf-dist/tex/latex/mathtools/mhsetup.sty
INPUT /usr/share/texmf-dist/tex/latex/mathtools/mhsetup.sty
INPUT /usr/share/texmf-dist/tex/latex/float/float.sty
INPUT /usr/share/texmf-dist/tex/latex/float/float.sty
INPUT /usr/share/texmf-dist/tex/latex/fancyhdr/fancyhdr.sty
INPUT /usr/share/texmf-dist/tex/latex/fancyhdr/fancyhdr.sty
INPUT /usr/share/texmf-dist/tex/latex/caption/subcaption.sty
INPUT /usr/share/texmf-dist/tex/latex/caption/subcaption.sty
INPUT /usr/share/texmf-dist/tex/latex/caption/caption.sty
INPUT /usr/share/texmf-dist/tex/latex/caption/caption.sty
INPUT /usr/share/texmf-dist/tex/latex/caption/caption3.sty
INPUT /usr/share/texmf-dist/tex/latex/caption/caption3.sty
INPUT /usr/share/texmf-dist/tex/latex/lastpage/lastpage.sty
INPUT /usr/share/texmf-dist/tex/latex/lastpage/lastpage.sty
INPUT /usr/share/texmf-dist/tex/latex/lastpage/lastpage2e.sty
INPUT /usr/share/texmf-dist/tex/latex/lastpage/lastpage2e.sty
INPUT /usr/share/texmf-dist/tex/latex/lastpage/lastpagemodern.sty
INPUT /usr/share/texmf-dist/tex/latex/lastpage/lastpagemodern.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/listings.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/listings.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/lstpatch.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/lstpatch.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/lstpatch.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/lstmisc.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/lstmisc.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/lstmisc.sty
INPUT /usr/share/texmf-dist/tex/latex/listings/listings.cfg
INPUT /usr/share/texmf-dist/tex/latex/listings/listings.cfg
INPUT /usr/share/texmf-dist/tex/latex/listings/listings.cfg
INPUT /usr/share/texmf-dist/tex/latex/todonotes/todonotes.sty
INPUT /usr/share/texmf-dist/tex/latex/todonotes/todonotes.sty
INPUT /usr/share/texmf-dist/tex/latex/base/ifthen.sty
INPUT /usr/share/texmf-dist/tex/latex/base/ifthen.sty
INPUT /usr/share/texmf-dist/tex/latex/xcolor/xcolor.sty
INPUT /usr/share/texmf-dist/tex/latex/xcolor/xcolor.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics-cfg/color.cfg
INPUT /usr/share/texmf-dist/tex/latex/graphics-cfg/color.cfg
INPUT /usr/share/texmf-dist/tex/latex/graphics-cfg/color.cfg
INPUT /usr/share/texmf-dist/tex/latex/graphics/mathcolor.ltx
INPUT /usr/share/texmf-dist/tex/latex/graphics/mathcolor.ltx
INPUT /usr/share/texmf-dist/tex/latex/graphics/mathcolor.ltx
INPUT /usr/share/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/pgf.revision.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/pgf.revision.tex
INPUT /usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeyslibraryfiltered.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.def
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfint.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorerdf.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex
INPUT /usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/utilities/pgffor.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/utilities/pgffor.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex
INPUT /usr/share/texmf-dist/tex/latex/pgf/math/pgfmath.sty
INPUT /usr/share/texmf-dist/tex/latex/pgf/math/pgfmath.sty
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarytopaths.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex
INPUT /usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarypositioning.code.tex
INPUT /usr/share/texmf-dist/tex/latex/silence/silence.sty
INPUT /usr/share/texmf-dist/tex/latex/silence/silence.sty
INPUT /usr/share/texmf-dist/tex/latex/appendix/appendix.sty
INPUT /usr/share/texmf-dist/tex/latex/appendix/appendix.sty
INPUT /usr/share/texmf-dist/tex/latex/tocloft/tocloft.sty
INPUT /usr/share/texmf-dist/tex/latex/tocloft/tocloft.sty
INPUT /usr/share/texmf-dist/tex/latex/titlesec/titlesec.sty
INPUT /usr/share/texmf-dist/tex/latex/titlesec/titlesec.sty
INPUT /usr/share/texmf-dist/tex/latex/lipsum/lipsum.sty
INPUT /usr/share/texmf-dist/tex/latex/lipsum/lipsum.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/l3keys2e/l3keys2e.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/l3keys2e/l3keys2e.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
INPUT /usr/share/texmf-dist/tex/latex/lipsum/lipsum.ltd.tex
INPUT /usr/share/texmf-dist/tex/latex/lipsum/lipsum.ltd.tex
INPUT /usr/share/texmf-dist/tex/latex/pdfpages/pdfpages.sty
INPUT /usr/share/texmf-dist/tex/latex/pdfpages/pdfpages.sty
INPUT /usr/share/texmf-dist/tex/latex/eso-pic/eso-pic.sty
INPUT /usr/share/texmf-dist/tex/latex/eso-pic/eso-pic.sty
INPUT /usr/share/texmf-dist/tex/latex/pdfpages/pppdftex.def
INPUT /usr/share/texmf-dist/tex/latex/pdfpages/pppdftex.def
INPUT /usr/share/texmf-dist/tex/latex/pdfpages/pppdftex.def
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.sty
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.sty
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.def
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.def
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.def
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.cfg
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.cfg
INPUT /usr/share/texmf-dist/tex/latex/csquotes/csquotes.cfg
INPUT /usr/share/texmf-dist/tex/latex/cleveref/cleveref.sty
INPUT /usr/share/texmf-dist/tex/latex/cleveref/cleveref.sty
INPUT /usr/share/texmf-dist/tex/latex/outlines/outlines.sty
INPUT /usr/share/texmf-dist/tex/latex/outlines/outlines.sty
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.sty
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.sty
INPUT /usr/share/texmf-dist/tex/latex/logreq/logreq.sty
INPUT /usr/share/texmf-dist/tex/latex/logreq/logreq.sty
INPUT /usr/share/texmf-dist/tex/latex/logreq/logreq.def
INPUT /usr/share/texmf-dist/tex/latex/logreq/logreq.def
INPUT /usr/share/texmf-dist/tex/latex/logreq/logreq.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-dm.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-dm.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-dm.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-compat.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-compat.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-compat.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.def
INPUT /usr/share/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/bbx/standard.bbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/bbx/standard.bbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/bbx/standard.bbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/cbx/numeric.cbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/cbx/numeric.cbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/cbx/numeric.cbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.cfg
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.cfg
INPUT /usr/share/texmf-dist/tex/latex/biblatex/biblatex.cfg
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-case-expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/biblatex/blx-case-expl3.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT /usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
INPUT build/main.aux
INPUT build/main.aux
INPUT build/main.aux
OUTPUT build/main.aux
INPUT /usr/share/texmf-dist/tex/latex/graphics/color.sty
INPUT build/main.out
INPUT build/main.out
INPUT build/main.out
INPUT build/main.out
OUTPUT build/main.pdf
INPUT build/main.out
INPUT build/main.out
OUTPUT build/main.out
INPUT /usr/share/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
INPUT /usr/share/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
INPUT /usr/share/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
INPUT /usr/share/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
INPUT /usr/share/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
INPUT /usr/share/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
INPUT /usr/share/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
INPUT /usr/share/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
INPUT /usr/share/texmf-dist/tex/latex/pdflscape/pdflscape.sty
INPUT /usr/share/texmf-dist/tex/latex/pdflscape/pdflscape.sty
INPUT /usr/share/texmf-dist/tex/latex/pdflscape/pdflscape-nometadata.sty
INPUT /usr/share/texmf-dist/tex/latex/pdflscape/pdflscape-nometadata.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/lscape.sty
INPUT /usr/share/texmf-dist/tex/latex/graphics/lscape.sty
INPUT /usr/share/texmf-dist/tex/latex/biblatex/lbx/english.lbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/lbx/english.lbx
INPUT /usr/share/texmf-dist/tex/latex/biblatex/lbx/english.lbx
OUTPUT build/main.bcf
INPUT build/main.bbl
INPUT build/main.bbl
INPUT build/main.bbl
INPUT build/main.bbl
INPUT build/main.bbl
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1728.tfm
INPUT ./img/DTU.pdf
INPUT ./img/DTU.pdf
INPUT ./img/DTU.pdf
INPUT ./img/DTU.pdf
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm2488.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/eccc2488.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/eccc1440.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1200.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmr12.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmr8.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmr6.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmmi12.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmmi8.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmmi6.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmsy10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmsy8.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/cm/cmex10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/umsa.fd
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/umsa.fd
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/umsa.fd
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam7.tfm
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/umsb.fd
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/umsb.fd
INPUT /usr/share/texmf-dist/tex/latex/amsfonts/umsb.fd
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm7.tfm
INPUT /usr/share/texmf-dist/tex/generic/stringenc/se-pdfdoc.def
INPUT /usr/share/texmf-dist/tex/generic/stringenc/se-pdfdoc.def
INPUT /usr/share/texmf-dist/tex/generic/stringenc/se-pdfdoc.def
INPUT /var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map
INPUT /usr/share/texmf-dist/fonts/enc/dvips/cm-super/cm-super-t1.enc
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecrm1440.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam7.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam5.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm10.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm7.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm5.tfm
INPUT ./img/dropout.png
INPUT ./img/dropout.png
INPUT ./img/dropout.png
INPUT ./img/dropout.png
INPUT ./img/dropout.png
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecbx1000.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecbi1000.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecbi0800.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecti1000.tfm
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecti0800.tfm
INPUT ./img/DTU.pdf
INPUT ./img/DTU.pdf
INPUT ./img/DTU.pdf
INPUT /usr/share/texmf-dist/tex/latex/base/t1cmtt.fd
INPUT /usr/share/texmf-dist/tex/latex/base/t1cmtt.fd
INPUT /usr/share/texmf-dist/tex/latex/base/t1cmtt.fd
INPUT /usr/share/texmf-dist/fonts/tfm/jknappen/ec/ectt1000.tfm
INPUT ./img/DTU.pdf
INPUT ./img/DTU.pdf
INPUT ./img/DTU.pdf
INPUT build/main.aux
INPUT build/main.out
INPUT build/main.out
INPUT build/main.run.xml
OUTPUT build/main.run.xml
INPUT /usr/share/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb
INPUT /usr/share/texmf-dist/fonts/type1/public/cm-super/sfbi0800.pfb
INPUT /usr/share/texmf-dist/fonts/type1/public/cm-super/sfcc1440.pfb
INPUT /usr/share/texmf-dist/fonts/type1/public/cm-super/sfcc2488.pfb
INPUT /usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm1000.pfb
INPUT /usr/share/texmf-dist/fonts/type1/public/cm-super/sfrm1200.pfb
INPUT /usr/share/texmf-dist/fonts/type1/public/cm-super/sfti0800.pfb
INPUT /usr/share/texmf-dist/fonts/type1/public/cm-super/sftt1000.pfb

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
\BOOKMARK [1][-]{section.1}{Test\040Case}{}% 1
\BOOKMARK [1][-]{section.2}{Test\040Specification}{}% 2
\BOOKMARK [1][-]{section.3}{Test\040Results}{}% 3
\BOOKMARK [1][-]{section.4}{Discussion\040and\040Outlook}{}% 4

Binary file not shown.

View File

@ -1,82 +0,0 @@
<?xml version="1.0" standalone="yes"?>
<!-- logreq request file -->
<!-- logreq version 1.0 / dtd version 1.0 -->
<!-- Do not edit this file! -->
<!DOCTYPE requests [
<!ELEMENT requests (internal | external)*>
<!ELEMENT internal (generic, (provides | requires)*)>
<!ELEMENT external (generic, cmdline?, input?, output?, (provides | requires)*)>
<!ELEMENT cmdline (binary, (option | infile | outfile)*)>
<!ELEMENT input (file)+>
<!ELEMENT output (file)+>
<!ELEMENT provides (file)+>
<!ELEMENT requires (file)+>
<!ELEMENT generic (#PCDATA)>
<!ELEMENT binary (#PCDATA)>
<!ELEMENT option (#PCDATA)>
<!ELEMENT infile (#PCDATA)>
<!ELEMENT outfile (#PCDATA)>
<!ELEMENT file (#PCDATA)>
<!ATTLIST requests
version CDATA #REQUIRED
>
<!ATTLIST internal
package CDATA #REQUIRED
priority (9) #REQUIRED
active (0 | 1) #REQUIRED
>
<!ATTLIST external
package CDATA #REQUIRED
priority (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8) #REQUIRED
active (0 | 1) #REQUIRED
>
<!ATTLIST provides
type (static | dynamic | editable) #REQUIRED
>
<!ATTLIST requires
type (static | dynamic | editable) #REQUIRED
>
<!ATTLIST file
type CDATA #IMPLIED
>
]>
<requests version="1.0">
<internal package="biblatex" priority="9" active="0">
<generic>latex</generic>
<provides type="dynamic">
<file>main.bcf</file>
</provides>
<requires type="dynamic">
<file>main.bbl</file>
</requires>
<requires type="static">
<file>blx-dm.def</file>
<file>blx-compat.def</file>
<file>biblatex.def</file>
<file>standard.bbx</file>
<file>numeric.bbx</file>
<file>numeric.cbx</file>
<file>biblatex.cfg</file>
<file>english.lbx</file>
</requires>
</internal>
<external package="biblatex" priority="5" active="0">
<generic>biber</generic>
<cmdline>
<binary>biber</binary>
<infile>main</infile>
</cmdline>
<input>
<file>main.bcf</file>
</input>
<output>
<file>main.bbl</file>
</output>
<provides type="dynamic">
<file>main.bbl</file>
</provides>
<requires type="dynamic">
<file>main.bcf</file>
</requires>
</external>
</requests>

Binary file not shown.

View File

@ -0,0 +1 @@
Makes folder visible for git.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
Makes folder visible for git.

View File

@ -0,0 +1,4 @@
{"unit": "dumpload_sp", "value": 0.0, "time": 1718013928.6948164}
{"unit": "dumpload_sp", "value": 20.0, "time": 1718013959.6977577}
{"unit": "dumpload_sp", "value": 10.0, "time": 1718013989.6989655}
{"unit": "dumpload_sp", "value": 0.0, "time": 1718014018.716735}

48
demo_datalogger.py Executable file
View File

@ -0,0 +1,48 @@
#!./venv/bin/python3
import syslab
from json import dump
from time import sleep, time
# Defines location and name of the measurements file
LOG_FILE = f'data/measurements/measurements_{time():.00f}.json'
print(f"Logging to file {LOG_FILE}")
# Set up a connection to the switchboards
sb3192 = syslab.SwitchBoard('319-2')
sb3193 = syslab.SwitchBoard('319-3')
sb33012 = syslab.SwitchBoard("330-12")
sb1172 = syslab.SwitchBoard('117-2')
# Convenience function to
def take_measurements():
measurements = {
"pcc_p": sb3192.getActivePower('Grid'),
"pcc_q": sb3192.getReactivePower('Grid'),
"pv319_p": sb3192.getActivePower('PV'),
"pv319_q": sb3192.getReactivePower('PV'),
"dumpload_p": sb3192.getActivePower('Dumpload'),
"dumpload_q": sb3192.getReactivePower('Dumpload'),
"gaia_p": sb33012.getActivePower('Gaia'),
"gaia_q": sb33012.getReactivePower('Gaia'),
"pv330_p": sb33012.getActivePower('PV_1'),
"pv330_q": sb33012.getReactivePower('PV_1'),
"b2b_p": sb3193.getActivePower('ABB_Sec'),
"b2b_q": sb3193.getReactivePower('ABB_Sec'),
"battery_p": sb1172.getActivePower('Battery'),
"battery_q": sb1172.getReactivePower('Battery'),
}
return [{'unit': k, 'value': meas.value, 'time': meas.timestampMicros/1e6} for k, meas in measurements.items()]
while True:
measurement = take_measurements()
# Open the output file in "append" mode which adds lines to the end
with open(LOG_FILE, 'a') as file:
for m in measurement:
# Convert the dictionary m to a json string and put it
# in the file.
dump(m, file)
# Write a newline for each measurement to make loading easier
file.write('\n')
sleep(1)

73
demo_plotter.py Executable file
View File

@ -0,0 +1,73 @@
#!./venv/bin/python3
import pandas as pd
import numpy as np
import json
import matplotlib.pyplot as plt
import os
from datetime import timedelta
## Read the measurements data file ##
DATA_MEAS_DIR = 'data/measurements'
# Always plot latest datafile - replace [-1] with another index if you want to plot a specific file.
MEAS_LOG_FILE = sorted(os.listdir(DATA_MEAS_DIR))[-1]
# Store each dictionary of the measurements json in a list
with open(os.path.join(DATA_MEAS_DIR, MEAS_LOG_FILE)) as f:
meas_data = [json.loads(line) for line in f]
# Use setpoint logger (only necessary for part two of the exercise "collecting fresh data")
use_setpoint_log = False
## Read the setpoints data file ##
if use_setpoint_log:
DATA_SP_DIR = 'data/setpoints'
# Always plot latest datafile
SP_LOG_FILE = sorted(os.listdir(DATA_SP_DIR))[-1]
# Store each dictionary of the setpoints json in a list
with open(os.path.join(DATA_SP_DIR, SP_LOG_FILE)) as f:
sp_data = [json.loads(line) for line in f]
# Merge measurements and setpoints in one list
data = meas_data + sp_data
else:
data = meas_data
################################################################################
################## Part 2 ######################################################
################################################################################
def overshoot(df, T1, T2):
yT1, yT2 = df[T1], df[T2]
over = 1 / (yT1 - yT2) * np.max(yT2 - df)
return over
SETPOINT_UNIX = 1718013959.6977577
SETPOINT_TS = pd.to_datetime(SETPOINT_UNIX, unit='s')
WINDOW = pd.to_datetime(SETPOINT_UNIX+25, unit='s')
## The controller is reasonably fast at reacting to changes; the sum of in and
## out is at zero roughly 5-10 seconds after a change.
# Construct a dataframe and pivot it to obtain a dataframe with a column per unit, and a row per timestamp.
df = pd.DataFrame.from_records(data)
df['time'] = pd.to_datetime(df['time'], unit='s')
df_pivot = df.pivot_table(values='value', columns='unit', index='time')
df_resampled = df_pivot.resample('0.1s').mean()
df_resampled.interpolate(method='linear', inplace=True)
df_resampled = pd.DataFrame(df_resampled)
# Plot the data. Note, that the data will mostly not be plotted with lines.
plt.ion() # Turn interactive mode on
plt.figure()
ax1, ax2 = plt.subplot(211), plt.subplot(212)
df_resampled[[c for c in df_resampled.columns if '_p' in c]].plot(marker='.', ax=ax1, linewidth=3)
ax2.plot(df_resampled['pcc_p'][SETPOINT_TS:WINDOW], marker='.', linewidth=3, label='pcc_p')
ax2.plot(df_resampled['dumpload_p'][SETPOINT_TS:WINDOW], marker='.', linewidth=3, label='dumpload')
plt.legend()
# print(overshoot(df_resampled['pcc_p'][SETPOINT_TS:WINDOW], SETPOINT_TS, WINDOW))
plt.show(block=True)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 KiB

129
main.tex
View File

@ -1,129 +0,0 @@
%! TEX root = main.tex
\input{preamble.tex}
\begin{document}
\maketitle
\thispagestyle{empty}
\newpage
The following sections will concern the fourth test, \enquote{Supervisor
Controller Dropout}.
\section{Test Case}
\label{sec:test_case}
The purpose of this was to observe the system response to one of the failure
modes to which we set out to become (more) resilient. As written in the group
report, all of the controller units, as well as the supervisor, was activated
during the test. Wind production was also active. As such, all units were under
observation, however specifically the system daemon was the object under
investigation, as this was our way of implementing a system response to failure
in the form of loss of a controller. The system daemon should be passive during
normal modes of operation, only receiving supervisor heartbeats, and should only
kick into action when failure occurs.
Our use case, as specified in the group report, concerns the hybrid power plant
as a means of frequency regulation with compensation for the fluctuations
normally associated with renewable energy production. Therefore, the system
should not be disruptive in case of a single controller breaking down; if that
were the case, a single operator mistake could be rather costly to the rest of
the grid participants.
Because of these concerns, the main metrics with which the outcome is measured
is the $ RMSE $ from the expected frequency response during a supervisor
controller dropout, as well as a more qualitative assessment of the system
reaction time to a fault.
\section{Test Specification}
\label{sec:test_spec}
This test begins with the system in a normal operating state. The individual
unit controllers, as well as the supervisor controller, are started. The
supervisor controller should then be given some amount of time (in this case
15-20 seconds) to calculate a PCC baseline based on the baseline load combined
with median PV production. Once this has been established, regular frequency
data should be published to the controller.
After the supervisor has had a chance to behave somewhat normally - in our run,
10 seconds after baseline establishing, the controller process is interrupted.
The unit controllers are not directly investigated during this step, however
their reaction to the lack of frequency data and split points should be apparent
in the general system response. To stress the system further, the battery
controller is also dropped after the supervisor has been ressurrected.
The output of the test, from which the $ RMSE $ is calculated, is the deviance
in the FCR activation from the FCR setpoint, in the span of the test.
\section{Test Results}
\label{sec:test_results}
\begin{figure}[H]
\centering
\includegraphics[width=0.8\textwidth]{img/dropout.png}
\caption{Results from the dropout test}
\label{fig:results}
\end{figure}
The above \cref{fig:results} shows the results from the test. The two
dotted, horizontal, black lines specify the times at which controllers were
dropped; first the supervisor, then the battery controller. The dotted red line
is when the system daemon notices the absence of the supervisor, and revives it
by spawning a new process.
From the $ RMSE $ value, it seems that the system responds rather well to the
supervisor being killed. Further inspection shows that the system shows signs of
instability in the period immediately following the controllers being dropped,
as should be expected, since it takes some time for the other components to mark
the supervisor as deceased. However, it seems that the system stabilizes after a
while with only minor fluctuations which could be from the wind turbine, since
that is out of our control.
More results can be found in our GitLab repository. In the subfolder
\texttt{testing/final\_tests\_and\_results/dropout/console\_logs}
the complete logs from the three unit controllers, the supervisor, and the
system daemon. It should be noted that when the supervisor is killed, the
console output ends; this is since the system daemon, after a set timeout,
revives the supervisor by spawning a new subprocess. This subprocess will then
be a child of the system daemon, which itself is a child of a specific terminal.
Therefore, the console output of the newly spawned supervisor is printed to the
terminal containing the system daemon. This is the same for the battery
controller. During this test, neither the PV- or load controllers were touched,
so these have continuing output all throughout the test.
\section{Discussion and Outlook}
\label{sec:discussion}
To conclude our test of controller dropout, the system seems to handle a
controller dropout reasonably well. This failure mode is a last-ditch effort in
case of absolute emergency; only in extreme circumstances would it be expected
for a controller to drop out spontaneously. It is, however, rather comforting to
know that should it happen, the system seems to stabilize after a short while.
There is, however, room for improvement. The controllers seem to react violently
in the first phases of a supervisor dropout. This may not be able to be
mitigated, however it could be an area of interest.
As was also apparent from our other tests, the controllers seem to behave in a
competing way, sometimes over-shadowing the other unit controllers. By phasing
in controller response following e.g. a sigmoid curve, the system response could
perhaps be modified to be a more gentle, gradual reaction.
It should also be noted that this way of spawning new processes only works by
spawning said child process on the machine running the system daemon. This
detail could of course be changed by changing the mechanism of spawning a
controller, however it is a definite limitation of the current design. Due to
time constraints, it was not further developed, as the principle of reviving
controllers was more important than that controller running on a specific
machine.
A next test, which could yield interesting results and conclusions, could be to
observe how the system were to react in case of temporary controller dropouts,
where the dropped controller came back to life. In this case, the system daemon
would probably respond by creating another controller, which would then compete
with the old one, however the exact result cannot be recognized without running
the test. It could also be interesting to see how the system would act in case
of these dropouts happening more frequently, or during periods of frequency
volatility.
\end{document}

28
plotter.py Executable file
View File

@ -0,0 +1,28 @@
#!./venv/bin/python3
import pandas as pd
import json
import matplotlib.pyplot as plt
import os
DATA_MEAS_DIR = 'data/measurements'
SPECIFIC_FILE = ''
MEAS_LOG_FILE = sorted(os.listdir(DATA_MEAS_DIR))[-1] if not SPECIFIC_FILE else SPECIFIC_FILE
with open(os.path.join(DATA_MEAS_DIR, MEAS_LOG_FILE)) as f:
meas_data = [json.loads(line) for line in f]
data = meas_data
df = pd.DataFrame.from_records(data)
df['time'] = pd.to_datetime(df['time'], unit='s')
df_pivot = df.pivot_table(values='value', columns='unit', index='time')
df_resampled = df_pivot.resample('s').mean()
df_resampled.interpolate(method='linear', inplace=True)
df_resampled = pd.DataFrame(df_resampled)
# Plot the data. Note, that the data will mostly not be plotted with lines.
plt.ion() # Turn interactive mode on
plt.figure()
ax1, ax2 = plt.subplot(211), plt.subplot(212)
df_resampled[[c for c in df_resampled.columns if '_p' in c]].plot(marker='.', ax=ax1, linewidth=3)
df_resampled[[c for c in df_resampled.columns if '_q' in c]].plot(marker='.', ax=ax2, linewidth=3)
plt.show(block=True)

View File

@ -1,142 +0,0 @@
%! TEX root = main.tex
\documentclass[a4paper, english]{article}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% Variables & commands prone to change
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\newcommand{\titl}{Individual Final Report}
\newcommand{\auth}{Daniel Brasholt s214676}
\newcommand{\courseno}{46045}
\newcommand{\course}{Design and Implementation\\ of Coordinated Distributed Energy Systems}
\newcommand{\dato}{June 2024}
\newcommand{\secheader}{Section}
\newcommand{\subsecheader}{Part}
\title{\includegraphics[width=.15\textwidth]{DTU}\\
\vspace{.5em}\Huge\scshape\titl\\
\vspace{-4mm}\rule{4cm}{0.5mm}\\
\Large{\courseno\ \course}}
\author{\auth}
\date{\dato}
% \newcommand{\sourcefile}{}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% Packages & Geometry
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\usepackage[
a4paper,
top=2cm,
bottom=2cm,
left=2cm,
right=2cm,
marginparwidth=2cm,
headheight=10mm,
footskip=10mm
]{geometry}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[pdfencoding=auto, psdextra]{hyperref}
\usepackage[export]{adjustbox}
\usepackage[bottom]{footmisc}
% \usepackage[dansk]{babel}
\usepackage{
amsmath,
amssymb,
mathtools,
graphicx,
float,
fancyhdr,
subcaption,
lastpage,
listings,
todonotes,
silence,
appendix,
tocloft,
titlesec,
lipsum,
pdfpages,
csquotes,
cleveref,
outlines
}
\usepackage[
format=plain,
labelfont={bf,it,footnotesize},
textfont={it,footnotesize}
]{caption}
\usepackage[
backend=biber,
style=numeric,
sorting=nty
]{biblatex}
% \addbibresource{\sourcefile}
\hypersetup{
colorlinks = true,
urlcolor = blue,
linkcolor = blue,
citecolor = red
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% Equations
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\renewcommand{\theequation}{\arabic{section}.\arabic{equation}}
\numberwithin{equation}{section}
\DeclarePairedDelimiter{\paren}{(}{)}
\DeclarePairedDelimiter\ceil{\lceil}{\rceil}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% Figures and sections
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\renewcommand{\thesection}{\arabic{section}}
\renewcommand{\thesubsection}{\arabic{section}.\arabic{subsection}}
\renewcommand{\thesubsubsection}{\arabic{section}.\arabic{subsection}.\alph{subsubsection}}
\Crefname{section}{\secheader}{\secheader}
\graphicspath{{./img}}
% Section
\titleformat{\section}[block]
{\normalfont\Large\scshape\filright}{\fbox{\secheader\ \thesection}}{1em}{}
% Subsection
\titleformat{\subsection}
{\titlerule
\vspace{.8ex}%
\normalfont}
{\subsecheader\ \thesubsection:}{1em}{}
% Subsubsection
\titleformat{\subsubsection}
{}{\thesubsubsection)}{1em}{}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% Page & footer styling
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\pagestyle{fancy}
\lhead{\titl \\ \auth}
\chead{\includegraphics[width=.05\textwidth]{DTU}}
\rhead{\courseno \ \course}
\cfoot{Page \thepage\, of \pageref*{LastPage}}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
\setlength{\headheight}{36.75034pt}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% ToC styling
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\pagenumbering{arabic}
\setcounter{tocdepth}{2}
\tocloftpagestyle{fancy}
\setlength{\cftsubsecnumwidth}{42pt}
\addtolength{\cftsecnumwidth}{47pt}
\addtolength{\cftsubsecnumwidth}{28pt}
\addtocontents{toc}{~\hfill\textbf{Page}\par}

12
requirements.txt Normal file
View File

@ -0,0 +1,12 @@
beautifulsoup4==4.12.3
certifi==2024.6.2
charset-normalizer==3.3.2
greenlet==3.0.3
idna==3.7
msgpack==1.0.8
pynvim==0.5.0
requests==2.32.3
soupsieve==2.5
# Editable Git install with no remote (syslab==0.3.0)
-e /home/daniel/Dropbox/DTU/F24/46045/syslab/syslab-python
urllib3==2.2.1

109
syslab-python/.gitignore vendored Normal file
View File

@ -0,0 +1,109 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
\.DS_Store
# Pycharm
.idea/

27
syslab-python/README.md Normal file
View File

@ -0,0 +1,27 @@
# SYSLAB Python Interface
This project provides a Python interface to several SYSLAB components.
## Required software
- Python (>=3.7)
- python-requests (>=2.18)
- python-bs4 (>= 4.7)
## Installation
To install the package in your local path, run
```shell
python setup.py install
```
Alternatively, copy the `syslab` folder into your project directory.
# Contributors
- Anders Thavlov: Initial implementation
- Oliver Gehrke
- Daniel Esteban Morales Bondy
- Tue Vissing Jensen
- Federico Zarelli

11
syslab-python/example.py Normal file
View File

@ -0,0 +1,11 @@
from syslab import SwitchBoard
name = '319-2'
SB_connection = SwitchBoard(name)
print("Let's look at what is going on in the switchboard {}.".format(name))
for bay in range(SB_connection.getNumBays()):
print(SB_connection.getBayName(bay),' : ',SB_connection.getActivePower(bay))

99
syslab-python/notes.md Normal file
View File

@ -0,0 +1,99 @@
# Notes on SOAP implementation
- Units should reflect SOAP methods onto their own namespace (1)
- CompositeMeasurement should convert to/from SOAP
- Type checking via client.get\_type('ns0:compositeMeasurement').elements
-
# Notes about this module - to be discussed
SYSLAB\_Unit.py has a whole bunch of static methods - should these be split into a util library instead?
Generally, many places where static methods are used for things that should perhaps just be functions...
The following files are empty:
- BattOpMode.py
- FlowBatteryState.py
- GaiaWindTurbine.py
To check the methods available, use, e.g.:
http://syslab-33.syslab.dk:8080/typebased_WebService_HeatSubstation/HeatSwitchboardWebService/716-h1/resourceNames
To figure out this URL, look at software.xml for the corresponding machine, and use this template:
http://(machineName).syslab.dk:(port)/(interfaceName)/(shortServerName)/(unitname)/resourceNames
| field | corresponds to | notes |
| ----- | -------------- | ----- |
| machineName | N/A | Look this up on the wiki |
| port | N/A | Dynamically allocated, starting at 8080 - good luck! |
| interfaceName | typeBasedWebService, interfaceName | |
| shortServerName | typeBasedWebService, serverClass | Remove the "Server" at the end |
| unitname | dataLogger, unit | Also defined as "name" in hardware.xml |
-------------------------
SYSLAB COMMON
Broadcast event logger:
Transcode to python:
https://git.elektro.dtu.dk/syslab/syslab-common/-/blob/master/src/main/java/risoe/syslab/comm/broadcast/BroadcastLogSender.java
broadcast log sender
:: transcode the "send()" method to python. It byte-encodes the message for UDP.
Java:
send(String origin, byte[] origIP, long timestamp, int ploadType, String message,
int level, int flags, String[] tags)
------------
Python:
----------.
def send(origin, origIP, timestamp, ploadType, message, level, flags, tags):
ploadbytes = message[:min(1024, len(message))].encode()
origbytes = origin[:min(32, len(origin))].encode()
tagbytes = tagsToBytes(tags, 256)
pktlen = 2 + 2 + 1 + len(origbytes) + 4 + 2 + 2 + 8 + 1 + 2 + len(ploadbytes) + len(tagbytes)
buf = bytearray(pktlen)
buf[0] = BroadcastLogConstants.BROADCASTLOG_PKTID >> 8
buf[1] = BroadcastLogConstants.BROADCASTLOG_PKTID & 0xff
buf[2] = (pktlen >> 8) & 0xff
buf[3] = pktlen & 0xff
buf[4] = len(origbytes)
buf[5:5+len(origbytes)] = origbytes
writePtr = 5 + len(origbytes)
buf[writePtr:writePtr+4] = origIP
writePtr += 4
buf[writePtr] = (level >> 8) & 0xff
buf[writePtr+1] = level & 0xff
buf[writePtr+2] = (flags >> 8) & 0xff
buf[writePtr+3] = flags & 0xff
for i in range(8):
buf[writePtr+7-i] = timestamp & 0xff
timestamp >>= 8
writePtr += 8
buf[writePtr] = ploadType & 0xff
buf[writePtr+1] = (len(ploadbytes) >> 8) & 0xff
buf[writePtr+2] = len(ploadbytes) & 0xff
buf[writePtr+3:writePtr+3+len(ploadbytes)] = ploadbytes
writePtr += len(ploadbytes)
buf[writePtr:writePtr+len(tagbytes)] = tagbytes
pack = n
pack = DatagramPacket(buf, len(buf), InetAddress.getByName("localhost"), 4445)
sock.send(pack)
------------
broadcast log receiver
+ needs a logger
listener ist interface for receiver
gui wall (SYSLAB Userspacce)
broadcast log displet
https://git.elektro.dtu.dk/syslab/syslab-userspace/-/blob/master/src/main/java/risoe/syslab/gui/wall/displets/BroadcastLogDisplet.java
... maybe extend with simple log file writer.

View File

@ -0,0 +1,2 @@
requests >= 2.18
beautifulsoup4 >= 3.7

10
syslab-python/setup.cfg Normal file
View File

@ -0,0 +1,10 @@
[flake8]
ignore =
max-line-length = 79
max-complexity = 11
[pytest]
addopts = --doctest-glob="*.rst"
[wheel]
universal = True

36
syslab-python/setup.py Normal file
View File

@ -0,0 +1,36 @@
from setuptools import setup, find_packages
setup(
name='syslab',
version='0.3.0',
author='Tue Vissing Jensen',
author_email='tvjens at elektro.dtu.dk',
description=('SYSLAB webservice client library.'),
long_description=(''),
url='https://www.syslab.dk',
install_requires=[
'requests>=2.18',
'beautifulsoup4>=3.7',
],
packages=find_packages(exclude=['tests*']),
include_package_data=True,
entry_points={
'console_scripts': [
],
},
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Intended Audience :: Science/Research',
'License :: Other/Proprietary License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Scientific/Engineering',
'Topic :: Software Development :: Libraries :: Python Modules',
],
)

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
"""
SYSLAB library
~~~~~~~~~~~~~~~~~~~~~
SYSLAB is a Python 3 library for control and monitoring of units in SYSLAB. Usage of this library requires the running
computer to be located in the SYSLAB network, either physically or virtually, ie. using a VPN connection.
"""
__title__ = 'syslab'
__version__ = '0.3.0'
__author__ = 'Anders Thavlov, Daniel Esteban Morales Bondy, Tue Vissing Jensen'
__maintainer__ = 'Tue Vissing Jensen'
__license__ = ''
__copyright__ = 'DTU'
# Raise error if we are not on Python 3
import sys
if not (sys.version_info.major == 3 and sys.version_info.minor >= 5):
raise RuntimeError("Must be using at least Python 3.5")
# DataTypes
from .core.datatypes import CompositeMeasurement
from .core.datatypes import CompositeBoolean
# Physical units
from .physical import \
SwitchBoard, \
Dumpload, \
Photovoltaics, \
Battery, \
HeatSwitchBoard, \
WindTurbine, \
DieselGenerator, \
B2BConverter, \
MeteoMast, \
EVSE
# Logging utilities
#from .comm.LogUtils import e

View File

@ -0,0 +1,59 @@
###############################################################
# SYSLAB remote logging utilities v0.9
# collect and record log events
#
# Author: Kai Heussen
# Date: 2023/06/18
###############################################################
import logging
import logging.handlers
import time
import syslab.config as config
event_log_formats = '%(asctime)s - %(name)s - %(levelname)s - %(filename)s - %(funcName)s - %(message)s'
simple_log_formats = '%(asctime)s - %(name)s - %(message)s'
def setup_event_logger(loggername=config.EVLOG_NAME, host='localhost', port=config.REMOTE_PORT_EV, level=logging.INFO, formats=simple_log_formats): # UDP
handler1 = logging.handlers.DatagramHandler(config.REMOTE_IP_EV, config.REMOTE_PORT_EV)
handler2 = logging.handlers.DatagramHandler(host, port)
handler3 = logging.StreamHandler()
formatter = logging.Formatter(formats)
handler3.setFormatter(formatter)
loggr = logging.getLogger(loggername)
loggr.setLevel(level)
loggr.addHandler(handler1)
loggr.addHandler(handler2)
loggr.addHandler(handler3)
return loggr
def setup_local_logger(loggername=config.EVLOG_NAME, host='localhost', port=config.REMOTE_PORT_EV, level=logging.INFO, formats=simple_log_formats): # UDP
handler = logging.handlers.DatagramHandler(host, port)
formatter = logging.Formatter(formats)
handler.setFormatter(formatter)
loggr = logging.getLogger(loggername)
loggr.setLevel(level)
loggr.addHandler(handler)
return loggr
def setup_udp_logger(loggername=config.SPLOG_NAME, host=config.REMOTE_IP_SP, port=config.REMOTE_PORT_SP, level=logging.INFO, formats=config.LOG_FORMATS): # UDP
handler = logging.handlers.DatagramHandler(host, port)
formatter = logging.Formatter(formats)
handler.setFormatter(formatter)
loggr = logging.getLogger(loggername)
loggr.setLevel(level)
loggr.addHandler(handler)
return loggr
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)-8s %(message)s')
# logging.getLogger().addHandler(logging.handlers.DatagramHandler('10.42.242.3', 51010))
logger = setup_local_logger()
while True:
logger.debug("This shouldn't show up")
logger.info("This should show up")
time.sleep(3)

View File

@ -0,0 +1,2 @@
#from BroadcastLogger import BroadcastLogSender
#from LogUtils import setup_udp_logger

View File

@ -0,0 +1,80 @@
###############################################################
# SYSLAB remote logger v0.9
# utility to display and record log events
# usage: move bash to target log directory, then call
# python <path to syslab-python>/syslab/comm/logrec.py
#
# Author: Kai Heussen
# Date: 2023/06/18
###############################################################
import pickle
import logging
import socket
from syslab import config
# import sys
from time import time
# from json import dump
BASIC = True
FILE = True
FILE_RAW = False # TODO: write a JSON formatter - based on https://stackoverflow.com/questions/50144628/python-logging-into-file-as-a-dictionary-or-json
logtype = "EV"
time=time()
logfile =f"syslab_{logtype}_log_{time:.00f}.txt"
logfile_raw =f"syslab_{logtype}_log_{time:.00f}.raw" # not yet json
DEFAULT_PORT = config.REMOTE_PORT_SP if logtype == "SP" else config.REMOTE_PORT_EV
port = DEFAULT_PORT
#simple_formats = '%(asctime)s - %(name)s - %(message)s'
simple_formats = '%(asctime)s - %(module)s - %(message)s'
#formats='foo: %(levelname)s - %(module)s.%(funcName)s - %(message)s'
event_log_formatter = '%(asctime)s - %(name)s - %(levelname)s - %(filename)s - %(module)s - %(funcName)s - %(message)s'
formats = simple_formats
formatter = logging.Formatter(formats)
if BASIC:
logging.basicConfig(format=formats)
else:
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)
if FILE:
handler = logging.FileHandler(logfile)
handler.setLevel(logging.INFO)
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)
logger = logging.getLogger()
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('', port))
print(f'Listening for log records on port {port}...')
if FILE:
print(f'recording log entries in file: {logfile}')
if FILE_RAW:
print(f'recording raw log entries in file: {logfile_raw}')
try:
while True:
d, _ = s.recvfrom(1024)
msg = pickle.loads(d[4:])
logrec = logging.makeLogRecord(msg)
logger.handle(logrec)
if FILE_RAW:
with open(logfile_raw, 'a') as file:
# dump(logrec, file) # requires a JSON formatter
file.write(f'{logrec.__str__() }\n') # Write a newline for each measurement to make loading easier
#print(log)
finally:
s.close()
if FILE_RAW:
file.close()

View File

@ -0,0 +1,19 @@
DEBUG = False
SPLOG_NAME = 'SetPointLogger'
EVLOG_NAME = 'EventLog'
REMOTE_LOGGER = True
REMOTE_IP_SP = '10.42.242.3' # UI Machine 03
REMOTE_IP_EV = '10.42.242.3' # UI Machine 03
REMOTE_PORT_SP = 51010
REMOTE_PORT_EV = 51020
LOCAL_IP = 'localhost'
LOCAL_PORT_SP = 51010
LOCAL_PORT_EV = 51020
LOCAL_LOGGER = False
LOG_FORMATS = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # '%(asctime)s %(levelname)-8s %(message)s'
TEST_LOCAL = False
if TEST_LOCAL:
REMOTE_PORT_SP = LOCAL_PORT_SP
REMOTE_PORT_EV = LOCAL_PORT_EV
REMOTE_IP_SP = LOCAL_IP
REMOTE_IP_EV = LOCAL_IP

View File

@ -0,0 +1,306 @@
from .datatypes import CompositeBoolean, BattOpMode, CompositeMeasurement
from .datatypes import FlowBatteryState, ConvState, ConvOpMode
from .datatypes import HeatCirculationPumpMode, HeatCirculationPumpState
import syslab.config as config
class SyslabUnit:
__HIDDEN_RESOURCES = {
'authenticate': ('boolean', 'String', 'String'),
'isAuthenticated': ('boolean',),
'checkConnection': ('boolean',),
'logout': ('void',)}
def __init__(self, baseurl, units=None, which=None, host=None, port=None, unit_name=None, unit_type=""):
"""
Initialize a proxy to the given unit.
:param baseurl: Example: 'http://{host}:{port}/typebased_WebService_Battery/VRBBatteryWebService/{unit_name}/'
:param units: dictionary of units to be loaded in the format {'which': ('host', 'port', 'unit_name')}
:param which: string indicating which unit we pick from _units_
:param host: (optional) override host
:param port: (optional) override port
:param unit_name: (optional) override unit_name
:param unit_type: (optional) Used to indicate the type of the unit.
"""
if units is None:
units = {}
if which is not None:
if which not in units:
raise TypeError(
'The {unit_type} should be one of: "{list_of_units}"'.format(
unit_type=unit_type,
list_of_units='", "'.join(units.keys())
))
else:
host, port, unit_name = units.get(which)
if host is not None:
host = host
if port is not None:
port = port
assert host is not None, "When assigning custom port, host must not be None."
assert port is not None, "When assigning custom host, port must not be None."
assert unit_name is not None, "When assigning custom host and port, unit_name must not be None."
url = baseurl.format(host=host, port=port, unit_name=unit_name)
print(url)
self._url = url
self._resources = parse_resources(url)
self._resources = {**self._resources, **SyslabUnit.__HIDDEN_RESOURCES}
# TODO: Do type checking on these types. Ignore for now
self._complex_arg_types = ['BattOpMode', 'CompositeMeasurement', 'HeatCirculationPumpMode']
if config.REMOTE_LOGGER:
#import logger
if config.DEBUG:
print(f"Setting up remote logger with default IP: {config.REMOTE_IP} and port: {config.REMOTE_PORT}")
from uuid import uuid1, getnode
from ..comm.LogUtils import setup_udp_logger
self.__logger = setup_udp_logger(config.SPLOG_NAME)
self.__session_id = uuid1().__str__()
self.__host_id = getnode().__str__()
# TODO: Set up local setpoints Logger
if config.LOCAL_LOGGER:
pass
def __request_int(self, resource, method, args, body) -> int:
return int(send_request(self._url, resource, method, args, body))
def __request_float(self, resource, method, args, body) -> float:
return float(send_request(self._url, resource, method, args, body))
def __request_boolean(self, resource, method, args, body) -> bool:
return bool(send_request(self._url, resource, method, args, body))
def __request_string(self, resource, method, args, body) -> str:
return send_request(self._url, resource, method, args, body)
def __request_void(self, resource, method, args, body) -> None:
send_request(self._url, resource, method, args, body)
def __request_composite_boolean(self, resource, method, args, body) -> CompositeBoolean:
if self.__get_resource_return_type(resource) == 'CompositeBoolean':
json_string = self.__request_string(resource, method, args, body)
result = CompositeBoolean.parseFromJSON(json_string)
else:
raise TypeError('Error: resource "{0}" does not return a CompositeBoolean.'.format(resource))
return result
def __request_composite_measurement(self, resource, method, args, body) -> CompositeMeasurement:
if self.__get_resource_return_type(resource) == 'CompositeMeasurement':
json_string = self.__request_string(resource, method, args, body)
result = CompositeMeasurement.parseFromJSON(json_string)
else:
raise TypeError('Error: resource "{0}" does not return a CompositeMeasurement.'.format(resource))
return result
def _request_resource(self, resource, args=(), method='get', body=None, check_types=True):
if not type(resource) is str:
raise TypeError("Resource should be a string, found {0}.".format(type(resource)))
if resource not in self._resources:
# print(resource)
# FIXME: Workaround for metmast, where string-based methods are suffixed by a '2' and int-based methods by a '1'
if not resource[-1] in ('1', '2'):
raise ValueError('Unknown resource: {0} - no such resource found for {1}'.format(resource, self._url))
if type(args) is int or type(args) is float or type(args) is str:
args = (args,)
if config.DEBUG:
print("Resource: ", resource, " and args: ", args, "with method: ", method)
if config.REMOTE_LOGGER and method == 'put':
logstr = f'SessionID:{self.__session_id}||SenderID:{self.__host_id}||Unit-URL:{self._url}||Setterfcn:{resource}||ArgValue:{args}'
self.__logger.info(logstr)
return_type = self.__get_resource_return_type(resource)
self.__check_argument(resource, args)
result = None
if check_types:
try:
if return_type == 'CompositeMeasurement':
result = self.__request_composite_measurement(resource, method, args, body)
elif return_type == 'CompositeBoolean':
result = self.__request_composite_boolean(resource, method, args, body)
elif return_type == 'String':
result = self.__request_string(resource, method, args, body)
elif return_type == 'int':
result = self.__request_int(resource, method, args, body)
elif return_type == 'boolean':
result = self.__request_boolean(resource, method, args, body)
elif return_type == 'float' or return_type == 'double':
result = self.__request_float(resource, method, args, body)
elif return_type == 'void':
self.__request_string(resource, method, args, body)
elif return_type == 'BattOpMode':
result = BattOpMode.parseFromJSON(self.__request_string(resource, method, args, body))
elif return_type == 'FlowBatteryState':
result = FlowBatteryState.parseFromJSON(self.__request_string(resource, method, args, body))
elif return_type == 'ConvState':
result = ConvState.parseFromJSON(self.__request_string(resource, method, args, body))
elif return_type == 'EVSEState':
result = EVSEState.parseFromJSON(self.__request_string(resource, method, args, body))
elif return_type == 'ConvOpMode':
result = ConvOpMode.parseFromJSON(self.__request_string(resource, method, args, body))
elif return_type == 'HeatCirculationPumpMode':
result = HeatCirculationPumpMode.parseFromJSON(self.__request_string(resource, method, args, body))
elif return_type == 'HeatCirculationPumpState':
result = HeatCirculationPumpState.parseFromJSON(self.__request_string(resource, method, args, body))
elif return_type == 'WYEV' or return_type == 'WYEA' or return_type == 'DELV':
import json
result = json.loads(self.__request_string(resource, method, args, body))
else:
raise TypeError(
'TypeError: The return type {0} by {1} is unknown or not supported yet.'.format(return_type,
resource))
except KeyError as e:
# raise e
raise ValueError('{0} - no such resource found for {1}'.format(resource, self._url))
else:
import json
return_string = self.__request_string(resource, method, args, body)
try:
result = json.loads(return_string)
except RecursionError:
raise RecursionError(
"Maximum recursion depth exceeded while decoding "
"a JSON array from a unicode string. Length: {0}".format(
len(return_string)))
return result
def __get_resource_return_type(self, resource):
if not resource[-1] in ('1', '2'):
# FIXME: Workaround for metmast, where string-based methods are suffixed by a '2' and int-based methods by a '1'
return self._resources[resource][0]
else:
return self._resources[resource[:-1]][0]
def __get_resource_argument_types(self, resource):
if not resource[-1] in ('1', '2'):
# FIXME: Workaround for metmast, where string-based methods are suffixed by a '2' and int-based methods by a '1'
return self._resources[resource][1:]
else:
return self._resources[resource[:-1]][1:]
def __check_argument(self, resource, args):
arg_types = self.__get_resource_argument_types(resource)
try:
# TODO Ignore complex arguments (for now)
for complex_arg_type in self._complex_arg_types:
if complex_arg_type in arg_types:
return
if len(args) == len(arg_types):
for arg, arg_type in zip(args, arg_types):
if arg_type == 'String' and type(arg) is not str:
raise TypeError()
elif arg_type == 'int' and type(arg) is not int:
raise TypeError()
elif (arg_type == 'float' or arg_type == 'double') and type(arg) is not float and type(
arg) is not int:
raise TypeError()
else:
raise TypeError()
except TypeError:
raise TypeError(
'The resource "{0}" requires exactly {1} argument(s) of type {2}.'.format(resource, len(arg_types),
arg_types))
def parse_resources(url):
import re
from ast import literal_eval
result = {}
resources = send_request(url, 'resourceNames', 'get')
resources = literal_eval(resources)
for resource in resources:
try:
# TODO - handle java arrays, ie, []
m = re.match(r"(\w+)\[?\]? (\w+)(\([\w ,]*\))?", resource).groups()
except AttributeError as e:
print(e)
continue
args = ''
if m[2] is not None:
args = tuple(m[2].replace(' ', '').replace('(', '').replace(')', '').split(','))
result[m[1]] = (m[0],) + args
else:
result[m[1]] = (m[0],)
if config.DEBUG:
print(f'Key: {m[1]} - {m[0]} -- {args}')
return result
def send_request(url, resource, method, args=None, body=None):
from requests import request
from requests.exceptions import ConnectionError, RequestException
if not type(url) is str:
raise TypeError('URL should be a string, found {0}'.format(type(url)))
if not type(resource) is str:
raise TypeError('URL should be a string, found {0}'.format(type(resource)))
kwargs = {}
kwargs.setdefault('allow_redirects', True)
kwargs.setdefault('headers', {'accept': 'application/json', 'content-type': 'application/json'})
if not url.endswith('/'):
url += '/'
full_url = url + resource + '/'
if args is not None and len(args) > 0:
full_url += '/'.join(tuple(str(x) for x in args))
if config.DEBUG:
print(f'Calling: {full_url}')
try:
response = request(method, full_url, data=body, **kwargs)
except ConnectionError as e:
print('Unable to connect to host: {0}.'.format(url))
raise e
except RequestException as e:
print('Request error from host: {0}.'.format(full_url))
raise e
if 200 <= response.status_code < 300:
result = response.text
elif response.status_code == 404:
raise ConnectionError(
'Resource not found on host or arguments are not formatted correctly: {0}'.format(response.text))
elif response.status_code == 405:
raise ConnectionError('Method not allowed on host: \n {0}'.format(response.text))
elif response.status_code == 500:
from pprint import pprint
# TODO: Handle exception
print('Exception on server:')
pprint(response.json())
raise
else:
raise ConnectionError(
'Unable to successfully connect to host. Returned with HTTP status code {0}.\n Content: {1}'.format(
response.status_code, response.text))
if config.DEBUG:
print(f'Succesfully called {full_url}')
print(f'Returned: {result}')
return result

View File

@ -0,0 +1 @@
from . import *

View File

@ -0,0 +1,47 @@
class BattOpMode:
_OPMODES = {
0: 'BATT_UNKNOWN',
1: 'BATT_AUTO',
2: 'BATT_AUTO_SUBMODE1',
3: 'BATT_AUTO_SUBMODE2',
4: 'BATT_AUTO_SUBMODE3',
5: 'BATT_AUTO_SUBMODE4',
6: 'BATT_MANUAL',
7: 'BATT_OFF',
8: 'BATT_NOBMS',
9: 'BATT_WITHBMS', }
def __init__(self, mode):
assert mode in BattOpMode.__OPMODES
self._mode = mode
@property
def mode(self):
return self._mode
def modeAsString(self):
return BattOpMode.__OPMODES[self.mode]
def __str__(self):
return 'BattOpMode: {0} ({1})'.format(self.mode, BattOpMode.__OPMODES[self.mode])
def parseToJSON(self):
from json import dumps
jsonObj = {};
jsonObj['mode'] = self.mode;
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
# result = None ## Appears unused
try:
jsonObj = loads(jsonString)
return BattOpMode(jsonObj.get('mode'))
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,5 @@
# TODO: Implement
class CommonDeviceConfig:
pass

View File

@ -0,0 +1,68 @@
class CompositeBoolean:
def __init__(self, value, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
self._value = value
self._timestampMicros = timestampMicros
self._timePrecision = timePrecision if timePrecision != None else 0
self._quality = quality if quality != None else 0
self._validity = validity if validity != None else 0
self._source = source if source != None else 0
@property
def value(self):
return self._value
@property
def timestampMicros(self):
return self._timestampMicros
@property
def timePrecision(self):
return self._timePrecision
@property
def quality(self):
return self._quality
@property
def validity(self):
return self._validity
@property
def source(self):
return self._source
def __repr__(self):
return "{0}(".format(self.__class__.__name__) + \
"{0!r}, {1!r}, ".format(self._value, self._timestampMicros) + \
"{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \
"{0!r}, {1!r})".format(self._validity, self._source)
def __str__(self):
from datetime import datetime
return 'CompositeBoolean: {0} (@time: {1} UTC) '.format(self.value, datetime.utcfromtimestamp(self.timestampMicros/1000000))
def parseToJSON(self):
from json import dumps
jsonObj = {};
jsonObj['value'] = self.value;
jsonObj['timestampMicros'] = self.timestampMicros;
jsonObj['timePrecision'] = self.timePrecision
jsonObj['quality'] = self.quality
jsonObj['validity'] = self.validity
jsonObj['source'] = self.source;
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
try:
jsonObj = loads(jsonString)
result = CompositeBoolean(jsonObj.get('value'), jsonObj.get('timestampMicros'),
jsonObj.get('timePrecision'),
jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source'))
return result
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,75 @@
MICROS_PER_SECOND = 1000000
def none_to_zero(x):
if x is None:
return 0
return x
class CompositeMeasurement:
def __init__(self, value, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
self._value = value
self._timestampMicros = timestampMicros
self._timePrecision = none_to_zero(timePrecision)
self._quality = quality if quality != None else 0
self._validity = validity if validity != None else 0
self._source = source if source != None else 0
@property
def value(self):
return self._value
@property
def timestampMicros(self):
return self._timestampMicros
@property
def timePrecision(self):
return self._timePrecision
@property
def quality(self):
return self._quality
@property
def validity(self):
return self._validity
@property
def source(self):
return self._source
def __str__(self):
from datetime import datetime
return 'CompositeMeasurement: {0} (@time: {1})'.format(self.value, datetime.fromtimestamp(self.timestampMicros/MICROS_PER_SECOND))
def __repr__(self):
return "{0}(".format(self.__class__.__name__) + \
"{0!r}, {1!r}, ".format(self._value, self._timestampMicros) + \
"{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \
"{0!r}, {1!r})".format(self._validity, self._source)
def parseToJSON(self):
from json import dumps
jsonObj = {};
jsonObj['value'] = self.value;
jsonObj['timestampMicros'] = self.timestampMicros;
jsonObj['timePrecision'] = self.timePrecision
jsonObj['quality'] = self.quality
jsonObj['validity'] = self.validity
jsonObj['source'] = self.source;
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
try:
jsonObj = loads(jsonString)
result = CompositeMeasurement(jsonObj.get('value'), jsonObj.get('timestampMicros'),
jsonObj.get('timePrecision'),
jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source'))
return result
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,5 @@
# TODO: Add Processing here
class CompositeStatus:
pass

View File

@ -0,0 +1,132 @@
from .CompositeStatus import CompositeStatus
class ConvOpMode:
_OPMODES = {
0: 'CONV_UNKNOWN',
1: 'CONV_PQ',
2: 'CONV_UF',
3: 'CONV_OFF',
}
def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
assert status in ConvOpMode._OPMODES
self._status = status
self._timestampMicros = timestampMicros
self._timePrecision = timePrecision
self._quality = quality
self._validity = validity
self._source = source
@property
def mode(self) -> int:
return self._status
def modeAsString(self):
return ConvOpMode._OPMODES[self.mode]
def __str__(self):
return f'ConvOpMode({self.mode}; {ConvOpMode._OPMODES[self.mode]})'
def parseToJSON(self):
from json import dumps
jsonObj = {'mode': self.mode}
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str:
raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
try:
jsonObj = loads(jsonString)
return ConvOpMode(**jsonObj.get('mode'))
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e
class ConvState:
_STATES = {
0: 'CONV_UNKNOWN',
1: 'CONV_STOPPED',
2: 'CONV_STARTING',
4: 'CONV_RUNNING',
8: 'CONV_STOPPING',
16: 'CONV_SYNCED',
32: 'CONV_DROOP',
64: 'CONV_LOADENABLED',
128: 'CONV_INHIBITED',
1024: 'CONV_READY',
2048: 'CONV_WARNING',
4096: 'CONV_ALARM'}
def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
self._status = status
self._timestampMicros = timestampMicros
self._timePrecision = timePrecision
self._quality = quality
self._validity = validity
self._source = source
@property
def status(self):
return self._status
@property
def timestampMicros(self):
return self._timestampMicros
@property
def timePrecision(self):
return self._timePrecision
@property
def quality(self):
return self._quality
@property
def validity(self):
return self._validity
@property
def source(self):
return self._source
def statusAsString(self):
return ConvState._STATES[self.status]
def __repr__(self):
return "{0}(".format(self.__class__.__name__) + \
"{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \
"{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \
"{0!r}, {1!r})".format(self._validity, self._source)
def __str__(self):
from datetime import datetime
return 'FlowBatteryState: {0} : {1} (@time: {2}))'.format(
self._status,
# status & statuscode evaluates to True if statuscode is 2^k and in the binary expansion of status
";".join([label for statuscode, label in ConvState._STATES.items() if self._status & statuscode]),
datetime.fromtimestamp(self.timestampMicros / 1000000))
def parseToJSON(self):
from json import dumps
json_obj = {'status': self._status}
return dumps(json_obj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
# result = None
try:
jsonObj = loads(jsonString).get('status')
return ConvState(
jsonObj.get('status'), jsonObj.get('timestampMicros'),
jsonObj.get('timePrecision'),
jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source'))
except JSONDecodeError as e:
print('Not a valid JSON string in parsing Converter Status: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,83 @@
class EVSEState:
_STATES = {
0: 'EVSE_UNKNOWN',
1: 'EVSE_NO_EV',
2: 'EVSE_EV_STOPPED',
4: 'EVSE_EV_READY',
8: 'EVSE_EV_STARTING',
16: 'EVSE_EV_CHARGING',
32: 'EVSE_EV_DISCHARGING',
64: 'EVSE_EV_PAUSED',
128: 'EVSE_EV_ALARM',
256: 'EVSE_EV_ESD', }
def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
self._status = status
self._timestampMicros = timestampMicros
self._timePrecision = timePrecision
self._quality = quality
self._validity = validity
self._source = source
@property
def status(self):
return self._status
@property
def timestampMicros(self):
return self._timestampMicros
@property
def timePrecision(self):
return self._timePrecision
@property
def quality(self):
return self._quality
@property
def validity(self):
return self._validity
@property
def source(self):
return self._source
def statusAsString(self):
return EVSEState.__STATES[self.status]
def __repr__(self):
return "{0}(".format(self.__class__.__name__) + \
"{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \
"{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \
"{0!r}, {1!r})".format(self._validity, self._source)
def __str__(self):
from datetime import datetime
return 'EVSEState: {0} : {1} (@time: {2}))'.format(
self.status,
# status & statuscode evaluates to True if statuscode is 2^k and in the binary expansion of status
";".join([label for statuscode, label in EVSEState._STATES.items() if self._status & statuscode]),
datetime.fromtimestamp(self.timestampMicros / 1000000))
def parseToJSON(self):
from json import dumps
jsonObj = {'status': self.status}
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
# result = None
try:
jsonObj = loads(jsonString).get('status')
return EVSEState(
jsonObj.get('status'), jsonObj.get('timestampMicros'),
jsonObj.get('timePrecision'),
jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source'))
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,88 @@
class FlowBatteryState:
_STATES = {
0: 'BATT_UNKNOWN',
1: 'BATT_STOPPED',
2: 'BATT_STARTING',
4: 'BATT_FLOODING',
8: 'BATT_RUNNING',
16: 'BATT_STOPPING',
32: 'BATT_DRAINING',
64: 'BATT_PUMPSRAMP',
128: 'BATT_TANKVALVESOP',
256: 'BATT_PUMPSRUN',
512: 'BATT_DCBREAKER',
1024: 'PCS_READY',
2048: 'PCS_RUNNING',
4096: 'BATT_ALARM',
8192: 'BATT_ESD'}
def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
self._status = status
self._timestampMicros = timestampMicros
self._timePrecision = timePrecision
self._quality = quality
self._validity = validity
self._source = source
@property
def status(self):
return self._status
@property
def timestampMicros(self):
return self._timestampMicros
@property
def timePrecision(self):
return self._timePrecision
@property
def quality(self):
return self._quality
@property
def validity(self):
return self._validity
@property
def source(self):
return self._source
def statusAsString(self):
return FlowBatteryState.__STATES[self.status]
def __repr__(self):
return "{0}(".format(self.__class__.__name__) + \
"{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \
"{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \
"{0!r}, {1!r})".format(self._validity, self._source)
def __str__(self):
from datetime import datetime
return 'FlowBatteryState: {0} : {1} (@time: {2}))'.format(
self.status,
# status & statuscode evaluates to True if statuscode is 2^k and in the binary expansion of status
";".join([label for statuscode, label in FlowBatteryState._STATES.items() if self._status & statuscode]),
datetime.fromtimestamp(self.timestampMicros / 1000000))
def parseToJSON(self):
from json import dumps
jsonObj = {'status': self.status}
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
# result = None
try:
jsonObj = loads(jsonString).get('status')
return FlowBatteryState(
jsonObj.get('status'), jsonObj.get('timestampMicros'),
jsonObj.get('timePrecision'),
jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source'))
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,89 @@
class HeatCirculationPumpMode:
__OPMODES = {
-1: 'PUMP_MODE_UNKNOWN',
0: 'PUMP_MODE_CONSTANT_SPEED',
1: 'PUMP_MODE_CONSTANT_FREQUENCY',
3: 'PUMP_MODE_CONSTANT_HEAD',
4: 'PUMP_MODE_CONSTANT_PRESSURE',
5: 'PUMP_MODE_CONSTANT_DIFF_PRESSURE',
6: 'PUMP_MODE_PROPORTIONAL_PRESSURE',
7: 'PUMP_MODE_CONSTANT_FLOW',
8: 'PUMP_MODE_CONSTANT_TEMP',
10: 'PUMP_MODE_CONSTANT_LEVEL',
128: 'PUMP_MODE_AUTO_ADAPT',
129: 'PUMP_MODE_FLOW_ADAPT' }
__OPMODES_R = {v: k for k, v in __OPMODES.items()}
def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
"""
@Input:
mode (int or str): Pump operating mode, can be both int and string
To get list of available modes: HeatCirculationPumpMode.opmodes()
"""
if status in HeatCirculationPumpMode.__OPMODES:
self._status = status
elif mode in HeatCirculationPumpMode.__OPMODES_R:
self._status = HeatCirculationPumpMode.__OPMODES_R[status]
else:
raise AssertionError('HeatCirculationPumpMode not recognized: \'{0}\''.format(mode))
self._timestampMicros = timestampMicros
self._timePrecision = timePrecision
self._quality = quality
self._validity = validity
self._source = source
@staticmethod
def opmodes():
return HeatCirculationPumpMode.__OPMODES.copy()
@property
def mode(self):
return self._status
@property
def modeAsString(self):
return HeatCirculationPumpMode.__OPMODES[self.mode]
def __repr__(self):
return "{0}(".format(self.__class__.__name__) + \
"{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \
"{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \
"{0!r}, {1!r})".format(self._validity, self._source)
def __str__(self):
return 'HeatCirculationPumpMode: {0} ({1})'.format(self.mode, self.modeAsString)
def parseToJSON(self):
from json import dumps
jsonObj = {};
jsonObj['mode'] = {
'status': self._status,
'timestampMicros': self._timestampMicros,
'timePrecision': self._timePrecision,
'quality': self._quality,
'validity': self._validity,
'source': self._source,
}
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
# result = None ## Appears unused
try:
jsonObj = loads(jsonString).get('mode')
return HeatCirculationPumpMode(
jsonObj.get('status'),
jsonObj.get('timestampMicros'),
jsonObj.get('timePrecision'),
jsonObj.get('quality'),
jsonObj.get('validity'),
jsonObj.get('source'))
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,83 @@
class HeatCirculationPumpState:
__OPSTATES = {
-1: 'PUMP_STATE_UNKNOWN',
0: 'PUMP_STATE_STOPPED',
1: 'PUMP_STATE_RUNNING',
2: 'PUMP_STATE_ERROR',}
__OPSTATES_R = {v: k for k, v in __OPSTATES.items()}
def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0):
"""
@Input:
status (int or str): Pump operating mode, can be both int and string
To get list of available modes: HeatCirculationPumpState.opstates()
"""
if status in HeatCirculationPumpState.__OPSTATES:
self._status = status
elif state in HeatCirculationPumpState.__OPSTATES_R:
self._status = HeatCirculationPumpState.__OPSTATES_R[status]
else:
raise AssertionError('HeatCirculationPumpState not recognized: \'{0}\''.format(mode))
self._timestampMicros = timestampMicros
self._timePrecision = timePrecision
self._quality = quality
self._validity = validity
self._source = source
@staticmethod
def opstates():
return HeatCirculationPumpState.__OPSTATES.copy()
@property
def state(self):
return self._status
@property
def stateAsString(self):
return HeatCirculationPumpState.__OPSTATES[self._status]
def __repr__(self):
return "{0}(".format(self.__class__.__name__) + \
"{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \
"{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \
"{0!r}, {1!r})".format(self._validity, self._source)
def __str__(self):
return 'HeatCirculationPumpState: {0} ({1})'.format(self.state, self.stateAsString)
def parseToJSON(self):
from json import dumps
jsonObj = {}
jsonObj['state'] = {
'status': self._status,
'timestampMicros': self._timestampMicros,
'timePrecision': self._timePrecision,
'quality': self._quality,
'validity': self._validity,
'source': self._source,
}
return dumps(jsonObj)
@staticmethod
def parseFromJSON(jsonString):
from json import JSONDecodeError, loads
if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString)))
# result = None ## Appears unused
try:
jsonObj = loads(jsonString).get('state')
return HeatCirculationPumpState(
jsonObj.get('status'),
jsonObj.get('timestampMicros'),
jsonObj.get('timePrecision'),
jsonObj.get('quality'),
jsonObj.get('validity'),
jsonObj.get('source'))
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(jsonString))
raise e

View File

@ -0,0 +1,73 @@
from dataclasses import dataclass
from . import CompositeMeasurement
from typing import Optional
# TODO: Add processing to these.
@dataclass
class DELV:
phaseAB: CompositeMeasurement
phaseBC: CompositeMeasurement
phaseCA: CompositeMeasurement
phaseAverage: CompositeMeasurement
@staticmethod
def parseFromJSON(json_string):
from json import JSONDecodeError, loads
if not type(json_string) is str:
raise TypeError('jsonString should be a string, found {0}'.format(type(json_string)))
try:
jsonObj = loads(json_string)
phaseABcm = CompositeMeasurement(**jsonObj.get('phaseAB'))
phaseBCcm = CompositeMeasurement(**jsonObj.get('phaseBC'))
phaseCAcm = CompositeMeasurement(**jsonObj.get('phaseCA'))
phaseAVGcm = CompositeMeasurement(**jsonObj.get('phaseAverage'))
return DELV(phaseABcm, phaseBCcm, phaseCAcm, phaseAVGcm)
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(json_string))
raise e
class GPSL:
pass
class HLTH:
pass
class LNPL:
pass
class PNPL:
pass
@dataclass()
class WYEA:
phaseAB: CompositeMeasurement
phaseBC: CompositeMeasurement
phaseCA: CompositeMeasurement
neutral: Optional[CompositeMeasurement] = None
@staticmethod
def parseFromJSON(json_string):
from json import JSONDecodeError, loads
if not type(json_string) is str:
raise TypeError('jsonString should be a string, found {0}'.format(type(json_string)))
try:
jsonObj = loads(json_string)
phaseAcm = CompositeMeasurement(**jsonObj.get('phaseA'))
phaseBcm = CompositeMeasurement(**jsonObj.get('phaseB'))
phaseCcm = CompositeMeasurement(**jsonObj.get('phaseC'))
neutral = jsonObj.get('neutral')
if neutral is not None:
neutral = CompositeMeasurement(**neutral)
return WYEA(phaseAcm, phaseBcm, phaseCcm, neutral)
except JSONDecodeError as e:
print('Not a valid JSON string: {0}.'.format(json_string))
raise e

View File

@ -0,0 +1,16 @@
__all__ = []
# Composite Data Types
from .CompositeBoolean import CompositeBoolean
from .CompositeMeasurement import CompositeMeasurement
from .CompositeStatus import CompositeStatus
# SYSLAB-specific data types
from .Identifiers import DELV, GPSL, HLTH, LNPL, PNPL, WYEA
# Unit-specific data types
from .BattOpMode import BattOpMode
from .FlowBatteryState import FlowBatteryState
from .HeatCirculationPumpMode import HeatCirculationPumpMode
from .HeatCirculationPumpState import HeatCirculationPumpState
from .ConverterTypes import ConvState, ConvOpMode

View File

@ -0,0 +1,27 @@
def get_flexhouse(simulated=False, physical=False, simulate_on_dumpload_ID="", simulate_on_battery=False):
"""
Return an instantiated object which operates as a flexhouse.
input:
simulated (bool): Whether the flexhouse should be simulated
physical (bool): Whether the flexhouse should be the real flexhouse
simulate_on_dumpload_ID (string): The ID of the dumpload on which to simulate the flexhouse
simulate_on_battery (bool): Whether to simulate on battery
return:
flexhouse: A flexhouse object
"""
if simulated:
if simulate_on_battery:
from .virtual.FlexHouse_sim_batt import FlexHouse_sim_batt
return FlexHouse_sim_batt('batt1')
else:
from .virtual.FlexHouse_sim import FlexHouse_sim
assert simulate_on_dumpload_ID != "", "Must supply an ID string for the dumpload used in Flexhouse simulation if not simulating on battery"
return FlexHouse_sim(simulate_on_dumpload_ID)
elif physical:
from .physical.FlexHouse_real import FlexHouse_real
return FlexHouse_real()
else:
raise Exception('Must define if FlexHouse instance is real or simulated')

View File

@ -0,0 +1,258 @@
from ..core.SyslabUnit import SyslabUnit
from ..core.datatypes import CompositeMeasurement, CompositeBoolean, CompositeStatus
from ..core.datatypes import CommonDeviceConfig
from ..core.datatypes import DELV, WYEA, GPSL, PNPL, LNPL, HLTH, ConvOpMode, ConvState
from typing import List, Union
from time import time
def cast_to_cm(m: Union[CompositeMeasurement, float]):
if type(m) == float:
# TODO: Is there a better way to estimate precision of time.time?
request = CompositeMeasurement(m, timestampMicros=time()*1e6, timePrecision=1000)
elif type(m) == CompositeMeasurement:
request = m
else:
raise TypeError(f"Unknown request type: {type(m)}")
return request
class B2BConverter(SyslabUnit):
"""
Class covering back-to-back converters in SYSLAB.
A full list of available switchboards can be found by calling 'SwitchBoard.getAvailableSwitchBoards()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
_CONVERTERS = {
'ABBB2B': ('syslab-04.syslab.dk', '8080', 'B2BConverter'),
}
MAXP = 60
MAXQ = 60
@staticmethod
def getAvailableUnits():
return list(B2BConverter.__CONVERTERS.keys())
def __init__(self, which=None, host=None, port=None, unit_name=None):
baseurl = 'http://{host}:{port}/typebased_WebService_ABBConverter/ABBConverterWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self._CONVERTERS,
host=host,
port=port,
unit_name=unit_name,
unit_type="B2BConverter")
# Inventory functions
# Configuration class used for HMI layout
def getNodeConfiguration(self, ) -> CommonDeviceConfig:
return self._request_resource('getNodeConfiguration')
# Component description
def getConverterName(self, ) -> str:
return self._request_resource('getConverterName')
def getConverterLogicalNameplate(self, ) -> LNPL:
raise NotImplementedError
def getConverterPhysicalNameplate(self, ) -> PNPL:
raise NotImplementedError
def getConverterHealth(self, ) -> HLTH:
raise NotImplementedError
def getGPSLocation(self, ) -> GPSL:
raise NotImplementedError
# Operating characteristics (corresponding to COPR/DRAT node)
def getRatedP(self, ) -> float:
raise NotImplementedError
# return self._request_resource('getRatedP')
def getRatedS(self, ) -> float:
raise NotImplementedError
# return self._request_resource('getRatedS')
def getRatedQ(self, ) -> float:
raise NotImplementedError
# return self._request_resource('getRatedQ')
def getRatedU(self, ) -> float:
raise NotImplementedError
# return self._request_resource('getRatedU')
def getRatedI(self, ) -> float:
raise NotImplementedError
# return self._request_resource('getRatedI')
def getRatedf(self, ) -> float:
raise NotImplementedError
# return self._request_resource('getRatedf')
# Operating mode settings (corresponding to DOPM node)
def getAvailableOperatingModes(self, ) -> List[ConvOpMode]:
raise NotImplementedError
def getCurrentOperatingMode(self, ) -> ConvOpMode:
return self._request_resource('getCurrentOperatingMode')
def setOperatingMode(self, mode: ConvOpMode) -> None:
raise NotImplementedError
def isPSetpointEnabled(self, ) -> CompositeBoolean:
return self._request_resource('isPSetpointEnabled')
def isQSetpointEnabled(self, ) -> CompositeBoolean:
return self._request_resource('isQSetpointEnabled')
def isUSetpointEnabled(self, ) -> CompositeBoolean:
return self._request_resource('isUSetpointEnabled')
def isfSetpointEnabled(self, ) -> CompositeBoolean:
return self._request_resource('isfSetpointEnabled')
# Status information (corresponding to DPST node)
def getConverterStatus(self, ) -> ConvState:
return self._request_resource('getConverterStatus')
# Alarms information
def hasActiveFault(self, ) -> CompositeBoolean:
raise NotImplementedError
def hasActiveWarning(self, ) -> CompositeBoolean:
raise NotImplementedError
def resetAlarms(self, ) -> None:
raise NotImplementedError
def getActiveEventCode(self, ) -> CompositeStatus:
raise NotImplementedError
# DER controller characteristics (corresponding to DRCT node)
def setActivePower(self, m: Union[CompositeMeasurement, float]) -> None:
"""
Send a request for the converter's output active power. Requires the converter to be in PQ mode.
:param m: [kW] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.)
:return: None
"""
P_UpperLimit = 15.0
P_LowerLimit = -15.0
return self._request_resource('setActivePower', (), 'put', cast_to_cm(max(P_LowerLimit, min(m, P_UpperLimit))).parseToJSON())
def setFrequency(self, m: Union[CompositeMeasurement, float]) -> None:
"""
Send a request for the converter's output power. Requires the converter to be in UF mode.
:param m: [Hz] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.)
:return: None
"""
return self._request_resource('seFrequency', (), 'put', cast_to_cm(m).parseToJSON())
def setReactivePower(self, m: Union[CompositeMeasurement, float]) -> None:
"""
Send a request for the converter's output reactive power. Requires the converter to be in PQ mode.
:param m: [kVA] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.)
:return: None
"""
Q_UpperLimit = 15.0
Q_LowerLimit = -15.0
return self._request_resource('setReactivePower', (), 'put', cast_to_cm(max(Q_LowerLimit, min(m, Q_UpperLimit))).parseToJSON())
def setVoltage(self, m: Union[CompositeMeasurement, float]) -> None:
"""
Send a request for the converter's output voltage. Requires the converter to be in UF mode.
:param m: [V] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.)
:return: None
"""
return self._request_resource('setVoltage', (), 'put', cast_to_cm(m).parseToJSON())
def getActivePowerSetpoint(self, ) -> CompositeMeasurement:
raise NotImplementedError
def getFrequencySetpoint(self, ) -> CompositeMeasurement:
raise NotImplementedError
def getReactivePowerSetpoint(self, ) -> CompositeMeasurement:
raise NotImplementedError
def getVoltageSetpoint(self, ) -> CompositeMeasurement:
raise NotImplementedError
# Synchronisation (corresponding to RSYN node)
def synchronize(self, ) -> None:
raise NotImplementedError
def unsynchronize(self, ) -> None:
raise NotImplementedError
def setDroopEnable(self, b: CompositeBoolean) -> None:
raise NotImplementedError
def setLoadEnable(self, b: CompositeBoolean) -> None:
raise NotImplementedError
def getVoltageDroopPct(self, ) -> CompositeMeasurement:
raise NotImplementedError
def getFrequencyDroopPct(self, ) -> CompositeMeasurement:
raise NotImplementedError
def setVoltageDroopPct(self, pct: CompositeMeasurement) -> None:
raise NotImplementedError
def setFrequencyDroopPct(self, pct: CompositeMeasurement) -> None:
raise NotImplementedError
# Reciprocating Engine (corresponds to DCIP node)
def startConverter(self, ) -> None:
return self._request_resource('startConverter', (), 'put')
def softStopConverter(self, ) -> None:
return self._request_resource('softStopConverter', (), 'put')
def stopConverter(self, ) -> None:
return self._request_resource('stopConverter', (), 'put')
# AC quantities (corresponds to MMXU nodes)
def getActivePowerOutput(self, ) -> CompositeMeasurement:
return self._request_resource('getActivePowerOutput')
def getReactivePowerOutput(self, ) -> CompositeMeasurement:
return self._request_resource('getReactivePowerOutput')
def getOutputFrequency(self, ) -> CompositeMeasurement:
return self._request_resource('getOutputFrequency')
def getOutputInterphaseVoltages(self, ) -> DELV:
return self._request_resource('getOutputInterphaseVoltages')
def getOutputPhaseCurrents(self, ) -> WYEA:
raise NotImplementedError
def getRectifierInterphaseVoltages(self, ) -> DELV:
return self._request_resource('getRectifierInterphaseVoltages')
def getRectifierPhaseCurrents(self, ) -> WYEA:
return self._request_resource('getRectifierPhaseCurrents')
def getSyncBusInterphaseVoltages(self, ) -> DELV:
raise NotImplementedError
class ABBB2BConverter(B2BConverter):
def __init__(self):
super(ABBB2BConverter, self).__init__(which='ABBB2B')

View File

@ -0,0 +1,85 @@
from ..core.SyslabUnit import SyslabUnit
from ..core.datatypes.FlowBatteryState import FlowBatteryState
from ..core.datatypes.BattOpMode import BattOpMode
class Battery(SyslabUnit):
"""The Battery class represents a battery in SYSLAB.
The Battery class is instantiated using a string with the unique name of the battery, ie. 'which'
A full list of available batteries can be found by calling 'Battery.getAvailableBatteries()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
__BATTERIES = {
'batt1': ('syslab-12.syslab.dk', '8080', 'batt1'),
'battemu': ('syslab-31.syslab.dk', '8080', 'battemu'),
'battfh1': ('syslab-s01.syslab.dk', '8080', 'battfh1'),
'simlab-15': ('192.168.0.115', '8080', 'batt1'),
'vbatt1': ('simlab-12', '8080', 'batt1'),
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_Battery/VRBBatteryWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__BATTERIES,
host=host,
port=port,
unit_name=unitname,
unit_type="Battery")
def startBattery(self):
return self._request_resource('startBattery', (), 'put')
def stopBattery(self):
return self._request_resource('stopBattery', (), 'put')
def getActivePower(self):
return self._request_resource('getACActivePower')
def setActivePower(self, setPoint):
P_UpperLimit = 15.0
P_LowerLimit = -15.0
return self._request_resource('setP', (max(P_LowerLimit, min(setPoint, P_UpperLimit))), 'put')
def getReactivePower(self):
return self._request_resource('getACReactivePower')
def setReactivePower(self, setPoint):
return self._request_resource('setQ', (setPoint), 'put')
def getFrequency(self):
return self._request_resource('getACFrequency')
def getRemainingFloodTime(self):
return min(self._request_resource('getRemainingFloodTime'), self._request_resource('getRemainingDrainTime'))
def getCurrentOperatingMode(self):
return self._request_resource('getCurrentOperatingMode')
def getCurrentOperatingState(self):
return self._request_resource('getCurrentOperatingState')
def setOperatingMode(self, mode):
return self._request_resource('setOperatingMode', (), 'put', BattOpMode(mode).parseToJSON())
def getSOC(self):
return self._request_resource('getSOC')
def getRatedActivePower(self):
return self._request_resource('getRatedP')
def getRatedReactivePower(self):
return self._request_resource('getRatedQ')
def getName(self):
return self._request_resource('getBatteryName')
@staticmethod
def getAvailableBatteries():
return list(Battery.__BATTERIES.keys())

View File

@ -0,0 +1,100 @@
from ..core.SyslabUnit import SyslabUnit
class DieselGenerator(SyslabUnit):
"""The DieselGenerator class represents a photovoltaic panel array in SYSLAB.
The DieselGenerator class is instantiated using a string with the unique name of the dumpload, ie. 'which'
A full list of available panel arrays can be found by calling 'DieselGenerator.getAvailableDieselGenerator()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
__UNITS = {
'diesel319': ('syslab-02.syslab.dk', '8080', 'genset1'),
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_Diesel/DEIFDieselGensetWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__UNITS,
host=host,
port=port,
unit_name=unitname,
unit_type="DieselGenerator")
def getRatedP(self):
return self._request_resource('getRatedP')
def getRatedQ(self):
return self._request_resource('getRatedQ')
def getRatedS(self):
return self._request_resource('getRatedS')
def getRatedU(self):
return self._request_resource('getRatedU')
def getRatedf(self):
return self._request_resource('getRatedf')
def isGeneratorBreakerClosed(self):
return self._request_resource('isGeneratorBreakerClosed')
def isGensetRunning(self):
return self._request_resource('isGensetRunning')
def isGensetSynchronized(self):
return self._request_resource('isGensetSynchronized')
def isSynchronising(self):
return self._request_resource('isSynchronising')
def closeGB(self):
return self._request_resource('closeGB', method='put')
def openGB(self):
return self._request_resource('openGB', method='put')
def startGenset(self):
return self._request_resource('startGenset', method='put')
def stopGenset(self):
return self._request_resource('stopGenset', method='put')
def setTargetActivePower(self, setpoint):
return self._request_resource('setTargetActivePower', setpoint, 'put')
def setTargetReactivePower(self, setpoint):
return self._request_resource('setTargetReactivePower', setpoint, 'put')
def getActivePower(self):
return self._request_resource('getActivePower')
def getReactivePower(self):
return self._request_resource('getReactivePower')
#TODO: Implement
def getCurrentGensetMode(self):
pass
#TODO: Implement
def getCurrentRunningMode(self):
pass
#TODO: Implement
def setGensetMode(self, mode):
pass
#TODO: Implement
def setRunningMode(self, mode):
pass
@staticmethod
def getAvailableDieselGenerators():
return list(DieselGenerator.__UNITS.keys())

View File

@ -0,0 +1,112 @@
from ..core.SyslabUnit import SyslabUnit
import warnings
class Dumpload(SyslabUnit):
"""The Dumpload class represents a dumpload in SYSLAB.
The Dumpload class is instantiated using a string with the unique name of the dumpload, ie. 'which'
A full list of available dumploads can be found by calling 'Dumpload.getAvailableDumpLoads()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
__DUMPLOADS = {
'mobload1': ('syslab-16.syslab.dk', '8080', 'mobload1'),
'mobload2': ('syslab-17.syslab.dk', '8080', 'mobload2'),
'mobload3': ('syslab-18.syslab.dk', '8080', 'mobload3'),
'load1': ('syslab-05.syslab.dk', '8080', 'load1'),
'vmobload1': ('simlab-16', '8080', 'mobload1'),
'vmobload2': ('simlab-17', '8080', 'mobload2'),
'vmobload3': ('simlab-18', '8080', 'mobload3'),
'vload1': ('simlab-05', '8080', 'load1'),
'simlab-05': ('192.168.0.105', '8080', 'mobload2'),
'simlab-11': ('192.168.0.111', '8080', 'mobload1'),
'simlab-12': ('192.168.0.112', '8080', 'load1'),
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_Load/GenericLoadWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__DUMPLOADS,
host=host,
port=port,
unit_name=unitname,
unit_type="Battery")
def startLoad(self):
return self._request_resource('startLoad', (), 'put')
def stopLoad(self):
return self._request_resource('stopLoad', (), 'put')
def getPowerSetPoint(self):
return self._request_resource('getConstantP')
def setPowerSetPoint(self, setPoint):
"""
Set the active power setpoint for the load.
Inputs:
setPoint (float): Requested power setpoint
Outputs:
Ack (bool): Acknowledgement of receiver
"""
P_UpperLimit = 15.0
P_LowerLimit = 0
return self._request_resource('setConstantP', max(P_LowerLimit, min(setPoint, P_UpperLimit)), 'put')
def getActivePower(self):
return self._request_resource('getActivePower')
def getReactivePower(self):
"""
Get the reactive power draw from the load.
Outputs:
Q (CompositeMeasurement): Current reactive power draw, calculated from active power draw (see note)
NOTE: This is a theoretical value calculated from the relation
Q = Q_r * sin (pi * P /P_r)
where P_r and Q_r are the rated active and reactive power draw.
For control purposes, use the measured value from the switchboard
instead to get an actual measurement.
"""
warnings.warn("The output of getReactivePower from the Dumpload class is calculated from the active power draw rather than a measured value. For control purposes, use the measured power draw on the switchboard.")
return self._request_resource('getReactivePower')
def getRatedPower(self):
return self._request_resource('getRatedP')
def getRatedReactivePower(self):
return self._request_resource('getRatedQ')
def getName(self):
return self._request_resource('getLoadName')
def isLoadOn(self):
return self._request_resource('isLoadOn')
@staticmethod
def getAvailableDumploads():
return list(Dumpload.__DUMPLOADS.keys())
class MobileLoad1(Dumpload):
def __init__(self):
super(MobileLoad1, self).__init__("mobload1")
class MobileLoad2(Dumpload):
def __init__(self):
super(MobileLoad2, self).__init__("mobload2")
class MobileLoad3(Dumpload):
def __init__(self):
super(MobileLoad3, self).__init__("mobload3")

View File

@ -0,0 +1,213 @@
from ..core.SyslabUnit import SyslabUnit
from ..core.datatypes import CompositeMeasurement
from typing import Union
from time import time
def cast_to_cm(m: Union[CompositeMeasurement, float]):
if type(m) == float:
# TODO: Is there a better way to estimate precision of time.time?
request = CompositeMeasurement(m, timestampMicros=time()*1e6, timePrecision=1000)
elif type(m) == CompositeMeasurement:
request = m
else:
raise TypeError(f"Unknown request type: {type(m)}")
return request
class EVSE(SyslabUnit):
"""The EVSE class represents a charging post in SYSLAB.
The EVSE class is instantiated using a string with the unique name of the charging post, ie. 'which'
A full list of available charging posts can be found by calling 'EVSE.getAvailableChargingPosts()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
__CHARGINGPOSTS = {
'V2G-319': ('syslab-35.syslab.dk', '8080', 'Endesa_V2G'),
'EVSE-NEVIC-6': ('10.42.245.96', '8080', 'EVSE_NEVIC_6'),
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_EVSE/EVSEWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__CHARGINGPOSTS,
host=host,
port=port,
unit_name=unitname,
unit_type="EVSE")
def isEVPresent(self):
return self._request_resource('isEVPresent')
def canRemoteStartStop(self):
return self._request_resource('canRemoteStartStop')
def canLimitChargePower(self):
return self._request_resource('canLimitChargePower')
def canDischarge(self):
return self._request_resource('canDischarge')
def hasSOC(self):
return self._request_resource('hasSOC')
def hasACMeasurements(self):
return self._request_resource('hasACMeasurements')
def hasDCMeasurements(self):
return self._request_resource('hasDCMeasurements')
def getAvailableOperatingStates(self):
return self._request_resource('getAvailableOperatingStates')
def getCurrentOperatingState(self):
return self._request_resource('getCurrentOperatingState')
def getACActivePower(self):
return self._request_resource('getACActivePower')
def getACReactivePower(self):
return self._request_resource('getACReactivePower')
def getACFrequency(self):
return self._request_resource('getACFrequency')
def getACVoltages(self):
return self._request_resource('getACVoltages')
def getACCurrents(self):
return self._request_resource('getACCurrents')
def getSOC(self):
return self._request_resource('getSOC')
def getMinSOC(self):
return self._request_resource('getMinSOC')
def getMaxSOC(self):
return self._request_resource('getMaxSOC')
def startCharge(self):
return self._request_resource('startCharge', (), 'put')
def stopCharge(self):
return self._request_resource('stopCharge', (), 'put')
def startDischarge(self):
return self._request_resource('startDischarge', (), 'put')
def stopDischarge(self):
return self._request_resource('stopDischarge', (), 'put')
def getMinimumChargePower(self):
return self._request_resource('getMinimumChargePower')
def getMaximumChargePower(self):
return self._request_resource('getMaximumChargePower')
def getMinimumDischargePower(self):
return self._request_resource('getMinimumDischargePower')
def getMaximumDischargePower(self):
return self._request_resource('getMaximumDischargePower')
def setPowerSetpoint(self, setPoint):
P_UpperLimit = 10.0
P_LowerLimit = -10.0
return self._request_resource('setP', (), 'put', cast_to_cm(max(P_LowerLimit, min(setPoint, P_UpperLimit))).parseToJSON())
def isPSetpointEnabled(self):
return self._request_resource('isPSetpointEnabled')
def getActiveEnergyImport(self): # TODO not working/implemented
return self._request_resource('getActiveEnergyImport')
def getActiveEnergyExport(self): # TODO not working/implemented
return self._request_resource('getActiveEnergyExport')
def getReactiveEnergyImport(self): # TODO not working/implemented
return self._request_resource('getReactiveEnergyImport')
def getReactiveEnergyExport(self): # TODO not working/implemented
return self._request_resource('getReactiveEnergyExport')
def getNodeConfiguration(self): # TODO not working/implemented
return self._request_resource('getNodeConfiguration')
def getEVSEName(self):
return self._request_resource('getEVSEName')
def getEVSELogicalNameplate(self): # TODO not working/implemented
return self._request_resource('getEVSELogicalNameplate')
def getEVSEPhysicalNameplate(self): # TODO not working/implemented
return self._request_resource('getEVSEPhysicalNameplate')
def getEVSEHealth(self): # TODO not working/implemented
return self._request_resource('getEVSEHealth')
def getGPSLocation(self): # TODO not working/implemented
return self._request_resource('getGPSLocation')
def getNumberOfEvents(self): # TODO not working/implemented
return self._request_resource('getNumberOfEvents')
def getEvents(self): # TODO not working/implemented
return self._request_resource('getEvents')
def getNumberOfAlarms(self): # TODO not working/implemented
return self._request_resource('getNumberOfAlarms')
def getAlarms(self): # TODO not working/implemented
return self._request_resource('getAlarms')
def getNumberOfUnacknowledgedAlarms(self): # TODO not working/implemented
return self._request_resource('getNumberOfUnacknowledgedAlarms')
def getUnacknowledgedAlarms(self): # TODO not working/implemented
return self._request_resource('getUnacknowledgedAlarms')
def acknowledgeAlarms(self): # TODO not working/implemented
return self._request_resource('acknowledgeAlarms')
def getRatedP(self):
return self._request_resource('getRatedP')
def getRatedQ(self):
return self._request_resource('getRatedQ')
def getRatedS(self):
return self._request_resource('getRatedS')
def getRatedU(self):
return self._request_resource('getRatedU')
def getRatedf(self):
return self._request_resource('getRatedf')
def getInverterName(self): # TODO not working/implemented
return self._request_resource('getInverterName')
def getInverterLogicalNameplate(self): # TODO not working/implemented
return self._request_resource('getInverterLogicalNameplate')
def getInverterHealth(self): # TODO not working/implemented
return self._request_resource('getInverterHealth')
def getDCVoltage(self): # TODO not working/implemented
return self._request_resource('getDCVoltage')
def getDCPower(self): # TODO not working/implemented
return self._request_resource('getDCPower')
def isDCContactorClosed(self): # TODO not working/implemented
return self._request_resource('isDCContactorClosed')
@staticmethod
def getAvailableEVSE():
return list(EVSE.__CHARGINGPOSTS.keys())

View File

@ -0,0 +1,164 @@
from ..core.SyslabUnit import SyslabUnit
from ..core.datatypes import CompositeMeasurement, HeatCirculationPumpMode, HeatCirculationPumpState
class Valve:
"""
Convenience class which calls methods on its associated heatswitchboard
"""
def __init__(self, label, heatswitchboard):
self.label = label
self.heatswitchboard = heatswitchboard
#TODO: Add methods
class Pump:
"""
Convenience class which calls methods on its associated heatswitchboard
"""
def __init__(self, label, heatswitchboard):
self.label = label
self.heatswitchboard = heatswitchboard
#TODO: Add methods
class Meter:
"""
Convenience class which calls methods on its associated heatswitchboard
"""
def __init__(self, label, heatswitchboard):
self.label = label
self.heatswitchboard = heatswitchboard
#TODO: Add methods
class HeatSwitchBoard(SyslabUnit):
"""
The HeatSwitchBoard class represents a HeatSwitchBoard in SYSLAB.
The HeatSwitchBoard class is instantiated using a string with the unique name of the switchboard, ie. 'which'
A full list of available switchboards can be found by calling 'SwitchBoard.getAvailableSwitchBoards()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
__HEAT_SWITCH_BOARDS = {
'716-h1': ('syslab-33.syslab.dk', '8080', '716-h1'),
}
@staticmethod
def getAvailableHeatSwitchBoards():
return list(HeatSwitchBoard.__HEAT_SWITCH_BOARDS.keys())
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = f'http://{host}:{port}/typebased_WebService_HeatSubstation/HeatSwitchboardWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__HEAT_SWITCH_BOARDS,
host=host,
port=port,
unit_name=unitname,
unit_type="HeatSwitchBoard")
# Inventory functions
#TODO Handle these in core/SYSLAB_unit.py by properly handling arrays. This is a bodge for now.
def getHeatMeters(self):
result = self._request_resource('getHeatMeters', check_types=False)
return result
#TODO, P1: Awaiting Valve[]
def getValves(self):
raise NotImplementedError
# Valve functions
def getValvePosition(self, valve) -> CompositeMeasurement:
return self._request_resource('getValvePosition', valve)
#TODO: Implement
#def setValvePosition(self, valve: str, position: float, timestamp=0):
def setValvePosition(self, valve: str, position: CompositeMeasurement):
"""
Sets *valve* to *position*. Position must be float in the
interval from 0.0 to 1.0
Position = 0.0 => *valve* fully closed
Position = 1.0 => *valve* fully open
"""
assert 0.0 <= position.value and position.value <= 1.0
return self._request_resource('setValvePosition', (valve), 'put',
position.parseToJSON())
#CompositeMeasurement(value=position, timestampMicros=timestamp).parseToJSON())
# Meter functions
def getBackTemperature(self, meter) -> CompositeMeasurement:
return self._request_resource('getBackTemperature', meter)
def getFlow(self, meter) -> CompositeMeasurement:
return self._request_resource('getFlow', meter)
def getThermalPower(self, meter) -> CompositeMeasurement:
return self._request_resource('getThermalPower', meter)
def getFwdTemperature(self, meter) -> CompositeMeasurement:
return self._request_resource('getFwdTemperature', meter)
def getPressure(self, meter) -> CompositeMeasurement:
return self._request_resource('getPressure', meter)
def getVolume(self, meter) -> CompositeMeasurement:
return self._request_resource('getVolume', meter)
def getMass(self, meter) -> CompositeMeasurement:
return self._request_resource('getMass', meter)
def getHeatEnergy(self, meter) -> CompositeMeasurement:
return self._request_resource('getHeatEnergy', meter)
def getCoolingEnergy(self, meter) -> CompositeMeasurement:
return self._request_resource('getCoolingEnergy', meter)
# Pump functions
def getPumpState(self, pump) -> HeatCirculationPumpState:
return self._request_resource('getPumpState', pump)
def getPumpControlMode(self, pump) -> HeatCirculationPumpMode:
return self._request_resource('getPumpControlMode', pump)
def setPumpControlMode(self, pump, mode: HeatCirculationPumpMode):
return self._request_resource('setPumpControlMode', (pump), 'put', mode.parseToJSON())
def setPumpMaxFlow(self, pump, limit: CompositeMeasurement):
return self._request_resource('setPumpMaxFlow', (pump), 'put', limit.parseToJSON())
def startPump(self, pump):
return self._request_resource('startPump', pump)
def stopPump(self, pump):
return self._request_resource('stopPump', pump)
#TODO: Split into three functions that check against current pump mode.
def setPumpSetpoint(self, pump, setpoint: CompositeMeasurement):
"""
Sets the target for the pump.
NOTE: How to interpret the setpoint depends on the mode which the pump is in.
Here be dragons.
"""
return self._request_resource('setPumpSetpoint', (pump), 'put', setpoint.parseToJSON())
def getPumpHead(self, pump) -> CompositeMeasurement:
return self._request_resource('getPumpHead', pump)
def getPumpFlow(self, pump) -> CompositeMeasurement:
return self._request_resource('getPumpFlow', pump)
def getPumpRPM(self, pump) -> CompositeMeasurement:
return self._request_resource('getPumpRPM', pump)
def getPumpRelPerformance(self, pump) -> CompositeMeasurement:
return self._request_resource('getPumpPerformance', pump)

View File

@ -0,0 +1,72 @@
from ..core.SyslabUnit import SyslabUnit
class MeteoMast(SyslabUnit):
__METMASTS = {
'metmast1': ('syslab-13.syslab.dk', '8080', 'meteo1'),
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_Meteo/MeteoStationWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__METMASTS,
host=host,
port=port,
unit_name=unitname,
unit_type="MeteoMast")
def getAirPressure(self, instrumentIndex: int or str):
if type(instrumentIndex) is str:
return self._request_resource('getAirPressure2', instrumentIndex)
if type(instrumentIndex) is int:
return self._request_resource('getAirPressure1', instrumentIndex)
def getAirTemperature(self, instrumentIndex: int or str):
if type(instrumentIndex) is str:
return self._request_resource('getAirTemperature2', instrumentIndex)
if type(instrumentIndex) is int:
return self._request_resource('getAirTemperature1', instrumentIndex)
def getHeightAboveGround(self):
return self._request_resource('getHeightAboveGround')
def getInsolation(self, instrumentIndex: int or str):
if type(instrumentIndex) is str:
return self._request_resource('getInsolation2', instrumentIndex)
if type(instrumentIndex) is int:
return self._request_resource('getInsolation1', instrumentIndex)
def getMeteoGPSLocation(self):
return self._request_resource('getMeteoGPSLocation')
def getRelativeHumidity(self, instrumentIndex: int or str):
if type(instrumentIndex) is str:
return self._request_resource('getRelativeHumidity2', instrumentIndex)
if type(instrumentIndex) is int:
return self._request_resource('getRelativeHumidity1', instrumentIndex)
def getWindDirection(self, instrumentIndex: int or str):
if type(instrumentIndex) is str:
return self._request_resource('getWindDirection2', instrumentIndex)
if type(instrumentIndex) is int:
return self._request_resource('getWindDirection1', instrumentIndex)
def getWindSpeed(self, instrumentIndex: int or str):
if type(instrumentIndex) is str:
return self._request_resource('getWindSpeed2', instrumentIndex)
if type(instrumentIndex) is int:
return self._request_resource('getWindSpeed1', instrumentIndex)
def getInstrumentNames(self):
return self._request_resource('getInstrumentNames')
def getSupportedInstrumentTypes(self):
return self._request_resource('getSupportedInstrumentTypes')
def getInstrumentNamesForType(self, instrumentType: str):
return self._request_resource('getInstrumentNamesForType', instrumentType)

View File

@ -0,0 +1,61 @@
from ..core.SyslabUnit import SyslabUnit
class Photovoltaics(SyslabUnit):
"""The Photovoltaics class represents a photovoltaic panel array in SYSLAB.
The Photovoltaics class is instantiated using a string with the unique name of the dumpload, ie. 'which'
A full list of available panel arrays can be found by calling 'Photovoltaics.getAvailablePhotovoltaics()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
__PHOTOVOLTAICS = {
'pv319': ('syslab-24.syslab.dk', '8080', 'pv319'),
'pv715': ('syslab-10.syslab.dk', '8080', 'pv715'),
'pv117': ('syslab-07.syslab.dk', '8080', 'pv117'),
'simlab-03': ('192.168.0.103', '8080', 'pv715'),
'simlab-13': ('192.168.0.113', '8080', 'pv319'),
'vpv319': ('simlab-24', '8080', 'pv319'),
'vpv715': ('simlab-10', '8080', 'pv715'),
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_PV/PVSystemWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__PHOTOVOLTAICS,
host=host,
port=port,
unit_name=unitname,
unit_type="Photovoltaics")
def getACActivePower(self):
return self._request_resource('getACActivePower')
def getACReactivePower(self):
return self._request_resource('getACReactivePower')
def getPacLimit(self):
return self._request_resource('getPacLimit')
def getQSetpoint(self):
return self._request_resource('getQSetpoint')
def getRatedPower(self):
return self._request_resource('getRatedP')
def getName(self):
return self._request_resource('getSystemName')
def setPacLimit(self, setPoint):
return self._request_resource('setPacLimit', setPoint, 'put')
def setQSetpoint(self, Q):
return self._request_resource('setQ', Q, 'put')
@staticmethod
def getAvailablePhotovoltaics():
return list(Photovoltaics.__PHOTOVOLTAICS.keys())

View File

@ -0,0 +1,144 @@
from ..core.SyslabUnit import SyslabUnit
class SwitchBoard(SyslabUnit):
"""
The SwitchBoard class represents a SwitchBoard in SYSLAB.
The SwitchBoard class is instantiated using a string with the unique name of the switchboard, ie. 'which'
A full list of available switchboards can be found by calling 'SwitchBoard.getAvailableSwitchBoards()'
Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments.
"""
__SWITCH_BOARDS = {
'319-2':('syslab-01.syslab.dk', '8080', '319-2'),
'319-3':('syslab-52.syslab.dk', '8080', '319-3'),
'117-2':('syslab-11.syslab.dk', '8080', '117-2'),
'117-4':('syslab-11.syslab.dk', '8080', '117-4'),
'117-5':('syslab-11.syslab.dk', '8080', '117-5'),
'117-6':('syslab-26.syslab.dk', '8080', '117-6'),
'715-2':('syslab-09.syslab.dk', '8080', '715-2'),
'716-2':('syslab-29.syslab.dk', '8080', '716-2'),
'simlab-00': ('192.168.0.1', '8080', '319-2'),
'simlab-10': ('192.168.0.2', '8080', '319-2'),
'simlab-20': ('192.168.0.3', '8080', '319-2'),
'vswitchboard': ('simlab-01', '8080', '319-2')
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_Substation/StandardSubstationWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__SWITCH_BOARDS,
host=host,
port=port,
unit_name=unitname,
unit_type="SwitchBoard")
def getName(self):
return self._request_resource('getSwitchboardName')
def getActivePower(self, instrName):
return self._request_resource('getActivePower', instrName)
def getPhaseActivePower(self, instrName):
return self._request_resource('getPhaseActivePower', instrName)
def getReactivePower(self, instrName):
return self._request_resource('getReactivePower', instrName)
def getPhaseReactivePower(self, instrName):
return self._request_resource('getPhaseReactivePower', instrName)
def getApparentPower(self, instrName):
return self._request_resource('getApparentPower', instrName)
def getPhaseApparentPower(self, instrName):
return self._request_resource('getPhaseApparentPower', instrName)
def getPowerFactor(self, instrName):
return self._request_resource('getPowerFactor', instrName)
def getPhasePowerFactor(self, instrName):
return self._request_resource('getPhasePowerFactor', instrName)
def getActiveEnergyExport(self, instrName):
return self._request_resource('getActiveEnergyExport', instrName)
def getActiveEnergyImport(self, instrName):
return self._request_resource('getActiveEnergyImport', instrName)
def getReactiveEnergyExport(self, instrName):
return self._request_resource('getReactiveEnergyExport', instrName)
def getReactiveEnergyImport(self, instrName):
return self._request_resource('getReactiveEnergyImport', instrName)
def isAuthenticated(self):
return self._request_resource('isAuthenticated')
def getBreakerState(self, breakerName):
return self._request_resource('getBreakerState', breakerName)
def getFrequency(self, instrName):
return self._request_resource('getFrequency', instrName)
def getBayNames(self):
return self._request_resource('getBayNames')
def getBreakerName(self, busbarName, bayName):
return self._request_resource('getBreakerName', (busbarName, bayName))
def getInstrumentNames(self, bayName):
return self._request_resource('getInstrumentNamesPerBay', bayName)
def getBusbarNames(self):
return self._request_resource('getBusbarNames')
def getInterphaseVoltage(self, instrName):
return self._request_resource('getInterphaseVoltage', instrName)
def getInterphaseVoltages(self, instrName):
return self._request_resource('getInterphaseVoltages', instrName)
def getPhaseVoltage(self, instrName):
return self._request_resource('getPhaseVoltage', instrName)
def getPhaseVoltages(self, instrName):
return self._request_resource('getPhaseVoltages', instrName)
def getVoltageImbalance(self, instrName):
return self._request_resource('getVoltageImbalance', instrName)
def getPhaseCurrent(self, instrName):
return self._request_resource('getPhaseCurrent', instrName)
def getNeutralCurrent(self, instrName):
return self._request_resource('getNeutralCurrent', instrName)
def getPhaseCurrents(self, instrName):
return self._request_resource('getPhaseCurrents', instrName)
def getCurrentImbalance(self, instrName):
return self._request_resource('getCurrentImbalance', instrName)
def authenticate(self, user, password):
return self._request_resource('authenticate', (user, password), 'put')
def logout(self):
return self._request_resource('logout', (), 'put')
def closeBreaker(self, breakerName):
return self._request_resource('closeBreaker', breakerName, 'put')
def openBreaker(self, breakerName):
return self._request_resource('openBreaker', (breakerName), 'put')
@staticmethod
def getAvailableSwitchBoards():
return list(SwitchBoard.__SWITCH_BOARDS.keys())

View File

@ -0,0 +1,36 @@
from ..core.SyslabUnit import SyslabUnit
class WindTurbine(SyslabUnit):
__TURBINES = {
'gaia1': ('syslab-03.syslab.dk', '8080', 'gaia1'),
'simlab-01': ('192.168.0.101', '8080', 'gaia1'),
'vgaia1': ('simlab-03', '8080', 'gaia1'),
}
def __init__(self, which=None, host=None, port=None, unitname=None):
baseurl = 'http://{host}:{port}/typebased_WebService_WTGS/GaiaWindTurbineWebService/{unit_name}/'
super().__init__(
baseurl=baseurl,
which=which,
units=self.__TURBINES,
host=host,
port=port,
unit_name=unitname,
unit_type="WindTurbine")
def getGeneratorName(self):
return self._request_resource('getGeneratorName')
def getName(self):
return self.getGeneratorName()
def getActivePower(self):
return self._request_resource('getActivePower')
def getReactivePower(self):
return self._request_resource('getReactivePower')
def getWindspeed(self):
return self._request_resource('getWindspeedOutsideNacelle')

View File

@ -0,0 +1,10 @@
from .Battery import Battery
from .DieselGenerator import DieselGenerator
from .Dumpload import Dumpload
from .HeatSwitchBoard import HeatSwitchBoard
from .MeteoMast import MeteoMast
from .Photovoltaics import Photovoltaics
from .SwitchBoard import SwitchBoard
from .WindTurbine import WindTurbine
from .B2BConverter import B2BConverter
from .EVSE import EVSE

View File

@ -0,0 +1,47 @@
class FlexHouse_real():
def __init__(self):
pass
def setActivePower(self, power_reference):
if power_reference < -10:
power_reference = -10
if power_reference > 0:
raise ValueError("Positive power means production")
self.__setValue('flexHousePowerRef_kW',-power_reference)
def getTemperature(self):
return self.__getValue('flexHouseTemperature_C')
def getActivePower(self):
return self.__getValue('flexHousePower_kW')
def __getValue(self, key):
from requests import get, auth
import re
if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key)))
if key is '': raise TypeError('Key should be an empty string')
url = 'http://whiteboard.syslab.dk/wbget.php'
r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
entries = r.text.split('\n')
result = None
for entry in entries:
entry = entry.rstrip().lstrip()
g = re.match('SYSLAB@(\d+):{0}=(.+);'.format(key), entry)
if g is None:
continue
else:
g = g.groups()
result = g[1]
break
return result
def __setValue(selv, key, value):
from requests import post, auth
url = '{0}{1}?source=SYSLAB&{2}={3}'.format('http://whiteboard.syslab.dk/', 'wbset.php', key, str(value))
post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))

View File

@ -0,0 +1,51 @@
from ..physical.Dumpload import Dumpload
from ..virtual.MetMast import MetMast
from datetime import datetime
class FlexHouse_sim(Dumpload):
def __init__(self, which='localhost'):
super().__init__(which)
self.internal_temperature = 20 # Celsius
self.time = datetime.now()
self.mt = MetMast()
# The variables below are only relevant for implementing an advanced house model.
#self.__Th = 20 # internal heater state
#self.__Te = 19 # envelope state
#self.__Aw = 14.351 # m^2
#self.__Ce = 4.741 # kWh/C
#self.__Ch = 0.00225 # kWh/Cst
#self.__Ci = 2.555 # kWh/C
#self.__Rea = 3.265 # C/kW
#self.__Rie = 0.817 # C/kW
#self.__Ria = 37.005 # C/kW
#self.__Rih = 140.44 # C/kW
def setPowerConsumption(self, power_reference):
if power_reference > 10:
self.startLoad()
power_reference = 10.0
if power_reference < 0:
raise ValueError("Negative power means production")
if power_reference == 0:
self.stopLoad()
self.setPowerSetPoint(power_reference)
def step_sim(self):
time_now = datetime.now()
time_delta = (time_now - self.time).total_seconds()
T_ambient = self.mt.getTemperature()
irradiance = self.mt.getInsolation()/1000
# Below is a simple house model. A more complex one can be implemented
self.internal_temperature += 0.5*(self.getActivePower().value/3600*time_delta) - 0.00025*((self.internal_temperature-T_ambient)*time_delta) + 0.001*irradiance*time_delta
#self.__Th = 20 # internal heater state
#self.__Te = 19 # envelope state
self.time = time_now
def getTemperature(self):
self.step_sim()
return self.internal_temperature

View File

@ -0,0 +1,52 @@
from ..physical.Battery import Battery
from datetime import datetime
class FlexHouse_sim_batt(Battery):
def __init__(self, which='localhost'):
super().__init__(which)
self.internal_temperature = 20 # Celsius
self.time = datetime.now()
self.loss = 1 # kW
if self.getRemainingFloodTime() == 300:
#print('Battery is not started. I will initiate start sequence, please retry in 5 minutes')
self.startBattery()
raise SystemError('Battery is not started. I will initiate start sequence, please retry in 5 minutes')
if (self.getRemainingFloodTime() > 1) and (self.getRemainingFloodTime() < 300):
#print('Battery is not ready yet')
raise SystemError('Battery is not ready yet, please wait {} seconds'.format(self.getRemainingFloodTime()))
# The variables below are only relevant for implementing and advanced house model.
#self.__Th = 20 # internal heater state
#self.__Te = 19 # envelope state
#self.__Aw = 14.351 # m^2
#self.__Ce = 4.741 # kWh/C
#self.__Ch = 0.00225 # kWh/C
#self.__Ci = 2.555 # kWh/C
#self.__Rea = 3.265 # C/kW
#self.__Rie = 0.817 # C/kW
#self.__Ria = 37.005 # C/kW
#self.__Rih = 140.44 # C/kW
def setPowerConsumption(self, power_reference):
if power_reference > 10:
power_reference = 10
if power_reference < 0:
raise ValueError("Negative power means production")
self.setActivePower(power_reference)
def step_sim(self):
time_now = datetime.now()
time_delta = (time_now - self.time).total_seconds()
# Below is a simple house model. A more complex one can be implemented
self.internal_temperature += 0.5*(self.getActivePower().value/3600*time_delta) - 2*(self.loss/3600*time_delta)
#self.__Th = 20 # internal heater state
#self.__Te = 19 # envelope state
self.time = time_now
def getTemperature(self):
self.step_sim()
return self.internal_temperature

View File

@ -0,0 +1,40 @@
class MetMast():
def __init__(self):
pass
def getTemperature(self):
return self.__getValue('OutsideTemperature')
def getInsolation(self):
return self.__getValue('Insolation')
def getWindDirection(self):
return self.__getValue('WindDirection')
def getWindSpeed(self):
return self.__getValue('WindSpeed')
def __getValue(self, key):
from requests import get, auth
import re
if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key)))
assert key is not '', 'Key should not be an empty string'
url = 'http://whiteboard.syslab.dk/wbget.php'
r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
entries = r.text.split('\n')
result = None
for entry in entries:
entry = entry.rstrip().lstrip()
g = re.match('SYSLAB@(\d+):{0}=(.+);'.format(key), entry)
if g is None:
continue
else:
g = g.groups()
result = g[1]
break
return result

View File

@ -0,0 +1,28 @@
from ..physical.Dumpload import Dumpload
from datetime import datetime
class WaterBoiler(Dumpload):
def __init__(self, which='localhost'):
super().__init__(which)
self.energy_state = 10 # kWh
self.energy_max = 15
self.time = datetime.now()
self.loss = 1 # kW
def step_sim(self):
time_now = datetime.now()
time_delta = (time_now - self.time).total_seconds()
power = self.getActivePower().value
self.energy_state += (power - self.loss)/3600*time_delta
self.time = time_now
def getSOC(self):
self.step_sim()
if self.energy_state > self.energy_max:
self.energy_state = self.energy_max
elif self.energy_state < 0:
self.energy_state = 0.0
return self.energy_state

View File

@ -0,0 +1 @@
from . import *

View File

@ -0,0 +1,143 @@
from syslab.whiteboard.WhiteBoardEntry import WhiteBoardEntry
class CommModule():
__BASE_URL = 'http://whiteboard.syslab.dk/'
def __init__(self, namespace):
if not type(namespace) is str or len(namespace) == 0: raise TypeError('Namespace should be an non-empty string, found "{0}"'.format(type(namespace)))
self.__namespace = namespace
self.__entries = {}
def appendValue(self, key, value):
values = self.getList(key)
if values == None:
self.publishToWhiteBoardServer(key, '[{0}]'.format(str(value)))
else:
values.append(value)
self.publishList(key, values)
def getList(self, key):
from ast import literal_eval
if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key)))
entry = self.getFromWhiteBoardServer(key)
values = None
if entry is not None:
values_str = str(entry.value)
values_str = values_str.replace('[','["').replace(']','"]'.replace(',','","'))
values = literal_eval(values_str)
if type(values) is not list:
values = [values, ]
return values
def getAllEntries(self):
return self.__entries
def update(self):
from requests import get, auth
import re
url = CommModule.__BASE_URL + 'wbget.php'
r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
entries = r.text.split('\n')
result = {}
for entry in entries:
entry = entry.rstrip().lstrip()
g = re.match('SYSLAB@(\d+):{0}::(\w+)=(.+);'.format(self.__namespace), entry)
if g is None:
continue
else:
g = g.groups()
self.__entries[g[1]] = WhiteBoardEntry(g[1], g[2], g[0])
def printEntries(self):
if len(self.__entries)==0:
print('No WhiteBoard entries found for namespace: {0}'.format(self.__namespace))
return
for e in self.__entries:
print(e)
def publishList(self, key, values):
from requests import post, auth
if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key)))
if not type(values) is list: raise TypeError('Values should be represented by a list'.format(type(values)))
values_str = "[{0}]".format(",".join(map(str,values)))
url = '{0}{1}?source=SYSLAB&{2}::{3}={4}'.format(CommModule.__BASE_URL, 'wbset.php', self.__namespace, str(key), values_str)
post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
def getFromWhiteBoardServer(self, key):
"""
Go in and read/write the value from a key from the whiteboard. The value
should use decimal dot, not comma!
"""
from requests import get, auth
import re
if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key)))
if key is '': raise TypeError('Key should be an empty string')
url = CommModule.__BASE_URL+'wbget.php'
r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
entries = r.text.split('\n')
result = None
for entry in entries:
entry = entry.rstrip().lstrip()
g = re.match('SYSLAB@(\d+):{0}::{1}=(.+);'.format(self.__namespace, key), entry)
if g is None:
continue
else:
g = g.groups()
result = WhiteBoardEntry(key, g[1], g[0])
break
return result
def publishToWhiteBoardServer(self, key, value):
from requests import post, auth
if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key)))
if type(value) is list or type(value) is dict or type(value) is tuple: raise TypeError('This function only supports single values, found {0}'.format(type(value)))
url = '{0}{1}?source=SYSLAB&{2}::{3}={4}'.format(CommModule.__BASE_URL, 'wbset.php', self.__namespace, str(key), str(value))
post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
def getPoolKeys(self):
from requests import get, auth
import re
url = CommModule.__BASE_URL+'wbget.php'
r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
entries = r.text.split('\n')
result = {}
for entry in entries:
entry = entry.rstrip().lstrip()
g = re.match('SYSLAB@(\d+):{0}::(\w+)=(.+)'.format(self.__namespace), entry)
if g is None:
continue
else:
g = g.groups()
result[g[1]] = g[2]
return result
def clearKey(self, key):
from requests import post, auth
if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key)))
url = '{0}{1}?source=SYSLAB&key={2}::{3}'.format(CommModule.__BASE_URL, 'wbclean.php', self.__namespace, str(key))
post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
def clearAllKeys(self):
entries = self.getPoolKeys()
for key in entries.keys():
self.clearKey(key)

View File

@ -0,0 +1,21 @@
class WhiteBoardEntry:
def __init__(self, key, value, time):
self.__key = key
self.__value = value
self.__time = int(time)
@property
def key(self):
return self.__key
@property
def value(self):
return self.__value
@property
def time(self):
return self.__time
def __str__(self):
from datetime import datetime
return 'WhiteBoardEntry: {0} -> {1} (@time: {2})'.format(self.key, self.value, datetime.fromtimestamp(self.time))

View File

@ -0,0 +1,23 @@
from bs4 import BeautifulSoup
import requests
def getFromWhiteBoardServer(key):
"""
Go in and read/write the value from a key from the whiteboard. The value
should use decimal dot, not comma!
"""
url = 'http://whiteboard.syslab.dk/wbget.php?mode=html'
r = requests.get(url, auth=requests.auth.HTTPBasicAuth('twinPV99', 'twinPV99'))
data = r.text
print(data)
soup = BeautifulSoup(data)
table = soup.find('table')
key = table.find('td', text=key)
value = key.findNext('td')
return value.text
def publishToWhiteBoardServer(key, value):
url = 'http://whiteboard.syslab.dk/wbset.php?source=SYSLAB&' + key + '=' + str(value)
r = requests.post(url, auth=requests.auth.HTTPBasicAuth('twinPV99', 'twinPV99'))

View File

@ -0,0 +1,3 @@
from .CommModule import CommModule
from .WhiteBoardEntry import WhiteBoardEntry
from .Whiteboard import publishToWhiteBoardServer, getFromWhiteBoardServer

View File

View File

5
util.py Normal file
View File

@ -0,0 +1,5 @@
def clamp(a, x, b):
"""
Restrict x to lie in the range [a, b]
"""
return max(a, min(x, b))