├── .gitignore ├── Assests ├── PowerPIGuide.png ├── PowerPi_R3_1.step ├── UPSPi.png ├── dashboard_R3.PNG ├── final.jpg ├── nodered_import.png └── ups_R3_1.png ├── Enclosure ├── Enclosure_RPi3b.step ├── Enclosure_RPi4b.step ├── STL │ ├── RPi3b │ │ ├── Case.stl │ │ ├── Standoff.stl │ │ └── Top.stl │ └── RPi4b │ │ ├── Case.stl │ │ ├── Standoff.stl │ │ └── Top.stl └── Standoff.step ├── Hardware ├── BOM_UPS for Raspberry Pi R3_1_2020-06-22_12-30-28.csv ├── Gerber_PCB_R3_1.zip ├── PickAndPlace_PCB_R3_1_2020-06-22_12-30-39.csv └── Schematic_UPS for Raspberry Pi R3_1_2020-07-21_13-59-35.pdf ├── LICENSE ├── README.md ├── TestRig ├── .gitignore ├── .travis.yml ├── .vscode │ └── extensions.json ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── main.cpp │ ├── tester.cpp │ └── tester.h └── test │ └── README └── src ├── init.py ├── install.sh ├── powerpi.py ├── ups.py ├── ups_flow.json └── ups_with_timeout.py /.gitignore: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | ### Python ### 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # pytype static type analyzer 134 | .pytype/ 135 | ======= 136 | /.vs 137 | >>>>>>> 9b649e3487e2e2e398a0459c583209f845195f20 138 | -------------------------------------------------------------------------------- /Assests/PowerPIGuide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Assests/PowerPIGuide.png -------------------------------------------------------------------------------- /Assests/UPSPi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Assests/UPSPi.png -------------------------------------------------------------------------------- /Assests/dashboard_R3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Assests/dashboard_R3.PNG -------------------------------------------------------------------------------- /Assests/final.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Assests/final.jpg -------------------------------------------------------------------------------- /Assests/nodered_import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Assests/nodered_import.png -------------------------------------------------------------------------------- /Assests/ups_R3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Assests/ups_R3_1.png -------------------------------------------------------------------------------- /Enclosure/STL/RPi3b/Case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Enclosure/STL/RPi3b/Case.stl -------------------------------------------------------------------------------- /Enclosure/STL/RPi3b/Standoff.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Enclosure/STL/RPi3b/Standoff.stl -------------------------------------------------------------------------------- /Enclosure/STL/RPi3b/Top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Enclosure/STL/RPi3b/Top.stl -------------------------------------------------------------------------------- /Enclosure/STL/RPi4b/Case.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Enclosure/STL/RPi4b/Case.stl -------------------------------------------------------------------------------- /Enclosure/STL/RPi4b/Standoff.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Enclosure/STL/RPi4b/Standoff.stl -------------------------------------------------------------------------------- /Enclosure/STL/RPi4b/Top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Enclosure/STL/RPi4b/Top.stl -------------------------------------------------------------------------------- /Enclosure/Standoff.step: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | /* Generated by software containing ST-Developer 4 | * from STEP Tools, Inc. (www.steptools.com) 5 | */ 6 | 7 | FILE_DESCRIPTION( 8 | /* description */ (''), 9 | /* implementation_level */ '2;1'); 10 | 11 | FILE_NAME( 12 | /* name */ 13 | 'D:/Drive/DE/SEM3/Project/SCiON 12CP/PowerPi/Case/RPi3B/Standoff.step', 14 | 15 | /* time_stamp */ '2020-08-01T10:18:10+02:00', 16 | /* author */ (''), 17 | /* organization */ (''), 18 | /* preprocessor_version */ 'ST-DEVELOPER v18.1', 19 | /* originating_system */ 'Autodesk Translation Framework v9.3.0.1241', 20 | /* authorisation */ ''); 21 | 22 | FILE_SCHEMA (('AUTOMOTIVE_DESIGN { 1 0 10303 214 3 1 1 }')); 23 | ENDSEC; 24 | 25 | DATA; 26 | #10=MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(#13),#176); 27 | #11=SHAPE_REPRESENTATION_RELATIONSHIP('SRR','None',#183,#12); 28 | #12=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#14),#175); 29 | #13=STYLED_ITEM('',(#192),#14); 30 | #14=MANIFOLD_SOLID_BREP('Body1',#92); 31 | #15=FACE_BOUND('',#34,.T.); 32 | #16=FACE_BOUND('',#36,.T.); 33 | #17=PLANE('',#109); 34 | #18=PLANE('',#113); 35 | #19=PLANE('',#117); 36 | #20=PLANE('',#118); 37 | #21=FACE_OUTER_BOUND('',#28,.T.); 38 | #22=FACE_OUTER_BOUND('',#29,.T.); 39 | #23=FACE_OUTER_BOUND('',#30,.T.); 40 | #24=FACE_OUTER_BOUND('',#31,.T.); 41 | #25=FACE_OUTER_BOUND('',#32,.T.); 42 | #26=FACE_OUTER_BOUND('',#33,.T.); 43 | #27=FACE_OUTER_BOUND('',#35,.T.); 44 | #28=EDGE_LOOP('',(#64,#65,#66,#67)); 45 | #29=EDGE_LOOP('',(#68)); 46 | #30=EDGE_LOOP('',(#69,#70,#71,#72)); 47 | #31=EDGE_LOOP('',(#73)); 48 | #32=EDGE_LOOP('',(#74,#75,#76,#77)); 49 | #33=EDGE_LOOP('',(#78)); 50 | #34=EDGE_LOOP('',(#79)); 51 | #35=EDGE_LOOP('',(#80)); 52 | #36=EDGE_LOOP('',(#81)); 53 | #37=LINE('',#155,#40); 54 | #38=LINE('',#162,#41); 55 | #39=LINE('',#169,#42); 56 | #40=VECTOR('',#125,1.6); 57 | #41=VECTOR('',#134,1.15); 58 | #42=VECTOR('',#143,3.); 59 | #43=CIRCLE('',#107,1.6); 60 | #44=CIRCLE('',#108,1.6); 61 | #45=CIRCLE('',#111,1.15); 62 | #46=CIRCLE('',#112,1.15); 63 | #47=CIRCLE('',#115,3.); 64 | #48=CIRCLE('',#116,3.); 65 | #49=VERTEX_POINT('',#152); 66 | #50=VERTEX_POINT('',#154); 67 | #51=VERTEX_POINT('',#159); 68 | #52=VERTEX_POINT('',#161); 69 | #53=VERTEX_POINT('',#166); 70 | #54=VERTEX_POINT('',#168); 71 | #55=EDGE_CURVE('',#49,#49,#43,.T.); 72 | #56=EDGE_CURVE('',#49,#50,#37,.T.); 73 | #57=EDGE_CURVE('',#50,#50,#44,.T.); 74 | #58=EDGE_CURVE('',#51,#51,#45,.T.); 75 | #59=EDGE_CURVE('',#51,#52,#38,.T.); 76 | #60=EDGE_CURVE('',#52,#52,#46,.T.); 77 | #61=EDGE_CURVE('',#53,#53,#47,.T.); 78 | #62=EDGE_CURVE('',#53,#54,#39,.T.); 79 | #63=EDGE_CURVE('',#54,#54,#48,.T.); 80 | #64=ORIENTED_EDGE('',*,*,#55,.F.); 81 | #65=ORIENTED_EDGE('',*,*,#56,.T.); 82 | #66=ORIENTED_EDGE('',*,*,#57,.T.); 83 | #67=ORIENTED_EDGE('',*,*,#56,.F.); 84 | #68=ORIENTED_EDGE('',*,*,#57,.F.); 85 | #69=ORIENTED_EDGE('',*,*,#58,.F.); 86 | #70=ORIENTED_EDGE('',*,*,#59,.T.); 87 | #71=ORIENTED_EDGE('',*,*,#60,.F.); 88 | #72=ORIENTED_EDGE('',*,*,#59,.F.); 89 | #73=ORIENTED_EDGE('',*,*,#58,.T.); 90 | #74=ORIENTED_EDGE('',*,*,#61,.F.); 91 | #75=ORIENTED_EDGE('',*,*,#62,.T.); 92 | #76=ORIENTED_EDGE('',*,*,#63,.T.); 93 | #77=ORIENTED_EDGE('',*,*,#62,.F.); 94 | #78=ORIENTED_EDGE('',*,*,#61,.T.); 95 | #79=ORIENTED_EDGE('',*,*,#60,.T.); 96 | #80=ORIENTED_EDGE('',*,*,#63,.F.); 97 | #81=ORIENTED_EDGE('',*,*,#55,.T.); 98 | #82=CYLINDRICAL_SURFACE('',#106,1.6); 99 | #83=CYLINDRICAL_SURFACE('',#110,1.15); 100 | #84=CYLINDRICAL_SURFACE('',#114,3.); 101 | #85=ADVANCED_FACE('',(#21),#82,.F.); 102 | #86=ADVANCED_FACE('',(#22),#17,.T.); 103 | #87=ADVANCED_FACE('',(#23),#83,.T.); 104 | #88=ADVANCED_FACE('',(#24),#18,.T.); 105 | #89=ADVANCED_FACE('',(#25),#84,.T.); 106 | #90=ADVANCED_FACE('',(#26,#15),#19,.T.); 107 | #91=ADVANCED_FACE('',(#27,#16),#20,.F.); 108 | #92=CLOSED_SHELL('',(#85,#86,#87,#88,#89,#90,#91)); 109 | #93=DERIVED_UNIT_ELEMENT(#95,1.); 110 | #94=DERIVED_UNIT_ELEMENT(#178,3.); 111 | #95=( 112 | MASS_UNIT() 113 | NAMED_UNIT(*) 114 | SI_UNIT(.KILO.,.GRAM.) 115 | ); 116 | #96=DERIVED_UNIT((#93,#94)); 117 | #97=MEASURE_REPRESENTATION_ITEM('density measure', 118 | POSITIVE_RATIO_MEASURE(7850.),#96); 119 | #98=PROPERTY_DEFINITION_REPRESENTATION(#103,#100); 120 | #99=PROPERTY_DEFINITION_REPRESENTATION(#104,#101); 121 | #100=REPRESENTATION('material name',(#102),#175); 122 | #101=REPRESENTATION('density',(#97),#175); 123 | #102=DESCRIPTIVE_REPRESENTATION_ITEM('Steel','Steel'); 124 | #103=PROPERTY_DEFINITION('material property','material name',#185); 125 | #104=PROPERTY_DEFINITION('material property','density of part',#185); 126 | #105=AXIS2_PLACEMENT_3D('placement',#150,#119,#120); 127 | #106=AXIS2_PLACEMENT_3D('',#151,#121,#122); 128 | #107=AXIS2_PLACEMENT_3D('',#153,#123,#124); 129 | #108=AXIS2_PLACEMENT_3D('',#156,#126,#127); 130 | #109=AXIS2_PLACEMENT_3D('',#157,#128,#129); 131 | #110=AXIS2_PLACEMENT_3D('',#158,#130,#131); 132 | #111=AXIS2_PLACEMENT_3D('',#160,#132,#133); 133 | #112=AXIS2_PLACEMENT_3D('',#163,#135,#136); 134 | #113=AXIS2_PLACEMENT_3D('',#164,#137,#138); 135 | #114=AXIS2_PLACEMENT_3D('',#165,#139,#140); 136 | #115=AXIS2_PLACEMENT_3D('',#167,#141,#142); 137 | #116=AXIS2_PLACEMENT_3D('',#170,#144,#145); 138 | #117=AXIS2_PLACEMENT_3D('',#171,#146,#147); 139 | #118=AXIS2_PLACEMENT_3D('',#172,#148,#149); 140 | #119=DIRECTION('axis',(0.,0.,1.)); 141 | #120=DIRECTION('refdir',(1.,0.,0.)); 142 | #121=DIRECTION('center_axis',(0.,0.,1.)); 143 | #122=DIRECTION('ref_axis',(1.,0.,0.)); 144 | #123=DIRECTION('center_axis',(0.,0.,1.)); 145 | #124=DIRECTION('ref_axis',(1.,0.,0.)); 146 | #125=DIRECTION('',(0.,0.,1.)); 147 | #126=DIRECTION('center_axis',(0.,0.,1.)); 148 | #127=DIRECTION('ref_axis',(1.,0.,0.)); 149 | #128=DIRECTION('center_axis',(0.,0.,-1.)); 150 | #129=DIRECTION('ref_axis',(1.,0.,0.)); 151 | #130=DIRECTION('center_axis',(0.,0.,1.)); 152 | #131=DIRECTION('ref_axis',(-1.,0.,0.)); 153 | #132=DIRECTION('center_axis',(0.,0.,1.)); 154 | #133=DIRECTION('ref_axis',(-1.,0.,0.)); 155 | #134=DIRECTION('',(0.,0.,-1.)); 156 | #135=DIRECTION('center_axis',(0.,0.,-1.)); 157 | #136=DIRECTION('ref_axis',(-1.,0.,0.)); 158 | #137=DIRECTION('center_axis',(0.,0.,1.)); 159 | #138=DIRECTION('ref_axis',(-1.,0.,0.)); 160 | #139=DIRECTION('center_axis',(0.,0.,1.)); 161 | #140=DIRECTION('ref_axis',(1.,0.,0.)); 162 | #141=DIRECTION('center_axis',(0.,0.,1.)); 163 | #142=DIRECTION('ref_axis',(1.,0.,0.)); 164 | #143=DIRECTION('',(0.,0.,-1.)); 165 | #144=DIRECTION('center_axis',(0.,0.,1.)); 166 | #145=DIRECTION('ref_axis',(1.,0.,0.)); 167 | #146=DIRECTION('center_axis',(0.,0.,1.)); 168 | #147=DIRECTION('ref_axis',(1.,0.,0.)); 169 | #148=DIRECTION('center_axis',(0.,0.,1.)); 170 | #149=DIRECTION('ref_axis',(1.,0.,0.)); 171 | #150=CARTESIAN_POINT('',(0.,0.,0.)); 172 | #151=CARTESIAN_POINT('Origin',(5.,5.,0.)); 173 | #152=CARTESIAN_POINT('',(3.4,5.,0.)); 174 | #153=CARTESIAN_POINT('Origin',(5.,5.,0.)); 175 | #154=CARTESIAN_POINT('',(3.4,5.,5.)); 176 | #155=CARTESIAN_POINT('',(3.4,5.,0.)); 177 | #156=CARTESIAN_POINT('Origin',(5.,5.,5.)); 178 | #157=CARTESIAN_POINT('Origin',(5.,5.,5.)); 179 | #158=CARTESIAN_POINT('Origin',(5.,5.,16.)); 180 | #159=CARTESIAN_POINT('',(6.15,5.,19.5)); 181 | #160=CARTESIAN_POINT('Origin',(5.,5.,19.5)); 182 | #161=CARTESIAN_POINT('',(6.15,5.,16.)); 183 | #162=CARTESIAN_POINT('',(6.15,5.,16.)); 184 | #163=CARTESIAN_POINT('Origin',(5.,5.,16.)); 185 | #164=CARTESIAN_POINT('Origin',(5.,5.,19.5)); 186 | #165=CARTESIAN_POINT('Origin',(5.,5.,0.)); 187 | #166=CARTESIAN_POINT('',(2.,5.,16.)); 188 | #167=CARTESIAN_POINT('Origin',(5.,5.,16.)); 189 | #168=CARTESIAN_POINT('',(2.,5.,0.)); 190 | #169=CARTESIAN_POINT('',(2.,5.,0.)); 191 | #170=CARTESIAN_POINT('Origin',(5.,5.,0.)); 192 | #171=CARTESIAN_POINT('Origin',(5.,5.,16.)); 193 | #172=CARTESIAN_POINT('Origin',(5.,5.,0.)); 194 | #173=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.01),#177, 195 | 'DISTANCE_ACCURACY_VALUE', 196 | 'Maximum model space distance between geometric entities at asserted c 197 | onnectivities'); 198 | #174=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.01),#177, 199 | 'DISTANCE_ACCURACY_VALUE', 200 | 'Maximum model space distance between geometric entities at asserted c 201 | onnectivities'); 202 | #175=( 203 | GEOMETRIC_REPRESENTATION_CONTEXT(3) 204 | GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#173)) 205 | GLOBAL_UNIT_ASSIGNED_CONTEXT((#177,#179,#180)) 206 | REPRESENTATION_CONTEXT('','3D') 207 | ); 208 | #176=( 209 | GEOMETRIC_REPRESENTATION_CONTEXT(3) 210 | GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#174)) 211 | GLOBAL_UNIT_ASSIGNED_CONTEXT((#177,#179,#180)) 212 | REPRESENTATION_CONTEXT('','3D') 213 | ); 214 | #177=( 215 | LENGTH_UNIT() 216 | NAMED_UNIT(*) 217 | SI_UNIT(.MILLI.,.METRE.) 218 | ); 219 | #178=( 220 | LENGTH_UNIT() 221 | NAMED_UNIT(*) 222 | SI_UNIT($,.METRE.) 223 | ); 224 | #179=( 225 | NAMED_UNIT(*) 226 | PLANE_ANGLE_UNIT() 227 | SI_UNIT($,.RADIAN.) 228 | ); 229 | #180=( 230 | NAMED_UNIT(*) 231 | SI_UNIT($,.STERADIAN.) 232 | SOLID_ANGLE_UNIT() 233 | ); 234 | #181=SHAPE_DEFINITION_REPRESENTATION(#182,#183); 235 | #182=PRODUCT_DEFINITION_SHAPE('',$,#185); 236 | #183=SHAPE_REPRESENTATION('',(#105),#175); 237 | #184=PRODUCT_DEFINITION_CONTEXT('part definition',#189,'design'); 238 | #185=PRODUCT_DEFINITION('Standoffs','Standoffs v3',#186,#184); 239 | #186=PRODUCT_DEFINITION_FORMATION('',$,#191); 240 | #187=PRODUCT_RELATED_PRODUCT_CATEGORY('Standoffs v3','Standoffs v3',(#191)); 241 | #188=APPLICATION_PROTOCOL_DEFINITION('international standard', 242 | 'automotive_design',2009,#189); 243 | #189=APPLICATION_CONTEXT( 244 | 'Core Data for Automotive Mechanical Design Process'); 245 | #190=PRODUCT_CONTEXT('part definition',#189,'mechanical'); 246 | #191=PRODUCT('Standoffs','Standoffs v3',$,(#190)); 247 | #192=PRESENTATION_STYLE_ASSIGNMENT((#193)); 248 | #193=SURFACE_STYLE_USAGE(.BOTH.,#194); 249 | #194=SURFACE_SIDE_STYLE('',(#195)); 250 | #195=SURFACE_STYLE_FILL_AREA(#196); 251 | #196=FILL_AREA_STYLE('Steel - Satin',(#197)); 252 | #197=FILL_AREA_STYLE_COLOUR('Steel - Satin',#198); 253 | #198=COLOUR_RGB('Steel - Satin',0.627450980392157,0.627450980392157,0.627450980392157); 254 | ENDSEC; 255 | END-ISO-10303-21; 256 | -------------------------------------------------------------------------------- /Hardware/BOM_UPS for Raspberry Pi R3_1_2020-06-22_12-30-28.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Hardware/BOM_UPS for Raspberry Pi R3_1_2020-06-22_12-30-28.csv -------------------------------------------------------------------------------- /Hardware/Gerber_PCB_R3_1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Hardware/Gerber_PCB_R3_1.zip -------------------------------------------------------------------------------- /Hardware/PickAndPlace_PCB_R3_1_2020-06-22_12-30-39.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Hardware/PickAndPlace_PCB_R3_1_2020-06-22_12-30-39.csv -------------------------------------------------------------------------------- /Hardware/Schematic_UPS for Raspberry Pi R3_1_2020-07-21_13-59-35.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjohn327/raspberry_pi_ups/627ee6249c4a9d87569f6028957df1dc8bda8707/Hardware/Schematic_UPS for Raspberry Pi R3_1_2020-07-21_13-59-35.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 tjohn327 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UPS for Raspberry Pi 2 | 3 | ![UPS Image](Assests/ups_R3_1.png "UPS powering a Raspberry Pi 3B+ model") 4 | 5 | ## Description 6 | 7 | An uninterruptible power supply for Raspberry Pi that can provide more than an hour of backup power and can shutdown the Pi safely. 8 | This UPS can be used to power any 5V device with up to 3A continuous current. It is based on Texas Instruments [BQ25895](http://www.ti.com/product/BQ25895) power management IC and [TPS61236P](http://www.ti.com/product/TPS61236P) boost converter IC. 9 | 10 | This UPS can power a Raspberry Pi through the GPIO header by using it as a hat. When used as a hat, the GPIO pins 2, 3 and 4 will be utilized for I2C and interrupt signals. 11 | 12 | Note: 13 | 14 | * Do not connect two input sources together! 15 | 16 | * The device does not have reverse polarity protection, be sure to observe the polarity marked in the battery holder while inserting the battery. 17 | 18 | * UPS script was tested on a Raspberry Pi 3B+ running Raspbian Buster. 19 | 20 | ## Specifications 21 | 22 | * Input: 4.5V - 14V DC, 2A - 5A 23 | 24 | * Input Ports: Screw Terminal, micro USB 25 | 26 | * Output: 5V, up to 3A 27 | 28 | * Output Ports: Screw Terminal, USB A, 40 pin GPIO header for Raspberry Pi 29 | 30 | * Battery: Not included (Samsung INR18650-29E recommended, other 18650 size li-Ion batteries can be used) 31 | 32 | * Communication: I2C 33 | 34 | ## Status LEDs 35 | 36 | * IN: Input connected or not 37 | 38 | * STATUS: ON- Charging, OFF- Charging done, Blinking- Error 39 | 40 | * OUT: Output on or off 41 | 42 | ## Setting up Power Pi for use with a Raspberry Pi 43 | 44 | ### 1. Connect Power Pi to Raspberry pi 45 | 46 | * Make sure the switch of the Power Pi is turned off and no power input is connected to the Power Pi. 47 | 48 | * Insert the battery into the battery holder of the Power Pi following the correct polarity. 49 | 50 | * Connect Power Pi to Raspberry Pi by inserting it into the GPIO pins. 51 | 52 | * Connect a USB power cable into the Raspberry Pi's USB input as usual to turn the Pi on. (This is to set up Power Pi. After the setup, power cable can be connected to Power Pi's input.) 53 | 54 | ![Setup](Assests/PowerPIGuide.png "Steps for setting up Power Pi") 55 | 56 | ### 2. Set up Raspberry Pi to communicate with Power Pi 57 | 58 | #### Enable I2C and install smbus 59 | 60 | Update the system (optional): 61 | 62 | ```shell 63 | sudo apt update && sudo apt upgrade -y 64 | ``` 65 | 66 | Enable I2C: 67 | 68 | ```shell 69 | sudo raspi-config 70 | ``` 71 | 72 | Choose Interfacing Options, then I2C and then select enable to enable I2C in the Raspberry Pi. 73 | 74 | Install smbus by running the following command: 75 | 76 | ```shell 77 | sudo apt-get install -y python-smbus 78 | ``` 79 | 80 | For more information, checkout the [link](https://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-i2c) 81 | 82 | ### 3. Install the ups service 83 | 84 | Clone the Power Pi repository: 85 | 86 | ```shell 87 | cd ~ 88 | git clone https://github.com/tjohn327/raspberry_pi_ups.git 89 | cd raspberry_pi_ups/src/ 90 | ``` 91 | 92 | Edit the file powerpi.py between lines 16 and 24 if you are not using Samsung INR18650-29E battery. It is recommended to keep the VBAT_LOW at 3.2V for Li-Ion batteries. 93 | 94 | *Power PI uses GPIO4 for interrupts from the Power Management IC. 1-Wire interface uses the same pin. If you are not using 1-Wire interface, disable it before proceeding. Power Pi can still function fully without the interrupt. So if you want to use 1-Wire interface it is advised to remove the resistor R12 from Power Pi, otherwise it will cause interference with 1-Wire interface.* 95 | 96 | Run the install.sh script to install a service for the ups. 97 | 98 | ```shell 99 | chmod +x install.sh 100 | ./install.sh 101 | ``` 102 | 103 | This creates a service named ups.service that will run on startup. 104 | If there are no errors, you will see this as the output: 105 | 106 | ```shell 107 | Checking Python 108 | Python found 109 | Initializing Power Pi 110 | INFO:root:UPS initialized 111 | Creating ups service 112 | Enabling ups service to run on startup 113 | ups service enabled 114 | Power Pi configured successfully 115 | ``` 116 | 117 | Don't panic - since the Power is not connected to Power Pi this message will appear. 118 | 119 | ```shell 120 | Broadcast message from user@raspberry (somewhere) (Sat Aug 15 18:40:10 2020): 121 | 122 | Power Disconnected, system will shutdown in 72 minutes! 123 | ``` 124 | 125 | Now turn off the Raspberry Pi and remove the power cable form it. Connect the power cable to Power Pi and turn the switch to ON position. This will power up the Raspberry Pi through Power Pi. 126 | 127 | ![PowerPi](Assests/final.jpg "Power Pi powering the Raspberry Pi") 128 | 129 | When the Pi is powered back up, check the status of the ups service by running: 130 | 131 | ```shell 132 | sudo systemctl status ups.service 133 | ``` 134 | 135 | If everything is running correctly, you will see a status similar to this: 136 | 137 | ```shell 138 | ● ups.service - UPS Service 139 | Loaded: loaded (/lib/systemd/system/ups.service; enabled; vendor preset: enabled) 140 | Active: active (running) since Tue 2020-06-09 06:44:15 UTC; 34s ago 141 | Main PID: 542 (python) 142 | Tasks: 2 (limit: 2200) 143 | Memory: 6.9M 144 | CGroup: /system.slice/ups.service 145 | └─542 /usr/bin/python /home/pi/code/raspberry_pi_ups/src/ups.py 146 | 147 | Jun 09 06:44:15 raspberrypi systemd[1]: Started UPS Service. 148 | Jun 09 06:44:17 raspberrypi python[542]: INFO:root:UPS initialized 149 | ``` 150 | 151 | Your Power Pi is now ready. 152 | 153 | The ups service will now run on startup and send the status of the UPS to UDP Port 40001 every 2 seconds. You can see the status of the UPS by listening to that port. 154 | 155 | To test if the UPS status is being read correctly, run the following command: 156 | 157 | ```shell 158 | nc -lvu 40001 159 | ``` 160 | 161 | ### 4. Setup Node-Red dashboard for visualization (optional) 162 | 163 | ![Dashboard](Assests/dashboard_R3.PNG "UPS Monitoring Dashboard") 164 | 165 | If you are new to Node-Red, please checkout their Essentials [video](https://www.youtube.com/watch?v=ksGeUD26Mw0&list=PLyNBB9VCLmo1hyO-4fIZ08gqFcXBkHy-6) series. 166 | 167 | Follow the instructions given in the [link](https://nodered.org/docs/getting-started/raspberrypi) and install Node-Red on the Raspberry Pi. 168 | 169 | It may take a while to install. After the installation is done, copy the ups_flow.json file to Node-Red's directory. 170 | 171 | ```shell 172 | cp ups_flow.json ~/.node-red/lib/flows/ups_flow.json 173 | ``` 174 | 175 | Install the Dashboard extension for Node-Red. 176 | 177 | ```shell 178 | cd ~/.node-red/ 179 | npm i node-red-dashboard 180 | ``` 181 | 182 | Enable Node-Red to run on startup and start the Node-Red service: 183 | 184 | ```shell 185 | sudo systemctl enable nodered.service 186 | sudo systemctl start nodered.service 187 | ``` 188 | 189 | Open the Node-Red link in a browser. The link is usually: 190 | 191 | http://[IP of Raspberry Pi]:1880/ 192 | 193 | Import the ups flow into the Node-Red environment. 194 | 195 | ![Import](Assests/nodered_import.png "Importing ups flow") 196 | 197 | Edit one of the UI nodes (e.g BAT) by double clicking on it. In its setup menu click on the edit button next to "Group' and select 'Home' in the Tab drop down. Click Update and then Done. 198 | 199 | Deploy the flow by clicking the Deploy button and open the dashboard link of Node-Red to see the status of the UPS. 200 | 201 | http://[IP of Raspberry Pi]:1880/ui 202 | 203 | More [info](https://nodered.org/docs/user-guide/editor/workspace/import-export) about importing flows and setting up [dashboards](https://flows.nodered.org/node/node-red-dashboard). 204 | 205 | ## Using Power Pi with Raspberry Pi 206 | 207 | When powering the Raspberry Pi through Power Pi, always connect the power cable to the input of the Power Pi. Use the switch of Power Pi to turn the devices ON and OFF. 208 | -------------------------------------------------------------------------------- /TestRig/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /TestRig/.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /TestRig/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /TestRig/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /TestRig/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /TestRig/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | monitor_speed = 115200 16 | lib_deps = TFT_eSPI 17 | -------------------------------------------------------------------------------- /TestRig/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "tester.h" 3 | 4 | uint8_t y_pos=15; 5 | void setup() 6 | { 7 | Serial.begin(115200); 8 | PinSetup(); 9 | Beep(1); 10 | I2CSetup(); 11 | DisplaySetup(); 12 | } 13 | 14 | void loop() 15 | { 16 | switch (state) 17 | { 18 | case 0: 19 | tft.drawString("Ready", tft.width() / 2, tft.height() / 2 ); 20 | yield(); 21 | break; 22 | 23 | case 10: 24 | y_pos=30; 25 | if (CheckI2C()) 26 | { 27 | tft.fillScreen(TFT_BLACK); 28 | state = 20; 29 | tft.drawString("I2C OK", 0, y_pos ); 30 | Progress(); 31 | } 32 | else 33 | { 34 | tft.fillScreen(TFT_BLACK); 35 | tft.drawString("I2C Failed!", 0, y_pos ); 36 | Beep(2); 37 | Reset(); 38 | } 39 | y_pos+=15; 40 | break; 41 | 42 | case 20: 43 | tft.drawString("VIN_COUT Test Started", 0, y_pos ); 44 | y_pos+=15; 45 | if(TestVIN_COUT()) 46 | { 47 | tft.drawString("VIN_COUT Test OK", 0, y_pos ); 48 | state = 30; 49 | } 50 | else 51 | { 52 | tft.drawString("VIN_COUT Test Failed", 0, y_pos ); 53 | Beep(2); 54 | Reset(); 55 | } 56 | y_pos+=15; 57 | break; 58 | 59 | case 30: 60 | tft.drawString("Remove Input", 0, y_pos); 61 | y_pos+=15; 62 | Beep(3); 63 | delay(3000); 64 | tft.drawString("COUT Test started", 0, y_pos ); 65 | y_pos+=15; 66 | if(TestCOUT()) 67 | { 68 | tft.drawString("COUT Test OK", 0, y_pos ); 69 | state = 40; 70 | } 71 | else 72 | { 73 | tft.drawString("COUT Test Failed", 0, y_pos ); 74 | Beep(2); 75 | Reset(); 76 | } 77 | y_pos+=15; 78 | break; 79 | case 40: 80 | tft.drawString("All tests Passed", 0, y_pos); 81 | Beep(4); 82 | tft.drawRect(0,0,230,5,TFT_WHITE); 83 | Reset(); 84 | break; 85 | default: 86 | break; 87 | } 88 | } -------------------------------------------------------------------------------- /TestRig/src/tester.cpp: -------------------------------------------------------------------------------- 1 | #include "tester.h" 2 | 3 | uint8_t state = 0; 4 | uint8_t progress = 0; 5 | TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library 6 | 7 | void IRAM_ATTR ButtonPress() 8 | { 9 | if (state == 0) 10 | { 11 | Serial.println("Button pressed"); 12 | pinMode(COUT_DAC_PULLDOWN, INPUT); 13 | state = 10; 14 | delay(1000); 15 | } 16 | } 17 | 18 | void PinSetup(void) 19 | { 20 | pinMode(CIN_ADC, INPUT); 21 | pinMode(COUT_ADC, INPUT); 22 | pinMode(VIN_DAC, OUTPUT); 23 | pinMode(COUT_DAC, OUTPUT); 24 | pinMode(COUT_DAC_PULLDOWN, OUTPUT); 25 | pinMode(COUT_DAC_PULLDOWN, LOW); 26 | dacWrite(VIN_DAC, VIN_5V); 27 | dacWrite(COUT_DAC, 0); 28 | pinMode(BUTTON_PIN, INPUT_PULLUP); 29 | attachInterrupt(BUTTON_PIN, ButtonPress, FALLING); 30 | pinMode(BUZZER_PIN, OUTPUT); 31 | ledcSetup(BUZZ_CHANNEL, 1000, 8); 32 | ledcAttachPin(BUZZER_PIN, BUZZ_CHANNEL); 33 | ledcWrite(BUZZ_CHANNEL, 0); 34 | } 35 | 36 | void DisplaySetup(void) 37 | { 38 | tft.init(); 39 | tft.setRotation(1); 40 | tft.fillScreen(TFT_BLACK); 41 | tft.setTextSize(1); 42 | tft.setCursor(0, 0); 43 | } 44 | 45 | void Beep(uint8_t type) 46 | { 47 | switch (type) 48 | { 49 | case 1: //start 50 | ledcWrite(BUZZ_CHANNEL, 50); 51 | delay(50); 52 | ledcWrite(BUZZ_CHANNEL, 0); 53 | break; 54 | case 2: //error 55 | for (int i = 0; i < 3; i++) 56 | { 57 | ledcWrite(BUZZ_CHANNEL, 50); 58 | delay(30); 59 | ledcWrite(BUZZ_CHANNEL, 0); 60 | delay(30); 61 | } 62 | break; 63 | case 3: //input remove 64 | ledcWrite(BUZZ_CHANNEL, 50); 65 | delay(30); 66 | ledcWrite(BUZZ_CHANNEL, 0); 67 | break; 68 | case 4: //tesp passed 69 | ledcWrite(BUZZ_CHANNEL, 50); 70 | delay(500); 71 | ledcWrite(BUZZ_CHANNEL, 0); 72 | break; 73 | default: 74 | break; 75 | } 76 | 77 | } 78 | 79 | void BeepError(void) 80 | { 81 | for (int i = 0; i < 3; i++) 82 | { 83 | ledcWrite(BUZZ_CHANNEL, 50); 84 | delay(30); 85 | ledcWrite(BUZZ_CHANNEL, 0); 86 | delay(30); 87 | } 88 | } 89 | 90 | void I2CSetup(void) 91 | { 92 | Serial.println("Setting up I2C"); 93 | Wire.begin(SDA_PIN, SCL_PIN); 94 | } 95 | 96 | bool CheckI2C(void) 97 | { 98 | Wire.beginTransmission(I2C_ADD); 99 | byte err = Wire.endTransmission(); 100 | if (err == 0) 101 | { 102 | Serial.println("I2C device found"); 103 | Wire.beginTransmission(I2C_ADD); 104 | Wire.write(0x07); 105 | Wire.write(B10001101); 106 | Wire.endTransmission(); 107 | Wire.beginTransmission(I2C_ADD); 108 | Wire.write(0x03); 109 | Wire.write(B00010000); 110 | Wire.endTransmission(); 111 | Wire.beginTransmission(I2C_ADD); 112 | Wire.write(0x00); 113 | Wire.write(B01111111); 114 | Wire.endTransmission(); 115 | Wire.beginTransmission(I2C_ADD); 116 | Wire.write(0x04); 117 | Wire.write(B00010000); 118 | Wire.endTransmission(); 119 | Wire.beginTransmission(I2C_ADD); 120 | Wire.write(0x09); 121 | Wire.write(B01001000); 122 | Wire.endTransmission(); 123 | return true; 124 | } 125 | Serial.println("I2C device not found"); 126 | return false; 127 | } 128 | 129 | void Reset(void) 130 | { 131 | state = 0; 132 | pinMode(COUT_DAC_PULLDOWN, OUTPUT); 133 | pinMode(COUT_DAC_PULLDOWN, LOW); 134 | dacWrite(VIN_DAC,VIN_5V); 135 | delay(3000); 136 | progress = 0; 137 | tft.fillScreen(TFT_BLACK); 138 | } 139 | 140 | float ReadVBus(void) 141 | { 142 | byte vbus_byte = 0; 143 | float vbus = 0; 144 | _adcReady(); 145 | Wire.beginTransmission(I2C_ADD); 146 | Wire.write(REG_VBUS); 147 | Wire.endTransmission(false); 148 | Wire.requestFrom(I2C_ADD, 1); 149 | while (Wire.available() >= 1) 150 | { 151 | vbus_byte = Wire.read(); 152 | } 153 | vbus = 2.6; 154 | vbus += bitRead(vbus_byte, 6) * 6.4; 155 | vbus += bitRead(vbus_byte, 5) * 3.2; 156 | vbus += bitRead(vbus_byte, 4) * 1.6; 157 | vbus += bitRead(vbus_byte, 3) * 0.8; 158 | vbus += bitRead(vbus_byte, 2) * 0.4; 159 | vbus += bitRead(vbus_byte, 1) * 0.2; 160 | vbus += bitRead(vbus_byte, 0) * 0.1; 161 | return vbus; 162 | } 163 | 164 | float ReadVBat(void) 165 | { 166 | byte vbat_byte = 0; 167 | float vbat; 168 | _adcReady(); 169 | Wire.beginTransmission(I2C_ADD); 170 | Wire.write(REG_VBAT); 171 | Wire.endTransmission(false); 172 | Wire.requestFrom(I2C_ADD, 1); 173 | while (Wire.available() >= 1) 174 | { 175 | vbat_byte = Wire.read(); 176 | } 177 | Wire.endTransmission(); 178 | 179 | vbat = 2.304; 180 | vbat += bitRead(vbat_byte, 6) * 1.280; 181 | vbat += bitRead(vbat_byte, 5) * 0.640; 182 | vbat += bitRead(vbat_byte, 4) * 0.320; 183 | vbat += bitRead(vbat_byte, 3) * 0.160; 184 | vbat += bitRead(vbat_byte, 2) * 0.08; 185 | vbat += bitRead(vbat_byte, 1) * 0.04; 186 | vbat += bitRead(vbat_byte, 0) * 0.02; 187 | return vbat; 188 | } 189 | 190 | byte ReadStatus(void) 191 | { 192 | byte status = 0; 193 | Wire.beginTransmission(I2C_ADD); 194 | Wire.write(REG_STATUS); 195 | Wire.endTransmission(false); 196 | Wire.requestFrom(I2C_ADD, 1); 197 | while (Wire.available() >= 1) 198 | { 199 | status = Wire.read(); 200 | } 201 | return status; 202 | } 203 | 204 | void _adcReady(void) 205 | { 206 | Wire.beginTransmission(I2C_ADD); 207 | Wire.write(REG_CONV_ADC); 208 | Wire.write(BYTE_CONV_ADC_START); 209 | Wire.endTransmission(); 210 | delay(1100); 211 | Wire.beginTransmission(I2C_ADD); 212 | Wire.write(REG_CONV_ADC); 213 | Wire.write(BYTE_CONV_ADC_STOP); 214 | Wire.endTransmission(); 215 | } 216 | 217 | unsigned int _readAnalog(int pin) 218 | { 219 | unsigned int raw = 0; 220 | for (int i = 0; i < 100; i++) 221 | { 222 | raw += analogRead(pin); 223 | } 224 | raw /= 100; 225 | return raw; 226 | } 227 | 228 | float ReadCIN(void) 229 | { 230 | unsigned int cin_raw = _readAnalog(CIN_ADC); 231 | Serial.println((float)cin_raw * (3.3 / 4096)); 232 | float cin = ((cin_raw * (3.3 / 4096.0)) - 2.34) / 0.100; 233 | return cin; 234 | } 235 | 236 | float ReadVOUT(void) 237 | { 238 | unsigned int vout_raw = _readAnalog(VOUT_ADC); 239 | Serial.println(vout_raw); 240 | float vout = 0.001565 * vout_raw + 0.5449; 241 | return vout; 242 | } 243 | 244 | float ReadCOUT(void) 245 | { 246 | unsigned int cout_raw = _readAnalog(COUT_ADC); 247 | float cout = (float)cout_raw * (3.3 / 4096.0) * 10; 248 | return cout; 249 | } 250 | 251 | 252 | bool TestAndPrintParams(void) 253 | { 254 | float vin = ReadVBus(); 255 | float cin = ReadCIN(); 256 | float vout = ReadVOUT(); 257 | float cout = ReadCOUT(); 258 | float eff = ((vout * cout) / (vin * cin)) * 100; 259 | char str[50]; 260 | sprintf(str, "|IN:%.2fV%.2fA|OUT:%.2fV%.2fA|E:%.0f", vin, cin, vout, cout, eff); 261 | Serial.println(str); 262 | tft.drawString(str, 0, 15); 263 | 264 | byte status = ReadStatus(); 265 | if (bitRead(status, 2)) 266 | { 267 | if (!(vin > 4 || vin < 15)) 268 | { 269 | Serial.println("VIN Error"); 270 | return false; 271 | } 272 | } 273 | if (!(vout > 4.75 && vout < 5.25)) 274 | { 275 | Serial.println("VOUT Error"); 276 | return false; 277 | } 278 | Progress(); 279 | return true; 280 | } 281 | 282 | bool TestCOUT(void) 283 | { 284 | dacWrite(COUT_DAC,COUT_1A); 285 | delay(100); 286 | if(!TestAndPrintParams()) 287 | { 288 | return false; 289 | } 290 | dacWrite(COUT_DAC,COUT_2A); 291 | delay(100); 292 | if(!TestAndPrintParams()) 293 | { 294 | return false; 295 | } 296 | dacWrite(COUT_DAC,COUT_3A); 297 | delay(100); 298 | if(!TestAndPrintParams()) 299 | { 300 | return false; 301 | } 302 | return true; 303 | } 304 | 305 | bool TestVIN_COUT(void) 306 | { 307 | dacWrite(VIN_DAC,VIN_5V); 308 | delay(100); 309 | if(!TestCOUT()) 310 | { 311 | return false; 312 | } 313 | dacWrite(VIN_DAC,VIN_9V); 314 | delay(100); 315 | if(!TestCOUT()) 316 | { 317 | return false; 318 | } 319 | dacWrite(VIN_DAC,VIN_14V); 320 | delay(100); 321 | if(!TestCOUT()) 322 | { 323 | return false; 324 | } 325 | return true; 326 | } 327 | 328 | void Progress(void) 329 | { 330 | progress += 18; 331 | tft.drawRect(0,0,progress,5,TFT_WHITE); 332 | } 333 | 334 | 335 | -------------------------------------------------------------------------------- /TestRig/src/tester.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "TFT_eSPI.h" 4 | #include 5 | 6 | //Pin Defenitions 7 | #define VIN_DAC 25 8 | #define COUT_DAC 26 9 | #define COUT_DAC_PULLDOWN 27 10 | #define CIN_ADC 37 11 | #define COUT_ADC 38 12 | #define VOUT_ADC 39 13 | #define BUTTON_PIN 32 14 | #define BUZZER_PIN 33 15 | #define BUZZ_CHANNEL 0 16 | #define SDA_PIN 21 17 | #define SCL_PIN 22 18 | 19 | //Display definitions 20 | #ifndef TFT_DISPOFF 21 | #define TFT_DISPOFF 0x28 22 | #endif 23 | #ifndef TFT_SLPIN 24 | #define TFT_SLPIN 0x10 25 | #endif 26 | #define TFT_MOSI 19 27 | #define TFT_SCLK 18 28 | #define TFT_CS 5 29 | #define TFT_DC 16 30 | #define TFT_RST 23 31 | #define TFT_BL 4 // Display backlight control pin 32 | 33 | //I2C defenitions 34 | #define I2C_ADD 0x6A 35 | #define REG_VBAT 0x0E 36 | #define REG_CONV_ADC 0x02 37 | #define BYTE_CONV_ADC_START B10011101 38 | #define BYTE_CONV_ADC_STOP B00011101 39 | #define REG_VBUS 0x11 40 | #define REG_STATUS 0x0B 41 | 42 | //VIN DAC Values 43 | #define VIN_14V 0 44 | #define VIN_12V 90 45 | #define VIN_9V 160 46 | #define VIN_7_5V 195 47 | #define VIN_6V 233 48 | #define VIN_5V 255 49 | 50 | //COUT DAC Values 51 | #define COUT_1A 1 52 | #define COUT_1_5A 5 53 | #define COUT_2A 10 54 | #define COUT_3A 17 55 | 56 | extern uint8_t state; 57 | extern uint8_t progress; 58 | extern TFT_eSPI tft; 59 | 60 | extern void IRAM_ATTR ButtonPress(); 61 | extern void PinSetup(void); 62 | extern void Beep(uint8_t type); 63 | extern void DisplaySetup(void); 64 | extern void BeepError(void); 65 | extern void I2CSetup (void); 66 | extern bool CheckI2C (void); 67 | extern void Reset(void); 68 | extern unsigned int _readAnalog(int pin); 69 | extern void _adcReady(void); 70 | extern float ReadVBus(void); 71 | extern float ReadVBat(void); 72 | extern byte ReadStatus(void); 73 | extern float ReadCIN(void); 74 | extern float ReadVOUT(void); 75 | extern bool TestAndPrintParams(void); 76 | extern bool TestCOUT(void); 77 | extern bool TestVIN_COUT(void); 78 | extern void Progress(void); 79 | 80 | -------------------------------------------------------------------------------- /TestRig/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /src/init.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os, sys 4 | import logging 5 | from powerpi import Powerpi 6 | 7 | logging.basicConfig(level=logging.INFO) 8 | 9 | if __name__=="__main__": 10 | ppi = Powerpi() 11 | if ppi.initialize(): 12 | sys.exit(1) 13 | else: 14 | sys.exit(0) 15 | 16 | -------------------------------------------------------------------------------- /src/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Checking Python" 3 | if ! command -v python >/dev/null ; 4 | then 5 | echo "Python not found, please install python" 6 | exit 1 7 | fi 8 | echo "Python found" 9 | 10 | echo "Initializing Power Pi" 11 | python init.py 12 | init_exit_status=$? 13 | if [ "${init_exit_status}" -ne 0 ]; 14 | then 15 | echo "Initialization failed" 16 | exit 1 17 | fi 18 | 19 | _dir="${1:-${PWD}}" 20 | _user="${USER}" 21 | _service=" 22 | [Unit] 23 | Description=UPS Service 24 | After=multi-user.target 25 | 26 | [Service] 27 | Type=idle 28 | User=${_user} 29 | ExecStart=/usr/bin/python ${_dir}/ups.py 30 | Restart=on-failure 31 | 32 | [Install] 33 | WantedBy=multi-user.target 34 | " 35 | _file="/lib/systemd/system/ups.service" 36 | 37 | echo "Creating ups service" 38 | if [ -f "${_file}" ]; 39 | then 40 | sudo rm "${_file}" 41 | fi 42 | 43 | sudo touch "${_file}" 44 | sudo echo "${_service}" | sudo tee -a "${_file}" > /dev/null 45 | 46 | echo "Enabling ups service to run on startup" 47 | sudo systemctl daemon-reload 48 | sudo systemctl enable ups.service 49 | if [ $? != 0 ]; 50 | then 51 | echo "Error enabling ups service" 52 | exit 1 53 | fi 54 | sudo systemctl restart ups.service 55 | echo "ups service enabled" 56 | echo "Power Pi configured successfully" 57 | exit 0 58 | 59 | -------------------------------------------------------------------------------- /src/powerpi.py: -------------------------------------------------------------------------------- 1 | import smbus 2 | import logging 3 | import time 4 | 5 | class Powerpi: 6 | 7 | #Refer to http://www.ti.com/lit/ds/symlink/bq25895.pdf for register maps 8 | 9 | 10 | ####Edit this section to suit your battery and input specs############################## 11 | 12 | """ 13 | BYTE_ILIM is used to set the input current limit, i.e the maximum current the 14 | UPS will draw from the power input. It does not affect the output current from 15 | the UPS. If more current is required at the output than the input is cpabale of, 16 | the UPS will augment that current from the battery. 17 | """ 18 | #BYTE_ILIM = 0b01101000 #2A input current limit 19 | BYTE_ILIM = 0b01111111 #3.25A input current limit 20 | #BYTE_ICHG = 0b00001000 #.5A charging current limit 21 | BYTE_ICHG = 0b00010000 #1A charging current limit 22 | 23 | VBAT_LOW = 3.2 # Determines the battery voltage at which the UPS will shutoff. 24 | 25 | #Charge Voltage, uncomment the line suitable for your battery type. 26 | #BYTE_VREG = 0b00000010 #3.84v 27 | #BYTE_VREG = 0b00010010 #3.9V 28 | #BYTE_VREG = 0b00101010 #4V 29 | #BYTE_VREG = 0b01000110 #4.112V 30 | BYTE_VREG = 0b01011110 #4.208V 31 | #BYTE_VREG = 0b01110110 #4.304V 32 | #BYTE_VREG = 0b10001110 #4.4V 33 | #BYTE_VREG = 0b10101010 #4.512V 34 | #BYTE_VREG = 0b11000010 #4.608V 35 | 36 | """ 37 | BAT_CAPACITY, CURRENT_DRAW and VBAT_MAX are used to estimated the state of charge 38 | of the battery since there is not current sensor on this UPS. These values along 39 | with the battery voltage is used to derive the state of charge of the battery. 40 | 41 | To make the charge percent of the battery shown more accurate, take a note of 42 | the battery voltage when charging is complete (red LED turns off after plugging in) 43 | and edit the VBAT_MAX to that value. 44 | 45 | NB:Changing these values does not affect or change the behavior of the UPS. 46 | """ 47 | BAT_CAPACITY = 2900 #Battery capacity in mAh 48 | CURRENT_DRAW = 2000 #Current draw in mAh approximately 49 | VBAT_MAX = 4.208 #This should be the battery when charged to a 100% 50 | 51 | ################################################################################## 52 | 53 | 54 | PORT = 1 55 | ADDRESS = 0x6a #I2C address of the ups 56 | 57 | REG_WATCHDOG = 0x07 58 | BYTE_WATCHDOG_STOP = 0b10001101 #Stop Watchdog timer 59 | REG_SYSMIN = 0x03 60 | BYTE_SYSMIN = 0b00010000 61 | REG_ILIM = 0x00 #ILIM register 62 | REG_VREG = 0x06 #Charge voltage register 63 | 64 | REG_ICHG = 0x04 65 | REG_ICHGR = 0x12 66 | REG_CONV_ADC = 0x02 67 | REG_BATFET = 0x09 68 | BYTE_BATFET = 0b01001000 #delay before battery is disconnected 69 | 70 | REG_CONV_ADC = 0x02 71 | BYTE_CONV_ADC_START = 0b10011101 72 | BYTE_CONV_ADC_STOP = 0b00011101 73 | REG_BATFET_DIS = 0x09 74 | BYTE_BATFET_DIS = 0b01101000 75 | REG_STATUS = 0x0B #address of status register 76 | REG_VBAT = 0x0e 77 | REG_FAULT = 0x0c 78 | REG_IBAT = 0x12 79 | REG_VBUS = 0x11 80 | 81 | 82 | def __init__(self): 83 | pass 84 | 85 | def initialize(self): 86 | try: 87 | self.bus = smbus.SMBus(self.PORT) 88 | self.bus.write_byte_data(self.ADDRESS, self.REG_WATCHDOG, self.BYTE_WATCHDOG_STOP) 89 | self.bus.write_byte_data(self.ADDRESS, self.REG_ILIM,self.BYTE_ILIM) 90 | self.bus.write_byte_data(self.ADDRESS, self.REG_ICHG, self.BYTE_ICHG) 91 | self.bus.write_byte_data(self.ADDRESS, self.REG_BATFET, self.BYTE_BATFET) 92 | self.bus.write_byte_data(self.ADDRESS, self.REG_SYSMIN, self.BYTE_SYSMIN) 93 | self.bus.write_byte_data(self.ADDRESS, self.REG_VREG, self.BYTE_VREG) 94 | logging.info("UPS initialized") 95 | return 0 96 | except Exception as ex: 97 | logging.error("Initialization failed, check connection to the UPS:"+ str(ex)) 98 | return 1 99 | 100 | def _int_to_bool_list(self,num): 101 | return [bool(num & (1< 1: 144 | bat_charge_percent = 1 145 | return bat_charge_percent 146 | 147 | def _calc_time_left(self,vbat): 148 | time_left = int(self._calc_bat_charge_percent(vbat) * 60 * self.BAT_CAPACITY / self.CURRENT_DRAW) 149 | if time_left < 0: 150 | time_left = 0 151 | return time_left 152 | 153 | def read_status(self, clear_fault=False): 154 | try: 155 | if clear_fault: 156 | self.bus.read_byte_data(self.ADDRESS, self.REG_FAULT) 157 | self.bus.write_byte_data(self.ADDRESS, self.REG_CONV_ADC, self.BYTE_CONV_ADC_START) 158 | time.sleep(2) 159 | status = self.bus.read_byte_data(self.ADDRESS, self.REG_STATUS) 160 | status = self._int_to_bool_list(int(status)) 161 | vbat = self._vbat_convert(self.bus.read_byte_data(self.ADDRESS, self.REG_VBAT)) 162 | ibat = self._ibat_convert(self.bus.read_byte_data(self.ADDRESS, self.REG_ICHGR)) 163 | vbus = self._vbus_convert(self.bus.read_byte_data(self.ADDRESS, self.REG_VBUS)) 164 | self.bus.write_byte_data(self.ADDRESS, self.REG_CONV_ADC, self.BYTE_CONV_ADC_STOP) 165 | except Exception as ex: 166 | logging.error("An exception occurred while reading values from the UPS: " + str(ex)) 167 | time.sleep(2) 168 | return 1, None 169 | 170 | if status[2]: 171 | power_status = "Connected" 172 | time_left = -1 173 | else: 174 | power_status = "Not Connected" 175 | time_left = self._calc_time_left(vbat) 176 | 177 | if status[3] and status[4]: 178 | charge_status = "Charging done" 179 | elif status[4] and not status[3]: 180 | charge_status = "Charging" 181 | elif not status[4] and status[3]: 182 | charge_status = "Pre-Charge" 183 | else: 184 | charge_status = "Not Charging" 185 | 186 | 187 | data = { 188 | 'PowerInputStatus': power_status, 189 | 'InputVoltage' : round(vbus,3), 190 | 'ChargeStatus' : charge_status, 191 | 'BatteryVoltage' : round(vbat,3), 192 | "BatteryPercentage" : int(self._calc_bat_charge_percent(vbat)*100), 193 | 'ChargeCurrent' : ibat, 194 | 'TimeRemaining' : int(time_left) 195 | } 196 | 197 | return 0, data 198 | 199 | def bat_disconnect(self): 200 | for i in (0,3): 201 | try: 202 | self.bus.write_byte_data(self.ADDRESS, self.REG_BATFET_DIS, self.BYTE_BATFET_DIS) 203 | return 0 204 | except: 205 | time.sleep(1) 206 | return 1 207 | -------------------------------------------------------------------------------- /src/ups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import time 4 | import os, sys 5 | import logging 6 | import socket 7 | import json 8 | import signal 9 | from powerpi import Powerpi 10 | 11 | logging.basicConfig(level=logging.INFO) 12 | GPIO4_AVAILABLE = True 13 | 14 | try: 15 | import RPi.GPIO as GPIO 16 | except : 17 | GPIO4_AVAILABLE = False 18 | logging.error("Error importing GPIO library, UPS will work without interrupt") 19 | 20 | ENABLE_UDP = True 21 | UDP_PORT = 40001 22 | serverAddressPort = ("127.0.0.1", UDP_PORT) 23 | UDPClientSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) 24 | disconnectflag = False 25 | ppi = Powerpi() 26 | 27 | def read_status(clear_fault=False): 28 | global disconnectflag, ENABLE_UDP 29 | err, status = ppi.read_status(clear_fault) 30 | 31 | if err: 32 | time.sleep(1) 33 | return 34 | 35 | if status["PowerInputStatus"] == "Not Connected" and disconnectflag == False : 36 | disconnectflag = True 37 | message = "echo Power Disconnected, system will shutdown in %d minutes! | wall -n " % (status['TimeRemaining']) 38 | os.system(message) 39 | 40 | if status["PowerInputStatus"] == "Connected" and disconnectflag == True : 41 | disconnectflag = False 42 | message = "echo Power Restored, battery at %d percent | wall -n " % (status['BatteryPercentage']) 43 | os.system(message) 44 | 45 | if ENABLE_UDP: 46 | try: 47 | UDPClientSocket.sendto(json.dumps(status,indent=4,sort_keys=True), serverAddressPort) 48 | except Exception as ex: 49 | logging.error(ex) 50 | 51 | logging.debug(status) 52 | 53 | if(status['BatteryVoltage'] < ppi.VBAT_LOW): 54 | ppi.bat_disconnect() 55 | os.system('sudo shutdown now') 56 | 57 | def interrupt_handler(channel): 58 | read_status(True) 59 | 60 | def main(): 61 | if ppi.initialize(): 62 | sys.exit(1) 63 | 64 | if GPIO4_AVAILABLE: 65 | try: 66 | GPIO.setmode(GPIO.BCM) 67 | GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP) 68 | GPIO.add_event_detect(4, GPIO.FALLING, callback=interrupt_handler, bouncetime=200) 69 | except Exception as ex: 70 | logging.error("Error attaching interrupt to GPIO4, UPS will work without interrupt.") 71 | 72 | while (True): 73 | read_status() 74 | 75 | if __name__=="__main__": 76 | main() 77 | 78 | -------------------------------------------------------------------------------- /src/ups_flow.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "6c370707.c02a", 4 | "type": "tab", 5 | "label": "UPS_Status", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "abe46b43.4ed548", 11 | "type": "udp in", 12 | "z": "6c370707.c02a", 13 | "name": "", 14 | "iface": "", 15 | "port": "40001", 16 | "ipv": "udp4", 17 | "multicast": "false", 18 | "group": "", 19 | "datatype": "utf8", 20 | "x": 80, 21 | "y": 140, 22 | "wires": [ 23 | [ 24 | "f878bed4.981fa8" 25 | ] 26 | ] 27 | }, 28 | { 29 | "id": "f878bed4.981fa8", 30 | "type": "json", 31 | "z": "6c370707.c02a", 32 | "name": "", 33 | "property": "payload", 34 | "action": "obj", 35 | "pretty": false, 36 | "x": 210, 37 | "y": 140, 38 | "wires": [ 39 | [ 40 | "7318b5d4.37a48c", 41 | "e3bad180.23d308", 42 | "6278c82d.c83ee", 43 | "d4f135a.11f6c48", 44 | "3e09aa9e.7ad096", 45 | "18b148b0.483f0f", 46 | "4eeb878f.737d1" 47 | ] 48 | ] 49 | }, 50 | { 51 | "id": "9db423cf.522798", 52 | "type": "file", 53 | "z": "6c370707.c02a", 54 | "name": "", 55 | "filename": "/home/pi/ups_test3.csv", 56 | "appendNewline": false, 57 | "createDir": true, 58 | "overwriteFile": "false", 59 | "encoding": "none", 60 | "x": 1130, 61 | "y": 80, 62 | "wires": [ 63 | [] 64 | ] 65 | }, 66 | { 67 | "id": "10ac9133.58f8d7", 68 | "type": "ui_gauge", 69 | "z": "6c370707.c02a", 70 | "name": "Bat", 71 | "group": "6ca7e7c1.5b5a18", 72 | "order": 1, 73 | "width": 0, 74 | "height": 0, 75 | "gtype": "wave", 76 | "title": "Battery Level", 77 | "label": "%", 78 | "format": "{{value}}", 79 | "min": 0, 80 | "max": "100", 81 | "colors": [ 82 | "#00b500", 83 | "#e6e600", 84 | "#ca3838" 85 | ], 86 | "seg1": "", 87 | "seg2": "", 88 | "x": 750, 89 | "y": 40, 90 | "wires": [] 91 | }, 92 | { 93 | "id": "7318b5d4.37a48c", 94 | "type": "change", 95 | "z": "6c370707.c02a", 96 | "name": "BatteryPercentage", 97 | "rules": [ 98 | { 99 | "t": "set", 100 | "p": "payload", 101 | "pt": "msg", 102 | "to": "payload.BatteryPercentage", 103 | "tot": "msg" 104 | } 105 | ], 106 | "action": "", 107 | "property": "", 108 | "from": "", 109 | "to": "", 110 | "reg": false, 111 | "x": 430, 112 | "y": 40, 113 | "wires": [ 114 | [ 115 | "10ac9133.58f8d7" 116 | ] 117 | ] 118 | }, 119 | { 120 | "id": "e3bad180.23d308", 121 | "type": "change", 122 | "z": "6c370707.c02a", 123 | "name": "TimeRemaining", 124 | "rules": [ 125 | { 126 | "t": "set", 127 | "p": "payload", 128 | "pt": "msg", 129 | "to": "payload.TimeRemaining", 130 | "tot": "msg" 131 | } 132 | ], 133 | "action": "", 134 | "property": "", 135 | "from": "", 136 | "to": "", 137 | "reg": false, 138 | "x": 420, 139 | "y": 80, 140 | "wires": [ 141 | [ 142 | "9131125.37003f" 143 | ] 144 | ] 145 | }, 146 | { 147 | "id": "18b148b0.483f0f", 148 | "type": "change", 149 | "z": "6c370707.c02a", 150 | "name": "InputVoltage", 151 | "rules": [ 152 | { 153 | "t": "set", 154 | "p": "payload", 155 | "pt": "msg", 156 | "to": "payload.InputVoltage", 157 | "tot": "msg" 158 | } 159 | ], 160 | "action": "", 161 | "property": "", 162 | "from": "", 163 | "to": "", 164 | "reg": false, 165 | "x": 410, 166 | "y": 280, 167 | "wires": [ 168 | [ 169 | "9380a267.81c478" 170 | ] 171 | ] 172 | }, 173 | { 174 | "id": "d4f135a.11f6c48", 175 | "type": "change", 176 | "z": "6c370707.c02a", 177 | "name": "ChargeCurrent", 178 | "rules": [ 179 | { 180 | "t": "set", 181 | "p": "payload", 182 | "pt": "msg", 183 | "to": "payload.ChargeCurrent", 184 | "tot": "msg" 185 | } 186 | ], 187 | "action": "", 188 | "property": "", 189 | "from": "", 190 | "to": "", 191 | "reg": false, 192 | "x": 420, 193 | "y": 200, 194 | "wires": [ 195 | [ 196 | "258e1d5b.7f676a" 197 | ] 198 | ] 199 | }, 200 | { 201 | "id": "6278c82d.c83ee", 202 | "type": "change", 203 | "z": "6c370707.c02a", 204 | "name": "ChargeStatus", 205 | "rules": [ 206 | { 207 | "t": "set", 208 | "p": "payload", 209 | "pt": "msg", 210 | "to": "payload.ChargeStatus", 211 | "tot": "msg" 212 | } 213 | ], 214 | "action": "", 215 | "property": "", 216 | "from": "", 217 | "to": "", 218 | "reg": false, 219 | "x": 420, 220 | "y": 160, 221 | "wires": [ 222 | [ 223 | "727267e0.028dd" 224 | ] 225 | ] 226 | }, 227 | { 228 | "id": "3e09aa9e.7ad096", 229 | "type": "change", 230 | "z": "6c370707.c02a", 231 | "name": "PowerInputStatus", 232 | "rules": [ 233 | { 234 | "t": "set", 235 | "p": "payload", 236 | "pt": "msg", 237 | "to": "payload.PowerInputStatus", 238 | "tot": "msg" 239 | } 240 | ], 241 | "action": "", 242 | "property": "", 243 | "from": "", 244 | "to": "", 245 | "reg": false, 246 | "x": 430, 247 | "y": 240, 248 | "wires": [ 249 | [ 250 | "997fda11.624728" 251 | ] 252 | ] 253 | }, 254 | { 255 | "id": "34670b4f.b585a4", 256 | "type": "ui_text", 257 | "z": "6c370707.c02a", 258 | "group": "6ca7e7c1.5b5a18", 259 | "order": 2, 260 | "width": 0, 261 | "height": 0, 262 | "name": "TimeRemaining", 263 | "label": "Time Remaining (approx.)", 264 | "format": "{{msg.payload}} Min", 265 | "layout": "row-spread", 266 | "x": 780, 267 | "y": 80, 268 | "wires": [] 269 | }, 270 | { 271 | "id": "727267e0.028dd", 272 | "type": "ui_text", 273 | "z": "6c370707.c02a", 274 | "group": "6ca7e7c1.5b5a18", 275 | "order": 4, 276 | "width": 0, 277 | "height": 0, 278 | "name": "ChargeStatus", 279 | "label": "Charge Status", 280 | "format": "{{msg.payload}}", 281 | "layout": "row-spread", 282 | "x": 780, 283 | "y": 160, 284 | "wires": [] 285 | }, 286 | { 287 | "id": "258e1d5b.7f676a", 288 | "type": "ui_text", 289 | "z": "6c370707.c02a", 290 | "group": "6ca7e7c1.5b5a18", 291 | "order": 5, 292 | "width": 0, 293 | "height": 0, 294 | "name": "ChargeCurrent", 295 | "label": "Charge Current", 296 | "format": "{{msg.payload}} mAh", 297 | "layout": "row-spread", 298 | "x": 780, 299 | "y": 200, 300 | "wires": [] 301 | }, 302 | { 303 | "id": "997fda11.624728", 304 | "type": "ui_text", 305 | "z": "6c370707.c02a", 306 | "group": "6ca7e7c1.5b5a18", 307 | "order": 6, 308 | "width": 0, 309 | "height": 0, 310 | "name": "PowerInputStatus", 311 | "label": "Power Input ", 312 | "format": "{{msg.payload}}", 313 | "layout": "row-spread", 314 | "x": 790, 315 | "y": 240, 316 | "wires": [] 317 | }, 318 | { 319 | "id": "6175c42d.51ef4c", 320 | "type": "ui_text", 321 | "z": "6c370707.c02a", 322 | "group": "6ca7e7c1.5b5a18", 323 | "order": 7, 324 | "width": 0, 325 | "height": 0, 326 | "name": "InputVoltage", 327 | "label": "Input Voltage ", 328 | "format": "{{msg.payload}} V", 329 | "layout": "row-spread", 330 | "x": 770, 331 | "y": 280, 332 | "wires": [] 333 | }, 334 | { 335 | "id": "4eeb878f.737d1", 336 | "type": "change", 337 | "z": "6c370707.c02a", 338 | "name": "BatteryVoltage", 339 | "rules": [ 340 | { 341 | "t": "set", 342 | "p": "payload", 343 | "pt": "msg", 344 | "to": "payload.BatteryVoltage", 345 | "tot": "msg" 346 | } 347 | ], 348 | "action": "", 349 | "property": "", 350 | "from": "", 351 | "to": "", 352 | "reg": false, 353 | "x": 420, 354 | "y": 120, 355 | "wires": [ 356 | [ 357 | "44bf8f35.ac90c8" 358 | ] 359 | ] 360 | }, 361 | { 362 | "id": "8b2153f3.55bf78", 363 | "type": "ui_text", 364 | "z": "6c370707.c02a", 365 | "group": "6ca7e7c1.5b5a18", 366 | "order": 3, 367 | "width": 0, 368 | "height": 0, 369 | "name": "BatteryVoltage", 370 | "label": "Battery Voltage ", 371 | "format": "{{msg.payload}} V", 372 | "layout": "row-spread", 373 | "x": 780, 374 | "y": 120, 375 | "wires": [] 376 | }, 377 | { 378 | "id": "9131125.37003f", 379 | "type": "function", 380 | "z": "6c370707.c02a", 381 | "name": "", 382 | "func": "if (msg.payload === -1)\n{\n msg.payload = \"\\u221E\";\n}\nreturn msg;", 383 | "outputs": 1, 384 | "noerr": 0, 385 | "x": 590, 386 | "y": 80, 387 | "wires": [ 388 | [ 389 | "34670b4f.b585a4" 390 | ] 391 | ] 392 | }, 393 | { 394 | "id": "44bf8f35.ac90c8", 395 | "type": "smooth", 396 | "z": "6c370707.c02a", 397 | "name": "", 398 | "property": "payload", 399 | "action": "mean", 400 | "count": "10", 401 | "round": "3", 402 | "mult": "single", 403 | "reduce": false, 404 | "x": 600, 405 | "y": 120, 406 | "wires": [ 407 | [ 408 | "8b2153f3.55bf78" 409 | ] 410 | ] 411 | }, 412 | { 413 | "id": "9380a267.81c478", 414 | "type": "smooth", 415 | "z": "6c370707.c02a", 416 | "name": "", 417 | "property": "payload", 418 | "action": "mean", 419 | "count": "2", 420 | "round": "2", 421 | "mult": "single", 422 | "reduce": false, 423 | "x": 600, 424 | "y": 280, 425 | "wires": [ 426 | [ 427 | "6175c42d.51ef4c" 428 | ] 429 | ] 430 | }, 431 | { 432 | "id": "6ca7e7c1.5b5a18", 433 | "type": "ui_group", 434 | "z": "", 435 | "name": "UPS Status", 436 | "tab": "313a437b.4a236c", 437 | "order": 1, 438 | "disp": true, 439 | "width": "6", 440 | "collapse": false 441 | }, 442 | { 443 | "id": "313a437b.4a236c", 444 | "type": "ui_tab", 445 | "z": "", 446 | "name": "Home", 447 | "icon": "dashboard", 448 | "disabled": false, 449 | "hidden": false 450 | } 451 | ] -------------------------------------------------------------------------------- /src/ups_with_timeout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import time 4 | import os, sys 5 | import logging 6 | import socket 7 | import json 8 | import signal 9 | from powerpi import Powerpi 10 | 11 | logging.basicConfig(level=logging.INFO) 12 | GPIO4_AVAILABLE = True 13 | 14 | try: 15 | import RPi.GPIO as GPIO 16 | except : 17 | GPIO4_AVAILABLE = False 18 | logging.error("Error importing GPIO library, UPS will work without interrupt") 19 | 20 | ENABLE_UDP = True 21 | UDP_PORT = 40001 22 | serverAddressPort = ("127.0.0.1", UDP_PORT) 23 | UDPClientSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) 24 | disconnectflag = False 25 | ppi = Powerpi() 26 | 27 | TIMEOUT = 100 #timeout to shutdown in seconds 28 | counter = 0 29 | 30 | def read_status(clear_fault=False): 31 | global disconnectflag, ENABLE_UDP, counter, TIMEOUT 32 | err, status = ppi.read_status(clear_fault) 33 | 34 | if err: 35 | time.sleep(1) 36 | return 37 | 38 | if status["PowerInputStatus"] == "Not Connected" and disconnectflag == False : 39 | disconnectflag = True 40 | message = "echo Power Disconnected, system will shutdown in %d minutes! | wall" % (status['TimeRemaining']) 41 | os.system(message) 42 | 43 | if status["PowerInputStatus"] == "Connected" and disconnectflag == True : 44 | disconnectflag = False 45 | message = "echo Power Restored, battery at %d percent | wall" % (status['BatteryPercentage']) 46 | os.system(message) 47 | counter = 0 48 | 49 | if ENABLE_UDP: 50 | try: 51 | UDPClientSocket.sendto(json.dumps(status,indent=4,sort_keys=True), serverAddressPort) 52 | except Exception as ex: 53 | logging.error(ex) 54 | 55 | logging.debug(status) 56 | 57 | if(status['BatteryVoltage'] < ppi.VBAT_LOW): 58 | ppi.bat_disconnect() 59 | os.system('sudo shutdown now') 60 | 61 | if disconnectflag: 62 | if counter > TIMEOUT: 63 | ppi.bat_disconnect() 64 | os.system('sudo shutdown now') 65 | counter = counter +2 66 | 67 | def interrupt_handler(channel): 68 | read_status(True) 69 | 70 | def main(): 71 | if ppi.initialize(): 72 | sys.exit(1) 73 | 74 | if GPIO4_AVAILABLE: 75 | try: 76 | GPIO.setmode(GPIO.BCM) 77 | GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP) 78 | GPIO.add_event_detect(4, GPIO.FALLING, callback=interrupt_handler, bouncetime=200) 79 | except Exception as ex: 80 | logging.error("Error attaching interrupt to GPIO4, UPS will work without interrupt.") 81 | 82 | while (True): 83 | read_status() 84 | 85 | if __name__=="__main__": 86 | main() 87 | 88 | --------------------------------------------------------------------------------