- deleted old brewpi
This commit is contained in:
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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
@@ -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.")
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user