From 7cb53cc8f1c6950efabb6c5b8e8414e76fde2ae4 Mon Sep 17 00:00:00 2001 From: Jeff Curless Date: Sun, 1 Feb 2026 16:14:00 -0500 Subject: [PATCH] Add the default set python flles that Argon40 uses. The files being uploaded are the current versions of the files from argon40. These files are being archived as I want to be able to reference them from time to time. Also I can compare the files that were ship with the proto type. --- pythonscript/kickstarter/argon-uninstall.sh | 180 ++++ pythonscript/kickstarter/argon-verioninfo.sh | 14 + pythonscript/kickstarter/argondashboard.py | 374 ++++++++ pythonscript/kickstarter/argonkeyboard.py | 817 ++++++++++++++++++ .../kickstarter/argononeup-eepromconfig.py | 568 ++++++++++++ .../kickstarter/argononeup-lidconfig.sh | 114 +++ pythonscript/kickstarter/argononeup.sh | 471 ++++++++++ pythonscript/kickstarter/argononeupd.py | 466 ++++++++++ pythonscript/kickstarter/argononeupd.service | 10 + .../kickstarter/argononeupduser.service | 10 + .../kickstarter/argonpowerbutton_libgpiod.py | 206 +++++ .../kickstarter/argonpowerbutton_rpigpio.py | 156 ++++ pythonscript/kickstarter/argonregister.py | 74 ++ pythonscript/kickstarter/argonsysinfo.py | 394 +++++++++ pythonscript/kickstarter/getfiles.sh | 40 + pythonscript/kickstarter/ups/charge_0.png | Bin 0 -> 408 bytes pythonscript/kickstarter/ups/charge_1.png | Bin 0 -> 404 bytes pythonscript/kickstarter/ups/charge_10.png | Bin 0 -> 422 bytes pythonscript/kickstarter/ups/charge_100.png | Bin 0 -> 388 bytes pythonscript/kickstarter/ups/charge_11.png | Bin 0 -> 410 bytes pythonscript/kickstarter/ups/charge_12.png | Bin 0 -> 417 bytes pythonscript/kickstarter/ups/charge_13.png | Bin 0 -> 424 bytes pythonscript/kickstarter/ups/charge_14.png | Bin 0 -> 415 bytes pythonscript/kickstarter/ups/charge_15.png | Bin 0 -> 424 bytes pythonscript/kickstarter/ups/charge_16.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_17.png | Bin 0 -> 410 bytes pythonscript/kickstarter/ups/charge_18.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_19.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_2.png | Bin 0 -> 407 bytes pythonscript/kickstarter/ups/charge_20.png | Bin 0 -> 424 bytes pythonscript/kickstarter/ups/charge_21.png | Bin 0 -> 418 bytes pythonscript/kickstarter/ups/charge_22.png | Bin 0 -> 418 bytes pythonscript/kickstarter/ups/charge_23.png | Bin 0 -> 431 bytes pythonscript/kickstarter/ups/charge_24.png | Bin 0 -> 421 bytes pythonscript/kickstarter/ups/charge_25.png | Bin 0 -> 430 bytes pythonscript/kickstarter/ups/charge_26.png | Bin 0 -> 427 bytes pythonscript/kickstarter/ups/charge_27.png | Bin 0 -> 421 bytes pythonscript/kickstarter/ups/charge_28.png | Bin 0 -> 429 bytes pythonscript/kickstarter/ups/charge_29.png | Bin 0 -> 423 bytes pythonscript/kickstarter/ups/charge_3.png | Bin 0 -> 410 bytes pythonscript/kickstarter/ups/charge_30.png | Bin 0 -> 425 bytes pythonscript/kickstarter/ups/charge_31.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_32.png | Bin 0 -> 426 bytes pythonscript/kickstarter/ups/charge_33.png | Bin 0 -> 421 bytes pythonscript/kickstarter/ups/charge_34.png | Bin 0 -> 428 bytes pythonscript/kickstarter/ups/charge_35.png | Bin 0 -> 430 bytes pythonscript/kickstarter/ups/charge_36.png | Bin 0 -> 427 bytes pythonscript/kickstarter/ups/charge_37.png | Bin 0 -> 423 bytes pythonscript/kickstarter/ups/charge_38.png | Bin 0 -> 432 bytes pythonscript/kickstarter/ups/charge_39.png | Bin 0 -> 426 bytes pythonscript/kickstarter/ups/charge_4.png | Bin 0 -> 414 bytes pythonscript/kickstarter/ups/charge_40.png | Bin 0 -> 427 bytes pythonscript/kickstarter/ups/charge_41.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_42.png | Bin 0 -> 430 bytes pythonscript/kickstarter/ups/charge_43.png | Bin 0 -> 436 bytes pythonscript/kickstarter/ups/charge_44.png | Bin 0 -> 424 bytes pythonscript/kickstarter/ups/charge_45.png | Bin 0 -> 430 bytes pythonscript/kickstarter/ups/charge_46.png | Bin 0 -> 429 bytes pythonscript/kickstarter/ups/charge_47.png | Bin 0 -> 423 bytes pythonscript/kickstarter/ups/charge_48.png | Bin 0 -> 434 bytes pythonscript/kickstarter/ups/charge_49.png | Bin 0 -> 431 bytes pythonscript/kickstarter/ups/charge_5.png | Bin 0 -> 411 bytes pythonscript/kickstarter/ups/charge_50.png | Bin 0 -> 426 bytes pythonscript/kickstarter/ups/charge_51.png | Bin 0 -> 421 bytes pythonscript/kickstarter/ups/charge_52.png | Bin 0 -> 424 bytes pythonscript/kickstarter/ups/charge_53.png | Bin 0 -> 426 bytes pythonscript/kickstarter/ups/charge_54.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_55.png | Bin 0 -> 422 bytes pythonscript/kickstarter/ups/charge_56.png | Bin 0 -> 428 bytes pythonscript/kickstarter/ups/charge_57.png | Bin 0 -> 415 bytes pythonscript/kickstarter/ups/charge_58.png | Bin 0 -> 418 bytes pythonscript/kickstarter/ups/charge_59.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_6.png | Bin 0 -> 406 bytes pythonscript/kickstarter/ups/charge_60.png | Bin 0 -> 414 bytes pythonscript/kickstarter/ups/charge_61.png | Bin 0 -> 411 bytes pythonscript/kickstarter/ups/charge_62.png | Bin 0 -> 412 bytes pythonscript/kickstarter/ups/charge_63.png | Bin 0 -> 423 bytes pythonscript/kickstarter/ups/charge_64.png | Bin 0 -> 409 bytes pythonscript/kickstarter/ups/charge_65.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_66.png | Bin 0 -> 409 bytes pythonscript/kickstarter/ups/charge_67.png | Bin 0 -> 415 bytes pythonscript/kickstarter/ups/charge_68.png | Bin 0 -> 422 bytes pythonscript/kickstarter/ups/charge_69.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_7.png | Bin 0 -> 405 bytes pythonscript/kickstarter/ups/charge_70.png | Bin 0 -> 417 bytes pythonscript/kickstarter/ups/charge_71.png | Bin 0 -> 415 bytes pythonscript/kickstarter/ups/charge_72.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_73.png | Bin 0 -> 418 bytes pythonscript/kickstarter/ups/charge_74.png | Bin 0 -> 409 bytes pythonscript/kickstarter/ups/charge_75.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_76.png | Bin 0 -> 423 bytes pythonscript/kickstarter/ups/charge_77.png | Bin 0 -> 408 bytes pythonscript/kickstarter/ups/charge_78.png | Bin 0 -> 423 bytes pythonscript/kickstarter/ups/charge_79.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_8.png | Bin 0 -> 404 bytes pythonscript/kickstarter/ups/charge_80.png | Bin 0 -> 418 bytes pythonscript/kickstarter/ups/charge_81.png | Bin 0 -> 417 bytes pythonscript/kickstarter/ups/charge_82.png | Bin 0 -> 423 bytes pythonscript/kickstarter/ups/charge_83.png | Bin 0 -> 422 bytes pythonscript/kickstarter/ups/charge_84.png | Bin 0 -> 422 bytes pythonscript/kickstarter/ups/charge_85.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_86.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_87.png | Bin 0 -> 424 bytes pythonscript/kickstarter/ups/charge_88.png | Bin 0 -> 404 bytes pythonscript/kickstarter/ups/charge_89.png | Bin 0 -> 413 bytes pythonscript/kickstarter/ups/charge_9.png | Bin 0 -> 409 bytes pythonscript/kickstarter/ups/charge_90.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_91.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_92.png | Bin 0 -> 421 bytes pythonscript/kickstarter/ups/charge_93.png | Bin 0 -> 419 bytes pythonscript/kickstarter/ups/charge_94.png | Bin 0 -> 417 bytes pythonscript/kickstarter/ups/charge_95.png | Bin 0 -> 420 bytes pythonscript/kickstarter/ups/charge_96.png | Bin 0 -> 410 bytes pythonscript/kickstarter/ups/charge_97.png | Bin 0 -> 406 bytes pythonscript/kickstarter/ups/charge_98.png | Bin 0 -> 413 bytes pythonscript/kickstarter/ups/charge_99.png | Bin 0 -> 403 bytes pythonscript/kickstarter/ups/discharge_0.png | Bin 0 -> 323 bytes pythonscript/kickstarter/ups/discharge_1.png | Bin 0 -> 322 bytes pythonscript/kickstarter/ups/discharge_10.png | Bin 0 -> 336 bytes .../kickstarter/ups/discharge_100.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_11.png | Bin 0 -> 329 bytes pythonscript/kickstarter/ups/discharge_12.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_13.png | Bin 0 -> 342 bytes pythonscript/kickstarter/ups/discharge_14.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_15.png | Bin 0 -> 342 bytes pythonscript/kickstarter/ups/discharge_16.png | Bin 0 -> 339 bytes pythonscript/kickstarter/ups/discharge_17.png | Bin 0 -> 332 bytes pythonscript/kickstarter/ups/discharge_18.png | Bin 0 -> 337 bytes pythonscript/kickstarter/ups/discharge_19.png | Bin 0 -> 339 bytes pythonscript/kickstarter/ups/discharge_2.png | Bin 0 -> 325 bytes pythonscript/kickstarter/ups/discharge_20.png | Bin 0 -> 341 bytes pythonscript/kickstarter/ups/discharge_21.png | Bin 0 -> 333 bytes pythonscript/kickstarter/ups/discharge_22.png | Bin 0 -> 336 bytes pythonscript/kickstarter/ups/discharge_23.png | Bin 0 -> 348 bytes pythonscript/kickstarter/ups/discharge_24.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_25.png | Bin 0 -> 347 bytes pythonscript/kickstarter/ups/discharge_26.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_27.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_28.png | Bin 0 -> 347 bytes pythonscript/kickstarter/ups/discharge_29.png | Bin 0 -> 341 bytes pythonscript/kickstarter/ups/discharge_3.png | Bin 0 -> 327 bytes pythonscript/kickstarter/ups/discharge_30.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_31.png | Bin 0 -> 340 bytes pythonscript/kickstarter/ups/discharge_32.png | Bin 0 -> 344 bytes pythonscript/kickstarter/ups/discharge_33.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_34.png | Bin 0 -> 346 bytes pythonscript/kickstarter/ups/discharge_35.png | Bin 0 -> 346 bytes pythonscript/kickstarter/ups/discharge_36.png | Bin 0 -> 346 bytes pythonscript/kickstarter/ups/discharge_37.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_38.png | Bin 0 -> 345 bytes pythonscript/kickstarter/ups/discharge_39.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_4.png | Bin 0 -> 331 bytes pythonscript/kickstarter/ups/discharge_40.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_41.png | Bin 0 -> 336 bytes pythonscript/kickstarter/ups/discharge_42.png | Bin 0 -> 347 bytes pythonscript/kickstarter/ups/discharge_43.png | Bin 0 -> 348 bytes pythonscript/kickstarter/ups/discharge_44.png | Bin 0 -> 340 bytes pythonscript/kickstarter/ups/discharge_45.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_46.png | Bin 0 -> 342 bytes pythonscript/kickstarter/ups/discharge_47.png | Bin 0 -> 337 bytes pythonscript/kickstarter/ups/discharge_48.png | Bin 0 -> 347 bytes pythonscript/kickstarter/ups/discharge_49.png | Bin 0 -> 346 bytes pythonscript/kickstarter/ups/discharge_5.png | Bin 0 -> 327 bytes pythonscript/kickstarter/ups/discharge_50.png | Bin 0 -> 339 bytes pythonscript/kickstarter/ups/discharge_51.png | Bin 0 -> 339 bytes pythonscript/kickstarter/ups/discharge_52.png | Bin 0 -> 341 bytes pythonscript/kickstarter/ups/discharge_53.png | Bin 0 -> 344 bytes pythonscript/kickstarter/ups/discharge_54.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_55.png | Bin 0 -> 340 bytes pythonscript/kickstarter/ups/discharge_56.png | Bin 0 -> 348 bytes pythonscript/kickstarter/ups/discharge_57.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_58.png | Bin 0 -> 340 bytes pythonscript/kickstarter/ups/discharge_59.png | Bin 0 -> 342 bytes pythonscript/kickstarter/ups/discharge_6.png | Bin 0 -> 323 bytes pythonscript/kickstarter/ups/discharge_60.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_61.png | Bin 0 -> 333 bytes pythonscript/kickstarter/ups/discharge_62.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_63.png | Bin 0 -> 344 bytes pythonscript/kickstarter/ups/discharge_64.png | Bin 0 -> 331 bytes pythonscript/kickstarter/ups/discharge_65.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_66.png | Bin 0 -> 332 bytes pythonscript/kickstarter/ups/discharge_67.png | Bin 0 -> 337 bytes pythonscript/kickstarter/ups/discharge_68.png | Bin 0 -> 340 bytes pythonscript/kickstarter/ups/discharge_69.png | Bin 0 -> 337 bytes pythonscript/kickstarter/ups/discharge_7.png | Bin 0 -> 322 bytes pythonscript/kickstarter/ups/discharge_70.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_71.png | Bin 0 -> 333 bytes pythonscript/kickstarter/ups/discharge_72.png | Bin 0 -> 336 bytes pythonscript/kickstarter/ups/discharge_73.png | Bin 0 -> 336 bytes pythonscript/kickstarter/ups/discharge_74.png | Bin 0 -> 328 bytes pythonscript/kickstarter/ups/discharge_75.png | Bin 0 -> 336 bytes pythonscript/kickstarter/ups/discharge_76.png | Bin 0 -> 339 bytes pythonscript/kickstarter/ups/discharge_77.png | Bin 0 -> 324 bytes pythonscript/kickstarter/ups/discharge_78.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_79.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_8.png | Bin 0 -> 318 bytes pythonscript/kickstarter/ups/discharge_80.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_81.png | Bin 0 -> 332 bytes pythonscript/kickstarter/ups/discharge_82.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_83.png | Bin 0 -> 337 bytes pythonscript/kickstarter/ups/discharge_84.png | Bin 0 -> 337 bytes pythonscript/kickstarter/ups/discharge_85.png | Bin 0 -> 335 bytes pythonscript/kickstarter/ups/discharge_86.png | Bin 0 -> 334 bytes pythonscript/kickstarter/ups/discharge_87.png | Bin 0 -> 340 bytes pythonscript/kickstarter/ups/discharge_88.png | Bin 0 -> 324 bytes pythonscript/kickstarter/ups/discharge_89.png | Bin 0 -> 333 bytes pythonscript/kickstarter/ups/discharge_9.png | Bin 0 -> 322 bytes pythonscript/kickstarter/ups/discharge_90.png | Bin 0 -> 339 bytes pythonscript/kickstarter/ups/discharge_91.png | Bin 0 -> 339 bytes pythonscript/kickstarter/ups/discharge_92.png | Bin 0 -> 342 bytes pythonscript/kickstarter/ups/discharge_93.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_94.png | Bin 0 -> 342 bytes pythonscript/kickstarter/ups/discharge_95.png | Bin 0 -> 343 bytes pythonscript/kickstarter/ups/discharge_96.png | Bin 0 -> 336 bytes pythonscript/kickstarter/ups/discharge_97.png | Bin 0 -> 333 bytes pythonscript/kickstarter/ups/discharge_98.png | Bin 0 -> 338 bytes pythonscript/kickstarter/ups/discharge_99.png | Bin 0 -> 329 bytes pythonscript/kickstarter/ups/loading_0.png | Bin 0 -> 328 bytes 218 files changed, 3894 insertions(+) create mode 100644 pythonscript/kickstarter/argon-uninstall.sh create mode 100644 pythonscript/kickstarter/argon-verioninfo.sh create mode 100644 pythonscript/kickstarter/argondashboard.py create mode 100644 pythonscript/kickstarter/argonkeyboard.py create mode 100644 pythonscript/kickstarter/argononeup-eepromconfig.py create mode 100644 pythonscript/kickstarter/argononeup-lidconfig.sh create mode 100644 pythonscript/kickstarter/argononeup.sh create mode 100644 pythonscript/kickstarter/argononeupd.py create mode 100644 pythonscript/kickstarter/argononeupd.service create mode 100644 pythonscript/kickstarter/argononeupduser.service create mode 100644 pythonscript/kickstarter/argonpowerbutton_libgpiod.py create mode 100644 pythonscript/kickstarter/argonpowerbutton_rpigpio.py create mode 100644 pythonscript/kickstarter/argonregister.py create mode 100644 pythonscript/kickstarter/argonsysinfo.py create mode 100755 pythonscript/kickstarter/getfiles.sh create mode 100644 pythonscript/kickstarter/ups/charge_0.png create mode 100644 pythonscript/kickstarter/ups/charge_1.png create mode 100644 pythonscript/kickstarter/ups/charge_10.png create mode 100644 pythonscript/kickstarter/ups/charge_100.png create mode 100644 pythonscript/kickstarter/ups/charge_11.png create mode 100644 pythonscript/kickstarter/ups/charge_12.png create mode 100644 pythonscript/kickstarter/ups/charge_13.png create mode 100644 pythonscript/kickstarter/ups/charge_14.png create mode 100644 pythonscript/kickstarter/ups/charge_15.png create mode 100644 pythonscript/kickstarter/ups/charge_16.png create mode 100644 pythonscript/kickstarter/ups/charge_17.png create mode 100644 pythonscript/kickstarter/ups/charge_18.png create mode 100644 pythonscript/kickstarter/ups/charge_19.png create mode 100644 pythonscript/kickstarter/ups/charge_2.png create mode 100644 pythonscript/kickstarter/ups/charge_20.png create mode 100644 pythonscript/kickstarter/ups/charge_21.png create mode 100644 pythonscript/kickstarter/ups/charge_22.png create mode 100644 pythonscript/kickstarter/ups/charge_23.png create mode 100644 pythonscript/kickstarter/ups/charge_24.png create mode 100644 pythonscript/kickstarter/ups/charge_25.png create mode 100644 pythonscript/kickstarter/ups/charge_26.png create mode 100644 pythonscript/kickstarter/ups/charge_27.png create mode 100644 pythonscript/kickstarter/ups/charge_28.png create mode 100644 pythonscript/kickstarter/ups/charge_29.png create mode 100644 pythonscript/kickstarter/ups/charge_3.png create mode 100644 pythonscript/kickstarter/ups/charge_30.png create mode 100644 pythonscript/kickstarter/ups/charge_31.png create mode 100644 pythonscript/kickstarter/ups/charge_32.png create mode 100644 pythonscript/kickstarter/ups/charge_33.png create mode 100644 pythonscript/kickstarter/ups/charge_34.png create mode 100644 pythonscript/kickstarter/ups/charge_35.png create mode 100644 pythonscript/kickstarter/ups/charge_36.png create mode 100644 pythonscript/kickstarter/ups/charge_37.png create mode 100644 pythonscript/kickstarter/ups/charge_38.png create mode 100644 pythonscript/kickstarter/ups/charge_39.png create mode 100644 pythonscript/kickstarter/ups/charge_4.png create mode 100644 pythonscript/kickstarter/ups/charge_40.png create mode 100644 pythonscript/kickstarter/ups/charge_41.png create mode 100644 pythonscript/kickstarter/ups/charge_42.png create mode 100644 pythonscript/kickstarter/ups/charge_43.png create mode 100644 pythonscript/kickstarter/ups/charge_44.png create mode 100644 pythonscript/kickstarter/ups/charge_45.png create mode 100644 pythonscript/kickstarter/ups/charge_46.png create mode 100644 pythonscript/kickstarter/ups/charge_47.png create mode 100644 pythonscript/kickstarter/ups/charge_48.png create mode 100644 pythonscript/kickstarter/ups/charge_49.png create mode 100644 pythonscript/kickstarter/ups/charge_5.png create mode 100644 pythonscript/kickstarter/ups/charge_50.png create mode 100644 pythonscript/kickstarter/ups/charge_51.png create mode 100644 pythonscript/kickstarter/ups/charge_52.png create mode 100644 pythonscript/kickstarter/ups/charge_53.png create mode 100644 pythonscript/kickstarter/ups/charge_54.png create mode 100644 pythonscript/kickstarter/ups/charge_55.png create mode 100644 pythonscript/kickstarter/ups/charge_56.png create mode 100644 pythonscript/kickstarter/ups/charge_57.png create mode 100644 pythonscript/kickstarter/ups/charge_58.png create mode 100644 pythonscript/kickstarter/ups/charge_59.png create mode 100644 pythonscript/kickstarter/ups/charge_6.png create mode 100644 pythonscript/kickstarter/ups/charge_60.png create mode 100644 pythonscript/kickstarter/ups/charge_61.png create mode 100644 pythonscript/kickstarter/ups/charge_62.png create mode 100644 pythonscript/kickstarter/ups/charge_63.png create mode 100644 pythonscript/kickstarter/ups/charge_64.png create mode 100644 pythonscript/kickstarter/ups/charge_65.png create mode 100644 pythonscript/kickstarter/ups/charge_66.png create mode 100644 pythonscript/kickstarter/ups/charge_67.png create mode 100644 pythonscript/kickstarter/ups/charge_68.png create mode 100644 pythonscript/kickstarter/ups/charge_69.png create mode 100644 pythonscript/kickstarter/ups/charge_7.png create mode 100644 pythonscript/kickstarter/ups/charge_70.png create mode 100644 pythonscript/kickstarter/ups/charge_71.png create mode 100644 pythonscript/kickstarter/ups/charge_72.png create mode 100644 pythonscript/kickstarter/ups/charge_73.png create mode 100644 pythonscript/kickstarter/ups/charge_74.png create mode 100644 pythonscript/kickstarter/ups/charge_75.png create mode 100644 pythonscript/kickstarter/ups/charge_76.png create mode 100644 pythonscript/kickstarter/ups/charge_77.png create mode 100644 pythonscript/kickstarter/ups/charge_78.png create mode 100644 pythonscript/kickstarter/ups/charge_79.png create mode 100644 pythonscript/kickstarter/ups/charge_8.png create mode 100644 pythonscript/kickstarter/ups/charge_80.png create mode 100644 pythonscript/kickstarter/ups/charge_81.png create mode 100644 pythonscript/kickstarter/ups/charge_82.png create mode 100644 pythonscript/kickstarter/ups/charge_83.png create mode 100644 pythonscript/kickstarter/ups/charge_84.png create mode 100644 pythonscript/kickstarter/ups/charge_85.png create mode 100644 pythonscript/kickstarter/ups/charge_86.png create mode 100644 pythonscript/kickstarter/ups/charge_87.png create mode 100644 pythonscript/kickstarter/ups/charge_88.png create mode 100644 pythonscript/kickstarter/ups/charge_89.png create mode 100644 pythonscript/kickstarter/ups/charge_9.png create mode 100644 pythonscript/kickstarter/ups/charge_90.png create mode 100644 pythonscript/kickstarter/ups/charge_91.png create mode 100644 pythonscript/kickstarter/ups/charge_92.png create mode 100644 pythonscript/kickstarter/ups/charge_93.png create mode 100644 pythonscript/kickstarter/ups/charge_94.png create mode 100644 pythonscript/kickstarter/ups/charge_95.png create mode 100644 pythonscript/kickstarter/ups/charge_96.png create mode 100644 pythonscript/kickstarter/ups/charge_97.png create mode 100644 pythonscript/kickstarter/ups/charge_98.png create mode 100644 pythonscript/kickstarter/ups/charge_99.png create mode 100644 pythonscript/kickstarter/ups/discharge_0.png create mode 100644 pythonscript/kickstarter/ups/discharge_1.png create mode 100644 pythonscript/kickstarter/ups/discharge_10.png create mode 100644 pythonscript/kickstarter/ups/discharge_100.png create mode 100644 pythonscript/kickstarter/ups/discharge_11.png create mode 100644 pythonscript/kickstarter/ups/discharge_12.png create mode 100644 pythonscript/kickstarter/ups/discharge_13.png create mode 100644 pythonscript/kickstarter/ups/discharge_14.png create mode 100644 pythonscript/kickstarter/ups/discharge_15.png create mode 100644 pythonscript/kickstarter/ups/discharge_16.png create mode 100644 pythonscript/kickstarter/ups/discharge_17.png create mode 100644 pythonscript/kickstarter/ups/discharge_18.png create mode 100644 pythonscript/kickstarter/ups/discharge_19.png create mode 100644 pythonscript/kickstarter/ups/discharge_2.png create mode 100644 pythonscript/kickstarter/ups/discharge_20.png create mode 100644 pythonscript/kickstarter/ups/discharge_21.png create mode 100644 pythonscript/kickstarter/ups/discharge_22.png create mode 100644 pythonscript/kickstarter/ups/discharge_23.png create mode 100644 pythonscript/kickstarter/ups/discharge_24.png create mode 100644 pythonscript/kickstarter/ups/discharge_25.png create mode 100644 pythonscript/kickstarter/ups/discharge_26.png create mode 100644 pythonscript/kickstarter/ups/discharge_27.png create mode 100644 pythonscript/kickstarter/ups/discharge_28.png create mode 100644 pythonscript/kickstarter/ups/discharge_29.png create mode 100644 pythonscript/kickstarter/ups/discharge_3.png create mode 100644 pythonscript/kickstarter/ups/discharge_30.png create mode 100644 pythonscript/kickstarter/ups/discharge_31.png create mode 100644 pythonscript/kickstarter/ups/discharge_32.png create mode 100644 pythonscript/kickstarter/ups/discharge_33.png create mode 100644 pythonscript/kickstarter/ups/discharge_34.png create mode 100644 pythonscript/kickstarter/ups/discharge_35.png create mode 100644 pythonscript/kickstarter/ups/discharge_36.png create mode 100644 pythonscript/kickstarter/ups/discharge_37.png create mode 100644 pythonscript/kickstarter/ups/discharge_38.png create mode 100644 pythonscript/kickstarter/ups/discharge_39.png create mode 100644 pythonscript/kickstarter/ups/discharge_4.png create mode 100644 pythonscript/kickstarter/ups/discharge_40.png create mode 100644 pythonscript/kickstarter/ups/discharge_41.png create mode 100644 pythonscript/kickstarter/ups/discharge_42.png create mode 100644 pythonscript/kickstarter/ups/discharge_43.png create mode 100644 pythonscript/kickstarter/ups/discharge_44.png create mode 100644 pythonscript/kickstarter/ups/discharge_45.png create mode 100644 pythonscript/kickstarter/ups/discharge_46.png create mode 100644 pythonscript/kickstarter/ups/discharge_47.png create mode 100644 pythonscript/kickstarter/ups/discharge_48.png create mode 100644 pythonscript/kickstarter/ups/discharge_49.png create mode 100644 pythonscript/kickstarter/ups/discharge_5.png create mode 100644 pythonscript/kickstarter/ups/discharge_50.png create mode 100644 pythonscript/kickstarter/ups/discharge_51.png create mode 100644 pythonscript/kickstarter/ups/discharge_52.png create mode 100644 pythonscript/kickstarter/ups/discharge_53.png create mode 100644 pythonscript/kickstarter/ups/discharge_54.png create mode 100644 pythonscript/kickstarter/ups/discharge_55.png create mode 100644 pythonscript/kickstarter/ups/discharge_56.png create mode 100644 pythonscript/kickstarter/ups/discharge_57.png create mode 100644 pythonscript/kickstarter/ups/discharge_58.png create mode 100644 pythonscript/kickstarter/ups/discharge_59.png create mode 100644 pythonscript/kickstarter/ups/discharge_6.png create mode 100644 pythonscript/kickstarter/ups/discharge_60.png create mode 100644 pythonscript/kickstarter/ups/discharge_61.png create mode 100644 pythonscript/kickstarter/ups/discharge_62.png create mode 100644 pythonscript/kickstarter/ups/discharge_63.png create mode 100644 pythonscript/kickstarter/ups/discharge_64.png create mode 100644 pythonscript/kickstarter/ups/discharge_65.png create mode 100644 pythonscript/kickstarter/ups/discharge_66.png create mode 100644 pythonscript/kickstarter/ups/discharge_67.png create mode 100644 pythonscript/kickstarter/ups/discharge_68.png create mode 100644 pythonscript/kickstarter/ups/discharge_69.png create mode 100644 pythonscript/kickstarter/ups/discharge_7.png create mode 100644 pythonscript/kickstarter/ups/discharge_70.png create mode 100644 pythonscript/kickstarter/ups/discharge_71.png create mode 100644 pythonscript/kickstarter/ups/discharge_72.png create mode 100644 pythonscript/kickstarter/ups/discharge_73.png create mode 100644 pythonscript/kickstarter/ups/discharge_74.png create mode 100644 pythonscript/kickstarter/ups/discharge_75.png create mode 100644 pythonscript/kickstarter/ups/discharge_76.png create mode 100644 pythonscript/kickstarter/ups/discharge_77.png create mode 100644 pythonscript/kickstarter/ups/discharge_78.png create mode 100644 pythonscript/kickstarter/ups/discharge_79.png create mode 100644 pythonscript/kickstarter/ups/discharge_8.png create mode 100644 pythonscript/kickstarter/ups/discharge_80.png create mode 100644 pythonscript/kickstarter/ups/discharge_81.png create mode 100644 pythonscript/kickstarter/ups/discharge_82.png create mode 100644 pythonscript/kickstarter/ups/discharge_83.png create mode 100644 pythonscript/kickstarter/ups/discharge_84.png create mode 100644 pythonscript/kickstarter/ups/discharge_85.png create mode 100644 pythonscript/kickstarter/ups/discharge_86.png create mode 100644 pythonscript/kickstarter/ups/discharge_87.png create mode 100644 pythonscript/kickstarter/ups/discharge_88.png create mode 100644 pythonscript/kickstarter/ups/discharge_89.png create mode 100644 pythonscript/kickstarter/ups/discharge_9.png create mode 100644 pythonscript/kickstarter/ups/discharge_90.png create mode 100644 pythonscript/kickstarter/ups/discharge_91.png create mode 100644 pythonscript/kickstarter/ups/discharge_92.png create mode 100644 pythonscript/kickstarter/ups/discharge_93.png create mode 100644 pythonscript/kickstarter/ups/discharge_94.png create mode 100644 pythonscript/kickstarter/ups/discharge_95.png create mode 100644 pythonscript/kickstarter/ups/discharge_96.png create mode 100644 pythonscript/kickstarter/ups/discharge_97.png create mode 100644 pythonscript/kickstarter/ups/discharge_98.png create mode 100644 pythonscript/kickstarter/ups/discharge_99.png create mode 100644 pythonscript/kickstarter/ups/loading_0.png diff --git a/pythonscript/kickstarter/argon-uninstall.sh b/pythonscript/kickstarter/argon-uninstall.sh new file mode 100644 index 0000000..94b782f --- /dev/null +++ b/pythonscript/kickstarter/argon-uninstall.sh @@ -0,0 +1,180 @@ +#!/bin/bash +echo "----------------------" +echo " Argon Uninstall Tool" +echo "----------------------" +echo -n "Press Y to continue:" +read -n 1 confirm +echo +if [ "$confirm" = "y" ] +then + confirm="Y" +fi + +if [ "$confirm" != "Y" ] +then + echo "Cancelled" + exit +fi + +destfoldername=$USERNAME +if [ -z "$destfoldername" ] +then + destfoldername=$USER +fi +if [ "$destfoldername" = "root" ] +then + destfoldername="" +fi +if [ -z "$destfoldername" ] +then + destfoldername="pi" +fi + + +shortcutfile="/home/$destfoldername/Desktop/argonone-config.desktop" +if [ -f "$shortcutfile" ]; then + sudo rm $shortcutfile + if [ -f "/usr/share/pixmaps/ar1config.png" ]; then + sudo rm /usr/share/pixmaps/ar1config.png + fi + if [ -f "/usr/share/pixmaps/argoneon.png" ]; then + sudo rm /usr/share/pixmaps/argoneon.png + fi +fi +shortcutfile="/home/$destfoldername/Desktop/argononeup.desktop" +if [ -f "$shortcutfile" ]; then + sudo rm $shortcutfile +fi + +INSTALLATIONFOLDER=/etc/argon + +argononefanscript=$INSTALLATIONFOLDER/argononed.py + +if [ -f $argononefanscript ]; then + sudo systemctl stop argononed.service + sudo systemctl disable argononed.service + + # Turn off the fan + /usr/bin/python3 $argononefanscript FANOFF + + # Remove files + sudo rm /lib/systemd/system/argononed.service +fi + +argononeupscript=$INSTALLATIONFOLDER/argononeupd.py +if [ -f $argononeupscript ]; then + sudo systemctl stop argononeupd.service + sudo systemctl disable argononeupd.service + + # Remove files + sudo rm /lib/systemd/system/argononeupd.service +fi + +# Remove RTC if any +argoneonrtcscript=$INSTALLATIONFOLDER/argoneond.py +if [ -f "$argoneonrtcscript" ] +then + # Disable Services + sudo systemctl stop argoneond.service + sudo systemctl disable argoneond.service + + # No need for sudo + /usr/bin/python3 $argoneonrtcscript CLEAN + /usr/bin/python3 $argoneonrtcscript SHUTDOWN + + # Remove files + sudo rm /lib/systemd/system/argoneond.service +fi + +# Remove UPS daemon if any +argononeupsscript=$INSTALLATIONFOLDER/argononeupsd.py +if [ -f "$argononeupsscript" ] +then + #sudo rmmod argonbatteryicon + # Disable Services + sudo systemctl stop argononeupsd.service + sudo systemctl disable argononeupsd.service + + sudo systemctl stop argonupsrtcd.service + sudo systemctl disable argonupsrtcd.service + + # Remove files + sudo rm /lib/systemd/system/argononeupsd.service + sudo rm /lib/systemd/system/argonupsrtcd.service + + find "/home" -maxdepth 1 -type d | while read line; do + shortcutfile="$line/Desktop/argonone-ups.desktop" + if [ -f "$shortcutfile" ]; then + sudo rm $shortcutfile + fi + done +fi + + +# Remove UPS daemon if any +argononeupsscript=$INSTALLATIONFOLDER/argononeupd.py +if [ -f "$argononeupsscript" ] +then + #sudo rmmod argonbatteryicon + # Disable Services + sudo systemctl stop argononeupd.service + sudo systemctl disable argononeupd.service + + for tmpuser in `awk -F: '{ if ($3 >= 1000) print $1 }' /etc/passwd` + do + if [ "$tmpuser" != "nobody" ] + then + sudo -u "$tmpuser" systemctl --user stop argononeupduser.service + sudo -u "$tmpuser" systemctl --user disable argononeupduser.service + fi + done + systemctl --user stop argononeupduser.service + systemctl --user disable argononeupduser.service + + # Remove files + sudo rm /lib/systemd/system/argononeupd.service + sudo rm /etc/systemd/user/argononeupduser.service + + find "/home" -maxdepth 1 -type d | while read line; do + shortcutfile="$line/Desktop/argononeup.desktop" + if [ -f "$shortcutfile" ]; then + sudo rm $shortcutfile + fi + done +fi + + +if [ -f "/usr/bin/argon-config" ] +then + sudo rm /usr/bin/argon-config +fi + +if [ -f "/usr/bin/argonone-config" ] +then + sudo rm /usr/bin/argonone-config + sudo rm /usr/bin/argonone-uninstall +fi + + +if [ -f "/usr/bin/argonone-ir" ] +then + sudo rm /usr/bin/argonone-ir +fi + +# Delete config files +for configfile in argonunits argononed argononed-hdd argoneonrtc argoneonoled argonupsrtc argononeupd +do + if [ -f "/etc/${configfile}.conf" ] + then + sudo rm "/etc/${configfile}.conf" + fi +done + + + +sudo rm /lib/systemd/system-shutdown/argon-shutdown.sh + +sudo rm -R -f $INSTALLATIONFOLDER + +echo "Removed Argon Services." +echo "Cleanup will complete after restarting the device." diff --git a/pythonscript/kickstarter/argon-verioninfo.sh b/pythonscript/kickstarter/argon-verioninfo.sh new file mode 100644 index 0000000..e869da5 --- /dev/null +++ b/pythonscript/kickstarter/argon-verioninfo.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +VERSIONINFO="2601005" + +echo "Version $VERSIONINFO" +if [ -z "$1" ] +then + echo + echo "We acknowledge the valuable feedback of the following:" + echo "ghalfacree, NHHiker" + echo + echo "Feel free to join the discussions at https://forum.argon40.com" + echo +fi diff --git a/pythonscript/kickstarter/argondashboard.py b/pythonscript/kickstarter/argondashboard.py new file mode 100644 index 0000000..78a1877 --- /dev/null +++ b/pythonscript/kickstarter/argondashboard.py @@ -0,0 +1,374 @@ +#!/bin/python3 + +import time +import os +import sys + +import signal +import curses + + +sys.path.append("/etc/argon/") +from argonsysinfo import * +from argonregister import * + + + +############ +# Constants +############ +COLORPAIRID_DEFAULT=1 +COLORPAIRID_LOGO=2 +COLORPAIRID_DEFAULTINVERSE=3 +COLORPAIRID_ALERT=4 +COLORPAIRID_WARNING=5 +COLORPAIRID_GOOD=6 + + + + +INPUTREFRESHMS=100 +DISPLAYREFRESHMS=5000 +UPS_LOGFILE="/dev/shm/upslog.txt" + + +################### +# Display Elements +################### + +def displaydatetime(stdscr): + try: + curtimenow = time.localtime() + + stdscr.addstr(1, 1, time.strftime("%A", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT)) + stdscr.addstr(2, 1, time.strftime("%b %d,%Y", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT)) + stdscr.addstr(3, 1, time.strftime("%I:%M%p", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT)) + except: + pass + +def displayipbattery(stdscr): + try: + displaytextright(stdscr,1, argonsysinfo_getip()+" ", COLORPAIRID_DEFAULT) + except: + pass + try: + status = "" + level = "" + outobj = {} + # Load status + fp = open(UPS_LOGFILE, "r") + logdata = fp.read() + alllines = logdata.split("\n") + ctr = 0 + while ctr < len(alllines): + tmpval = alllines[ctr].strip() + curinfo = tmpval.split(":") + if len(curinfo) > 1: + tmpattrib = curinfo[0].lower().split(" ") + # The rest are assumed to be value + outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip() + ctr = ctr + 1 + + # Map to data + try: + statuslist = outobj["power"].lower().split(" ") + if statuslist[0] == "battery": + tmp_charging = 0 + else: + tmp_charging = 1 + tmp_battery = int(statuslist[1].replace("%","")) + + colorpairidx = COLORPAIRID_DEFAULT + if tmp_charging: + if tmp_battery > 99: + status="Plugged" + level="" + else: + status="Charging" + level=str(tmp_battery)+"%" + else: + status="Battery" + level=str(tmp_battery)+"%" + if tmp_battery <= 20: + colorpairidx = COLORPAIRID_ALERT + elif tmp_battery <= 50: + colorpairidx = COLORPAIRID_WARNING + else: + colorpairidx = COLORPAIRID_GOOD + + displaytextright(stdscr,2, status+" ", colorpairidx) + displaytextright(stdscr,3, level+" ", colorpairidx) + except: + pass + + + except: + pass + + +def displayramcpu(stdscr, refcpu, rowstart, colstart): + curusage_b = argonsysinfo_getcpuusagesnapshot() + try: + outputlist = [] + tmpraminfo = argonsysinfo_getram() + outputlist.append({"title": "ram ", "value": tmpraminfo[1]+" "+tmpraminfo[0]+" Free"}) + + for cpuname in refcpu: + if cpuname == "cpu": + continue + if refcpu[cpuname]["total"] == curusage_b[cpuname]["total"]: + outputlist.append({"title": cpuname, "value": "Loading"}) + else: + total = curusage_b[cpuname]["total"]-refcpu[cpuname]["total"] + idle = curusage_b[cpuname]["idle"]-refcpu[cpuname]["idle"] + outputlist.append({"title": cpuname, "value": str(int(100*(total-idle)/(total)))+"% Used"}) + displaytitlevaluelist(stdscr, rowstart, colstart, outputlist) + except: + pass + return curusage_b + + +def displaytempfan(stdscr, rowstart, colstart): + try: + outputlist = [] + try: + if busobj is not None: + fanspeed = argonregister_getfanspeed(busobj) + fanspeedstr = "Off" + if fanspeed > 0: + fanspeedstr = str(fanspeed)+"%" + outputlist.append({"title": "Fan ", "value": fanspeedstr}) + except: + pass + # Todo load from config + temperature = "C" + hddtempctr = 0 + maxcval = 0 + mincval = 200 + + + # Get min/max of hdd temp + hddtempobj = argonsysinfo_gethddtemp() + for curdev in hddtempobj: + if hddtempobj[curdev] < mincval: + mincval = hddtempobj[curdev] + if hddtempobj[curdev] > maxcval: + maxcval = hddtempobj[curdev] + hddtempctr = hddtempctr + 1 + + cpucval = argonsysinfo_getcputemp() + if hddtempctr > 0: + alltempobj = {"cpu": cpucval,"hdd min": mincval, "hdd max": maxcval} + # Update max C val to CPU Temp if necessary + if maxcval < cpucval: + maxcval = cpucval + + displayrowht = 8 + displayrow = 8 + for curdev in alltempobj: + if temperature == "C": + # Celsius + tmpstr = str(alltempobj[curdev]) + if len(tmpstr) > 4: + tmpstr = tmpstr[0:4] + else: + # Fahrenheit + tmpstr = str(32+9*(alltempobj[curdev])/5) + if len(tmpstr) > 5: + tmpstr = tmpstr[0:5] + if len(curdev) <= 3: + outputlist.append({"title": curdev.upper(), "value": tmpstr +temperature}) + else: + outputlist.append({"title": curdev.upper(), "value": tmpstr +temperature}) + else: + maxcval = cpucval + if temperature == "C": + # Celsius + tmpstr = str(cpucval) + if len(tmpstr) > 4: + tmpstr = tmpstr[0:4] + else: + # Fahrenheit + tmpstr = str(32+9*(cpucval)/5) + if len(tmpstr) > 5: + tmpstr = tmpstr[0:5] + + outputlist.append({"title": "Temp", "value": tmpstr +temperature}) + displaytitlevaluelist(stdscr, rowstart, colstart, outputlist) + except: + pass + + + +def displaystorage(stdscr, rowstart, colstart): + try: + outputlist = [] + tmpobj = argonsysinfo_listhddusage() + for curdev in tmpobj: + outputlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total'])+ " "+ str(int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']))+"% Used" }) + displaytitlevaluelist(stdscr, rowstart, colstart, outputlist) + except: + pass + +################## +# Helpers +################## + +# Initialize I2C Bus +bus = argonregister_initializebusobj() + +def handle_resize(signum, frame): + # TODO: Not working? + curses.update_lines_cols() + # Ideally redraw here + +def displaytitlevaluelist(stdscr, rowstart, leftoffset, curlist): + rowidx = rowstart + while rowidx < curses.LINES and len(curlist) > 0: + curline = "" + tmpitem = curlist.pop(0) + curline = tmpitem["title"]+": "+str(tmpitem["value"]) + + stdscr.addstr(rowidx, leftoffset, curline) + rowidx = rowidx + 1 + + +def displaytextcentered(stdscr, rownum, strval, colorpairidx = COLORPAIRID_DEFAULT): + leftoffset = 0 + numchars = len(strval) + if numchars < 1: + return + elif (numchars > curses.COLS): + leftoffset = 0 + strval = strval[0:curses.COLS] + else: + leftoffset = (curses.COLS - numchars)>>1 + + stdscr.addstr(rownum, leftoffset, strval, curses.color_pair(colorpairidx)) + + +def displaytextright(stdscr, rownum, strval, colorpairidx = COLORPAIRID_DEFAULT): + leftoffset = 0 + numchars = len(strval) + if numchars < 1: + return + elif (numchars > curses.COLS): + leftoffset = 0 + strval = strval[0:curses.COLS] + else: + leftoffset = curses.COLS - numchars + + stdscr.addstr(rownum, leftoffset, strval, curses.color_pair(colorpairidx)) + + +def displaylinebreak(stdscr, rownum, colorpairidx = COLORPAIRID_DEFAULTINVERSE): + strval = " " + while len(strval) < curses.COLS: + strval = strval + " " + stdscr.addstr(rownum, 0, strval, curses.color_pair(colorpairidx)) + + + + +################## +# Main Loop +################## + +def mainloop(stdscr): + try: + # Set up signal handler + signal.signal(signal.SIGWINCH, handle_resize) + + maxloopctr = int(DISPLAYREFRESHMS/INPUTREFRESHMS) + sleepsecs = INPUTREFRESHMS/1000 + + loopctr = maxloopctr + loopmode = True + + stdscr = curses.initscr() + + # Turn off echoing of keys, and enter cbreak mode, + # where no buffering is performed on keyboard input + curses.noecho() + curses.cbreak() + curses.curs_set(0) + curses.start_color() + + #curses.COLOR_BLACK + #curses.COLOR_BLUE + #curses.COLOR_CYAN + #curses.COLOR_GREEN + #curses.COLOR_MAGENTA + #curses.COLOR_RED + #curses.COLOR_WHITE + #curses.COLOR_YELLOW + + curses.init_pair(COLORPAIRID_DEFAULT, curses.COLOR_WHITE, curses.COLOR_BLACK) + curses.init_pair(COLORPAIRID_LOGO, curses.COLOR_WHITE, curses.COLOR_RED) + curses.init_pair(COLORPAIRID_DEFAULTINVERSE, curses.COLOR_BLACK, curses.COLOR_WHITE) + curses.init_pair(COLORPAIRID_ALERT, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(COLORPAIRID_WARNING, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(COLORPAIRID_GOOD, curses.COLOR_GREEN, curses.COLOR_BLACK) + + stdscr.nodelay(True) + + refcpu = argonsysinfo_getcpuusagesnapshot() + while True: + try: + key = stdscr.getch() + # if key == ord('x') or key == ord('X'): + # Any key + if key > 0: + break + except curses.error: + # No key was pressed + pass + + loopctr = loopctr + 1 + if loopctr >= maxloopctr: + loopctr = 0 + # Screen refresh loop + # Clear screen + stdscr.clear() + + displaytextcentered(stdscr, 0, " ", COLORPAIRID_LOGO) + displaytextcentered(stdscr, 1, " Argon40 Dashboard ", COLORPAIRID_LOGO) + displaytextcentered(stdscr, 2, " ", COLORPAIRID_LOGO) + displaytextcentered(stdscr, 3, "Press any key to close") + displaylinebreak(stdscr, 5) + + # Display Elements + displaydatetime(stdscr) + displayipbattery(stdscr) + + # Data Columns + rowstart = 7 + colstart = 20 + refcpu = displayramcpu(stdscr, refcpu, rowstart, colstart) + displaystorage(stdscr, rowstart, colstart+30) + displaytempfan(stdscr, rowstart, colstart+60) + + # Main refresh even + stdscr.refresh() + + time.sleep(sleepsecs) + + except Exception as initerr: + pass + + ########## + # Cleanup + ########## + + try: + curses.curs_set(1) + curses.echo() + curses.nocbreak() + curses.endwin() + except Exception as closeerr: + pass + +try: + curses.wrapper(mainloop) +except Exception as wrapperr: + pass diff --git a/pythonscript/kickstarter/argonkeyboard.py b/pythonscript/kickstarter/argonkeyboard.py new file mode 100644 index 0000000..8c1a960 --- /dev/null +++ b/pythonscript/kickstarter/argonkeyboard.py @@ -0,0 +1,817 @@ +#!/usr/bin/python3 + +# +# This script monitor battery via ic2 and keyboard events. +# +# Additional comments are found in each function below +# +# + + +from evdev import InputDevice, categorize, ecodes, list_devices +from select import select + +import subprocess + +import sys +import os +import time + +from threading import Thread +from queue import Queue + + +UPS_LOGFILE="/dev/shm/upslog.txt" +KEYBOARD_LOCKFILE="/dev/shm/argononeupkeyboardlock.txt" + + +KEYCODE_BRIGHTNESSUP = "KEY_BRIGHTNESSUP" +KEYCODE_BRIGHTNESSDOWN = "KEY_BRIGHTNESSDOWN" +KEYCODE_VOLUMEUP = "KEY_VOLUMEUP" +KEYCODE_VOLUMEDOWN = "KEY_VOLUMEDOWN" +KEYCODE_PAUSE = "KEY_PAUSE" +KEYCODE_MUTE = "KEY_MUTE" + + +################### +# Utilty Functions +################### + +# Debug Logger +def debuglog(typestr, logstr): + return + # try: + # DEBUGFILE="/dev/shm/argononeupkeyboarddebuglog.txt" + # tmpstrpadding = " " + + # with open(DEBUGFILE, "a") as txt_file: + # txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n") + # except: + # pass + +def runcmdlist(key, cmdlist): + try: + cmdresult = subprocess.run(cmdlist, + capture_output=True, + text=True, + check=True + ) + #debuglog(key+"-result-output",str(cmdresult.stdout)) + if cmdresult.stderr: + debuglog(key+"-result-error",str(cmdresult.stderr)) + #debuglog(key+"-result-code",str(cmdresult.returncode)) + + except subprocess.CalledProcessError as e: + debuglog(key+"-error-output",str(e.stdout)) + if e.stderr: + debuglog(key+"-error-error",str(e.stderr)) + debuglog(key+"-error-code",str(e.returncode)) + except FileNotFoundError: + debuglog(key+"-error-filenotfound","Command Not Found") + except Exception as othererr: + try: + debuglog(key+"-error-other", str(othererr)) + except: + debuglog(key+"-error-other", "Other Error") + +def createlockfile(fname): + # try: + # if os.path.isfile(fname): + # return True + # except Exception as checklockerror: + # try: + # debuglog("keyboard-lock-error", str(checklockerror)) + # except: + # debuglog("keyboard-lock-error", "Error Checking Lock File") + # try: + # with open(fname, "w") as txt_file: + # txt_file.write(time.asctime(time.localtime(time.time()))+"\n") + # except Exception as lockerror: + # try: + # debuglog("keyboard-lock-error", str(lockerror)) + # except: + # debuglog("keyboard-lock-error", "Error Creating Lock File") + return False + +def deletelockfile(fname): + # try: + # os.remove(fname) + # except Exception as lockerror: + # try: + # debuglog("keyboard-lock-error", str(lockerror)) + # except: + # debuglog("keyboard-lock-error", "Error Removing Lock File") + return True + + +# System Notifcation +def notifymessage(message, iscritical): + if not isinstance(message, str) or len(message.strip()) == 0: + return + + wftype="notify" + if iscritical: + wftype="critical" + os.system("export SUDO_UID=1000; wfpanelctl "+wftype+" \""+message+"\"") + os.system("export DISPLAY=:0.0; lxpanelctl notify \""+message+"\"") + + +############# +# Battery (copied) +############# + +def battery_loadlogdata(): + # status, version, time, schedule + outobj = {} + try: + fp = open(UPS_LOGFILE, "r") + logdata = fp.read() + alllines = logdata.split("\n") + ctr = 0 + while ctr < len(alllines): + tmpval = alllines[ctr].strip() + curinfo = tmpval.split(":") + if len(curinfo) > 1: + tmpattrib = curinfo[0].lower().split(" ") + # The rest are assumed to be value + outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip() + ctr = ctr + 1 + except Exception as einit: + try: + debuglog("keyboard-battery-error", str(einit)) + except: + debuglog("keyboard-battery-error", "Error getting battery status") + #pass + + return outobj + + +def keyboardevent_getdevicepaths(): + outlist = [] + try: + for path in list_devices(): + try: + tmpdevice = InputDevice(path) + keyeventlist = tmpdevice.capabilities().get(ecodes.EV_KEY, []) + # Keyboard has EV_KEY (key) and EV_REP (autorepeat) + if ecodes.KEY_BRIGHTNESSDOWN in keyeventlist and ecodes.KEY_BRIGHTNESSDOWN in keyeventlist: + outlist.append(path) + #debuglog("keyboard-device-keys", path) + #debuglog("keyboard-device-keys", str(keyeventlist)) + elif ecodes.KEY_F2 in keyeventlist and ecodes.KEY_F3 in keyeventlist: + # Keyboards with FN key sometimes do not include KEY_BRIGHTNESS in declaration + outlist.append(path) + #debuglog("keyboard-device-keys", path) + #debuglog("keyboard-device-keys", str(keyeventlist)) + tmpdevice.close() + except: + pass + except: + pass + return outlist + +def keyboardevent_devicechanged(curlist, newlist): + try: + for curpath in curlist: + if curpath not in newlist: + return True + for newpath in newlist: + if newpath not in curlist: + return True + except: + pass + return False + +def keyboardevent_getbrigthnesstoolid(): + toolid = 0 + try: + output = subprocess.check_output(["ddcutil", "--version"], text=True, stderr=subprocess.DEVNULL) + lines = output.splitlines() + if len(lines) > 0: + tmpline = lines[0].strip() + toolid = int(tmpline.split(" ")[1].split(".")[0]) + except Exception as einit: + try: + debuglog("keyboard-brightness-tool-error", str(einit)) + except: + debuglog("keyboard-brightness-tool-error", "Error getting tool id value") + + debuglog("keyboard-brightness-tool", toolid) + return toolid + +def keyboardevent_getbrigthnessinfo(toolid, defaultlevel=50): + level = defaultlevel + try: + # VCP code x10(Brightness ): current value = 90, max value = 100 + if toolid > 1: + # Disabled dynamic sleep "--disable-dynamic-sleep", "--sleep-multiplier", "0.1" + output = subprocess.check_output(["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", "0.1", "getvcp", "10"], text=True, stderr=subprocess.DEVNULL) + else: + output = subprocess.check_output(["ddcutil", "--sleep-multiplier", "0.1", "getvcp", "10"], text=True, stderr=subprocess.DEVNULL) + debuglog("keyboard-brightness-info", output) + level = int(output.split(":")[-1].split(",")[0].split("=")[-1].strip()) + except Exception as einit: + try: + debuglog("keyboard-brightness-error", str(einit)) + except: + debuglog("keyboard-brightness-error", "Error getting base value") + + + return { + "level": level + } + + +def keyboardevent_adjustbrigthness(toolid, baselevel, adjustval=5): + curlevel = baselevel + if adjustval == 0: + return { + "level": baselevel + } + + # Moved reading because ddcutil has delay + # try: + # tmpobj = keyboardevent_getbrigthnessinfo(toolid, curlevel) + # curlevel = tmpobj["level"] + # except Exception: + # pass + + tmpval = max(10, min(100, curlevel + adjustval)) + if tmpval != curlevel: + try: + debuglog("keyboard-brightness", str(curlevel)+"% to "+str(tmpval)+"%") + if toolid > 1: + # Disabled dynamic sleep "--disable-dynamic-sleep", "--sleep-multiplier", "0.1" + runcmdlist("brightness", ["ddcutil", "--disable-dynamic-sleep", "--sleep-multiplier", "0.1", "setvcp", "10", str(tmpval)]) + else: + runcmdlist("brightness", ["ddcutil", "--sleep-multiplier", "0.1", "setvcp", "10", str(tmpval)]) + notifymessage("Brightness: "+str(tmpval)+"%", False) + except Exception as adjusterr: + try: + debuglog("keyboard-brightness-error", str(adjusterr)) + except: + debuglog("keyboard-brightness-error", "Error adjusting value") + return { + "level": curlevel + } + + # DEBUG: Checking + #keyboardevent_getbrigthnessinfo(toolid, tmpval) + return { + "level": tmpval + } + + +def keyboardevent_getvolumesinkid(usedefault=True): + if usedefault == True: + return "@DEFAULT_SINK@" + cursinkid = 0 + try: + output = subprocess.check_output(["wpctl", "status"], text=True, encoding='utf-8', stderr=subprocess.DEVNULL) + + # Find Audio section + tmpline = "" + foundidx = 0 + lines = output.splitlines() + lineidx = 0 + while lineidx < len(lines): + tmpline = lines[lineidx].strip() + if tmpline == "Audio": + foundidx = lineidx + break + lineidx = lineidx + 1 + + if foundidx < 1: + return 0 + + # Find Sinks section + foundidx = 0 + lineidx = lineidx + 1 + while lineidx < len(lines): + if "Sinks:" in lines[lineidx]: + foundidx = lineidx + break + elif len(lines[lineidx]) < 1: + break + lineidx = lineidx + 1 + + if foundidx < 1: + return 0 + + # Get find default id, or first id + lineidx = lineidx + 1 + while lineidx < len(lines): + if "vol:" in lines[lineidx] and "." in lines[lineidx]: + tmpstr = lines[lineidx].split(".")[0] + tmplist = tmpstr.split() + if len(tmplist) > 1: + if tmplist[len(tmplist)-2] == "*": + return int(tmplist[len(tmplist)-1]) + if len(tmplist) > 0 and cursinkid < 1: + cursinkid = int(tmplist[len(tmplist)-1]) + elif len(lines[lineidx]) < 3: + break + lineidx = lineidx + 1 + except Exception as einit: + try: + debuglog("keyboard-volume-error", str(einit)) + except: + debuglog("keyboard-volume-error", "Error getting device ID") + + return cursinkid + + +def keyboardevent_getvolumeinfo(deviceidstr="", defaultlevel=50, defaultmuted=0): + muted = defaultmuted + level = defaultlevel + try: + if deviceidstr == "": + audioidstr = str(keyboardevent_getvolumesinkid()) + if audioidstr == "0": + debuglog("keyboard-volume-error", "Error getting device id") + return { + "level": defaultmuted, + "muted": defaultlevel + } + + deviceidstr = audioidstr + + output = subprocess.check_output(["wpctl", "get-volume", deviceidstr], text=True, stderr=subprocess.DEVNULL) + debuglog("keyboard-volume-info", output) + + muted = 0 + level = 0 + # Parse output, examples + # Volume: 0.65 + # Volume: 0.55 [MUTED] + outlist = output.split() + if len(outlist) > 0: + # Get last element + tmpstr = outlist[len(outlist)-1] + # Check if muted + if "MUTE" in tmpstr: + muted = 1 + if len(outlist) > 1: + tmpstr = outlist[len(outlist)-2] + if tmpstr.endswith("%"): + # Level 100% to 0% + level = int(float(tmpstr[:-1])) + elif tmpstr.replace('.', '').isdigit(): + # Level 1.00 to 0.00 + level = int(float(tmpstr) * 100.0) + except Exception as einit: + try: + debuglog("keyboard-volume-error", str(einit)) + except: + debuglog("keyboard-volume-error", "Error getting base value") + return { + "level": defaultmuted, + "muted": defaultlevel + } + + #debuglog("keyboard-volume-get", str(level)+"% Mute:"+str(muted)) + + return { + "level": level, + "muted": muted + } + + +def keyboardevent_adjustvolume(baselevel, basemuted, adjustval=5): + curlevel = baselevel + curmuted = basemuted + needsnotification = False + + deviceidstr = str(keyboardevent_getvolumesinkid()) + if deviceidstr == "0": + debuglog("keyboard-volume-error", "Error getting device id") + return { + "level": baselevel, + "muted": basemuted + } + + # try: + # tmpobj = keyboardevent_getvolumeinfo(deviceidstr, curlevel, curmuted) + # curlevel = tmpobj["level"] + # curmuted = tmpobj["muted"] + # except Exception: + # pass + + tmpmuted = curmuted + if adjustval == 0: + # Toggle Mute + if curmuted == 0: + tmpmuted = 1 + else: + tmpmuted = 0 + + tmpval = max(10, min(100, curlevel + adjustval)) + if tmpval != curlevel: + try: + debuglog("keyboard-volume", str(curlevel)+"% to "+str(tmpval)+"%") + runcmdlist("volume", ["wpctl", "set-volume", deviceidstr, f"{tmpval}%"]) + needsnotification = True + tmpmuted = 0 + except Exception as adjusterr: + try: + debuglog("keyboard-volume-error", str(adjusterr)) + except: + debuglog("keyboard-volume-error", "Error adjusting value") + return { + "level": curlevel, + "muted": curmuted + } + elif adjustval != 0: + # To unmute even if no volume level change + tmpmuted = 0 + + if tmpmuted != curmuted: + try: + debuglog("keyboard-mute", str(tmpmuted)) + runcmdlist("mute", ["wpctl", "set-mute", deviceidstr, str(tmpmuted)]) + needsnotification = True + except Exception as adjusterr: + try: + debuglog("keyboard-mute-error", str(adjusterr)) + except: + debuglog("keyboard-mute-error", "Error adjusting value") + return { + "level": tmpval, + "muted": curmuted + } + #if tmpmuted == 1: + # notifymessage("Volume: Muted", False) + #else: + # notifymessage("Volume: "+str(tmpval)+"%", False) + + # DEBUG: Checking + #keyboardevent_getvolumeinfo(deviceidstr, tmpval, tmpmuted) + + return { + "level": tmpval, + "muted": tmpmuted + } + +def keyboard_getlayoutfieldvalue(tmpval): + debuglog("keyboard-layout-lang", tmpval) + if "us" in tmpval: + debuglog("keyboard-layout-langout", "us") + return "us" + debuglog("keyboard-layout-langout", "gb") + return "gb" # uk, gb, etc + #return tmpval + + +def keyboard_getdevicefw(kbdevice): + # info: vendor 0x6080=24704, product 0x8062=32866 + try: + if kbdevice.info.vendor == 24704 and kbdevice.info.product == 32866: + # Special HID + return "314" + except Exception as infoerr: + pass + + return "" + + +def keyboardevemt_keyhandler(readq): + + ADJUSTTYPE_NONE=0 + ADJUSTTYPE_BRIGHTNESS=1 + ADJUSTTYPE_VOLUME=2 + ADJUSTTYPE_MUTE=3 + ADJUSTTYPE_BATTERYINFO=4 + + DATAREFRESHINTERVALSEC = 10 + + PRESSWAITINTERVALSEC = 0.5 + FIRSTHOLDINTERVALSEC = 0.5 + HOLDWAITINTERVALSEC = 0.5 + + + # Get current levels + volumetime = time.time() + curvolumemuted = 0 + curvolume = 50 + + brightnesstime = volumetime + curbrightness = 50 + brightnesstoolid = 0 + + try: + brightnesstoolid = keyboardevent_getbrigthnesstoolid() + except Exception: + brightnesstoolid = 0 + pass + + try: + tmpobj = keyboardevent_getbrigthnessinfo(brightnesstoolid) + curbrightness = tmpobj["level"] + except Exception: + pass + + try: + tmpobj = keyboardevent_getvolumeinfo() + curvolumemuted = tmpobj["muted"] + curvolume = tmpobj["level"] + except Exception: + pass + + while True: + try: + tmpkeymode = 0 + tmpkeycode = "" + adjustval = 0 + adjusttype = ADJUSTTYPE_NONE + + tmpcode = readq.get() # Blocking + try: + codeelements = tmpcode.split("+") + if len(codeelements) == 2: + if codeelements[0] == "PRESS": + tmpkeymode = 1 + else: + tmpkeymode = 2 + tmpkeycode = codeelements[1] + elif tmpcode == "EXIT": + readq.task_done() + return + + except Exception: + tmpkeycode = "" + tmpkeymode = 0 + pass + tmptime = time.time() + if tmpkeycode in [KEYCODE_BRIGHTNESSDOWN, KEYCODE_BRIGHTNESSUP]: + if tmpkeymode == 1 and tmptime - brightnesstime > DATAREFRESHINTERVALSEC: + # Do not update value during hold + try: + tmpobj = keyboardevent_getbrigthnessinfo(brightnesstoolid) + curbrightness = tmpobj["level"] + except Exception: + pass + + adjusttype = ADJUSTTYPE_BRIGHTNESS + if tmpkeycode == KEYCODE_BRIGHTNESSDOWN: + adjustval = -5*tmpkeymode + else: + adjustval = 5*tmpkeymode + brightnesstime = tmptime + elif tmpkeycode in [KEYCODE_MUTE, KEYCODE_VOLUMEDOWN, KEYCODE_VOLUMEUP]: + if tmpkeymode == 1 and tmptime - volumetime > DATAREFRESHINTERVALSEC and tmpkeymode == 1: + # Do not update value during hold + try: + tmpobj = keyboardevent_getvolumeinfo() + curvolumemuted = tmpobj["muted"] + curvolume = tmpobj["level"] + except Exception: + pass + + if tmpkeycode == KEYCODE_MUTE: + adjusttype = ADJUSTTYPE_MUTE + adjustval = 0 + else: + adjusttype = ADJUSTTYPE_VOLUME + if tmpkeycode == KEYCODE_VOLUMEDOWN: + adjustval = -5*tmpkeymode + else: + adjustval = 5*tmpkeymode + volumetime = tmptime + + elif tmpkeycode == KEYCODE_PAUSE: + adjusttype = ADJUSTTYPE_BATTERYINFO + else: + readq.task_done() + continue + + try: + tmplockfilea = KEYBOARD_LOCKFILE+".a" + if createlockfile(tmplockfilea) == False: + # Debug ONLY + # if tmpkeymode == 1: + # debuglog("keyboard-event", "Press Key Code: "+str(tmpkeycode)) + # else: + # debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode)) + + if adjusttype == ADJUSTTYPE_BRIGHTNESS: + try: + tmpobj = keyboardevent_adjustbrigthness(brightnesstoolid, curbrightness, adjustval) + curbrightness = tmpobj["level"] + except Exception as brightnesserr: + try: + debuglog("keyboard-brightnessother-error", str(brightnesserr)) + except: + debuglog("keyboard-brightnessother-error", "Error adjusting value") + pass + elif adjusttype == ADJUSTTYPE_VOLUME or adjusttype == ADJUSTTYPE_MUTE: + try: + tmpobj = keyboardevent_adjustvolume(curvolume, curvolumemuted, adjustval) + curvolumemuted = tmpobj["muted"] + curvolume = tmpobj["level"] + except Exception as volumeerr: + try: + debuglog("keyboard-volumeother-error", str(volumeerr)) + except: + debuglog("keyboard-volumeother-error", "Error adjusting value") + pass + elif adjusttype == ADJUSTTYPE_BATTERYINFO: + outobj = battery_loadlogdata() + try: + notifymessage(outobj["power"], False) + except: + pass + deletelockfile(tmplockfilea) + + + except Exception as keyhandlererr: + try: + debuglog("keyboard-handlererror", str(keyhandleerr)) + except: + debuglog("keyboard-handlererror", "Error") + + readq.task_done() + + except Exception as mainerr: + time.sleep(10) + # While True + + +def keyboardevent_monitor(writeq): + + READTIMEOUTSECS = 1.0 + + FIRSTHOLDINTERVALSEC = 0.5 + HOLDWAITINTERVALSEC = 0.5 + + while True: + try: + keypresstimestamp = {} + keyholdtimestamp = {} + # Get Devices + devicelist = [] + devicefdlist = [] + devicepathlist = keyboardevent_getdevicepaths() + devicefwlist = [] + + deviceidx = 0 + while deviceidx < len(devicepathlist): + try: + tmpdevice = InputDevice(devicepathlist[deviceidx]) + devicelist.append(tmpdevice) + devicefdlist.append(tmpdevice.fd) + devicefwlist.append(keyboard_getdevicefw(tmpdevice)) + #debuglog("keyboard-device-info", devicepathlist[deviceidx]) + #debuglog("keyboard-device-info", str(tmpdevice.info)) + except Exception as deverr: + try: + debuglog("keyboard-deviceerror", str(deverr)+ " "+ devicepathlist[deviceidx]) + except: + debuglog("keyboard-deviceerror", "Error "+devicepathlist[deviceidx]) + deviceidx = deviceidx + 1 + + try: + debuglog("keyboard-update", str(len(devicefdlist))+" Devices") + while len(devicefdlist) > 0: + # Exception when one of the devices gets removed + # Wait for events on any registered device + r, w, x = select(devicefdlist, [], [], READTIMEOUTSECS) + for fd in r: + found = False + curdevicefw = "" + deviceidx = 0 + while deviceidx < len(devicefdlist): + if devicefdlist[deviceidx] == fd: + curdevicefw = devicefwlist[deviceidx] + found = True + break + deviceidx = deviceidx + 1 + if found: + for event in devicelist[deviceidx].read(): + try: + # Process the event + #print("Device: "+devicelist[deviceidx].path+", Event: ", event) + #debuglog("keyboard-event", "Device: "+devicelist[deviceidx].path+", Event: "+str(event)) + if event.type == ecodes.EV_KEY: + key_event = categorize(event) + keycodelist = [] + # 2 hold, 0 release, 1 press + if event.value == 2 or event.value == 1: + #debuglog("keyboard-event", "Mode:"+str(event.value)+" Key Code: "+str(key_event.keycode)) + + if isinstance(key_event.keycode, str): + keycodelist = [key_event.keycode] + else: + keycodelist = key_event.keycode + else: + continue + + keycodelistidx = 0 + while keycodelistidx < len(keycodelist): + tmpkeycode = keycodelist[keycodelistidx] + if curdevicefw == "314": + # Remap printscreen event as pause and vice versa for special handling + if tmpkeycode == "KEY_PRINTSCREEN": + tmpkeycode = KEYCODE_PAUSE + elif tmpkeycode == "KEY_SYSRQ": + # This gets fired for some devices + tmpkeycode = KEYCODE_PAUSE + elif tmpkeycode == KEYCODE_PAUSE: + # Some other key so it will not fire + tmpkeycode = "KEY_PRINTSCREEN" + #debuglog("keyboard-event", "FW:" + curdevicefw+ " Key Code: "+tmpkeycode + " Press:"+keycodelist[keycodelistidx]) + + + keycodelistidx = keycodelistidx + 1 + if tmpkeycode not in [KEYCODE_BRIGHTNESSDOWN, KEYCODE_BRIGHTNESSUP, KEYCODE_VOLUMEDOWN, KEYCODE_VOLUMEUP]: + if event.value == 2: + # Skip hold for unhandled keys + continue + elif tmpkeycode not in [KEYCODE_PAUSE, KEYCODE_MUTE]: + # Skip press for unhandled keys + continue + + tmptime = time.time() + finalmode = event.value + if event.value == 2: + # Hold needs checking + if tmpkeycode in keypresstimestamp: + # Guard time before first for hold + if (tmptime - keypresstimestamp[tmpkeycode]) >= FIRSTHOLDINTERVALSEC: + # Guard time for hold + if tmpkeycode in keyholdtimestamp: + if (tmptime - keyholdtimestamp[tmpkeycode]) < HOLDWAITINTERVALSEC: + #debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode)+" - Skip") + continue + else: + #debuglog("keyboard-event", "Hold Key Code: "+str(tmpkeycode)+" - Skip") + continue + else: + # Should not happen, but treat as if first press + finalmode = 1 + + #debuglog("keyboard-event", "Mode:"+str(event.value) + " Final:"+str(finalmode)+" " +str(tmpkeycode)) + + if finalmode == 1: + keypresstimestamp[tmpkeycode] = tmptime + writeq.put("PRESS+"+tmpkeycode) + else: + keyholdtimestamp[tmpkeycode] = tmptime + writeq.put("HOLD+"+tmpkeycode) + + except Exception as keyhandleerr: + try: + debuglog("keyboard-keyerror", str(keyhandleerr)) + except: + debuglog("keyboard-keyerror", "Error") + + newpathlist = keyboardevent_getdevicepaths() + if keyboardevent_devicechanged(devicepathlist, newpathlist): + debuglog("keyboard-update", "Device list changed") + break + + except Exception as e: + try: + debuglog("keyboard-mainerror", str(e)) + except: + debuglog("keyboard-mainerror", "Error") + + # Close devices + while len(devicelist) > 0: + tmpdevice = devicelist.pop(0) + try: + tmpdevice.close() + except: + pass + + except Exception as mainerr: + time.sleep(10) + # While True + try: + writeq.put("EXIT") + except Exception: + pass + + +if len(sys.argv) > 1: + cmd = sys.argv[1].upper() + if cmd == "SERVICE": + if createlockfile(KEYBOARD_LOCKFILE) == True: + debuglog("keyboard-service", "Already running") + else: + try: + debuglog("keyboard-service", "Service Starting") + ipcq = Queue() + t1 = Thread(target = keyboardevemt_keyhandler, args =(ipcq, )) + t2 = Thread(target = keyboardevent_monitor, args =(ipcq, )) + t1.start() + t2.start() + + ipcq.join() + + except Exception as einit: + try: + debuglog("keyboard-service-error", str(einit)) + except: + debuglog("keyboard-service-error", "Error") + debuglog("keyboard-service", "Service Stopped") + deletelockfile(KEYBOARD_LOCKFILE) diff --git a/pythonscript/kickstarter/argononeup-eepromconfig.py b/pythonscript/kickstarter/argononeup-eepromconfig.py new file mode 100644 index 0000000..d7c486f --- /dev/null +++ b/pythonscript/kickstarter/argononeup-eepromconfig.py @@ -0,0 +1,568 @@ +#!/usr/bin/env python3 + +# Based on /usr/bin/rpi-eeprom-config of bookworm +""" +rpi-eeprom-config +""" + +import argparse +import atexit +import os +import subprocess +import string +import struct +import sys +import tempfile +import time + +VALID_IMAGE_SIZES = [512 * 1024, 2 * 1024 * 1024] + +BOOTCONF_TXT = 'bootconf.txt' +BOOTCONF_SIG = 'bootconf.sig' +PUBKEY_BIN = 'pubkey.bin' + +# Each section starts with a magic number followed by a 32 bit offset to the +# next section (big-endian). +# The number, order and size of the sections depends on the bootloader version +# but the following mask can be used to test for section headers and skip +# unknown data. +# +# The last 4KB of the EEPROM image is reserved for internal use by the +# bootloader and may be overwritten during the update process. +MAGIC = 0x55aaf00f +PAD_MAGIC = 0x55aafeef +MAGIC_MASK = 0xfffff00f +FILE_MAGIC = 0x55aaf11f # id for modifiable files +FILE_HDR_LEN = 20 +FILENAME_LEN = 12 +TEMP_DIR = None + +# Modifiable files are stored in a single 4K erasable sector. +# The max content 4076 bytes because of the file header. +ERASE_ALIGN_SIZE = 4096 +MAX_FILE_SIZE = ERASE_ALIGN_SIZE - FILE_HDR_LEN + +DEBUG = False + +# BEGIN: Argon40 added methods +def argon_rpisupported(): + # bcm2711 = pi4, bcm2712 = pi5 + return rpi5() + +def argon_edit_config(): + # modified/stripped version of edit_config + + config_src = '' + # If there is a pending update then use the configuration from + # that in order to support incremental updates. Otherwise, + # use the current EEPROM configuration. + bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip() + pending = os.path.join(bootfs, 'pieeprom.upd') + if os.path.exists(pending): + config_src = pending + image = BootloaderImage(pending) + current_config = image.get_file(BOOTCONF_TXT).decode('utf-8') + else: + current_config, config_src = read_current_config() + + # Add PSU Mas Current etc if not yet set + foundnewsetting = 0 + addsetting="\nPSU_MAX_CURRENT=5000" + current_config_lines = current_config.splitlines() + new_config = current_config + lineidx = 0 + while lineidx < len(current_config_lines): + current_config_pair = current_config_lines[lineidx].split("=") + newsetting = "" + if current_config_pair[0] == "PSU_MAX_CURRENT": + newsetting = "PSU_MAX_CURRENT=5000" + + if newsetting != "": + addsetting = addsetting.replace("\n"+newsetting,"",1) + if current_config_lines[lineidx] != newsetting: + foundnewsetting = foundnewsetting + 1 + new_config = new_config.replace(current_config_lines[lineidx], newsetting, 1) + + lineidx = lineidx + 1 + + if addsetting != "": + # Append additional settings after [all] + new_config = new_config.replace("[all]", "[all]"+addsetting, 1) + foundnewsetting = foundnewsetting + 1 + + if foundnewsetting == 0: + # Already configured + print("EEPROM settings up to date") + sys.exit(0) + + # Skipped editor and write new config to temp file + create_tempdir() + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') + out = open(tmp_conf, 'w') + out.write(new_config) + out.close() + + # Apply updates + + apply_update(tmp_conf, None, config_src) + +# END: Argon40 added methods + + +def debug(s): + if DEBUG: + sys.stderr.write(s + '\n') + + +def rpi4(): + compatible_path = "/sys/firmware/devicetree/base/compatible" + if os.path.exists(compatible_path): + with open(compatible_path, "rb") as f: + compatible = f.read().decode('utf-8') + if "bcm2711" in compatible: + return True + return False + +def rpi5(): + compatible_path = "/sys/firmware/devicetree/base/compatible" + if os.path.exists(compatible_path): + with open(compatible_path, "rb") as f: + compatible = f.read().decode('utf-8') + if "bcm2712" in compatible: + return True + return False + +def exit_handler(): + """ + Delete any temporary files. + """ + if TEMP_DIR is not None and os.path.exists(TEMP_DIR): + tmp_image = os.path.join(TEMP_DIR, 'pieeprom.upd') + if os.path.exists(tmp_image): + os.remove(tmp_image) + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') + if os.path.exists(tmp_conf): + os.remove(tmp_conf) + os.rmdir(TEMP_DIR) + +def create_tempdir(): + global TEMP_DIR + if TEMP_DIR is None: + TEMP_DIR = tempfile.mkdtemp() + +def pemtobin(infile): + """ + Converts an RSA public key into the format expected by the bootloader. + """ + # Import the package here to make this a weak dependency. + from Cryptodome.PublicKey import RSA + + arr = bytearray() + f = open(infile,'r') + key = RSA.importKey(f.read()) + + if key.size_in_bits() != 2048: + raise Exception("RSA key size must be 2048") + + # Export N and E in little endian format + arr.extend(key.n.to_bytes(256, byteorder='little')) + arr.extend(key.e.to_bytes(8, byteorder='little')) + return arr + +def exit_error(msg): + """ + Trapped a fatal error, output message to stderr and exit with non-zero + return code. + """ + sys.stderr.write("ERROR: %s\n" % msg) + sys.exit(1) + +def shell_cmd(args): + """ + Executes a shell command waits for completion returning STDOUT. If an + error occurs then exit and output the subprocess stdout, stderr messages + for debug. + """ + start = time.time() + arg_str = ' '.join(args) + result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + while time.time() - start < 5: + if result.poll() is not None: + break + + if result.poll() is None: + exit_error("%s timeout" % arg_str) + + if result.returncode != 0: + exit_error("%s failed: %d\n %s\n %s\n" % + (arg_str, result.returncode, result.stdout.read(), result.stderr.read())) + else: + return result.stdout.read().decode('utf-8') + +def get_latest_eeprom(): + """ + Returns the path of the latest EEPROM image file if it exists. + """ + latest = shell_cmd(['rpi-eeprom-update', '-l']).rstrip() + if not os.path.exists(latest): + exit_error("EEPROM image '%s' not found" % latest) + return latest + +def apply_update(config, eeprom=None, config_src=None): + """ + Applies the config file to the latest available EEPROM image and spawns + rpi-eeprom-update to schedule the update at the next reboot. + """ + if eeprom is not None: + eeprom_image = eeprom + else: + eeprom_image = get_latest_eeprom() + create_tempdir() + + # Replace the contents of bootconf.txt with the contents of the config file + tmp_update = os.path.join(TEMP_DIR, 'pieeprom.upd') + image = BootloaderImage(eeprom_image, tmp_update) + image.update_file(config, BOOTCONF_TXT) + image.write() + + config_str = open(config).read() + if config_src is None: + config_src = '' + sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig_src: %s\nconfig: %s\n%s\n%s\n%s\n" % + (eeprom_image, config_src, config, '#' * 80, config_str, '#' * 80)) + + sys.stdout.write("\n*** To cancel this update run 'sudo rpi-eeprom-update -r' ***\n\n") + + # Ignore APT package checksums so that this doesn't fail when used + # with EEPROMs with configs delivered outside of APT. + # The checksums are really just a safety check for automatic updates. + args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update] + resp = shell_cmd(args) + sys.stdout.write(resp) + +def edit_config(eeprom=None): + """ + Implements something like 'git commit' for editing EEPROM configs. + """ + # Default to nano if $EDITOR is not defined. + editor = 'nano' + if 'EDITOR' in os.environ: + editor = os.environ['EDITOR'] + + config_src = '' + # If there is a pending update then use the configuration from + # that in order to support incremental updates. Otherwise, + # use the current EEPROM configuration. + bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip() + pending = os.path.join(bootfs, 'pieeprom.upd') + if os.path.exists(pending): + config_src = pending + image = BootloaderImage(pending) + current_config = image.get_file(BOOTCONF_TXT).decode('utf-8') + else: + current_config, config_src = read_current_config() + + create_tempdir() + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') + out = open(tmp_conf, 'w') + out.write(current_config) + out.close() + cmd = "\'%s\' \'%s\'" % (editor, tmp_conf) + result = os.system(cmd) + if result != 0: + exit_error("Aborting update because \'%s\' exited with code %d." % (cmd, result)) + + new_config = open(tmp_conf, 'r').read() + if len(new_config.splitlines()) < 2: + exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf) + apply_update(tmp_conf, eeprom, config_src) + +def read_current_config(): + """ + Reads the configuration used by the current bootloader. + """ + fw_base = "/sys/firmware/devicetree/base/" + nvmem_base = "/sys/bus/nvmem/devices/" + + if os.path.exists(fw_base + "/aliases/blconfig"): + with open(fw_base + "/aliases/blconfig", "rb") as f: + nvmem_ofnode_path = fw_base + f.read().decode('utf-8') + for d in os.listdir(nvmem_base): + if os.path.realpath(nvmem_base + d + "/of_node") in os.path.normpath(nvmem_ofnode_path): + return (open(nvmem_base + d + "/nvmem", "rb").read().decode('utf-8'), "blconfig device") + + return (shell_cmd(['vcgencmd', 'bootloader_config']), "vcgencmd bootloader_config") + +class ImageSection: + def __init__(self, magic, offset, length, filename=''): + self.magic = magic + self.offset = offset + self.length = length + self.filename = filename + debug("ImageSection %x offset %d length %d %s" % (magic, offset, length, filename)) + +class BootloaderImage(object): + def __init__(self, filename, output=None): + """ + Instantiates a Bootloader image writer with a source eeprom (filename) + and optionally an output filename. + """ + self._filename = filename + self._sections = [] + self._image_size = 0 + try: + self._bytes = bytearray(open(filename, 'rb').read()) + except IOError as err: + exit_error("Failed to read \'%s\'\n%s\n" % (filename, str(err))) + self._out = None + if output is not None: + self._out = open(output, 'wb') + + self._image_size = len(self._bytes) + if self._image_size not in VALID_IMAGE_SIZES: + exit_error("%s: Expected size %d bytes actual size %d bytes" % + (filename, self._image_size, len(self._bytes))) + self.parse() + + def parse(self): + """ + Builds a table of offsets to the different sections in the EEPROM. + """ + offset = 0 + magic = 0 + while offset < self._image_size: + magic, length = struct.unpack_from('>LL', self._bytes, offset) + if magic == 0x0 or magic == 0xffffffff: + break # EOF + elif (magic & MAGIC_MASK) != MAGIC: + raise Exception('EEPROM is corrupted %x %x %x' % (magic, magic & MAGIC_MASK, MAGIC)) + + filename = '' + if magic == FILE_MAGIC: # Found a file + # Discard trailing null characters used to pad filename + filename = self._bytes[offset + 8: offset + FILE_HDR_LEN].decode('utf-8').replace('\0', '') + debug("section at %d length %d magic %08x %s" % (offset, length, magic, filename)) + self._sections.append(ImageSection(magic, offset, length, filename)) + + offset += 8 + length # length + type + offset = (offset + 7) & ~7 + + def find_file(self, filename): + """ + Returns the offset, length and whether this is the last section in the + EEPROM for a modifiable file within the image. + """ + offset = -1 + length = -1 + is_last = False + + next_offset = self._image_size - ERASE_ALIGN_SIZE # Don't create padding inside the bootloader scratch page + for i in range(0, len(self._sections)): + s = self._sections[i] + if s.magic == FILE_MAGIC and s.filename == filename: + is_last = (i == len(self._sections) - 1) + offset = s.offset + length = s.length + break + + # Find the start of the next non padding section + i += 1 + while i < len(self._sections): + if self._sections[i].magic == PAD_MAGIC: + i += 1 + else: + next_offset = self._sections[i].offset + break + ret = (offset, length, is_last, next_offset) + debug('%s offset %d length %d is-last %d next %d' % (filename, ret[0], ret[1], ret[2], ret[3])) + return ret + + def update(self, src_bytes, dst_filename): + """ + Replaces a modifiable file with specified byte array. + """ + hdr_offset, length, is_last, next_offset = self.find_file(dst_filename) + update_len = len(src_bytes) + FILE_HDR_LEN + + if hdr_offset + update_len > self._image_size - ERASE_ALIGN_SIZE: + raise Exception('No space available - image past EOF.') + + if hdr_offset < 0: + raise Exception('Update target %s not found' % dst_filename) + + if hdr_offset + update_len > next_offset: + raise Exception('Update %d bytes is larger than section size %d' % (update_len, next_offset - hdr_offset)) + + new_len = len(src_bytes) + FILENAME_LEN + 4 + struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len) + struct.pack_into(("%ds" % len(src_bytes)), self._bytes, + hdr_offset + 4 + FILE_HDR_LEN, src_bytes) + + # If the new file is smaller than the old file then set any old + # data which is now unused to all ones (erase value) + pad_start = hdr_offset + 4 + FILE_HDR_LEN + len(src_bytes) + + # Add padding up to 8-byte boundary + while pad_start % 8 != 0: + struct.pack_into('B', self._bytes, pad_start, 0xff) + pad_start += 1 + + # Create a padding section unless the padding size is smaller than the + # size of a section head. Padding is allowed in the last section but + # by convention bootconf.txt is the last section and there's no need to + # pad to the end of the sector. This also ensures that the loopback + # config read/write tests produce identical binaries. + pad_bytes = next_offset - pad_start + if pad_bytes > 8 and not is_last: + pad_bytes -= 8 + struct.pack_into('>i', self._bytes, pad_start, PAD_MAGIC) + pad_start += 4 + struct.pack_into('>i', self._bytes, pad_start, pad_bytes) + pad_start += 4 + + debug("pad %d" % pad_bytes) + pad = 0 + while pad < pad_bytes: + struct.pack_into('B', self._bytes, pad_start + pad, 0xff) + pad = pad + 1 + + def update_key(self, src_pem, dst_filename): + """ + Replaces the specified public key entry with the public key values extracted + from the source PEM file. + """ + pubkey_bytes = pemtobin(src_pem) + self.update(pubkey_bytes, dst_filename) + + def update_file(self, src_filename, dst_filename): + """ + Replaces the contents of dst_filename in the EEPROM with the contents of src_file. + """ + src_bytes = open(src_filename, 'rb').read() + if len(src_bytes) > MAX_FILE_SIZE: + raise Exception("src file %s is too large (%d bytes). The maximum size is %d bytes." + % (src_filename, len(src_bytes), MAX_FILE_SIZE)) + self.update(src_bytes, dst_filename) + + def write(self): + """ + Writes the updated EEPROM image to stdout or the specified output file. + """ + if self._out is not None: + self._out.write(self._bytes) + self._out.close() + else: + if hasattr(sys.stdout, 'buffer'): + sys.stdout.buffer.write(self._bytes) + else: + sys.stdout.write(self._bytes) + + def get_file(self, filename): + hdr_offset, length, is_last, next_offset = self.find_file(filename) + offset = hdr_offset + 4 + FILE_HDR_LEN + file_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4] + return file_bytes + + def extract_files(self): + for i in range(0, len(self._sections)): + s = self._sections[i] + if s.magic == FILE_MAGIC: + file_bytes = self.get_file(s.filename) + open(s.filename, 'wb').write(file_bytes) + + def read(self): + config_bytes = self.get_file('bootconf.txt') + if self._out is not None: + self._out.write(config_bytes) + self._out.close() + else: + if hasattr(sys.stdout, 'buffer'): + sys.stdout.buffer.write(config_bytes) + else: + sys.stdout.write(config_bytes) + +def main(): + """ + Utility for reading and writing the configuration file in the + Raspberry Pi bootloader EEPROM image. + """ + description = """\ +Bootloader EEPROM configuration tool for the Raspberry Pi 4 and Raspberry Pi 5. +Operating modes: + +1. Outputs the current bootloader configuration to STDOUT if no arguments are + specified OR the given output file if --out is specified. + + rpi-eeprom-config [--out boot.conf] + +2. Extracts the configuration file from the given 'eeprom' file and outputs + the result to STDOUT or the output file if --output is specified. + + rpi-eeprom-config pieeprom.bin [--out boot.conf] + +3. Writes a new EEPROM image replacing the configuration file with the contents + of the file specified by --config. + + rpi-eeprom-config --config boot.conf --out newimage.bin pieeprom.bin + + The new image file can be installed via rpi-eeprom-update + rpi-eeprom-update -d -f newimage.bin + +4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update + to schedule an update of the bootloader when the system is rebooted. + + Since this command launches rpi-eeprom-update to schedule the EEPROM update + it must be run as root. + + sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin] + + If the 'eeprom' argument is not specified then the latest available image + is selected by calling 'rpi-eeprom-update -l'. + +5. The '--edit' parameter behaves the same as '--apply' except that instead of + applying a predefined configuration file a text editor is launched with the + contents of the current EEPROM configuration. + + Since this command launches rpi-eeprom-update to schedule the EEPROM update + it must be run as root. + + The configuration file will be taken from: + * The blconfig reserved memory nvmem device + * The cached bootloader configuration 'vcgencmd bootloader_config' + * The current pending update - typically /boot/pieeprom.upd + + sudo -E rpi-eeprom-config --edit [pieeprom.bin] + + To cancel the pending update run 'sudo rpi-eeprom-update -r' + + The default text editor is nano and may be overridden by setting the 'EDITOR' + environment variable and passing '-E' to 'sudo' to preserve the environment. + +6. Signing the bootloader config file. + Updates an EEPROM binary with a signed config file (created by rpi-eeprom-digest) plus + the corresponding RSA public key. + + Requires Python Cryptodomex libraries and OpenSSL. To install on Raspberry Pi OS run:- + sudo apt install openssl python-pip + sudo python3 -m pip install cryptodomex + + rpi-eeprom-digest -k private.pem -i bootconf.txt -o bootconf.sig + rpi-eeprom-config --config bootconf.txt --digest bootconf.sig --pubkey public.pem --out pieeprom-signed.bin pieeprom.bin + + Currently, the signing process is a separate step so can't be used with the --edit or --apply modes. + + +See 'rpi-eeprom-update -h' for more information about the available EEPROM images. +""" + + if os.getuid() != 0: + exit_error("Please run as root") + elif not argon_rpisupported(): + # Skip + sys.exit(0) + argon_edit_config() + +if __name__ == '__main__': + atexit.register(exit_handler) + main() diff --git a/pythonscript/kickstarter/argononeup-lidconfig.sh b/pythonscript/kickstarter/argononeup-lidconfig.sh new file mode 100644 index 0000000..bcc756a --- /dev/null +++ b/pythonscript/kickstarter/argononeup-lidconfig.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +tmpfile="/dev/shm/argontmpconf.txt" +daemonconfigfile="/etc/argononeupd.conf" + +if [ -f "$daemonconfigfile" ] +then + . $daemonconfigfile +fi + +if [ -z "$lidshutdownsecs" ] +then + lidshutdownsecs=0 +fi + + +mainloopflag=1 +newmode=0 + + +get_number () { + read curnumber + if [ -z "$curnumber" ] + then + echo "-2" + return + elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] + then + if [ $curnumber -lt 0 ] + then + echo "-1" + return + fi + echo $curnumber + return + fi + echo "-1" + return +} + +while [ $mainloopflag -eq 1 ] +do + + lidshutdownmins=$((lidshutdownsecs / 60)) + + + echo "------------------------------------------" + echo " Argon One Up Lid Configuration Tool" + echo "------------------------------------------" + + echo + echo "Lid Close Behavior:" + if [ $lidshutdownsecs -lt 1 ] + then + echo "(Do Nothing)" + else + echo "(Shut down after $lidshutdownmins minute(s))" + fi + echo " 1. Do Nothing" + echo " 2. Shutdown" + echo + echo " 0. Exit" + echo "NOTE: You can also edit $daemonconfigfile directly" + echo -n "Enter Number (0-2):" + newmode=$( get_number ) + + if [[ $newmode -eq 0 ]] + then + mainloopflag=0 + elif [ $newmode -eq 1 ] + then + lidshutdownsecs=0 + elif [ $newmode -eq 2 ] + then + maxmins=120 + echo "Please provide number of minutes until shutdown:" + echo -n "Enter Number (1-$maxmins):" + curval=$( get_number ) + if [ $curval -gt $maxmins ] + then + newmode=0 + echo "Invalid input" + elif [ $curval -lt 1 ] + then + newmode=0 + echo "Invalid input" + else + lidshutdownsecs=$((curval * 60)) + fi + fi + + if [ $newmode -eq 1 ] || [ $newmode -eq 2 ] + then + if [ -f "$daemonconfigfile" ] + then + grep -v 'lidshutdownsecs' "$daemonconfigfile" > $tmpfile + else + echo '#' > $tmpfile + echo '# Argon One Up Configuration' >> $tmpfile + echo '#' >> $tmpfile + fi + echo '# lidshutdownsecs number of seconds till shutdown when lid is closed 0 if do nothing' >> $tmpfile + echo "lidshutdownsecs=$lidshutdownsecs" >> $tmpfile + + sudo cp $tmpfile $daemonconfigfile + sudo chmod 666 $daemonconfigfile + + echo "Configuration updated." + + fi +done + +echo + diff --git a/pythonscript/kickstarter/argononeup.sh b/pythonscript/kickstarter/argononeup.sh new file mode 100644 index 0000000..8d667a3 --- /dev/null +++ b/pythonscript/kickstarter/argononeup.sh @@ -0,0 +1,471 @@ +#!/bin/bash + +echo "*************" +echo " Argon Setup " +echo "*************" + + +# Check time if need to 'fix' +NEEDSTIMESYNC=0 +LOCALTIME=$(date -u +%s%N | cut -b1-10) +GLOBALTIME=$(curl -s 'http://worldtimeapi.org/api/ip.txt' | grep unixtime | cut -b11-20) +TIMEDIFF=$((GLOBALTIME-LOCALTIME)) + +# about 26hrs, max timezone difference +if [ $TIMEDIFF -gt 100000 ] +then + NEEDSTIMESYNC=1 +fi + + +argon_time_error() { + echo "**********************************************" + echo "* WARNING: Device time seems to be incorrect *" + echo "* This may cause problems during setup. *" + echo "**********************************************" + echo "Possible Network Time Protocol Server issue" + echo "Try running the following to correct:" + echo " curl -k https://download.argon40.com/tools/setntpserver.sh | bash" +} + +if [ $NEEDSTIMESYNC -eq 1 ] +then + argon_time_error +fi + + +# Helper variables +ARGONDOWNLOADSERVER=https://download.argon40.com + +INSTALLATIONFOLDER=/etc/argon +pythonbin="sudo /usr/bin/python3" + +versioninfoscript=$INSTALLATIONFOLDER/argon-versioninfo.sh + +uninstallscript=$INSTALLATIONFOLDER/argon-uninstall.sh +configscript=$INSTALLATIONFOLDER/argon-config +argondashboardscript=$INSTALLATIONFOLDER/argondashboard.py + + +setupmode="Setup" + +if [ -f $configscript ] +then + setupmode="Update" + echo "Updating files" +else + sudo mkdir $INSTALLATIONFOLDER + sudo chmod 755 $INSTALLATIONFOLDER +fi + +########## +# Start code lifted from raspi-config +# set_config_var based on raspi-config + +if [ -e /boot/firmware/config.txt ] ; then + FIRMWARE=/firmware +else + FIRMWARE= +fi +CONFIG=/boot${FIRMWARE}/config.txt + +set_config_var() { + if ! grep -q -E "$1=$2" $3 ; then + echo "$1=$2" | sudo tee -a $3 > /dev/null + fi +} + +# End code lifted from raspi-config +########## + +# Reuse set_config_var +set_nvme_default() { + set_config_var dtparam nvme $CONFIG + set_config_var dtparam=pciex1_gen 3 $CONFIG +} + +set_external_antenna() { + set_config_var dtparam ant2 $CONFIG +} + + +argon_check_pkg() { + RESULT=$(dpkg-query -W -f='${Status}\n' "$1" 2> /dev/null | grep "installed") + + if [ "" == "$RESULT" ]; then + echo "NG" + else + echo "OK" + fi +} + + +CHECKDEVICE="oneup" # Hardcoded for argononeup + +CHECKGPIOMODE="libgpiod" # libgpiod or rpigpio + +# Check if Raspbian, Ubuntu, others +CHECKPLATFORM="Others" +CHECKPLATFORMVERSION="" +CHECKPLATFORMVERSIONNUM="" +if [ -f "/etc/os-release" ] +then + source /etc/os-release + if [ "$ID" = "raspbian" ] + then + CHECKPLATFORM="Raspbian" + CHECKPLATFORMVERSION=$VERSION_ID + elif [ "$ID" = "debian" ] + then + # For backwards compatibility, continue using raspbian + CHECKPLATFORM="Raspbian" + CHECKPLATFORMVERSION=$VERSION_ID + elif [ "$ID" = "ubuntu" ] + then + CHECKPLATFORM="Ubuntu" + CHECKPLATFORMVERSION=$VERSION_ID + fi + echo ${CHECKPLATFORMVERSION} | grep -e "\." > /dev/null + if [ $? -eq 0 ] + then + CHECKPLATFORMVERSIONNUM=`cut -d "." -f2 <<< $CHECKPLATFORMVERSION ` + CHECKPLATFORMVERSION=`cut -d "." -f1 <<< $CHECKPLATFORMVERSION ` + fi +fi + +gpiopkg="python3-libgpiod" +if [ "$CHECKGPIOMODE" = "rpigpio" ] +then + if [ "$CHECKPLATFORM" = "Raspbian" ] + then + gpiopkg="raspi-gpio python3-rpi.gpio" + else + gpiopkg="python3-rpi.gpio" + fi +fi + +pkglist=($gpiopkg python3-smbus i2c-tools python3-evdev ddcutil) + +echo "Installing/updating dependencies..." + +for curpkg in ${pkglist[@]}; do + sudo apt-get install -y $curpkg + RESULT=$(argon_check_pkg "$curpkg") + if [ "NG" == "$RESULT" ] + then + echo "********************************************************************" + echo "Please also connect device to the internet and restart installation." + echo "********************************************************************" + exit + fi +done + +echo "Updating configuration ..." + +# Ubuntu Mate for RPi has raspi-config too +command -v raspi-config &> /dev/null +if [ $? -eq 0 ] +then + # Enable i2c + sudo raspi-config nonint do_i2c 0 +fi + +# Added to enabled NVMe for pi5 +set_nvme_default + +# Fan Setup +basename="argononeup" +daemonname=$basename"d" +eepromrpiscript="/usr/bin/rpi-eeprom-config" +eepromconfigscript=$INSTALLATIONFOLDER/${basename}-eepromconfig.py +daemonscript=$INSTALLATIONFOLDER/$daemonname.py +daemonservice=/lib/systemd/system/$daemonname.service +userdaemonservice=/etc/systemd/user/${daemonname}user.service +daemonconfigfile=/etc/$daemonname.conf + +lidconfigscript=$INSTALLATIONFOLDER/${basename}-lidconfig.sh + + +for TMPDIRECTORY in "/lib/systemd/system" +do + sudo mkdir -p "$TMPDIRECTORY" + sudo chmod 755 $TMPDIRECTORY + sudo chown root:root "$TMPDIRECTORY" +done + +echo "Installing/Updating scripts and services ..." + +if [ ! -f $daemonconfigfile ]; then + # Generate config file for fan speed + sudo touch $daemonconfigfile + sudo chmod 666 $daemonconfigfile + echo '#' >> $daemonconfigfile + echo '# Argon One Up Configuration' >> $daemonconfigfile + echo '#' >> $daemonconfigfile + echo '# lidshutdownsecs number of seconds till shutdown when lid is closed 0 if do nothing' >> $daemonconfigfile + echo 'lidshutdownsecs=300' >> $daemonconfigfile +fi + +# Lid Config Script +sudo wget $ARGONDOWNLOADSERVER/scripts/argononeup-lidconfig.sh -O $lidconfigscript --quiet +sudo chmod 755 $lidconfigscript + + +if [ -f "$eepromrpiscript" ] +then + # EEPROM Config Script + sudo wget $ARGONDOWNLOADSERVER/scripts/argon-rpi-eeprom-config-psu.py -O $eepromconfigscript --quiet + sudo chmod 755 $eepromconfigscript +fi + +# Daemon/Service Files +sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.py -O $daemonscript --quiet +sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.service -O $daemonservice --quiet +sudo chmod 644 $daemonservice + +sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}user.service -O $userdaemonservice --quiet +sudo chmod 644 $userdaemonservice + + +# Battery Images +if [ ! -d "$INSTALLATIONFOLDER/ups" ] +then + sudo mkdir $INSTALLATIONFOLDER/ups +fi +sudo wget $ARGONDOWNLOADSERVER/ups/upsimg.tar.gz -O $INSTALLATIONFOLDER/ups/upsimg.tar.gz --quiet +sudo tar xfz $INSTALLATIONFOLDER/ups/upsimg.tar.gz -C $INSTALLATIONFOLDER/ups/ +sudo rm -Rf $INSTALLATIONFOLDER/ups/upsimg.tar.gz + +sudo wget "$ARGONDOWNLOADSERVER/scripts/argonpowerbutton-${CHECKGPIOMODE}.py" -O $INSTALLATIONFOLDER/argonpowerbutton.py --quiet + +sudo wget $ARGONDOWNLOADSERVER/scripts/argonkeyboard.py -O $INSTALLATIONFOLDER/argonkeyboard.py --quiet + +# Other utility scripts +sudo wget $ARGONDOWNLOADSERVER/scripts/argondashboard.py -O $INSTALLATIONFOLDER/argondashboard.py --quiet + +sudo wget $ARGONDOWNLOADSERVER/scripts/argon-versioninfo.sh -O $versioninfoscript --quiet +sudo chmod 755 $versioninfoscript + +sudo wget $ARGONDOWNLOADSERVER/scripts/argonsysinfo.py -O $INSTALLATIONFOLDER/argonsysinfo.py --quiet + +sudo wget $ARGONDOWNLOADSERVER/scripts/argonregister-v1.py -O $INSTALLATIONFOLDER/argonregister.py --quiet + + +# Argon Uninstall Script +sudo wget $ARGONDOWNLOADSERVER/scripts/argon-uninstall.sh -O $uninstallscript --quiet +sudo chmod 755 $uninstallscript + +# Argon Config Script +if [ -f $configscript ]; then + sudo rm $configscript +fi +sudo touch $configscript + +# To ensure we can write the following lines +sudo chmod 666 $configscript + +echo '#!/bin/bash' >> $configscript + +echo 'echo "--------------------------"' >> $configscript +echo 'echo "Argon Configuration Tool"' >> $configscript +echo "$versioninfoscript simple" >> $configscript +echo 'echo "--------------------------"' >> $configscript + +echo 'get_number () {' >> $configscript +echo ' read curnumber' >> $configscript +echo ' if [ -z "$curnumber" ]' >> $configscript +echo ' then' >> $configscript +echo ' echo "-2"' >> $configscript +echo ' return' >> $configscript +echo ' elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]' >> $configscript +echo ' then' >> $configscript +echo ' if [ $curnumber -lt 0 ]' >> $configscript +echo ' then' >> $configscript +echo ' echo "-1"' >> $configscript +echo ' return' >> $configscript +echo ' elif [ $curnumber -gt 100 ]' >> $configscript +echo ' then' >> $configscript +echo ' echo "-1"' >> $configscript +echo ' return' >> $configscript +echo ' fi ' >> $configscript +echo ' echo $curnumber' >> $configscript +echo ' return' >> $configscript +echo ' fi' >> $configscript +echo ' echo "-1"' >> $configscript +echo ' return' >> $configscript +echo '}' >> $configscript +echo '' >> $configscript + +echo 'mainloopflag=1' >> $configscript +echo 'while [ $mainloopflag -eq 1 ]' >> $configscript +echo 'do' >> $configscript +echo ' echo' >> $configscript +echo ' echo "Choose Option:"' >> $configscript + + +echo ' echo " 1. Get Battery Status"' >> $configscript +echo ' echo " 2. Configure Lid Behavior"' >> $configscript + + +uninstalloption="4" + +statusoption=$(($uninstalloption-1)) +echo " echo \" $statusoption. Dashboard\"" >> $configscript + +echo " echo \" $uninstalloption. Uninstall\"" >> $configscript +echo ' echo ""' >> $configscript +echo ' echo " 0. Exit"' >> $configscript +echo " echo -n \"Enter Number (0-$uninstalloption):\"" >> $configscript +echo ' newmode=$( get_number )' >> $configscript + + + +echo ' if [ $newmode -eq 0 ]' >> $configscript +echo ' then' >> $configscript +echo ' echo "Thank you."' >> $configscript +echo ' mainloopflag=0' >> $configscript +echo ' elif [ $newmode -eq 1 ]' >> $configscript +echo ' then' >> $configscript + +# Option 1 +echo " $pythonbin $daemonscript GETBATTERY" >> $configscript + +echo ' elif [ $newmode -eq 2 ]' >> $configscript +echo ' then' >> $configscript + +# Option 2 +echo " $lidconfigscript" >> $configscript + +# Standard options +echo " elif [ \$newmode -eq $statusoption ]" >> $configscript +echo ' then' >> $configscript +echo " $pythonbin $argondashboardscript" >> $configscript + +echo " elif [ \$newmode -eq $uninstalloption ]" >> $configscript +echo ' then' >> $configscript +echo " $uninstallscript" >> $configscript +echo ' mainloopflag=0' >> $configscript +echo ' fi' >> $configscript +echo 'done' >> $configscript + +sudo chmod 755 $configscript + +# Desktop Icon +destfoldername=$USERNAME +if [ -z "$destfoldername" ] +then + destfoldername=$USER +fi +if [ -z "$destfoldername" ] +then + destfoldername="pi" +fi + +shortcutfile="/home/$destfoldername/Desktop/argononeup.desktop" +if [ -d "/home/$destfoldername/Desktop" ] +then + echo "Creating/Updating Desktop Elements ..." + + terminalcmd="lxterminal --working-directory=/home/$destfoldername/ -t" + if [ -f "/home/$destfoldername/.twisteros.twid" ] + then + terminalcmd="xfce4-terminal --default-working-directory=/home/$destfoldername/ -T" + fi + imagefile=argon40.png + sudo wget https://download.argon40.com/$imagefile -O /etc/argon/$imagefile --quiet + if [ -f $shortcutfile ]; then + sudo rm $shortcutfile + fi + + # Create Shortcuts + echo "[Desktop Entry]" > $shortcutfile + echo "Name=Argon Configuration" >> $shortcutfile + echo "Comment=Argon Configuration" >> $shortcutfile + echo "Icon=/etc/argon/$imagefile" >> $shortcutfile + echo 'Exec='$terminalcmd' "Argon Configuration" -e '$configscript >> $shortcutfile + echo "Type=Application" >> $shortcutfile + echo "Encoding=UTF-8" >> $shortcutfile + echo "Terminal=false" >> $shortcutfile + echo "Categories=None;" >> $shortcutfile + chmod 755 $shortcutfile +fi + +configcmd="$(basename -- $configscript)" + +echo "Initializing Services ..." + +# Force remove lock files +sudo rm -f /dev/shm/argononeupkeyboardlock.txt +sudo rm -f /dev/shm/argononeupkeyboardlock.txt.a + +if [ "$setupmode" = "Setup" ] +then + if [ -f "/usr/bin/$configcmd" ] + then + sudo rm /usr/bin/$configcmd + fi + sudo ln -s $configscript /usr/bin/$configcmd + + # Enable and Start Service(s) + sudo systemctl daemon-reload + sudo systemctl enable argononeupd.service + sudo systemctl start argononeupd.service +else + sudo systemctl daemon-reload + sudo systemctl restart argononeupd.service +fi + +# Enable and Start User Service(s) +for tmpuser in `awk -F: '{ if ($3 >= 1000) print $1 }' /etc/passwd` +do + if [ "$tmpuser" != "nobody" ] + then + if [ "$setupmode" = "Setup" ] + then + sudo -u "$tmpuser" systemctl --user enable argononeupduser.service + sudo -u "$tmpuser" systemctl --user start argononeupduser.service + else + sudo -u "$tmpuser" systemctl --user restart argononeupduser.service + fi + fi +done + +# Current user / fallback +if [ "$setupmode" = "Setup" ] +then + systemctl --user enable argononeupduser.service + systemctl --user start argononeupduser.service +else + systemctl --user restart argononeupduser.service +fi + +if [ "$CHECKPLATFORM" = "Raspbian" ] +then + if [ -f "$eepromrpiscript" ] + then + echo "Checking EEPROM ..." + sudo apt-get update && sudo apt-get upgrade -y + sudo rpi-eeprom-update + # EEPROM Config Script + sudo $eepromconfigscript + fi +else + echo "WARNING: EEPROM not updated. Please run this under Raspberry Pi OS" +fi + + +echo "*********************" +echo " $setupmode Completed " +echo "*********************" +$versioninfoscript +echo +echo "Use '$configcmd' to configure device" +echo + + + +if [ $NEEDSTIMESYNC -eq 1 ] +then + argon_time_error +fi + diff --git a/pythonscript/kickstarter/argononeupd.py b/pythonscript/kickstarter/argononeupd.py new file mode 100644 index 0000000..8ea1f0e --- /dev/null +++ b/pythonscript/kickstarter/argononeupd.py @@ -0,0 +1,466 @@ +#!/usr/bin/python3 + +# +# This script monitor battery via ic2 and keyboard events. +# +# Additional comments are found in each function below +# +# + +import sys +import os +import time + +from threading import Thread +from queue import Queue + +sys.path.append("/etc/argon/") +from argonregister import * +from argonpowerbutton import * + +# Initialize I2C Bus +bus = argonregister_initializebusobj() + +# Constants +ADDR_BATTERY = 0x64 + +UPS_LOGFILE="/dev/shm/upslog.txt" + + +################### +# Utilty Functions +################### + +# Debug Logger +def debuglog(typestr, logstr): + try: + DEBUGFILE="/dev/shm/argononeupdebuglog.txt" + tmpstrpadding = " " + + with open(DEBUGFILE, "a") as txt_file: + txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n") + except: + pass + + +# System Notifcation +def notifymessage(message, iscritical): + if not isinstance(message, str) or len(message.strip()) == 0: + return + + wftype="notify" + if iscritical: + wftype="critical" + os.system("export SUDO_UID=1000; wfpanelctl "+wftype+" \""+message+"\"") + os.system("export DISPLAY=:0.0; lxpanelctl notify \""+message+"\"") + + +############# +# Battery +############# +REG_CONTROL = 0x08 +REG_SOCALERT = 0x0b +REG_PROFILE = 0x10 +REG_ICSTATE = 0xA7 + + + +def battery_restart(): + # Set to active mode + try: + maxretry = 3 + while maxretry > 0: + maxretry = maxretry - 1 + + # Restart + bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x30) + time.sleep(0.5) + # Activate + bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x00) + time.sleep(0.5) + + # Wait for Ready Status + maxwaitsecs = 5 + while maxwaitsecs > 0: + tmpval = bus.read_byte_data(ADDR_BATTERY, REG_ICSTATE) + if (tmpval&0x0C) != 0: + debuglog("battery-activate", "Activated Successfully") + return 0 + time.sleep(1) + maxwaitsecs = maxwaitsecs - 1 + + + debuglog("battery-activate", "Failed to activate") + return 2 + except Exception as e: + try: + debuglog("battery-activateerror", str(e)) + except: + debuglog("battery-activateerror", "Activation Failed") + return 1 + + +def battery_getstatus(restartifnotactive): + try: + tmpval = bus.read_byte_data(ADDR_BATTERY, REG_CONTROL) + if tmpval != 0: + if restartifnotactive == True: + tmpval = battery_restart() + + if tmpval != 0: + debuglog("battery-status", "Inactive "+str(tmpval)) + return 2 + + tmpval = bus.read_byte_data(ADDR_BATTERY, REG_SOCALERT) + if (tmpval&0x80) == 0: + debuglog("battery-status", "Profile not ready "+str(tmpval)) + return 3 + + # OK + #debuglog("battery-status", "OK") + return 0 + except Exception as e: + try: + debuglog("battery-status-error", str(e)) + except: + debuglog("battery-status-error", "Battery Status Failed") + + return 1 + +def battery_checkupdateprofile(): + try: + REG_GPIOCONFIG = 0x0A + + PROFILE_DATALIST = [0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA8,0xAA,0xBE,0xC6,0xB8,0xAE,0xC2,0x98,0x82,0xFF,0xFF,0xCA,0x98,0x75,0x63,0x55,0x4E,0x4C,0x49,0x98,0x88,0xDC,0x34,0xDB,0xD3,0xD4,0xD3,0xD0,0xCE,0xCB,0xBB,0xE7,0xA2,0xC2,0xC4,0xAE,0x96,0x89,0x80,0x74,0x67,0x63,0x71,0x8E,0x9F,0x85,0x6F,0x3B,0x20,0x00,0xAB,0x10,0xFF,0xB0,0x73,0x00,0x00,0x00,0x64,0x08,0xD3,0x77,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFA] + + PROFILE_LEN = len(PROFILE_DATALIST) + + # Try to compare profile if battery is active + tmpidx = 0 + + tmpval = battery_getstatus(True) + if tmpval == 0: + # Status OK, check profile + tmpidx = 0 + while tmpidx < PROFILE_LEN: + tmpval = bus.read_byte_data(ADDR_BATTERY, REG_PROFILE+tmpidx) + if tmpval != PROFILE_DATALIST[tmpidx]: + debuglog("battery-profile-error", "Mismatch") + break + tmpidx = tmpidx + 1 + + if tmpidx == PROFILE_LEN: + # Matched + return 0 + else: + debuglog("battery-profile", "Status Error "+str(tmpval)+", will attempt to update") + + # needs update + debuglog("battery-profile", "Updating...") + + # Device Sleep state + + # Restart + bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0x30) + time.sleep(0.5) + # Sleep + bus.write_byte_data(ADDR_BATTERY, REG_CONTROL, 0xF0) + time.sleep(0.5) + + # Write Profile + tmpidx = 0 + while tmpidx < PROFILE_LEN: + bus.write_byte_data(ADDR_BATTERY, REG_PROFILE+tmpidx, PROFILE_DATALIST[tmpidx]) + tmpidx = tmpidx + 1 + + debuglog("battery-profile", "Profile Updated,Restarting...") + + # Set Update Flag + bus.write_byte_data(ADDR_BATTERY, REG_SOCALERT, 0x80) + time.sleep(0.5) + + # Close Interrupts + bus.write_byte_data(ADDR_BATTERY, REG_GPIOCONFIG, 0) + time.sleep(0.5) + + # Restart Battery + tmpval = battery_restart() + if tmpval == 0: + debuglog("battery-profile", "Update Completed") + return 0 + + debuglog("battery-profile", "Unable to restart") + return 3 + except Exception as e: + try: + debuglog("battery-profile-error", str(e)) + except: + debuglog("battery-profile-error", "Battery Profile Check/Update Failed") + + return 1 + + + +def battery_getpercent(): + # State of Charge (SOC) + try: + SOC_HIGH_REG = 0x04 + + socpercent = bus.read_byte_data(ADDR_BATTERY, SOC_HIGH_REG) + if socpercent > 100: + return 100 + elif socpercent > 0: + return socpercent + + # Support Fraction percent + #SOC_LOW_REG = 0x05 + #soc_low = bus.read_byte_data(ADDR_BATTERY, SOC_LOW_REG) + #socpercentfloat = socpercent + (soc_low / 256.0) + #if socpercentfloat > 100.0: + # return 100.0 + #elif socpercentfloat > 0.0: + # return socpercentfloat + + except Exception as e: + try: + debuglog("battery-percenterror", str(e)) + except: + debuglog("battery-percenterror", "Read Battery Failed") + + return 0 + + +def battery_isplugged(): + # State of Charge (SOC) + try: + CURRENT_HIGH_REG = 0x0E + + current_high = bus.read_byte_data(ADDR_BATTERY, CURRENT_HIGH_REG) + + if (current_high & 0x80) > 0: + return 1 + + #CURRENT_LOW_REG = 0x0F + #R_SENSE = 10.0 + #current_low = bus.read_byte_data(ADDR_BATTERY, CURRENT_LOW_REG) + #raw_current = int.from_bytes([current_high, current_low], byteorder='big', signed=True) + #current = (52.4 * raw_current) / (32768 * R_SENSE) + + + except Exception as e: + try: + debuglog("battery-chargingerror", str(e)) + except: + debuglog("battery-chargingerror", "Read Charging Failed") + + return 0 + +def battery_loadlogdata(): + # status, version, time, schedule + outobj = {} + try: + fp = open(UPS_LOGFILE, "r") + logdata = fp.read() + alllines = logdata.split("\n") + ctr = 0 + while ctr < len(alllines): + tmpval = alllines[ctr].strip() + curinfo = tmpval.split(":") + if len(curinfo) > 1: + tmpattrib = curinfo[0].lower().split(" ") + # The rest are assumed to be value + outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip() + ctr = ctr + 1 + except OSError: + pass + + return outobj + +def battery_check(readq): + CMDSTARTBYTE=0xfe + CMDCONTROLBYTECOUNT=3 + CHECKSTATUSLOOPFREQ=50 + + CMDsendrequest = [ 0xfe, 0, 0, 0xfe, 0xfe, 0, 0, 0xfe, 0, 0, 0] + + lastcmdtime="" + loopCtr = CHECKSTATUSLOOPFREQ + sendcmdid = -1 + + debuglog("battery", "Starting") + + updatedesktopicon("Argon ONE UP", "/etc/argon/argon40.png") + + maxretry = 5 + while maxretry > 0: + try: + if battery_checkupdateprofile() == 0: + break + except Exception as mainerr: + try: + debuglog("battery-mainerror", str(mainerr)) + except: + debuglog("battery-mainerror", "Error") + # Give time before retry + time.sleep(10) + maxretry = maxretry - 1 + + while maxretry > 0: # Outer loop; maxretry never decrements so infinite + qdata = "" + if readq.empty() == False: + qdata = readq.get() + + if battery_getstatus(True) != 0: + # Give time before retry + time.sleep(3) + continue + + prevnotifymsg = "" + previconfile = "" + statusstr = "" + + needsupdate=False + device_battery=0 + device_charging=0 + + while True: # Command loop + try: + if sendcmdid < 0: + cmddatastr = "" + + if cmddatastr == "": + if loopCtr >= CHECKSTATUSLOOPFREQ: + # Check Battery Status + sendcmdid = 0 + loopCtr = 0 + else: + loopCtr = loopCtr + 1 + if (loopCtr&1) == 0: + sendcmdid = 0 # Check Battery Status + + if sendcmdid == 0: + tmp_battery = battery_getpercent() + tmp_charging = battery_isplugged() + + if tmp_charging != device_charging or tmp_battery!=device_battery: + device_battery=tmp_battery + device_charging=tmp_charging + tmpiconfile = "/etc/argon/ups/" + needsupdate=True + curnotifymsg = "" + curnotifycritical = False + + if device_charging == 0: + if "Shutting Down" in prevnotifymsg: + os.system("shutdown -c ""Charging, shutdown cancelled.""") + debuglog("battery-shutdown", "Abort") + + if device_battery>99: + # Prevents switching issue + statusstr = "Charged" + curnotifymsg = statusstr + tmpiconfile = tmpiconfile+"charge_"+str(device_battery) + elif device_charging == 0: + statusstr = "Charging" + curnotifymsg = statusstr + tmpiconfile = tmpiconfile+"charge_"+str(device_battery) + else: + statusstr = "Battery" + tmpiconfile = tmpiconfile+"discharge_"+str(device_battery) + + if device_battery > 50: + curnotifymsg="Battery Mode" + elif device_battery > 20: + curnotifymsg="50%% Battery" + elif device_battery > 10: + curnotifymsg="20%% Battery" + elif device_battery > 5: + #curnotifymsg="Low Battery" + curnotifymsg="Low Battery: The device may power off automatically soon." + curnotifycritical=True + else: + curnotifymsg="CRITICAL BATTERY: Shutting Down in 1 minute" + curnotifycritical=True + + tmpiconfile = tmpiconfile + ".png" + statusstr = statusstr + " " + str(device_battery)+"%" + + # Add/update desktop icons too; add check to minimize write + if previconfile != tmpiconfile: + updatedesktopicon(statusstr, tmpiconfile) + previconfile = tmpiconfile + + # Send notification if necessary + if prevnotifymsg != curnotifymsg: + notifymessage(curnotifymsg, curnotifycritical) + if device_battery <= 5 and device_charging != 0: + os.system("shutdown +1 """+curnotifymsg+".""") + debuglog("battery-shutdown", "Shutdown in 1 minute") + prevnotifymsg = curnotifymsg + + + sendcmdid=-1 + + if needsupdate==True: + # Log File + otherstr = "" + with open(UPS_LOGFILE, "w") as txt_file: + txt_file.write("Status as of: "+time.asctime(time.localtime(time.time()))+"\n Power:"+statusstr+"\n"+otherstr) + + needsupdate=False + + except Exception as e: + try: + debuglog("battery-error", str(e)) + except: + debuglog("battery-error", "Error") + break + time.sleep(3) + +def updatedesktopicon(statusstr, tmpiconfile): + try: + icontitle = "Argon ONE UP" + tmp = os.popen("find /home -maxdepth 1 -type d").read() + alllines = tmp.split("\n") + for curfolder in alllines: + if curfolder == "/home" or curfolder == "": + continue + #debuglog("desktop-update-path", curfolder) + #debuglog("desktop-update-text", statusstr) + #debuglog("desktop-update-icon", tmpiconfile) + with open(curfolder+"/Desktop/argononeup.desktop", "w") as txt_file: + txt_file.write("[Desktop Entry]\nName="+icontitle+"\nComment="+statusstr+"\nIcon="+tmpiconfile+"\nExec=lxterminal --working-directory="+curfolder+"/ -t \"Argon ONE UP\" -e \"/etc/argon/argon-config\"\nType=Application\nEncoding=UTF-8\nTerminal=false\nCategories=None;\n") + except Exception as desktope: + #pass + try: + debuglog("desktop-update-error", str(desktope)) + except: + debuglog("desktop-update-error", "Error") + + +if len(sys.argv) > 1: + cmd = sys.argv[1].upper() + if cmd == "GETBATTERY": + outobj = battery_loadlogdata() + try: + print(outobj["power"]) + except: + print("Error retrieving battery status") + elif cmd == "RESETBATTERY": + battery_checkupdateprofile() + + elif cmd == "SERVICE": + # Starts sudo level services + try: + ipcq = Queue() + if len(sys.argv) > 2: + cmd = sys.argv[2].upper() + t1 = Thread(target = battery_check, args =(ipcq, )) + t2 = Thread(target = argonpowerbutton_monitorlid, args =(ipcq, )) + + t1.start() + t2.start() + + ipcq.join() + except Exception: + sys.exit(1) diff --git a/pythonscript/kickstarter/argononeupd.service b/pythonscript/kickstarter/argononeupd.service new file mode 100644 index 0000000..6ba565b --- /dev/null +++ b/pythonscript/kickstarter/argononeupd.service @@ -0,0 +1,10 @@ +[Unit] +Description=Argon ONE UP Service +After=multi-user.target +[Service] +Type=simple +Restart=always +RemainAfterExit=true +ExecStart=/usr/bin/python3 /etc/argon/argononeupd.py SERVICE +[Install] +WantedBy=multi-user.target diff --git a/pythonscript/kickstarter/argononeupduser.service b/pythonscript/kickstarter/argononeupduser.service new file mode 100644 index 0000000..62481a3 --- /dev/null +++ b/pythonscript/kickstarter/argononeupduser.service @@ -0,0 +1,10 @@ +[Unit] +Description=Argon ONE UP Service +After=multi-user.target +[Service] +Type=simple +Restart=always +RemainAfterExit=true +ExecStart=/usr/bin/python3 /etc/argon/argonkeyboard.py SERVICE +[Install] +WantedBy=default.target diff --git a/pythonscript/kickstarter/argonpowerbutton_libgpiod.py b/pythonscript/kickstarter/argonpowerbutton_libgpiod.py new file mode 100644 index 0000000..484e00c --- /dev/null +++ b/pythonscript/kickstarter/argonpowerbutton_libgpiod.py @@ -0,0 +1,206 @@ + +# For Libreelec/Lakka, note that we need to add system paths +# import sys +# sys.path.append('/storage/.kodi/addons/virtual.rpi-tools/lib') +import gpiod +import os +import time + +# Debug Logger +def argonpowerbutton_debuglog(typestr, logstr): + try: + DEBUGFILE="/dev/shm/argononegpiodebuglog.txt" + tmpstrpadding = " " + + with open(DEBUGFILE, "a") as txt_file: + txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n") + except: + pass + +def argonpowerbutton_getvalue(lineobj,lineid): + if lineid is not None: + tmpval = lineobj.get_value(lineid) != gpiod.line.Value.INACTIVE + if tmpval == False: + return 0 + return 1 + return lineobj.get_value() + +def argonpowerbutton_watchline(debugname, dataq, lineid, callback): + monitormode = True + argonpowerbutton_debuglog(debugname, "Starting") + # Pi5 mapping, 0 for older + chippath = '/dev/gpiochip4' + try: + chip = gpiod.Chip(chippath) + except Exception as gpioerr: + try: + # Old mapping + chippath = '/dev/gpiochip0' + chip = gpiod.Chip(chippath) + except Exception as gpioolderr: + chippath = "" + + if len(chippath) == 0: + argonpowerbutton_debuglog(debugname+"-error", "Unable to initialize GPIO") + try: + dataq.put("ERROR") + except: + pass + return + + # Monitoring starts + try: + try: + # Reference https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/gpiomon.py + + lineobj = chip.get_line(lineid) + if lineid == 27: + lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES, flags=gpiod.LINE_REQ_FLAG_BIAS_PULL_UP) + else: + lineobj.request(consumer="argon", type=gpiod.LINE_REQ_EV_BOTH_EDGES) + while monitormode == True: + hasevent = lineobj.event_wait(10) + if hasevent: + eventdata = lineobj.event_read() + monitormode = callback(eventdata.type == gpiod.LineEvent.RISING_EDGE, lineobj, dataq, None) + + lineobj.release() + chip.close() + except Exception: + # https://github.com/brgl/libgpiod/blob/master/bindings/python/examples/watch_line_rising.py + configobj = {lineid: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT, edge_detection=gpiod.line.Edge.BOTH)} + if lineid == 27: + configobj = {lineid: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT, edge_detection=gpiod.line.Edge.BOTH, bias=gpiod.line.Bias.PULL_UP )} + + with gpiod.request_lines( + chippath, + consumer="argon", + config=configobj, + ) as request: + while monitormode: + # Blocks until at least one event is available + for event in request.read_edge_events(): + monitormode = callback(event.event_type == event.Type.RISING_EDGE, request, dataq, event.line_offset) + except Exception as monitorerror: + try: + argonpowerbutton_debuglog(debugname+"-error", str(monitorerror)) + except: + argonpowerbutton_debuglog(debugname+"-error", "Error aborting") + try: + dataq.put("ERROR") + except: + pass + +# This function is the thread that monitors activity in our shutdown pin +# The pulse width is measured, and the corresponding shell command will be issued + +def argonpowerbutton_getconfigval(keyname, datatype="int"): + keyname = keyname.lower() + fname = "/etc/argononeupd.conf" + try: + with open(fname, "r") as fp: + for curline in fp: + if not curline: + continue + tmpline = curline.replace(" ", "").replace("\t", "") + if not tmpline: + continue + if tmpline[0] == "#": + continue + tmppair = tmpline.split("=") + if len(tmppair) != 2: + continue + + tmpvar = tmppair[0].lower() + if tmpvar != keyname: + continue + + try: + if datatype == "int": + return int(tmppair[1]) + elif datatype == "float": + return float(tmppair[1]) + return tmppair[1] + except: + continue + except: + pass + if datatype == "int": + return -1 + elif datatype == "float": + return -1 + return "" + +def argonpowerbutton_monitorlidevent(isrising, lineobj, writeq, lineid): + if isrising == False: + targetsecs = argonpowerbutton_getconfigval("lidshutdownsecs") + if targetsecs > 0: + argonpowerbutton_debuglog("lid-monitor", "Close Detect; Wait for :"+str(targetsecs)) + else: + argonpowerbutton_debuglog("lid-monitor", "Close Detected; Do nothing") + # Time pulse data + time.sleep(1) + pulsetimesec = 1 + # 0 - Lid is closed, 1 - Lid is open + while argonpowerbutton_getvalue(lineobj, lineid) == 0: + if targetsecs > 0: + if pulsetimesec >= targetsecs: + argonpowerbutton_debuglog("lid-monitor", "Target Reached, shutting down") + monitormode = False + os.system("shutdown now -h") + return False + + time.sleep(1) + pulsetimesec += 1 + argonpowerbutton_debuglog("lid-monitor", "Open Detected") + return True + +def argonpowerbutton_monitorlid(writeq): + LINE_LIDMONITOR=27 + argonpowerbutton_watchline("lid-monitor", writeq, LINE_LIDMONITOR, argonpowerbutton_monitorlidevent) + +def argonpowerbutton_monitorevent(isrising, lineobj, writeq, lineid): + pulsetime = 0 + if isrising == True: + # Time pulse data + while argonpowerbutton_getvalue(lineobj, lineid) == 1: + time.sleep(0.01) + pulsetime += 1 + + if pulsetime >=2 and pulsetime <=3: + # Testing + #writeq.put("OLEDSWITCH") + writeq.put("OLEDSTOP") + os.system("reboot") + return False + elif pulsetime >=4 and pulsetime <=5: + writeq.put("OLEDSTOP") + os.system("shutdown now -h") + return False + elif pulsetime >=6 and pulsetime <=7: + writeq.put("OLEDSWITCH") + return True + +def argonpowerbutton_monitor(writeq): + LINE_SHUTDOWN=4 + argonpowerbutton_watchline("button", writeq, LINE_SHUTDOWN, argonpowerbutton_monitorevent) + + +def argonpowerbutton_monitorswitchevent(isrising, lineobj, writeq, lineid): + pulsetime = 0 + if isrising == True: + # Time pulse data + while argonpowerbutton_getvalue(lineobj, lineid) == 1: + time.sleep(0.01) + pulsetime += 1 + + if pulsetime >= 10: + writeq.put("OLEDSWITCH") + return True + +def argonpowerbutton_monitorswitch(writeq): + LINE_SHUTDOWN=4 + argonpowerbutton_watchline("button-switch", writeq, LINE_SHUTDOWN, argonpowerbutton_monitorswitchevent) + +# Testing +#argonpowerbutton_monitor(None) diff --git a/pythonscript/kickstarter/argonpowerbutton_rpigpio.py b/pythonscript/kickstarter/argonpowerbutton_rpigpio.py new file mode 100644 index 0000000..0d40c8c --- /dev/null +++ b/pythonscript/kickstarter/argonpowerbutton_rpigpio.py @@ -0,0 +1,156 @@ + +# For Libreelec/Lakka, note that we need to add system paths +# import sys +# sys.path.append('/storage/.kodi/addons/virtual.rpi-tools/lib') +import RPi.GPIO as GPIO +import os +import time + +# Debug Logger +def argonpowerbutton_debuglog(typestr, logstr): + try: + DEBUGFILE="/dev/shm/argononegpiodebuglog.txt" + tmpstrpadding = " " + + with open(DEBUGFILE, "a") as txt_file: + txt_file.write("["+time.asctime(time.localtime(time.time()))+"] "+typestr.upper()+" "+logstr.strip().replace("\n","\n"+tmpstrpadding)+"\n") + except: + pass + + +# This function is the thread that monitors activity in our shutdown pin +# The pulse width is measured, and the corresponding shell command will be issued + +def argonpowerbutton_getconfigval(keyname, datatype="int"): + keyname = keyname.lower() + fname = "/etc/argononeupd.conf" + try: + with open(fname, "r") as fp: + for curline in fp: + if not curline: + continue + tmpline = curline.replace(" ", "").replace("\t", "") + if not tmpline: + continue + if tmpline[0] == "#": + continue + tmppair = tmpline.split("=") + if len(tmppair) != 2: + continue + + tmpvar = tmppair[0].lower() + if tmpvar != keyname: + continue + + try: + if datatype == "int": + return int(tmppair[1]) + elif datatype == "float": + return float(tmppair[1]) + return tmppair[1] + except: + continue + except: + pass + if datatype == "int": + return -1 + elif datatype == "float": + return -1 + return "" + +def argonpowerbutton_monitorlid(writeq): + try: + argonpowerbutton_debuglog("lid-monitor", "Starting") + monitormode = True + # 0 - Lid is closed, 1 - Lid is open + # Pin Assignments + PIN_LIDMONITOR=27 + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(PIN_LIDMONITOR, GPIO.IN, pull_up_down=GPIO.PUD_UP) + + while monitormode == True: + pulsetimesec = 1 + GPIO.wait_for_edge(PIN_LIDMONITOR, GPIO.FALLING) + targetsecs = argonpowerbutton_getconfigval("lidshutdownsecs") + if targetsecs > 0: + argonpowerbutton_debuglog("lid-monitor", "Close Detect; Wait for :"+str(targetsecs)) + else: + argonpowerbutton_debuglog("lid-monitor", "Close Detected; Do nothing") + # Time pulse data + time.sleep(1) + while GPIO.input(PIN_LIDMONITOR) == GPIO.LOW: + if targetsecs > 0: + if pulsetimesec >= targetsecs: + argonpowerbutton_debuglog("lid-monitor", "Target Reached, shutting down") + monitormode = False + os.system("shutdown now -h") + break + time.sleep(1) + pulsetimesec += 1 + argonpowerbutton_debuglog("lid-monitor", "Open Detected") + except Exception as liderror: + try: + argonpowerbutton_debuglog("lid-monitor-error", str(liderror)) + except: + argonpowerbutton_debuglog("lid-monitor-error", "Error aborting") + #pass + GPIO.cleanup() + + +def argonpowerbutton_monitor(writeq): + try: + # Pin Assignments + PIN_SHUTDOWN=4 + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(PIN_SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + + while True: + pulsetime = 1 + GPIO.wait_for_edge(PIN_SHUTDOWN, GPIO.RISING) + time.sleep(0.01) + while GPIO.input(PIN_SHUTDOWN) == GPIO.HIGH: + time.sleep(0.01) + pulsetime += 1 + if pulsetime >=2 and pulsetime <=3: + # Testing + #writeq.put("OLEDSWITCH") + writeq.put("OLEDSTOP") + os.system("reboot") + break + elif pulsetime >=4 and pulsetime <=5: + writeq.put("OLEDSTOP") + os.system("shutdown now -h") + break + elif pulsetime >=6 and pulsetime <=7: + writeq.put("OLEDSWITCH") + except Exception: + writeq.put("ERROR") + GPIO.cleanup() + + + +def argonpowerbutton_monitorswitch(writeq): + try: + # Pin Assignments + PIN_SHUTDOWN=4 + + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) + GPIO.setup(PIN_SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + + while True: + pulsetime = 1 + GPIO.wait_for_edge(PIN_SHUTDOWN, GPIO.RISING) + time.sleep(0.01) + while GPIO.input(PIN_SHUTDOWN) == GPIO.HIGH: + time.sleep(0.01) + pulsetime += 1 + if pulsetime >= 10: + writeq.put("OLEDSWITCH") + except Exception: + writeq.put("ERROR") + GPIO.cleanup() diff --git a/pythonscript/kickstarter/argonregister.py b/pythonscript/kickstarter/argonregister.py new file mode 100644 index 0000000..edd9c1d --- /dev/null +++ b/pythonscript/kickstarter/argonregister.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 + +# +# Argon Register Helper methods +# Same as argonregister, but no support for new register commands +# + +import time +import smbus + +# I2C Addresses +ADDR_ARGONONEFAN=0x1a +ADDR_ARGONONEREG=ADDR_ARGONONEFAN + +# ARGONONEREG Addresses +ADDR_ARGONONEREG_DUTYCYCLE=0x80 +ADDR_ARGONONEREG_FW=0x81 +ADDR_ARGONONEREG_IR=0x82 +ADDR_ARGONONEREG_CTRL=0x86 + +# Initialize bus +def argonregister_initializebusobj(): + try: + return smbus.SMBus(1) + except Exception: + try: + # Older version + return smbus.SMBus(0) + except Exception: + print("Unable to detect i2c") + return None + + +# Checks if the FW supports control registers +def argonregister_checksupport(busobj): + return False + +def argonregister_getbyte(busobj, address): + if busobj is None: + return 0 + return busobj.read_byte_data(ADDR_ARGONONEREG, address) + +def argonregister_setbyte(busobj, address, bytevalue): + if busobj is None: + return + busobj.write_byte_data(ADDR_ARGONONEREG,address,bytevalue) + time.sleep(1) + +def argonregister_getfanspeed(busobj, regsupport=None): + return 0 + +def argonregister_setfanspeed(busobj, newspeed, regsupport=None): + if busobj is None: + return + + if newspeed > 100: + newspeed = 100 + elif newspeed < 0: + newspeed = 0 + + busobj.write_byte(ADDR_ARGONONEFAN,newspeed) + time.sleep(1) + +def argonregister_signalpoweroff(busobj): + if busobj is None: + return + + busobj.write_byte(ADDR_ARGONONEFAN,0xFF) + +def argonregister_setircode(busobj, vallist): + if busobj is None: + return + + busobj.write_i2c_block_data(ADDR_ARGONONEREG, ADDR_ARGONONEREG_IR, vallist) diff --git a/pythonscript/kickstarter/argonsysinfo.py b/pythonscript/kickstarter/argonsysinfo.py new file mode 100644 index 0000000..102f2e5 --- /dev/null +++ b/pythonscript/kickstarter/argonsysinfo.py @@ -0,0 +1,394 @@ +#!/usr/bin/python3 + +# +# Misc methods to retrieve system information. +# + +import os +import time +import socket + +def argonsysinfo_listcpuusage(sleepsec = 1): + outputlist = [] + curusage_a = argonsysinfo_getcpuusagesnapshot() + time.sleep(sleepsec) + curusage_b = argonsysinfo_getcpuusagesnapshot() + + for cpuname in curusage_a: + if cpuname == "cpu": + continue + if curusage_a[cpuname]["total"] == curusage_b[cpuname]["total"]: + outputlist.append({"title": cpuname, "value": "0%"}) + else: + total = curusage_b[cpuname]["total"]-curusage_a[cpuname]["total"] + idle = curusage_b[cpuname]["idle"]-curusage_a[cpuname]["idle"] + outputlist.append({"title": cpuname, "value": int(100*(total-idle)/(total))}) + return outputlist + +def argonsysinfo_getcpuusagesnapshot(): + cpupercent = {} + errorflag = False + try: + cpuctr = 0 + # user, nice, system, idle, iowait, irc, softirq, steal, guest, guest nice + tempfp = open("/proc/stat", "r") + alllines = tempfp.readlines() + for temp in alllines: + temp = temp.replace('\t', ' ') + temp = temp.strip() + while temp.find(" ") >= 0: + temp = temp.replace(" ", " ") + if len(temp) < 3: + cpuctr = cpuctr +1 + continue + + checkname = temp[0:3] + if checkname == "cpu": + infolist = temp.split(" ") + idle = 0 + total = 0 + colctr = 1 + while colctr < len(infolist): + curval = int(infolist[colctr]) + if colctr == 4 or colctr == 5: + idle = idle + curval + total = total + curval + colctr = colctr + 1 + if total > 0: + cpupercent[infolist[0]] = {"total": total, "idle": idle} + cpuctr = cpuctr +1 + + tempfp.close() + except IOError: + errorflag = True + return cpupercent + + +def argonsysinfo_liststoragetotal(): + outputlist = [] + ramtotal = 0 + errorflag = False + + try: + hddctr = 0 + tempfp = open("/proc/partitions", "r") + alllines = tempfp.readlines() + + for temp in alllines: + temp = temp.replace('\t', ' ') + temp = temp.strip() + while temp.find(" ") >= 0: + temp = temp.replace(" ", " ") + infolist = temp.split(" ") + if len(infolist) >= 4: + # Check if header + if infolist[3] != "name": + parttype = infolist[3][0:3] + if parttype == "ram": + ramtotal = ramtotal + int(infolist[2]) + elif parttype[0:2] == "sd" or parttype[0:2] == "hd": + lastchar = infolist[3][-1] + if lastchar.isdigit() == False: + outputlist.append({"title": infolist[3], "value": argonsysinfo_kbstr(int(infolist[2]))}) + else: + # SD Cards + lastchar = infolist[3][-2] + if lastchar[0] != "p": + outputlist.append({"title": infolist[3], "value": argonsysinfo_kbstr(int(infolist[2]))}) + + tempfp.close() + #outputlist.append({"title": "ram", "value": argonsysinfo_kbstr(ramtotal)}) + except IOError: + errorflag = True + return outputlist + +def argonsysinfo_getram(): + totalram = 0 + totalfree = 0 + tempfp = open("/proc/meminfo", "r") + alllines = tempfp.readlines() + + for temp in alllines: + temp = temp.replace('\t', ' ') + temp = temp.strip() + while temp.find(" ") >= 0: + temp = temp.replace(" ", " ") + infolist = temp.split(" ") + if len(infolist) >= 2: + if infolist[0] == "MemTotal:": + totalram = int(infolist[1]) + elif infolist[0] == "MemFree:": + totalfree = totalfree + int(infolist[1]) + elif infolist[0] == "Buffers:": + totalfree = totalfree + int(infolist[1]) + elif infolist[0] == "Cached:": + totalfree = totalfree + int(infolist[1]) + if totalram == 0: + return "0%" + return [str(int(100*totalfree/totalram))+"%", str((totalram+512*1024)>>20)+"GB"] + +def argonsysinfo_getcputemp(): + try: + tempfp = open("/sys/class/thermal/thermal_zone0/temp", "r") + temp = tempfp.readline() + tempfp.close() + #cval = temp/1000 + #fval = 32+9*temp/5000 + return float(int(temp)/1000) + except IOError: + return 0 + + +def argonsysinfo_getmaxhddtemp(): + maxtempval = 0 + try: + hddtempobj = argonsysinfo_gethddtemp() + for curdev in hddtempobj: + if hddtempobj[curdev] > maxtempval: + maxtempval = hddtempobj[curdev] + return maxtempval + except: + return maxtempval + +def argonsysinfo_gethddtemp(): + # May 2022: Used smartctl, hddtemp is not available on some platforms + hddtempcmd = "/usr/sbin/smartctl" + if os.path.exists(hddtempcmd) == False: + # Fallback for now + hddtempcmd = "/usr/sbin/hddtemp" + + outputobj = {} + if os.path.exists(hddtempcmd): + try: + tmp = os.popen("lsblk | grep -e '0 disk' | awk '{print $1}'").read() + alllines = tmp.split("\n") + for curdev in alllines: + if curdev[0:2] == "sd" or curdev[0:2] == "hd": + tempval = argonsysinfo_getdevhddtemp(hddtempcmd,curdev) + if tempval > 0: + outputobj[curdev] = tempval + return outputobj + except: + return outputobj + return outputobj + +def argonsysinfo_getdevhddtemp(hddtempcmd, curdev): + cmdstr = "" + if hddtempcmd == "/usr/sbin/hddtemp": + cmdstr = "/usr/sbin/hddtemp -n sata:/dev/"+curdev + elif hddtempcmd == "/usr/sbin/smartctl": + cmdstr = "/usr/sbin/smartctl -d sat -A /dev/"+curdev+" | grep Temperature_Celsius | awk '{print $10}'" + + tempval = 0 + if len(cmdstr) > 0: + try: + temperaturestr = os.popen(cmdstr+" 2>&1").read() + tempval = float(temperaturestr) + except: + tempval = -1 + + return tempval + +def argonsysinfo_getip(): + ipaddr = "" + st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # Connect to nonexistent device + st.connect(('254.255.255.255', 1)) + ipaddr = st.getsockname()[0] + except Exception: + ipaddr = 'N/A' + finally: + st.close() + return ipaddr + + +def argonsysinfo_getrootdev(): + tmp = os.popen('mount').read() + alllines = tmp.split("\n") + + for temp in alllines: + temp = temp.replace('\t', ' ') + temp = temp.strip() + while temp.find(" ") >= 0: + temp = temp.replace(" ", " ") + infolist = temp.split(" ") + if len(infolist) >= 3: + + if infolist[2] == "/": + return infolist[0] + return "" + +def argonsysinfo_listhddusage(): + outputobj = {} + raidlist = argonsysinfo_listraid() + raiddevlist = [] + raidctr = 0 + while raidctr < len(raidlist['raidlist']): + raiddevlist.append(raidlist['raidlist'][raidctr]['title']) + # TODO: May need to use different method for each raid type (i.e. check raidlist['raidlist'][raidctr]['value']) + #outputobj[raidlist['raidlist'][raidctr]['title']] = {"used":int(raidlist['raidlist'][raidctr]['info']['used']), "total":int(raidlist['raidlist'][raidctr]['info']['size'])} + raidctr = raidctr + 1 + + rootdev = argonsysinfo_getrootdev() + + tmp = os.popen('df').read() + alllines = tmp.split("\n") + + for temp in alllines: + temp = temp.replace('\t', ' ') + temp = temp.strip() + while temp.find(" ") >= 0: + temp = temp.replace(" ", " ") + infolist = temp.split(" ") + if len(infolist) >= 6: + if infolist[1] == "Size": + continue + if len(infolist[0]) < 5: + continue + elif infolist[0][0:5] != "/dev/": + continue + curdev = infolist[0] + if curdev == "/dev/root" and rootdev != "": + curdev = rootdev + tmpidx = curdev.rfind("/") + if tmpidx >= 0: + curdev = curdev[tmpidx+1:] + + if curdev in raidlist['hddlist']: + # Skip devices that are part of a RAID setup + continue + elif curdev in raiddevlist: + # Skip RAID ID that already have size data + # (use df information otherwise) + if curdev in outputobj: + continue + elif curdev[0:2] == "sd" or curdev[0:2] == "hd": + curdev = curdev[0:-1] + else: + curdev = curdev[0:-2] + + # Aggregate values (i.e. sda1, sda2 to sda) + if curdev in outputobj: + outputobj[curdev] = {"used":outputobj[curdev]['used']+int(infolist[2]), "total":outputobj[curdev]['total']+int(infolist[1])} + else: + outputobj[curdev] = {"used":int(infolist[2]), "total":int(infolist[1])} + + return outputobj + +def argonsysinfo_kbstr(kbval, wholenumbers = True): + remainder = 0 + suffixidx = 0 + suffixlist = ["KB", "MB", "GB", "TB"] + while kbval > 1023 and suffixidx < len(suffixlist): + remainder = kbval & 1023 + kbval = kbval >> 10 + suffixidx = suffixidx + 1 + + #return str(kbval)+"."+str(remainder) + suffixlist[suffixidx] + remainderstr = "" + if kbval < 100 and wholenumbers == False: + remainder = int((remainder+50)/100) + if remainder > 0: + remainderstr = "."+str(remainder) + elif remainder >= 500: + kbval = kbval + 1 + return str(kbval)+remainderstr + suffixlist[suffixidx] + +def argonsysinfo_listraid(): + hddlist = [] + outputlist = [] + # cat /proc/mdstat + # multiple mdxx from mdstat + # mdadm -D /dev/md1 + + ramtotal = 0 + errorflag = False + try: + hddctr = 0 + tempfp = open("/proc/mdstat", "r") + alllines = tempfp.readlines() + for temp in alllines: + temp = temp.replace('\t', ' ') + temp = temp.strip() + while temp.find(" ") >= 0: + temp = temp.replace(" ", " ") + infolist = temp.split(" ") + if len(infolist) >= 4: + + # Check if raid info + if infolist[0] != "Personalities" and infolist[1] == ":": + devname = infolist[0] + raidtype = infolist[3] + #raidstatus = infolist[2] + hddctr = 4 + while hddctr < len(infolist): + tmpdevname = infolist[hddctr] + tmpidx = tmpdevname.find("[") + if tmpidx >= 0: + tmpdevname = tmpdevname[0:tmpidx] + hddlist.append(tmpdevname) + hddctr = hddctr + 1 + devdetail = argonsysinfo_getraiddetail(devname) + outputlist.append({"title": devname, "value": raidtype, "info": devdetail}) + + tempfp.close() + except IOError: + # No raid + errorflag = True + + return {"raidlist": outputlist, "hddlist": hddlist} + + +def argonsysinfo_getraiddetail(devname): + state = "" + raidtype = "" + size = 0 + used = 0 + total = 0 + working = 0 + active = 0 + failed = 0 + spare = 0 + rebuildstat = "" + tmp = os.popen('mdadm -D /dev/'+devname).read() + alllines = tmp.split("\n") + + for temp in alllines: + temp = temp.replace('\t', ' ') + temp = temp.strip() + while temp.find(" ") >= 0: + temp = temp.replace(" ", " ") + infolist = temp.split(" : ") + if len(infolist) == 2: + if infolist[0].lower() == "raid level": + raidtype = infolist[1] + elif infolist[0].lower() == "array size": + tmpidx = infolist[1].find(" ") + if tmpidx > 0: + size = (infolist[1][0:tmpidx]) + elif infolist[0].lower() == "used dev size": + tmpidx = infolist[1].find(" ") + if tmpidx > 0: + used = (infolist[1][0:tmpidx]) + elif infolist[0].lower() == "state": + tmpidx = infolist[1].rfind(" ") + if tmpidx > 0: + state = (infolist[1][tmpidx+1:]) + else: + state = infolist[1] + elif infolist[0].lower() == "total devices": + total = infolist[1] + elif infolist[0].lower() == "active devices": + active = infolist[1] + elif infolist[0].lower() == "working devices": + working = infolist[1] + elif infolist[0].lower() == "failed devices": + failed = infolist[1] + elif infolist[0].lower() == "spare devices": + spare = infolist[1] + elif infolist[0].lower() == "rebuild status": + tmpidx = infolist[1].find("%") + if tmpidx > 0: + rebuildstat = (infolist[1][0:tmpidx])+"%" + return {"state": state, "raidtype": raidtype, "size": int(size), "used": int(used), "devices": int(total), "active": int(active), "working": int(working), "failed": int(failed), "spare": int(spare), "rebuildstat": rebuildstat} \ No newline at end of file diff --git a/pythonscript/kickstarter/getfiles.sh b/pythonscript/kickstarter/getfiles.sh new file mode 100755 index 0000000..b1e275d --- /dev/null +++ b/pythonscript/kickstarter/getfiles.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +ARGONDOWNLOADSERVER=https://download.argon40.com + +INSTALLATIONFOLDER=. +versioninfoscript=$INSTALLATIONFOLDER/argon-verioninfo.sh +uninstallscript=$INSTALLATIONFOLDER/argon-uninstall.sh +configscript=$INSTALLATIONFOLDER/argon-config +argondashboardscript=$INSTALLATIONFOLDER/argondashboard.py + +basename="argononeup" +daemonname=$basename"d" +eepromrpiscript="/usr/bin/rpi-eeprom-config" +eepromconfigscript=$INSTALLATIONFOLDER/${basename}-eepromconfig.py +daemonscript=$INSTALLATIONFOLDER/$daemonname.py +daemonservice=$INSTALLATIONFOLDER/$daemonname.service +userdaemonservice=$INSTALLATIONFOLDER/${daemonname}user.service +daemonconfigfile=$INSTALLATIONFOLDER/$daemonname.conf + +lidconfigscript=$INSTALLATIONFOLDER/${basename}-lidconfig.sh +imagefile=argon40.png + + +wget $ARGONDOWNLOADSERVER/scripts/argononeup-lidconfig.sh -O $lidconfigscript --quiet +wget $ARGONDOWNLOADSERVER/scripts/argon-rpi-eeprom-config-psu.py -O $eepromconfigscript --quiet +wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.py -O $daemonscript --quiet +wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.service -O $daemonservice --quiet +wget $ARGONDOWNLOADSERVER/scripts/${daemonname}user.service -O $userdaemonservice --quiet +wget $ARGONDOWNLOADSERVER/ups/upsimg.tar.gz -O $INSTALLATIONFOLDER/ups/upsimg.tar.gz --quiet +tar xfz $INSTALLATIONFOLDER/ups/upsimg.tar.gz -C $INSTALLATIONFOLDER/ups/ +rm -Rf $INSTALLATIONFOLDER/ups/upsimg.tar.gz +wget "$ARGONDOWNLOADSERVER/scripts/argonpowerbutton-rpigpio.py" -O $INSTALLATIONFOLDER/argonpowerbutton_rpigpio.py --quiet +wget "$ARGONDOWNLOADSERVER/scripts/argonpowerbutton-libgpiod.py" -O $INSTALLATIONFOLDER/argonpowerbutton_libgpiod.py --quiet +wget $ARGONDOWNLOADSERVER/scripts/argonkeyboard.py -O $INSTALLATIONFOLDER/argonkeyboard.py --quiet +wget $ARGONDOWNLOADSERVER/scripts/argondashboard.py -O $INSTALLATIONFOLDER/argondashboard.py --quiet +wget $ARGONDOWNLOADSERVER/scripts/argon-versioninfo.sh -O $versioninfoscript --quiet +wget $ARGONDOWNLOADSERVER/scripts/argonsysinfo.py -O $INSTALLATIONFOLDER/argonsysinfo.py --quiet +wget $ARGONDOWNLOADSERVER/scripts/argonregister-v1.py -O $INSTALLATIONFOLDER/argonregister.py --quiet +wget $ARGONDOWNLOADSERVER/scripts/argon-uninstall.sh -O $uninstallscript --quiet +wget https://download.argon40.com/$imagefile -O /etc/argon/$imagefile --quiet diff --git a/pythonscript/kickstarter/ups/charge_0.png b/pythonscript/kickstarter/ups/charge_0.png new file mode 100644 index 0000000000000000000000000000000000000000..23a72a95e0a2311f3259ff257a1ba4704d0274b1 GIT binary patch literal 408 zcmV;J0cZY+P)?M+Jp#4t9TRbTJns=WO6_h)+$YO$wRjG`-0nmPEA@TP%=P zV?e6>W3^>5o#^{Qt?_@<06)ALu{!Jlycn@OZu0@s>x@s41E44X0000|qH literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_1.png b/pythonscript/kickstarter/ups/charge_1.png new file mode 100644 index 0000000000000000000000000000000000000000..f9a8827bd9d0a3fa398daeb37f02bbdbaba1c6d2 GIT binary patch literal 404 zcmV;F0c-w=P)j3mY%JBj{68S%R*aoVDJCjN1$02DkT@O9szB(=1Px%7CFx^>xlaZnAd?^ zCEi;PdIbIr_Yvp@v}OS0di1>zy*EQbD4Tw%OtmUx{BTV|2q2=Ocjb&VA3`i%3R440 zhs++#3}ErnL)63o4bL;`-DoVn*86d`G+7gCdY(};z(PErG=y^EQ}=6!m3Rb7JTH-v z#3Qg2l`}{xovNqNlu);o7;sp~ByR0=uIZEB&wCzVsW&Z0}?F& yueL1OiM}t?8vjQP_=7hiz7BgZUW|y3+q?ke9DztwL?Gb+0000Pf4%_# zpVzM{I|+H8v@5N<6evZsWLJu0t%A~iDZmk{RYIaRC!{_9z7*(%jO3Amr5srbw4%4# ztUjQH(a6hO39rELG}>FoYweHjUkc1dcmz1j3n*&T=h{Q1F0zvq^1%ge(&~Wt5Yp)v~Am{#<)_~ z_2zl@Da77^;Lk!p(==A|lv1R1iUMnFBkzOWEukYFmO{2*4%?$El64-9IAGoA41B2J~hR@Ot!CL_RM=Qd+nDa+#`H$n?*cq?8~ctL82l>%N3k zJPTI?)-IVA+ze3h>?P@9fQ9E3^KNu5eyjKA+0x~i*wXWgnE@W+0iz{U5}*5i>$0Lp zV2$S)8HpZ&rKplYtaNIgMsq^LR$?GvA!F8o9B!{w7~*s7v`Zng@VwGx6EiWb#Ma`0 zhCT34OQ26%9__?;)!1J}Zg)RZ~9_5c6?07*qoM6N<$ Eg3;x&tN;K2 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_12.png b/pythonscript/kickstarter/ups/charge_12.png new file mode 100644 index 0000000000000000000000000000000000000000..154d28f625be6c2bbc79b414b0453a3586a5a723 GIT binary patch literal 417 zcmV;S0bc%zP)pj2$>oOBx|1P|UXE+D~{E27QWhU|}o;mdv(8?q=(fjoT09~E{3$vv@V<{7mBEW`s!ODHEk z^?mEI5_g~`^AZ(F+<{nBE+DCLs$ND@LfxLkfWw1K@*GHE_E<$ie5#!`Imk3J&veW%+b4fun%ApTzVV7vseeck2*wKJ8&ANc`e00000 LNkvXXu0mjfCMvYK literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_13.png b/pythonscript/kickstarter/ups/charge_13.png new file mode 100644 index 0000000000000000000000000000000000000000..ad7efcc6a32dec7c26880c9a4a6ef40692259966 GIT binary patch literal 424 zcmV;Z0ayNsP)}bE0+Jc3_sC z6FrBv1EW|~sdL{+@4$2)Xmw)knn>#m*$%8Gy#qUd)m(t_HN6!gKQBW{(cONvN;TBT zctI;lDG(9WxUFErrx3+k5gLGY%8U>e0L5ER$B+O#GEdxBqetZ1r_W$g(GIv0ed5elRv``Leq9)AYmi3>;pBzUaMG$uPvuT z8ktAt$&#Dc64y@bEgl%W2eiuqK5a!TC-!@x)#U%wfIqSY@#kX?k|l`a=QiJ-DVLt2bty^XyJCOq9MMuopu>y7Lg}g9%4;Q zC$Y77VDK53-4^g|%VRr{=R&)Q|EU3gM04WL#~vh#6UWbOegOS3mTvRT=br!o002ov JPDHLkV1m>{yTAYd literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_15.png b/pythonscript/kickstarter/ups/charge_15.png new file mode 100644 index 0000000000000000000000000000000000000000..edcad927fc96f610822b99b0e93717814eac6e58 GIT binary patch literal 424 zcmV;Z0ayNsP)%N z5YU?(+p$0SB~mslIBuv{nGfQfj{O^6kJD^sDX6Hb!Gx>eOJD^sDTC_6U>tKF-J77)A zne9L?txWcwpI5W+4ovrfT74hw2cyoA?Z9fnJFo*-%?TJ^)6XLE`!Xb@vFTT;R9%gX zH`F4f1Q8j!ZY@~%Aw=<7xCSsfWO{H5fa0}>qe}o5nJ4C}(WCg;>>sP8%QLZM=80JV z9^wI`AygAz+CMw2)EyYfyhcS*cOVy43rMS+nwQa%(6F5t2-wIp`#=e|*D4y~OUr4O zMrM(DvSbo#V%mwV#RHvlV02l)r!9}=#C|Wdn)sg@@JF;D9zOOUQGz&rZu0{%wvjM8 S%Yh0200007 zlmZb^joAt|dv@&-a_U}2YeLg@Vjy87v+M&k!XB$wh_7v@LmHV! z=E;&pY>8_p_7)E))_}G_iAczHVm}vJP5xI6_#s;me_r+=S%NseZu1RS?vuRoGuMCs O00009MPl5|mGZJv7(v~#g ztwsg>ZJ}idgawDwB_ROMc_M!PJVX)Cun`2r6VE*7i72XgX4hLlD|@Yh9`_pn;CR0N z{Cy4XpSQ1#J0c+UZ0k6A`|uG^%R*~wK&xNgK70h!vXGQk&od(e)~rQpM8Jxg=kt0o zB9MFJ-+GX;tfC>lw4HV-WEP%hx-4Q%Oe?Xq zctEfQqy>tIT(%SAT&OqkuNv@&XhwWp_F$qIvAk~c0@quNPj5U*u>b%707*qoM6N<$ Ef?KJpf&c&j literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_18.png b/pythonscript/kickstarter/ups/charge_18.png new file mode 100644 index 0000000000000000000000000000000000000000..7a34bc23a2e5ee7dac7eba0262ccb87985f29533 GIT binary patch literal 419 zcmV;U0bKrxP)p5Y(}@F$*Gmzl_`c;?hwKr55fMDN!V0C2fI zipIsn=f&f4^9~1c8O@E$WwO-4c>HjHGgj(E?YuFuQ^SE$_M9rMUh%l$Kq*J7(B65& z0WJ0%Vca{=yc?~pDo3kO?)>3EZ`?aD0_e>I$nPZgAo{)x38C)xOI50>M#dLzC4>MX zs+unstoagR@lu!uP`hMGFbjaiOJ*N13P2;Twk)<2p~P&JaZT=pqHc8K(FTm0C2wE ztNP{S_vQVhai;@PMx}mICTktc`%ecrW3^7Sj+>J^H65s>*C}a@Ob1%oo7+p%&&*D4y~OUG%KLS~V9 zX2>Ge#IzDyiw7FlKy8uYr!9};#C$I_n)sg@@Q-Lg{C(`fL?4aIZur1&xIG`0i0kWf5wC7JTZGU9_)G>(8}KWg^&9!dH;C6 z{u~!S2Sb5U5Yb#Dx1Q(54+TmojqKL=SgD~vE2X)g8#fgAm)6LGp+GIIk&i31dj-t8 z(OeG{Q>qLY3iQUk0>gpc?1{=}2q~D~n;{|8eSWDKYE{Va=bD5Nh?%S2m2=iSgA@<6>CT^ipS{pDaT40T(Hi5wO($vTk2Y+FUb_*6S>Qphx0^e!K3 Bu4w=O literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_20.png b/pythonscript/kickstarter/ups/charge_20.png new file mode 100644 index 0000000000000000000000000000000000000000..6b2be49787783e1a31eb6ac7c73687d90a933dfe GIT binary patch literal 424 zcmV;Z0ayNsP)O03UwZw1Gf?W(5^MJm&i9*v9HnbHw@0M&?im1Uov)pD&w$j? zTIb`v;yweb??#uZ23T@1H?H)${bt}_aG!xafUOyb$}5T%%%7JbA;`90s#1v>87^E) z2!WVcG+!>*=1U0TwP+fEbjg&^EC7htbf2hE02a;z^(V5Jx%gW5=hd>wGqI)fz$}0k z;)#rwP)>a6c7Agyw0UPeUO$G zBeQTGY_f?pG3~_G;)#TNptMQhX{*KU#Qt7rYy5v|fIr@X`1#lacnM6SETO}R S0`hwR0000c*ueb{0vlL;P7)SqP>B-NX>1p( zd8<;|bnHpuq$<;d!||Lb0MEybar`-mA|4JRdjasN%tHZ2`UPdmH$_UyJAe zP8ZRAwd?YBYu9bp0ci&F|7(sdeg1YGnCs(n_suyWb#}WB)R-F^+U*VpfOqD>aKJnB z`RoWn zRSVX=3qiaVt^u@m83}FyK)h!5iJStk$UIQrk>yOq_hx@OTe|FtEi(_y0`L$|WYmRf z;!EdyyOp{FEt%Jtk<=Z?Mb!e*noiB9(UQ=xm6#}CA=9h_CCm#M&=6l*PP-H`i_C*n z2C*ilmDpN5(Sk7`DOA{Pc`PT!d!e<7|EU3fL<{2UV-FA|i0S7xFJRG?!4m_-x~e-YH4yN){Hz*bHGA8kkmc@2r|1Q)U|5pw0!lpLj4m@62AsgWb{sdfE35@Os>m_mAgm zRl8ijxP3exSZ@U2p`2&p|7+VxpTFJ+NIe!)@_cQ?+($sY4oK&8ZTnoiEdro5o0R&O z#&sV7wMyJNho$Y~3hh1u&1*oqw<-0LMh2`m0{?>h2y6x{&7P<}qiDhW`4|#{Z26@! zRZ}5@fL1~X#LS{`a>fpyLJ+UTPy?h>riNh#K)j~+i3S;9;d#)#8!a&x-|PK(wjA)bnV><4;t1kjs-sQ%tu3iJ12NC>j!m#a*h$|wYEC4@lC zEE=a2Z1X7u@me$uKssgC&@2Fm*UUaqqW~;257c*LDY5w4?8nuz$)4CU^S~^C7UGGF zrcg8`dk;6u&*#~l%H!`3hK6g9aq>))< z9&}m6nwWNCYw<)I*T7PGbIWl`C4;*?EsKrp*!Rv94-alP# zN$sTfmhG4Q0o!!|3Q5XH&!zUI|G!-aN|j8VGpWwHcR;-ktj&$m_oV0U9jLAYK&kSz z{q7E=Dxd3+)P9-8+&fU5g>u(6YwzVc18moUe}Q`k_5gZgAWF|DT44Ts3<)7``o%I; zQ6U3?QiKqQnX~$lf^9y8Kwb+?1IQgRDQFe|$ZM)k)F=Q8iUN(x9Totmf77*V&Cn8;xv)2stAm=`iY zgM4f`-K3CNC=Ya*#F{Xz#Mb1A3g^IDvBGYv#d2c*F4P;)! Y1MQNV7}yi*vH$=807*qoM6N<$g4AxiqyPW_ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_26.png b/pythonscript/kickstarter/ups/charge_26.png new file mode 100644 index 0000000000000000000000000000000000000000..956ec9b1507e2b4ed8cb217223a4536d4075906e GIT binary patch literal 427 zcmV;c0aX5pP)o<_#;=fH}103(>lo_H`%BePZU;IOrTT87#Vo{xL<`ss2j zdM<3gbRYB^0eC3onbiHI=Stt-Zv;v`SnB!Q$o3n76uEOFBsXh??e++O+%Yblwe|=| z<@MZt!#QL=0;^Y}scaImJFLVbP~v%sj3gd`P*lnwsdTEIMq@(FR$?N9g-o&z#859}Ktg$d8CZ*GDY;CRW_G(7$gwsyYVFqSM+QJ`M5Wpt z8L*CgTrakpfjAPSYobz0pL3squ^;HoaG*DPqWZOXE6neYAtBTaztqh1_cRI-{}4hT zX0B=@=d5`bf_N!R9jNUxC73w?@siOeGIBs8@<4n?7PA!J8vW&LX|gBQj66_tz(PEc zQWwgJPu*|rR^l0`iM+&&B%Xm-RL&u(=~R6hO$l{diHRH*GRZoSLcNdy3Gu1rv`Hb; zh&<>rh$T_2#M9we%i}gLZHkpX)I>OQ P00000NkvXXu0mjfi(a;6 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_28.png b/pythonscript/kickstarter/ups/charge_28.png new file mode 100644 index 0000000000000000000000000000000000000000..ac42a41f210a67960e9e83e70662c8afc4cedd53 GIT binary patch literal 429 zcmV;e0aE^nP)$b5vcW)b^m#!!biZo4%CkIoT-RP{f3W#x()zsmXYpH zJ-0@nHGgaS&F4_~2>hIdq->HikJ;dx;8Js>d_ul4>oTXy*- z_Vhe(GoXifBBvoFi7#E(4lDBrw0PbkBbi4a7bO{Fl}_E$Xh~?;N=y{6kXhD&67Gcz zScoqzr@Is~56^>HCb1>1mDpQ6k#Y`v%kj+IWjV3_7n+;+TMcj$&4`c99w3Slt0M!<)5y5~Tv!ngU<4D{6A$KTWVR|E9JUrv%TPPO^Kp;1pDwqm z{$an=c>P8I9$NA={x9vH`}}?*kn1Vw@xw^<8-Wz5xg4$qsd4uRfZQxnS|66i-6K$% zzjNcuzEL|n@!g4adMyAZv_4Y^AXq$*qVW8{r+Sv%s~=>C+k&8HB=OVQMT(kYWeGXo%A()&b>4AAgA5Pu^J@x|AA-_Mp!-ib9m57Z23 zA)ZKS3Z=xy+H0qkcmzs3FOiYNBM^#886=fX)zfH9sM$(PWU!D))`1x6g$ziDk8P)$ z6fzCZgH0B(B&wBITRc(a8u*ptnYqb!V*f6*HTI7h;D_;(ORJU|dk6i+-@mzg=LcyQ`1pq0sMp!e%pblv6l z$X<{0dxit0NEt+?rT-;xUw8+)RiW0*kTQJaPpq1b zUJKU%YNt#IZUI2N_VjcKz#{X&d^frk-kA#OKbpPAhc> zYBH};k<=a7i;@D;DyQaUG$%A{CngHm$Ta&v4!74T8sc-?X_rQ3xjpZ)h&3_o#Ma`8 zgf-B*4Zzcu$97`87wS#?PYv)RS`dF9dw?iGY(Ka80OA^s3c}Njy8r+H07*qoM6N<$ Eg6w{*g8%>k literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_30.png b/pythonscript/kickstarter/ups/charge_30.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc7c311a30cfd61fb0d57457fe9b225052669e4 GIT binary patch literal 425 zcmV;a0apHrP)NQoQZJ~t;OfwFrOCSfl9uwpF``|@9fD=sQPds=%CT6eV!LGM}TJ~!VpZ8nVxa0Yn zWw$g={tmXAfmB2gxl}|;?NVU;b~7L;ji9uX2M2#G}PAl~cltf;mBB^IUib^@8RZh*zXiTWvPE6#mk!kjU80L)(Xo!z( zr<*h~i^zj6i&zuWPHZinsB#VbtMSa-WIHk53-!kTRRetR=ETos58%a#`E{E&Ko+8_ TWOC5=00000NkvXXu0mjf7+AWA literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_31.png b/pythonscript/kickstarter/ups/charge_31.png new file mode 100644 index 0000000000000000000000000000000000000000..696292db86305529552d728b7a06b6d40e3155a8 GIT binary patch literal 420 zcmV;V0bBlwP)dCrXM?}Zid06{R3J@H_9&dgTDgF|ltwM<(Zz8?2g&z&x} zzwLAX=la=i1*9^&74&1i0%EIBF7vg|r`nmXz~(s+f{bjrgFqXHq)mB{t8-wJ4r z^<4ja7ICl;E5r zW-e-*3by$Wf_N#K22eU=*3c{fh?mShQKJAfG7rSx2SR-Dwb}Pq%O>x{nwbY`0kjZL zq%?$5;$!z~hn08*N-{68B8gWZ6qO1{YB^P3Mq@(FIx&$!Ba`%j80tm_B*e$I(@l&_ zBlDokB$h`s?k z(YTyl{6F~L3`h|{|7IX27s1lV=IrC=xX*xkAJ{sVbM}(mo&gE1s*|&G zpMmC1Tw7gQBOiBY_Zetj16%ht5~Y~|dj>}1J_EZ0qnU`xD})xzpN}CS)GfbMg=*eT zLre`J1Y+i@v68dhrx3(z(ba+4DN{l>2OwTE`b3=^u!uZRdncAyi*Jqox>|O5C$@|{ zFms@Xcp{@IB#BSmZ=F`^8K{Z8MnzK3KrBjfNUNNhm(i3^x1E^CVI$M*11ZcK8PE`) z+D>u<#3;+NC literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_33.png b/pythonscript/kickstarter/ups/charge_33.png new file mode 100644 index 0000000000000000000000000000000000000000..be993d32197d50a64296a4124ab94fea35c10f1a GIT binary patch literal 421 zcmV;W0b2fvP)G2a^VN|7)@5cKe-5mO2MB_R?1=~KGBaBh4-UNr)H3az_6#+@!V zk?}b1^S=(nGJ?$c|Cf$WfqCW~5UWC^nXJt`<_!mYw48{I8xF*faw2sd^A1#ZVy&u^ zas-%npn4C)?r~C`H+Ot${&1i-<{cOT^kyQ;e_E1)`Ta7)xw7pSs#LohSx8VW&N*V{ zqIt1kn=c`Vm!fF^rAsDh0K6E?Xq>*W4 z9(38nlBjlKZSh1xd%&;tc-m@lJ2Bo1^~V0E2KZquh_8=5fR!M&pWD0u`}m`N?EgC3 P00000NkvXXu0mjf+rhNm literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_34.png b/pythonscript/kickstarter/ups/charge_34.png new file mode 100644 index 0000000000000000000000000000000000000000..5156b380e8bf1bb6094d50dd0392fba35b46ccec GIT binary patch literal 428 zcmV;d0aN~oP)x+Ds~^t>~kzYkHw0~oy1E;*7F!AMWQysTLeJvQK_o@i;#pgKYRq-I&p2T z=V&7rJ_601xHhM?eWSKRuQviq>$rCPe}s>~b{-hbX259nMD2TXEzF;fAt~uzzfxr~ z*~~#uEh!~pX4O2&*zPF=@m6#-K$|i(bTa_rExk|F$p8<}1G5vE#9Vx<_vh8J%bD2I z^T5r39^#3dQb-bC`oA@;%p;)jyhTMak3cR;GRUf&x|h+C(6F7DC}1PA>;om-8yT<= zU)oN0X=EOr2SZ+BOI$m#w|F9>cOb9zIBoUVPHf+WMic+40e(a?;^(plh+@S2y3HFr W7@d;`NrHd?00004C8JHHFulGO>n5U7k{oC*&9v}!N@+Th5)5z>qJUI0hP|L9O!uRW$b>8Ln zP&ps_WA_7bX?kB3hv{+0%|U_mie_gkXxDfK1T&&)+({}I_bFk3T$>EiP*<$ zU1#lo_FI9{3~QYqyP5k6RJ)Mm@1^~qv@+nRKyTbv;0T~M15y5JNekxh%a9PtreDgb zR30TDD3=fdF>}#%Qn1a35X5WIG=S0}lS8urAYL>3M2!Nl$UIPA55z3R*Jgj|mQB{g zmYD};0kjZLWHf{%@u~f_!%DpZC7IW7B=ri!qNISd?9|*wQ$pQ#Vj_o)OtTN9FmGf) zLwxFSx=ACm$UNvWi8V3p#Ma`8l;%L}^;m7Sc$_%i3-!kTrv~`pEr`F5J%E=WmY>^v Y0CvTkg=ZgsjsO4v07*qoM6N<$g6QGF^8f$< literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_36.png b/pythonscript/kickstarter/ups/charge_36.png new file mode 100644 index 0000000000000000000000000000000000000000..b8b5bc38a85aaf8387c65d3fa6a791efbbcd3fd1 GIT binary patch literal 427 zcmV;c0aX5pP) z-)TA^e_L8$foZ|{dJp7)c^VndzXvbk0gPZGf8xPBjm%!fgHvw-TN&1Ne7~Mm?Jl=R zRsXnM={?wQ1WKM=`@R;8<30jnRmj@&80UyUOwQA#s#S_iZd~^fSY3s3z(YBYwdP1E zclH|rsjAXCew<70Be1y&<*G~V9;np5)c=S;Z`?=VFrYUBQT^#k3g+*}kPzC^FIB0; zwh|CkLkNMGxw%gY^C<-JQkWXhI%R4wGXUZxy-#FhfQILR_<0~^DSoT>m(|kbOswg7 zpk{!Dcp{}KloOx&f9tdok3fs(B`T751Y%J+gQUu-dKpa#b-NN1Ib6sj*MSu33mK3Q zpW05FEMyv<2VIg_64jMhTRf3q4#dgI%qH83n5U6({k!lY9>54D@+Th5)5z>qJUI0hP|L8jI&ycSIjC>=66G&2C=HN8*N$N&q^1NHrYWGTMZ`^#+EWKV4Ad0=Kh z3-LroLntLa_PlmjsYjs1^BNgRJpxix${?+DYMw@8LfuwkB8P=cvkt^CFJwSNd~7-0 zq>x#79(0++nwVB%Yw<*ib3jg3W^S^aIQ|Ru#{W?R+;}tM@305(V#M;e%?DMQmH--7 R>;(V-002ovPDHLkV1le-y50Z) literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_38.png b/pythonscript/kickstarter/ups/charge_38.png new file mode 100644 index 0000000000000000000000000000000000000000..b24e946d33a1b2a16ffc2e0a231db05eb475184e GIT binary patch literal 432 zcmV;h0Z;ykP)dD!hJPco9!%gaH1;lgIrk;#EBPSS?_b!`6vEw@cHw z&(n9)yxO?*fAYQ&D0zgQD|xhcUI~tW-w34iday=oq})e9y$`hJdP*&FrRVMuD3N+> zYZa=^b02}>POMdRTA!b;A@>m&UIV>VWos2G&EF%i8ut;{4OqObgQtpmrfhn4u?LA_ME<$WKcpt2 U5n!Z?F8}}l07*qoM6N<$f+Qcgg8%>k literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_4.png b/pythonscript/kickstarter/ups/charge_4.png new file mode 100644 index 0000000000000000000000000000000000000000..7b6d1c71a4710355d001c6bd32e54a8ab2402512 GIT binary patch literal 414 zcmV;P0b%}$P)-xpEDGi(F_@x(LBeI|-3p4s&l(8|fB4wM_@!>t-R*AP}9jPAtZ5`;|3w^7^ z-zs!?4|MN^B(`R&@E+*C23iU4fnh*zCZN4HuZ8IKGbE+D9ND^Oq z-Wpcw9;lgljf|x3fn1bikXAZ1PopKFVJk5Zu#joiff8<;dJXZVcG{(oS+?h07O^I# zmDpN5AgqDbHh_0q9__^VFVvg(M-BKUnh_s|J(ws)tdH9~0fAbZ2$lO07*qo IM6N<$g6Gk(s{jB1 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_40.png b/pythonscript/kickstarter/ups/charge_40.png new file mode 100644 index 0000000000000000000000000000000000000000..bc0762bac2d3f0d54ecd2e689cc7e44f47ca1aa9 GIT binary patch literal 427 zcmV;c0aX5pP)Qs0K*7Lw@Rsgd(0sSY)QHXwDhNRTD{7RKc zwT!Z`7AYkV(dPaJIc|UZWzZJ0L~X0@5m{=4G@bH0(|c1l-6p_kj{_5A_=2 zOUG%Kjm#qR%#=l}iRn&kEgn!_1NoF8BA4UDdM`Aa_@5f^k7z;s``Ckt5=8#F%?}mm VpjIlo$|wK;002ovPDHLkV1nKs!<7I4 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_41.png b/pythonscript/kickstarter/ups/charge_41.png new file mode 100644 index 0000000000000000000000000000000000000000..c94d76e0ec13f3ae6e611067c89a6f3f9ce314f9 GIT binary patch literal 420 zcmV;V0bBlwP)@A>|llFvvzdrzg zuk*EQ9DgUZaU%j`>9O8%t@DfsEOU9y!&}F#-S4;&XstF{)b4lO2#{JXYei^np5sQK zwMx88ZJuSsj~jt^S$eSM>DCB_kHFXu?9DJ>Z%%;T8od>f$IFnEwmp8O%A{UKDegZ~ zN)VBzvH5>WxTg@sYvF3Z)|6?%%>atm^giHZfQ9FY`C2F!-|PLnTDt6sEj>@n4Db*S z7^P56eChk%v{H?Lh-~t_Mny93#5IGo%BgAuT2mx!Ck6sGGR;0v!fjKpA-=Snc4=f5 zo+suhWGNmn?!?yO0fT2C-)(s;C&qiBy@`L+fFGh6@p9OML@^>iZgU5$d6{mnDtJ%; O00000L$Y>eEfe9MLfe!5D-s1vpjA@QN=U6tp$v-*S_KNegyy= z&zHB?OG^9FPP*UB03$`Vyxz);bX~Ha87L#yTjMQyGxfb0SX;BB_mS+vXW-*W-0C{a zXxC}keQyRNwEDedS7ttZ2GrL=5@qCC^P;!Ah0nm;4{XhJU~Be3|5Vk2=!^8tx^;;}B}JYZBplK9&B(Xvv_01>Tt2bty^sNuG$ z*AQP@PP+^;i^wzPC1fccFrLKL;sJwaAm43yEGOoBp{vnsWw@5Xnq$4!TMpzI?5*`y=6fA`TnDz+F3BBh%~Kl}-hs`X zcywLwov+qERv*Y2UDrqNncj~6xDJfc>JxdUdz}jJz0w#`6mOY*U{U}cnJ4bgLaF$@*-ux? zDeuIdnI~=m%n%Pal#qy2#g~rnEh|$8{u5stzZVw=QfdKNEvKdqsOlfVC81$EF%Ynk zS@wYv(_ZQ=#FrkYr!+E;%oFz#@)Qp^cVchxfa5(d+SE2*XMGnsnE1aMa3@+2zkc>0 eQG!T+xA_Jj`=FEKaZ$Jc00002pG2m!mpiQ$0pHjrGuF1&~r#0UcXi5JG(K;%`tuvuF`EnDpu-mez`!2Woy zYA?@)-1~Y2gxF)+?Q{Km1mYZ?@@#4QT+YY+|2G0!vrX-I?jx|c5|_>*rQb%}J|5?1 z>wx-NNV^hGRcQASP+tpaQ5r$_5$OAYwdn?|%@(L%o6m*l`7?xh-shK^nM|fp5txIS zK}3`7;yx*wr;v!(qNxFMQ>KJw28eh~?*oktu<*QKz7`6_*LpvkEt~9#Ej=%o8PGyJ zV30yYlog+PUYk~`5%?xP*S;2$2!!Me(wa_HBf!ibf>T1>R${Zu z(@hGQh35tH6tWZ#7*}Fz@qpnsFt?~J9;g2oS{eUG4dlj~5pRb*1TRK}$88=TsGmr0 S{jbFU0000uz~v@1U8QqoWv}UQ3x?&*Q0`M4l!F?^1il00B>1^5Mo!E2a z1-AfthzA@}h=^42rQ>tc%G7~x;<~2ogTOz7+wu-!8)I=S39pgh2=pPdvHbE+SFIlaH+htmRER$vW?b9BfNw}ycMAVEG?NH!U7QSme~h_0`SPZ;Jz0s#gAscoGnAn#GaWKTmm4X z5b=OR3B3}pJs&MAQwNr2-eN{_aiF9Xkkxc*>Hsr8e}X17?Me(JT*xff0gbRjy@hyf zI~}r+d1PL2FCkCyfb&Z1Ego<_kwt7L=6j*F$^WT=+++*l-^U(8mLST{ZSLaEnpwHD RiYx#C002ovPDHLkV1lfrw@Ls2 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_48.png b/pythonscript/kickstarter/ups/charge_48.png new file mode 100644 index 0000000000000000000000000000000000000000..d6ca87085777870a60a6b785611c77823e5a1b59 GIT binary patch literal 434 zcmV;j0ZsmiP)GRZik8I_fwcE4G6q zMiajk!9c$Bg=`oI8K2>OT9T=_ImP4)UNdFh!ftP3E z-dZm+KiY*_=Y@CR<$FM@pG&kHVcyq)QAWE^uY=(on9l=8GXpr91<+eT_CoacG9;z3 z+ppG{Oxq|6ElDYXh{SQ}oEq*W#NxGZ4PdlndTOss`+2uC ze&X=z5oCK literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_49.png b/pythonscript/kickstarter/ups/charge_49.png new file mode 100644 index 0000000000000000000000000000000000000000..ba139750e0fa7d368c15e502eed57cc5511f6f82 GIT binary patch literal 431 zcmV;g0Z{&lP)kcz|I~4-5;8WsLqY)V*E8|wtPM^ld$F&3x&6cn8#bp($E>oK^?JJ20LHmSzO7G;2VwgKUN9`7$J>zT2-> znM}(l3oS`0fr!L$=|45xrx1(R!Zm>2Dbs>m04!cJ`+!pb7MW+vd!bzX-0bJo()^j2 zh)grjm;~S@9x#|fpTyVp&z)AP4)kVTqav9&kRt`8RZdj{D8+KSP}{GCdn*xcA=6w3 zYPdbrYlyG4(=H2{MdlgvDP$=gFkXqR#RG=-fLA*)-U}^F{7((|N3*teB&{5$X>9v}!N@+Thb+sf=!JUERO(92?Vqxb7sHSThI zWXI!t%5pw=1u55`Xiq)0Pcd)>VQ=2fVbBWI=qwH#%#c6?gR z+&f@ig-ZRP)fq4y7>#=eW&opEi0Yq~q+tHO3<;ra`Q<8AwvpimT7(dYnVarY3f6oI zLA(^E0klq;8q5NKcOQIW(QNJW(bk}9X_Wi%($Z6_vj*vKUNKn}ClDiY#z+i87GEsO2Md@nQ_|DPJ*hqoa9KK1}!f`~u2`2dWJhMkj8xJLj0002ovPDHLk FV1lqo7u-^!Pl4nxj(Nh0fd#V47KxoQ}Oar#&NYuW&l7ji~FeHS&&o5W0RF@JE zni4`FW-hju#^f+hA&8g4)PUZUX~E0@h?n#}k&yu!o(Jmtfmr;g_v31*z9(j8RnG$z z1FXaoDWuRh@wxY-X(bwgUe8NZB$Wunlnjz8r=kX=VzFGvy_dp#DdBD+lbi=~m>q0M zh|evjO(rr8&ja-o(iBgmJQHh+CsO z+4ZI8rG56R03+v_9BP%T-N(HGtE*7SlO<(JIZ(UKy#lLqK+D5R_uH=mRwyaCUj?LN zymtR-^>eSl=4mw74{B$e()*?LhXSqPUV+1a*60Aa8NC)nzr&CamZe{+QmKy;Og2Uc z0YsF1mP5{1^AKY4QZO}OX~@*T%m5}Y>3zV+01eMGRFjy9M&!48KdzSQ|B0vMwV+~v z6?s4*gyzVn?r#k%Q3aNIUZNtYC=erOkW@JpH6Rxv)BA2aG2pC`N%w&i%nr6B$fw3> zlSZcDd4_rpXp#q%JFzx-Krsib#);!xs5Sh5YQR6d88Q0UgW<)9_;Z^tXL*!FgPf9M P00000NkvXXu0mjfuo1Y7 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_52.png b/pythonscript/kickstarter/ups/charge_52.png new file mode 100644 index 0000000000000000000000000000000000000000..f18c40bcfcd32c1f35fdecbeb5e08f04c1537814 GIT binary patch literal 424 zcmV;Z0ayNsP)U1`*r_b~ zO{HW}sCWRv^*)NSt_In0OYq~VDOZI7|+-JaS9Vo5oRy+3@Xr2RW(aO;O zW}voj*M2A2rCrZ`2AaFj+P#fLX=a!`16$)h1G@uTGXUisLJOkLVMqvd(=XSd%(oIO zHYS7sBFc_QNX~XIAr`MiR|jfKri5+|uz1br1DzbOh&*FDiHT??zBT%Bw>1AxoQk)? z#DQMo0fQ3yB|i7QwX9S#P#bxTj%1R77|9{6cB<-tR3hnnw<|H=Y$4NL2Xb^f*wPT6 z+fH{`$Sfkyn9l)A@qqD4Y%LxzoCCeK6Z^T)*7(0_z(2e>F}v)+cyS`WZu16W2A(WR S#>eRZ0000Cx+-IP<3bm5f{WQX* z3~e_9bE}AyeOmq8XP~(X<@!OX^VZsz=If_N!R9hkdhYA|yE;w7U`WZ(cZt3)2CzQn2c*67F8QvI7) zJMuuq0W0xD3QK58{MdNwvJ%a}+{jB*B$W)rIb6_ zgyiOQYE?-cC;E-R-a4MU{ubsVFun&`GYn|W1W>*=&js;w7~)*s>ldp`Dq9H>OgQHN z5&y>5;EYvIArfB;RReNUCIvMENPJE211K1Ph$5aRL|@`ie5v>SYAOCrEbVzh!~iYv z00JqrCO$S^npRaKkn8yx6-gulKAb_d%4t;txD*M;&30k{V~wnKABdrLuvJ5RY&)&e z$Rs>ZsO*I#5$(hp9a-!h@Wo3SG0ufrWB*qJy0K=&&(9u&6(juLZQf&+p~El?3ukf4-RWP&`P)T#@FLsWp}#V zMC>u1vfT`TTx6u|b09&?9EG6JD z2q6$N|BX+P6Kh^Va9#>i0dq^H1~UP0UNZVb1_GE_CGtS^B~G1hjeguM)xU|gBM(#r zSUFFmP(oAAkBzsMm1qX$MqZ*LsbnA~CrGNDiVDb;NILFzCMI$=kx9=3W0-AhNjN{Y zoHm)rG$Id7)xk=-2cRJBS54)lY%^3IxaPj`v~mLLfjrmt!XLG(>%wG07vYp(ui^M5$+>UorOx_ z>O$$>Ic@}`vP$Xv@m_Ktf$A)j$}YKQAg(_*{)|9t+(+Ovpfw#p{@axli1WvgX)4=( zTbau2C_$nDX5omqYU4;5Yrcd?ycDJelrEVZObkH8MZBc<0SpWX#cQCtiDU7#-Vd{- z`a7|<=Lr=9ti%H-ETMnmx7ur$m1qP?Jui`wR3Z?PGDs?&iW-nwB5}LhN(|tvkV)5p zEt;H0_p)`IHYsEpo+sR&g;XoCMMYNI1F?85L(cC)t?_@=fPQ#0Vs+Vr@M1)G-R1{o Wx1}Ok2?e(R0000 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_57.png b/pythonscript/kickstarter/ups/charge_57.png new file mode 100644 index 0000000000000000000000000000000000000000..768103c857717f8a59e4eeaeb6844588abf389dd GIT binary patch literal 415 zcmV;Q0bu@#P);+>UVp* zSo%}DPrnWjvCNeA%XP@_Z{C5!RVbCQTxaM#NBj5dK#EjmYco%ncc8ioNt1G_Uk6IZ zFxUJ2>esIWsXb1uGr7;3ci{XUXw4ZwYX*Swy?8B%>(7v7DVu(w%A~fHAkhG;a75g- zrHBQqK7>d-7pei24w)QO0zkw~JZJU+6asL?OCUOleet!~cdMoNH?ef)36TJ_!~+Nn zp?~5-?X|;-)q&E?b5tY|2V7zSag|fn0AfSLZ#TOV0~lM#xYvOYO?IP432moU7BY#< z6Xy3qqARh+iYzt7yfi6H5dxgA_X!+e9D1bV`M?W#0*OEXf8+_{&?9;!PdM#OpcdWM2j8z}*0{^< zk@ei#xcENdxDF7w$i$$Ca^u(TbK{!>xj9{mT&LVSpwpX26cE2ZL#C;0`lTwB*-`>v zNgNU9^Bizu%~OcTOTkP4A{M7i4on1u;M1& literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_59.png b/pythonscript/kickstarter/ups/charge_59.png new file mode 100644 index 0000000000000000000000000000000000000000..176c8e55ed8cb8201c3d13a2a11cfd9efb5816a5 GIT binary patch literal 419 zcmV;U0bKrxP)ag=BNaXZW__TR(_7U`x<p6#-vF<5E;h;#9+-cPHg`7^P#=Lr)7 zyuVj@%{)9wQ$+;*cH;!E3U zmqupU7sBs_OgphhM>bmnCGi%<^!Gx06aP~KI*De)$HyK-6eH5lZC*~MskLt5W{dy; N002ovPDHLkV1lmcvSR=M literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_6.png b/pythonscript/kickstarter/ups/charge_6.png new file mode 100644 index 0000000000000000000000000000000000000000..d3a4c1f99b78a05d947eac506231da5d7bd4bf21 GIT binary patch literal 406 zcmV;H0crk;P)%+tuY{#-;64-fHpn<)`WMU2hf^Q5#aQ?p+AtwQvof zw9EX0TL2KR-8EeTu*f_xUyUxs*JgiNEnU8eEi(_y0`L$|WYmQu@ww-<-AdholFVyV zBy|V2qNISd%Bgu7%?S-V6B7lT$Ta7H9B$hx8sc-yX_tx2a%tXV5Nl#O6I+WX`Z5M; zmICovveWBLGKWc!RXhEzFdw?iGERWl~0AxsrF#d60`v3p{07*qoM6N<$g5F4~ AcmMzZ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_60.png b/pythonscript/kickstarter/ups/charge_60.png new file mode 100644 index 0000000000000000000000000000000000000000..ec4e5c2903927e707a8fdc817d624670781dcdf1 GIT binary patch literal 414 zcmV;P0b%}$P)bMyoN+TG*M|Qb!a`r^eKxtL^x7zeR zAVqEDG1J%3Hf9p8ce((LBf!4UsKo`)O0bqY0A`RmAXUMXYEx%NyGHqm`2-`&M&t| z(fz6WO2-=xJVA63hvZwM?`KQ#Z(`}l6GQ}Pkp~c%Lchqz zuD4DrRtIV$&ykTt9Pp76#Fb821*E1(c->?rCYUN@+;t!ZwQiK7k&Cs{Duqn4E|~9y zL@TjIMHX8FDe~Nc#($ySu)o!SPOOR8ZT28o5#evQ`2g8urO;TY^VI+V002ovPDHLk FV1kCNvGo7| literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_62.png b/pythonscript/kickstarter/ups/charge_62.png new file mode 100644 index 0000000000000000000000000000000000000000..fe3f525ad14868f27d311a80e52718b9b915da59 GIT binary patch literal 412 zcmV;N0b~A&P)^=0P7k&Q+aTh(gC#eU+(lxDPE6|Yj zJ8cVg{Ln&OiV$(W-UB#bdXGrg^Mx1Z2@-(-{+uVK_lW4#dE(UCfm#Nsfj+P2;{I}b zueaqYO{r1rKWKuRivs{&%jn{ zBcHB0_Zg_(jqcssl;CED=ox5@`wVmetr-CL6+#Nc-^Y*;wk^M0g)(nshzKDBK*U)H zhmu&+DHJ;|g_!_yUK$etvGbDA2QUzz6?sB+5vR`AMnA5W>i>zgBTuLZuyP(iVG8|o zKKHzKT8U<0Yvd&=l1c_*N`j=ysi=U`6iJ`E?8JntMkd_{a+vK#B{p)o?X*cF)9efG z_d=?j*rFq=t$~vB(t`STq1O06YCt#ML@W+_5MD&Y$8El^m9D=A9JKxb0000 z-^5?Q&yMYYV2ZfjpMe}O4n1Rg+VEmLfCwh?XFM2(p4qGM;8NRwTso~AeLwHn{_XMF zJTHN9d-nq^0`QdNAt0Sk9WRY@AA!?V=&a{*$1lgV2uP%QHZ>B`b?zgOU4?S1%d+lE z{iF4PT#i?Ueo(T1q4`m9+)Qa()ipQh^wXfJF&Ir zfr)@EX?Upzr6sy1zYM zS^dK2Q^)HL#3Gp5ulBv?b)Yu#)Gy{lY}~N_c^x36MzGYeqyF82)LaeQbD!^ZpcbW0 z9XWVj2TIrR-0_cK-huu-&>CGpYYYJOd-Gf%{(puzmpA>kRwlJVMnFVx&H*AWLQ)VD ztC~VSc`m34AoAQ$5#W>Oj6Q&Z0IA3mL?>}bzIG1ytEKoiv2^4KA_DfvbI$?x6xt)d z)m|%z)q&i!;c!F50Uy$-#3~?`B4NA9N=z_S$YOPX6KcCr4)R;uX_Z1IS=7w$g+wc{ z#&$w%4&+wk;`j9bLXBbnr~%zr6R|q%L9imiKW_5_=4!J~rb9AY00000NkvXXu0mjf DJ6N<> literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_65.png b/pythonscript/kickstarter/ups/charge_65.png new file mode 100644 index 0000000000000000000000000000000000000000..3b46b5105523eb0a2441752b9163b75ea2a8b93b GIT binary patch literal 420 zcmV;V0bBlwP)m02Sr z5D`KMfQXACIdICFhEOcL6lMyL@Y0wl5DPD9eE1sdWP9CZCnj7qGOiCuVzwHU5Pokt zZPLgzo0|KzkZLEkSWc+rKxs!l{ht0`s4@Px8qkS1C03g~2ro*++iiZpO|c@UIef4H O0000MloqVMOuy1zYM zMdSC**By{ZMK2AQ$QI_oL%;d#5{q zLe3F^c?XKS(OOw3^?rAtHOxEE1+>OMl+Fw&F#kV>ICpIO#WGd(LdMMGoFislgzP~| zY}F;?lh;Bu0YY9IRRs9twS~Y1pdv4TZsL%9sTe<7!v7PaBQJnButi=o0}%lPY>|&W zFBPP^gpMv74L2YT_&_fuRRO6b5Y zesntl+XFfBTEhBwq1Lc})Ie^miC7)>5Lgl6AGi4d4;i?w*5bOF00000NkvXXu0mjf D+ik3; literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_67.png b/pythonscript/kickstarter/ups/charge_67.png new file mode 100644 index 0000000000000000000000000000000000000000..7ec7d82ab5dd0494b644194fe776d4fdde10303d GIT binary patch literal 415 zcmV;Q0bu@#P)suwb5Cg&V6 z^CDyqDX~>k$Twb!Y64)qG*txn#!CxP7og>NpgM^|<4eW(tEKukv9{-dN(8oymt28} z6a;J;AG=;ENHm3xrj0~2R3hM`ol2qtQYjK%H(7~+sX``Q2Vzv~MkS1owbNA!nPyQl z-wUY-@cVx4c0z3qp<>|_S&YniHgAwxt6 zApj!ILL_ivt1h89_}tm&s3t%&=$I-3;^4J~mCTD75v)uQbDRqD0kUtxMAWzjP+7d6~HZ#^m<&8OR3(Cx+-MabzqHZ-Ka+J zYwdKELS|Xi-1kBz0xbG|4zqVa61;}E{$Hpy{}XYZHW zgJnnAbLVRrz(`RM;@WZN$?cDu0cpgfa5Ht>3?ST71>Y%q?(gLDwhTy56={9g+G{NX zrBD&x8b{n`ptu|5oKZe98+kQ(YR`opU0 z0ALcrff8GF38l_Uqfb#yfTq(aRRpBYYYQ O0000y)X%EC7g?o|+~BXk;F!?}cLVz1feerOBCCGxI<#01NR%N>ivLKKHzLT8TT*l6i@W zBudR#OJotCXGyUYd&NVOQPC|wZ#+tSpz*= z0i3ohwiENa&}jVsYJeMWLHzve0lWke|8DaJD<*z7|6o2>00000NkvXXu0mjfR3)%G literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_70.png b/pythonscript/kickstarter/ups/charge_70.png new file mode 100644 index 0000000000000000000000000000000000000000..c0a37ed1a349cd851a8673eda87856bf2a2971fa GIT binary patch literal 417 zcmV;S0bc%zP)PyR`Q|NVBw4a}qjKjwr&x1RG?9 zZ^{EA|NePKfD1TZ@7@_;dC!2?>xGrz5flN=*$W<7-ZOyJ;E_}B0BV``R{Xr4ta6vz zgHXIItCb$HRo zh_+AtpL)*!IuObT5^^ZlxfJ*u^$xJlLaDx8dS9xYdIze5%46(Ls`1xk0?$am$7;Ehz0)JD4 z#CGjMH2Bi&hv+7NGiXW|0W^4SA>sn0GLMK3VmJ6!F?6;ReNQ>_%7iB)D*%gG|$glF-SN zZ)1PJ$x8x(4ipkjms<=D7+(Y7`gh^QcmNR;fKN z-bMYE+U4)Tej~tn1mqsXJ=bsPJ=cD}5#ZoFDfR49`~3fV1VCvHm-ge@^$4t`*7Kl8 zpp;tA=k=vWKnhoxbKr65Z00@!eLt`@UBK2HLy+&Tv>^I^42iL9_)X1B%?lY3g&5;c z1b&y`#5PT#)c8{Gb2Jm6X>>{x0jcrYLd*qNc^;S!;@tRJG3jh+z9+W!JTQsCobl3W zQ;NEPIpe3E*9uZiq0+R`h=xf7Qsz=At;lOp6~LuP{@i6H2CfR3b{&|a*=kh7_^IV| zlR{=$)ZDLyOaxf0`#H?UfYkU}f&IJC*7!eafE#Zj{tkNpFCx<8HXo3nyh4-gWzPTr N002ovPDHLkV1iy^zncI6 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_73.png b/pythonscript/kickstarter/ups/charge_73.png new file mode 100644 index 0000000000000000000000000000000000000000..ed8ce781f6a8732758627969ffca1b02db678a23 GIT binary patch literal 418 zcmV;T0bTxyP)@c?9Ho5c5+17kf{A1lX%ku19M4}@sejdB87C{;LM~p-sfkWe=c5gnQlQ0~OT2{&p!%FaQ7m M07*qoM6N<$f+EDTcK`qY literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_74.png b/pythonscript/kickstarter/ups/charge_74.png new file mode 100644 index 0000000000000000000000000000000000000000..8158b7887b5fcc4f926bd052d4f89bb7c61a4770 GIT binary patch literal 409 zcmV;K0cQS*P)wy*V2$}%r?1@K~c>=I19=Ws@P|9%h!O#1Zb>D4# zX7v-_7xz!~bKVH#j_lFvd*JiujQ|Ce)6{h;61mU!2q4^%m+HSqfD4uLd-b5#fl|10 zBez2L2xyVJTA7bPe;;T~H_)2Fx$?a^7l7Z#5bt@@FSRmNUq%7IdGA*f*tQ2{wrUE6 z;<=+wQO$s)=#VM~Lh;f@)D38jJfb>@WAUYC!qrm!omhM15tRsRiO*RwIfxtB5}#@> zH6)rs+_aHMGnEL0Y^9RK91%4@rAXXvvJxYv3Yl~rNKtJ!Dj`0#ovu>IG>e+~y^x9l zjeS3b+8p4BFEQD_3$@1nuLgKx&BWi&9*7ka;qNwIxnI6i0|WxL00000NkvXXu0mjf DVzsu+ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_75.png b/pythonscript/kickstarter/ups/charge_75.png new file mode 100644 index 0000000000000000000000000000000000000000..52bfa339b85b59648fc9eae21f417287b8913068 GIT binary patch literal 419 zcmV;U0bKrxP)bKz0^+j}dM5UCt_HY~iQhjWfKZ;7dY<~}GdaiKElC0J{TSkXS^1@Arsk~#066dc zCIZKDP-2^=P-uLq_bHkQkTg1^iGa{}X(8$Yv^CG N002ovPDHLkV1n#1uRs6* literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_76.png b/pythonscript/kickstarter/ups/charge_76.png new file mode 100644 index 0000000000000000000000000000000000000000..5936d04ae0a7ea09a7b040ddffc2b3b462060e98 GIT binary patch literal 423 zcmV;Y0a*TtP)vdGm%!~!Kt?aN165xe7~MW{Vumh z(RjaK?tk#V5r}yN#3F?D-1r=LkNOC(uR<{ob9)}|Q6GWbIp9<3;aF~zj`RDC2-K3R z3$?j3A|ORBB49+I6nL_96)LS4>LW1D1HBmndNUbQe!Aj<==(9m+Pd@$txU}e84($4 z?Iyx)PZ1N_bPBn~*LpujGXb1NyEGBt8qY06T!574foKx@#vc{qu9o6wVrkC&*;h1J35pYROC5{|n6%d;ue!I#_3{({|?m7^n*>04> z_|SH`Ng)ks8tji4eyggVEkDv)~&YpN=U1k8Q;*nEt0j*3&|M-4Acg?%p z9xU^5Jmh~fkctShH-f48C2+p^46t`0j7%xwT=UIm;P4s<(T+SaG9XPUQzHXXq#_^g z7b62w?CnnGGceu`VO5Fa~Et88SFo0|Evkca_^ zbAJl4HBc-5sL}CVs5kbn8sHCWCVnn^AXZG!>oy-*6Sp!uN<`iO0000d> literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_78.png b/pythonscript/kickstarter/ups/charge_78.png new file mode 100644 index 0000000000000000000000000000000000000000..cc7a791539b54d5adac7c0e6e65a4e2e0c7c2ac7 GIT binary patch literal 423 zcmV;Y0a*TtP)xC8L5flN=*)twl-ZOwz?D<|80J2Pn2? zp;EsQ0WC@qK}H0)peMOmh+9X@M_}v+dNTy{W^#_dyOILn_cO%%y6Kn7Ow9`!066dc zDk5&9pu{#ELZR`s-lu3LK+@=tCIUj^rG=;q(DFQ@I*DWB_lgO#rTU&&+w+J@1aiiw zQso|@;sSEUr=I6>KL^nvR6A@WhM7tPLUKb%VvdLkpoU2N++-z2OcgTeI*_8-YE;7b z)N;B>A=4~s=GQ_h0yNhB6l!x|i}72;j{icvv47M6Z>)*xC8L5flN=*)twl-ZOwzrCZ z2kW`qxVRrVZUiWgAh8GWo_a3VPmMor1Sm>9Nx_ZOhyarFJhpRJFYfzC1WL)}Hq}1r zfEJX8bM458Kq=fk4!4e&kHGjI=*xAmrcLa%G7)r1pw#0Uq!@y z6qMMeDHIxC>V1l40wj$NX(AvrURsE{04>iWs*^Z2ey^BtwN(Em*7iK25`it_Q%mI@ zq2dCzj8C<1t^FKCQ>Zj;B+^VJ0wG(eBr!)s1yCsxx0|fQh^ay*T?bM$+l@*XpW040 zDP)>O&HP?SMS#Y>pF(X8)EHkQcKjFWjs2qrcwlpLj4&BePfW;LuvYUIyzIJ|Fkw{nO>P z+HUzA^a#`fkLEmCdY)_FBcMfXq})fqybjc6pl<{*0_&AH=s2GQ9d(h!TjC~31QpxOJ%CnLWUPf6G9+n-t_-DW6g&U#7kjnz}6vC zgP8#kFFhH1h#lyA|r`M zU@0nRkW@NVPopWJZYwd7!$Kxm2U3`At4N4XEvHQinbxwLsL>>rM70uYizh0~fnS#c y_}a2qPV{}D*7!eafFItB_&V$XyciK5w|N1)9g8pvHM<-D0000y$rE7Fa1I%Rr5jy0LEIoi}2?t zD6vgL$TdE9_bHkQ;56E$i2&DlZXx0Vq;?(=O=92pS~2cvDgK{Wdgl=l55$ZQ9hEgg z*#*Rm4~@sp{uEe4C^u|4!c4>iF0oMJ$PrcnREYTFDl0Lfs*rKlfe_8QQ4Zrn?R1ku zCRxH{|u^cEdzC`SNFVq|TpBmr~Z6f|Y_CU0Va6h;C0{=wLzRj9|`2YX_ M07*qoM6N<$f-c0iWB>pF literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_81.png b/pythonscript/kickstarter/ups/charge_81.png new file mode 100644 index 0000000000000000000000000000000000000000..373c90522190d4c75196dd4be9e620b64f068070 GIT binary patch literal 417 zcmV;S0bc%zP)Z4V0q@t79KYNi zN%wKr#n0ug>(_zQ8H~?!j+MT@Uk6Hoj%+!R>TJIbY|Z3W7Rm*;ruucDGz&?;r(XxO zky=O0J5b$?=4PSVtaUi>Z!qt`Az*I|&dK%YsQ~zX4Dmj1`B9mwc_9M;=e=JNjgLm0U>#5A?gCOW*(ush-31#V!~{x{-0QT<`F6mtdXbEWQ|a9 z0c+%`aW3ztLNtVO!$u;^R2&Gg7D^IpL{tD5BJsG%N{ld7$fWB4g=XET1bM2RZc@lJ zi<_Kv)qGZnyaWJNUxB4{F5900000 LNkvXXu0mjf0tU6a literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_82.png b/pythonscript/kickstarter/ups/charge_82.png new file mode 100644 index 0000000000000000000000000000000000000000..523b7c37eeb9f19d04b41ef9734906f012d165d8 GIT binary patch literal 423 zcmV;Y0a*TtP)H#LFZN_#5)TfYmTwyZ+-uMBfv$gXKVI(q})fKyAN=y5Osgep0aZv0kbN^oo}_< zZv_5DujlhM=RN{vRfw{y-B)|x5rM7YJ_3h;tuchqULm9a@ckGPBX9YYDwKI61Aq`? zTtt-SOQ6J>rcg?r>wOAl0whVNFcFZFmlmQfK+E$8)kU0>?-i3)OZET6+MY+KL|~14 zsZ90=6&J8ZzBEqN{Z@#k5I1ck(o7`+Db`X+VvUFjpi(3sciD*%t{RziA1J|WH!4BC zw4F9-WSULQ{a#2#fX2R`g4!DRmpu2L_%1XU`$rA%hcyvDhdmH0BEsV~AE<=S49Y#? Q`~Uy|07*qoM6N<$f&*Bz$p8QV literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_84.png b/pythonscript/kickstarter/ups/charge_84.png new file mode 100644 index 0000000000000000000000000000000000000000..ac590a13ed2caf25030238df1bc7a4248b97af8f GIT binary patch literal 422 zcmV;X0a^ZuP)aM$Kz7tjq3qw=D7dYK7U*f>?QULcMrw=$Mpbr2DkRa7F2)p9@w3UxlyPz>To@0 z-UH2|5I0|xX6SJ}us4o#$KS%d2aflF-W&pYGdWjZn{xs9eGKuQxBXI)sd+gK0M2{A zilFX^p~N;_LZR{8*{5hGK+@=tCIUj^rG=;q(3*KfbrZ+N*NO?FrTROu_RJ$H9>^Kb z36Uj;3&NDq(!8 zoNkiHG?SY7T1Z8JM%_=Lwg+mAXC=eqyHIEBA2q-q)C;9_Z~3MLekIK zuLrnrJwo-*W6r$?%(GB$);c`!Z@BlsAz*8U5Zd3CqyYGS42h8!ex*!hzMTeu5Mx|K zPjZQHUkQy&7L|uT^%pJdjdbN)j<5Du7xddArL>jJPUf(siJO*=|(A_|kUT zq>yPAHTQcV6#*LiehRf1=rNvE43F8v*nX=NayPX)0KOkXVys(!xlCnV$N(V37#9(1 z{V1GR(4=hA13 zP;mik#^>5g^`M046snyz64Oj20+h9>B(X+B1#nX&ZFgCT5m$vwx(?(p+l@*XpW9BG z6f(`C=6)}vB0yu`&!M&kT8-a2_V_N;8~;ZQ@P{`MKZiXKFCyr1n-2m2%AW!u0WJUl N002ovPDHLkV1n6;u|og= literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_87.png b/pythonscript/kickstarter/ups/charge_87.png new file mode 100644 index 0000000000000000000000000000000000000000..ca0a4781cb81abf7b9620787034c271063019703 GIT binary patch literal 424 zcmV;Z0ayNsP)nk*tlZ`3SJ{K zo_Qmn4b}7UoSQcS)Se}N&TYSh`3M}}1EVbqIydxAs0oHvy7PhjbATIxj6mU4T~4BdU`)cAhIHTrJhViM4wkQHem# z`PA~s5h^Yq=X|QXln+XX4x!dzBQeZWA`p@rN)mHKQ~)(Z;&PLj7%`Q|r1L6_fx3N0j~44b&vl-qp^R~0B@{`_&e-@SP>B(xA_7w%fTA& Se4~v30000)jd!tjkRNetV-uJi{i)7=7ZIb(x8@if2xv1++4?p5Xm@29IBE zkEZs1yV&{mn*fhcZlwA9+V-WtzuyGVlqRJl9>$l$o(Bu+f z)F+^(3dQ;#35-U40*8UoOvc130}7(=%@AwrF27Kv>Ye}*6$>Y#?G>Y|0Ty3lB1bm^ zlA>L@7;wc)8-W`@jXVS0jr!tS&A8PP{+}2<@(d(_l=x6dY%wZsASFI@JXPaM5S>D` z(?(*NK@xCDO(ltIMASfRiulJ>uEdP0g-m)K2+{4fiiG&C+iB$f$3^{K2x0)S?~ehy y1|-Cn^gr$kjYj{Y2K+&riJyl(7%e8;$8A1UKgutUyuw=m0000agJe literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_89.png b/pythonscript/kickstarter/ups/charge_89.png new file mode 100644 index 0000000000000000000000000000000000000000..a63ed0617205055633c13b89dd472c949e95991e GIT binary patch literal 413 zcmV;O0b>4%P)j^%uXSDrt zdo=AI?w2~>eiJ|sm3kW6Yxn0~f4>Q6!%fP30?qS)R2AYL7j8G7K({IcYLh8lXTJ$Z zk?P@6J20QX@G8Wec~i$935>>k0*8UoOwN_pC(;o89)_%I-R&2vSltsKBDQcM+CC|| z8i4p36D7JCkQD9H#egqf+K9RVt)2&}tI<$=s~Nvqs{bd}?s=e+Ku&zDB(@k8H;@w_ zYtPkq4x&S-cGyS^GnE89tT|8<)$m2}E4Co%kj00000NkvXX Hu0mjf=HRe3 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_9.png b/pythonscript/kickstarter/ups/charge_9.png new file mode 100644 index 0000000000000000000000000000000000000000..1f8b8953681f601100dae36ec1457b2a3aa441a1 GIT binary patch literal 409 zcmV;K0cQS*P)U*rG?y|oBgDjWz)IN!KnEon@Ol>EC zg~WGCov8T%8*pT%6(85HLIHT*7smDLB8qqbBbX?jc<{V0%u&UI(`*4pIeWkG@$+3B zzkJ@_KDWIOG6KE8qmn0E_oe4E0#@`!DkIPfJSJtK7SbBY2#nI2+tPT}0V^aAx5hz6 zV3gh-r?(=9k3jARW|IcY=1jD|KiLcO-^Y+r#+F|#Grditc#$@x6k_I~|Cfw)pF$9? zg{uLhQ>F(u10Y^|TDllu;dx-*3$4W;_5M0ry6lN9JrB$b@DNXAG=(Jbweh3VN<9K2 zp4Z4o>JiwAk_^&Hr{-z2CNyj%CJI={H0wYOw@tl<_*y&dQpl{@a-vC#SQFDqY%QLs zu?F_G1K4eOv=jMVXg2YW8sJAXBmN!s08xxsAGi4hiQ0xfR)ysM00000NkvXXu0mjf DKqInt literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_90.png b/pythonscript/kickstarter/ups/charge_90.png new file mode 100644 index 0000000000000000000000000000000000000000..082c4b78ea0c75f673d045041ff4ff36a1fc5744 GIT binary patch literal 420 zcmV;V0bBlwP)WhWAup!%X=o$Dju9#3n*nOy}{@8^p;<4 zkD~f+z0m#ojR1ORucvW6w|?sX_ZtB&j?N)m?>7SO*v=(1M^fv9ej@;K*ZZTZ&DaN0 z+3T^=o_8awJ_5zrXll+9s-M~})P6*uHR>ZU476r4=6F7H6r$hDkaf+Qe!i8edIX3l zSvV1GzYtXoKzxpgJ*pYt6m3(*fGwWeh`0f%o(H1O#IE>KGxlmJ{!A?0^FSm5G4Z}7 zvBW65ftYw-d2EeOfprMw4jYbPCK3Ug*ihn_Bdi8OL&Pmtxf26b8yWXL;G^2hT@LY8 z(`m{4$3}fFBw|1!?++n12ROv1v>)$=+~y08V#`jE(E{uM O0000BrqBJ zt+clI9*>{2Q*a*V>wR?wSl%<>=k0?P@d%m#=j@3`miG)`RXlR)EufaEwt>&<89jcv zJy_RK=Y{Q3=Z%{Hw+F-Dsl-bEf7}F0!9;RNaFZQ30d7x9LCvXg6F|7@cqzHn?nodc z*JHKqgvpvupt>66)-2Td-0P_8j|6&SK7qqPZzkv3)iVgd?_tQg?wfwGm8y9H0I*9q z0BnY1G&O+Y_nJu2%z&h5pC$%;@zO@r4QTZ|qIyppimx@}ua@ff#M(WNs3edQA6pV@ zjEWmbiH{vmt??X0hfv8TRXfa767We4C5bsAYJeId;c=5YF=A>XlimknG<&!!A--uj zty%y0n6HIY3~1E-6l!xoN_?)3U N002ovPDHLkV1jAdAM^FSfXD@hUdCve=gGWxS1K=`o2l%|6!S>7T z!Rk-@#m4K`0i2<|na2C2{d51nUk6Ibo!Pzq)Tw?QplkamxobbxuU`j{(tJ#aL1M`>TLny- P00000NkvXXu0mjf@36CC literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/charge_93.png b/pythonscript/kickstarter/ups/charge_93.png new file mode 100644 index 0000000000000000000000000000000000000000..34c9eccb173d040934e1258873b49b8bf478c17f GIT binary patch literal 419 zcmV;U0bKrxP)O0!F- zeySby4zOpTy**jl4p8p^dlt&gCN8DUfZ;%I)H^T)^ky<9f49U1(f2T9+sa-)U#4pA z01*`j4#7T}3PA9CM1*K2fHP>DCIal>xrK-ekm`9Lx+ivnuN7lwOYuFibk7432V%kd zQeurzb^)>AedVznp9AXdA@AHrAz+SjxhU=_(2ddYB-0Z^b^LxjXc?YV;KrUmo z+;xC^|8Sr;<{cOUdNVmEw`y_$_}#1z_-dL{KynAQ`kz69Ink(n8b)Xk{KzeJ2isuNC9Zmg?`s+L=dG97qKp zn-Xh;iVH{uA1hDI@f<|EP-(Z3=w>Po_@ug$#2673Ky{I@+~i7(m|DoB*MS(#KJH2c z-*h{zx&QGozZX&wpt0|#FtHa(wKKj8^~U~D1N>o4#NS~L#EJ<2xXlOC-*5 O0000wR+uSeKb_{kyPYJb(yt&YtmLU1lPy#)DID18SLSAMk!XqwSa5 zgEcPfU%C&*jQ~89@=WS}X&m?e$Bh6NuIKkgcH9WS1F2akMeVE=_8T_>pp>iJ{@hvX zh(L(+JYQO&%}1cR8r@s7r26k2ml}UWpf~0ta0uwl*Dp3xHID!h zu?^4BQ~->Z6wyq8wC6z+0lx9(Qb^15K=qqAG`?1hKU=DQ6Ki`Os6-%VJk|i88lmC> za>mEnV}dw{rVux6B+^VJ0zSD^l6Xf%1*B3WY&W?Q15*o`^g0lu+2*c<@lDHV&HBg3 zd@ZCRK%?$YVZIhR_Jw+5|EK|eSQGJg*aKJ*;UBm802IZ;F1>rfhyVZp07*qoM6N<$ Ef>b;Qe|A%P+S_ zQoY#kczxa;=+^=CP^@S8_py4se(wMG>wpw7CsUp6*MZn*k;>6Klv=-E2f$ITmU86O zaDbDW+qvy(NcX+tljfK#eu!xF$egR2o)Ew7ksEZ zBq#^b6v|B-i8NDjz-KR&B;FBG0kIU}!~9xkoC~$a{#65fuqNW~vInpt!oP0w0o#4%P);(^&=S*ZZcyJgUKr2&g1Fy$DT7J6R zn(Bx3QtfTm0rXI*r*WTKzxMgtbwHb1r_4LhJP)L@3MgflRK3)5!g}T%XnqH3^ZmbB zsPwy!=e83%Yu*9zEL59KLbx+9*c}*+c?Wg@qnVs5uVUp60O&kW_D#p*2>fglLJr7hI$OVr%z^6s1 zxPV;nvGSN84x&SdJ8UF|nTi8GxuGQSj))3K4Uw?iP)CVV>oOCbe-B!W2M|HV=rbOy%S@!zcyMZMKq*t{3*N7%xBYT^ z6!mxeh2Gb%2QWgZk;ebI{r7&qUk^yL*His^z>RJ0{w|?8(ys?V>3W~)pIV)U2V9P> z=b=oT%Fx3Ddux{5{(C*B-UDMl(3&BjHIp&N>yx7peGfynEpPhyR;KD6Afjx;V^kFY z<2glC6Tm(4po#$7cy%EpHS$1oPwX0BD#l(d#rMS0BM(G85HlV*z@|hfyMUPSzILCW z99TmrH*7ey{H%)~%dBIC{jKB^t=au{E=oR+MAY}D66A_64p x{t)VGp>ZzM8vR!d@I#x3zsnv#iwOI=%?BbQ!ZqcLl-d9Q002ovPDHLkV1g)$vNQky literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_0.png b/pythonscript/kickstarter/ups/discharge_0.png new file mode 100644 index 0000000000000000000000000000000000000000..a30674cdb48d39e98cafc3c2b423926d345e7422 GIT binary patch literal 323 zcmV-J0lfZ+P)43R3;qH*B}6lL$6kT7f?EHm%1`zy(((wje~aw*3xbL!sp|jy??sg zzP3y24LUFvi)bm5wfa(fIkn#D7 VSwf6IETA;rFcB((*Lb7vT2<7>8yoKitfle#@bz*Q5s}^D z^zputt49ZxGVa;6?}2x~EecJIfw|AYJKz?DmXdRf4wPnUtk8i{N4%7y`0~Fv*Bl5jDdTCOTYDi>y-(k_WDs>P-I@ZAdLKNT)ynt$FqbcHO`8R7~ z3h;&kyrBSZ-~~V>vea}M-T?~mW*7sH6%X1T3-^V*!9QwX7ic104tp3VBI4sVH<3k7 UJJ6qMY5)KL07*qoM6N<$f@B_qQvd(} literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_10.png b/pythonscript/kickstarter/ups/discharge_10.png new file mode 100644 index 0000000000000000000000000000000000000000..16448a5082cad0cfe2915746d4774b5b730ec114 GIT binary patch literal 336 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|9 z)5S3)qw(!5U!f)go>t-Z>K9(-RsWk$xP=u7#kd-5an6-AK3{piYyN>RPcLpsUA5^! z#Xdi#0~TMhj=yJ~a!gvy$@PO&evk9|u3Ht41@1P4?b;jnrRI*S%af9wqDL$L6x->2 zkF%O^Izvxy`pF>nS{vC7JyWe7YQ7b=yZ7#LV^odWc9}C@1+6~lZoS&}_(0s2w0qx0 zE7)hHFWy?Z$E5CcrIi5h9%c~%U-cmS$$v~&dYSU4tXe($wRWLT@UCkI>YYS@@(mkA zBY96}{WQ-`;_7H)TC}hs;{mG!Z_3J-CLq0M@30GL%eDO1{i83!`!9^y+EMu9#+!#7 f1UKHv`^+@ySIRl=qR5ZHAY$-z^>bP0l+XkKqQZ$( literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_100.png b/pythonscript/kickstarter/ups/discharge_100.png new file mode 100644 index 0000000000000000000000000000000000000000..9496526925cf3918f85470d95efd917bee681dac GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|J z)5S3)qw(!5U!f)gp4Q;@>K9(-RsWk$xP=u7#Voyc%Okq+gVoP4gWA@8E&mKlMG9=< z)tM4xs*Yd($F%e#&+G!1ir(~N3(NVoemvv2oALi8PuBCt=XP}XO}~AfeS@Ccs_Coy zOD}!Cb-F=B(bHT!Q&0cu^L_jG@*EM=Ub-bNSN_qbZ$<&#U(T*yny$mVb@B0_s?!d6 zD&Aj%o;=>iG{15E)fZ9Ov!njT{QYLsz*fU{rol`$@b9VO>>%Z-^H1FQ&3{^NriSgc z1NBa64$>9O-EU89Q;^Y}eN%WgV=hoFSTI01GRu;8KBMu1-uKLx`qJOus8`JW&@c6Y lcUEWrVL6_z&JR1za`Qw~MH?Nv>I4ia22WQ%mvv4FO#sZdkMsZl literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_11.png b/pythonscript/kickstarter/ups/discharge_11.png new file mode 100644 index 0000000000000000000000000000000000000000..8d8483183b74f04dce16a1ac1f633222d4154bd8 GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5DEQmc z#W5tK@$D=_zGed+*Ztb>F8ZZrzOSFKY0AnuEq;2Mz9K3zGvj~yE?{|bUcF4nbwa_u zduaj&z0EJaK5tU3aCPN4Ail?_x0xgR!>N$Ni8V#B|n zYk#xfFDyTx!yVb*xc2-->x5~oQNcwzG8w+PYg(AsYSjKLm0eh#P%f(xdTnj-oT&e| zK0j5oJUT&WAwb8TgS!NB0@>gTe~DWM4fP5gyX literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_12.png b/pythonscript/kickstarter/ups/discharge_12.png new file mode 100644 index 0000000000000000000000000000000000000000..8a51bbed0ce78e465cc925a7ae4438bca46ae07d GIT binary patch literal 334 zcmV-U0kQsxP)Xr7I3o!jK7olDVyXy6^7fM`0A@(K~nQiYlaGS#n}BFVl>Fw?YLYv4a23Wx?? zK=_KRhO}D#&6=13qM?9jC?Fbm0Z;?k(sUZ$0Sbs_{s%HEo?qW{;l5Bb_(u)w0!_rn gVGjdEM0(uj1%g~+wv@!!9{>OV07*qoM6N<$fG_`K@eB1#5r_1eY zdk*ctygqf`aKJjUss5?<+;iz2sFsDSBUw7~+K=9WYFWt2GTY>wNL?2Wl#+8I=TJDX zm0DS9S$F6iD6RvkN^G4IS)EbB0dMFX2m#(S!1g)4H*1+{Dr6!bjbqa;;n*04^;|>$ z3WWe~=mog12Q199<=?D{Lx49Sz?%@@4ZQ%WLbf)YrgtC&cr$$ii4_k3{PKU7_%GxQ o|5gJ%VH5GT*+XFwG2d?U0&`Mim(m4O+W-In07*qoM6N<$f_Czbq5uE@ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_14.png b/pythonscript/kickstarter/ups/discharge_14.png new file mode 100644 index 0000000000000000000000000000000000000000..66f405f95c7c23d39f3edd572f96aad1d10dfa62 GIT binary patch literal 334 zcmV-U0kQsxP)b7harKRD=NjoLBm;74ho4aZ z#ZI3qx$iOp=}2vDm)bJ|nwzB~n&y1YE|>GEJtI)cw^eG+o)K6|F6Zaw1I`1rlvb6} z=L#c`j!12_QA+LIM}Yl+H56b?1Jtk0_hPCtjb&8i(3xnzbJEeB1#5r_1eY zdk(!nzCQI_cc64+mDEW7+AqBWt7W03-$1D|aX<79td@mxeKFin&ZR6*7^J#<6LWaOxO_`CLQ) z3b_Dl=mqS4JrELR%fDF@y8vrkfHf|_8hQa#g=}s*P49pUux9!O5-T16*z o|5gJ%VH5GT*+XFw5pK760S4b*BaLULr~m)}07*qoM6N<$g0R4f0{{R3 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_16.png b/pythonscript/kickstarter/ups/discharge_16.png new file mode 100644 index 0000000000000000000000000000000000000000..9ebea3c9fdf6649b8452b76d86da9d612d11853b GIT binary patch literal 339 zcmV-Z0j&OsP)>j&O}YFS7d+1ev7{lGg=EeowBH)k|wat^u!rIgMQZSJ@O ztIi^&8d=Iq0VQ}rL@*Iv@WMQe%&5T&2kiiA8CnhWeB6nM=ybVl z*5{D^Vf)m$=0NGp){Yalhj*Y^7Rr4Ct@?%S;T>p}g-WTNdBGg$P0lHEpcko`57!HG zAe@oC>p{r3{ox(3^ME!cK%0T6d@rs=EHkZXl=-95XS=pP&c$;L{3~Pvw1F4!v%Aq4 zezyFZGqDNK#sp|%0X7f#k4*h;roL$Bw9h=?xz zy(&(l>*=^$yY4`4M00Vuk*sx4$GZbEVzo{zZC9h6atCTDIaQmzbeua-%i1hds^<>0 zqU4AG@4)8O=+dmRHVftIy93t1JKzGW>4^4UNwy-Dspg4{`RApk9MO&t(zyoy6LJC8 zzzaC+YBWWhE&pau>;kND0oJ$xYv2VyO=PL%G`s^Yz?%6V$n1Dxj(Gm!=R(%tUp0^i jG!b8yJp>dH@pYRwO|W9NEddzn00000NkvXXu0mjf^Ingw literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_19.png b/pythonscript/kickstarter/ups/discharge_19.png new file mode 100644 index 0000000000000000000000000000000000000000..7764268b70ceac78f627ebc76c633167f9f99e41 GIT binary patch literal 339 zcmV-Z0j&OsP)MzC#KEt&*3kUwD~U{cpa}8+&u|1E=FkO>@02eVqAyZN|(cdn%{wS5Qpc zZGVj6fW?gLu;m>?VTx(``jW1YefUmQF7V V*V^VSbO0Cx44$rjF6*2UngCl5jg0^R literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_20.png b/pythonscript/kickstarter/ups/discharge_20.png new file mode 100644 index 0000000000000000000000000000000000000000..70b9823e04bdc081b387d70480bb03f1d6302657 GIT binary patch literal 341 zcmV-b0jmCqP)Dd>SZICXeI!>Ii^&8d;9t2PlIVL#L$xtrS(q0>1djEbMD9u!kwR1??`*k2i>9d@gBengS1H#(OxAtoe)Mm6b z^Wj{9cVP9`Xs&7qxeTV_O7GjR1A7790TZCjKvZ5)w24)!kA&UfD00000NkvXXu0mjf-^7w4 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_21.png b/pythonscript/kickstarter/ups/discharge_21.png new file mode 100644 index 0000000000000000000000000000000000000000..ae1e31016d370503a80288cef839f45210d7cf26 GIT binary patch literal 333 zcmV-T0kZyyP)MwHZG4h% zMGK`o9{&SV#PNI$1i(CvEa7(mrSO7?U?RN23-dHGqZVE`SQlU|!`cj2gzj zS4x-WTT0ij1KJu)<4cY${eQm>)Y^FMyP6YPXZv-aM6GYM&et6f_O5xs9oW0()6omw zfyJv)?To6OiM0;81HT5m11`XtfvDXYeQRC2LLy`LPDR3Aq4k-~}|_ zk!?vlTK>(R*acYQ0<3WX*1!vZn#gj?X?O=*fHhGc(4DRnHb!RnxsWyZR}JI=O~l7# f4*^9)eBI^+CHi8v4AX5Z00000NkvXXu0mjfEF6jW literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_22.png b/pythonscript/kickstarter/ups/discharge_22.png new file mode 100644 index 0000000000000000000000000000000000000000..12909cd9bba3984a8e5c2f8cd41032abb95a2cac GIT binary patch literal 336 zcmV-W0k8gvP)lb)dxBzPuD)igO2qtursU16yZ4ouT0! zSiKr8t!YxynOGaIt+!tXeg$|3T!1wLk$!}*CO1Mw6B)l1=3re|b3(|EHSk@?1y}Gub(bA zQCzNE93R)mjOzgRP_Ad<`_j1D=a1_^ZI8v2dS2=nyaV0mf!Z-GjnBoI159f+DU~nP z3-3U;nYeWf*T%;)G`s`N??CO^rc|!h8D?Aueg$|3On^3%bMlO$O)66b6B&Ko%t7+d zZg`*0HSj~o1ZV>UtP)^0|BruGt2Sw0A=ukh+rbT!3*m$Goub(*jW#tm8rFZ`|X-Ne>h#L z+Nbr?ezpF_bwGP^wbxtkKdu9%b37$=ruH35+mGvj#;2&#%Q*qwfo_?&SJbI>&K=ll zZkJ|gcn6v@u~rtob*8i(yaT=-u*L;gGZD3aoBxVjWQxKlzg^Ve@~YhkAs=htr;rP< z23|n-71@@=(eiKB#4f-Z7hsJGum)ZLR3giLPQyFk0<4MNf%3mA%Hqfb|1V?>{#FAi kpow_d>>;3th_~B30BFB$BMnxkQUCw|07*qoM6N<$f^OfDLI3~& literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_25.png b/pythonscript/kickstarter/ups/discharge_25.png new file mode 100644 index 0000000000000000000000000000000000000000..3557203430e15f70bd2d2cbc020e7d1424943fe0 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|N z)5S3)qw(!5U!f)go>uAh>K9(-RsWk$xP=u>+SL`jQS#D!MZ1&RnSLk7%lT>sb8#O3 zY|6&)EK_A)UHk%`d5MA|O!pqltd!~uU_URjhW)~+3%3q_Ex|3HKCS(`i}}`x z=~gONm=rdjRL;|=W1N=mkQpP9Dp&fsS-;S?kA5vJN{eB) zaN2p|$tmx*S@Z4JF>e)I)4S)&%|h7|igTq6FRc;1Zp<$6QsVvGt!EnJDr2keSMVLQ z{_yR3hTiNWQVPn6?-)-U4cP!0NE~M9}=pVBJlY0pH72H#_{Y`WEjY{bwy( mO_SQA#Leu@N=kR~ikX*P&zx5g{n;BBQVgE1elF{r5}E)y^^i>f literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_27.png b/pythonscript/kickstarter/ups/discharge_27.png new file mode 100644 index 0000000000000000000000000000000000000000..7ac672534304bc1f9ce599562b51ede31ffcfe4c GIT binary patch literal 338 zcmV-Y0j>UtP)=AD}dzVG$GpUgMd08i`OF&m629u#{nG1<%Jld;N5| zHN}B1KiW4^C}e}!Ct zHShvfpOI}z+FSn3oY)0e;{vR40oK3^fclZ;hSTs4xBzRCZ$P)YQkWp^j;{+@gTK{) kPtZhsZ1!MKM5NnoUOKa4F~o3q@Bjb+07*qoM6N<$f&{CNkpKVy literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_28.png b/pythonscript/kickstarter/ups/discharge_28.png new file mode 100644 index 0000000000000000000000000000000000000000..56096ed2fa49ddb5adb50cab2801eeea6f21f8c0 GIT binary patch literal 347 zcmV-h0i^zkP)pZhAGZAX%nS*u*TA5k{JRkSu_0#3n zR6njK#T(ZF9--Vwb9rt3()P!7pmd~q$B&(YccA+`Q2MOL$U zXAacn@7B2SI|T2*<}4&-lbGBHO7%+H9oKjJ^tUQ2i~N z@jjhv;2$9qpbfl$ZtHTdxk~g|gV&uSO#CQDj zRVjvRscLq0@(WDnB?_kS#66x_INIm$ugMIycz3t0B)$NSfQz-jD z&g-kmeSO{e(f@bwJ^2>${>Q%H$5sN$-}HXpcUr&W6~pA&u3pF6Lt;u7nAAVFo4@Pa z0qX!;>9c3mwieI1Q+2%bL3l&C1FMIzWU#&c&DjCRir9>P?V8ziN`B?BTLtSH>svz_ zOg=E}T7P2Ok+ak4H>-s)URuGB2^L(yw&_BN)ICN~h2GD+Q~t+x>P#wg{m<>$y#H#0 l{{@$toNoyME<0}L#^t`t(Q5xA`4AXR44$rjF6*2UngG@_kM{ro literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_3.png b/pythonscript/kickstarter/ups/discharge_3.png new file mode 100644 index 0000000000000000000000000000000000000000..647deeadc7aaecf31e7a7261ed33715344256117 GIT binary patch literal 327 zcmV-N0l5B&P)Ex05 zRs~9!*GH#6sY=25dQU>YJdKL-x4;rSfCv@g3m(kVsLC2VIC%%Km7&*%-mhmGKvtVh(>o9Xyvco_Sn)9Jv&6oTH~dEpaKR?x=dcH05m6qu Z`2d$gTK%CY99jSX002ovPDHLkV1kWBj$!}+ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_30.png b/pythonscript/kickstarter/ups/discharge_30.png new file mode 100644 index 0000000000000000000000000000000000000000..4af715b485fb8ff5d6e636a6c2c6fa4e5486e390 GIT binary patch literal 343 zcmV-d0jU0oP) zR<)~q|JG@$P)OJx&oKb7E;I4*^8jV=42z%;@CMJU%S?nicxGokfKjI2#QW{4iaVSx zs=Qj9yv}Ubfl@`da;1v4=9S?1?K&W7t)R@4Xq|Eg7_Nxc2^m-GYNh+S1Cp8(z1_y= z0ZDJy8Lhm0SKu8O{u=G=dKm2r%EO=db?PI+EcoTG2d(8 zA0ZcD4ZMJluLr(K+AaTPP3!`!aRJu20Bhg{Kuu)1&uMrET!1ynJJ9}jrLai)DgIx` p8vLyWe1az8WwQr^A|l;x^8gV)Z=QUGnWO*!002ovPDHLkV1oUalYamJ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_31.png b/pythonscript/kickstarter/ups/discharge_31.png new file mode 100644 index 0000000000000000000000000000000000000000..f115ac6f89f32cb03844ffb9ebcace5e39762819 GIT binary patch literal 340 zcmV-a0jvIrP)ivEfHo#T8xx=nya1>|7Mo7PJ75B|@&19b-Ic^bX)|nJNE`g4 m26BTY;^VM~fFi;_Zu0^raAcRc-G)^F0000k@=TiWr~2 zK5NaetyIzOpUeWEd5MBgWbQqgnH$>?pniVN7WE6KuULKfS|f5a_F47C9WgOAb1l~~ z2f0}k2f1Y*_bm8RDBE!|b%V*~oj-oqecUtP)_%Fh;X~j3)LZTaYac4pa1{>07*qoM6N<$g3hLjHvj+t literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_34.png b/pythonscript/kickstarter/ups/discharge_34.png new file mode 100644 index 0000000000000000000000000000000000000000..e407b27352f0b2e89795b57df36aa182fd4b13bd GIT binary patch literal 346 zcmV-g0j2(lP)P`7{9r3JJ&aH3k6YX(WDrKcIA;VG$GpUgw#48i`Ol&m62f(96(T@%gx`%AGDZ zRXcgPynU&i{W>6xh%};7xmLLTejUiE9eFM%IiYpf9bobim7>a$LlRPbcn5x#iM4Y* zr`9=m2f8z{c23vk4Q+Fc8wYOks$ z#&WEIpF%Fc8h8P#?Z~zujh272Cw2kWxBzQhfHm*}pb}YWISucC3$P~n2ii|pgw4_> s__>fZ_*V`11x>`qWe)~LM0(xk1r?oRhNVz$Dk6Rqu}a(D-tQ%EAt@!r(Q z{myycc43kqB@ z=iLcfkYk7EwR{9v-B(<0Nn*-XwJ75B|nVc&>U1^ibRKZ3@UzIgbUw0Aj z)42xz6*2+ZzzZ0D9@vsFTmH?N*aT=}0<0Rs4Jc+6WZ`~Uy|07*qoM6N<$g76rUG5`Po literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_37.png b/pythonscript/kickstarter/ups/discharge_37.png new file mode 100644 index 0000000000000000000000000000000000000000..0de290ca3d00e230f4cdc9b7bbef4020dbaca899 GIT binary patch literal 343 zcmV-d0jU0oP)q3M&c;=)%fL5lxiLd9QDDE=f zyTtQ($+!-rD#DeM*HiH_uN01lcc5DrN>#pho{TpKB&8K?tvmzo!0s-TLiX|6XF`&@ z&$tfss&MW7<@;h>2Xb?qR%c4{;T^E`fHo#To5{KQxwsaw&J?s!`ngyGwWVkAKHh8K z|3W4}8+ZZT*8@iqcFVt66Po~SOn^2fKpS`gP%pCBbQ<0P6QE7-477b+Ni3KS$G!_` pgTK{)f1ru@+U&ughzPgaya6)9V)Op0RA2xA002ovPDHLkV1oLTmuUb1 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_38.png b/pythonscript/kickstarter/ups/discharge_38.png new file mode 100644 index 0000000000000000000000000000000000000000..a571a77c8504c2825a9f3129570d5acdc7ed67a9 GIT binary patch literal 345 zcmV-f0jBKy>0un2MvZ}7~z%tWYzXAar}=w)gRd_L|)?M|0l zQCzNF{G8dZ15!pfb5cfY<0QEKejSLZ&0vmFr{EnJz7CYW>oGa!q<+l-i8y1eRVWt+ z??CrTJgVxH_8-q7cn7-YKy6iNtwK_~IiL-^113P5$+_~~l{TqL6)a@*^{^J|??S}; zbghAZg-n1p@B)VIiGM*@E&pauYyz|~0os@VZQun!Eo7NMSY??O3 r{tIb?f7F0)&_sM3_Fzy%gvV`OQ<`rCMXY#300000NkvXXu0mjfRAP@R literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_39.png b/pythonscript/kickstarter/ups/discharge_39.png new file mode 100644 index 0000000000000000000000000000000000000000..d47f6f12a446848f12d680b5af40c7b21b227cc0 GIT binary patch literal 343 zcmV-d0jU0oP)JevMYnKJgrb0Pr7p=Wu1KA?175D`p-*Lh(adS=wl3p?))w9+kYc)wk%>K#s( zs&;er%5q`54rmdP$Y~L^w$qUM+jXGC8o@bgoq~5@^?9K7U)QjXk#Im`X;!gjFL(!< zXX4hZV~u<{hTt7&9s{*&8?C<8!EoT$fOjASc+(Np@2$>KnUczC#8)F1c5#4{(_k{BOR%8+&nb%T&(}p|8T4hePUCZwXcLSKm5UON;aQ zYg-P+&nByS_cKp>z$?5^spgc5L3S0Et`75OHN5^p%hx?rKWL+A;ww4gq7Y+zkWOaSX8LQrE9}eiqm7K8N*UWWlyAIUO+0y$B2Wly08y)GnPQg16`vGr4fHxgc{R+~Hsmzq6QGTneh1eLV!2$0-%0mQ_E?12SR{1**DOBx?*gazDk@6 pd4qq|KpxOUyj=DWP(pvgbb>_^9x#E1cWHVA<$$t2`Ls;wcmQ_QED7R)!0p40tY>W5|u)0Rf@51+RN zaMx}7Guw6}CrEWNNRZ*Ha<}gChq4BYw<_*C%)dPK$gxcGH!&X4e^#@7YEXOhcvE|W e(xLr%pPBe7k`uGD4c&o3#Ng@b=d#Wzp$P!eeuzQ< literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_42.png b/pythonscript/kickstarter/ups/discharge_42.png new file mode 100644 index 0000000000000000000000000000000000000000..28b49ef99ca87786c3833ae3eb46669621217179 GIT binary patch literal 347 zcmV-h0i^zkP)E_$d?uynz?+v%OFY(ro!RXW|gxO$hKN1b7240BRvy+D^ke5CXhO{(&~PE5^a; tYs9^fH~3Etp&~DK9Ocx>lC~Lz8>AR{mxylrkQU3UGE!;2E zLJ0X<13!gafHm*}e)cTXgScA$&6?N+SmOe$aRJuA3xH~5xyNaE2V8(P(LK=b?Mh+s uvM0YyZ--R1?JGHQm)cJ@O60000 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_44.png b/pythonscript/kickstarter/ups/discharge_44.png new file mode 100644 index 0000000000000000000000000000000000000000..fb540c803a542309b819bc5d822b2a58857f7bf0 GIT binary patch literal 340 zcmV-a0jvIrP)Y zTls^)Baj3KrikL~?;bh%`RyyqpU(W{-5ncLw zQ+atVl&_cicS z$OTvfFW_f$p&YTdJj|Zh1z6((tZ@O>zzcv1WVv)2-U2SbnrIE^{aq<+o_4|i3t5AI m)Ie^~M7$mL5Ku(K$8Bbu-)TFK1g(?+0000K9(-RsWk$xP=u>+Qk)|Y`iq;Y|Y7N#iJL_>wBJ_qM2N8 zqt28t&u!Y{rwmI!H+3W^?K#K&*~DmZS=^1v@(00f_ZZ)A*A^6%^_%|ar_?9=+P!`1 zEzC}@h2NF#FL3DpIZHTfciFKbulxDC`66x}TT~XSJMZegSaz0dpY2v5-SVXlm#xC0 zWh0|6Zh!5dz13>tHolApvK?O=%oPvJZp`=_b8mJ~+Nnz#`%U(7f9KvhA;|Lj0se&o z4|w-9%s<;&H6i`<{z&0=hI}WG2I&gMfYvo8bKA-lSSlv(W&XQwokaKY^#b=>_Z+;R nrC@)-#U}TgK!D4W+qrU;zf+fTO}f?%3@HXrS3j3^P6M0838OsAd{;5k?Y16d#l2kY4w}c%NRkb!GbH)Hf?=svX@!!z^nwGEw-_qISXbze(rSbL%it$?%5sv ir{x-4T`KNAW8E=1!|>2c>lwg+V(@hJb6Mw<&;$T8^OAx9 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_47.png b/pythonscript/kickstarter/ups/discharge_47.png new file mode 100644 index 0000000000000000000000000000000000000000..2c5221cba469fb140e71759273529f98d2e1077b GIT binary patch literal 337 zcmV-X0j~auP)r5@ILu(Z6E-yKND{Mw#O9nu_2^Y`n3)^l5x*Yfx4fS`@}T8B#O z>DPhMD6y8(8NFWzwx-lUcn5qxV2ul~W+3`~1!+YtGDTsOzdqK$ZL6aY^05Yf2)O`j z;05eHc-v+b0KT+uNue$ jnuw3f9s-Jp_`1yt*^XgJ7?b+F00000NkvXXu0mjf3XY45 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_48.png b/pythonscript/kickstarter/ups/discharge_48.png new file mode 100644 index 0000000000000000000000000000000000000000..c6283082570b608f2a3c3f6dc99a191b397f9e1d GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|N z)5S3)qw(!5U!g+^JgvgZzh9V|_vIh^M(N#~)K{*y=;(EjK5H{O-<92~(nhL|ZCT6T zk2Zn~k1uRqGM{tGF>a-%&JR-hJ>oWwIHvn^oJrzGmESE>HOA6Jvet^Su0Cr={N? z$VU9(k2qS~J4yKvP}NdXuB%oY?;YN-@8M*;SK?BpeInmjeC8yBQ~5Kq6Ax=G&$PRC zfPbOD1KvFi^VhcOcr1Q;|4pZJgSp~?*&soN6`eVGmb~vBI6kQTWPjryKE-#6W})5_ r-hZLYl@7umi*7nQ2u_UKUdix(MV63-Wp6exv=}^H{an^LB{Ts5K0uF| literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_49.png b/pythonscript/kickstarter/ups/discharge_49.png new file mode 100644 index 0000000000000000000000000000000000000000..054c91f553b340951d90e129208d4d35bf8d3f0b GIT binary patch literal 346 zcmV-g0j2(lP)fO#&oHlEkceCa&A1LnEV9JM)4t%L9m#D2h=5a3NmWS2pHo2kf@g;9R1Y=sTG zOldmSz<)v^z#Di0KhK3)$fMf(Pp|tFi_U4%PvD%QWgi&&R!~-|2EI z_Q&}gcc7Q8k?aKy(K|3K3)Q}XR%dV@^mhk-MLx2v*XbP?o`rg;IpYqrQX9$E{&_Z| zcVKuH+L{k~odNEEHS`X+0Ba_Z_G-zRQl^R%nQCkf38#+`%DIOA6>*S~1T>9=uL^u@)uaX+?F zSYjlsBwLZp=JySp7t)lp?#E^duMWBAbS%DiZNYJOEAjQFAA(P?WH9SL zc>cRl>`&HD^Xeqw1-+aXj)4RjvJ|^({SI3vFm3sGukl}fxX|ns>u%I5=6>jxI>9@u jqyMlRQy0ho9cQJb`ZBdlep;Ubh7yCPtDnm{r-UW|Gq#C3 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_51.png b/pythonscript/kickstarter/ups/discharge_51.png new file mode 100644 index 0000000000000000000000000000000000000000..bc9a530277aded96116a6ea975534beebd54af7a GIT binary patch literal 339 zcmV-Z0j&OsP)z|MeL7fPXNr*==eE2pfsLg5snD2@ytAp&Y?D**=aXmEyL1^_uI9o+~IWj zSYP@s#p%}pCf74LN}Vf>1Mk4%EF|@$fvwW_3OY^NKW?aKeH=0`yO7}QYd8xiRpbfkOCP15ksNNpEHK|M$7BcrdVW~@{u->P04g3%? z0ouR|`1${Vm^fSh%|EdT(8dI4V*<2+7XY=8rMA=X4wwLK!W{6_=yav=p^cpjX@h^& lfM3u=yj=ERP(;MnZ62H-UL%~pasB`R002ovPDHLkV1nlrmBRo4 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_52.png b/pythonscript/kickstarter/ups/discharge_52.png new file mode 100644 index 0000000000000000000000000000000000000000..fa05a5b22076bff89a7c4f6080adbb6655a12b5c GIT binary patch literal 341 zcmV-b0jmCqP)y_fttOxHva~7)I+mw(x!;_EgamaJ$C&utYk00000NkvXXu0mjf2X&9Q literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_53.png b/pythonscript/kickstarter/ups/discharge_53.png new file mode 100644 index 0000000000000000000000000000000000000000..0592bd4fb78f94f419eb8ee02b7d767bab312500 GIT binary patch literal 344 zcmV-e0jK_nP)!O8rZ zw^AoC{Co|<5Gdn(y+;CIp2on}=>lc&42uXvc!Ou=X$%2%@XX1209uBv4d1V4Q{3hD z=n~J*OOERRvy_?EIL>ryKV>_32R2tB>7AA`s?9vNJFWu~tr^zF!8@R?LR!kYUuIZx z=(rB7ts=eR%jyU3fVv7T%?HxV+Zr#mcL%J2cfbW$GZ3|}maM5&sx*;>?+#0UI&>Xl zz1G0LLN34>cmd|?fi>jS@^99}F2EWWV2ul~23`QvL{|HqhIhaPSd+eiNIUMX?AW#M q3t5AI)PQf$MEo4~U{FNl$8A0~A8B~vLgd~60000UtP)j!?n|YFN zMLQ+K3Q$OS6lVZEBI~_v^q~q%mKb4{RPt zSvsa`vr6rIre6ov=JBQROTar|`vGlCfHnhB``Y{{a+xWlQTIJz*`5tTd!NrW@I%N1 zXag_c@clpwakl)MJ+TSU#sp|%0AP_|tC-=}w8@>NQOk*l@608=H39}er)-U_2 zm~0}Sk3YiTDB^g&dIDfwX2-|j0HyH^i*Q7Ejc3+nb`G`i%t5;WwM=_&d_L|)b*Ia% zNj;xW8P@?O*E6l^bFf434(v`LX-ua*{cXMUy<9)80}^RZ)z;^(!#hx&LcNik8P|c< zW4yFJO*420s#7SJ?^2qS)=T%B1KPklU;?z6h{|V6+H56Nc#*mH3QKz$Jlgy1tbrdw zCO{i_0Y5(6qkZ|1}%KpPXFjS0{OUI5gKZ0qkdyaOgcoA4X()NysC6+5CI|OyZM|F^cn4NzA!!Wc{^_lHp5yfEfJE9;wT?;g7IG-sh)b}3y0N#&*b=72Wv4wwLK2BPxal{QP6DokYVd&060 zjp4mt&NcAAkO|NRUclk|ffnLy`8RuF6QGR=(8dI411|t-B3oKc!#iLCvOy3Gf9XJ!OEoK-{s0000cz~6D2Z!3B*L#3m=IfYu zn)sD+({*-P17tK7UKg z%}@E?j;J|E3oq#9yucR6aHU^o>zuao11uSj_cOZB^$)i)&42E6>p{Hf1a=ca`RTSS eB7zS~XN#{iim6vLuMhAP_|tC->0GGsX0|SJR^_zVa z86fcUVF?U|JkHmfB>}I}7}U-)C+QBfGOVrmdOnKEUB-J+ zy;2r3;4y4BPTE|lFgLj~L4wSMs3gzk@ z*MZu&z191}IRx)Oa}=tLDy{2=I`C`2JD>uj8HmbvSJK2Hm0QTn=Y*vn4NX`Zk2UZ^ zNCij(FJQO*z!vz?@^ALUDnJ?)AdL!;23`QvLKgd-hIc>(NaN;!r5#RJIy9l@Lek)0 mHQ)y{5nq=*7!(oyb(=Tsj%Szof_MG^0000T}d literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_59.png b/pythonscript/kickstarter/ups/discharge_59.png new file mode 100644 index 0000000000000000000000000000000000000000..f12df42775ae16bcedf0a5a1721af419b641afaa GIT binary patch literal 342 zcmV-c0jd6pP)vw4luQzDd8Tim2cgzm3IfEV_J*UDR>8l<$>CmZbdKc9D#SBdk$!6^>FQf z&jTgexUGGUbPmBg&>e-ec|iJ&BdyOJum;`%7hugqBwt-wvlX#YA`9;wmT`1c5#x5O zfgeIHz#4b~yRQd&$fM=YtchKKH7>v!7hny%0H{Q^^*IgifD5oD&4EZe?5+%`!uN%& o!9QxiKhQ*c9QI&PMC8Y9UMy#t%i zMnA1Wwfo%xH1rO*05pS0y)x{LQl(l3GS#F(^DDuGP_8xfpO6bcLoZ;rv(XZ9wfvhk zu?s-s0?@bsH1qUtP)8E>H%~un0$lH+W_odgo9F&z!Ue&`P&fqW9}Lxxd^V zqPSALG@m)Zl`2(88Yj(@#vj*#xfQGBNs&5rTnCuB6-=MwxKf@{JYx>b?J7&Vjm-mV z(OUWRTY-0=c{RGUk4xig&zl3yTd|*Qvrp=Ds^D+tKg}@AJI|{wHJt zw1F3}`)ag>xLf|se_|7$jS0}k1ZV><0BR!3EvMlfFag?xZ@|+IyDJ;K!uExpF literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_61.png b/pythonscript/kickstarter/ups/discharge_61.png new file mode 100644 index 0000000000000000000000000000000000000000..66e0381a8c59459dad03194cc020c85c8e197b14 GIT binary patch literal 333 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5DEQyg z#W5tK@$D>MzC#KEt?ZYpUwD~U{cpa}8+&u|gRBWc9J!Kn?2PYo3H_5>&okwL#-p0O z)l3QV+>XDT;2>#pSV>0k-h-KyQgfYUZHh0+IcPVBIKRJMJaPXWxz(qppIV!HUS`GF zl$Coz_J81e)8`bO9=ED4ivJ$tM$-wKuFs6J&)ogYLAP(aRbcjUnHR2^&n}y?zjyeY zS&}W1)f@Hfz{Q6011uTF0w?EQte)H*lBisKUwYcUH*b#pu36Lgzs0A)>;n^DwbWTb z_tX1-s+%$1n!%6@7F=Mq=}w{4Jx1XLz7fpx>PmJi-RZr{o!R{VN`rBRl32g3goxnB b;@SKb%M#WvJY>HL7!(Yiu6{1-oD!M!c ziYvuS+cO7vrb;s;jgz*M#vj*#turQ#TZ>QoJ+1?cG^?yVU%Q6Pf!eInD|?v(TWOv7 zbj`s#(7YS1-P@FqI>VR)+Q2(t0<;;3*c%RQT@UM^w&<0+> z?z_!0Mv&px1EM}zyxR$)_|v-PFFg2W9LHJ;9oW13Yv(; gWe)~LM10-m0}j_|o?+Q(I{*Lx07*qoM6N<$f^KPyQ2+n{ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_63.png b/pythonscript/kickstarter/ups/discharge_63.png new file mode 100644 index 0000000000000000000000000000000000000000..9d8d02fe2520c0dfbb88a57275abddc410d8bd2c GIT binary patch literal 344 zcmV-e0jK_nP)KpDsLH4*^xGzPv-7buNqSVSPgYdkYgV+g2?XAag4&@%LT(erU%luwu2 z$NLf-S9>4h4scINJ;X@&TjQlTcn3CTp{+fqjbF#P0}`!0+v%1?` zA3hIg`8Mm+9AV%cP_IU%Y*ZT?t;kND0oJ$xYv2VyO=P9zG`s^Yz?w7$BJH@l(y=Pv q7qSNbr~wzyL@W+_FeoDO<2EmGDQtM3rx#8D0000tMB9@t48a3Y?mfzFRl(q3(13!gSfHd#|c3X{( zz|WR{^G~b-q)`FVr~ql;1wehsV#{fG2ULJG?j5kSv*>O4~P#Vv$2uFn1cxGK@=TIBZ9JCwI%QUK>=i|P4K3#5K z-{;`(r1BVZfO{&}L!Xr9E3Fsaf#xVw+w;;Ks4a){n*%v&$GYTb?mfH%-BGBv$7+$v zF$YR%jat%*4(~wsZZsE-q`6U=U;5r0&<5TC6QIpRRQ|W5O)64_h0MKASbk4KRoGI5fNXvc>(=rW(0+(MOpv=002ovPDHLkV1nVJlBoay literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_66.png b/pythonscript/kickstarter/ups/discharge_66.png new file mode 100644 index 0000000000000000000000000000000000000000..7a591989065a5f0e6fa706060fc486c197e9fecd GIT binary patch literal 332 zcmV-S0ki&zP)PhG-6d4$bBJp?f zRys!H=SxHerhxPH?g)T+8d;A00ZQiu5y3=wofqb5WJc}0aMJF;R)$s$y@^Q|kYYqG>WCFB-7jW3w zXo|2}{>?wJ3DCv_Xk!AjffoSvAxllC;TR4det(#A36D efFdH?Zu0@i!)KQ>wUo>N0000$mn_0^R}pAJE1GXwwlbUyEyz%1l0u@_UD+4GSOVTsqglzd|NJ z8+ZX~pgv@&~PGa*tU6o4erg z&sT*RKATw8@$qXkr$@9XACk5)_Fr7QqB))K7W0eL8><#Rf4om)zMgqtj9%QWd6ox^ zr_NT)oU>(a>7V~r0oKV|J?1u*FQ}9~kt)2*ocrmkwT~ajzBuP}seG#`_taN2nddKh z!omFB;d|Z|v(;PD7RJ;TJ4jcs`!tv_2Uf;!77v=KrIYfm_RuF=tzBR2=4L;LR}@~r zyQksqJGbB?H$SES>-23fQ*)382{No`)!90?t^5E>hVVW{f3D@)(j}SCoo+pdH+{e~ ktK(=YKbxyd&E03Lw(MEc58kfX1q>zzPgg&ebxsLQ05QXf)&Kwi literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_69.png b/pythonscript/kickstarter/ups/discharge_69.png new file mode 100644 index 0000000000000000000000000000000000000000..1b519b33b5c284fa02176927c33d9431627ee8af GIT binary patch literal 337 zcmV-X0j~auP)WVz`yy#p=)&GHW&AuK)l507*qoM6N<$g6Z3dA^-pY literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_70.png b/pythonscript/kickstarter/ups/discharge_70.png new file mode 100644 index 0000000000000000000000000000000000000000..be480a70e5412b540cf8b1f4983f611494c46221 GIT binary patch literal 334 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|1 z)5S3)qw(!5N4`S}0$}1XuZ^tt@ri3Rr$@9XACk7&thc#%MRU5}EoO<-SGhmxY+b+VZEs%@yEZQBoXqvc zAeR{?O*QdZ;oACF?bk7@Rw>U^Dm^8WP^EnSYj)(?FEzyrrUot#I@1{DraZq+`&q-3 zsBNCgx}{wA7^C?!l9{&5?0B+TJGW@2=90kgcN1^+x9ROY|6J-v_z9K_X8nZkH(ziR zZu>LabR*}5V=N%`jp7Vnl~4OFe<*9fc&jDkPO-(_+Bd=Mhdb{NHs2%X6ZzR( eU25(;W9AS{GCY57Ru3?M7(8A5T-G@yGywoQhJ`r* literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_71.png b/pythonscript/kickstarter/ups/discharge_71.png new file mode 100644 index 0000000000000000000000000000000000000000..4fcc530e597fc865185359671df2735b42aa2564 GIT binary patch literal 333 zcmV-T0kZyyP)(hs}?n`fcYyS=ntsU5rntG|JmwGnOY-yG;o$ysxt7rDsC z^N2aHlsJO9NY`dFcn9o%KpPXF%|Nu>8of8E%;eK3KX+I@OXXvoOXnK+SI7it125of zSEDJyZ22?)#3n!+6QGR=&<0)r)I^qAPQyE30<`hpfFq^dmBz=~zK}NfM-AixO~lJ# f4*^9)c--az_w`vq{nGEN00000NkvXXu0mjf0w$9c literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_72.png b/pythonscript/kickstarter/ups/discharge_72.png new file mode 100644 index 0000000000000000000000000000000000000000..ca8a765647c404135b7f9c8e00f3b05f3a5cb2f8 GIT binary patch literal 336 zcmV-W0k8gvP))`{)hlrJBQb{)9fMwTWR$?hzP8u z)${fG5)rVXt;{v>xHX&6MeIuZNs%A2{&RMKerp0MnTvD=*Ia}E6z3IX2G z3vk;HY+;%$|7K4d0=x+U-h=>e=mk(MWOKjM^brUF-jr`3i__^!W4CcGSVT;(+k61xhGH>^iV_O|0000jsSN+OKVP4Mq29>y#u};u*L;gGl=L{OV-pfQ%a-Zy~FZ-6kW$y&o%U~kPEPe zUV!;}U=4Y;{Fybe3$VroSmOe$p%*~)BdblP=^bzZ)}(JBisA0cj;?)Q$Qu5m2Do4o i@p0G#u!zWy+q?ipWn%Lo4!mFh0000$eQCicb{E{WK_1ywO?aZ&$zFVM{(wd)(2ObAnsr@&! z5!x;AEwtVW-U5Ceu*L;g(-F1r&9@@wnW7uz_YKSTD7p?I?`zt0c z@^8+>F2EWWV2ul~23`PEAj@s1;Vs|-tcm|XAj8|0hOYfy$Qt~o2J(R>;^VQ0fFdG& aZu0`@@L@^Q^NR2Q00000|78kBg=6-Kxw=nBA5uT@xnZf%&3hQ4%Q7=%doWI^Kn-_ce>oV zJTKRC{?~z8M1)$zTjjJ!{qPQS&q5NJJkqT`pmip%-yINYk?&Q$b|2n>#aU=8TF$rw zTE6Wg+HB?yXlad!@G#6 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_76.png b/pythonscript/kickstarter/ups/discharge_76.png new file mode 100644 index 0000000000000000000000000000000000000000..55b60dfc74534e48a8e6a7b9160e9ccd86ff488e GIT binary patch literal 339 zcmV-Z0j&OsP)i58N=pEQR3$-G=b>#6JdIwhbff8+mTYW=YuPo;dETuOt zEbTk)z^CXr0^9*Dh@>_PX~zqC2YfzYjSH}55Yf-Btf^(Dlt#mIg~f)>>tn3v8v0Mj z1z1Bb;A{UzYvkGTXXeB%z#11|jSH}bUI5iVR{Nc%cfbW$llnjuD`;u4~7azN(e0cn0>FS?2@Ma{v`(vh-|Q* zC(F1&&)EC6d4QFE2Z!3B*L#3mD%O8p)^6`MUX9uUk)FXZm6O1kMX=aR;uSYYl3-`YHX|5j6*C;RU@Q zL53^+I;L~m$`7z)JU-r_{PgbBW7lu*n_%|Co%aco?~&7q{EV(H6?dL7UWy9Wn#3sT Q3=9DVPgg&ebxsLQ0Ml=Sga7~l literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_78.png b/pythonscript/kickstarter/ups/discharge_78.png new file mode 100644 index 0000000000000000000000000000000000000000..a2b844affe9c3f9054163a75949c8cba32b14303 GIT binary patch literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|5 z)5S3)qw(!5U!f)go>t-Z>K9(-RsWk$xP=u>+U2#@Ct3MXi~7$ngDpOBa!c%kmo@$U zIESO5@I~ah&#Yb_nmQ7c_B?1Ud@(byS-k8XV~w}@_Q!wAOt)Ik(OXuxw(RorLuIlT zjFfzgHNHpfzgE2VFOd7gWU^G)@&~dJIWim4?nSk(dEZ{1@Uh+@IZrg}D>r+c>uEtn znGH%aw}dIawqSbiu!emPC*!>mmv2|6nr}Td<+O%Qi<*9BQ8}*;?{tIN@=_vfM i=!Ua{V4&RgN(MpaL?IrI-DiM-#Ng@b=d#Wzp$Pzy28L4otF%NtS z&BLX75D}=wy2i22BlHo7{eU+iz?(*7`TFGB%w?t_ji%=biyv=H$ay~3&<~*y;0?V1 z_tj_(<81jid*Tq_O$hKN1b9O)fNCO}TTatQAOv_b`~z8j;B;ky32`pu4gXaG6xc+( gT=oDgBF5Kk9yuLfF$I4Jx&QzG07*qoM6N<$g1J$IGXMYp literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_8.png b/pythonscript/kickstarter/ups/discharge_8.png new file mode 100644 index 0000000000000000000000000000000000000000..164810152f739b6d434a2aa27b3634d09a61279b GIT binary patch literal 318 zcmV-E0m1%>P)n}538 zzSf(U8+2eTHln4Gtd*DQ(}As6>lC~LyUzn_qfn{cydJy*%}1eHJ36oxHAf712VkO5 zsXZNt2HpV*h^7-MM~2q$BGojJsV)kaIdP=q#~S!ghytR47tri#)FY3Uf3qj1fM_Tn z8VZO8UI5fY<}Ih;9iV_{rhmZM@gSVH@LVVw{Hq4`fF|POvWI~pBEN3)0)a+YBlEpA QAOHXW07*qoM6N<$f(inHV*mgE literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_80.png b/pythonscript/kickstarter/ups/discharge_80.png new file mode 100644 index 0000000000000000000000000000000000000000..c4563408db6371c9f69b524c6d06e5d0c72bfcc7 GIT binary patch literal 334 zcmV-U0kQsxP)J$0*>daBLL=UWJ&u3it&PoU?M!@g?Sp8QH>W4+6J^Tv{vwZ+>`%Lms?Z$ zxLmHjemx*PwA9m*zqWj7`~7-gD{AMEnLbL8eoX?dYv57b7L+Gu0zz*e;O zd^}g+J;QAiymK0a4sEd;D?Y2&<0+B z`k8nQVYK|4GqDNK#sp|%0R07*qoM6N<$f;cOYjsO4v literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_81.png b/pythonscript/kickstarter/ups/discharge_81.png new file mode 100644 index 0000000000000000000000000000000000000000..5e957feb7354b7abf0fbe130e97b9c5a6e060aac GIT binary patch literal 332 zcmV-S0ki&zP)7MXzzB$b>L`n z#}Rl3nrEY>S*SH@sRO+M?|=%BW+K{ej(#+uOyveL^Ic()B@%|U;amg%3aJ2T;01I$ z8x7%S%fI<2Rsqte0BKZ!H1GnT2C~p}8r}gFAdUM5EOoNFQW%D=3rT~&)qoGsMEq>_ eU{FN(+igC@KVr7QhhG%{0000UtP)Ii^+TUoaK21@dRh+rbTof+Z(awahe|yy=Ud}z??0~ttE@$mF{N_Xanzn3D9OCs;>~*q$-pj$oM_OqDv+X=hC$X{t+?( z+Q17K{x#Y|SS|nNpV$OwV*<1>0ouR|fEvhBpVROTm;i13JK*R?W>*^1uz4YE@Ru6M k4w{IM#U28Rh;X^h3#hnlmzns$8~^|S07*qoM6N<$f;L-|djJ3c literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_83.png b/pythonscript/kickstarter/ups/discharge_83.png new file mode 100644 index 0000000000000000000000000000000000000000..71bee1e2e777c4c9ea21ea8e962cdf08aba311ee GIT binary patch literal 337 zcmV-X0j~auP)zVxga(h(8 zqjZFM~ZQRn|@7IA=N+VzE7`y|`^FVDCD&1d-&&BoYz|tzTw!cz8 z@DA)&g|>D?fOlZ`DzsJA63hW@;2kgl+6+YXt0iqxmC8?K{M}*ECy|PC=~@H-7cv3b zzza}Y6R#nxmVdJ*HUZk00BuZwHt+(VCbHD)G`s^QKpX!II6Bbo%7RpEUq~DLqXu$; jCgSI?hkzm?JZ|#=IofXoU6c^100000NkvXXu0mjf<`0jg literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_84.png b/pythonscript/kickstarter/ups/discharge_84.png new file mode 100644 index 0000000000000000000000000000000000000000..6e36e1f44b5a58d5f90440d966d730ee1f6954d0 GIT binary patch literal 337 zcmV-X0j~auP) z)jw!nUkN{hqlokM?g@Z(nH?X81C-zy7U78Sf@jucb`CXo=A<1!D^qI)@7FVX{c?MV z%BRn#^_J=z*MZWPliqKA|F{n9C3QyHhSKumIw1XmwKh?sR36@e-95213vJChQs>|u z=$3_~{l(TEdRzzg=5cBLHQ*hv`G7ViK%0rEeQe%~TxJSs)O}Z2^vQ(ceLmN~|3W4} z8+ZZT=fo|<+467Z#3n!+6QGR=&<0)rR7aK@PQyE30<;Njz|( z)gMapcqJ4DrhxPH<_LgoU09C&0ZQq zT<*EJd|aO}t^?9Sxt7V@&z0Z%{&5}HOKG1ebr#-%=5e63MN8|c?Njg$bk9P+e$TiL z)Kcq6u6*3*;2r3mg<7+gIdC-K9WVjfOho0gC2dlf$`54xSz*y95sGu^Tm%0JnE-9z z1$3Viw-9E_znK%80BuZwHYPwDcmYrYS?YHh-T@P!jqd}F4z#*b5sIw~X@kGjKu*v^ hd~Nm+P(*~=ZQjq_XP1f!)ky#V002ovPDHLkV1m4ajHUnp literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_86.png b/pythonscript/kickstarter/ups/discharge_86.png new file mode 100644 index 0000000000000000000000000000000000000000..dc12d6de6a73aba84c893bdf098738ce69c008bd GIT binary patch literal 334 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|1 z)5S3)qw(!5N1;OsJg&``t6zATSN(6k(Hnbn@&m079v+3Q554cL+Uoe}r

tV*lfx zS)2`}GkN28b5D69DVEsrVN&|FR5Lcy%4Z659khdAi`&S5fAM_z>9wn3CFWP3>v_%4 zACQ$2biVj))^GXijZssS*u+Mtlcu8wYm-o@-zc?R1kiD^&D>ruQvC;?EpU5Nv zxof#93M@&^-E-~0|CW#jlMhV0VtbE$ zSpD?=AN4TCODh;M!Ga6aHr*(ay2mK0&|A%}Q@U+;(2Tx!+@8(#R~qs!xYWpeO9*iJ caXdF}wtLp}+#KD_zyMK9(-RsWk$xP=u>+QoIv$5=Pm&}Mf2QGbofS^=-+jY56T zvsDdCGP8zenW{HxFlCH3Ock}V`(`&W-cKaPW{cT2_ z>;t(=Rc`aHKT7#?>-YE92ex0Bv!+z+UgNX(4!NS2)@dJ|`sktTk4Vv5uk8L>Z7_{G zf4Y2KJo^^IZkzkJ>NqRd|I{Cl5>Ghmu%%8X&vl)VZmP+i^oVbb9_q(x);89+#59 literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_88.png b/pythonscript/kickstarter/ups/discharge_88.png new file mode 100644 index 0000000000000000000000000000000000000000..0700c7fb1f24d53152788bf4a134005170ff1ce1 GIT binary patch literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5DEQ6O z#W5tK@$D=}zGejh*L11(7uVhD-TUA1)v|T3JYQr@km4|D;fa5?Hh}T@?@|Y*-~(Un zteFy`#iwo3U%;Z@!J&4DJMPA{R!P%}hl^w#bc1u*=YQ9(`uD9O?5h1W@2}?Tm_M}? z`A4n$AHDy}zsFV=W(6&OAiF}2Jt}o!$nIp@3ie&+opzRiB<3hH?O`^E)VO+2<^tP2 zM&Si#U8bDl*(-QDq)y>+ZR=9`rU`SppG*A+Kf#j0tiRy)&5Ry{=y}iP-(bl|X4+!N zcgTe~DWM4fod|?a literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_89.png b/pythonscript/kickstarter/ups/discharge_89.png new file mode 100644 index 0000000000000000000000000000000000000000..10ee6d3c14c4aa9f4369f21b3afc94a5f350acc9 GIT binary patch literal 333 zcmV-T0kZyyP)!Lsr3fW$31%fbh$OP z-?mHLZ(IkYhjKkF^|kFwpFgex-=@?lcn6xtfxWU&>2=$Bcn5x#g@jsXa?cspfxSre zaPGVS?|^z1Djj)K*P8>{z&l_9w3&$V^OIhPWh_6C@q2}(AA*pai{~2nSI7it11~^* zHd;fNE&t}9*aT=}0<>Ki+L4vs-|J00000NkvXXu0mjfrW}Yx literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_9.png b/pythonscript/kickstarter/ups/discharge_9.png new file mode 100644 index 0000000000000000000000000000000000000000..96692c17253258a7e25c869f818bd2ae79c668a2 GIT binary patch literal 322 zcmV-I0lof-P)Ii^!nOTng0ZQ_Mh+rbTl^ z>uveiS$GG!XQQQamRx-4b-C-!0d3$NFag?3MD6pLUz4g-ejwxb3d?>7QgSX`Yv5lY z6QB*e0QGO;8p3M%H{Zl2KpPXFjS0{OUI5fUmYPn(J75B|@qNJ24p&zSQnGa+ZSc1m l$PY9TADcY{6cOQen-@6DZbC2`LW}?a002ovPDHLkV1l!ckdgoZ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_91.png b/pythonscript/kickstarter/ups/discharge_91.png new file mode 100644 index 0000000000000000000000000000000000000000..cab81949e614353164ab0cd9cdead7b2399c28ea GIT binary patch literal 339 zcmV-Z0j&OsP)4z$9K)SQqyJFWxLp8OT1Z)#iz7-=4FrIzi?fs|U0 z^|q5b3-3VpY*adDDV3MjE7dm#w1IcP1ZXo6Z8x9kC6}qfK;}LxEc@aemiPHw1OEz{ z0Bzs}Z2nK&L!2%D=AYOEXk!AjF#+1Z3xFEPa?@#e2TXuA;T!O@)76zqShg;t4gOXG lK0y=lwb_F~5fN{#TKVnTOSo!|ff002ovPDHLkV1lFdjIICx literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_92.png b/pythonscript/kickstarter/ups/discharge_92.png new file mode 100644 index 0000000000000000000000000000000000000000..294a9c962f414497f754174f2f9fc70b8e52d096 GIT binary patch literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|3 z)5S3)qw(!5U!g+^Jgv=_t6!YT`|^){qx9}g>IZurQxY03W&TpRzsi25S-(4Ts=<@O zefM-2H=LRLFuA;8lFeZy9>KUzXN>%hEl@i@XNhn|YDVpW*FQVr_V;{W_QOE$>z+E< z2V$qTn%rOCt6y8cpYKZ0txY?BTv%Br%kleLl-KV%t#$G2Gj3JM-;2NfsmDrSQ|Mm* zqpjx-SzoY`)}6a~O4#b4bcs?2sS5Ux1{3DM-!*CaNusBCQ=aWURHS2<87aNq^h2x% zs|T}w!uI+{x*t|Qz5hpeHA5y)wH8npV*qnxjur3w2CfOmYMH0;?bIv^vI8=3g`+PK#1AJ>6iN+VzE7`y}B^FXP(lUUV literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_94.png b/pythonscript/kickstarter/ups/discharge_94.png new file mode 100644 index 0000000000000000000000000000000000000000..4de3af27292ffd9983b838f950e56676b1dec3e3 GIT binary patch literal 342 zcmV-c0jd6pP)T$l_0|C%BHA}fGpcpTR2qwZaUTB+|8P#~`AJ+p@(Vod2L%RNPJ&^i?n$(z>S|8p6i)G@meO)t@4|bae zX7@mDUr%M?RECE4!0dCOsZmBVwjFp6_Pi2iY#59qjRBT#PK`v3p{07*qoM6N<$g1v-|O#lD@ literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_95.png b/pythonscript/kickstarter/ups/discharge_95.png new file mode 100644 index 0000000000000000000000000000000000000000..959af9128ec3708ca89bc59bd041556d14fdf668 GIT binary patch literal 343 zcmV-d0jU0oP)?1KPklU;?xmh?c9G-zGIv`9j9e6_)kEM9!t> z8u&-Z1ZV><;OF1OJ%qF6-+U9B0BuZwHYPwDcmYs_EcH4K?|=!=#*YC002ovPDHLkV1kryk^uk! literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_96.png b/pythonscript/kickstarter/ups/discharge_96.png new file mode 100644 index 0000000000000000000000000000000000000000..396d6e5b8134cc17168f066c2801a887f5a9728d GIT binary patch literal 336 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv(mfq{|9 z)5S3)qw(!5U!i6No>u4M)mv7VUB35Up)@%Bt4f)um!#2*gNFMi<+n1OdYt(~l1X*W z`tPj_GnN|`Sg8d_o!`*m&~m*ZZQe0~0O|8{)<|DCb>UaT_t%ko>UY|PeEsxkYt6EF z_8sj@=1u&R{dLRx_tzP9Z_SH;owJ{>fZrp~W07gr)2F{m8&=k?zV+$Wil=-}a%7)p zE1T?LKD+ti)?%@he{NL;)s`QSs$h?4FlC-tXP4}%8FaK!r=~oD@1)by2fhN}H&`PcGp#9Pymo-QLF$CpmdKI;Vst0F_^kD*ylh literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_97.png b/pythonscript/kickstarter/ups/discharge_97.png new file mode 100644 index 0000000000000000000000000000000000000000..9f9fe5cf3e940e3c5816a46c0fc22f2e8b5a3ffb GIT binary patch literal 333 zcmV-T0kZyyP)PhS>lrm(1Q78VA zw?d7`=VK5DrikUtP)Qd(k>);(|eg;bO{?`=B z{m&c-sm=4+c0y<29oRgDO4B5yb_5p80d3$NFag>OMEUobUWg@?8_4+cg{4bYif0Y{ zEo1_;ffvy2oH&M`mVYxRHUZk00BuZwHt+(V2C~@eG`s^QKpXcAq-H$5W8a0e!Cz`1 kKhQ*cE%p#lMEJ{X-t2H|Bz5AxA^-pY07*qoM6N<$f=$4VkN^Mx literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/discharge_99.png b/pythonscript/kickstarter/ups/discharge_99.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d4bd325cd7148ec67a0fc02161b5a55f8cb283 GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5DEQmc z#W5tK@$D>Mp+gEht-{N{UznQrD`;uSFTntGPn@L>;GwO#_Y^W8>u=rwf@IH zcL^|jHrX^KmT~E)rj7)qJ*T)on;0z)i?hfGUodUqH`epVZL9v5>c zb^oXN<}htBWCW>x!0N!8vhk(K9%eNM>F@kY!fe(i|8~0dK;Co$w~3&9yDf`|;J?Dz Z;%9u*j7lUMGl0Rs;OXk;vd$@?2>|-bijx2U literal 0 HcmV?d00001 diff --git a/pythonscript/kickstarter/ups/loading_0.png b/pythonscript/kickstarter/ups/loading_0.png new file mode 100644 index 0000000000000000000000000000000000000000..2b82e29a40b312828a6d282c97f1b77da66f537e GIT binary patch literal 328 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5DEQ0M z#W5tK@$D=}zGejh*M$$|%2Lh0)yXvezUmu$b@Gj?0$v%iNvFR|uGBtq@J#xb#cbI^ zZH2dZ7+xDiXB}tr`oz+?;n)LJ|23y`bhADk@w94OY>{R2ZhL>@^V3^z{LhP#+h6{- zM#fxjAJa!kg`?HRbI%Ky@B3y063}vxnqFQI`!xQ=dZw-G4t{+de=(jlYh~=h{l4|0 z0peLbb^IQSnU)kDJF$LQjF+E8?v?8MCNra)IF8o+i1uLhVAg-|;dX|1&~_E~ru7Y6 z6Wmxq${WQQwoaHDxBQ{@0;VM!4_oA(m_OxLyoc1EwQMB~YL8Mkvo|U!-Oc;VBJ?a` VhGm+&6EGAQJYD@<);T3K0RVJ_g?|75 literal 0 HcmV?d00001