- deleted old brewpi

This commit is contained in:
2022-06-30 18:37:24 +02:00
parent 04cc9f61fd
commit a6e9cc79f4
28 changed files with 0 additions and 12761 deletions
-1
View File
@@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2020-11-21T10:54:52.582Z" agent="5.0 (X11)" etag="b07tUVNTsjMTtKty_VSm" version="13.10.0" type="device"><diagram id="ej0de8njTtl2yoZSF113" name="Page-1">1VvLdqM4EP0aL52DJIPtZdt59JzOnPHE3cn0KgcbGTSNkQeEH/n6ESAZg5SEjjGQbIIKIVBV3dKtktxD0/X+LrQ33p/UwX4PGs6+h657EAJkjvi/RHLIJCPLzARuSBzRKRfMyQsWQkNIY+LgqNCRUeozsikKlzQI8JIVZHYY0l2x24r6xbdubBcrgvnS9lXpE3GYJ2YBh7n8KyauJ98MrHF2Z23LzmImkWc7dHciQjc9NA0pZdnVej/FfqI8qZfsudtX7h4/LMQBq/LAD2cL7MBd/Hd/C53J9gk8YtoXHxuxg5wwdvj8RZOGzKMuDWz/JpdOQhoHDk5GNXgr73NP6YYLARf+ixk7CGPaMaNc5LG1L+6qXy4mE9E4XOI3PhdIF7BDF7M3Og6yfslkTt4gFHOH6Rqz8MA7hNi3GdkWjW0Ln3GP/XK18guh2d/QMhCfs7X9WLzqHpOIxYHLpTOtDe7tBYdSQW+2T9yAXy+53nDIBVscMsJ99Yu4sSaOk5kIR+TFXqTjJUbaUBKwdFbmpGdea23wln8kL8L7ngZw4iUFny5oWDzVN66gCAWVdS4GmyUfz7uICNKXgJKP0NUq4s5QNtLxG86wm6E1zYXhgfeE/ZM8fmWK1s+TO9d7MXLaOIjGGZCqiCjULUSNFETd4Wjp7UjgEPcXJonDPD5HNBmqs+iS/lULvAbGUDwrgYIG5wFOjo2s4rigOQBabQIQnMAvB6MegNzQ4eEEtUnz5+m9/LG01Rxw5ZrZFeQOFeT2oOXzGUwcsuWXbnL5XUDXSF7G2aNsZx258KRvlwFu1QVw4woMZdA74rAmeI+Lw1qDpuANPxn9RBUhJ5lOVyCnIG7OcIzDlH6W9e/7PLVK9LzzCMPzjZ1qZMezu4paVHz9VZ+GY6PgeQiJ9i7PtIBke95JljU2LqQppGjqoTdFvcnUC3c4/NWqtoBV1JZlVdTWqAZt/TXdzPaT2erm24/d30/P5stu9SiTrM/Cjkto/FgIGKghQKsb2Cbg1XRTs8R+xeQljQDV1tQGPd2CLXq6VqHDVjxdUspj44SIXoRSWlXXN6vu9U1PTeCg6AjQKhk4+1Lx1AX4iQqkGxK4nFpq/aEbnFPy6xoopzEGRW4o1sdzGWc5nxwVR7gc4bQUez7gF7xhijkjz94klw5dxutU3+/FvUUG9/tFjYHQNIv+D0yz5UAIUHfW/OMy/05C3lT0BLBi+Kw/I9fjDA2L7mOW3SKbkhI+1YHKC3K5BHvhOAxMBbff8XqDQ5vFIZd/fyaRiuHuhGSJmlrqfMYYoWL8hDXV+YBRGhc2FZeBmqLmBu4/YBf7bSerg1EJTGbb6VeHQnFj6ZcuxmqV027BBSrurKtxnoQwXQ62EqUHn0epNj0fDbqWjh231NvJx4yrsTEqsgpgWu/wirQ1wyHhKkiWnXPJxrgq2WiGa5iDMlVtOFVTtxW6n6pJP66FF1jcBgUTwPNYwTE7a44FjBUbasLmPHY8O47knUWY16z0lSxlhG80WBE35qGX0KBi/asbCaFVrpgbbYdi1E4o/gALaX6HFlatp3Vrhxap2ZY8STFNk638+rgv29kQi2oLsTzzgqCUeYEzY6wYegiLw1pNRVz4WkGs/2Xh2/GqVd5Z3h4EVTOui20PQvXU0Zy7f185elTSWh7vwPuaWxHfn1KfhumzaJX+1aNRIA/fysO4uuUDaDRarrvXF2tgm8vH7xzw+fgygCqf1OnWMqA5NpA4u5q25hSstFtuYML51W3K1joKiVJVBw00FfZmEdHKrvoZ3l35UEztm4bn6Vk96zHnc2AZsZlxn8XZ5ZxxHX4S9z0G+Cbc9/W6W0Grf0TszZihS/H0ceadFK1Gm2jYomKmN+plLa6yrx9GUSP5jO40pcWuqNGCzamRN/Mf4mTkOv85E7r5Hw==</diagram></mxfile>
-480
View File
@@ -1,480 +0,0 @@
S011000048656E64694374726C2E7372656396
S11300000C9434000C9451000C9451000C94510045
S11300100C9451000C9451000C9451000C94510018
S11300200C9451000C9451000C9451000C94510008
S11300300C9451000C94D9060C9451000C9451006A
S11300400C9451000C9451000C9492070C94880762
S11300500C947E070C9482000C9451000C94510073
S11300600C9451000C94510011241FBECFEFD8E022
S1130070DEBFCDBF12E0A0E0B1E0E2E7FCE102C0E8
S113008005900D92A036B107D9F722E0A0E6B2E0C0
S113009001C01D92A13EB207E1F70E94D1020C9467
S11300A0370E0C94000090937102809370022CE13F
S11300B031E041E050E060E170E082E692E00E94CD
S11300C01E01E4E6F0E080818E7F8083EAE7F0E0C1
S11300D08081876880830895ECE7F0E09081907FC9
S11300E090648F70982B9083EAE7F0E09081986495
S11300F090838093720281E090E0909361028093F8
S1130100600208951F920F920FB60F9211242F933D
S11301103F934F935F936F937F938F939F93AF938B
S1130120BF93EF93FF93CF93DF93CDB7DEB72597BC
S1130130DEBFCDBF10926102109260028091780000
S11301402091790090917A00906190937A0092E0E6
S11301509983909172029A8390E0922B9C838B8373
S1130160BE016F5F7F4F80917002909171020E9477
S1130170630182E692E00E94F201892B51F4BE01F0
S11301806B5F7F4F82E692E00E94AC018D810E94FA
S11301906C000F900F900F900F900F90DF91CF9104
S11301A0FF91EF91BF91AF919F918F917F916F914B
S11301B05F914F913F912F910F900FBE0F901F9021
S11301C01895CF93DF931F92CDB7DEB789838091C3
S11301D0600290916102892B21F489810E946C0054
S11301E007C0BE016F5F7F4F82E692E00E94630109
S11301F00F90DF91CF910895F894809173029091BC
S1130200740201969093740280937302089580910E
S1130210730290917402892B81F080917302909102
S11302207402019790937402809373028091730215
S113023090917402892B09F4789408958F929F9277
S1130240AF92BF92CF92DF92EF92FF920F931F93E0
S1130250CF93DF93EC017B014A015901198618867B
S11302601B861A861F821E827B836A835D834C836E
S1130270CB01880F991F0E94DE089983888300E0D0
S113028010E00E151F0581F0C801880F991FC88062
S1130290D980C80ED91EC4010E94DE08F6019183DC
S11302A080830F5F1F4FEDCFBD86AC86DF91CF916A
S11302B01F910F91FF90EF90DF90CF90BF90AF9080
S11302C09F908F9008950F931F93CF93DF93EC012A
S11302D08B010E94FC002E813F818A819B8128171B
S11302E0390768F5D8019D01201B310B8C819D8154
S11302F02817390780F4E885F9854D91EE0FFF1F23
S113030088819981E80FF91F80819181FC01E20FB6
S1130310F31F4083E8CF888599850196998788875C
S11303208E819F8101969F838E83288539858A815A
S11303309B812817390731F41986188603C0C0E059
S1130340D0E002C0C1E0D0E00E940701CE01DF91FD
S1130350CF911F910F9108950F931F93CF93DF9324
S1130360EC018B010E94FC008E819F81892B69F135
S1130370D801CD01801B910B2C813D8182179307FD
S113038080F4EA85FB85EE0FFF1F28813981E20F97
S1130390F31F20813181F901E80FF91F80818D93CA
S11303A0E8CF8A859B8501969B878A878E819F816A
S11303B001979F838E832A853B858A819B81281799
S11303C0390731F41B861A8603C0C0E0D0E002C0AE
S11303D0C1E0D0E00E940701CE01DF91CF911F91CF
S11303E00F910895CF93DF93EC010E94FC000E80DF
S11303F0DF81C02D0E94070181E090E0CD2B11F038
S113040080E090E0DF91CF9108958FEF8093B80062
S11304101092B900089594EA9093BC009091BC00A6
S113042097FFFCCF9091B900987F983019F085E23E
S113043091E017C0880F682B6093BB0084E8809319
S1130440BC008091BC0087FFFCCF8091B900887FFD
S1130450883129F4FA01240F351F94E805C085E397
S113046091E00E94780B14C0E217F30789F08191A0
S11304708093BB009093BC008091BC0087FFFCCFAD
S11304808091B900887F883279F387E491E0E9CFDD
S113049084E98093BC0008950F931F93CF93DF9357
S11304A000D000D0CDB7DEB78C0190937A02809350
S11304B079028091180190911901A0911A01B091CB
S11304C01B0189839A83AB83BC8380917D029091C5
S11304D07E02892BE9F0E1E0F0E00115110511F449
S11304E0E0E0F0E0EE0FFF1F81E090E08C0F9D1F35
S11304F0E80FF91F81818F9380818F938AE591E0C2
S11305009F938F930E94620B0F900F900F900F9008
S113051061E070E0012B11F460E070E087E090E0AE
S11305200F900F900F900F90DF91CF911F910F912B
S11305300C94BA060F931F93CF93DF9300D0CDB7DB
S1130540DEB78C01809375021093760280917D0250
S113055090917E02892B61F01F930F9383E791E0C2
S11305609F938F930E94620B0F900F900F900F90A8
S113057019821A8242E050E0BE016F5F7F4FC801CA
S11305800E949F069C01AE014F5F5F4F60E080E6D2
S11305900E940B020F900F90DF91CF911F910F914A
S11305A00895CF93DF93CDB7DEB76C970FB6F89469
S11305B0DEBF0FBECDBFF894A8950FB6F894A895EA
S11305C080916000886180936000109260000FBE8B
S11305D020E231E044E050E068E070E087EC92E033
S11305E00E941E0140E052EC61E070E087EC92E072
S11305F00E94D60742E06FEF76EF87EC92E00E940C
S11306004E0787EC92E00E9453000E9405020E946C
S1130610B3068AE091E09093DC028093DB0284E3EA
S113062092E09F938F938AE392E09F938F938BE85A
S113063091E09F938F930E94620B1D8A1E8A1F8AEA
S113064043E050E0BE016B5E7F4F80E090E00E948B
S1130650A6069C01AE014B5E5F4F60E080E60E94FF
S11306600B026091DB027091DC028DE090E00E944D
S1130670260B6091DB027091DC028AE090E00E941C
S1130680260B6091DB027091DC028AE390E00E9409
S1130690260B60E070E080E00E94660760E070E096
S11306A082E00E94660778940F900F900F900F904D
S11306B00F900F9072E0F72E212C312CE8EE4E2E85
S11306C0E3E05E2E612C712CF8EB8F2EF1E09F2E6F
S11306D0A2EBCA2EA1E0DA2E87EC92E00E94F2018E
S11306E0892B09F02AC3BE016F5E7F4F87EC92E02D
S11306F00E94AC018989823009F432C2833009F442
S11307005DC2813009F019C38A8920917F0230913A
S11307108002AE014F5F5F4FFA01E20FF31F1082B8
S11307208D3009F008C2598F488F109280021092C0
S11307307F02EF2C8F2D90E0880F991F9C01245D80
S11307403D4F3B8F2A8F6AE9A62E61E0B62E3F2DDE
S11307503D7F3C8F088D198DF80180818823D9F065
S113076090E00E941E0B9C01C8010196998F888F0E
S11307702035310509F055C0F80121812F3329F4C2
S11307800E5F1F4F198F088F31C030E0205331099D
S11307902A303105F0F060E177E281E00E946607DB
S11307A06091DB027091DC028DE090E00E94260BE8
S11307B06091DB027091DC028AE090E00E94260BDB
S11307C06091DB027091DC028AE390E00E94260BC8
S11307D071C24AE050E0BE01685E7F4F0E94FF098B
S11307E0E1E0FE12C5C0CB010E949A026091DB02D7
S11307F07091DC028DE090E00E94260B6091DB0298
S11308007091DC028AE090E00E94260B80917602CF
S11308108F93809175028F9322E931E03F932F9358
S11308206AC123353105B1F5F80121812F3329F44B
S11308300E5F1F4F198F088F14C030E02053310909
S11308402A30310508F0A7CF4AE050E0BE01685EC7
S11308507F4F0E94FF09E1E0FE128AC0CB010E9493
S11308604C026091DB027091DC028DE090E00E940A
S1130870260B6091DB027091DC028AE090E00E941A
S1130880260B80917A028F93809179028F932CEAC0
S113089031E0C4CF2235310509F07DC0F801218152
S11308A02F3309F480C030E0205331092A30310558
S11308B008F071CF4AE050E0BE01685E7F4F0E94AD
S11308C0FF096130710549F5FC8DF11151C08FEFBD
S11308D09FE00E949A0280E090E00E944C026091A6
S11308E0DB027091DC028DE090E00E94260B6091A7
S11308F0DB027091DC028AE090E00E94260B1F92DA
S113090081E08F93DF92CF920E94620B0F900F9041
S11309100F900F90EE24E3941DCF672B09F01ACFAC
S113092021E0F21225C08FEF9FE00E949A0280E03E
S113093090E00E944C026091DB027091DC028DE039
S113094090E00E94260B6091DB027091DC028AE049
S113095090E00E94260B1F921F92DF92CF920E947A
S1130960620B0F900F900F900F9042E0E42EF2CEA6
S11309706091DB027091DC028DE090E00E94260B16
S11309806091DB027091DC028AE090E00E94260B09
S1130990BF92AF92CBC02434310509F048C0F801AE
S11309A021812F33C9F40E5F1F4F198F088F609177
S11309B0DB027091DC028DE090E00E94260B6091D6
S11309C0DB027091DC028AE090E00E94260B81E059
S11309D0E81280E01F9227C030E0205331092A300A
S11309E0310508F0D8CE4AE050E0BE01685E7F4F82
S11309F00E94FF0970937E0260937D026091DB0286
S1130A007091DC028DE090E00E94260B6091DB0285
S1130A107091DC028AE090E00E94260B80917E02B5
S1130A208F9380917D028F93DF92CF9264C029349B
S1130A303105E1F4F80181818F3309F08BCE0E5F2B
S1130A401F4F198F088F6091DB027091DC028DE0DB
S1130A5090E00E94260B6091DB027091DC028AE038
S1130A6090E00E94260B2AE332E01EC026353105B1
S1130A70F1F4F80181818F3309F06CCE0E5F1F4FC2
S1130A80198F088F6091DB027091DC028DE090E099
S1130A900E94260B6091DB027091DC028AE090E0F8
S1130AA00E94260B24E332E03F932F9322C0243587
S1130AB0310541F5F80181818F3309F04BCE0E5F8A
S1130AC01F4F198F088FEA8DFB8D10810181609172
S1130AD0DB027091DC028DE090E00E94260B6091B5
S1130AE0DB027091DC028AE090E00E94260B0F93F7
S1130AF01F939F928F920E94620B0F900F900F9002
S1130B000F9028CE6091DB027091DC028DE090E0C2
S1130B100E94260B6091DB027091DC028AE090E077
S1130B200E94260B2EEB31E03F932F930E94620B21
S1130B300F900F900FCE8A3009F4FFC02F3031058B
S1130B4038F42F5F3F4F3093800220937F028083DD
S1130B506091DB027091DC0290E00E94260BEDC0F4
S1130B608A890B891C89811116C080917702909122
S1130B7078020817190709F4E0C0F11007C0C8018A
S1130B80880F991F880F991F0E949A021093780268
S1130B9000937702D2C0823009F0CFC010937C0258
S1130BA000937B0281E090E00130124014F480E075
S1130BB090E09093090180930801BFC08A89813035
S1130BC049F118F08230A9F1B8C021E0F21203C053
S1130BD024EF31E002C028EE33E0810181E00827F0
S1130BE0232819F462E370E00BC022533109A8EE04
S1130BF0B3E00E94C408A30192010E94A208B901B3
S1130C0080E00E946607B80180E090E00E94BA0686
S1130C10180193C091E0F91290C080917D029091E7
S1130C207E02892B09F486C082ED91E00E94780B44
S1130C3081C062E370E082E00E946607809108014F
S1130C4090910901209106013091070182179307C1
S1130C5011F120917D0230917E02232B61F09F934C
S1130C608F9382EE91E09F938F930E94620B0F907B
S1130C700F900F900F908091080190910901892B9A
S1130C8051F48FEF8F0D833030F481E090E00E94B7
S1130C904C02E12C01C0EF2C8091080190910901D4
S1130CA0909307018093060180E00E94E10082E0B6
S1130CB00E94E100FE14E1F180917D0290917E0298
S1130CC0892B01F1EE2DF0E0EE0FFF1FE45DFD4FE7
S1130CD081818F9380818F93EF2DF0E0EE0FFF1FC2
S1130CE0E45DFD4F81818F9380818F9385EF91E047
S1130CF09F938F930E94620B0F900F900F900F9011
S1130D000F900F90E3E0EE120BC08FEF9FE00E9474
S1130D109A0280E090E00E944C0261E070E004C01E
S1130D20E11006C060E070E082E090E00E94BA0644
S1130D30FE2CD2CC83E0E82EBFCFEF2CF9CF9F70EE
S1130D40FB019083818382E090E00895FB0120E41D
S1130D50208324E0880F991F2A95E1F791838283E9
S1130D6083E090E008959CEF9AB986E084B99BB93A
S1130D7085B90895FC01EE0FFF1F8E0F9F1FFC0124
S1130D80E85BFD4F808121E030E0018002C0220F4A
S1130D90331F0A94E2F7428150E0E82FF0E080A18B
S1130DA04617570711F0282B02C02095282320A3AB
S1130DB008951F920F920FB60F921124DF92EF92B3
S1130DC0FF920F931F932F933F934F935F936F93D0
S1130DD07F938F939F93AF93BF93EF93FF93CF939F
S1130DE0DF9300D000D0CDB7DEB78091C3029091DD
S1130DF0C4029093850080938400E12CF12C93E04D
S1130E00D92E8701000F111F000F111F0D571D4F01
S1130E10F80180819181892BF1F082819381892B62
S1130E2031F08281938101979383828314C0198264
S1130E301A821B821C82D982EA82BE016F5F7F4FB5
S1130E4080918102909182020E946301892B19F0A2
S1130E50F80111821082FFEFEF1AFF0A80E1E81611
S1130E60F10479F60F900F900F900F90DF91CF91CE
S1130E70FF91EF91BF91AF919F918F917F916F916E
S1130E805F914F913F912F911F910F91FF90EF90A0
S1130E90DF900F900FBE0F901F90189590938202D1
S1130EA0809381027093C4026093C302709385009F
S1130EB06093840010928000E1E8F0E08081877004
S1130EC0482B408381E080936F0008950F931F9314
S1130ED0CF93C82F8B010E94FC0084E0C89FF001CF
S1130EE01124ED57FD4F1383028381E090E0918339
S1130EF08083CF911F910F910C9407011F920F9241
S1130F000FB60F9211240F900FBE0F901F901895DB
S1130F101F920F920FB60F9211240F900FBE0F90D5
S1130F201F9018951F920F920FB60F9211242F93B2
S1130F303F934F935F936F937F938F939F93AF935D
S1130F40BF93EF93FF93CF93DF9300D000D0CDB73F
S1130F50DEB78091C00087FF12C08091C60019825D
S1130F601A821B821C8291E099838A83BE016F5F7F
S1130F707F4F8091C5029091C6020E9463010F9039
S1130F800F900F900F90DF91CF91FF91EF91BF9150
S1130F90AF919F918F917F916F915F914F913F910D
S1130FA02F910F900FBE0F901F9018954F925F9244
S1130FB06F927F928F929F92AF92BF92CF92DF9265
S1130FC0EF92FF92CF93DF9300D000D0CDB7DEB77E
S1130FD06A017B019093C6028093C5024A015B01BA
S1130FE083E0880C991CAA1CBB1C8A95D1F760E08D
S1130FF070E88CEF9AE0A50194010E94A208CA014E
S1131000B9016B5F7F4F8F4F9F4F2AE0422E512CC7
S1131010612C712CA30192010E94A20829833A83B6
S11310204B835C8334E0CC0CDD1CEE1CFF1C3A9536
S1131030D1F760E070E88CEF9AE0A70196010E9476
S1131040A208CA01B9016B5F7F4F8F4F9F4FA30165
S113105092010E94A20829013A01A5019401698123
S11310607A818B819C810E949208705477FF03C01F
S11310707195619571094B01770FAA08BB08A70107
S11310809601C301B2010E949208705477FF03C015
S1131090719561957109CB01770FAA0BBB0B88166B
S11310A09906AA06BB0610F0920107C029813A816D
S11310B08091C00082608093C00021503109209348
S11310C0C4003093C50088E18093C1008091C100C1
S11310D080688093C1000F900F900F900F90DF9164
S11310E0CF91FF90EF90DF90CF90BF90AF909F9003
S11310F08F907F906F905F904F9008959091C00073
S113110095FFFCCF8093C6000895CF93C82F0E940B
S11311107E08CA3019F48DE00E947E0880E090E0D9
S1131120CF910895DB018F939F930E94D308BF91C1
S1131130AF91A29F800D911DA39F900DB29F900D22
S113114011240895A1E21A2EAA1BBB1BFD010DC098
S1131150AA1FBB1FEE1FFF1FA217B307E407F50763
S113116020F0A21BB30BE40BF50B661F771F881F3F
S1131170991F1A9469F760957095809590959B01D5
S1131180AC01BD01CF010895A29FB001B39FC0017E
S1131190A39F700D811D1124911DB29F700D811D9F
S11311A01124911D08950E94C408A59F900DB49F19
S11311B0900DA49F800D911D112408950F931F93EA
S11311C0CF93DF938230910510F482E090E0E091B8
S11311D0D702F091D80220E030E0A0E0B0E03097F0
S11311E019F1408151810281138148175907C8F0D0
S11311F08417950769F4109731F012960C9312979F
S113120013961C9327C00093D7021093D80222C0D0
S11312102115310519F04217530718F49A01BD013D
S1131220EF01DF01F801DBCF21153105F9F0281BAF
S1131230390B2430310580F48A819B816115710555
S113124021F0FB019383828304C09093D80280939E
S1131250D702FE01329644C0FE01E20FF31F8193D0
S1131260919322503109398328833AC02091D502C1
S11312703091D602232B41F42091020130910301D5
S11312803093D6022093D5022091000130910101C0
S11312902115310541F42DB73EB740910401509119
S11312A00501241B350BE091D502F091D602E2171B
S11312B0F307A0F42E1B3F0B2817390778F0AC0175
S11312C04E5F5F4F2417350748F04E0F5F1F509352
S11312D0D6024093D5028193919302C0E0E0F0E0FE
S11312E0CF01DF91CF911F910F910895CF93DF9399
S11312F0009709F481C0FC01329713821282A091F5
S1131300D702B091D802109781F420813181820FE5
S1131310931F2091D5023091D6022817390751F531
S1131320F093D602E093D50267C0ED0120E030E0EF
S1131330CE17DF0740F44A815B819E0141155105B8
S1131340F1F0EA01F5CFD383C28340815181840F48
S1131350951FC817D90759F488819981840F951F5F
S11313600296918380838A819B8193838283211552
S1131370310529F4F093D802E093D7023DC0E90186
S1131380FB83EA8349915991C40FD51FEC17FD07DC
S113139061F480819181840F951F0296E9019983FC
S11313A08883828193819B838A83E0E0F0E01296B4
S11313B08D919C911397009719F0FD01DC01F7CFF3
S11313C08D919C9111979D012E5F3F4F820F931F2A
S11313D02091D5023091D6022817390769F4309745
S11313E029F41092D8021092D70202C013821282FA
S11313F0B093D602A093D502DF91CF910895A0E0D7
S1131400B0E0E5E0FAE00C94010E5C016B017A01B6
S11314106115710519F0FB0191838083E114F104D6
S113142029F0C7010297839708F0E5C0E5012196EA
S1131430F5011081812F90E00E94150B892B11F08A
S11314405E01F4CF1D3229F42196F501118101E0EA
S113145007C01B3221F4E5012296F501118100E059
S1131460E114F10409F1F0E1EF16F10429F43FC0AD
S113147010E3E114F10421F128E0E216F10401F192
S113148054F4E2E0EE16F10421F5812C912CA12C08
S1131490B0E4BB2E3EC0FAE0EF16F10439F020E1CF
S11314A0E216F104B1F430C0103319F1FAE0EF2E72
S11314B0F12CACEC8A2E982CA82CACE0BA2E29C0C6
S11314C078E0E72EF12C812C912CA12CE0E1BE2EAA
S11314D020C060E070E080E090E897010F2C000CE1
S11314E0440B550B0E94A20849015A0112C0103343
S11314F059F488818F7D883509F0BACF19812296F5
S1131500026080E1E82EF12C812C912CA12C68E062
S1131510B62E40E060E070E0CB0127010F2C000CF8
S113152066087708FE0150ED352E310E29E02315AB
S113153070F42FEB210F2A3118F439EC332E06C046
S11315402FE9210F2A3118F529EA322E310E232DE5
S113155030E02E153F05DCF447FD16C086169706CD
S1131560A806B90670F0A30192010E949208630DC7
S1131570711D811D911D61307105810520E892075F
S113158010F04FEF01C041E021961081CBCF202F06
S11315902170C114D10471F0442329F02197F6017C
S11315A0D183C08307C001FF19C02297F601D183FC
S11315B0C08314C047FF12C0222329F060E070E00A
S11315C080E090E804C06FEF7FEF8FEF9FE722E2A7
S11315D030E03093E0022093DF0209C0222381F03F
S11315E090958095709561957F4F8F4F9F4F462FB3
S11315F0372F282F12C040E030E020E090E00DC0EB
S113160097FFF5CF82E290E09093E0028093DF02AF
S11316106FEF7FEF8FEF9FE7EACF642F732F822F57
S1131620CDB7DEB7E1E10C941D0E91110C94890D38
S1131630803219F089508550C8F7089591110895A2
S113164081568A5108F48052855808950F931F9348
S1131650CF93DF93FB01238121FD03C08FEF9FEF25
S11316602CC022FF16C04681578124813581421740
S1131670530744F4A081B1819D012F5F3F4F318313
S113168020838C93268137812F5F3F4F37832683B6
S113169014C08B01EC01FB010084F185E02D099558
S11316A0892BE1F6D80116968D919C911797019696
S11316B017969C938E931697CE01DF91CF911F912D
S11316C00F910895A0E0B0E0E8E6FBE00C94100E62
S11316D0AE014B5F5F4FFA0161917191AF0180914F
S11316E0DB029091DC020E94A80BE2E00C942C0E29
S11316F00F931F93CF93DF93E091DB02F091DC0211
S1131700238121FF1BC08C01D0E0C0E0F80181914E
S11317108F016091DB027091DC02DB011896ED9180
S1131720FC911997882331F00995892B79F3DFEF20
S1131730CFEFECCF8AE00995892B19F08FEF9FEF5B
S113174002C08D2F9C2FDF91CF911F910F9108958F
S1131750ABE0B0E0EEEAFBE00C94000E6C017B0120
S11317608A01FC0117821682838181FFCCC1CE01DC
S113177001963C01F6019381F70193FD859193FF56
S113178081917F01882309F4BAC1853239F493FD2C
S1131790859193FF81917F01853229F4B60190E010
S11317A00E94260BE7CF912C212C312CFFE1F3155D
S11317B0D8F08B3279F038F4803279F08332A1F4A6
S11317C0232D20611DC08D3261F0803369F4232DF7
S11317D0216016C0832D8260382EE32DE4603E2EF6
S11317E02AC0F32DF8601DC037FC2DC020ED280F52
S11317F02A3040F08E32B9F436FC81C1232D2064A6
S1131800322E19C036FE06C08AE0989E200D11249F
S1131810922E11C0EAE02E9E200D1124222EF32DCB
S1131820F0623F2E08C08C3621F4832D8068382E58
S113183002C0883641F4F70193FD859193FF8191AD
S11318407F018111B3CF982F9F7D9554933028F455
S11318500C5F1F4F9FE399830DC0833631F08337AC
S113186071F0833509F059C021C0F8018081898362
S11318700E5F1F4F88248394912C530113C02801B9
S1131880F2E04F0E511CF801A080B18036FE03C077
S1131890692D70E002C06FEF7FEFC5010E94970DC4
S11318A04C018201F32DFF773F2E16C0280122E060
S11318B0420E511CF801A080B18036FE03C0692D90
S11318C070E002C06FEF7FEFC5010E948C0D4C01E8
S11318D0F32DF0683F2E820133FC1BC0822D90E073
S11318E088169906B0F4B60180E290E00E94260BB7
S11318F02A94F4CFF50137FC859137FE81915F017D
S1131900B60190E00E94260B21102A9421E0821A4D
S113191091088114910471F7E8C0843611F0893676
S113192041F5F80137FE07C060817181828193819E
S11319300C5F1F4F08C060817181072E000C880B5B
S1131940990B0E5F1F4FF32DFF763F2E97FF09C0B3
S113195090958095709561957F4F8F4F9F4FF0685C
S11319603F2E2AE030E0A3010E94A20D882E8618A3
S113197045C0853731F4232D2F7EB22E2AE030E086
S113198025C0932D997FB92E8F36C1F018F4883570
S113199079F0B5C0803719F0883721F0B0C0E92F4D
S11319A0E061BE2EB4FE0DC0FB2DF460BF2E09C055
S11319B034FE0AC0292F2660B22E06C028E030E08B
S11319C005C020E130E002C020E132E0F801B7FEBA
S11319D007C060817181828193810C5F1F4F06C0B3
S11319E06081718180E090E00E5F1F4FA3010E942F
S11319F0A20D882E8618FB2DFF773F2E36FE0DC0D4
S1131A00232D2E7FA22E891458F434FE0BC032FCF1
S1131A1009C0832D8E7EA82E05C0B82CA32C03C02C
S1131A20B82C01C0B92CA4FE0FC0FE01E80DF11DB5
S1131A308081803321F49A2D997EA92E09C0A2FEBB
S1131A4006C0B394B39404C08A2D867809F0B39485
S1131A50A3FC11C0A0FE06C0B21488F4280C922C7A
S1131A609B180EC0B21460F4B60180E290E00E94AC
S1131A70260BB394F7CFB21418F42B1802C0982C89
S1131A80212CA4FE10C0B60180E390E00E94260B36
S1131A90A2FE17C0A1FC03C088E790E002C088E55D
S1131AA090E0B6010CC08A2D867859F0A1FE02C0E0
S1131AB08BE201C080E2A7FC8DE2B60190E00E94B7
S1131AC0260B891438F4B60180E390E00E94260BBB
S1131AD09A94F7CF8A94F301E80DF11D8081B60141
S1131AE090E00E94260B8110F5CF222009F442CE0B
S1131AF0B60180E290E00E94260B2A94F6CFF6010C
S1131B008681978102C08FEF9FEF2B96E2E10C94C0
S1131B101C0E992788270895FC0105906150704098
S1131B200110D8F7809590958E0F9F1F0895FC01A2
S1131B306150704001900110D8F7809590958E0FF8
S1131B409F1F0895FA01AA27283051F1203181F10D
S1131B50E8946F936E7F6E5F7F4F8F4F9F4FAF4FB1
S1131B60B1E03ED0B4E03CD0670F781F891F9A1FC4
S1131B70A11D680F791F8A1F911DA11D6A0F711D78
S1131B80811D911DA11D20D009F468943F912AE084
S1131B90269F11243019305D3193DEF6CF0108956C
S1131BA0462F4770405D4193B3E00FD0C9F7F6CF9D
S1131BB0462F4F70405D4A3318F0495D31FD405265
S1131BC0419302D0A9F7EACFB4E0A69597958795FB
S1131BD077956795BA95C9F70097610571050895DA
S1131BE09B01AC010A2E0694579547953795279586
S1131BF0BA95C9F7620F731F841F951FA01D08951E
S1131C002F923F924F925F926F927F928F929F9208
S1131C10AF92BF92CF92DF92EF92FF920F931F93F6
S1131C20CF93DF93CDB7DEB7CA1BDB0B0FB6F894A7
S1131C30DEBF0FBECDBF09942A88398848885F84E7
S1131C406E847D848C849B84AA84B984C884DF8058
S1131C50EE80FD800C811B81AA81B981CE0FD11D3C
S1131C600FB6F894DEBF0FBECDBFED010895F89412
S1051C70FFCFA0
S1131C720000E1022000FFFFFFFF0000000200005D
S1131C8200008508000000006C017001436F6E764D
S1131C924669666F004572726F72204932435F5320
S1131CA254415254004572726F72204932435F5359
S1131CB24C415F41434B004572726F72204932437B
S1131CC25F444154415F41434B0053657420737731
S1131CD26974636820746F2025730A004F464600B6
S1131CE24F4E0053657420506F77657220746F20D5
S1131CF22575206469676974730A00257320257346
S1131D020A004F4B3A25303475004552523A496E17
S1131D1276616C6964207374617465004F4B3A2573
S1131D2275004F4B3A2564004F4B3A2573004552D8
S1131D32523A556E6B6E6F776E20636F6D6D616E86
S1131D42640052656D6F74652074696D656F757496
S1131D5221005077725377697463685F696E203D1E
S1131D622025640A005374617465206368616E6798
S1131D72652022257322203D3E20222573220A005B
S1131D824E6F726D616C0052656D6F7465005375B0
S1131D927370656E64004572726F720010021702EE
S1131DA21E02260276312E31320048656E64692D98
S1131DB2436F6E74726F6C000502010B06010B0710
S1131DC2010B02010B03010B04010B0501050101C7
S9030000FC
-39
View File
@@ -1,39 +0,0 @@
{
"Name" : "Rezept-001",
"pot_weight_kg" : 5.960,
"pot_material" : "Edelstahl 18/10",
"Schuettung_kg" : 0,
"Wasser_kg" : 20,
"stirrSpeedHeat" : 50,
"stirrSpeedRast" : 50,
"stirrDutyRast" : 1.0,
"stirrCycleTime" : 120,
"Rasten" :
[
{
"time" : 1,
"temp" : 50.0,
"heatRate" : 1.00,
"waitForUser" : true
},
{
"time" : 1,
"temp" : 60.0,
"heatRate" : 1.00,
"waitForUser" : true
},
{
"time" : 1,
"temp" : 70.0,
"heatRate" : 1.00,
"waitForUser" : true
},
{
"time" : 1,
"temp" : 80.0,
"heatRate" : 1.00,
"waitForUser" : false
}
]
}
-32
View File
@@ -1,32 +0,0 @@
import time
import abc
class APlant(abc.ABC):
def __init__(self, params):
pass
def log(self, s):
print("{:.2f}: {}".format(time.time(), s))
@abc.abstractmethod
def activate(self, enable):
return None
@abc.abstractmethod
def process(self):
pass
@abc.abstractmethod
def setPower(self, power_W):
pass
@abc.abstractmethod
def getPower(self):
return None
@abc.abstractmethod
def getTemperature(self):
return None
-62
View File
@@ -1,62 +0,0 @@
import abc
import logging
class AStirrer(abc.ABC):
@abc.abstractmethod
def name(self):
return ""
def __init__(self, dt):
self.dt = dt
self.speed = 0
self.cycleTime = 1
self.dutyCycle = 1
self.cycleCounter = 0
self.isOn = 1
def log(self, s):
d = {'user': "Stirrer" + "::" + self.name()}
logging.info ("{}".format(s), extra=d)
def process(self):
isOn = self.isOn
self.cycleCounter += self.dt
if self.cycleCounter >= self.cycleTime:
self.cycleCounter -= self.cycleTime
isOn = 0
if self.cycleCounter >= (self.cycleTime * (1-self.dutyCycle)):
if not isOn:
isOn = 1
if self.isOn != isOn:
if isOn:
self.onSetSpeed(self.speed)
else:
self.onSetSpeed(0)
self.isOn = isOn
def setSpeed(self, speed):
self.speed = speed
if self.isOn or self.dutyCycle == 1:
self.onSetSpeed(speed)
def setCycleTime(self, time):
self.cycleTime = time
def setDutyCycle(self, dutyCycle):
self.dutyCycle = dutyCycle
@abc.abstractmethod
def getSpeed(self):
return None
@abc.abstractmethod
def activate(self):
pass
@abc.abstractmethod
def deactivate(self):
pass
-18
View File
@@ -1,18 +0,0 @@
import abc
import logging
class ATemperatureSensor(abc.ABC):
def log(self, s):
d = {'user': "TemperatureSensor" + "::" + self.name()}
logging.info ("{}".format(s), extra=d)
@abc.abstractmethod
def name(self):
return ""
@abc.abstractmethod
def temperature(self):
return None
-52
View File
@@ -1,52 +0,0 @@
{
"Controller" :
{
"dt" : 1.0,
"P_min" : 0,
"P_max" : 3500,
"sim_warp_factor" : 1000.0,
"stirrer_name" : "Stirrer",
"plant_name" : "Mass",
"useKalman" : true,
"kalman" : {
"dt" : 1.0,
"var_P" : 0,
"var_Q" : 0.000001,
"var_R" : 1,
"var_Z" : 0.0
},
"Hold" : {
"Pid" : {
"kp" : 0.4,
"ki" : 0.0001,
"kd" : 0.0,
"rho" : 1.0
}
},
"Heat" : {
"Pid" : {
"kp" : 0.001,
"ki" : 0.0004,
"kd" : 0.0,
"rho" : 1.0
}
}
},
"WaterSim" :
{
"dt" : 1.0,
"theta_amb" : 27,
"C" : 4190,
"M" : 25,
"L" : 0.3,
"Td" : 10,
"kn" : 0.2
},
"Stirrer" :
{
"dt" : 1.0
}
}
-114
View File
@@ -1,114 +0,0 @@
#!/usr/bin/python3
import logging
import argparse
import signal
import json
from controller import Controller, commands
from plant_sim import Plant_sim
from plant import Plant
from stirrer_sim import Stirrer_sim
from stirrer_pololu1376 import Stirrer_pololu1376
from matplotlib.pyplot import figure, clf, plot, xlabel, ylabel, xlim, ylim, title, grid, axes, show, subplot
def results_plot(self):
fp = open("results.dat", 'w')
for n in range(0, len(self.time_v)):
d = self.time_v[n]
v1 = self.theta_v[n]
v2 = self.heatrate_v[n]
v3 = self.power_v[n]
v4 = self.error_v[n]
fp.write("{:6.3f} {:6.3f} {:6.3f} {:6.3f} {:6.3f}\n".format(d, v1, v2, v3, v4,))
fp.close()
figure(1)
subplot(4, 1, 1)
plot(self.time_v, self.theta_v, 'r-', linewidth=1)
title('Temperature')
grid(True)
ylabel('°C')
subplot(4, 1, 2)
plot(self.time_v, self.power_heat_v, self.time_v, self.power_hold_v, self.time_v, self.power_v, 'r-', linewidth=1)
title('Power')
grid(True)
ylabel('W')
subplot(4, 1, 3)
plot(self.time_v, self.error_v, 'b-', linewidth=1)
title('Error')
grid(True)
ylabel('°C')
subplot(4, 1, 4)
plot(self.time_v, self.heatrate_v, 'r-', linewidth=1)
title('Heatrate')
grid(True)
ylabel('°C/min')
xlabel('t/min')
show()
if __name__ == '__main__':
logging.getLogger().setLevel(logging.INFO)
FORMAT = '%(asctime)-15s %(user)-8s %(message)s'
logging.basicConfig(format=FORMAT)
parser = argparse.ArgumentParser(description='Brew some beer.')
parser.add_argument('--receipe', help='the name of the receipe')
parser.add_argument('--config', help='the name of the configuration file')
args = parser.parse_args()
if args.receipe is None:
raise Exception("Invalid receipe")
config = "brewpi.json"
if args.config is not None:
config = args.config
stirr_isOn = False
def handler(signum, frame):
global stirr_isOn
print('Signal handler called with signal', signum)
if signum == signal.SIGINT:
ablauf.command(commands.ABORT)
if signum == signal.SIGSTOP:
ablauf.command(commands.PAUSE)
if signum == signal.SIGUSR1:
ablauf.command(commands.RUN)
if signum == signal.SIGHUP:
if stirr_isOn:
ablauf.command(commands.STIRR_OFF)
stirr_isOn = False
else:
ablauf.command(commands.STIRR_ON)
stirr_isOn = True
# Set the signal handler and a 5-second alarm
signal.signal(signal.SIGABRT, handler)
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGUSR1, handler)
signal.signal(signal.SIGHUP, handler)
fp = open(config)
config = json.load(fp)
plant_class = globals()[config["Controller"]['plant_name']]
plant = plant_class(config["Plant"])
ruehrer_class = globals()[config["Controller"]['stirrer_name']]
ruehrer = ruehrer_class(config["Stirrer"])
fp = open(args.receipe)
recipe = json.load(fp)
ablauf = Controller(config["Controller"], plant, ruehrer, recipe)
ablauf.wait_finished()
results_plot(ablauf)
print ("End of program.")
-54
View File
@@ -1,54 +0,0 @@
{
"Controller" :
{
"dt" : 1.0,
"P_min" : 0,
"P_max" : 3500,
"sim_warp_factor" : 100.0,
"stirrer_name" : "Stirrer_sim",
"plant_name" : "Plant_sim",
"useKalman" : true,
"kalman" : {
"dt" : 1.0,
"var_P" : 0,
"var_Q" : 0.000001,
"var_R" : 1,
"var_Z" : 0.0
},
"Hold" : {
"Pid" : {
"kp" : 0.4,
"ki" : 0.0002,
"kd" : 0.0,
"rho" : 1.0
}
},
"Heat" : {
"Pid" : {
"kp" : 0.002,
"ki" : 0.0002,
"kd" : 0.0,
"rho" : 1.0
}
}
},
"Plant" :
{
"dt" : 1.0,
"theta_amb" : 15,
"C" : 4190,
"M" : 20,
"L" : 0.1,
"Td" : 60,
"kn" : 0.2
},
"Stirrer" :
{
"dt" : 1.0,
"port" : "/dev/ttyACM0",
"speed" : "115200"
}
}
-285
View File
@@ -1,285 +0,0 @@
import numpy as np
import json
from enum import Enum
from pid import Pid
from utils import Smoother, Stable
import time
import threading
from kalman import Kalman
from timer import Timer, TimerManager, TimerListener
import queue
ovenStates = Enum('ovenStates', 'IDLE HEAT HOLD')
states = Enum('controllerStates', 'IDLE NORMAL PAUSE')
commands = Enum('controllerCommands', 'RUN PAUSE ABORT STIRR_OFF STIRR_ON')
class Controller(TimerListener):
def __init__(self, params, plant, stirrer, recipe):
print(json.dumps({'Controller': params}, indent=4, sort_keys=True))
self.sim_warp_factor = params['sim_warp_factor']
self.ovenState = ovenStates.IDLE
self.plant = plant
self.stirrer = stirrer
self.dt = params['dt']
self.params = params
self.pid_hold = Pid()
self.pid_rate = Pid()
self.sensorTime_v = np.empty(0)
self.theta_raw_v = np.empty(0)
self.theta_v = np.empty(0)
self.theta_k_v = np.empty(0)
self.heatrate_v = np.empty(0)
self.heatrate_k_v = np.empty(0)
self.time_v = np.empty(0)
self.power_soll = 0
self.power_v = np.empty(0)
self.power_heat_v = np.empty(0)
self.power_hold_v = np.empty(0)
self.error_v = np.empty(0)
self.time = 0
self.report_interval = 1.0
self.thread = None
self.receipe_sema = threading.Semaphore(0)
self.receipe = recipe
self.rasten = recipe['Rasten']
self.rasten.append(None)
self.theta_sm = Smoother(1.0)
self.heatrate_sm = Smoother(0.01)
self.theta_err_sm = Smoother(1.0)
self.heatrate_err_sm = Smoother(1.0)
self.useKalman = params['useKalman']
self.kalman = Kalman(params['kalman'])
self.sensor_dt = params['kalman']['dt']
self.timerMgr = TimerManager()
self.processTimer = Timer("Process", self)
self.rastTimer = Timer("Rast", self)
self.sensorTimer = Timer("Sensor", self)
self.plantTimer = Timer("Plant", self)
self.reportTimer = Timer("Report", self)
self.timerMgr.registerTimer(self.processTimer)
self.timerMgr.registerTimer(self.rastTimer)
self.timerMgr.registerTimer(self.sensorTimer)
self.timerMgr.registerTimer(self.plantTimer)
self.timerMgr.registerTimer(self.reportTimer)
self.power_hold_offset = 200
self.power_heat_offset = 1500
self.queue = queue.Queue()
self.rast_done = False
self.receipe_done = False
self.thread = threading.Thread(target=self.main)
self.thread.start()
self.fp_log = None
# Stirrer
self.stirrSpeedHeat = recipe['stirrSpeedHeat']
self.stirrSpeedRast = recipe['stirrSpeedRast']
self.stirrDutyRast = recipe['stirrDutyRast']
self.stirrCycleTime = recipe['stirrCycleTime']
print(json.dumps({recipe['Name'] : recipe}, indent=4, sort_keys=True))
def command(self, cmd):
self.queue.put_nowait(cmd)
def onTimer(self, timer):
if timer == self.sensorTimer:
self.sensorTime_v = np.append(self.sensorTime_v, timer.count*self.sensor_dt / 60)
# Temperature
self.theta_raw = self.plant.getTemperature()
if timer.count == 0:
# Kalman filter initial value
self.kalman.initial((self.theta_raw, 0))
self.theta_sm.initial(self.theta_raw)
self.theta_raw_v = np.append(self.theta_raw_v, self.theta_raw)
# Process Kalman
Z = self.kalman.process_measurement((self.theta_raw, 0))
xp = self.kalman.process(Z)
self.theta_ist_k = xp[0, 0]
self.heatrate_ist_k = xp[1, 0] * 60
self.theta_k_v = np.append(self.theta_k_v, self.theta_ist_k)
self.heatrate_k_v = np.append(self.heatrate_k_v, self.heatrate_ist_k)
elif timer == self.plantTimer:
# Process plant
self.plant.process()
# Process stirrer
self.stirrer.process()
elif timer == self.reportTimer:
self.report()
elif timer == self.processTimer:
rast = self.rasten[self.rast_index]
if rast is not None:
self.ctrl_theta_soll = rast['temp']
self.ctrl_heatrate_soll = rast['heatRate']
isFinished = self.rast(rast)
if isFinished:
if not self.rastTimer.isActive and not self.rast_done:
rast_timer_soll = 60 * rast['time']
self.log ("Start rast timer with {} min.".format(rast_timer_soll/60))
self.rastTimer.start(rast_timer_soll / self.sim_warp_factor, mode='oneshot')
self.rast_done = True
else:
self.receipe_done = True
elif timer == self.rastTimer:
self.log("Rast timer elapsed")
rast = self.rasten[self.rast_index]
if not rast["waitForUser"]:
self.rast_index += 1
self.rast_done = False
self.ovenState = ovenStates.IDLE
else:
self.log("Wait user input!")
def log(self, s):
now = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
print("{}: {}".format(now, s))
self.fp_log.write("{}: {}\n".format(now, s))
def report(self):
self.log("Temp SOLL {} °C".format(self.ctrl_theta_soll))
self.log("Temp IST = {:0.1f} °C".format(self.ctrl_theta))
self.log("Heatrate SOLL = {:0.1f} °C/min.".format(self.ctrl_heatrate_soll))
self.log("Heatrate IST = {:0.1f} °C/min.".format(self.ctrl_heatrate))
self.log("Power = {:0.0f} W".format(self.power_soll))
if self.rastTimer.isActive:
self.log("Rast remaining : {:0.1f} min.".format(self.rastTimer.elapseDuration / 60))
def wait_finished(self):
self.receipe_sema.acquire()
pass
def main(self):
self.sensorTimer.start(self.sensor_dt/self.sim_warp_factor)
self.plantTimer.start(1/self.sim_warp_factor)
self.stirrer.deactivate()
self.plant.activate(False)
run = True
while(run and not self.receipe_done):
self.timerMgr.process()
wait = self.timerMgr.getMinTimeout(1.0/self.sim_warp_factor)
try:
msg = self.queue.get(block=True, timeout=wait)
print ("Received: ", msg)
except:
continue
if msg == commands.PAUSE:
pass
elif msg == commands.RUN:
self.stirrer.activate()
self.plant.activate(True)
if not self.processTimer.isActive:
self.fp_log = open(self.receipe['Name'] + '.log', "w")
self.time = 0
self.rast_index = 0
self.reportTimer.start(self.report_interval)
self.processTimer.start(self.dt / self.sim_warp_factor)
# Stirrer and plant
self.stirrer.setCycleTime(self.stirrCycleTime)
self.receipe_done = False
else:
self.rast_index += 1
self.rast_done = False
self.ovenState = ovenStates.IDLE
elif msg == commands.ABORT:
run = False
elif msg == commands.STIRR_ON:
self.stirrer.activate()
elif msg == commands.STIRR_OFF:
self.stirrer.deactivate()
# Stirrer and plant
self.stirrer.deactivate()
self.plant.activate(False)
self.fp_log.close()
self.receipe_sema.release()
def rast(self, rast):
# Temperature
self.ctrl_theta = self.theta_ist_k
self.ctrl_heatrate = self.heatrate_ist_k
ovenStateNext = self.ovenState
# -----------------------------------------
ctrl_theta = self.ctrl_theta
ctrl_heatrate = self.ctrl_heatrate
ctrl_theta_err = self.ctrl_theta_soll - ctrl_theta
ctrl_heatrate_err = self.ctrl_heatrate_soll - ctrl_heatrate
if self.ovenState != ovenStates.IDLE:
self.pid_hold.process(self.dt, self.params['Hold']['Pid'], ctrl_theta_err)
self.pid_rate.process(self.dt, self.params['Heat']['Pid'], ctrl_heatrate_err)
power_hold = self.power_hold_offset + self.params['P_max'] * self.pid_hold.get_y()
power_heat = self.power_heat_offset + self.params['P_max'] * self.pid_rate.get_y()
power_hold = max(self.params['P_min'], min(self.params['P_max'], power_hold))
power_heat = max(self.params['P_min'], min(self.params['P_max'], power_heat))
self.power_soll = power_hold
if self.ovenState == ovenStates.HEAT:
self.power_soll = power_heat
self.plant.setPower(self.power_soll)
self.time_v = np.append(self.time_v, self.time/60)
self.theta_v = np.append(self.theta_v, ctrl_theta)
self.heatrate_v = np.append(self.heatrate_v, ctrl_heatrate)
self.power_v = np.append(self.power_v, self.power_soll)
self.power_hold_v = np.append(self.power_hold_v, power_hold)
self.power_heat_v = np.append(self.power_heat_v, power_heat)
self.error_v = np.append(self.error_v, ctrl_theta_err)
self.time += self.dt
# -----------------------------------------
rast_finished = False
if self.ovenState == ovenStates.IDLE:
if (ctrl_theta + 1.0) < self.ctrl_theta_soll:
ovenStateNext = ovenStates.HEAT
else:
ovenStateNext = ovenStates.HOLD
if self.ovenState == ovenStates.HEAT:
if (ctrl_theta + 1.0) >= self.ctrl_theta_soll:
ovenStateNext = ovenStates.HOLD
if ovenStateNext != self.ovenState:
self.log("{} -> {}".format(self.ovenState, ovenStateNext))
if ovenStateNext == ovenStates.HEAT:
self.pid_rate.reset()
self.stirrer.setSpeed(self.stirrSpeedHeat)
self.stirrer.setDutyCycle(1.0)
if self.ovenState == ovenStates.HOLD:
self.power_hold_offset = power_hold
if ovenStateNext == ovenStates.HOLD:
self.pid_hold.reset()
self.stirrer.setSpeed(self.stirrSpeedRast)
self.stirrer.setDutyCycle(self.stirrDutyRast)
if self.ovenState == ovenStates.HEAT:
self.power_heat_offset = power_heat
# ------------------------------
self.rast_running = True
self.ovenState = ovenStateNext
return self.ovenState == ovenStates.HOLD
-47
View File
@@ -1,47 +0,0 @@
#!/usr/bin/python3
#from core import MouseEvent
import wx
import colorsys as cs
class myGui(wx.Frame):
def __init__(self, *args, **kwargs):
super(myGui, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fileItem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.OnQuit, fileItem)
self.Bind(wx.EVT_MOUSE_EVENTS, self.onMouse)
self.SetSize((300, 200))
self.SetTitle('Simple menu')
self.Centre()
def OnQuit(self, e):
self.Close()
def onMouse(self, e):
wxm = wx.MouseEvent(e)
rgb_f = cs.hsv_to_rgb(float(wxm.Position[0])/300, 1.0, float(wxm.Position[1])/200)
color = wx.Colour(int(rgb_f[0]*255), int(rgb_f[1]*255), int(rgb_f[2]*255))
self.SetBackgroundColour(color)
def main():
app = wx.App()
ex = myGui(None)
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
-230
View File
@@ -1,230 +0,0 @@
#!/usr/bin/python3
import time
import serial
import sys
import numpy as np
import logging
class HendiCtrl():
def name(self):
return "HendiCtrl"
def log(self, s):
d = {'user': self.name()}
logging.info ("{}".format(s), extra=d)
def __init__(self, port, baudrate):
self.ser = serial.Serial(port, baudrate)
try:
self.ser.open()
except:
self.ser.close()
self.ser.open()
self.ser.timeout = 1.000
self.prompt = b':'
self.caps = {
"sw_id" : self.getSoftwareIdentifier(),
"sw_ver" : self.getSoftwareVersion(),
"pwr_watts_min" : 500,
"pwr_watts_max" : 3500,
"pwr_watts_step" : 100,
"pwr_digits_min" : 4092,
"pwr_digits_max" : 670
}
powers = [ 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300,3400, 3500]
digits = [4092, 3840, 3700, 3440, 3300, 3060, 3000, 2930, 2860, 2780, 2640, 2510, 2430, 2360, 2190, 2130, 2030, 1980, 1880, 1730, 1630, 1560, 1480, 1400, 1240, 1150, 1150, 1080, 920, 740, 670]
self.poly_w2d = np.polyfit(powers, digits, 5)
self.poly_d2w = np.polyfit(digits, powers, 5)
self.sw_id = self.getSoftwareIdentifier()
self.sw_ver = self.getSoftwareVersion()
self.log("Create {}, Version {}".format(self.sw_id, self.sw_ver))
def toDigits(self, power_watts):
return np.polyval(self.poly_w2d, float(power_watts))
def toWatts(self, power_digits):
return np.polyval(self.poly_d2w, float(power_digits))
def reset(self):
self.ser.dtr = True
self.ser.rts = False
time.sleep(0.01)
self.ser.dtr = True
self.ser.rts = True
def enter_bootloader(self):
self.ser.dtr = True
self.ser.rts = False
time.sleep(0.01)
self.ser.dtr = False
self.ser.rts = True
def firmware_update(self, filename):
print("Start firmware update")
self.enter_bootloader()
self.ser.readline()
self.ser.flushInput()
with open(filename, 'r') as fp:
line_count = 0
while (True):
s = fp.readline()
if s == '':
break
data = s.encode()
self.ser.write(data)
ack = self.ser.read()
sys.stdout.write(ack.decode('utf-8'))
sys.stdout.flush()
if line_count == 64:
line_count = 0
sys.stdout.write("\n")
line_count += 1
self.ser.readline()
self.ser.flushInput()
self.ser.write(b'q')
self.ser.flushOutput()
self._debugEnable()
def __write(self, s):
self.ser.flushInput()
data = s.encode()
self.ser.write(data + b'\r')
def __read(self):
# Read line echo
echo = self.ser.readline()
# Read answer line
answer = self.ser.readline().decode('utf-8').replace('\r', '').replace('\n', '')
if ':' in answer:
result = answer.split(':')
else:
result = answer
return result
def cmd(self, req):
self.__write(req)
rsp = self.__read()
if "OK" not in rsp[0]:
raise Exception(rsp[1])
return rsp[1]
def getCapabilties(self):
return self.caps
def getInfo(self):
result = {
'caps' : self.caps,
'state' : self.getState(),
'pwr_digits' : self.getPowerDigits(),
'Switch_state' : self.getSwitch()
}
return result
def _debugEnable(self, enable):
return self.cmd("D" + str(int(enable)))
def _isDebugEnable(self):
return self.cmd("D?")
def getSoftwareIdentifier(self):
return self.cmd("I?")
def getSoftwareVersion(self):
return self.cmd("V?")
def getState(self):
return self.cmd("T?")
def remoteEnable(self, enable):
return self.cmd("R" + str(int(enable)))
def isRemoteEnable(self):
return '1' in self.cmd("R?")
def setPowerDigits(self, power_digits):
return self.cmd("P" + str(power_digits))
def getPowerDigits(self):
return self.cmd("P?")
def setSwitch(self, enable):
return self.cmd("S" + str(int(enable)))
def getSwitch(self):
return self.cmd("S?")
def setPowerWatts(self, power_watts):
pwr_digits_min = self.caps['pwr_digits_min']
pwr_digits_max = self.caps['pwr_digits_max']
max_digits = max(pwr_digits_max, pwr_digits_min)
min_digits = min(pwr_digits_max, pwr_digits_min)
pwr_digits = int(self.toDigits(power_watts))
pwr_digits = int(min(max_digits, max(min_digits, pwr_digits)))
return self.setPowerDigits(pwr_digits)
def getPowerWatts(self):
return int(self.toWatts(self.getPowerDigits()) + 0.5)
if __name__ == '__main__':
logging.getLogger().setLevel(logging.INFO)
FORMAT = '%(asctime)-15s %(user)-8s %(message)s'
logging.basicConfig(format=FORMAT)
hendi = HendiCtrl('/dev/ttyUSB0', 115200)
# hendi.firmware_update("HendiCtrl.srec")
# hendi.reset()
time.sleep(1)
hendi.getState()
hendi.getSoftwareIdentifier()
hendi.getSoftwareVersion()
print ("Test converters")
for pwr in range(500, 3600, 100):
print("pwr", pwr)
digits = hendi.toDigits(pwr)
print("digits", digits)
power = hendi.toWatts(digits)
print("power", power)
print ("Test Hendi")
hendi.remoteEnable(True)
hendi.setSwitch(1)
time.sleep(1.0)
for pwr in range(500, 3600, 100):
hendi.setPowerWatts(pwr)
pw = hendi.getPowerWatts()
print("power_w", pw)
time.sleep(1.0)
hendi.setSwitch(0)
hendi.remoteEnable(False)
if 0:
hendi.remoteEnable(True)
if hendi.isRemoteEnable():
hendi.setSwitch(True)
for p in range(0, 4096, 100):
hendi.setPowerDigits(p)
time.sleep(1)
hendi.setSwitch(False)
hendi.remoteEnable(False)
print ("End of program.")
-143
View File
@@ -1,143 +0,0 @@
import numpy as np
from numpy.linalg import inv
from matplotlib.pyplot import plot, figure, subplot, title, xlabel, ylabel, grid, show
class Kalman():
def __init__(self, params={}):
dt = params['dt']
var_P = params['var_P']
var_Q = params['var_Q']
var_R = params['var_R']
var_Z = params['var_Z']
model = np.matrix([1, dt, 1/2*dt**2]).transpose()
N = len(model)-1
P = var_P*np.eye(N)
R = var_R*np.eye(N)
H = np.eye(N)
A = np.eye(N)
for row in range(0, N):
A[row, row:N] = model.transpose()[0, 0:N-row]
G = np.matrix(model[N:0:-1])
Q = G * G.transpose() * var_Q
self.P = P
self.Q = Q
self.R = R
self.N = N
self.A = A
self.H = H
self.var_Z = var_Z
X = np.matrix([0, 1.0]).transpose()
Xp = np.matrix([0, 0]).transpose()
self.Xp = Xp
self.X = X
np.set_printoptions(precision=3)
@staticmethod
def print(p, d):
print(p)
print(d)
def initial(self, X):
self.Xp = np.matrix([X[0], X[1]]).transpose()
def process_truth(self):
# ----------------------------
# Process ground truth
self.X = self.A * self.X
return self.X
def process_measurement(self, y):
Y = np.matrix([y[0], y[1]]).transpose()
# ----------------------------
# Take noisy measurement
Z = self.H * Y + self.var_Z * np.random.randn(self.N, 1)
return Z
def process(self, Z):
# ----------------------------
# State estimate
self.Xp = self.A * self.Xp
# ----------------------------
# Measurement prediction
Zp = self.H * self.Xp
# ----------------------------
# Measurement residual
V = Z - Zp
# ----------------------------
# State prediction covariance
self.P = self.A * self.P * self.A.transpose() + self.Q
# ----------------------------
# Measurement prediction covariance
S = self.H * self.P * self.H.transpose() + self.R
# ----------------------------
# Kalman gain
K = self.P * self.H.transpose() * inv(S)
# ----------------------------
# Update state estimate
self.Xp = self.Xp + K * V
# ----------------------------
# Updated state covariance
self.P = self.P - K * S * K
return self.Xp
# Main
if __name__ == '__main__':
dt =1
params = {
'dt' : dt,
'var_P' : 1,
'var_Q' : 0,
'var_R' : 1,
'var_Z' : 0
}
k = Kalman(params)
_x1 = np.empty(0)
_y1 = np.empty(0)
_x2 = np.empty(0)
_y2 = np.empty(0)
N = int(100/dt)
seqn = range(0, N)
for n in seqn:
X = k.process_truth()
Z = k.process_measurement((X[0,0], 0))
#Kalman.print("Z:", Z)
Xp = k.process(Z)
_x1 = np.append(_x1, Z[0])
_x2 = np.append(_x2, Z[1])
_y1 = np.append(_y1, Xp[0])
_y2 = np.append(_y2, Xp[1])
figure(1)
subplot(2, 1, 1)
plot(seqn, _x1, 'bx', seqn, _y1, '-r', linewidth=1)
grid(True)
subplot(2, 1, 2)
plot(seqn, _x2, 'bx', seqn, _y2, '-r', linewidth=1)
grid(True)
show()
print("End of program")
-49
View File
@@ -1,49 +0,0 @@
import numpy as np
class Pid():
def __init__(self):
# Integrator
self.yi = 0
# Differentiator
self.xd = 0
# Auto windup
self.y_min = -1.0
self.y_max = 1.0
# Output
self.y = 0
@staticmethod
def params(kp, ki, kd, rho):
p = dict(kp=kp, ki=ki, kd=kd, rho=rho)
return p
def reset(self):
self.yi = 0
self.xd = 0
def process(self, dt, params, err):
kp = params['kp']
ki = params['ki']
kd = params['kd']
rho = params['rho']
yi = rho*self.yi + ki*dt * err
yd = err - self.xd
_yp = kp * err
_yi = yi
_yd = kd/dt * yd
y = _yp + _yi + _yd
self.y = max(self.y_min, min(self.y_max, y))
self.yi = yi
self.xd = err
def get_y(self):
return self.y
-65
View File
@@ -1,65 +0,0 @@
import numpy as np
import json
from aplant import APlant
from math import sqrt
from hendiCtrl import HendiCtrl
from tempSensor_max31865 import TempSensor_max31865 as TempSensor
class Plant(APlant):
def __init__(self, params):
self.heater = HendiCtrl('/dev/ttyUSB0', 115200)
self.tempsensor = TempSensor()
self.P = 0
self.isOn = False
self.cycleTime = 30
self.dutyCycle = 1
self.cycleCounter = 0
def activate(self, enable=True):
if enable:
if not self.heater.isRemoteEnable():
try:
self.heater.remoteEnable(enable)
except:
raise Exception("Error: Could not activate heater!")
self.heater.setSwitch(enable)
print ("Plant activated")
else:
if self.heater.isRemoteEnable():
self.heater.setSwitch(enable)
self.heater.remoteEnable(enable)
print ("Plant deactivated")
def process(self, dt=1):
if not self.heater.isRemoteEnable():
return
isOn = self.isOn
self.cycleCounter += dt
if self.cycleCounter >= self.cycleTime:
self.cycleCounter -= self.cycleTime
isOn = 0
if self.cycleCounter >= (self.cycleTime * (1-self.dutyCycle)):
if not isOn:
isOn = 1
if self.isOn != isOn:
if isOn:
self.heater.setSwitch(True)
else:
self.heater.setSwitch(False)
self.isOn = isOn
def setPower(self, power_w):
self.P = power_w
self.heater.setPowerWatts(max(500, power_w))
self.dutyCycle = min(1, power_w / 500)
def getPower(self):
return self.heater.getPowerWatts()
def getTemperature(self):
return self.tempsensor.temperature()
-60
View File
@@ -1,60 +0,0 @@
import numpy as np
import json
from aplant import APlant
from math import sqrt
class Plant_sim(APlant):
def __init__(self, params):
print(json.dumps({'Plant_sim': params}, indent=4, sort_keys=True))
self.dt = params['dt']
self.alpha = 1.0
self.e = 0
self.x = 0
self.gain = 0.999
self.C = params['C']
self.M = params['M']
self.L = params['L']
self.Td = params['Td']
self.kn = params['kn']
self.theta_amb = params['theta_amb']
self.theta = 0
self.P = 0
self.alpha = self.dt/1
self.Nd = int(self.Td/self.dt)
self.delay = np.zeros((self.Nd, 1))
self.ri = 0
self.wi = 0
def activate(self, enable):
pass
def process(self):
if self.ri >= self.delay.size:
self.ri = 0
P = self.delay[self.ri][0]
self.ri += 1
self.e = self.e*(1 - ((self.L*self.theta)*self.dt)/(self.M*self.C))
self.x = (1-self.alpha)*self.x + self.gain*self.alpha*P
self.e += self.x
self.theta = self.e/(self.M*self.C)
def setPower(self, power_w):
k = int(power_w / 100 +0.5)
P = k * 100
if self.wi >= self.delay.size:
self.wi = 0
self.delay[self.wi] = P
self.wi += 1
self.P = P
def getPower(self):
return self.P
def getTemperature(self):
return round(self.theta + self.theta_amb + self.kn*np.random.normal(0,1)/sqrt(12.0), 1)
-104
View File
@@ -1,104 +0,0 @@
#!/usr/bin/python3
from numpy import cos, sin, pi, absolute, arange
from scipy.signal import kaiserord, lfilter, firwin, freqz
from matplotlib.pyplot import figure, clf, plot, xlabel, ylabel, xlim, ylim, title, grid, axes, show
#------------------------------------------------
# Create a signal for demonstration.
#------------------------------------------------
sample_rate = 100.0
nsamples = 400
t = arange(nsamples) / sample_rate
x = cos(2*pi*0.5*t) + 0.2*sin(2*pi*2.5*t+0.1) + \
0.2*sin(2*pi*15.3*t) + 0.1*sin(2*pi*16.7*t + 0.1) + \
0.1*sin(2*pi*23.45*t+.8)
#------------------------------------------------
# Create a FIR filter and apply it to x.
#------------------------------------------------
# The Nyquist rate of the signal.
nyq_rate = sample_rate / 2.0
# The desired width of the transition from pass to stop,
# relative to the Nyquist rate. We'll design the filter
# with a 5 Hz transition width.
width = 5.0/nyq_rate
# The desired attenuation in the stop band, in dB.
ripple_db = 60.0
# Compute the order and Kaiser parameter for the FIR filter.
N, beta = kaiserord(ripple_db, width)
# The cutoff frequency of the filter.
cutoff_hz = 10.0
# Use firwin with a Kaiser window to create a lowpass FIR filter.
taps = firwin(N, cutoff_hz/nyq_rate, window=('kaiser', beta))
# Use lfilter to filter x with the FIR filter.
filtered_x = lfilter(taps, 1.0, x)
#------------------------------------------------
# Plot the FIR filter coefficients.
#------------------------------------------------
figure(1)
plot(taps, 'bo-', linewidth=2)
title('Filter Coefficients (%d taps)' % N)
grid(True)
#------------------------------------------------
# Plot the magnitude response of the filter.
#------------------------------------------------
figure(2)
clf()
w, h = freqz(taps, worN=8000)
plot((w/pi)*nyq_rate, absolute(h), linewidth=2)
xlabel('Frequency (Hz)')
ylabel('Gain')
title('Frequency Response')
ylim(-0.05, 1.05)
grid(True)
# Upper inset plot.
ax1 = axes([0.42, 0.6, .45, .25])
plot((w/pi)*nyq_rate, absolute(h), linewidth=2)
xlim(0,8.0)
ylim(0.9985, 1.001)
grid(True)
# Lower inset plot
ax2 = axes([0.42, 0.25, .45, .25])
plot((w/pi)*nyq_rate, absolute(h), linewidth=2)
xlim(12.0, 20.0)
ylim(0.0, 0.0025)
grid(True)
#------------------------------------------------
# Plot the original and filtered signals.
#------------------------------------------------
# The phase delay of the filtered signal.
delay = 0.5 * (N-1) / sample_rate
figure(3)
# Plot the original signal.
plot(t, x)
# Plot the filtered signal, shifted to compensate for the phase delay.
plot(t-delay, filtered_x, 'r-')
# Plot just the "good" part of the filtered signal. The first N-1
# samples are "corrupted" by the initial conditions.
plot(t[N-1:]-delay, filtered_x[N-1:], 'g', linewidth=4)
xlabel('t')
grid(True)
show()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/python3
import time
import serial
import sys
def reset():
ser.dtr = True
ser.rts = False
time.sleep(0.05)
ser.dtr = True
ser.rts = True
def enter_bootloader():
ser.dtr = True
ser.rts = False
time.sleep(0.05)
ser.dtr = False
ser.rts = True
if __name__ == '__main__':
ser = serial.Serial ('/dev/ttyUSB0', 115200)
try:
ser.open()
except:
ser.close()
ser.open()
if 0:
ser.timeout = 1.000
while(True):
print(ser.readline().decode('utf-8'), end='')
if 1:
ser.timeout = 1.000
print("Start firmware update")
enter_bootloader()
ser.readline()
ser.flushInput()
with open("HendiCtrl.srec", 'r') as fp:
while(True):
s = fp.readline()
if s == '':
break
data = s.encode()
ser.write(data)
ack = ser.read()
sys.stdout.write(ack.decode('utf-8'))
sys.stdout.flush()
ser.readline()
ser.flushInput()
reset()
print("\nStart application")
print(ser.readline().decode('utf-8'))
ser.close()
print ("End of program.")
-84
View File
@@ -1,84 +0,0 @@
from astirrer import AStirrer
import serial
import time
import serial
class Stirrer_pololu1376(AStirrer):
def name(self):
return "Pololu1376"
def __init__(self, params):
super(Stirrer_pololu1376, self).__init__(params["dt"])
self.isMasterOn = 0
self.ser_speed = params["speed"]
self.ser_port = params["port"]
self.ser = serial.Serial(self.ser_port, self.ser_speed)
self.ser.set_output_flow_control(False)
try:
self.ser.open()
except:
self.ser.close()
self.ser.open()
self.ser_send('V')
self.ser_recv()
self.log("Created")
def ser_send(self, cmd):
self.ser.write((cmd + '\r\n').encode())
def ser_recv(self):
s = self.ser.readline().decode("utf-8").replace("\n", '').replace("\r", '')
return s
def getSpeed(self):
if self.isOn and self.isMasterOn:
return self.speed
return 0
def activate(self):
if self.isMasterOn == 0:
self.log("Switched On")
self.isMasterOn = 1
self.ser_send("go")
def deactivate(self):
if self.isMasterOn == 1:
self.log("Switched Off")
self.isMasterOn = 0
self.ser_send("x")
def onSetSpeed(self, speed):
self.ser_send("F" + str(speed) + "%")
self.log("Set speed to {} %".format(speed))
if __name__ == '__main__':
s = Stirrer_pololu1376({'dt': 1.0, "port":"/dev/ttyACM0", "speed":"115200"})
s.activate()
print ("Set some speeds")
s.setSpeed(50)
time.sleep(2)
s.setSpeed(20)
time.sleep(2)
s.setSpeed(50)
time.sleep(2)
print ("Pulsed operation, duty cycle = 50%")
s.setDutyCycle(0.5)
s.setCycleTime(10)
for i in range(1,30):
s.process()
time.sleep(1)
print ("Pulsed operation, duty cycle = 20%")
s.setDutyCycle(0.2)
s.setCycleTime(10)
for i in range(1,30):
s.process()
time.sleep(1)
s.deactivate()
-61
View File
@@ -1,61 +0,0 @@
from astirrer import AStirrer
import time
class Stirrer_sim(AStirrer):
def name(self):
return "FakeStirrer"
def __init__(self, params):
super(Stirrer_sim, self).__init__(params['dt'])
self.isMasterOn = 0
self.log("Created")
def __del__(self):
self.deactivate()
def activate(self):
if self.isMasterOn == 0:
self.log("Switched On")
self.isMasterOn = 1
def deactivate(self):
if self.isMasterOn == 1:
self.log("Switched Off")
self.isMasterOn = 0
def getSpeed(self):
if self.isOn and self.isMasterOn:
return self.speed
return 0
def onSetSpeed(self, speed):
self.log("Set speed to {} %".format(speed))
if __name__ == '__main__':
s = Stirrer_sim({'dt' : 1.0})
s.activate()
print ("Set some speeds")
s.setSpeed(50)
time.sleep(2)
s.setSpeed(100)
time.sleep(2)
s.setSpeed(50)
time.sleep(2)
print ("Pulsed operation, duty cycle = 50%")
s.setDutyCycle(0.5)
s.setCycleTime(10)
for i in range(1,30):
s.process()
time.sleep(1)
print ("Pulsed operation, duty cycle = 20%")
s.setDutyCycle(0.2)
s.setCycleTime(10)
for i in range(1,30):
s.process()
time.sleep(1)
s.deactivate()
-40
View File
@@ -1,40 +0,0 @@
{
"Name" : "Sud-0010",
"Description" : "Rotfraenkisch, Dunkles Lager",
"pot_weight_kg" : 5.960,
"pot_material" : "Edelstahl 18/10",
"Schuettung_kg" : 5.21,
"Wasser_kg" : 22,
"stirrSpeedHeat" : 30,
"stirrSpeedRast" : 30,
"stirrDutyRast" : 1.0,
"stirrCycleTime" : 120,
"Rasten" :
[
{
"time" : 0,
"temp" : 57.0,
"heatRate" : 1.00,
"waitForUser" : true
},
{
"time" : 40,
"temp" : 63.0,
"heatRate" : 1.00,
"waitForUser" : false
},
{
"time" : 30,
"temp" : 72.0,
"heatRate" : 1.00,
"waitForUser" : true
},
{
"time" : 0,
"temp" : 76.0,
"heatRate" : 1.00,
"waitForUser" : true
}
]
}
-99
View File
@@ -1,99 +0,0 @@
#!/usr/bin/python3
import time
import numpy as np
from matplotlib.pyplot import figure, clf, plot, xlabel, ylabel, xlim, ylim, title, grid, axes, show, subplot
import signal
import threading
import logging
from tempSensorSim import TempSensorSim as TempSensor
#from tempSensor_max31865 import TempSensor_max31865 as TempSensor
class TempLogger():
def __init__(self, sensor, filename="temp.log"):
self.filename = filename
self.sensor = sensor
self.temps = np.empty(0)
self.times = np.empty(0)
self.time = 0
self.abort = False
self.thread = None
self.sema = threading.Semaphore(0)
def start(self, dt, duration):
try:
if self.thread == None:
self.thread = threading.Thread(target=self.run, args=(dt, duration,))
self.thread.start()
except:
self.sema.release()
def stop(self):
if self.thread:
self.abort = True
self.thread.join()
self.thread = None
def run(self, *args):
dt = args[0]
duration = args[1]
fp = open(self.filename, 'w')
while self.time < duration:
temp = self.sensor.temperature()
print ("Current temperature is {:0.2f} °C".format(temp))
self.times = np.append(self.times, self.time)
self.temps = np.append(self.temps, temp)
fp.write("{:0.2f} {:0.2f}\n".format(self.time, temp))
self.time += dt
time.sleep(dt)
if self.abort:
break
fp.close()
self.sema.release()
def wait(self):
if self.thread:
self.sema.acquire()
pass
def result(self):
return self.times, self.temps
if __name__ == '__main__':
def tempPlot(result):
figure(1)
plot(result[0]/60, result[1], 'b-', linewidth=1)
ylabel('°C/min')
xlabel('t/min')
show()
def handler(signum, frame):
print('Signal handler called with signal', signum)
if signum == signal.SIGINT:
logger.stop()
if signum == signal.SIGHUP:
tempPlot(logger.result())
logging.getLogger().setLevel(logging.INFO)
FORMAT = '%(asctime)-15s %(user)-8s %(message)s'
logging.basicConfig(format=FORMAT)
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGHUP, handler)
sensor = TempSensor()
logger = TempLogger(sensor)
logger.start(1.0, 30*60)
logger.wait()
tempPlot(logger.result())
print("End of program")
-17
View File
@@ -1,17 +0,0 @@
from atemperatureSensor import ATemperatureSensor
import math
class TempSensorSim(ATemperatureSensor):
def name(self):
return "FakeTemp"
def __init__(self):
self.count = 0
self.freq = 0.2
self.log("Created")
def temperature(self):
self.count += 1
return 22.37 + 0.1*math.sin(2*math.pi*self.count*self.freq)
-118
View File
@@ -1,118 +0,0 @@
#!/usr/bin/python3
import time
import spidev
from atemperatureSensor import ATemperatureSensor
class TempSensor_max31865(ATemperatureSensor):
def name(self):
return "Max31865"
def __init__(self):
# Open SPI bus
self.spi = spidev.SpiDev()
self.spi.open(0, 0)
self.spi.max_speed_hz = 50000
self.spi.mode = 0b01
self.temp_correction = -0.2
self.write_reg(0x00, 0xA3)
time.sleep(0.100)
self.digits = 0
self.log("Created")
def read_reg(self, addr):
reg = self.spi.xfer([addr, 0xFF])
return reg[1]
def write_reg(self, addr, data):
self.spi.xfer([addr+0x80, data])
def read_digits(self):
msb = self.read_reg(0x01)
lsb = self.read_reg(0x02)
if (lsb & 0x01 == 0x00):
self.digits = float(256*msb + lsb)/2
self.write_reg(0x00, 0xA3)
return self.digits
def temperature(self):
return self.__tempRaw()
def __tempRaw(self):
try:
digits = self.read_digits()
except:
return None
R = TempSensor_max31865.calc_R(430, digits)
T = TempSensor_max31865.vanDusen_temp(R) + self.temp_correction
# print("R={:.3f} Ohm, T={:.2f} degC".format(R, T))
return T
@staticmethod
def calc_R(Rref, digits):
k = digits / 8192 / 4
R = k*Rref
return R
@staticmethod
def vanDusenLut (R0, Tmin, Tmax, dT):
a = +3.90830e-03
b = -5.77500e-07
c = -4.18301e-12
Rv = []
Tv = []
T = Tmin
while T <= Tmax:
R = R0*(1 + a*T + b*T**2)
if T < 0.0:
R += R0*c*(T - 100)*T**3
Rv.append(R)
Tv.append(T)
T += dT
return Rv, Tv
@staticmethod
def vanDusen_temp(Rmeas):
RLut, TLut = TempSensor_max31865.vanDusenLut(100, -150, +500, 1.0)
# Find candidate
i = 0
for R in RLut:
if R > Rmeas:
break
i += 1
# Linear interpolation
R0 = RLut[i-1]
R1 = RLut[i]
T0 = TLut[i-1]
T1 = TLut[i]
dR = R1 - R0
dT = T1 - T0
k = (Rmeas-R0)/dR
T = T0 + dT*k
return T
# Main
if __name__ == '__main__':
TempSensor_max31865 = TempSensor_max31865()
for i in range (0, 8):
print("Reg[0x{}]: 0x{:02X}".format(i, TempSensor_max31865.read_reg(i)))
TempSensor_max31865.temperature()
print("End of program")
-111
View File
@@ -1,111 +0,0 @@
import time
import abc
class Timer(object):
def __init__(self, name='Timer', listener=None):
self.name = name
self.listener = listener
self.isActive = False
self.mode = 'periodic'
self.interval = 0
self.elapseTime = 0
self.elapseDuration = 0
self.count = 0
def start(self, interval, mode='periodic'):
now = time.time()
self.interval = interval
self.mode = mode
self.elapseTime = now + interval
self.isActive = self.elapseTime >= now
pass
def stop(self):
self.isActive = False
self.count = 0
def __str__(self):
return self.name
def isElapsed(self):
result = False
now = time.time()
self.elapseDuration = max(0, self.elapseTime - now)
if self.isActive:
if self.elapseDuration == 0:
result = True
self.isActive = False
if 'periodic' in self.mode:
self.start(self.interval, self.mode)
return result
class TimerListener(abc.ABC):
def __init__(self):
pass
@abc.abstractmethod
def onTimer(self, timer):
pass
class TimerManager(object):
def __init__(self):
self.timers = []
pass
def registerTimer(self, timer):
self.timers.append(timer)
def process(self):
for timer in self.timers:
if timer.isElapsed():
if timer.listener is not None:
timer.listener.onTimer(timer)
timer.count += 1
def getMinTimeout(self, default=1.0):
timeouts = []
for timer in self.timers:
now = time.time()
if timer.isActive:
timeout = max(0, timer.elapseTime-now)
timeouts.append(timeout)
if len(timeouts) > 0:
minTimeout = min(timeouts)
return max(default, minTimeout)
if __name__ == '__main__':
class Test (TimerListener):
def __init__(self):
self.timerMgr = TimerManager()
self.timer200 = Timer('Timer 200', self)
self.timer500 = Timer('Timer 500', self)
self.timer1000 = Timer('Timer 1000', self)
self.timerMgr.registerTimer(self.timer200)
self.timerMgr.registerTimer(self.timer500)
self.timerMgr.registerTimer(self.timer1000)
self.timer200.start(0.200)
self.timer500.start(0.500)
self.timer1000.start(1.000)
pass
def onTimer(self, timer):
print ("{}, Count: {}".format(timer, timer.count))
if timer.count == 30:
timer.stop()
def run(self):
while(True):
wait = self.timerMgr.getMinTimeout(None)
if wait is None:
break
time.sleep(wait)
self.timerMgr.process()
test = Test()
test.run()
-37
View File
@@ -1,37 +0,0 @@
class Smoother:
def __init__(self, alpha):
self.a = alpha
self.b = 1-alpha
self.y = 0
def initial(self, ic):
self.y = ic
def process(self, x):
self.y = self.b*self.y + self.a*x
return self.y
def get_y(self):
return self.y
class Stable:
def __init__(self, stable_count, err_max):
self.stable_time = stable_count
self.count = stable_count
self.err_max = err_max
self.is_stable = 0
self.x = 0
def process(self, x):
if self.count > 0:
self.count -= 1
if abs(x - self.x) >= self.err_max:
self.count = self.stable_time
self.is_stable = (self.count == 0)
return self.is_stable
def is_stable(self):
return self.is_stable