├── .gitignore ├── Documentation ├── example_log.csv ├── example_log.txt ├── example_msmtprc ├── example_stresslog.csv ├── history │ └── CHANGELOG.md ├── rpi_tempmon.cfg ├── screenshots │ ├── graphmenu.png │ ├── graphmode1.jpg │ ├── graphmode10.jpg │ ├── graphmode11.jpg │ ├── graphmode12.jpg │ ├── graphmode2.jpg │ ├── graphmode3.jpg │ ├── graphmode4.jpg │ ├── graphmode5.jpg │ ├── graphmode6.jpg │ ├── graphmode7.jpg │ ├── graphmode8.jpg │ ├── graphmode9.jpg │ ├── graphstresstest.jpg │ ├── main_screen1.jpg │ └── nyalarm.jpg └── stresstestdata │ ├── alldata.jpg │ ├── stresstest.md │ ├── test1.jpg │ ├── test2.jpg │ ├── test3.jpg │ ├── test4.jpg │ ├── test5.jpg │ ├── test6.jpg │ └── test7.jpg ├── LICENSE.md ├── README.md ├── rpiTempMod ├── RpiTempmonGraph.py ├── RpiTempmonWork.py └── __init__.py ├── rpiTempSrc ├── __init__.py └── rpi_tempmon.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | rpiTempSrc/__pycache__ 2 | rpiTempMod/__pycache__ 3 | rpiTempMod/*.pyc 4 | dist/ 5 | build/ 6 | rpi_tempmon.py.egg-info/ 7 | stuff/ 8 | .gout* -------------------------------------------------------------------------------- /Documentation/example_log.csv: -------------------------------------------------------------------------------- 1 | 18-04-04 09:46:51,61.5,60.7,25.8,33.9,11.6 2 | 18-04-04 09:47:19,60.5,60.1,28.4,33.8,11.6 3 | 18-04-04 09:48:11,64.5,63.4,46.4,43.5,11.6 4 | 18-04-04 09:49:12,67.5,67.7,49.4,51.2,16.9 5 | 18-04-04 09:50:05,66.5,66.6,72.1,36.1,16.9 6 | 18-04-04 09:51:07,62.5,62.3,25.3,16.7,16.9 7 | 18-04-04 09:52:09,60.5,60.1,26.3,14.8,16.9 8 | 18-04-04 09:54:16,59.5,59.1,30.4,16,16.9 9 | 18-04-04 09:55:37,58.5,58.5,30.3,16,16.9 10 | -------------------------------------------------------------------------------- /Documentation/example_log.txt: -------------------------------------------------------------------------------- 1 | TS = 18-04-20 23:35:15 2 | EP = 1524263715.0 3 | GPU temperature = 50.5'C 4 | CPU temperature = 49.9 5 | Cpu usage = 25.7 6 | RAM usage = 42.3 7 | Swap usage = 58.8 8 | Raspberry pi temperature monitor: raspberrypi 9 | TS = 18-04-20 23:35:21 10 | EP = 1524263722.0 11 | GPU temperature = 50.5'C 12 | CPU temperature = 50.5 13 | Cpu usage = 29.1 14 | RAM usage = 42.0 15 | Swap usage = 58.8 16 | Raspberry pi temperature monitor: raspberrypi 17 | TS = 18-04-20 23:36:10 18 | EP = 1524263771.0 19 | GPU temperature = 50.5'C 20 | CPU temperature = 50.5 21 | Cpu usage = 25.4 22 | RAM usage = 42.3 23 | Swap usage = 58.8 24 | Raspberry pi temperature monitor: raspberrypi 25 | TS = 18-04-20 23:38:27 26 | EP = 1524263907.0 27 | GPU temperature = 50.5'C 28 | CPU temperature = 50.5 29 | Cpu usage = 25.1 30 | RAM usage = 42.7 31 | Swap usage = 58.7 32 | Raspberry pi temperature monitor: raspberrypi 33 | TS = 18-04-20 23:39:02 34 | EP = 1524263943.0 35 | GPU temperature = 50.5'C 36 | CPU temperature = 50.5 37 | Cpu usage = 25.7 38 | RAM usage = 42.7 39 | Swap usage = 58.7 40 | Raspberry pi temperature monitor: raspberrypi 41 | TS = 18-04-21 00:03:38 42 | EP = 1524265419.0 43 | GPU temperature = 55.8'C 44 | CPU temperature = 55.8 45 | Cpu usage = 31.8 46 | RAM usage = 67.0 47 | Swap usage = 66.1 48 | Raspberry pi temperature monitor: raspberrypi 49 | TS = 18-04-21 00:59:54 50 | EP = 1524268794.0 51 | GPU temperature = 53.7'C 52 | CPU temperature = 53.7 53 | Cpu usage = 25.5 54 | RAM usage = 52.7 55 | Swap usage = 86.9 56 | Raspberry pi temperature monitor: raspberrypi 57 | TS = 18-04-21 14:35:29 58 | EP = 1524317730.0 59 | GPU temperature = 58.0'C 60 | CPU temperature = 58.5 61 | Cpu usage = 45.2 62 | RAM usage = 79.9 63 | Swap usage = 75.2 64 | Raspberry pi temperature monitor: raspberrypi 65 | TS = 18-04-21 14:39:51 66 | EP = 1524317992.0 67 | GPU temperature = 56.9'C 68 | CPU temperature = 57.5 69 | Cpu usage = 46.5 70 | RAM usage = 80.5 71 | Swap usage = 90.2 72 | Raspberry pi temperature monitor: raspberrypi 73 | -------------------------------------------------------------------------------- /Documentation/example_msmtprc: -------------------------------------------------------------------------------- 1 | # Set default values for all following accounts. 2 | defaults 3 | auth on 4 | tls on 5 | tls_trust_file /etc/ssl/certs/ca-certificates.crt 6 | logfile ~/.msmtp.log 7 | 8 | # Gmail 9 | account gmail 10 | host smtp.gmail.com 11 | port 587 12 | from your_gmail_user_name.com 13 | user your_gmail_user_name.com 14 | # Your 16 character API password from google gmail. 15 | password aaaa bbbb cccc dddd 16 | 17 | # A freemail service 18 | account freemail 19 | host smtp.freemail.example 20 | from joe_smith@freemail.example 21 | 22 | 23 | # Set a default account 24 | account default : gmail 25 | -------------------------------------------------------------------------------- /Documentation/example_stresslog.csv: -------------------------------------------------------------------------------- 1 | 1,52.1,31.3 2 | 2,65.0,100.0 3 | 3,67.7,100.0 4 | 4,69.8,99.9 5 | 5,70.9,99.9 6 | -------------------------------------------------------------------------------- /Documentation/history/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version control history: 2 | ==================== 3 | 4 | * Version 1.0-1 020717 5 | * First version 6 | 7 | * Version 1.4-5 290817 8 | * Code rewritten complete in python3 (from a mixture of bash and python) 9 | * Graph of realtime CPU data added 10 | 11 | * Version 1.5-6 220318 12 | * Changes to graph modes due to matplotlib upgrade 2.2.2 causing display issues 13 | * -a data mode and -n notify mode added. 14 | 15 | * Version 2.0-1 040418 16 | * number of graph modes increased to 6 from 2 with menu selection , cli option -G removed 17 | * csv option added -s which converts log file to csv for third party use. 18 | * Added CPU,Ram and swap memory usage data 19 | * mail data now sent as attachment 20 | 21 | * Version 2.1-2 210418 22 | * Epoch unix time added as an alternative to time-date stamp. 23 | * Graph modes increased from 6 to 12 24 | * Stress test added with csv file and graph output. 25 | * Note: old logs files from version 1.x.x will no longer work with Version 2 26 | start again with fresh data. 27 | 28 | * version 2.2-3 210420 29 | * For mail mode, replaced ssmpt with msmtp in mail function. 30 | * ssmtp had to be replaced as apparently ssmtp is deprecated in RPi [Buster](https://raspberrypi.stackexchange.com/questions/99968/cannot-send-mail-from-buster), 31 | * and is considered obsolete, users were reporting issues via mail. 32 | * Users using mail mode option will have to install and configure msmtp. 33 | * Details in Readme. 34 | 35 | * version 2.3-4 05-05-2022 36 | * added new graph mode option 12 GPU & CPU% & RAM% versus live time 37 | * Automated creation of config file if missing (on start up new user) 38 | 39 | * version 2.4-5 12-2023 40 | * Changed setup.py so it installs RPi.GPIO with pipx tool 41 | * fixed GPU data not reporting properly on 64 bit systems 42 | 43 | * version 3.0-1 07-2024 44 | * Switched from Rpi.gpio to gpiozero so it works on raspberry pi 5 45 | -------------------------------------------------------------------------------- /Documentation/rpi_tempmon.cfg: -------------------------------------------------------------------------------- 1 | [MAIN] 2 | RPI_AuthUser=example@gmail.com 3 | MAIL_ALERT=1 4 | ALARM_MODE=1 5 | CPU_UPPERLIMIT=20 6 | LED_MODE=1 7 | GPIO_LED=26 -------------------------------------------------------------------------------- /Documentation/screenshots/graphmenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmenu.png -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode1.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode10.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode11.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode12.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode2.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode3.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode4.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode5.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode6.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode7.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode8.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphmode9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphmode9.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/graphstresstest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/graphstresstest.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/main_screen1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/main_screen1.jpg -------------------------------------------------------------------------------- /Documentation/screenshots/nyalarm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/screenshots/nyalarm.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/alldata.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/alldata.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/stresstest.md: -------------------------------------------------------------------------------- 1 | ### CPU temperature stress test on a RPi using **rpi_tempmon.py** program. 2 | 3 | 4 | Specifications Test 1-6: Raspberry pi 3 model B, running Raspbian 10.0 Buster. 5 | Offical Standard Rpi plastic case from Rpi store. 6 | 7 | Specifications Test 7: Raspberry 5, running Raspbian 12.0 Bookworm. 8 | Offical Standard Rpi plastic case + fan from Rpi store. 9 | 10 | The CPU will be stressed and temperature measurements taken and graphed for 11 | a variety of cooling solutions. 12 | ARM CPU temperature measurements by software **[rpi_tempmon.py](https://github.com/gavinlyonsrepo/raspberrypi_tempmon)** stress test option. 13 | Rpi_tempmon uses the sysbench benchmarking tool to stress CPU. 14 | The CPU stress test consists of calculation of prime numbers up to a value of 20000. 15 | All calculations are performed using 64-bit integers. 4 worker threads are created. 16 | CPU temperature and freq are recorded for each test run and graphed. 17 | 5 test iterations per test. Test 2-6 duration: 10 minutes, Test 1 duration 12 minutes due to 18 | thermal throttling slowing down CPU. 19 | 20 | The first graph is created in libreoffice calc by using CSV(comma separted values) 21 | log file data produced by **rpi_tempmon.py** during testing. 22 | The other graphs are generated by rpi_tempmon.py at end of testrun. 23 | 24 | * Test 1 - RPI3 No Heatsink . 25 | * Test 2 - RPI3 Small Heatsink 13 by 15 by 7 (mm). 26 | * Test 3 - RPI3 Medium Heatsink 16 by 22 by 7 (mm). 27 | * Test 4 - RPI3 Large heatsink 35 by 35 by 22 (mm). 28 | * Test 5 - RPI3 Small Heatsink + 60mm fan running at 5V. 29 | * Test 6 - RPI3 Small Heatsink + 80mm fan running at 9V. 30 | * Test 7 - RPI 5 Official RPI basic Kit case + fan and small heatsink. 31 | 32 | ------------- 33 | 34 | 35 | * All Data graph. (test 1-6) 36 | 37 | 38 | ![all data graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/alldata.jpg) 39 | 40 | ------------ 41 | 42 | 43 | * Test 1 - No Heatsink. 44 | 45 | ![test1 graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/test1.jpg) 46 | 47 | * Test 2 - small Heatsink 13 by 15 by 7 (mm). 48 | 49 | ![test2 graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/test2.jpg) 50 | 51 | * Test 3 - Small + medium Heatsink 16 by 22 by 7 (mm). 52 | 53 | ![test3 graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/test3.jpg) 54 | 55 | * Test 4 - Small + medium + large heatsink 35 by 35 by 22 (mm). 56 | 57 | ![test4 graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/test4.jpg) 58 | 59 | * Test 5 - Small Heatsink + 70mm fan running at 5V. 60 | 61 | ![test5 graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/test5.jpg) 62 | 63 | * Test 6 - Small heatsink + 80mm fan running at 9V. 64 | 65 | ![test6 graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/test6.jpg) 66 | 67 | * Test 7 - RPI 5 Official RPI basic Kit case + fan and small heatsink 68 | 69 | ![test7 graph](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/stresstestdata/test7.jpg) 70 | . 71 | -------------------------------------------------------------------------------- /Documentation/stresstestdata/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/test1.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/test2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/test2.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/test3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/test3.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/test4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/test4.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/test5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/test5.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/test6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/test6.jpg -------------------------------------------------------------------------------- /Documentation/stresstestdata/test7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/Documentation/stresstestdata/test7.jpg -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | GNU GENERAL PUBLIC LICENSE 3 | Version 3, 29 June 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The GNU General Public License is a free, copyleft license for 12 | software and other kinds of works. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | the GNU General Public License is intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. We, the Free Software Foundation, use the 19 | GNU General Public License for most of our software; it applies also to 20 | any other work released this way by its authors. You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not 24 | price. Our General Public Licenses are designed to make sure that you 25 | have the freedom to distribute copies of free software (and charge for 26 | them if you wish), that you receive source code or can get it if you 27 | want it, that you can change the software or use pieces of it in new 28 | free programs, and that you know you can do these things. 29 | 30 | To protect your rights, we need to prevent others from denying you 31 | these rights or asking you to surrender the rights. Therefore, you have 32 | certain responsibilities if you distribute copies of the software, or if 33 | you modify it: responsibilities to respect the freedom of others. 34 | 35 | 36 | TERMS AND CONDITIONS 37 | 38 | 0. Definitions. 39 | 40 | "This License" refers to version 3 of the GNU General Public License. 41 | 42 | "Copyright" also means copyright-like laws that apply to other kinds of 43 | works, such as semiconductor masks. 44 | 45 | "The Program" refers to any copyrightable work licensed under this 46 | License. Each licensee is addressed as "you". "Licensees" and 47 | "recipients" may be individuals or organizations. 48 | 49 | To "modify" a work means to copy from or adapt all or part of the work 50 | in a fashion requiring copyright permission, other than the making of an 51 | exact copy. The resulting work is called a "modified version" of the 52 | earlier work or a work "based on" the earlier work. 53 | 54 | A "covered work" means either the unmodified Program or a work based 55 | on the Program. 56 | 57 | To "propagate" a work means to do anything with it that, without 58 | permission, would make you directly or secondarily liable for 59 | infringement under applicable copyright law, except executing it on a 60 | computer or modifying a private copy. Propagation includes copying, 61 | distribution (with or without modification), making available to the 62 | public, and in some countries other activities as well. 63 | 64 | To "convey" a work means any kind of propagation that enables other 65 | parties to make or receive copies. Mere interaction with a user through 66 | a computer network, with no transfer of a copy, is not conveying. 67 | 68 | An interactive user interface displays "Appropriate Legal Notices" 69 | to the extent that it includes a convenient and prominently visible 70 | feature that (1) displays an appropriate copyright notice, and (2) 71 | tells the user that there is no warranty for the work (except to the 72 | extent that warranties are provided), that licensees may convey the 73 | work under this License, and how to view a copy of this License. If 74 | the interface presents a list of user commands or options, such as a 75 | menu, a prominent item in the list meets this criterion. 76 | 77 | 1. Source Code. 78 | 79 | The "source code" for a work means the preferred form of the work 80 | for making modifications to it. "Object code" means any non-source 81 | form of a work. 82 | 83 | A "Standard Interface" means an interface that either is an official 84 | standard defined by a recognized standards body, or, in the case of 85 | interfaces specified for a particular programming language, one that 86 | is widely used among developers working in that language. 87 | 88 | The "System Libraries" of an executable work include anything, other 89 | than the work as a whole, that (a) is included in the normal form of 90 | packaging a Major Component, but which is not part of that Major 91 | Component, and (b) serves only to enable use of the work with that 92 | Major Component, or to implement a Standard Interface for which an 93 | implementation is available to the public in source code form. A 94 | "Major Component", in this context, means a major essential component 95 | (kernel, window system, and so on) of the specific operating system 96 | (if any) on which the executable work runs, or a compiler used to 97 | produce the work, or an object code interpreter used to run it. 98 | 99 | The Corresponding Source need not include anything that users 100 | can regenerate automatically from other parts of the Corresponding 101 | Source. 102 | 103 | The Corresponding Source for a work in source code form is that 104 | same work. 105 | 106 | 2. Basic Permissions. 107 | 108 | All rights granted under this License are granted for the term of 109 | copyright on the Program, and are irrevocable provided the stated 110 | conditions are met. This License explicitly affirms your unlimited 111 | permission to run the unmodified Program. The output from running a 112 | covered work is covered by this License only if the output, given its 113 | content, constitutes a covered work. This License acknowledges your 114 | rights of fair use or other equivalent, as provided by copyright law. 115 | 116 | You may make, run and propagate covered works that you do not 117 | convey, without conditions so long as your license otherwise remains 118 | in force. You may convey covered works to others for the sole purpose 119 | of having them make modifications exclusively for you, or provide you 120 | with facilities for running those works, provided that you comply with 121 | the terms of this License in conveying all material for which you do 122 | not control copyright. Those thus making or running the covered works 123 | for you must do so exclusively on your behalf, under your direction 124 | and control, on terms that prohibit them from making any copies of 125 | your copyrighted material outside their relationship with you. 126 | 127 | Conveying under any other circumstances is permitted solely under 128 | the conditions stated below. Sublicensing is not allowed; section 10 129 | makes it unnecessary. 130 | 131 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 132 | 133 | No covered work shall be deemed part of an effective technological 134 | measure under any applicable law fulfilling obligations under article 135 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 136 | similar laws prohibiting or restricting circumvention of such 137 | measures. 138 | 139 | 4. Conveying Verbatim Copies. 140 | 141 | You may convey verbatim copies of the Program's source code as you 142 | receive it, in any medium, provided that you conspicuously and 143 | appropriately publish on each copy an appropriate copyright notice; 144 | keep intact all notices stating that this License and any 145 | non-permissive terms added in accord with section 7 apply to the code; 146 | keep intact all notices of the absence of any warranty; and give all 147 | recipients a copy of this License along with the Program. 148 | 149 | You may charge any price or no price for each copy that you convey, 150 | and you may offer support or warranty protection for a fee. 151 | 152 | 5. Conveying Modified Source Versions. 153 | 154 | You may convey a work based on the Program, or the modifications to 155 | produce it from the Program, in the form of source code under the 156 | terms of section 4, provided that you also meet all of these conditions: 157 | 158 | a) The work must carry prominent notices stating that you modified 159 | it, and giving a relevant date. 160 | 161 | b) The work must carry prominent notices stating that it is 162 | released under this License and any conditions added under section 163 | 7. This requirement modifies the requirement in section 4 to 164 | "keep intact all notices". 165 | 166 | c) You must license the entire work, as a whole, under this 167 | License to anyone who comes into possession of a copy. This 168 | License will therefore apply, along with any applicable section 7 169 | additional terms, to the whole of the work, and all its parts, 170 | regardless of how they are packaged. This License gives no 171 | permission to license the work in any other way, but it does not 172 | invalidate such permission if you have separately received it. 173 | 174 | d) If the work has interactive user interfaces, each must display 175 | Appropriate Legal Notices; however, if the Program has interactive 176 | interfaces that do not display Appropriate Legal Notices, your 177 | work need not make them do so. 178 | 179 | 6. Conveying Non-Source Forms. 180 | 181 | You may convey a covered work in object code form under the terms 182 | of sections 4 and 5, provided that you also convey the 183 | machine-readable Corresponding Source under the terms of this License, 184 | in one of these ways: 185 | 186 | a) Convey the object code in, or embodied in, a physical product 187 | (including a physical distribution medium), accompanied by the 188 | Corresponding Source fixed on a durable physical medium 189 | customarily used for software interchange. 190 | 191 | b) Convey the object code in, or embodied in, a physical product 192 | (including a physical distribution medium), accompanied by a 193 | written offer, valid for at least three years and valid for as 194 | long as you offer spare parts or customer support for that product 195 | model, to give anyone who possesses the object code either (1) a 196 | copy of the Corresponding Source for all the software in the 197 | product that is covered by this License, on a durable physical 198 | medium customarily used for software interchange, for a price no 199 | more than your reasonable cost of physically performing this 200 | conveying of source, or (2) access to copy the 201 | Corresponding Source from a network server at no charge. 202 | 203 | c) Convey individual copies of the object code with a copy of the 204 | written offer to provide the Corresponding Source. This 205 | alternative is allowed only occasionally and noncommercially, and 206 | only if you received the object code with such an offer, in accord 207 | with subsection 6b. 208 | 209 | d) Convey the object code by offering access from a designated 210 | place (gratis or for a charge), and offer equivalent access to the 211 | Corresponding Source in the same way through the same place at no 212 | further charge. You need not require recipients to copy the 213 | Corresponding Source along with the object code. If the place to 214 | copy the object code is a network server, the Corresponding Source 215 | may be on a different server (operated by you or a third party) 216 | that supports equivalent copying facilities, provided you maintain 217 | clear directions next to the object code saying where to find the 218 | Corresponding Source. Regardless of what server hosts the 219 | Corresponding Source, you remain obligated to ensure that it is 220 | available for as long as needed to satisfy these requirements. 221 | 222 | e) Convey the object code using peer-to-peer transmission, provided 223 | you inform other peers where the object code and Corresponding 224 | Source of the work are being offered to the general public at no 225 | charge under subsection 6d. 226 | 227 | A "User Product" is either (1) a "consumer product", which means any 228 | tangible personal property which is normally used for personal, family, 229 | or household purposes, or (2) anything designed or sold for incorporation 230 | into a dwelling. In determining whether a product is a consumer product, 231 | doubtful cases shall be resolved in favor of coverage. For a particular 232 | product received by a particular user, "normally used" refers to a 233 | typical or common use of that class of product, regardless of the status 234 | of the particular user or of the way in which the particular user 235 | actually uses, or expects or is expected to use, the product. A product 236 | is a consumer product regardless of whether the product has substantial 237 | commercial, industrial or non-consumer uses, unless such uses represent 238 | the only significant mode of use of the product. 239 | 240 | "Installation Information" for a User Product means any methods, 241 | procedures, authorization keys, or other information required to install 242 | and execute modified versions of a covered work in that User Product from 243 | a modified version of its Corresponding Source. The information must 244 | suffice to ensure that the continued functioning of the modified object 245 | code is in no case prevented or interfered with solely because 246 | modification has been made. 247 | 248 | If you convey an object code work under this section in, or with, or 249 | specifically for use in, a User Product, and the conveying occurs as 250 | part of a transaction in which the right of possession and use of the 251 | User Product is transferred to the recipient in perpetuity or for a 252 | fixed term (regardless of how the transaction is characterized), the 253 | Corresponding Source conveyed under this section must be accompanied 254 | by the Installation Information. But this requirement does not apply 255 | if neither you nor any third party retains the ability to install 256 | modified object code on the User Product (for example, the work has 257 | been installed in ROM). 258 | 259 | Corresponding Source conveyed, and Installation Information provided, 260 | in accord with this section must be in a format that is publicly 261 | documented (and with an implementation available to the public in 262 | source code form), and must require no special password or key for 263 | unpacking, reading or copying. 264 | 265 | 7. Additional Terms. 266 | 267 | "Additional permissions" are terms that supplement the terms of this 268 | License by making exceptions from one or more of its conditions. 269 | Additional permissions that are applicable to the entire Program shall 270 | be treated as though they were included in this License, to the extent 271 | that they are valid under applicable law. If additional permissions 272 | apply only to part of the Program, that part may be used separately 273 | under those permissions, but the entire Program remains governed by 274 | this License without regard to the additional permissions. 275 | 276 | Notwithstanding any other provision of this License, for material you 277 | add to a covered work, you may (if authorized by the copyright holders of 278 | that material) supplement the terms of this License with terms: 279 | 280 | a) Disclaiming warranty or limiting liability differently from the 281 | terms of sections 15 and 16 of this License; or 282 | 283 | b) Requiring preservation of specified reasonable legal notices or 284 | author attributions in that material or in the Appropriate Legal 285 | Notices displayed by works containing it; or 286 | 287 | c) Prohibiting misrepresentation of the origin of that material, or 288 | requiring that modified versions of such material be marked in 289 | reasonable ways as different from the original version; or 290 | 291 | d) Limiting the use for publicity purposes of names of licensors or 292 | authors of the material; or 293 | 294 | e) Declining to grant rights under trademark law for use of some 295 | trade names, trademarks, or service marks; or 296 | 297 | f) Requiring indemnification of licensors and authors of that 298 | material by anyone who conveys the material (or modified versions of 299 | it) with contractual assumptions of liability to the recipient, for 300 | any liability that these contractual assumptions directly impose on 301 | those licensors and authors. 302 | 303 | All other non-permissive additional terms are considered "further 304 | restrictions" within the meaning of section 10. If the Program as you 305 | received it, or any part of it, contains a notice stating that it is 306 | governed by this License along with a term that is a further 307 | restriction, you may remove that term. If a license document contains 308 | a further restriction but permits relicensing or conveying under this 309 | License, you may add to a covered work material governed by the terms 310 | of that license document, provided that the further restriction does 311 | not survive such relicensing or conveying. 312 | 313 | Additional terms, permissive or non-permissive, may be stated in the 314 | form of a separately written license, or stated as exceptions; 315 | the above requirements apply either way. 316 | 317 | 8. Termination. 318 | 319 | You may not propagate or modify a covered work except as expressly 320 | provided under this License. Any attempt otherwise to propagate or 321 | modify it is void, and will automatically terminate your rights under 322 | this License (including any patent licenses granted under the third 323 | paragraph of section 11). 324 | 325 | 326 | Moreover, your license from a particular copyright holder is 327 | reinstated permanently if the copyright holder notifies you of the 328 | violation by some reasonable means, this is the first time you have 329 | received notice of violation of this License (for any work) from that 330 | copyright holder, and you cure the violation prior to 30 days after 331 | your receipt of the notice. 332 | 333 | Termination of your rights under this section does not terminate the 334 | licenses of parties who have received copies or rights from you under 335 | this License. If your rights have been terminated and not permanently 336 | reinstated, you do not qualify to receive new licenses for the same 337 | material under section 10. 338 | 339 | 9. Acceptance Not Required for Having Copies. 340 | 341 | You are not required to accept this License in order to receive or 342 | run a copy of the Program. Ancillary propagation of a covered work 343 | occurring solely as a consequence of using peer-to-peer transmission 344 | to receive a copy likewise does not require acceptance. However, 345 | nothing other than this License grants you permission to propagate or 346 | modify any covered work. These actions infringe copyright if you do 347 | not accept this License. Therefore, by modifying or propagating a 348 | covered work, you indicate your acceptance of this License to do so. 349 | 350 | 10. Automatic Licensing of Downstream Recipients. 351 | 352 | Each time you convey a covered work, the recipient automatically 353 | receives a license from the original licensors, to run, modify and 354 | propagate that work, subject to this License. You are not responsible 355 | for enforcing compliance by third parties with this License. 356 | 357 | An "entity transaction" is a transaction transferring control of an 358 | organization, or substantially all assets of one, or subdividing an 359 | organization, or merging organizations. If propagation of a covered 360 | work results from an entity transaction, each party to that 361 | transaction who receives a copy of the work also receives whatever 362 | licenses to the work the party's predecessor in interest had or could 363 | give under the previous paragraph, plus a right to possession of the 364 | Corresponding Source of the work from the predecessor in interest, if 365 | the predecessor has it or can get it with reasonable efforts. 366 | 367 | 11. Patents. 368 | 369 | A "contributor" is a copyright holder who authorizes use under this 370 | License of the Program or a work on which the Program is based. The 371 | work thus licensed is called the contributor's "contributor version". 372 | 373 | A contributor's "essential patent claims" are all patent claims 374 | owned or controlled by the contributor, whether already acquired or 375 | hereafter acquired, that would be infringed by some manner, permitted 376 | by this License, of making, using, or selling its contributor version, 377 | but do not include claims that would be infringed only as a 378 | consequence of further modification of the contributor version. For 379 | purposes of this definition, "control" includes the right to grant 380 | patent sublicenses in a manner consistent with the requirements of 381 | this License. 382 | 383 | Each contributor grants you a non-exclusive, worldwide, royalty-free 384 | patent license under the contributor's essential patent claims, to 385 | make, use, sell, offer for sale, import and otherwise run, modify and 386 | propagate the contents of its contributor version. 387 | 388 | In the following three paragraphs, a "patent license" is any express 389 | agreement or commitment, however denominated, not to enforce a patent 390 | (such as an express permission to practice a patent or covenant not to 391 | sue for patent infringement). To "grant" such a patent license to a 392 | party means to make such an agreement or commitment not to enforce a 393 | patent against the party. 394 | 395 | If, pursuant to or in connection with a single transaction or 396 | arrangement, you convey, or propagate by procuring conveyance of, a 397 | covered work, and grant a patent license to some of the parties 398 | receiving the covered work authorizing them to use, propagate, modify 399 | or convey a specific copy of the covered work, then the patent license 400 | you grant is automatically extended to all recipients of the covered 401 | work and works based on it. 402 | 403 | A patent license is "discriminatory" if it does not include within 404 | the scope of its coverage, prohibits the exercise of, or is 405 | conditioned on the non-exercise of one or more of the rights that are 406 | specifically granted under this License. You may not convey a covered 407 | work if you are a party to an arrangement with a third party that is 408 | in the business of distributing software, under which you make payment 409 | to the third party based on the extent of your activity of conveying 410 | the work, and under which the third party grants, to any of the 411 | parties who would receive the covered work from you, a discriminatory 412 | patent license (a) in connection with copies of the covered work 413 | conveyed by you (or copies made from those copies), or (b) primarily 414 | for and in connection with specific products or compilations that 415 | contain the covered work, unless you entered into that arrangement, 416 | or that patent license was granted, prior to 28 March 2007. 417 | 418 | 12. No Surrender of Others' Freedom. 419 | 420 | If conditions are imposed on you (whether by court order, agreement or 421 | otherwise) that contradict the conditions of this License, they do not 422 | excuse you from the conditions of this License. If you cannot convey a 423 | covered work so as to satisfy simultaneously your obligations under this 424 | License and any other pertinent obligations, then as a consequence you may 425 | not convey it at all. For example, if you agree to terms that obligate you 426 | to collect a royalty for further conveying from those to whom you convey 427 | the Program, the only way you could satisfy both those terms and this 428 | License would be to refrain entirely from conveying the Program. 429 | 430 | 13. Use with the GNU Affero General Public License. 431 | 432 | Notwithstanding any other provision of this License, you have 433 | permission to link or combine any covered work with a work licensed 434 | under version 3 of the GNU Affero General Public License into a single 435 | combined work, and to convey the resulting work. The terms of this 436 | License will continue to apply to the part which is the covered work, 437 | but the special requirements of the GNU Affero General Public License, 438 | section 13, concerning interaction through a network will apply to the 439 | combination as such. 440 | 441 | 14. Revised Versions of this License. 442 | 443 | The Free Software Foundation may publish revised and/or new versions of 444 | the GNU General Public License from time to time. Such new versions will 445 | be similar in spirit to the present version, but may differ in detail to 446 | address new problems or concerns. 447 | 448 | Each version is given a distinguishing version number. If the 449 | Program specifies that a certain numbered version of the GNU General 450 | Public License "or any later version" applies to it, you have the 451 | option of following the terms and conditions either of that numbered 452 | version or of any later version published by the Free Software 453 | Foundation. If the Program does not specify a version number of the 454 | GNU General Public License, you may choose any version ever published 455 | by the Free Software Foundation. 456 | 457 | Later license versions may give you additional or different 458 | permissions. However, no additional obligations are imposed on any 459 | author or copyright holder as a result of your choosing to follow a 460 | later version. 461 | 462 | 15. Disclaimer of Warranty. 463 | 464 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 465 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 466 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 467 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 468 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 469 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 470 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 471 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 472 | 473 | 16. Limitation of Liability. 474 | 475 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 476 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 477 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 478 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 479 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 480 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 481 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 482 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 483 | SUCH DAMAGES. 484 | 485 | 17. Interpretation of Sections 15 and 16. 486 | 487 | If the disclaimer of warranty and limitation of liability provided 488 | above cannot be given local legal effect according to their terms, 489 | reviewing courts shall apply local law that most closely approximates 490 | an absolute waiver of all civil liability in connection with the 491 | Program, unless a warranty or assumption of liability accompanies a 492 | copy of the Program in return for a fee. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/paypalme/whitelight976) 2 | 3 | ## Overview 4 | 5 | * Name: rpi_tempmon 6 | * Title : Display the ARM CPU and GPU temperature of Raspberry Pi 7 | * Description: 8 | 9 | This python program will display the ARM CPU and 10 | GPU temperature of a Raspberry Pi 11 | features include command line display, GPIO (LED) output, logging, alarm limit, 12 | graphing, desktop notification, stress tests and e-mailing options. 13 | It is run in terminal and uses matplotlib 14 | plots for graph modes. 15 | 16 | * Author: Gavin Lyons 17 | * URL: https://github.com/gavinlyonsrepo/raspeberrypi_tempmon 18 | * Tested on Toolchains: 19 | 1. RPI 3 model B. Raspbian 10 Buster, 32 bit. Python 3.7.3. 20 | 2. RPI 3 model B. Raspbian 12 Bookworm, 64 bit. Python 3.11.2. 21 | 3. RPI 5 Raspbian 12 Bookworm, 64 bit. Python 3.11.2. 22 | 23 | ## Table of contents 24 | 25 | 26 | * [Overview](#overview) 27 | * [Table of contents](#table-of-contents) 28 | * [Installation](#installation) 29 | * [Usage](#usage) 30 | * [Files and setup](#files-and-setup) 31 | * [Output](#output) 32 | * [Dependencies](#Dependencies) 33 | * [Features](#features) 34 | 35 | 36 | ## Installation 37 | 38 | Latest version 3.0 (07-2024) 39 | 40 | **PyPi & pip , pipx** 41 | 42 | The program is present in python package index, Pypi. 43 | Install using *pip* or *pipx* to the location or environment of your choice. 44 | Package name = rpi-tempmon.py 45 | 46 | **Manually install from github** 47 | 48 | The package is also archived on github and can be manually download and installed 49 | via python and setup.py 50 | 51 | ```sh 52 | curl -sL https://github.com/gavinlyonsrepo/raspberrypi_tempmon/archive/3.0.tar.gz | tar xz 53 | cd raspberrypi_tempmon-3.0 54 | python3 setup.py build 55 | python3 setup.py install --user 56 | ``` 57 | 58 | ## Usage 59 | 60 | Program is a python 3 package. Run in a terminal 61 | by typing rpi_tempmon.py or python3 rpi_tempmon.py: 62 | 63 | rpi_tempmon.py -[options][arguments] 64 | 65 | Options list *(Note: Options are stand alone, not designed to be combined)*: 66 | 67 | | Option | Description | 68 | | --------------- | --------------- | 69 | | -h | Print help information and exit | 70 | | -v | Print version information and exit | 71 | | -c | Enters continuous mode, optional number of seconds as a argument eg (-c 5)| 72 | | -l | Creates and/or appends to log file at output folder | 73 | | -L | Creates a sub-folder at output folder with date/time stamp and puts a log file in it | 74 | | -m | Sends the log file to an email account | 75 | | -g | graph mode, Generate a menu where 12 types of graphs can be selected | 76 | | -a | parse log file and produces a data report in terminal | 77 | | -n | send notifications to desktop, Number argument to define behaviour | 78 | | -s | CSV mode , parses log.txt and converts it to log.csv, CSV file | 79 | | -ST | Stress test CPU and measures temperature output to graph and csv file , optional number of test runs as a argument eg (-ST 5)| 80 | 81 | ## Files and setup 82 | 83 | rpi_tempmon files needed are listed below: 84 | 85 | | File Path | Description | 86 | | ------ | ------ | 87 | | rpi_tempmon.py | The main python script | 88 | | RpiTempmonWork.py| python module containing various utility functions used by main | 89 | | RpiTempmonGraph.py | python module dealing with graph output by matplotlib | 90 | | $HOME/.config/rpi_tempmon/rpi_tempmon.cfg | config file | 91 | | README.md | help file | 92 | 93 | 94 | Config file: The config file with dummy values is created if missing. 95 | A dummy config file is available in documentation folder at repository 96 | , used for -m mail option, GPIO/LED feature and the alarm function. 97 | 98 | The setting "RPI_AuthUser" the is email address 99 | destination of data from -m option. 100 | 101 | Mail_Alert(one: mail alert enabled , zero: off) 102 | 103 | The other settings are ALARM_MODE which should be set to one or zero(one: alarm on, zero: off) 104 | 105 | CPU_UPPERLIMIT is the temperature limit of CPU in Centigrade, should be a positive integer. 106 | If alarm mode is on when CPU temperature goes above this limit, the alarm function will activate. 107 | 108 | LED_MODE which should be set to one or zero(one: LED mode on, zero: off) if on 109 | an GPIO pin will switch on during an alarm state in continuous and normal mode. 110 | The RPI GPIO pin as defined by GPIO_LED number. You can connect an LED or another 111 | electronic component to this pin. 112 | 113 | Make sure to include the [MAIN] header and all settings just as below or 114 | copy from the example file. 115 | 116 | Settings: 117 | 118 | [MAIN] 119 | > 120 | >RPI_AuthUser=examplemail@gmail.com 121 | > 122 | >MAIL_ALERT = 1 123 | > 124 | >ALARM_MODE = 1 125 | > 126 | >CPU_UPPERLIMIT = 60 127 | > 128 | >LED_MODE = 0 129 | > 130 | >GPIO_LED = 26 131 | > 132 | 133 | Screenshots, example config/log files are also available in documentation. 134 | 135 | ## Output 136 | 137 | The output folder for log files is currently fixed at: 138 | 139 | ```sh 140 | $HOME/.cache/rpi_tempmon/ 141 | ``` 142 | 143 | ## Dependencies 144 | 145 | 146 | 1. simple MSMTP - Version: 1.6.6-1- Program which delivers email from a computer to a mailhost. 147 | [MSMTP help](https://wiki.archlinux.org/index.php/Msmtp) 148 | Optional, **ONLY** used by mail functions. 149 | light SMTP client with support for server profiles 150 | client that can be used to send mails . 151 | (SMTP server), needed only for -m mail option. 152 | Also needs to install another few small dependencies (mpack etc) in order to send attachment. 153 | See mail mode in features for setup. 154 | 155 | ```sh 156 | $ sudo apt install msmtp msmtp-mta mailutils mpack 157 | 158 | ``` 159 | 160 | 2. sysbench - Version 0.4.12-1.1 - benchmarking tool. 161 | [sysbench](https://manpages.debian.org/testing/sysbench/sysbench.1.en.html) 162 | Optional, **ONLY** used by stress test option -ST. 163 | 164 | ```sh 165 | $ sudo apt install sysbench. 166 | ``` 167 | 168 | 3. libnotify-bin - Version 0.7.6-2 - sends desktop notifications to a notification daemon. 169 | [libnotify](http://manpages.ubuntu.com/manpages/artful/man1/notify-send.1.html) 170 | Optional, This is **ONLY** needed if using the -n option which uses the notify-send command. 171 | 172 | ```sh 173 | $ sudo apt install libnotify-bin 174 | ``` 175 | 176 | 4. matplotlib - Version: 2.2.2 - Python plotting package. 177 | [matplotlib help](https://matplotlib.org/) 178 | The graph modules requires python module *matplotlib* to draw graphs, 179 | This is for -g and -ST options. 180 | Installed by rpi_tempmon setup.py during installation in theory. 181 | 182 | 5. psutil - Version (2.1.1) - Library for retrieving info on PC. 183 | [psutil](https://psutil.readthedocs.io/en/latest/) 184 | Used to retrieve some CPU and memory information. 185 | Installed by setup.py during installation. 186 | 187 | 6. gpiozero - version(2.0) - This package controls the GPIO on a Raspberry Pi. 188 | [gpiozero](https://github.com/gpiozero/gpiozero) 189 | GPIO Zero is installed by default in the Raspberry Pi OS desktop image, note before rpi_tempmon version 3.0 , 190 | rpi_tempmon used rpi.gpio. 191 | 192 | 193 | ## Features 194 | 195 | For a raspberry pi the official operating temperature limit is 85°C, 196 | and as a result the Raspberry Pi should start to thermally throttle 197 | performance around 82°C. The GPU and CPU are closely correlated 198 | to within a degree usually. 199 | 200 | The program calculates the ARM CPU and GPU temperature of 201 | a Raspberry Pi and outputs them in Centigrade together with 202 | datetime stamp. Also shows CPU, RAM memory usage . 203 | 204 | The program has ten features 205 | 1. Normal mode - output to screen with optional GPIO output, prompt for update. 206 | 2. Continuous mode - output to screen with optional GPIO output, updates based on timer. 207 | 3. Logfile mode - output to single logfile(also mail mode if mail alert mode on and triggered). 208 | 4. Logfolder mode - output to multiple logfile in separate folders. 209 | 5. Mail mode - output to email. 210 | 6. Graph mode - Displays a graph of logfile created in mode 3 211 | 7. CSV mode - parses log.txt and converts it to log.csv for external use 212 | 8. Data mode - parses log file and produces a small report. 213 | 9. Notify mode - send notifications to desktop, Number argument to define behaviour 214 | 10. Stress test mode - Stresses the CPU with math and records results in csv file and graph. 215 | 216 | **1. normal mode** 217 | 218 | Normal mode is run by running program with no command line arguments. 219 | In normal mode output, Data is sent to the terminal with prompt to repeat or quit. 220 | The GPIO pin in config file will be turned on 221 | and Data in red is displayed in screen for an Alarm state, if setup in config file. 222 | 223 | **2. Continuous mode** 224 | 225 | Same as normal mode except in continuous mode. The program enters a delay between scan. 226 | This delay is set by positive integer argument placed after -c. 227 | For example "-c 30" will wait 30 seconds between scans. 228 | Data is sent to terminal screen. 229 | The GPIO pin in config file will be turned on 230 | and Data in red is displayed in screen for an Alarm state, if setup in config file. 231 | 232 | ![ScreenShot cont mode](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/screenshots/main_screen1.jpg) 233 | 234 | **3. & 4. Log modes** 235 | 236 | In logfile mode the data is appended into a file log.txt at output folder. 237 | With optional mail setup if alarm mode setup. For mode 3 an email 238 | is sent using mode 5 function, 239 | but with warning in title. 240 | 241 | Sample output of logfile: 242 | 243 | ```sh 244 | TS = 18-04-22 14:19:42 245 | EP = 1524403183.0 246 | GPU temperature = 48.3'C 247 | CPU temperature = 48.3 248 | Cpu usage = 40.4 249 | RAM usage = 45.0 250 | Swap usage = 98.6 251 | Raspberry pi temperature monitor: raspberrypi 252 | ``` 253 | 254 | The log file is appended with "Warning:" text message if alarm state entered. 255 | 256 | In logfolder mode in the output folder, a new sub-folder is created each 257 | time it is ran and a new log-file put in here. The sub-folder has following syntax 258 | 1250-02Jul17_RPIT HHMM-DDMMMYY_XXXX. 259 | This folder mode does not work with mail or graph mode at present. 260 | 261 | Logging modes are designed to be used with UNIX automation like crontab. 262 | For example this crontab entry will run logfile mode once an hour, 263 | Note: The path to executable may differ on each users system. 264 | 265 | ```sh 266 | 0 * * * * /usr/local/bin/rpi_tempmon.py -l >/dev/null 2>&1 267 | ``` 268 | 269 | **5. Mail mode** 270 | 271 | In mail mode an email is sent using msmtp. 272 | The mail contains the data from logfile mode only, it will NOT work with 273 | sub-folders from logfolder mode. 274 | 275 | msmtp is used rather than than python inbuilt smtplib module 276 | as this program was originally a bash program and this a legacy of that, 277 | also allows access to msmtp config file for greater portability 278 | and security. The program originally used ssmtp but this is now obsolete 279 | in latest raspbian software. 280 | 281 | In order to get mail mode working you must complete 3-4 steps. 282 | 283 | 1. Set settings in rpi_tempmon config file, see Files and setup section. 284 | This file allows for user to set an email address without access to msmtp 285 | config file which should be set up just for root account. 286 | 2. Install msmtp and dependencies as per installation section 287 | 3. Configure msmtp configuration file [MSMTP help](https://wiki.archlinux.org/index.php/Msmtp) 288 | A working example msmtprc config file for gmail is in documentation folder, "example_msmtprc". 289 | 4. Optional, It is also possible you may need to configure your email account to accept msmtp mails 290 | this was the case for gmail and ssmtp. In 2024 in order to get gmail working with ssmtp mail ,you must first set up 291 | 2 step verification on your google account then request a App password for an App from 'apppasswords' section. Google will then generate 292 | a 16 character API password, 293 | like 'aaaa bbbb cccc dddd' , use this in the password field of your msmtp configuration file. 294 | 295 | **6. graph mode** 296 | 297 | In graph mode, the program using matplotlib (plotting library) 298 | creates a plot of various data versus time. 299 | The logfile.txt created by logfile mode 3 is used for data for graph 1-8. 300 | graphs 1-4 use time-date stamp as yaxis value 301 | graphs 5-8 use Unix Epoch stamp as yaxis value, this is better for irregular data 302 | points across multiple dates. 303 | The graphs 9-12 are live plots sampled every two seconds for 150 points, 304 | so five minutes of live data. 305 | 306 | ![graph menu](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/screenshots/graphmenu.png) 307 | 308 | Sample graph screenshot, screenshots of all others are in [screenshot folder of repo](screenshots/). 309 | 310 | ![graph mode 6](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/screenshots/graphmode2.jpg) 311 | 312 | ![graph mode 12](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/screenshots/graphmode12.jpg) 313 | 314 | **7. CSV(comma-separated values) convert** 315 | 316 | New in Version 2.0. Run with -s on the CLI. 317 | Parses log.txt and creates log.csv. 318 | This csv file can be then used by user in another app. 319 | A comma-separated values (CSV) file is a delimited text file 320 | that uses a comma to separate values. This file can then be loaded into libreoffice calc. 321 | for further processing, for example. 322 | 323 | sample output = time-data, CPU temp, GPU temp, CPU usage , RAM usage , swap usage 324 | 325 | ```sh 326 | 18-04-04 09:46:51,61.5,60.7,25.8,33.9,11.6 327 | ``` 328 | 329 | **8. data mode** 330 | 331 | Parses log file created by logfile mode 3 and produces a data report on console. 332 | 333 | **9. notify mode** 334 | 335 | Send notifications to desktop, Numbered argument to define behaviour 336 | After installing notify-send, Additional packages or steps 337 | **may** be required to get notify-send working, 338 | depending on system. for example 339 | [Jessie](https://raspberrypi.stackexchange.com/questions/75299/how-to-get-notify-send-working-on-raspbian-jessie) 340 | 341 | 342 | * -n 2 = argument 2 = If run always display CPU temperature , no warning. 343 | * -n 3 = argument 3 = If run only display if CPU temperature exceeds limit 344 | 345 | ![notify mode](https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/master/Documentation/screenshots/nyalarm.jpg) 346 | 347 | 348 | **10. Stress test mode** 349 | 350 | This mode uses the sysbench benchmarking tool. 351 | The test request consists in calculation of prime numbers up to a value of 20000. 352 | All calculations are performed using 64-bit integers. 4 worker threads are created. 353 | The number of test runs is passed on command line as integer max 50 min 2. 354 | CPU temperature and freq are recorded for each test run and are outputed to a csv file, 355 | called stresslog.csv . sample output = test run num, CPU temp, CPU usage. 356 | 357 | ```sh 358 | 1,56.9,27.1 359 | 2,61.3,99.7 360 | ``` 361 | 362 | At the end of test, there is an option to display results in a graph. 363 | Stress data carried out by rpi_tempmon can be found in repo [here](Documentation/stresstestdata/stresstest.md) 364 | -------------------------------------------------------------------------------- /rpiTempMod/RpiTempmonGraph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Module imported into rpi_tempmon contains 4 | containing a class with methods to display graphs from matlib 5 | Class : MatplotGraph 6 | """ 7 | import os 8 | import re 9 | import dateutil 10 | import matplotlib as mpl 11 | mpl.use('tkagg') 12 | import matplotlib.pyplot as plt 13 | import matplotlib.dates as md 14 | 15 | # My modules 16 | from . import RpiTempmonWork as Work 17 | # Local testing, comment out next line# for production 18 | #import RpiTempmonWork as Work 19 | 20 | class MatplotGraph(object): 21 | """ 22 | class with 6 methods to display graphs with matplotlib 23 | Methods: 24 | (1) init: pass 25 | (2) graph_log_data: Define data to draw graphs based on user choice 26 | (3) display_menu: Method to display a menu for 27 | user to select graph type 28 | (4) draw_graph: Draw a graph from logdata 29 | (5) graph_live_data: Draw live data graphs 30 | (6) Plot_now: Called from method graph_live_data to draw graph 31 | """ 32 | 33 | def __init__(self, name): 34 | """init class with name and define self.selection variable""" 35 | self.name = name 36 | self.selection = 0 37 | 38 | def graph_log_data(self, destlog): 39 | """define data to draw graphs from logdata""" 40 | # display menu return user selection 41 | self.display_menu() 42 | 43 | # get data from log file function data_func 44 | timelist, unixlist, cpulist, gpulist, cpu_uselist, ramlist, swaplist\ 45 | = Work.data_func(destlog, True) 46 | if re.match('[1-4]', self.selection): 47 | yaxislist = timelist 48 | elif re.match('[5-8]', self.selection): 49 | yaxislist = unixlist 50 | 51 | if (self.selection == '1' or self.selection == '5'): 52 | plotlabel1 = 'CPU' 53 | plotlabel2 = 'GPU' 54 | ylabel = 'Temperature (degrees)' 55 | title = 'CPU & GPU temperature of RPi' 56 | self.draw_graph(yaxislist, cpulist, gpulist, 57 | plotlabel1, plotlabel2, ylabel, title) 58 | 59 | elif (self.selection == '2' or self.selection == '6'): 60 | plotlabel1 = 'CPU temp' 61 | plotlabel2 = 'CPU usage' 62 | ylabel = 'CPU usage(%) / Temperature (degrees)' 63 | title = 'CPU temperature and usage RPi' 64 | self.draw_graph(yaxislist, cpulist, cpu_uselist, 65 | plotlabel1, plotlabel2, ylabel, title) 66 | elif (self.selection == '3' or self.selection == '7'): 67 | plotlabel1 = 'RAM' 68 | plotlabel2 = 'SWAP' 69 | ylabel = 'Memory (% used)' 70 | title = 'RAM & Swap memory usage of RPi' 71 | self.draw_graph(yaxislist, ramlist, swaplist, 72 | plotlabel1, plotlabel2, ylabel, title) 73 | elif (self.selection == '4' or self.selection == '8'): 74 | plotlabel1 = 'CPU' 75 | ylabel = 'CPU (% used)' 76 | title = 'CPU usage of RPi' 77 | self.draw_graph(yaxislist, cpu_uselist, False, 78 | plotlabel1, False, ylabel, title) 79 | else: 80 | Work.msg_func("red", "Error: graph_log_data: Bad selection value") 81 | return 82 | 83 | def display_menu(self): 84 | """ method to display a menu for 85 | user to select graph""" 86 | os.system('clear') 87 | menu = [] 88 | menu.append("CPU and GPU Temperature versus Time-date") 89 | menu.append("CPU Temperature and CPU usage versus Time-date") 90 | menu.append("RAM and Swap memory usage versus Time-date") 91 | menu.append("CPU usage versus Time-date") 92 | menu.append("CPU and GPU Temperature versus Epoch time") 93 | menu.append("CPU Temperature and CPU usage versus Epoch time") 94 | menu.append("RAM and Swap memory usage versus Epoch time") 95 | menu.append("CPU usage versus Epoch time") 96 | menu.append("CPU usage versus live Time") 97 | menu.append("GPU Temperature versus live Time") 98 | menu.append("RAM usage versus live Time") 99 | menu.append("CPU GPU & RAM usage versus live time") 100 | menu.append("Exit") 101 | try: 102 | while True: 103 | print("\n") 104 | Work.msg_func("blue", "RPI_tempmon :: Graph Menu Options") 105 | Work.msg_func("line", "") 106 | for number, string in enumerate(menu): 107 | print(number+1, string) 108 | Work.msg_func("line", "") 109 | self.selection = (input("Please Select:")) 110 | if int(self.selection) <= 8: 111 | return 112 | elif self.selection == '9': 113 | self.graph_live_data("CPU") 114 | break 115 | elif self.selection == '10': 116 | self.graph_live_data("GPU") 117 | break 118 | elif self.selection == '11': 119 | self.graph_live_data("RAM") 120 | break 121 | elif self.selection == '12': 122 | self.graph_all_live_data() 123 | break 124 | elif self.selection == '13': 125 | quit() 126 | else: 127 | Work.msg_func("red", "\n ** Warning : Unknown Option Selected! **") 128 | Work.msg_func("anykey", "") 129 | os.system('clear') 130 | except ValueError as error: 131 | print(error) 132 | Work.msg_func("red", "Error: Wrong menu Input: Integer only : Try Again") 133 | quit() 134 | 135 | def draw_graph(self, timelist, yaxis_list1, 136 | yaxis_list2, plot_label1, plot_label2, yaxis_label, graph_title): 137 | """ Method to draw graphs two modes, single and doulbe yaxis """ 138 | # convert to ints as strings cause issue with graph in new matlib version 139 | yaxis_list1 = list(map(float, yaxis_list1)) 140 | 141 | if plot_label2: # single plot graph mode 142 | yaxis_list2 = list(map(float, yaxis_list2)) 143 | 144 | plt.xticks(rotation=90) 145 | plt.xticks(fontsize=6) 146 | plt.subplots_adjust(bottom=0.2) 147 | axisx = plt.gca() 148 | # check user input for time date or unix epoch for yaxis 149 | if self.selection == 0: 150 | mydates = timelist 151 | plt.xlabel('TestRuns') 152 | elif re.match('[1-4]', self.selection): 153 | mydates = [dateutil.parser.parse(s) for s in timelist] 154 | axisx.set_xticks(mydates) 155 | xfmt = md.DateFormatter('%m/%d %H:%M') 156 | axisx.xaxis.set_major_formatter(xfmt) 157 | plt.xlabel('Date time stamp (DD-MM HH:MM)') 158 | elif re.match('[5-8]', self.selection): 159 | mydates = timelist 160 | plt.xlabel('Unix epoch time (seconds)') 161 | 162 | axisx.xaxis.label.set_color('red') 163 | axisx.yaxis.label.set_color('red') 164 | 165 | plt.plot(mydates, yaxis_list1, 166 | label=plot_label1, color='green', marker='x') 167 | if plot_label2: # single plot graph mode 168 | plt.plot(mydates, 169 | yaxis_list2, label=plot_label2, marker='*') 170 | 171 | plt.ylabel(yaxis_label) 172 | plt.title(graph_title, color='green') 173 | plt.legend(loc='upper right', fancybox=True, shadow=True) 174 | plt.grid(True) 175 | plt.show() 176 | 177 | def graph_all_live_data(self): 178 | """ Draw a live graph of pi GPU/CPU/RAM """ 179 | print("Drawing graph of all data usage versus live time") 180 | print("Press CTRL+c to quit.") 181 | try: 182 | time_cpu_axis = [] 183 | time_ram_axis = [] 184 | time_gpu_axis = [] 185 | yaxis_cpu_data = 0 186 | yaxis_ram_data = 0 187 | yaxis_gpu_data = 0 188 | plt.ion() 189 | labels = () 190 | 191 | # pre-load dummy data 192 | for i in range(0, 150): 193 | time_cpu_axis.append(.5) 194 | time_ram_axis.append(.5) 195 | time_gpu_axis.append(.5) 196 | 197 | while True: 198 | # get data 199 | yaxis_cpu_data = Work.get_cpu_use() 200 | yaxis_cpu_data = float(yaxis_cpu_data) 201 | yaxis_ram_data = Work.get_ram_info() 202 | yaxis_ram_data = float(yaxis_ram_data) 203 | ostemp = os.popen('vcgencmd measure_temp').readline() 204 | yaxis_gpu_data = (ostemp.replace("temp=", "").replace("'C\n", "")) 205 | yaxis_gpu_data = float(yaxis_gpu_data) 206 | 207 | # update the graph 208 | labels = "GPU Temp + CPU & RAM usage", "CPU-% RAM-% GPU-'C", "CPU-%", "RAM-%", "GPU-'C" 209 | time_cpu_axis.append(yaxis_cpu_data) 210 | time_ram_axis.append(yaxis_ram_data) 211 | time_gpu_axis.append(yaxis_gpu_data) 212 | time_cpu_axis.pop(0) 213 | time_ram_axis.pop(0) 214 | time_gpu_axis.pop(0) 215 | self.plot_all_now(time_cpu_axis, time_ram_axis, time_gpu_axis, labels) 216 | plt.pause(2) 217 | 218 | except Exception as error: 219 | print(error) 220 | Work.msg_func("bold", "Real-time matplotlib plot shutdown") 221 | quit() 222 | 223 | def plot_all_now(self, time_cpu_axis, time_ram_axis, time_gpu_axis, labels): 224 | """ Called from method graph_all_live_data to draw graph""" 225 | 226 | title, y_label, plot_cpu_label, plot_ram_label, plot_gpu_label = labels 227 | plt.clf() 228 | 229 | plt.ylim([1, 100]) 230 | plt.ylabel(y_label, color='red') 231 | 232 | plt.title(self.name + title, color='green') 233 | plt.grid(True) 234 | plt.xlabel("Time (last 300 seconds)", color='red') 235 | plt.plot(time_cpu_axis, color='blue', marker='', label=plot_cpu_label) 236 | plt.plot(time_ram_axis, color='red', marker='', label=plot_ram_label) 237 | plt.plot(time_gpu_axis, color='green', marker='', label=plot_gpu_label) 238 | plt.legend(loc='upper right', fancybox=True, shadow=True) 239 | plt.show() 240 | 241 | def graph_live_data(self, choice): 242 | """ Draw a live graph of pi GPU or CPU or RAM """ 243 | try: 244 | time_axis = [] 245 | yaxis_data = 0 246 | plt.ion() 247 | labels = () 248 | # pre-load dummy data 249 | for i in range(0, 150): 250 | time_axis.append(.5) 251 | 252 | while True: 253 | if choice == "GPU": 254 | ostemp = os.popen('vcgencmd measure_temp').readline() 255 | yaxis_data = (ostemp.replace("temp=", "").replace("'C\n", "")) 256 | labels = (" GPU live temp", "Temperature ('C)", "GPU") 257 | yaxis_data = float(yaxis_data) 258 | time_axis.append(yaxis_data) 259 | time_axis.pop(0) 260 | self.plot_now(time_axis, labels) 261 | elif choice == 'CPU': 262 | yaxis_data = Work.get_cpu_use() 263 | yaxis_data = float(yaxis_data) 264 | labels = (" CPU live usage", "Usage (%)", "CPU") 265 | time_axis.append(yaxis_data) 266 | time_axis.pop(0) 267 | self.plot_now(time_axis, labels) 268 | elif choice == 'RAM': 269 | yaxis_data = Work.get_ram_info() 270 | yaxis_data = float(yaxis_data) 271 | labels = (" RAM live usage", "Usage (%)", "RAM") 272 | time_axis.append(yaxis_data) 273 | time_axis.pop(0) 274 | self.plot_now(time_axis, labels) 275 | 276 | plt.pause(2) 277 | except Exception as error: 278 | print(error) 279 | Work.msg_func("bold", "Real-time matplotlib plot shutdown") 280 | quit() 281 | 282 | def plot_now(self, timeaxis, labels): 283 | """ Called from method graph_live_data to draw graph""" 284 | 285 | title, y_label, plot_label = labels 286 | plt.clf() 287 | 288 | plt.ylim([1, 100]) 289 | plt.ylabel(y_label, color='red') 290 | 291 | plt.title(self.name + title, color='green') 292 | plt.grid(True) 293 | plt.xlabel("Time (last 300 seconds)", color='red') 294 | plt.plot(timeaxis, color='blue', marker='*', label=plot_label) 295 | plt.legend(loc='upper right', fancybox=True, shadow=True) 296 | plt.show() 297 | 298 | 299 | def importtest(text): 300 | """import print test statement""" 301 | # print(text) 302 | pass 303 | 304 | # ===================== MAIN =============================== 305 | 306 | 307 | if __name__ == '__main__': 308 | importtest("main") 309 | else: 310 | importtest("Imported {}".format(__name__)) 311 | 312 | # ===================== END =============================== 313 | -------------------------------------------------------------------------------- /rpiTempMod/RpiTempmonWork.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Module imported into rpi_tempmon contains various utility functions 4 | Functions in this module 5 | (1) csv convert : converts log.txt into log.csv 6 | (2) notify_func : function to control notify-send alerts 7 | (3) get_cpu_tempfunc: Return CPU temperature 8 | (4) get_gpu_tempfunc: Return GPU temperature as a character string 9 | (5) get_cpu_use: Return CPU usage using psutil 10 | (6) get_ram_info: Return RAM usage using psutil 11 | (7) get_swap_info: Return swap memory usage using psutil 12 | (8) logging_func: sents log data to file 13 | (9) mail_func: Sends mail via msmpt 14 | (10) data_func: Function to parse log file also produce a Data report 15 | (11) stress_func: Carry out stress test 16 | (12) msg_func: Writes to console 17 | """ 18 | import os 19 | import datetime 20 | import csv 21 | import time 22 | import psutil 23 | 24 | def csv_convert(destlog): 25 | """ Function to convert log.txt to log.csv""" 26 | try: 27 | os.chdir(destlog) 28 | # Read in log.txt file 29 | cputemp = "" 30 | time_stamp = "" 31 | gputemp = "" 32 | cpu = "" 33 | ram = "" 34 | swap = "" 35 | 36 | # csv file 37 | mycsvpath = destlog + "/" + "log.csv" 38 | output_file = open(mycsvpath, 'w', newline='') 39 | output_writer = csv.writer(output_file) 40 | 41 | # get data from file and put into csv file 42 | mylogpath = destlog + "/" + "log.txt" 43 | if os.path.isfile(mylogpath): 44 | with open(mylogpath, 'r') as myfile: 45 | for line in myfile: 46 | if "TS" in line: 47 | time_stamp = line[5:-1] 48 | if "CPU" in line: 49 | cputemp = line[18:22] 50 | if "GPU" in line: 51 | gputemp = line[18:22] 52 | if "Cpu" in line: 53 | cpu = line[12:16] 54 | if "RAM" in line: 55 | ram = line[12:16] 56 | if "Swap" in line: 57 | swap = line[13:17] 58 | if "Raspberry" in line: 59 | output_writer.writerow([time_stamp, cputemp, gputemp, cpu, ram, swap]) 60 | output_file.close() 61 | else: 62 | msg_func("red", "Error: Log file not found at {}".format(mylogpath)) 63 | 64 | except Exception as error: 65 | msg_func("red", "Problem with csv convert function {}".format(error)) 66 | msg_func("red", "Check that log file exists, is not corrupt") 67 | msg_func("red", "or created by a version pre-2.0-1 - {}".format(mylogpath)) 68 | else: 69 | msg_func("bold", "Log File conversion from text to csv completed") 70 | 71 | 72 | def notify_func(cli_arg_2, cpu_limit): 73 | """ function to control notify-send alerts, passed in second command line argument""" 74 | title = "rpi_tempmon" 75 | message = "" 76 | 77 | if cli_arg_2 == "2": 78 | # send notify always 79 | message = "CPU temperature => " + get_cpu_tempfunc() + "'C" 80 | os.system('notify-send "{}" "{}"'.format(title, message)) 81 | elif cli_arg_2 == "3": 82 | # send notify if cpulimit exceed) 83 | if get_cpu_tempfunc() > cpu_limit: 84 | message = "ALARM : CPU temp => {}'C : Limit at {}'C"\ 85 | .format(get_cpu_tempfunc(), cpu_limit) 86 | os.system('notify-send "{}" "{}"'.format(title, message)) 87 | else: 88 | msg_func("red", "Error invalid argument to -n option 2 or 3 only") 89 | 90 | 91 | def get_cpu_tempfunc(): 92 | """ Return CPU temperature """ 93 | result = 0 94 | mypath = "/sys/class/thermal/thermal_zone0/temp" 95 | with open(mypath, 'r') as mytmpfile: 96 | for line in mytmpfile: 97 | result = line 98 | 99 | result = float(result)/1000 100 | result = round(result, 1) 101 | return str(result) 102 | 103 | 104 | def get_gpu_tempfunc(): 105 | """ Return GPU temperature as a character string""" 106 | res = os.popen('vcgencmd measure_temp').readline() 107 | return (res.replace("\n", "").replace("temp=", "")) 108 | 109 | 110 | def get_cpu_use(): 111 | """ Return CPU usage using psutil""" 112 | cpu_cent = psutil.cpu_percent() 113 | return str(cpu_cent) 114 | 115 | 116 | def get_ram_info(): 117 | """ Return RAM usage using psutil """ 118 | ram_cent = psutil.virtual_memory()[2] 119 | return str(ram_cent) 120 | 121 | 122 | def get_swap_info(): 123 | """ Return swap memory usage using psutil """ 124 | swap_cent = psutil.swap_memory()[3] 125 | return str(swap_cent) 126 | 127 | 128 | def logging_func(choice, mail_alert, cpu_limit, mailuser, destlog, alarm_mode): 129 | """ Function to log temps data to file, 130 | passed choice(args parameter which called it 131 | and 3 options from config file and path to log file""" 132 | try: 133 | if choice == "l": 134 | os.chdir(destlog) 135 | else: 136 | # Makes a directory with time/date stamp and enters it 137 | os.chdir(destlog) 138 | dirvar = datetime.datetime.now().strftime("%H:%M:%S%a%b%d") 139 | dirvar = destlog + "/" + dirvar + "_RPIT" 140 | if not os.path.exists(dirvar): 141 | os.makedirs(dirvar) 142 | os.chdir(dirvar) 143 | 144 | # making time string 145 | today = datetime.datetime.now().strftime("%y-%m-%d %H:%M:%S") 146 | # write to file 147 | with open("log.txt", "a+") as mylogfile: 148 | mylogfile.write("TS = " + str(today) + '\n') 149 | mylogfile.write("EP = " + str(round(time.time(), 0)) + '\n') 150 | mylogfile.write("GPU temperature = " + get_gpu_tempfunc() + '\n') 151 | mylogfile.write("CPU temperature = " + get_cpu_tempfunc() + '\n') 152 | mylogfile.write("Cpu usage = " + get_cpu_use() + '\n') 153 | mylogfile.write("RAM usage = " + get_ram_info() + '\n') 154 | mylogfile.write("Swap usage = " + get_swap_info() + '\n') 155 | mylogfile.write("Raspberry pi temperature monitor: raspberrypi \n") 156 | # Add alarm message to log 157 | if (alarm_mode == "1") and get_cpu_tempfunc() > cpu_limit: 158 | mylogfile.write("Warning : cpu over the temperature limit: " 159 | + cpu_limit + '\n') 160 | # Send log by mail 161 | if (choice == "l") and (mail_alert == "1"): 162 | if (alarm_mode == "1") and get_cpu_tempfunc() > cpu_limit: 163 | mail_func(" Warning ", mailuser, destlog) 164 | except Exception as error: 165 | msg_func("red", "Problem with data logging convert function {}".format(error)) 166 | msg_func("red", "Check that log file exists and is not corrupt ") 167 | else: 168 | msg_func("bold", "Logging function run completed") 169 | 170 | 171 | def mail_func(sub, mailu, destlog): 172 | """Function sends mail via ssmpt , 173 | passed subject line and mail account name and logfile""" 174 | try: 175 | os.chdir(destlog) 176 | if os.path.exists("log.txt"): 177 | # Command to send mail from bash 178 | os.system('echo "Datafile log.txt attached" > body.txt') 179 | os.system('mpack -s "raspberry-PI-temperature {}" -d body.txt log.txt {}'.format(sub, mailu)) 180 | else: 181 | msg_func("red", "Error: log file is not there {}".format(destlog)) 182 | except Exception as error: 183 | msg_func("red", "Error with mail send function {}".format(error)) 184 | msg_func("red", "Check that log file exists {}".format(destlog)) 185 | msg_func("red", "Check that network is up") 186 | msg_func("red", "ssmtp and the user is configured properly {}".format(mailu)) 187 | else: 188 | msg_func("bold", "Mail send function completed") 189 | 190 | 191 | def data_func(destlog, flag): 192 | """ Function to parse log file and produce a Data report 193 | also called from rpiTempmonGraph module when called 194 | from there exits before report build""" 195 | # parse logfile section 196 | # define lists to hold data from logfile 197 | timelist = [] 198 | unixlist = [] 199 | cpulist = [] 200 | gpulist = [] 201 | cpu_uselist = [] 202 | ramlist = [] 203 | swaplist = [] 204 | my_warning_count = 0 205 | 206 | try: 207 | # get data from file and put into lists 208 | mypath = destlog + "/" + "log.txt" 209 | if os.path.isfile(mypath): 210 | with open(mypath, 'r') as myfile: 211 | for line in myfile: 212 | if "TS" in line: 213 | timelist.append(line[5:-1]) 214 | if "EP" in line: 215 | unixlist.append(line[5:-1]) 216 | if "CPU" in line: 217 | cpulist.append(line[18:20]) 218 | if "GPU" in line: 219 | gpulist.append(line[18:20]) 220 | if "Cpu" in line: 221 | cpu_uselist.append(line[12:16]) 222 | if "RAM" in line: 223 | ramlist.append(line[12:16]) 224 | if "Swap" in line: 225 | swaplist.append(line[13:17]) 226 | if "Warning" in line: 227 | my_warning_count += 1 228 | else: 229 | msg_func("red", "Error: Log file not found at {}".format(mypath)) 230 | 231 | if flag: # if called from graph module go back with data 232 | return timelist, unixlist, cpulist, gpulist, cpu_uselist, ramlist, swaplist 233 | # Report code section 234 | msg_func("bold", "\nRaspberry pi CPU GPU temperature monitor program") 235 | print("Data report on {} \n".format(mypath)) 236 | print("Start temperature: {} Time: {}".format(cpulist[0], timelist[0])) 237 | length = len(cpulist) 238 | print("End temperature: {} Time: {}".format(cpulist[length-1], timelist[length-1])) 239 | print("Number of data points: {} \n".format(length)) 240 | cpulist = list(map(int, cpulist)) 241 | average_temp = sum(cpulist) / float(length) 242 | print("Average Temperature: {:.2f}".format(average_temp)) 243 | print("Max Tempertaure: {}".format(max(cpulist))) 244 | print("Min Tempertaure: {}".format(min(cpulist))) 245 | print("Number of temperature warnings: {}\n".format(my_warning_count)) 246 | 247 | cpu_uselist = list(map(float, cpu_uselist)) 248 | average_use = sum(cpu_uselist) / float(length) 249 | print("Average Cpu usage: {:.2f}".format(average_use)) 250 | print("Max Cpu usage: {}".format(max(cpu_uselist))) 251 | print("Min Cpu usage: {} \n".format(min(cpu_uselist))) 252 | ramlist = list(map(float, ramlist)) 253 | average_use = sum(ramlist) / float(length) 254 | print("Average RAM usage: {:.2f}".format(average_use)) 255 | print("Max RAM usage: {}".format(max(ramlist))) 256 | print("Min RAM usage: {}".format(min(ramlist))) 257 | except Exception as error: 258 | msg_func("red", error) 259 | msg_func("red", "Error in data_func function") 260 | return 261 | 262 | 263 | def stresstest(destlog, scancount): 264 | """ function to carry out stress test, creates a data csv file and 265 | optional graph""" 266 | # if not a positive int print message and ask for input again 267 | if int(scancount) < 2 or int(scancount) > 50: 268 | msg_func("red", "Error: input to -ST must be a positive integer 2-50") 269 | quit() 270 | 271 | try: 272 | yaxislist = [] 273 | cpu_uselist = [] 274 | cpulist = [] 275 | 276 | # csv file 277 | os.chdir(destlog) 278 | mycsvpath = destlog + "/" + "stresslog.csv" 279 | output_file = open(mycsvpath, 'w', newline='') 280 | output_writer = csv.writer(output_file) 281 | print("RPi tempmon, CPU Stress Test:") 282 | # loop thru data for scancount carrying out cpu stress test 283 | if os.path.isfile(mycsvpath): 284 | for i in range(0, int(scancount)): 285 | yaxislist.append(i+1) 286 | cpu_temp = get_cpu_tempfunc() 287 | cpu_use = get_cpu_use() 288 | cpu_uselist.append(cpu_use) 289 | cpulist.append(cpu_temp) 290 | msg_func("bold", "Testrun: {}".format(i+1)) 291 | # Sysbench stress test 292 | os.system('sysbench --test=cpu --cpu-max-prime=20000 --num-threads=4 run >/dev/null 2>&1') 293 | msg_func("bold", "CPU temp => {}'C".format(cpu_temp)) 294 | msg_func("bold", "CPU usage => {}%".format(cpu_use)) 295 | output_writer.writerow([i+1, cpu_temp, cpu_use]) 296 | output_file.close() 297 | print("\ncsv file, stresslog.csv, created at {}".format(destlog)) 298 | else: 299 | msg_func("red", "Error: Log file not found at {}".format(mycsvpath)) 300 | except Exception as error: 301 | print("Problem with stress test function {}".format(error)) 302 | quit() 303 | else: 304 | plotlabel1 = 'CPU temp' 305 | plotlabel2 = 'CPU usage' 306 | ylabel = 'CPU usage(%) / Temperature (degrees)' 307 | title = 'CPU temperature and usage , Stress Test results' 308 | return yaxislist, cpulist, cpu_uselist, plotlabel1, plotlabel2, ylabel, title 309 | 310 | 311 | def msg_func(myprocess, mytext): 312 | """NAME : msg_func 313 | DESCRIPTION :prints to screen 314 | prints line, text and anykey prompts, yesno prompt 315 | INPUTS : $1 process name $2 text input 316 | PROCESS :[1] print line [2] anykey prompt 317 | [3] print text "green , red ,blue , norm yellow and highlight" 318 | [4] yn prompt, 319 | OUTPUT yesno prompt return 1 or 0""" 320 | 321 | # colours for printf 322 | blue = '\033[96m' 323 | green = '\033[92m' 324 | yellow = '\033[93m' 325 | red = '\033[91m' 326 | bold = '\033[1m' 327 | end = '\033[0m' 328 | mychoice = "" 329 | 330 | if myprocess == "line": # print blue horizontal line of = 331 | print(blue + "="*80 + end) 332 | 333 | if myprocess == "anykey": 334 | input("Press to continue" + " " + mytext) 335 | 336 | if myprocess == "green": # print green text 337 | print(green + mytext + end) 338 | 339 | if myprocess == "red": # print red text 340 | print(red + mytext + end) 341 | 342 | if myprocess == "blue": # print blue text 343 | print(blue + mytext + end) 344 | 345 | if myprocess == "yellow": # print yellow text 346 | print(yellow + mytext + end) 347 | 348 | if myprocess == "bold": # print bold text 349 | print(bold + mytext + end) 350 | 351 | if myprocess == "yesno": # yes no prompt loop 352 | while True: 353 | mychoice = input("Repeat ? [y/n]") 354 | if mychoice == "y": 355 | choice = 1 356 | return choice 357 | elif mychoice == "n": 358 | choice = 0 359 | return choice 360 | else: 361 | print(yellow + "Please answer: y for yes OR n for no!" + end) 362 | 363 | return 2 364 | 365 | 366 | def importtest(text): 367 | """import print test statement""" 368 | # print(text) 369 | pass 370 | # ===================== MAIN =============================== 371 | 372 | 373 | if __name__ == '__main__': 374 | importtest("main") 375 | else: 376 | importtest("Imported {}".format(__name__)) 377 | 378 | # ===================== END =============================== 379 | -------------------------------------------------------------------------------- /rpiTempMod/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/rpiTempMod/__init__.py -------------------------------------------------------------------------------- /rpiTempSrc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinlyonsrepo/raspberrypi_tempmon/0aab47ac46a0640f5bd567998143da1b53cad90c/rpiTempSrc/__init__.py -------------------------------------------------------------------------------- /rpiTempSrc/rpi_tempmon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | # ========================HEADER======================================= 4 | # title :rpi_tempmon.py 5 | # description :python script to display the CPU & GPU temp of RPi 6 | # author :Gavin Lyons 7 | # intial date :17/08/2017 8 | # web url :https://github.com/gavinlyonsrepo/raspberrypi_tempmon 9 | # mail :glyons66@hotmail.com 10 | # python version : 3.7.3 11 | # Functions: 12 | (1) exit_handler_func: exit handler function 13 | (2) stresstest_func: stresstest function 14 | (3) process_cmd_arguments: process command line arguments 15 | (4) print_title_func: prints title to console 16 | (5) read_configfile_func: read in config file 17 | (6) alarm_mode_check: checks for alarm condition 18 | (7) cont_mode_check: checks for continuous mode 19 | (8) main: main program loop 20 | (9) set_paths_func: set and check the paths for config & cache 21 | (10) led_toggle_func: function to toggle a GPIO 22 | """ 23 | # =========================IMPORTS====================== 24 | # Import the system modules needed to run rpi_tempmon.py. 25 | import sys 26 | import os 27 | import datetime 28 | import argparse 29 | import configparser 30 | import time 31 | import socket # For hostname 32 | from gpiozero import LED 33 | 34 | # my modules 35 | from rpiTempMod import RpiTempmonGraph 36 | from rpiTempMod import RpiTempmonWork as Work 37 | # Local testing, comment out next 3 lines for production 38 | #sys.path.insert(0, '/home/gavin/Documents/tech/mytemp/rpiTempMod') 39 | #import RpiTempmonWork as Work 40 | #import RpiTempmonGraph 41 | 42 | # =======================GLOBALS========================= 43 | # metadata 44 | __VERSION__ = "3.0" 45 | __URL__ = "https://github.com/gavinlyonsrepo/raspberrypi_tempmon" 46 | # set the path for logfile 47 | DESTLOG = os.environ['HOME'] + "/.cache/rpi_tempmon" 48 | # set the path for config file 49 | DESTCONFIG = os.environ['HOME'] + "/.config/rpi_tempmon" 50 | DESTCONFIGFILE = DESTCONFIG + "/" + "rpi_tempmon.cfg" 51 | 52 | # ===================FUNCTION SECTION=============================== 53 | 54 | 55 | def exit_handler_func(): 56 | """Handle the program exit""" 57 | # Print exit message 58 | print("\nGoodbye " + os.environ['USER']) 59 | Work.msg_func("bold", "Endex") 60 | quit() 61 | 62 | 63 | def stresstest_func(testrun_count): 64 | """Call stresstest function and display stress test graph choice""" 65 | print(DESTLOG) 66 | yaxislist, cpulist, cpu_uselist, plotlabel1, \ 67 | plotlabel2, ylabel, title = Work.stresstest(DESTLOG, testrun_count) 68 | mychoice = input("\nDo you want to view graph of stress test? [y/N]") 69 | if (mychoice == "y") or (mychoice == "Y"): 70 | mygraph = RpiTempmonGraph.MatplotGraph("RPi Tempmon :") 71 | mygraph.draw_graph(yaxislist, cpulist, cpu_uselist, 72 | plotlabel1, plotlabel2, ylabel, title) 73 | 74 | 75 | def process_cmd_arguments(): 76 | """Function for processing CL arguments 77 | return tuple of 4 values to main""" 78 | str_desc = "URL help at: {}".format(__URL__) 79 | parser = argparse.ArgumentParser(description=str_desc) 80 | parser.add_argument( 81 | '-v', help='Print rpi_tempmon version and quit', 82 | default=False, dest='version', action='store_true') 83 | 84 | parser.add_argument( 85 | '-l', help='Log file mode', 86 | default=False, dest='logfile', action='store_true') 87 | 88 | parser.add_argument( 89 | '-s', help=' convert Log file to csv', 90 | default=False, dest='csv_convert', action='store_true') 91 | 92 | parser.add_argument( 93 | '-L', help='Log folder mode', 94 | default=False, dest='logfolder', action='store_true') 95 | 96 | parser.add_argument( 97 | '-m', help='Send email mode with ssmtp', 98 | default=False, dest='mail', action='store_true') 99 | 100 | parser.add_argument( 101 | '-g', help='Draw a graph from log file data', 102 | default=False, dest='graphlog', action='store_true') 103 | 104 | parser.add_argument( 105 | '-c', help='Continuous mode, + integer arg in secs for delay time', 106 | type=int, dest='cont') 107 | 108 | parser.add_argument( 109 | '-a', help='Analysis the log file and report', 110 | default=False, dest='data', action='store_true') 111 | 112 | parser.add_argument('-n', help='notify mode +int arg, 2=notify always ,\ 113 | 3=notify only on Cpu limit exceed', type=int, dest='notify') 114 | 115 | parser.add_argument( 116 | '-ST', help='Stress test mode, + integer arg for number of runs', 117 | type=int, dest='stresstest') 118 | 119 | args = parser.parse_args() 120 | 121 | # read config file 122 | config_file_data = read_configfile_func() 123 | # unpack tuple with values 124 | mailuser, mail_alert, alarm_mode, cpu_limit, led_mode, led_num = config_file_data 125 | # pack a new tuple for main 126 | config_file_data_main = config_file_data[2:6] 127 | 128 | # check the args 129 | if not len(sys.argv) > 1: 130 | # no argument > back to main 131 | return config_file_data_main 132 | 133 | if args.cont: 134 | # cont mode > back to main 135 | return config_file_data_main 136 | 137 | if args.stresstest: 138 | stresstest_func(sys.argv[2]) 139 | 140 | if args.version: 141 | Work.msg_func("bold", "\nVersion : rpi_tempmon " + __VERSION__) 142 | 143 | if args.csv_convert: 144 | Work.csv_convert(DESTLOG) 145 | 146 | if args.logfolder: 147 | # call log function pass L for folder 148 | Work.logging_func("L", "0", "100", "X", DESTLOG, False) 149 | 150 | if args.data: 151 | # call the data analysis function 152 | Work.data_func(DESTLOG, False) 153 | 154 | if args.logfile: 155 | # call log function pass l for file 156 | Work.logging_func("l", mail_alert, cpu_limit, mailuser, DESTLOG, alarm_mode) 157 | 158 | if args.notify: 159 | Work.notify_func(sys.argv[2], cpu_limit) 160 | 161 | if args.mail: 162 | # mail mode 163 | if mail_alert == "1": 164 | Work.mail_func(" Mail mode ", mailuser, DESTLOG) 165 | else: 166 | Work.msg_func("yellow", "Mail Alert disabled in config file rpitempmon.cfg ::" + mail_alert) 167 | 168 | if args.graphlog: 169 | mygraph = RpiTempmonGraph.MatplotGraph("RPi Tempmon :") 170 | mygraph.graph_log_data(DESTLOG) 171 | 172 | exit_handler_func() 173 | 174 | 175 | def print_title_func(): 176 | """Function to print title in normal and cont mode""" 177 | os.system('clear') 178 | print() 179 | Work.msg_func("line", "") 180 | today = datetime.datetime.now().strftime("%y-%m-%d %H:%M:%S") 181 | Work.msg_func("bold", "Raspberry pi CPU GPU temperature monitor program") 182 | print(today) 183 | print(socket.gethostname()) 184 | Work.msg_func("line", "") 185 | Work.msg_func("green", "CPU temp => {}'C".format(Work.get_cpu_tempfunc())) 186 | Work.msg_func("green", "GPU temp => {}".format(Work.get_gpu_tempfunc())) 187 | Work.msg_func("green", "CPU usage => {}%".format(Work.get_cpu_use())) 188 | Work.msg_func("green", "RAM usage => {}%".format(Work.get_ram_info())) 189 | Work.msg_func("green", "Swap usage => {}% \n".format(Work.get_swap_info())) 190 | 191 | 192 | def read_configfile_func(): 193 | """Read in configfile, return 6 values in a tuple""" 194 | try: 195 | myconfigfile = configparser.ConfigParser() 196 | myconfigfile.read(DESTCONFIGFILE) 197 | mailuser = myconfigfile.get("MAIN", "RPI_AuthUser") 198 | mail_alert = myconfigfile.get("MAIN", "MAIL_ALERT") 199 | alarm_mode = myconfigfile.get("MAIN", "ALARM_MODE") 200 | cpu_limit = myconfigfile.get("MAIN", "CPU_UPPERLIMIT") 201 | led_mode = myconfigfile.get("MAIN", "LED_MODE") 202 | led_num = myconfigfile.get("MAIN", "GPIO_LED") 203 | if (int(cpu_limit) < 1) or (int(cpu_limit) > 99): 204 | Work.msg_func("red", " ERROR : ") 205 | print("CPU_UPPERLIMIT must be between 1 and 99") 206 | quit() 207 | config_file_data = (mailuser, mail_alert, alarm_mode, cpu_limit, led_mode, led_num) 208 | except Exception as error: 209 | Work.msg_func("red", " ERROR : ") 210 | print("Problem with config file: {}".format(error)) 211 | print("Must have section header of [MAIN] and six parameters") 212 | print("Help and example file at url") 213 | print(__URL__) 214 | exit_handler_func() 215 | 216 | return config_file_data 217 | 218 | 219 | def alarm_mode_check(config_file_data_main, my_led): 220 | """function to check alarm mode """ 221 | alarm_mode, cpu_limit, led_mode, led_num = config_file_data_main 222 | if alarm_mode == "1": 223 | Work.msg_func("bold", "Alarm mode is on: " + cpu_limit + "'C") 224 | if Work.get_cpu_tempfunc() > cpu_limit: 225 | # display led mode 226 | Work.msg_func("red", "Warning : CPU over the temperature limit: " + cpu_limit + "'C") 227 | if led_mode == "1": 228 | Work.msg_func("bold", "LED mode is on, GPIO pin selected: " + led_num) 229 | led_toggle_func("on", my_led) 230 | 231 | 232 | def cont_mode_check(my_led): 233 | """check for continuous mode """ 234 | delay = 5 235 | # is their an argument? 236 | if len(sys.argv) > 1: 237 | # Was it -c? 238 | if sys.argv[1] == "-c": 239 | val = int(sys.argv[2]) 240 | # if not a positive int print message and ask for input again 241 | if val < 0: 242 | print("Sorry, input to -c must be a positive integer.") 243 | quit() 244 | delay = sys.argv[2] 245 | Work.msg_func("bold", "Continuous mode: On") 246 | Work.msg_func("bold", "Sleep delay set to: " + str(delay) + " seconds.") 247 | print("\nPress CTRL+c to quit.") 248 | time.sleep(float(delay)) 249 | os.system('clear') 250 | else: # normal mode prompt 251 | if Work.msg_func("yesno", ""): 252 | pass 253 | else: 254 | led_toggle_func("off", my_led) 255 | exit_handler_func() 256 | 257 | 258 | def set_paths_func(): 259 | """set and check the paths for config file and cache/logging""" 260 | if not os.path.exists(DESTLOG): # Check cache path is there 261 | os.makedirs(DESTLOG) 262 | if not os.path.exists(DESTCONFIG): # Check config path is there 263 | os.makedirs(DESTCONFIG) 264 | 265 | if os.path.isfile(DESTCONFIGFILE): # Check config file is there 266 | pass 267 | else: 268 | try: 269 | config = configparser.ConfigParser() 270 | config.optionxform = lambda option: option # case sensitive option 271 | config['MAIN'] = {'RPI_AuthUser': 'example@gmail.com', 272 | 'MAIL_ALERT': '1', 273 | 'ALARM_MODE': '1', 274 | 'CPU_UPPERLIMIT': '70', 275 | 'LED_MODE': '1', 276 | 'GPIO_LED': '26'} 277 | with open(DESTCONFIGFILE, 'w') as configfile: 278 | config.write(configfile) 279 | except Exception as error: 280 | Work.msg_func("red", " ERROR : ") 281 | print("Config file is missing at {}".format(DESTCONFIGFILE)) 282 | print("Problem Creating default config file: {}".format(error)) 283 | print("Example config file and info can be found at:") 284 | print(__URL__) 285 | exit_handler_func() 286 | else: 287 | Work.msg_func("yellow", " INFO : ") 288 | print("Config file was missing at {}".format(DESTCONFIGFILE)) 289 | print("Config file created with default values.") 290 | input("Press to continue") 291 | 292 | 293 | def led_toggle_func(mode, my_led): 294 | """ led_toggle_func , function 295 | to toggle a GPIO LED, passed mode on/off 296 | and GPIO pin of LED""" 297 | time.sleep(.05) # gpio init 298 | if mode == "off": 299 | my_led.off() 300 | else: 301 | my_led.on() 302 | 303 | 304 | def main(config_file_data_main): 305 | """Function to hold main program loop, param1 : config file data tuple""" 306 | 307 | # unpack config file data main tuple 308 | alarm_mode, cpu_limit, led_mode, led_num = config_file_data_main 309 | my_led = LED(int(led_num)) 310 | scan_count = 0 311 | 312 | # main loop 313 | try: 314 | while True: 315 | print_title_func() 316 | scan_count += 1 317 | Work.msg_func("bold", "Number of scans: " + str(scan_count)) 318 | alarm_mode_check(config_file_data_main, my_led) 319 | cont_mode_check(my_led) 320 | led_toggle_func("off", my_led) 321 | except KeyboardInterrupt: 322 | led_toggle_func("off", my_led) 323 | exit_handler_func() 324 | 325 | 326 | # =====================MAIN=============================== 327 | if __name__ == "__main__": 328 | try: 329 | print("\nRPi temperature monitor:") 330 | set_paths_func() 331 | exit(main(process_cmd_arguments())) 332 | except (KeyboardInterrupt, SystemExit): 333 | pass 334 | # ====================END=============================== 335 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="rpi_tempmon.py", 5 | version="3.0", 6 | author="gavin lyons", 7 | author_email="glyons66@hotmail.com", 8 | description="Monitor RAM,CPU and GPU data of Raspberry Pi", 9 | license=" GPL", 10 | keywords="PI Raspberry CPU ARM GPU temperature temp rpi monitor display gavin lyons", 11 | url="https://github.com/gavinlyonsrepo/raspberrypi_tempmon", 12 | download_url='https://github.com/gavinlyonsrepo/raspberrypi_tempmon/archive/3.0.tar.gz', 13 | packages=['rpiTempSrc','rpiTempMod',], 14 | install_requires= ['matplotlib','pip','psutil'], 15 | setup_requires = ['pip'], 16 | scripts=['rpiTempSrc/rpi_tempmon.py'], 17 | classifiers=[ 18 | "Topic :: Utilities", 19 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 20 | ], 21 | ) 22 | --------------------------------------------------------------------------------