├── README.md ├── src ├── argon-shutdown.sh ├── argon-uninstall.sh ├── argoneon-oledconfig.sh ├── argoneon-rtcconfig.sh ├── argoneond.py ├── argoneond.service ├── argoneonoled.py ├── argonone-fanconfig.sh ├── argonone-firmwareupdate.py ├── argonone-irconfig.sh ├── argonone-irdecoder.py ├── argononed.conf ├── argononed.py ├── argononed.service └── argonsysinfo.py └── tutorials ├── fanspeed.py ├── install-dependencies.sh ├── oledsample-apicalls.py ├── oledsample-imageloader.py ├── powerbuttonevents.py └── signalpowercut.py /README.md: -------------------------------------------------------------------------------- 1 | # Argon40 Cases and HATs Scripts 2 | 3 | The Argon40 Cases and HATs are the ergonomic and aesthetic case for the Raspberry Pi. This repository provides samples and references on how to customize our scripts and even create your own versions. You can find out more about it on: 4 | * [argon40.com](https://www.argon40.com/argon-one-raspberry-pi-4-case.html) 5 | * [raspberrypi.org](https://www.raspberrypi.org/blog/argon-one-raspberry-pi-case/) 6 | * [Magpi Magazine](https://magpi.raspberrypi.org/articles/argon-one-review) 7 | 8 | ## Official Installers 9 | * [Argon ONE](https://download.argon40.com/argon1.sh) 10 | * [Argon EON](https://download.argon40.com/argoneon.sh) 11 | 12 | 13 | ## Standard Mechanisms 14 | 15 | Below are the basics on how Argon40 cases and HATs interacts with the Raspberry Pi to enhance the user's experience. The scripts found in this repository are based on these mechanisms. 16 | 17 | ### Power Button Events 18 | 19 | Cases that come with a power button can turn the computer on and off while ensuring that the files stored are safe. It works by setting a pulse to BCM 4 (BOARD P7) depending on the action. The software that listens to these events should be able to react accordingly. 20 | * Double Tap - 20-30ms pulse 21 | * Hold and release after 3 seconds - 40-50ms pulse 22 | 23 | ### Fan Speed 24 | 25 | The built-in fan can be controlled via I2C commands. It's MCU (Micro Controller Unit) uses the address 0x1a (26 decimal value). The fan speed values range from 0 to 100, corresponding to fan speed percentage. The software can use the fan to manage the temperature as it sees fit. 26 | 27 | ### Power cut 28 | 29 | With the aid of an I2C command, we can cut the power to the devices after shutdown. Sending 0xff (255 decimal value) to 0x1a address informs the MCU (Micro Controller Unit) to cut the power after the device completes shutdown. Device shutdown is detected when the serial pin goes 'down', so it's best that serial/UART is enabled on the Pi. 30 | 31 | ### IR Receiver/Transmitter 32 | 33 | The board has placeholders for IR components. The transmitter is connected to BCM 22 (BOARD P15), while the receiver is connected to BCM 23 (BOARD P16). 34 | 35 | 36 | ## Repository Files 37 | 38 | Below is a summary of the files in this repository. The codes will have comments in key sections that explain detailed contructs. 39 | 40 | ### src 41 | 42 | Standard scripts and files used by primary daemon/services for reference. 43 | ``` 44 | Actual scripts installed vary depending on the OS. 45 | ``` 46 | 47 | ### tutorials 48 | 49 | Sample python scripts that can be ran to interact with Argon40 devices. Some requires installation of the scripts while others won't work if Argon40 daemon(s) are running. Please enable: 50 | * I2C 51 | * UART/Serial Port (for power cut) 52 | 53 | ## Support 54 | Feel free to get in touch through cs@argon40.com if you have any questions. 55 | -------------------------------------------------------------------------------- /src/argon-shutdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pythonbin=/usr/bin/python3 4 | argononefanscript=/etc/argon/argononed.py 5 | argoneonrtcscript=/etc/argon/argoneond.py 6 | 7 | if [ ! -z "$1" ] 8 | then 9 | $pythonbin $argononefanscript FANOFF 10 | if [ "$1" = "poweroff" ] || [ "$1" = "halt" ] 11 | then 12 | if [ -f $argoneonrtcscript ] 13 | then 14 | $pythonbin $argoneonrtcscript SHUTDOWN 15 | fi 16 | $pythonbin $argononefanscript SHUTDOWN 17 | fi 18 | fi 19 | -------------------------------------------------------------------------------- /src/argon-uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "----------------------" 3 | echo " Argon Uninstall Tool" 4 | echo "----------------------" 5 | echo -n "Press Y to continue:" 6 | read -n 1 confirm 7 | echo 8 | if [ "$confirm" = "y" ] 9 | then 10 | confirm="Y" 11 | fi 12 | 13 | if [ "$confirm" != "Y" ] 14 | then 15 | echo "Cancelled" 16 | exit 17 | fi 18 | 19 | shortcutfile="/home/pi/Desktop/argonone-config.desktop" 20 | if [ -f "$shortcutfile" ]; then 21 | sudo rm $shortcutfile 22 | if [ -f "/usr/share/pixmaps/ar1config.png" ]; then 23 | sudo rm /usr/share/pixmaps/ar1config.png 24 | fi 25 | if [ -f "/usr/share/pixmaps/argoneon.png" ]; then 26 | sudo rm /usr/share/pixmaps/argoneon.png 27 | fi 28 | fi 29 | 30 | 31 | INSTALLATIONFOLDER=/etc/argon 32 | 33 | argononefanscript=$INSTALLATIONFOLDER/argononed.py 34 | 35 | if [ -f $argononefanscript ]; then 36 | sudo systemctl stop argononed.service 37 | sudo systemctl disable argononed.service 38 | 39 | # Turn off the fan 40 | /usr/bin/python3 $argononefanscript FANOFF 41 | 42 | # Remove files 43 | sudo rm /lib/systemd/system/argononed.service 44 | fi 45 | 46 | # Remove RTC if any 47 | argoneonrtcscript=$INSTALLATIONFOLDER/argoneond.py 48 | if [ -f "$argoneonrtcscript" ] 49 | then 50 | # Disable Services 51 | sudo systemctl stop argoneond.service 52 | sudo systemctl disable argoneond.service 53 | 54 | # No need for sudo 55 | /usr/bin/python3 $argoneonrtcscript CLEAN 56 | /usr/bin/python3 $argoneonrtcscript SHUTDOWN 57 | 58 | # Remove files 59 | sudo rm /lib/systemd/system/argoneond.service 60 | fi 61 | 62 | sudo rm /usr/bin/argon-config 63 | 64 | if [ -f "/usr/bin/argonone-config" ] 65 | then 66 | sudo rm /usr/bin/argonone-config 67 | sudo rm /usr/bin/argonone-uninstall 68 | sudo rm /usr/bin/argonone-ir 69 | fi 70 | 71 | sudo rm /lib/systemd/system-shutdown/argon-shutdown.sh 72 | 73 | sudo rm -R -f $INSTALLATIONFOLDER 74 | 75 | echo "Removed Argon Services." 76 | echo "Cleanup will complete after restarting the device." 77 | -------------------------------------------------------------------------------- /src/argoneon-oledconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | oledconfigfile=/etc/argoneonoled.conf 4 | 5 | get_number () { 6 | read curnumber 7 | if [ -z "$curnumber" ] 8 | then 9 | echo "-2" 10 | return 11 | elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] 12 | then 13 | if [ $curnumber -lt 0 ] 14 | then 15 | echo "-1" 16 | return 17 | elif [ $curnumber -gt 100 ] 18 | then 19 | echo "-1" 20 | return 21 | fi 22 | echo $curnumber 23 | return 24 | fi 25 | echo "-1" 26 | return 27 | } 28 | 29 | get_pagename() { 30 | if [ "$1" == "clock" ] 31 | then 32 | pagename="Current Date/Time" 33 | elif [ "$1" == "cpu" ] 34 | then 35 | pagename="CPU Utilization" 36 | elif [ "$1" == "storage" ] 37 | then 38 | pagename="Storage Utilization" 39 | elif [ "$1" == "raid" ] 40 | then 41 | pagename="RAID Information" 42 | elif [ "$1" == "ram" ] 43 | then 44 | pagename="Available RAM" 45 | elif [ "$1" == "temp" ] 46 | then 47 | pagename="CPU Temperature" 48 | elif [ "$1" == "ip" ] 49 | then 50 | pagename="IP Address" 51 | else 52 | pagename="Invalid" 53 | fi 54 | } 55 | 56 | configure_pagelist () { 57 | pagemasterlist="clock cpu storage raid ram temp ip" 58 | newscreenlist="$1" 59 | pageloopflag=1 60 | while [ $pageloopflag -eq 1 ] 61 | do 62 | echo "--------------------------------" 63 | echo " OLED Pages " 64 | echo "--------------------------------" 65 | i=1 66 | for curpage in $newscreenlist 67 | do 68 | get_pagename $curpage 69 | echo " $i. Remove $pagename" 70 | i=$((i+1)) 71 | done 72 | if [ $i -eq 1 ] 73 | then 74 | echo " No page configured" 75 | fi 76 | echo 77 | echo " $i. Add Page" 78 | echo 79 | echo " 0. Done" 80 | echo -n "Enter Number (0-$i):" 81 | 82 | cmdmode=$( get_number ) 83 | if [ $cmdmode -eq 0 ] 84 | then 85 | pageloopflag=0 86 | elif [[ $cmdmode -eq $i ]] 87 | then 88 | 89 | echo "--------------------------------" 90 | echo " Choose Page to Add" 91 | echo "--------------------------------" 92 | echo 93 | i=1 94 | for curpage in $pagemasterlist 95 | do 96 | get_pagename $curpage 97 | echo " $i. $pagename" 98 | i=$((i+1)) 99 | done 100 | 101 | echo 102 | echo " 0. Cancel" 103 | echo -n "Enter Number (0-$i):" 104 | pagenum=$( get_number ) 105 | if [[ $pagenum -ge 1 && $pagenum -le $i ]] 106 | then 107 | i=1 108 | for curpage in $pagemasterlist 109 | do 110 | if [ $i -eq $pagenum ] 111 | then 112 | if [ "$newscreenlist" == "" ] 113 | then 114 | newscreenlist="$curpage" 115 | else 116 | newscreenlist="$newscreenlist $curpage" 117 | fi 118 | fi 119 | i=$((i+1)) 120 | done 121 | fi 122 | elif [[ $cmdmode -ge 1 && $cmdmode -lt $i ]] 123 | then 124 | tmpscreenlist="" 125 | i=1 126 | for curpage in $newscreenlist 127 | do 128 | if [ ! $i -eq $cmdmode ] 129 | then 130 | tmpscreenlist="$tmpscreenlist $curpage" 131 | fi 132 | i=$((i+1)) 133 | done 134 | if [ "$tmpscreenlist" == "" ] 135 | then 136 | newscreenlist="$tmpscreenlist" 137 | else 138 | # Remove leading space 139 | newscreenlist="${tmpscreenlist:1}" 140 | fi 141 | fi 142 | done 143 | } 144 | 145 | saveconfig () { 146 | echo "#" > $oledconfigfile 147 | echo "# Argon OLED Configuration" >> $oledconfigfile 148 | echo "#" >> $oledconfigfile 149 | echo "enabled=$1" >> $oledconfigfile 150 | echo "switchduration=$2" >> $oledconfigfile 151 | echo "screensaver=$3" >> $oledconfigfile 152 | echo "screenlist=\"$4\"" >> $oledconfigfile 153 | } 154 | 155 | updateconfig=1 156 | oledloopflag=1 157 | while [ $oledloopflag -eq 1 ] 158 | do 159 | if [ $updateconfig -eq 1 ] 160 | then 161 | . $oledconfigfile 162 | fi 163 | 164 | updateconfig=0 165 | if [ -z "$enabled" ] 166 | then 167 | enabled="Y" 168 | updateconfig=1 169 | fi 170 | 171 | if [ -z "$screenlist" ] 172 | then 173 | screenlist="clock ip" 174 | updateconfig=1 175 | fi 176 | 177 | if [ -z "$screensaver" ] 178 | then 179 | screensaver=120 180 | updateconfig=1 181 | fi 182 | 183 | if [ -z "$switchduration" ] 184 | then 185 | switchduration=0 186 | updateconfig=1 187 | fi 188 | 189 | # Write default values to config file, daemon already uses default so no need to restart service 190 | if [ $updateconfig -eq 1 ] 191 | then 192 | saveconfig $enabled $switchduration $screensaver "$screenlist" 193 | updateconfig=0 194 | fi 195 | 196 | displaystring=": Manually" 197 | if [ $switchduration -gt 1 ] 198 | then 199 | displaystring="Every $switchduration secs" 200 | fi 201 | 202 | echo "-----------------------------" 203 | echo "Argon OLED Configuration Tool" 204 | echo "-----------------------------" 205 | echo "Choose from the list:" 206 | echo " 1. Switch Page $displaystring" 207 | echo " 2. Configure Pages" 208 | echo " 3. Turn OFF OLED Screen when unchanged after $screensaver secs" 209 | echo " 4. Enable OLED Pages: $enabled" 210 | echo 211 | echo " 0. Exit" 212 | echo -n "Enter Number (0-3):" 213 | 214 | newmode=$( get_number ) 215 | if [ $newmode -eq 0 ] 216 | then 217 | oledloopflag=0 218 | elif [ $newmode -eq 1 ] 219 | then 220 | echo 221 | echo -n "Enter # of Seconds (10-60, Manual if 0):" 222 | 223 | cmdmode=$( get_number ) 224 | if [ $cmdmode -eq 0 ] 225 | then 226 | switchduration=0 227 | updateconfig=1 228 | elif [[ $cmdmode -ge 10 && $cmdmode -le 60 ]] 229 | then 230 | updateconfig=1 231 | switchduration=$cmdmode 232 | else 233 | echo 234 | echo "Invalid duration" 235 | echo 236 | fi 237 | elif [ $newmode -eq 3 ] 238 | then 239 | echo 240 | echo -n "Enter # of Seconds (60 or above, Manual if 0):" 241 | 242 | cmdmode=$( get_number ) 243 | if [ $cmdmode -eq 0 ] 244 | then 245 | screensaver=0 246 | updateconfig=1 247 | elif [ $cmdmode -ge 60 ] 248 | then 249 | updateconfig=1 250 | screensaver=$cmdmode 251 | else 252 | echo 253 | echo "Invalid duration" 254 | echo 255 | fi 256 | elif [ $newmode -eq 2 ] 257 | then 258 | configure_pagelist "$screenlist" 259 | if [ ! "$screenlist" == "$newscreenlist" ] 260 | then 261 | screenlist="$newscreenlist" 262 | updateconfig=1 263 | fi 264 | elif [ $newmode -eq 4 ] 265 | then 266 | echo 267 | echo -n "Enable OLED Pages (Y/n)?:" 268 | read -n 1 confirm 269 | tmpenabled="$enabled" 270 | if [[ "$confirm" == "n" || "$confirm" == "N" ]] 271 | then 272 | tmpenabled="N" 273 | elif [[ "$confirm" == "y" || "$confirm" == "Y" ]] 274 | then 275 | tmpenabled="Y" 276 | else 277 | echo "Invalid response" 278 | fi 279 | if [ ! "$enabled" == "$tmpenabled" ] 280 | then 281 | enabled="$tmpenabled" 282 | updateconfig=1 283 | fi 284 | 285 | fi 286 | 287 | if [ $updateconfig -eq 1 ] 288 | then 289 | saveconfig $enabled $switchduration $screensaver "$screenlist" 290 | sudo systemctl restart argononed.service 291 | fi 292 | done 293 | 294 | echo 295 | -------------------------------------------------------------------------------- /src/argoneon-rtcconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pythonbin=/usr/bin/python3 4 | argoneonrtcscript=/etc/argon/argoneond.py 5 | rtcconfigfile=/etc/argoneonrtc.conf 6 | 7 | CHECKPLATFORM="Others" 8 | # Check if Raspbian 9 | grep -q -F 'Raspbian' /etc/os-release &> /dev/null 10 | if [ $? -eq 0 ] 11 | then 12 | CHECKPLATFORM="Raspbian" 13 | fi 14 | 15 | 16 | get_number () { 17 | read curnumber 18 | if [ -z "$curnumber" ] 19 | then 20 | echo "-2" 21 | return 22 | elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] 23 | then 24 | if [ $curnumber -lt 0 ] 25 | then 26 | echo "-1" 27 | return 28 | elif [ $curnumber -gt 100 ] 29 | then 30 | echo "-1" 31 | return 32 | fi 33 | echo $curnumber 34 | return 35 | fi 36 | echo "-1" 37 | return 38 | } 39 | 40 | configure_schedule () { 41 | scheduleloopflag=1 42 | while [ $scheduleloopflag -eq 1 ] 43 | do 44 | echo "--------------------------------" 45 | echo " Configure Schedule " 46 | echo "--------------------------------" 47 | echo " 1. Add Schedule" 48 | echo " or" 49 | echo " Remove Schedule" 50 | $pythonbin $argoneonrtcscript GETSCHEDULELIST 51 | echo 52 | echo " 99. Exit" 53 | echo " 0. Back" 54 | #echo "NOTE: You can also edit $rtcconfigfile directly" 55 | echo -n "Enter Number:" 56 | 57 | newmode=$( get_number ) 58 | if [ $newmode -eq 0 ] 59 | then 60 | scheduleloopflag=0 61 | elif [ $newmode -eq 99 ] 62 | then 63 | scheduleloopflag=0 64 | rtcloopflag=2 65 | elif [ $newmode -eq 1 ] 66 | then 67 | configure_newschedule 68 | elif [ $newmode -gt 1 ] 69 | then 70 | echo "CONFIRM SCHEDULE REMOVAL" 71 | $pythonbin $argoneonrtcscript SHOWSCHEDULE $newmode 72 | echo -n "Press Y to remove schedule #$newmode:" 73 | read -n 1 confirm 74 | if [ "$confirm" = "y" ] 75 | then 76 | confirm="Y" 77 | fi 78 | if [ "$confirm" = "Y" ] 79 | then 80 | $pythonbin $argoneonrtcscript REMOVESCHEDULE $newmode 81 | sudo systemctl restart argoneond.service 82 | fi 83 | echo "" 84 | fi 85 | done 86 | } 87 | 88 | configure_newschedule () { 89 | 90 | cmdmode=1 91 | hour=8 92 | minute=0 93 | minuteprefix=":0" 94 | dayidx=0 95 | repeat=1 96 | 97 | subloopflag=1 98 | while [ $subloopflag -eq 1 ] 99 | do 100 | minuteprefix=":0" 101 | if [ $minute -ge 10 ] 102 | then 103 | minuteprefix=":" 104 | fi 105 | 106 | typestr="Shutdown" 107 | if [ $cmdmode -eq 1 ] 108 | then 109 | typestr="Startup" 110 | fi 111 | 112 | daystr="Daily" 113 | if [ $dayidx -eq 1 ] 114 | then 115 | daystr="Mon" 116 | elif [ $dayidx -eq 2 ] 117 | then 118 | daystr="Tue" 119 | elif [ $dayidx -eq 3 ] 120 | then 121 | daystr="Wed" 122 | elif [ $dayidx -eq 4 ] 123 | then 124 | daystr="Thu" 125 | elif [ $dayidx -eq 5 ] 126 | then 127 | daystr="Fri" 128 | elif [ $dayidx -eq 6 ] 129 | then 130 | daystr="Sat" 131 | elif [ $dayidx -eq 7 ] 132 | then 133 | daystr="Sun" 134 | fi 135 | 136 | repeatstr="Yes" 137 | if [ $repeat -eq 0 ] 138 | then 139 | repeatstr="Once" 140 | if [ $dayidx -eq 0 ] 141 | then 142 | daystr="Next Occurence" 143 | fi 144 | fi 145 | 146 | echo "--------------------------------" 147 | echo " Configure Schedule" 148 | echo "--------------------------------" 149 | echo " 1. Type: $typestr" 150 | echo " 2. Set Time: $hour$minuteprefix$minute" 151 | echo " 3. Repeating: $repeatstr" 152 | echo " 4. Day: $daystr" 153 | echo 154 | echo " 5. Add Schedule" 155 | echo 156 | echo " 0. Cancel" 157 | echo -n "Enter Number (0-5):" 158 | 159 | setmode=$( get_number ) 160 | if [ $setmode -eq 0 ] 161 | then 162 | subloopflag=0 163 | elif [ $setmode -eq 1 ] 164 | then 165 | echo "--------------------------------" 166 | echo " Schedule Type " 167 | echo "--------------------------------" 168 | echo " 1. Startup" 169 | echo " 2. Shutdown" 170 | echo 171 | echo -n "Enter Number (1-2):" 172 | 173 | tmpval=$( get_number ) 174 | if [ $tmpval -eq 1 ] 175 | then 176 | cmdmode=1 177 | elif [ $tmpval -eq 2 ] 178 | then 179 | cmdmode=0 180 | else 181 | echo "Invalid Option" 182 | fi 183 | elif [ $setmode -eq 2 ] 184 | then 185 | echo -n "Enter Hour (0-23):" 186 | tmphour=$( get_number ) 187 | echo -n "Enter Minute (0-59):" 188 | tmpminute=$( get_number ) 189 | if [[ $tmpminute -ge 0 && $tmpminute -le 59 && $tmphour -ge 0 && $tmphour -le 23 ]] 190 | then 191 | minute=$tmpminute 192 | hour=$tmphour 193 | else 194 | echo "Invalid value(s)" 195 | fi 196 | elif [ $setmode -eq 3 ] 197 | then 198 | echo -n "Repeat schedule (Y/n)?:" 199 | read -n 1 confirm 200 | if [ "$confirm" = "y" ] 201 | then 202 | repeat=1 203 | else 204 | repeat=0 205 | fi 206 | elif [ $setmode -eq 4 ] 207 | then 208 | echo "Select Day of the Week:" 209 | echo " 0. Daily" 210 | echo " 1. Monday" 211 | echo " 2. Tuesday" 212 | echo " 3. Wednesday" 213 | echo " 4. Thursday" 214 | echo " 5. Friday" 215 | echo " 6. Saturday" 216 | echo " 7. Sunday" 217 | 218 | echo -n "Enter Number (0-7):" 219 | tmpval=$( get_number ) 220 | if [[ $tmpval -ge 0 && $tmpval -le 7 ]] 221 | then 222 | dayidx=$tmpval 223 | else 224 | echo "Invalid Option" 225 | fi 226 | elif [ $setmode -eq 5 ] 227 | then 228 | if [ $dayidx -eq 0 ] 229 | then 230 | cronweekday="*" 231 | elif [ $dayidx -eq 7 ] 232 | then 233 | cronweekday="7" 234 | else 235 | cronweekday=$dayidx 236 | fi 237 | cmdcode="off" 238 | if [ $cmdmode -eq 1 ] 239 | then 240 | cmdcode="on" 241 | fi 242 | 243 | echo "$minute $hour * * $cronweekday $cmdcode" >> $rtcconfigfile 244 | sudo systemctl restart argoneond.service 245 | subloopflag=0 246 | fi 247 | done 248 | } 249 | 250 | configure_newcron () { 251 | subloopflag=1 252 | while [ $subloopflag -eq 1 ] 253 | do 254 | echo "--------------------------------" 255 | echo " Schedule Type " 256 | echo "--------------------------------" 257 | echo " 1. Startup" 258 | echo " 2. Shutdown" 259 | echo 260 | echo " 0. Cancel" 261 | echo -n "Enter Number (0-2):" 262 | 263 | cmdmode=$( get_number ) 264 | if [ $cmdmode -eq 0 ] 265 | then 266 | subloopflag=0 267 | elif [[ $cmdmode -ge 1 && $cmdmode -le 2 ]] 268 | then 269 | cmdcode="on" 270 | echo "--------------------------------" 271 | if [ $cmdmode -eq 1 ] 272 | then 273 | echo " Schedule Startup" 274 | else 275 | echo " Schedule Shutdown" 276 | cmdcode="off" 277 | fi 278 | echo "--------------------------------" 279 | echo "Select Schedule:" 280 | echo " 1. Hourly" 281 | echo " 2. Daily" 282 | echo " 3. Weekly" 283 | echo " 4. Monthly" 284 | echo 285 | echo " 0. Back" 286 | echo -n "Enter Number (0-4):" 287 | 288 | newmode=$( get_number ) 289 | if [[ $newmode -ge 1 && $newmode -le 4 ]] 290 | then 291 | echo "" 292 | if [ $cmdmode -eq 1 ] 293 | then 294 | echo "New Startup Schedule" 295 | else 296 | echo "New Shutdown Schedule" 297 | fi 298 | 299 | if [ $newmode -eq 1 ] 300 | then 301 | echo -n "Enter Minute (0-59):" 302 | minute=$( get_number ) 303 | if [[ $minute -ge 0 && $minute -le 59 ]] 304 | then 305 | echo "$minute * * * * $cmdcode" >> $rtcconfigfile 306 | sudo systemctl restart argoneond.service 307 | subloopflag=0 308 | else 309 | echo "Invalid value" 310 | fi 311 | elif [ $newmode -eq 2 ] 312 | then 313 | echo -n "Enter Hour (0-23):" 314 | hour=$( get_number ) 315 | echo -n "Enter Minute (0-59):" 316 | minute=$( get_number ) 317 | if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 ]] 318 | then 319 | echo "$minute $hour * * * $cmdcode" >> $rtcconfigfile 320 | sudo systemctl restart argoneond.service 321 | subloopflag=0 322 | else 323 | echo "Invalid value(s)" 324 | fi 325 | elif [ $newmode -eq 3 ] 326 | then 327 | echo "Select Day of the Week:" 328 | echo " 0. Sunday" 329 | echo " 1. Monday" 330 | echo " 2. Tuesday" 331 | echo " 3. Wednesday" 332 | echo " 4. Thursday" 333 | echo " 5. Friday" 334 | echo " 6. Saturday" 335 | 336 | echo -n "Enter Number (0-6):" 337 | weekday=$( get_number ) 338 | echo -n "Enter Hour (0-23):" 339 | hour=$( get_number ) 340 | echo -n "Enter Minute (0-59):" 341 | minute=$( get_number ) 342 | 343 | if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 && $weekday -ge 0 && $weekday -le 6 ]] 344 | then 345 | echo "$minute $hour * * $weekday $cmdcode" >> $rtcconfigfile 346 | sudo systemctl restart argoneond.service 347 | subloopflag=0 348 | else 349 | echo "Invalid value(s)" 350 | fi 351 | elif [ $newmode -eq 4 ] 352 | then 353 | echo -n "Enter Date (1-31):" 354 | monthday=$( get_number ) 355 | if [[ $monthday -ge 29 ]] 356 | then 357 | echo "WARNING: This schedule will not trigger for certain months" 358 | fi 359 | echo -n "Enter Hour (0-23):" 360 | hour=$( get_number ) 361 | echo -n "Enter Minute (0-59):" 362 | minute=$( get_number ) 363 | 364 | if [[ $minute -ge 0 && $minute -le 59 && $hour -ge 0 && $hour -le 23 && $monthday -ge 1 && $monthday -le 31 ]] 365 | then 366 | echo "$minute $hour $monthday * * $cmdcode" >> $rtcconfigfile 367 | sudo systemctl restart argoneond.service 368 | subloopflag=0 369 | else 370 | echo "Invalid value(s)" 371 | fi 372 | fi 373 | fi 374 | fi 375 | done 376 | } 377 | 378 | rtcloopflag=1 379 | while [ $rtcloopflag -eq 1 ] 380 | do 381 | echo "----------------------------" 382 | echo "Argon RTC Configuration Tool" 383 | echo "----------------------------" 384 | $pythonbin $argoneonrtcscript GETRTCTIME 385 | echo "Choose from the list:" 386 | echo " 1. Update RTC Time" 387 | echo " 2. Configure Startup/Shutdown Schedules" 388 | echo 389 | echo " 0. Exit" 390 | echo -n "Enter Number (0-2):" 391 | 392 | newmode=$( get_number ) 393 | if [ $newmode -eq 0 ] 394 | then 395 | rtcloopflag=0 396 | elif [[ $newmode -ge 1 && $newmode -le 2 ]] 397 | then 398 | if [ $newmode -eq 1 ] 399 | then 400 | echo "Matching RTC Time to System Time..." 401 | $pythonbin $argoneonrtcscript UPDATERTCTIME 402 | elif [ $newmode -eq 2 ] 403 | then 404 | configure_schedule 405 | fi 406 | fi 407 | done 408 | 409 | echo 410 | -------------------------------------------------------------------------------- /src/argoneond.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | 4 | import sys 5 | import datetime 6 | import math 7 | 8 | import os 9 | import time 10 | 11 | # Initialize I2C Bus 12 | import smbus 13 | import RPi.GPIO as GPIO 14 | 15 | rev = GPIO.RPI_REVISION 16 | if rev == 2 or rev == 3: 17 | bus=smbus.SMBus(1) 18 | else: 19 | bus=smbus.SMBus(0) 20 | 21 | 22 | ADDR_RTC=0x51 23 | 24 | ################# 25 | # Common/Helpers 26 | ################# 27 | 28 | RTC_CONFIGFILE = "/etc/argoneonrtc.conf" 29 | 30 | RTC_ALARM_BIT = 0x8 31 | RTC_TIMER_BIT = 0x4 32 | 33 | # PCF8563 number system Binary Coded Decimal (BCD) 34 | 35 | # BCD to Decimal 36 | def numBCDtoDEC(val): 37 | return (val & 0xf)+(((val >> 4) & 0xf)*10) 38 | 39 | # Decimal to BCD 40 | def numDECtoBCD(val): 41 | return (math.floor(val/10)<<4) + (val % 10) 42 | 43 | # Check if Event Bit is raised 44 | def hasRTCEventFlag(flagbit): 45 | bus.write_byte(ADDR_RTC,1) 46 | out = bus.read_byte_data(ADDR_RTC, 1) 47 | return (out & flagbit) != 0 48 | 49 | # Clear Event Bit if raised 50 | def clearRTCEventFlag(flagbit): 51 | out = bus.read_byte_data(ADDR_RTC, 1) 52 | if (out & flagbit) != 0: 53 | # Unset only if fired 54 | bus.write_byte_data(ADDR_RTC, 1, out&(0xff-flagbit)) 55 | return True 56 | return False 57 | 58 | # Enable Event Flag 59 | def setRTCEventFlag(flagbit, enabled): 60 | # 0x10 = TI_TP flag, 0 by default 61 | ti_tp_flag = 0x10 62 | # flagbit=0x4 for timer flag, 0x1 for enable timer flag 63 | # flagbit=0x8 for alarm flag, 0x2 for enable alarm flag 64 | enableflagbit = flagbit>>2 65 | disableflagbit = 0 66 | if enabled == False: 67 | disableflagbit = enableflagbit 68 | enableflagbit = 0 69 | 70 | out = bus.read_byte_data(ADDR_RTC, 1) 71 | bus.write_byte_data(ADDR_RTC, 1, (out&(0xff-flagbit-disableflagbit - ti_tp_flag))|enableflagbit) 72 | 73 | # Helper method to add proper suffix to numbers 74 | def getNumberSuffix(numval): 75 | onesvalue = numval % 10 76 | if onesvalue == 1: 77 | return "st" 78 | elif onesvalue == 2: 79 | return "nd" 80 | elif onesvalue == 3: 81 | return "rd" 82 | return "th" 83 | 84 | # Describe Timer Setting 85 | def describeTimer(showsetting): 86 | out = bus.read_byte_data(ADDR_RTC, 14) 87 | tmp = out & 3 88 | if tmp == 3: 89 | outstr = " Minute(s)" 90 | elif tmp == 2: 91 | outstr = " Second(s)" 92 | elif tmp == 1: 93 | outstr = "/64th Second" 94 | elif tmp == 0: 95 | outstr = "/4096th Second" 96 | 97 | if (out & 0x80) != 0: 98 | out = bus.read_byte_data(ADDR_RTC, 15) 99 | return "Every "+(numBCDtoDEC(out)+1)+outstr 100 | elif showsetting == True: 101 | return "Disabled (Interval every 1"+outstr+")" 102 | # Setting might matter to save resources 103 | return "None" 104 | 105 | 106 | def describeHourMinute(hour, minute): 107 | 108 | if hour < 0: 109 | return "" 110 | 111 | outstr = "" 112 | ampmstr = "" 113 | if hour <= 0: 114 | hour = 0 115 | outstr = outstr + "12" 116 | ampmstr = "am" 117 | elif hour <= 12: 118 | outstr = outstr + str(hour) 119 | if hour == 12: 120 | ampmstr = "pm" 121 | else: 122 | ampmstr = "am" 123 | else: 124 | outstr = outstr + str(hour-12) 125 | ampmstr = "pm" 126 | 127 | if minute >= 10: 128 | outstr = outstr+":" 129 | elif minute > 0: 130 | outstr = outstr+":0" 131 | else: 132 | if hour == 0: 133 | ampmstr = "mn" 134 | elif hour == 12: 135 | ampmstr = "nn" 136 | return outstr+ampmstr 137 | 138 | if minute <= 0: 139 | minute = 0 140 | outstr = outstr+str(minute) 141 | 142 | return outstr+ampmstr 143 | 144 | # Describe Schedule Parameter Values 145 | def describeSchedule(monthlist, weekdaylist, datelist, hourlist, minutelist): 146 | weekdaynamelist = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] 147 | monthnamelist = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] 148 | 149 | curprefix = "" 150 | hasDate = False 151 | hasMonth = False 152 | foundvalue = False 153 | monthdatestr = "" 154 | for curmonth in monthlist: 155 | for curdate in datelist: 156 | if curdate >= 0: 157 | hasDate = True 158 | if curmonth >= 0: 159 | hasMonth = True 160 | monthdatestr = monthdatestr + "," + monthnamelist[curmonth-1]+" "+str(curdate) + getNumberSuffix(curdate) 161 | else: 162 | monthdatestr = monthdatestr + ","+str(curdate) + getNumberSuffix(curdate) 163 | else: 164 | if curmonth >= 0: 165 | monthdatestr = monthdatestr + "," + monthnamelist[curmonth-1] 166 | 167 | if len(monthdatestr) > 0: 168 | foundvalue = True 169 | # Remove Leading Comma 170 | monthdatestr = monthdatestr[1:] 171 | if hasMonth == True: 172 | curprefix = "Annually:" 173 | else: 174 | curprefix = "Monthly:" 175 | monthdatestr = monthdatestr + " of the Month" 176 | monthdatestr = " Every "+monthdatestr 177 | 178 | weekdaystr = "" 179 | for curweekday in weekdaylist: 180 | if curweekday >= 0: 181 | hasDate = True 182 | weekdaystr = weekdaystr + "," + weekdaynamelist[curweekday] 183 | 184 | if len(weekdaystr) > 0: 185 | foundvalue = True 186 | # Remove Leading Comma 187 | weekdaystr = weekdaystr[1:] 188 | if len(curprefix) == 0: 189 | curprefix = "Weekly:" 190 | weekdaystr = " on " + weekdaystr 191 | else: 192 | weekdaystr = ",on " + weekdaystr 193 | 194 | hasHour = False 195 | hasMinute = False 196 | hourminstr = "" 197 | for curhour in hourlist: 198 | for curminute in minutelist: 199 | if curhour >= 0: 200 | hasHour = True 201 | if curminute >= 0: 202 | hasMinute = True 203 | hourminstr = hourminstr + "," + describeHourMinute(curhour, curminute) 204 | elif curminute >= 0: 205 | hasMinute = True 206 | hourminstr = hourminstr + "," + str(curminute) + getNumberSuffix(curminute) 207 | 208 | if len(hourminstr) > 0: 209 | foundvalue = True 210 | # Remove Leading Comma 211 | hourminstr = hourminstr[1:] 212 | if hasHour == True: 213 | if hasDate == True: 214 | hourminstr = "at " + hourminstr 215 | else: 216 | hourminstr = "Daily: " + hourminstr 217 | if hasMinute == False: 218 | hourminstr = hourminstr + " every minute" 219 | else: 220 | if hourminstr == "0": 221 | hourminstr = "At the start of every hour" 222 | else: 223 | hourminstr = "Hourly: At " + hourminstr + " minute" 224 | else: 225 | hourminstr = "Every minute" 226 | 227 | if len(curprefix) > 0: 228 | hourminstr = ","+hourminstr 229 | 230 | return (curprefix + monthdatestr + weekdaystr + hourminstr).strip() 231 | 232 | 233 | # Describe Alarm Setting 234 | def describeAlarm(): 235 | minute = -1 236 | hour = -1 237 | date = -1 238 | weekday = -1 239 | 240 | out = bus.read_byte_data(ADDR_RTC, 9) 241 | if (out & 0x80) == 0: 242 | minute = numBCDtoDEC(out & 0x7f) 243 | 244 | out = bus.read_byte_data(ADDR_RTC, 10) 245 | if (out & 0x80) == 0: 246 | hour = numBCDtoDEC(out & 0x3f) 247 | 248 | out = bus.read_byte_data(ADDR_RTC, 11) 249 | if (out & 0x80) == 0: 250 | date = numBCDtoDEC(out & 0x3f) 251 | 252 | out = bus.read_byte_data(ADDR_RTC, 12) 253 | if (out & 0x80) == 0: 254 | weekday = numBCDtoDEC(out & 0x7) 255 | 256 | if weekday < 0 and date < 0 and hour < 0 and minute < 0: 257 | return "None" 258 | 259 | # Convert from UTC 260 | utcschedule = describeSchedule([-1], [weekday], [date], [hour], [minute]) 261 | weekday, date, hour, minute = convertAlarmTimezone(weekday, date, hour, minute, False) 262 | 263 | return describeSchedule([-1], [weekday], [date], [hour], [minute]) + " Local (RTC Schedule: "+utcschedule+" UTC)" 264 | 265 | 266 | # Describe Control Flags 267 | def describeControlRegisters(): 268 | out = bus.read_byte_data(ADDR_RTC, 1) 269 | 270 | print("\n***************") 271 | print("Control Status 2") 272 | print("\tTI_TP Flag:", ((out & 0x10) != 0)) 273 | print("\tAlarm Flag:", ((out & RTC_ALARM_BIT) != 0),"( Enabled =", (out & (RTC_ALARM_BIT>>2)) != 0, ")") 274 | print("\tTimer Flag:", ((out & RTC_TIMER_BIT) != 0),"( Enabled =", (out & (RTC_TIMER_BIT>>2)) != 0, ")") 275 | 276 | print("Alarm Setting:") 277 | print("\t"+describeAlarm()) 278 | 279 | print("Timer Setting:") 280 | print("\t"+describeTimer(True)) 281 | 282 | print("***************\n") 283 | 284 | 285 | ######### 286 | # Alarm 287 | ######### 288 | 289 | # Alarm to UTC/Local time 290 | def convertAlarmTimezone(weekday, date, hour, minute, toutc): 291 | utcdiffsec = getLocaltimeOffset().seconds 292 | if toutc == False: 293 | utcdiffsec = utcdiffsec*(-1) 294 | 295 | utcdiffsec = utcdiffsec - (utcdiffsec%60) 296 | utcdiffmin = utcdiffsec % 3600 297 | utcdiffhour = int((utcdiffsec - utcdiffmin)/3600) 298 | utcdiffmin = int(utcdiffmin/60) 299 | 300 | addhour = 0 301 | if minute >= 0: 302 | minute = minute - utcdiffmin 303 | if minute < 0: 304 | addhour = -1 305 | minute = minute + 60 306 | elif minute > 59: 307 | addhour = 1 308 | minute = minute - 60 309 | 310 | addday = 0 311 | if hour >= 0: 312 | hour = hour - utcdiffhour 313 | tmphour = hour + addhour 314 | if hour < 0: 315 | hour = hour + 24 316 | elif hour > 23: 317 | hour = hour - 24 318 | if tmphour < 0: 319 | addday = -1 320 | elif tmphour > 23: 321 | addday = 1 322 | 323 | if addday != 0: 324 | if weekday >= 0: 325 | weekday = weekday + addday 326 | if weekday < 0: 327 | weekday = weekday + 7 328 | elif weekday > 6: 329 | weekday = weekday - 7 330 | if date > 0: 331 | # Edge cases might not be handled properly though 332 | curtime = datetime.datetime.now() 333 | maxmonthdate = getLastMonthDate(curtime.year, curtime.month) 334 | date = date + addday 335 | if date == 0: 336 | # move to end of the month 337 | date = maxmonthdate 338 | elif date > maxmonthdate: 339 | # move to next month 340 | date = 1 341 | 342 | return [weekday, date, hour, minute] 343 | 344 | # Check if RTC Alarm Flag is ON 345 | def hasRTCAlarmFlag(): 346 | return hasRTCEventFlag(RTC_ALARM_BIT) 347 | 348 | # Clear RTC Alarm Flag 349 | def clearRTCAlarmFlag(): 350 | return clearRTCEventFlag(RTC_ALARM_BIT) 351 | 352 | # Enables RTC Alarm Register 353 | def enableAlarm(registeraddr, value, mask): 354 | # 0x00 is Enabled 355 | bus.write_byte_data(ADDR_RTC, registeraddr, (numDECtoBCD(value)&mask)) 356 | 357 | # Disables RTC Alarm Register 358 | def disableAlarm(registeraddr): 359 | # 0x80 is disabled 360 | bus.write_byte_data(ADDR_RTC, registeraddr, 0x80) 361 | 362 | # Removes all alarm settings 363 | def removeRTCAlarm(): 364 | setRTCEventFlag(RTC_ALARM_BIT, False) 365 | 366 | disableAlarm(9) 367 | disableAlarm(10) 368 | disableAlarm(11) 369 | disableAlarm(12) 370 | 371 | # Set RTC Alarm (Negative values ignored) 372 | def setRTCAlarm(enableflag, weekday, date, hour, minute): 373 | if date < 1 and weekday < 0 and hour < 0 and minute < 0: 374 | return -1 375 | elif minute > 59: 376 | return -1 377 | elif hour > 23: 378 | return -1 379 | elif weekday > 6: 380 | return -1 381 | elif date > 31: 382 | return -1 383 | 384 | # Convert to UTC 385 | weekday, date, hour, minute = convertAlarmTimezone(weekday, date, hour, minute, True) 386 | 387 | clearRTCAlarmFlag() 388 | setRTCEventFlag(RTC_ALARM_BIT, enableflag) 389 | 390 | if minute >= 0: 391 | enableAlarm(9, minute, 0x7f) 392 | else: 393 | disableAlarm(9) 394 | 395 | if hour >= 0: 396 | enableAlarm(10, hour, 0x7f) 397 | else: 398 | disableAlarm(10) 399 | 400 | if date >= 0: 401 | enableAlarm(11, date, 0x7f) 402 | else: 403 | disableAlarm(11) 404 | 405 | if weekday >= 0: 406 | enableAlarm(12, weekday, 0x7f) 407 | else: 408 | disableAlarm(12) 409 | 410 | return 0 411 | 412 | # Set RTC Hourly Alarm 413 | def setRTCAlarmHourly(enableflag, minute): 414 | return setRTCAlarm(enableflag, -1, -1, -1, minute) 415 | 416 | # Set RTC Daily Alarm 417 | def setRTCAlarmDaily(enableflag, hour, minute): 418 | return setRTCAlarm(enableflag, -1, -1, hour, minute) 419 | 420 | # Set RTC Weekly Alarm 421 | def setRTCAlarmWeekly(enableflag, dayofweek, hour, minute): 422 | return setRTCAlarm(enableflag, dayofweek, -1, hour, minute) 423 | 424 | # Set RTC Monthly Alarm 425 | def setRTCAlarmMonthly(enableflag, date, hour, minute): 426 | return setRTCAlarm(enableflag, -1, date, hour, minute) 427 | 428 | ######### 429 | # Timer 430 | ######### 431 | 432 | # Check if RTC Timer Flag is ON 433 | def hasRTCTimerFlag(): 434 | return hasRTCEventFlag(RTC_TIMER_BIT) 435 | 436 | # Clear RTC Timer Flag 437 | def clearRTCTimerFlag(): 438 | return clearRTCEventFlag(RTC_TIMER_BIT) 439 | 440 | # Remove RTC Timer Setting 441 | def removeRTCTimer(): 442 | setRTCEventFlag(RTC_TIMER_BIT, False) 443 | 444 | # Timer disable and Set Timer frequency to lowest (0x3=1 per minute) 445 | bus.write_byte_data(ADDR_RTC, 14, 3) 446 | bus.write_byte_data(ADDR_RTC, 15, 0) 447 | 448 | # Set RTC Timer Interval 449 | def setRTCTimerInterval(enableflag, value, inSeconds = False): 450 | if value > 255 or value < 1: 451 | return -1 452 | clearRTCTimerFlag() 453 | setRTCEventFlag(RTC_TIMER_BIT, enableflag) 454 | 455 | # 0x80 Timer Enabled, mode: 0x3=1/Min, 0x2=1/Sec, 0x1=Per 64th Sec, 0=Per 4096th Sec 456 | timerconfigFlag = 0x83 457 | if inSeconds == True: 458 | timerconfigFlag = 0x82 459 | 460 | bus.write_byte_data(ADDR_RTC, 14, timerconfigFlag) 461 | bus.write_byte_data(ADDR_RTC, 15, numDECtoBCD(value&0xff)) 462 | return 0 463 | 464 | ############# 465 | # Date/Time 466 | ############# 467 | 468 | # Get local time vs UTC 469 | def getLocaltimeOffset(): 470 | localdatetime = datetime.datetime.now() 471 | utcdatetime = datetime.datetime.fromtimestamp(localdatetime.timestamp(), datetime.timezone.utc) 472 | # Remove TZ info to allow subtraction 473 | utcdatetime = utcdatetime.replace(tzinfo = None) 474 | 475 | return localdatetime - utcdatetime 476 | 477 | # Returns RTC timestamp as datetime object 478 | def getRTCdatetime(): 479 | 480 | # Data Sheet Recommends to read this manner (instead of from registers) 481 | bus.write_byte(ADDR_RTC,2) 482 | 483 | out = bus.read_byte(ADDR_RTC) 484 | out = numBCDtoDEC(out & 0x7f) 485 | second = out 486 | #warningflag = (out & 0x80)>>7 487 | 488 | out = bus.read_byte(ADDR_RTC) 489 | minute = numBCDtoDEC(out & 0x7f) 490 | 491 | out = bus.read_byte(ADDR_RTC) 492 | hour = numBCDtoDEC(out & 0x3f) 493 | 494 | out = bus.read_byte(ADDR_RTC) 495 | date = numBCDtoDEC(out & 0x3f) 496 | 497 | out = bus.read_byte(ADDR_RTC) 498 | #weekDay = numBCDtoDEC(out & 7) 499 | 500 | out = bus.read_byte(ADDR_RTC) 501 | month = numBCDtoDEC(out & 0x1f) 502 | 503 | out = bus.read_byte(ADDR_RTC) 504 | year = numBCDtoDEC(out) 505 | 506 | #print({"year":year, "month": month, "date": date, "hour": hour, "minute": minute, "second": second}) 507 | 508 | if month == 0: 509 | # Reset, uninitialized RTC 510 | month = 1 511 | 512 | # Timezone is GMT/UTC +0 513 | # Year is from 2000 514 | try: 515 | return datetime.datetime(year+2000, month, date, hour, minute, second)+getLocaltimeOffset() 516 | except: 517 | return datetime.datetime(2000, 1, 1, 0, 0, 0) 518 | 519 | # set RTC time using datetime object (Local time) 520 | def setRTCdatetime(localdatetime): 521 | # Set local time to UTC 522 | localdatetime = localdatetime - getLocaltimeOffset() 523 | 524 | # python Sunday = 6, RTC Sunday = 0 525 | weekDay = localdatetime.weekday() 526 | if weekDay == 6: 527 | weekDay = 0 528 | else: 529 | weekDay = weekDay + 1 530 | 531 | # Write to respective registers 532 | bus.write_byte_data(ADDR_RTC,2,numDECtoBCD(localdatetime.second)) 533 | bus.write_byte_data(ADDR_RTC,3,numDECtoBCD(localdatetime.minute)) 534 | bus.write_byte_data(ADDR_RTC,4,numDECtoBCD(localdatetime.hour)) 535 | bus.write_byte_data(ADDR_RTC,5,numDECtoBCD(localdatetime.day)) 536 | bus.write_byte_data(ADDR_RTC,6,numDECtoBCD(weekDay)) 537 | bus.write_byte_data(ADDR_RTC,7,numDECtoBCD(localdatetime.month)) 538 | 539 | # Year is from 2000 540 | bus.write_byte_data(ADDR_RTC,8,numDECtoBCD(localdatetime.year-2000)) 541 | 542 | # Sync Time to RTC Time (for Daemon use) 543 | def syncSystemTime(): 544 | rtctime = getRTCdatetime() 545 | os.system("date -s '"+rtctime.isoformat()+"' >/dev/null 2>&1") 546 | 547 | 548 | ######### 549 | # Config 550 | ######### 551 | 552 | # Load config value as array of integers 553 | def getConfigValue(valuestr): 554 | try: 555 | if valuestr == "*": 556 | return [-1] 557 | tmplist = valuestr.split(",") 558 | map_object = map(int, tmplist) 559 | return list(map_object) 560 | except: 561 | return [-1] 562 | 563 | # Load config line data as array of Command schedule 564 | def newCommandSchedule(curline): 565 | result = [] 566 | linedata = curline.split(" ") 567 | if len(linedata) < 6: 568 | return result 569 | 570 | minutelist = getConfigValue(linedata[0]) 571 | hourlist = getConfigValue(linedata[1]) 572 | datelist = getConfigValue(linedata[2]) 573 | #monthlist = getConfigValue(linedata[3]) 574 | monthlist = [-1] # Certain edge cases will not be handled properly 575 | weekdaylist = getConfigValue(linedata[4]) 576 | 577 | cmd = "" 578 | ctr = 5 579 | while ctr < len(linedata): 580 | cmd = cmd + " " + linedata[ctr] 581 | ctr = ctr + 1 582 | cmd = cmd.strip() 583 | 584 | for curmin in minutelist: 585 | for curhour in hourlist: 586 | for curdate in datelist: 587 | for curmonth in monthlist: 588 | for curweekday in weekdaylist: 589 | result.append({ "minute": curmin, "hour": curhour, "date": curdate, "month":curmonth, "weekday": curweekday, "cmd":cmd }) 590 | 591 | return result 592 | 593 | # Save updated config file 594 | def saveConfigList(fname, configlist): 595 | f = open(fname, "w") 596 | f.write("#\n") 597 | f.write("# Argon RTC Configuration\n") 598 | f.write("# - Follows cron general format, but with only * and csv support\n") 599 | f.write("# - Each row follows the following format:\n") 600 | f.write("# min hour date month dayOfWeek Command\n") 601 | f.write("# e.g. Shutdown daily at 1am\n") 602 | f.write("# 0 1 * * * off\n") 603 | f.write("# Shutdown daily at 1am and 1pm\n") 604 | f.write("# 0 1,13 * * * off\n") 605 | f.write("# - Commands are currently on or off only\n") 606 | f.write("# - Limititations\n") 607 | f.write("# Requires MINUTE value\n") 608 | f.write("# Month values are ignored (edge cases not supported)\n") 609 | f.write("#\n") 610 | 611 | for config in configlist: 612 | f.write(config+"\n") 613 | f.close() 614 | 615 | # Remove config line 616 | def removeConfigEntry(fname, entryidx): 617 | configlist = loadConfigList(fname) 618 | if len(configlist) > entryidx: 619 | configlist.pop(entryidx) 620 | saveConfigList(fname, configlist) 621 | 622 | # Load config list (removes invalid data) 623 | def loadConfigList(fname): 624 | try: 625 | result = [] 626 | with open(fname, "r") as fp: 627 | for curline in fp: 628 | if not curline: 629 | continue 630 | curline = curline.strip().replace('\t', ' ') 631 | # Handle special characters that get encoded 632 | tmpline = "".join([c if 0x20<=ord(c) and ord(c)<=0x7e else "" for c in curline]) 633 | 634 | if not tmpline: 635 | continue 636 | if tmpline[0] == "#": 637 | continue 638 | checkdata = tmpline.split(" ") 639 | if len(checkdata) > 5: 640 | # Don't include every minute type of schedule 641 | if checkdata[0] != "*": 642 | result.append(tmpline) 643 | return result 644 | except: 645 | return [] 646 | 647 | # Form Command Schedule list from config list 648 | def formCommandScheduleList(configlist): 649 | try: 650 | result = [] 651 | for config in configlist: 652 | result = result + newCommandSchedule(config) 653 | return result 654 | except: 655 | return [] 656 | 657 | # Describe config list entry 658 | def describeConfigListEntry(configlistitem): 659 | linedata = configlistitem.split(" ") 660 | if len(linedata) < 6: 661 | return "" 662 | 663 | minutelist = getConfigValue(linedata[0]) 664 | hourlist = getConfigValue(linedata[1]) 665 | datelist = getConfigValue(linedata[2]) 666 | #monthlist = getConfigValue(linedata[3]) 667 | monthlist = [-1] # Certain edge cases will not be handled properly 668 | weekdaylist = getConfigValue(linedata[4]) 669 | 670 | cmd = "" 671 | ctr = 5 672 | while ctr < len(linedata): 673 | cmd = cmd + " " + linedata[ctr] 674 | ctr = ctr + 1 675 | cmd = cmd.strip().lower() 676 | if cmd == "on": 677 | cmd = "Startup" 678 | else: 679 | cmd = "Shutdown" 680 | 681 | return cmd+" | "+describeSchedule(monthlist, weekdaylist, datelist, hourlist, minutelist) 682 | 683 | # Describe config list and show indices 684 | def describeConfigList(fname): 685 | # 1 is reserved for New schedule 686 | ctr = 2 687 | configlist = loadConfigList(fname) 688 | for config in configlist: 689 | tmpline = describeConfigListEntry(config) 690 | if len(tmpline) > 0: 691 | print(" "+str(ctr)+". ", tmpline) 692 | ctr = ctr + 1 693 | if ctr == 2: 694 | print(" No Existing Schedules") 695 | 696 | # Check Command schedule if it should fire for the give time 697 | def checkDateForCommandSchedule(commandschedule, datetimeobj): 698 | testminute = commandschedule.get("minute", -1) 699 | testhour = commandschedule.get("hour", -1) 700 | testdate = commandschedule.get("date", -1) 701 | testmonth = commandschedule.get("month", -1) 702 | testweekday = commandschedule.get("weekday", -1) 703 | 704 | if testminute < 0 or testminute == datetimeobj.minute: 705 | if testhour < 0 or testhour == datetimeobj.hour: 706 | if testdate < 0 or testdate == datetimeobj.day: 707 | if testmonth < 0 or testmonth == datetimeobj.month: 708 | if testweekday < 0: 709 | return True 710 | else: 711 | # python Sunday = 6, RTC Sunday = 0 712 | weekDay = datetimeobj.weekday() 713 | if weekDay == 6: 714 | weekDay = 0 715 | else: 716 | weekDay = weekDay + 1 717 | if testweekday == weekDay: 718 | return True 719 | return False 720 | 721 | # Get current command 722 | def getCommandForTime(commandschedulelist, datetimeobj, checkcmd): 723 | ctr = 0 724 | while ctr < len(commandschedulelist): 725 | testcmd = commandschedulelist[ctr].get("cmd", "") 726 | if (testcmd.lower() == checkcmd or len(checkcmd) == 0) and len(testcmd) > 0: 727 | if checkDateForCommandSchedule(commandschedulelist[ctr], datetimeobj) == True: 728 | return testcmd 729 | ctr = ctr + 1 730 | return "" 731 | 732 | # Get Last Date of Month 733 | def getLastMonthDate(year, month): 734 | if month < 12: 735 | testtime = datetime.datetime(year, month+1, 1) 736 | else: 737 | testtime = datetime.datetime(year+1, 1, 1) 738 | testtime = testtime - datetime.timedelta(days=1) 739 | return testtime.day 740 | 741 | # Increment to the next iteration of command schedule 742 | def incrementCommandScheduleTime(commandschedule, testtime, addmode): 743 | testminute = commandschedule.get("minute", -1) 744 | testhour = commandschedule.get("hour", -1) 745 | testdate = commandschedule.get("date", -1) 746 | testmonth = commandschedule.get("month", -1) 747 | testweekday = commandschedule.get("weekday", -1) 748 | 749 | if addmode == "minute": 750 | testfield = commandschedule.get(addmode, -1) 751 | if testfield < 0: 752 | if testtime.minute < 59: 753 | return testtime + datetime.timedelta(minutes=1) 754 | else: 755 | return incrementCommandScheduleTime(commandschedule, testtime.replace(minute=0), "hour") 756 | else: 757 | return incrementCommandScheduleTime(commandschedule, testtime, "hour") 758 | elif addmode == "hour": 759 | testfield = commandschedule.get(addmode, -1) 760 | if testfield < 0: 761 | if testtime.hour < 23: 762 | return testtime + datetime.timedelta(hours=1) 763 | else: 764 | return incrementCommandScheduleTime(commandschedule, testtime.replace(hour=0), "date") 765 | else: 766 | return incrementCommandScheduleTime(commandschedule, testtime, "date") 767 | elif addmode == "date": 768 | testfield = commandschedule.get(addmode, -1) 769 | if testfield < 0: 770 | maxmonthdate = getLastMonthDate(testtime.year, testtime.month) 771 | if testtime.day < maxmonthdate: 772 | return testtime + datetime.timedelta(days=1) 773 | else: 774 | return incrementCommandScheduleTime(commandschedule, testtime.replace(day=1), "month") 775 | else: 776 | return incrementCommandScheduleTime(commandschedule, testtime, "month") 777 | elif addmode == "month": 778 | testfield = commandschedule.get(addmode, -1) 779 | if testfield < 0: 780 | nextmonth = testtime.month 781 | nextyear = testtime.year 782 | while True: 783 | if nextmonth < 12: 784 | nextmonth = nextmonth + 1 785 | else: 786 | nextmonth = 1 787 | nextyear = nextyear + 1 788 | maxmonthdate = getLastMonthDate(nextyear, nextmonth) 789 | if testtime.day <= maxmonthdate: 790 | return testtime.replace(month=nextmonth, year=nextyear) 791 | else: 792 | return incrementCommandScheduleTime(commandschedule, testtime, "year") 793 | else: 794 | # Year 795 | if testtime.month == 2 and testtime.day == 29: 796 | # Leap day handling 797 | nextyear = testtime.year 798 | while True: 799 | nextyear = nextyear + 1 800 | maxmonthdate = getLastMonthDate(nextyear, testtime.month) 801 | if testtime.day <= maxmonthdate: 802 | return testtime.replace(year=nextyear) 803 | else: 804 | return testtime.replace(year=(testtime.year+1)) 805 | 806 | # Set Next Alarm on RTC 807 | def setNextAlarm(commandschedulelist, prevdatetime): 808 | curtime = datetime.datetime.now() 809 | if prevdatetime > curtime: 810 | return prevdatetime 811 | 812 | # Divisible by 4 for leap day 813 | checklimityears = 12 814 | foundnextcmd = False 815 | nextcommandschedule = {} 816 | # To be sure it's later than any schedule 817 | nextcommandtime = curtime.replace(year=(curtime.year+checklimityears)) 818 | 819 | ctr = 0 820 | while ctr < len(commandschedulelist): 821 | testcmd = commandschedulelist[ctr].get("cmd", "").lower() 822 | if testcmd == "on": 823 | invaliddata = False 824 | testminute = commandschedulelist[ctr].get("minute", -1) 825 | testhour = commandschedulelist[ctr].get("hour", -1) 826 | testdate = commandschedulelist[ctr].get("date", -1) 827 | testmonth = commandschedulelist[ctr].get("month", -1) 828 | testweekday = commandschedulelist[ctr].get("weekday", -1) 829 | 830 | tmpminute = testminute 831 | tmphour = testhour 832 | tmpdate = testdate 833 | tmpmonth = testmonth 834 | tmpyear = curtime.year 835 | 836 | if tmpminute < 0: 837 | tmpminute = curtime.minute 838 | 839 | if tmphour < 0: 840 | tmphour = curtime.hour 841 | 842 | if tmpdate < 0: 843 | tmpdate = curtime.day 844 | 845 | if tmpmonth < 0: 846 | tmpmonth = curtime.month 847 | 848 | maxmonthdate = getLastMonthDate(tmpyear, tmpmonth) 849 | if tmpdate > maxmonthdate: 850 | # Invalid month date 851 | if testdate < 0: 852 | tmpdate = maxmonthdate 853 | else: 854 | # Date is fixed 855 | if testminute < 0: 856 | tmpminute = 0 857 | if testhour < 0: 858 | tmphour = 0 859 | if testmonth < 0 and testdate <= 31: 860 | # Look for next valid month 861 | while tmpdate > maxmonthdate: 862 | if tmpmonth < 12: 863 | tmpmonth = tmpmonth + 1 864 | else: 865 | tmpmonth = 1 866 | tmpyear = tmpyear + 1 867 | maxmonthdate = getLastMonthDate(tmpyear, tmpmonth) 868 | elif tmpdate == 29 and tmpmonth == 2: 869 | # Fixed to leap day 870 | while tmpdate > maxmonthdate: 871 | tmpyear = tmpyear + 1 872 | maxmonthdate = getLastMonthDate(tmpyear, tmpmonth) 873 | else: 874 | invaliddata = True 875 | if invaliddata == False: 876 | try: 877 | testtime = datetime.datetime(tmpyear, tmpmonth, tmpdate, tmphour, tmpminute) 878 | except: 879 | # Force time diff 880 | testtime = curtime - datetime.timedelta(hours=1) 881 | tmptimediff = (curtime - testtime).total_seconds() 882 | else: 883 | tmptimediff = 0 884 | 885 | if testweekday >= 0: 886 | # Day of Week check 887 | # python Sunday = 6, RTC Sunday = 0 888 | weekDay = testtime.weekday() 889 | if weekDay == 6: 890 | weekDay = 0 891 | else: 892 | weekDay = weekDay + 1 893 | 894 | 895 | if weekDay != testweekday or tmptimediff > 0: 896 | # Resulting 0-ed time will be <= the testtime 897 | if testminute < 0: 898 | testtime = testtime.replace(minute=0) 899 | if testhour < 0: 900 | testtime = testtime.replace(hour=0) 901 | 902 | dayoffset = testweekday-weekDay 903 | if dayoffset < 0: 904 | dayoffset = dayoffset + 7 905 | elif dayoffset == 0: 906 | dayoffset = 7 907 | 908 | testtime = testtime + datetime.timedelta(days=dayoffset) 909 | 910 | # Just look for the next valid weekday; Can be optimized 911 | while checkDateForCommandSchedule(commandschedulelist[ctr], testtime) == False and (testtime.year - curtime.year) < checklimityears: 912 | testtime = testtime + datetime.timedelta(days=7) 913 | 914 | if (testtime.year - curtime.year) >= checklimityears: 915 | # Too many iterations, abort/ignore 916 | tmptimediff = 0 917 | else: 918 | tmptimediff = (curtime - testtime).total_seconds() 919 | if tmptimediff > 0: 920 | # Find next iteration that's greater than the current time (Day of Week check already handled) 921 | while tmptimediff >= 0: 922 | testtime = incrementCommandScheduleTime(commandschedulelist[ctr], testtime, "minute") 923 | tmptimediff = (curtime - testtime).total_seconds() 924 | 925 | if nextcommandtime > testtime and tmptimediff < 0: 926 | nextcommandschedule = commandschedulelist[ctr] 927 | nextcommandtime = testtime 928 | foundnextcmd = True 929 | 930 | 931 | ctr = ctr + 1 932 | if foundnextcmd == True: 933 | # Schedule Alarm 934 | if nextcommandschedule.get("weekday", -1) >=0 or nextcommandschedule.get("date", -1) > 0: 935 | # Set alarm based on hour/minute of next occurrence to factor in timezone changes if any 936 | setRTCAlarm(True, nextcommandschedule.get("weekday", -1), nextcommandschedule.get("date", -1), nextcommandtime.hour, nextcommandtime.minute) 937 | else: 938 | # no date,weekday involved just shift the hour and minute accordingly 939 | setRTCAlarm(True, nextcommandschedule.get("weekday", -1), nextcommandschedule.get("date", -1), nextcommandschedule.get("hour", -1), nextcommandschedule.get("minute", -1)) 940 | return nextcommandtime 941 | else: 942 | removeRTCAlarm() 943 | # This will ensure that this will be replaced next iteration 944 | return curtime 945 | 946 | ###### 947 | if len(sys.argv) > 1: 948 | cmd = sys.argv[1].upper() 949 | 950 | # Enable Alarm/Timer Flags 951 | enableflag = True 952 | 953 | if cmd == "CLEAN": 954 | removeRTCAlarm() 955 | removeRTCTimer() 956 | elif cmd == "SHUTDOWN": 957 | clearRTCAlarmFlag() 958 | clearRTCTimerFlag() 959 | 960 | elif cmd == "GETRTCTIME": 961 | print("RTC Time:", getRTCdatetime()) 962 | 963 | elif cmd == "UPDATERTCTIME": 964 | setRTCdatetime(datetime.datetime.now()) 965 | print("RTC Time:", getRTCdatetime()) 966 | 967 | elif cmd == "GETSCHEDULELIST": 968 | describeConfigList(RTC_CONFIGFILE) 969 | 970 | elif cmd == "SHOWSCHEDULE": 971 | if len(sys.argv) > 2: 972 | if sys.argv[2].isdigit(): 973 | # Display starts at 2, maps to 0-based index 974 | configidx = int(sys.argv[2])-2 975 | configlist = loadConfigList(RTC_CONFIGFILE) 976 | if len(configlist) > configidx: 977 | print (" ",describeConfigListEntry(configlist[configidx])) 978 | else: 979 | print(" Invalid Schedule") 980 | 981 | elif cmd == "REMOVESCHEDULE": 982 | if len(sys.argv) > 2: 983 | if sys.argv[2].isdigit(): 984 | # Display starts at 2, maps to 0-based index 985 | configidx = int(sys.argv[2])-2 986 | removeConfigEntry(RTC_CONFIGFILE, configidx) 987 | 988 | elif cmd == "SERVICE": 989 | syncSystemTime() 990 | commandschedulelist = formCommandScheduleList(loadConfigList(RTC_CONFIGFILE)) 991 | nextrtcalarmtime = setNextAlarm(commandschedulelist, datetime.datetime.now()) 992 | serviceloop = True 993 | while serviceloop==True: 994 | clearRTCAlarmFlag() 995 | clearRTCTimerFlag() 996 | 997 | tmpcurrenttime = datetime.datetime.now() 998 | if nextrtcalarmtime <= tmpcurrenttime: 999 | # Update RTC Alarm to next iteration 1000 | nextrtcalarmtime = setNextAlarm(commandschedulelist, nextrtcalarmtime) 1001 | elif len(getCommandForTime(commandschedulelist, tmpcurrenttime, "off")) > 0: 1002 | # Shutdown detected, issue command then end service loop 1003 | os.system("shutdown now -h") 1004 | serviceloop = False 1005 | # Don't break to sleep while command executes (prevents service to restart) 1006 | 1007 | 1008 | time.sleep(60) 1009 | 1010 | 1011 | elif False: 1012 | print("System Time: ", datetime.datetime.now()) 1013 | print("RTC Time: ", getRTCdatetime()) 1014 | 1015 | describeControlRegisters() 1016 | -------------------------------------------------------------------------------- /src/argoneond.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Argon EON RTC Service 3 | After=multi-user.target 4 | [Service] 5 | Type=simple 6 | Restart=always 7 | RemainAfterExit=true 8 | ExecStart=/usr/bin/python3 /etc/argon/argoneond.py SERVICE 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /src/argoneonoled.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | 4 | import sys 5 | import datetime 6 | import math 7 | 8 | import os 9 | import time 10 | 11 | # Initialize I2C Bus 12 | import smbus 13 | import RPi.GPIO as GPIO 14 | 15 | rev = GPIO.RPI_REVISION 16 | if rev == 2 or rev == 3: 17 | bus=smbus.SMBus(1) 18 | else: 19 | bus=smbus.SMBus(0) 20 | 21 | 22 | OLED_WD=128 23 | OLED_HT=64 24 | OLED_SLAVEADDRESS=0x6a 25 | ADDR_OLED=0x3c 26 | 27 | OLED_NUMFONTCHAR=256 28 | 29 | OLED_BUFFERIZE = ((OLED_WD*OLED_HT)>>3) 30 | oled_imagebuffer = [0] * OLED_BUFFERIZE 31 | 32 | 33 | def oled_getmaxY(): 34 | return OLED_HT 35 | 36 | def oled_getmaxX(): 37 | return OLED_WD 38 | 39 | def oled_loadbg(bgname): 40 | if bgname == "bgblack": 41 | oled_clearbuffer() 42 | return 43 | elif bgname == "bgwhite": 44 | oled_clearbuffer(1) 45 | return 46 | try: 47 | file = open("/etc/argon/oled/"+bgname+".bin", "rb") 48 | bgbytes = list(file.read()) 49 | file.close() 50 | ctr = len(bgbytes) 51 | if ctr == OLED_BUFFERIZE: 52 | oled_imagebuffer[:] = bgbytes 53 | elif ctr > OLED_BUFFERIZE: 54 | oled_imagebuffer[:] = bgbytes[0:OLED_BUFFERIZE] 55 | else: 56 | oled_imagebuffer[0:ctr] = bgbytes 57 | # Clear the rest of the buffer 58 | while ctr < OLED_BUFFERIZE: 59 | oled_imagebuffer[ctr] = 0 60 | ctr=ctr+1 61 | except FileNotFoundError: 62 | oled_clearbuffer() 63 | 64 | 65 | def oled_clearbuffer(value = 0): 66 | if value != 0: 67 | value = 0xff 68 | ctr = 0 69 | while ctr < OLED_BUFFERIZE: 70 | oled_imagebuffer[ctr] = value 71 | ctr=ctr+1 72 | 73 | def oled_writebyterow(x,y,bytevalue, mode = 0): 74 | bufferoffset = OLED_WD*(y>>3) + x 75 | if mode == 0: 76 | oled_imagebuffer[bufferoffset] = bytevalue 77 | elif mode == 1: 78 | oled_imagebuffer[bufferoffset] = bytevalue^oled_imagebuffer[bufferoffset] 79 | else: 80 | oled_imagebuffer[bufferoffset] = bytevalue|oled_imagebuffer[bufferoffset] 81 | 82 | 83 | def oled_writebuffer(x,y,value, mode = 0): 84 | 85 | yoffset = y>>3 86 | yshift = y&0x7 87 | ybit = (1<>3 127 | blocksize = 32 128 | try: 129 | # Set COM-H Addressing 130 | bus.write_byte_data(ADDR_OLED, 0, 0x20) 131 | bus.write_byte_data(ADDR_OLED, 0, 0x1) 132 | 133 | # Set Column range 134 | bus.write_byte_data(ADDR_OLED, 0, 0x21) 135 | bus.write_byte_data(ADDR_OLED, 0, xoffset) 136 | bus.write_byte_data(ADDR_OLED, 0, xoffset+blocksize-1) 137 | 138 | # Set Row Range 139 | bus.write_byte_data(ADDR_OLED, 0, 0x22) 140 | bus.write_byte_data(ADDR_OLED, 0, yoffset) 141 | bus.write_byte_data(ADDR_OLED, 0, yoffset) 142 | 143 | # Set Display Start Line 144 | bus.write_byte_data(ADDR_OLED, 0, 0x40) 145 | 146 | bufferoffset = OLED_WD*yoffset + xoffset 147 | # Write Out Buffer 148 | bus.write_i2c_block_data(ADDR_OLED, OLED_SLAVEADDRESS, oled_imagebuffer[bufferoffset:(bufferoffset+blocksize)]) 149 | except: 150 | return 151 | 152 | def oled_drawfilledrectangle(x, y, wd, ht, mode = 0): 153 | ymax = y + ht 154 | cury = y&0xF8 155 | 156 | xmax = x + wd 157 | curx = x 158 | if ((y & 0x7)) != 0: 159 | yshift = y&0x7 160 | bytevalue = (0xFF<>yshift) 166 | 167 | while curx < xmax: 168 | oled_writebyterow(curx,cury,bytevalue, mode) 169 | curx = curx + 1 170 | cury = cury + 8 171 | # Draw 8 rows at a time when possible 172 | while cury + 8 < ymax: 173 | curx = x 174 | while curx < xmax: 175 | oled_writebyterow(curx,cury,0xFF, mode) 176 | curx = curx + 1 177 | cury = cury + 8 178 | 179 | if cury < ymax: 180 | yshift = 8-((ymax-cury)&0x7) 181 | bytevalue = (0xFF>>yshift) 182 | 183 | curx = x 184 | while curx < xmax: 185 | oled_writebyterow(curx,cury,bytevalue, mode) 186 | curx = curx + 1 187 | 188 | 189 | def oled_writetextaligned(textdata, x, y, boxwidth, alignmode, charwd = 6, mode = 0): 190 | leftoffset = 0 191 | if alignmode == 1: 192 | # Centered 193 | leftoffset = (boxwidth-len(textdata)*charwd)>>1 194 | elif alignmode == 2: 195 | # Right aligned 196 | leftoffset = (boxwidth-len(textdata)*charwd) 197 | 198 | oled_writetext(textdata, x+leftoffset, y, charwd, mode) 199 | 200 | 201 | def oled_writetext(textdata, x, y, charwd = 6, mode = 0): 202 | if charwd < 6: 203 | charwd = 6 204 | 205 | charht = int((charwd<<3)/6) 206 | if charht & 0x7: 207 | charht = (charht&0xF8) + 8 208 | 209 | try: 210 | file = open("/etc/argon/oled/font"+str(charht)+"x"+str(charwd)+".bin", "rb") 211 | fontbytes = list(file.read()) 212 | file.close() 213 | except FileNotFoundError: 214 | try: 215 | # Default to smallest 216 | file = open("/etc/argon/oled/font8x6.bin", "rb") 217 | fontbytes = list(file.read()) 218 | file.close() 219 | except FileNotFoundError: 220 | return 221 | 222 | if ((y & 0x7)) == 0: 223 | # Use optimized loading 224 | oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode) 225 | return 226 | 227 | numfontrow = charht>>3 228 | ctr = 0 229 | while ctr < len(textdata): 230 | fontoffset = ord(textdata[ctr])*charwd 231 | fontcol = 0 232 | while fontcol < charwd and x < OLED_WD: 233 | fontrow = 0 234 | row = y 235 | while fontrow < numfontrow and row < OLED_HT and x >= 0: 236 | curbit = 0x80 237 | curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)]) 238 | subrow = 0 239 | while subrow < 8 and row < OLED_HT: 240 | value = 0 241 | if (curbyte&curbit) != 0: 242 | value = 1 243 | oled_writebuffer(x,row,value, mode) 244 | curbit = curbit >> 1 245 | row = row + 1 246 | subrow = subrow + 1 247 | fontrow = fontrow + 1 248 | fontcol = fontcol + 1 249 | x = x + 1 250 | ctr = ctr + 1 251 | 252 | def oled_fastwritetext(textdata, x, y, charht, charwd, fontbytes, mode = 0): 253 | 254 | numfontrow = charht>>3 255 | ctr = 0 256 | while ctr < len(textdata): 257 | fontoffset = ord(textdata[ctr])*charwd 258 | fontcol = 0 259 | while fontcol < charwd and x < OLED_WD: 260 | fontrow = 0 261 | row = y&0xF8 262 | while fontrow < numfontrow and row < OLED_HT and x >= 0: 263 | curbyte = (fontbytes[fontoffset + fontcol + (OLED_NUMFONTCHAR*charwd*fontrow)]) 264 | oled_writebyterow(x,row,curbyte, mode) 265 | fontrow = fontrow + 1 266 | row = row + 8 267 | fontcol = fontcol + 1 268 | x = x + 1 269 | ctr = ctr + 1 270 | return 271 | 272 | 273 | def oled_power(turnon = True): 274 | cmd = 0xAE 275 | if turnon == True: 276 | cmd = cmd|1 277 | try: 278 | bus.write_byte_data(ADDR_OLED, 0, cmd) 279 | except: 280 | return 281 | 282 | 283 | def oled_inverse(enable = True): 284 | cmd = 0xA6 285 | if enable == True: 286 | cmd = cmd|1 287 | try: 288 | bus.write_byte_data(ADDR_OLED, 0, cmd) 289 | except: 290 | return 291 | 292 | 293 | def oled_fullwhite(enable = True): 294 | cmd = 0xA4 295 | if enable == True: 296 | cmd = cmd|1 297 | try: 298 | bus.write_byte_data(ADDR_OLED, 0, cmd) 299 | except: 300 | return 301 | 302 | 303 | 304 | def oled_reset(): 305 | try: 306 | # Set COM-H Addressing 307 | bus.write_byte_data(ADDR_OLED, 0, 0x20) 308 | bus.write_byte_data(ADDR_OLED, 0, 0x1) 309 | 310 | # Set Column range 311 | bus.write_byte_data(ADDR_OLED, 0, 0x21) 312 | bus.write_byte_data(ADDR_OLED, 0, 0) 313 | bus.write_byte_data(ADDR_OLED, 0, OLED_WD-1) 314 | 315 | # Set Row Range 316 | bus.write_byte_data(ADDR_OLED, 0, 0x22) 317 | bus.write_byte_data(ADDR_OLED, 0, 0) 318 | bus.write_byte_data(ADDR_OLED, 0, (OLED_HT>>3)-1) 319 | 320 | # Set Page Addressing 321 | bus.write_byte_data(ADDR_OLED, 0, 0x20) 322 | bus.write_byte_data(ADDR_OLED, 0, 0x2) 323 | # Set GDDRAM Address 324 | bus.write_byte_data(ADDR_OLED, 0, 0xB0) 325 | 326 | # Set Display Start Line 327 | bus.write_byte_data(ADDR_OLED, 0, 0x40) 328 | except: 329 | return 330 | 331 | 332 | -------------------------------------------------------------------------------- /src/argonone-fanconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | daemonconfigfile=/etc/argononed.conf 4 | 5 | echo "------------------------------------" 6 | echo " Argon Fan Speed Configuration Tool" 7 | echo "------------------------------------" 8 | echo "WARNING: This will remove existing configuration." 9 | echo -n "Press Y to continue:" 10 | read -n 1 confirm 11 | echo 12 | 13 | 14 | fanloopflag=1 15 | newmode=0 16 | if [ "$confirm" = "y" ] 17 | then 18 | confirm="Y" 19 | fi 20 | 21 | if [ "$confirm" != "Y" ] 22 | then 23 | fanloopflag=0 24 | echo "Cancelled." 25 | else 26 | echo "Thank you." 27 | fi 28 | 29 | get_number () { 30 | read curnumber 31 | if [ -z "$curnumber" ] 32 | then 33 | echo "-2" 34 | return 35 | elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] 36 | then 37 | if [ $curnumber -lt 0 ] 38 | then 39 | echo "-1" 40 | return 41 | elif [ $curnumber -gt 100 ] 42 | then 43 | echo "-1" 44 | return 45 | fi 46 | echo $curnumber 47 | return 48 | fi 49 | echo "-1" 50 | return 51 | } 52 | 53 | while [ $fanloopflag -eq 1 ] 54 | do 55 | echo 56 | echo "Select fan mode:" 57 | echo " 1. Always on" 58 | echo " 2. Adjust to temperatures (55C, 60C, and 65C)" 59 | echo " 3. Customize behavior" 60 | echo 61 | echo " 0. Exit" 62 | echo "NOTE: You can also edit $daemonconfigfile directly" 63 | echo -n "Enter Number (0-3):" 64 | newmode=$( get_number ) 65 | 66 | if [[ $newmode -eq 0 ]] 67 | then 68 | fanloopflag=0 69 | elif [ $newmode -eq 1 ] 70 | then 71 | echo "#" > $daemonconfigfile 72 | echo "# Argon One Fan Speed Configuration" >> $daemonconfigfile 73 | echo "#" >> $daemonconfigfile 74 | echo "# Min Temp=Fan Speed" >> $daemonconfigfile 75 | echo 1"="100 >> $daemonconfigfile 76 | sudo systemctl restart argononed.service 77 | echo "Fan always on." 78 | elif [ $newmode -eq 2 ] 79 | then 80 | echo "Please provide fan speeds for the following temperatures:" 81 | echo "#" > $daemonconfigfile 82 | echo "# Argon One Fan Speed Configuration" >> $daemonconfigfile 83 | echo "#" >> $daemonconfigfile 84 | echo "# Min Temp=Fan Speed" >> $daemonconfigfile 85 | curtemp=55 86 | while [ $curtemp -lt 70 ] 87 | do 88 | errorfanflag=1 89 | while [ $errorfanflag -eq 1 ] 90 | do 91 | echo -n ""$curtemp"C (0-100 only):" 92 | curfan=$( get_number ) 93 | if [ $curfan -ge 0 ] 94 | then 95 | errorfanflag=0 96 | fi 97 | done 98 | echo $curtemp"="$curfan >> $daemonconfigfile 99 | curtemp=$((curtemp+5)) 100 | done 101 | 102 | sudo systemctl restart argononed.service 103 | echo "Configuration updated." 104 | elif [ $newmode -eq 3 ] 105 | then 106 | echo "Please provide fan speeds and temperature pairs" 107 | echo 108 | 109 | subloopflag=1 110 | paircounter=0 111 | while [ $subloopflag -eq 1 ] 112 | do 113 | errortempflag=1 114 | errorfanflag=1 115 | echo "(You may set a blank value to end configuration)" 116 | while [ $errortempflag -eq 1 ] 117 | do 118 | echo -n "Provide minimum temperature (in Celsius) then [ENTER]:" 119 | curtemp=$( get_number ) 120 | if [ $curtemp -ge 0 ] 121 | then 122 | errortempflag=0 123 | elif [ $curtemp -eq -2 ] 124 | then 125 | errortempflag=0 126 | errorfanflag=0 127 | subloopflag=0 128 | fi 129 | done 130 | while [ $errorfanflag -eq 1 ] 131 | do 132 | echo -n "Provide fan speed for "$curtemp"C (0-100) then [ENTER]:" 133 | curfan=$( get_number ) 134 | if [ $curfan -ge 0 ] 135 | then 136 | errorfanflag=0 137 | elif [ $curfan -eq -2 ] 138 | then 139 | errortempflag=0 140 | errorfanflag=0 141 | subloopflag=0 142 | fi 143 | done 144 | if [ $subloopflag -eq 1 ] 145 | then 146 | if [ $paircounter -eq 0 ] 147 | then 148 | echo "#" > $daemonconfigfile 149 | echo "# Argon Fan Configuration" >> $daemonconfigfile 150 | echo "#" >> $daemonconfigfile 151 | echo "# Min Temp=Fan Speed" >> $daemonconfigfile 152 | fi 153 | echo $curtemp"="$curfan >> $daemonconfigfile 154 | 155 | paircounter=$((paircounter+1)) 156 | 157 | echo "* Fan speed will be set to "$curfan" once temperature reaches "$curtemp" C" 158 | echo 159 | fi 160 | done 161 | 162 | echo 163 | if [ $paircounter -gt 0 ] 164 | then 165 | echo "Thank you! We saved "$paircounter" pairs." 166 | sudo systemctl restart argononed.service 167 | echo "Changes should take effect now." 168 | else 169 | echo "Cancelled, no data saved." 170 | fi 171 | fi 172 | done 173 | 174 | echo 175 | 176 | -------------------------------------------------------------------------------- /src/argonone-firmwareupdate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import urllib.request 4 | 5 | import time 6 | import smbus 7 | import RPi.GPIO as GPIO 8 | 9 | import serial 10 | import os.path 11 | 12 | # For finalization, location of binary to upload 13 | firmwareurl = "https://download.argon40.com/argon1.bin" 14 | firmwarefile = "/dev/shm/fwupdate.bin" 15 | 16 | # Serial config 17 | serialdev = "/dev/serial0" 18 | serialrate = 115200 19 | # Uncomment to write to file 20 | #serialdev = "" 21 | 22 | # Other Config 23 | MAXRETRY = 0 # Ignore warnings 0, otherwise number of retry 24 | ALWAYSDOWNLOAD = True # Always download firmware from URL 25 | 26 | # Display Packet Data 27 | DUMPPACKETDATA = False 28 | 29 | # Set Paths for data dump 30 | TESTPACKETFILE = "" # Dump packets sent/received 31 | TESTTRANSFERFILE = "" # Copy sent payload to file (should match source) 32 | 33 | # Sample Output paths 34 | #TESTPACKETFILE = "/dev/shm/fwtestpacket.bin" 35 | #TESTTRANSFERFILE = "/dev/shm/fwtesttransfer.bin" 36 | 37 | 38 | # Constants, no need to edit 39 | I2CADDRESS = 0x1A 40 | I2CCOMMAND = 0xBB 41 | PACKETSIZE = 64 42 | 43 | # Methods 44 | def dumpPacket(packet, offset, dumpsize, dumpid, title = "Send Packet"): 45 | print(title, dumpid) 46 | idx = 0 47 | while idx < dumpsize: 48 | if idx % 8 == 0: 49 | if idx > 0: 50 | print("") 51 | print(getHexString(idx, 0), ":") 52 | print(getHexString(packet[offset+idx])) 53 | idx = idx + 1 54 | print("") 55 | 56 | def dumpBytes(packet, offset, dumpsize, dumpid, title = "Receive Packet"): 57 | print(title, dumpid) 58 | idx = 0 59 | while idx < dumpsize: 60 | if idx % 8 == 0: 61 | if idx > 0: 62 | print("") 63 | print(getHexString(idx, 0), ":") 64 | print(getHexString(ord(packet[offset+idx]))) 65 | idx = idx + 1 66 | print("") 67 | 68 | 69 | def dumpFiledata(filename, fileoffset = 0, maxlength = 0): 70 | if len(filename) < 1: 71 | return 72 | ROWLEN = 8 73 | print("*** Dump ", filename) 74 | dumpfp = open(filename,"rb") 75 | bindata = dumpfp.read() 76 | dumpfp.close() 77 | 78 | packet = bytearray(ROWLEN) 79 | filesize = len(bindata) 80 | idx = fileoffset 81 | 82 | if maxlength > 0: 83 | dumpsize = idx + maxlength 84 | 85 | if dumpsize > filesize: 86 | dumpsize = filesize 87 | 88 | while idx < dumpsize: 89 | dumplen = ROWLEN 90 | if dumpsize - idx < dumplen: 91 | dumplen = dumpsize - idx 92 | 93 | print(getHexString(idx, 0), ":") 94 | while dumplen > 0 and idx < dumpsize: 95 | print(getHexString(ord(bindata[idx]))) 96 | idx = idx + 1 97 | dumplen = dumplen - 1 98 | print("") 99 | 100 | def getHexString(value, showbyte = 1): 101 | if showbyte == 1: 102 | return "0x{:02x}".format(value) 103 | return "0x{:08x}".format(value) 104 | 105 | 106 | def readPacketWord(packet, offset): 107 | word = 0 108 | try: 109 | word = 0 110 | idx = 4 111 | while idx > 0: 112 | idx = idx - 1 113 | word = (word<<8) + ord(packet[offset + idx]) 114 | except Exception as e: 115 | word = 0 116 | idx = 4 117 | while idx > 0: 118 | idx = idx - 1 119 | word = (word<<8) + int(packet[offset + idx]) 120 | return word 121 | 122 | def getPacketChecksum(packet, offset, length): 123 | checksum = 0 124 | idx = 0 125 | while idx < length: 126 | checksum = checksum + packet[offset+idx] 127 | idx = idx + 1 128 | return checksum 129 | 130 | def writePacketWord(packet, offset, word): 131 | idx = 0 132 | while idx < 4: 133 | packet[offset + idx] = word & 0xff 134 | word = (word >> 8) 135 | idx = idx + 1 136 | 137 | def writePacketBytes(packet, offset, bytedata, length): 138 | idx = 0 139 | while idx < length: 140 | packet[offset + idx] = bytedata[idx] 141 | idx = idx + 1 142 | 143 | # i2c bus 144 | rev = GPIO.RPI_REVISION 145 | if rev == 2 or rev == 3: 146 | bus = smbus.SMBus(1) 147 | else: 148 | bus = smbus.SMBus(0) 149 | 150 | if os.path.isfile(firmwarefile) == False or ALWAYSDOWNLOAD == True: 151 | print("Downloading Firmware ...") 152 | urllib.request.urlretrieve(firmwareurl, firmwarefile) 153 | print("Download completed") 154 | 155 | 156 | print("Preparing device...") 157 | attemptcounter = 0 158 | # Send update command to i2c 159 | try: 160 | bus.write_byte(I2CADDRESS,I2CCOMMAND) 161 | except: 162 | # Error at first attempt, I2C communication error 163 | print("Communication Failed.") 164 | attemptcounter = 100 165 | 166 | while attemptcounter<3: 167 | try: 168 | time.sleep(1) 169 | bus.write_byte(I2CADDRESS,I2CCOMMAND) 170 | attemptcounter = attemptcounter + 1 171 | except: 172 | # I2C command failed, MCU in update mode 173 | print("Update Mode Enabled.") 174 | attemptcounter = 5 175 | 176 | try: 177 | bus.close() 178 | except: 179 | print("Communication Failure.") 180 | 181 | if attemptcounter < 5: 182 | print("Error while trying to update.") 183 | print("Please try again or verify if device supports firmware update.") 184 | print("") 185 | exit() 186 | elif attemptcounter > 5: 187 | print("Unable to connect to Argon Device.") 188 | print("Please check if device is configured properly.") 189 | print("") 190 | exit() 191 | 192 | 193 | attemptcounter = 0 194 | errorflag = 0 195 | warningflag = 0 196 | state = 0 197 | if os.path.isfile(firmwarefile): 198 | state = 1 # File Exists 199 | datafp = open(firmwarefile,"rb") 200 | bindata = datafp.read() 201 | datafp.close() 202 | 203 | state = 2 # File loaded to Memory 204 | datatotal = len(bindata) 205 | 206 | print(datatotal, " bytes for processing") 207 | try: 208 | if len(serialdev) > 0: 209 | conn = serial.Serial(serialdev, serialrate, timeout=3) 210 | state = 3 # Serial Port Connected 211 | else: 212 | state = 4 # File mode 213 | 214 | packetid = 1 215 | dataidx = 0 216 | while dataidx < datatotal: 217 | 218 | # Form Packet Header 219 | packetdata = bytearray(PACKETSIZE) # Initialize 64-byte packet of zeros (so no need to manually pad zeros) 220 | packetdataoffset = 8 221 | if packetid == 1: 222 | writePacketWord(packetdata, 0, 0xa0) 223 | writePacketWord(packetdata, 12, datatotal) 224 | packetdataoffset = 16 225 | writePacketWord(packetdata, 4, packetid) 226 | 227 | # Form data chunk 228 | packetendidx = PACKETSIZE # For debugging only 229 | dataidxend = dataidx+(PACKETSIZE-packetdataoffset) 230 | if dataidxend > datatotal: 231 | packetendidx = PACKETSIZE-(dataidxend-datatotal) 232 | dataidxend = datatotal 233 | 234 | writePacketBytes(packetdata, packetdataoffset, bindata[dataidx:dataidxend], dataidxend-dataidx) 235 | dataidx = dataidxend 236 | 237 | # Should be able to count end since it's all zeros 238 | datacrc = getPacketChecksum(packetdata, 0, PACKETSIZE) 239 | 240 | # Debug 241 | if DUMPPACKETDATA == True: 242 | dumpPacket(packetdata, 0, PACKETSIZE, packetid) 243 | 244 | # Log packets to file(s) 245 | testfpmode = "ab" # Append by default 246 | if packetid == 1: 247 | testfpmode = "wb" # First packet, don't append 248 | 249 | # Test Packets 250 | if TESTPACKETFILE != "": 251 | testfp = open(TESTPACKETFILE, testfpmode) 252 | testfp.write(packetdata) 253 | testfp.close() 254 | 255 | # Test Transfer Data 256 | if TESTTRANSFERFILE != "": 257 | testfp = open(TESTTRANSFERFILE, testfpmode) 258 | testfp.write(packetdata[packetdataoffset:packetendidx]) 259 | testfp.close() 260 | 261 | # Send Packet Data 262 | if len(serialdev) > 0: 263 | state = 10 264 | conn.write(packetdata) 265 | state = 11 266 | outdata = conn.read(PACKETSIZE) 267 | if len(outdata) < PACKETSIZE: 268 | raise Exception("Serial read timeout") 269 | break 270 | 271 | state = 3 272 | 273 | else: 274 | packetdata = bytearray(PACKETSIZE) 275 | writePacketWord(packetdata, 0, datacrc) 276 | writePacketWord(packetdata, 4, packetid + 1) 277 | outdata = bytes(packetdata) 278 | 279 | # Log Packets 280 | testfpmode = "ab" 281 | if TESTPACKETFILE != "": 282 | testfp = open(TESTPACKETFILE, testfpmode) 283 | testfp.write(outdata) 284 | testfp.close() 285 | 286 | retpacketcrc = readPacketWord(outdata, 0) 287 | retpacketid = readPacketWord(outdata, 4) 288 | 289 | # Data Validation 290 | packetid = packetid + 1 291 | 292 | if DUMPPACKETDATA == True: 293 | dumpBytes(outdata, 0, PACKETSIZE, packetid) 294 | 295 | if retpacketcrc != datacrc: 296 | print("ERROR: CRC mismatch in packet ", (packetid - 1)) 297 | print("\tCRC Expected: " + getHexString(datacrc)) 298 | print("\tCRC Returned: " + getHexString(retpacketcrc)) 299 | errorflag = 1 300 | 301 | if retpacketid != packetid: 302 | print("ERROR: ID mismatch in response packet ", (packetid - 1)) 303 | print("\tID Expected:", (packetid)) 304 | print("\tID Returned:", (retpacketid)) 305 | errorflag = 1 306 | 307 | if errorflag == 1: 308 | if MAXRETRY > 0: 309 | attemptcounter = attemptcounter + 1 310 | if attemptcounter >= MAXRETRY: 311 | print("Too many failed attempts, aborting...") 312 | dataidx = datatotal # Abort 313 | else: 314 | print("Restarting transmission...") 315 | dataidx = 0 316 | packetid = 0 # Restart 317 | errorflag = 0 318 | else: 319 | print("Ignoring errors, proceeding") 320 | warningflag = 1 321 | errorflag = 0 322 | 323 | # Next Packet ID 324 | packetid = packetid + 1 325 | 326 | if state == 3: 327 | conn.close() 328 | 329 | state = 200 # Completed 330 | except: 331 | if state == 2: 332 | print("Unable to connect to serial port "+serialdev+", please check permission or if serial is enabled") 333 | elif state == 3: 334 | print("Data processing error") 335 | conn.close() 336 | elif state == 10: 337 | print("Error writing to serial port") 338 | elif state == 11: 339 | print("Error reading from serial port") 340 | elif state == 4: 341 | print("Error during file I/O") 342 | state = -1 343 | 344 | if state == 0: 345 | print("Firmware file not found") 346 | elif state == 1: 347 | print("Unable to read file") 348 | elif state == 200: 349 | if errorflag == 1: 350 | print("Failed to upload") 351 | elif warningflag == 1: 352 | print("Completed with warnings") 353 | else: 354 | print(dataidx, "bytes uploaded") 355 | 356 | 357 | filedumpsize = 0 358 | #filedumpsize = 160 359 | if filedumpsize > 0: 360 | filedumpoffset = 0 361 | dumpFiledata(TESTPACKETFILE, filedumpoffset, filedumpsize) 362 | dumpFiledata(TESTTRANSFERFILE, filedumpoffset, filedumpsize) 363 | dumpFiledata(firmwarefile, filedumpoffset, filedumpsize) 364 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /src/argonone-irconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CHECKPLATFORM="Others" 4 | # Check if Raspbian 5 | grep -q -F 'Raspbian' /etc/os-release &> /dev/null 6 | if [ $? -eq 0 ] 7 | then 8 | CHECKPLATFORM="Raspbian" 9 | fi 10 | 11 | echo "-----------------------------" 12 | echo " Argon IR Configuration Tool" 13 | echo "------------------------------" 14 | echo "WARNING: This only supports NEC" 15 | echo " protocol only." 16 | echo -n "Press Y to continue:" 17 | read -n 1 confirm 18 | echo 19 | 20 | if [ "$confirm" = "y" ] 21 | then 22 | confirm="Y" 23 | fi 24 | 25 | if [ "$confirm" != "Y" ] 26 | then 27 | echo "Cancelled" 28 | exit 29 | fi 30 | echo "Thank you." 31 | 32 | get_number () { 33 | read curnumber 34 | if [ -z "$curnumber" ] 35 | then 36 | echo "-2" 37 | return 38 | elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]] 39 | then 40 | if [ $curnumber -lt 0 ] 41 | then 42 | echo "-1" 43 | return 44 | elif [ $curnumber -gt 100 ] 45 | then 46 | echo "-1" 47 | return 48 | fi 49 | echo $curnumber 50 | return 51 | fi 52 | echo "-1" 53 | return 54 | } 55 | 56 | irexecrcfile=/etc/lirc/irexec.lircrc 57 | irexecshfile=/etc/argon/argonirexec 58 | irdecodefile=/etc/argon/argonirdecoder 59 | kodiuserdatafolder="$HOME/.kodi/userdata" 60 | kodilircmapfile="$kodiuserdatafolder/Lircmap.xml" 61 | remotemode="" 62 | needinstallation=0 63 | 64 | loopflag=1 65 | while [ $loopflag -eq 1 ] 66 | do 67 | echo 68 | echo "Select remote control to configure:" 69 | echo " 1. Configure Remote ON/OFF Button" 70 | if [ "$CHECKPLATFORM" = "Raspbian" ] 71 | then 72 | echo " 2. Configure Other Remote Buttons" 73 | if [ ! -f "$irexecshfile" ] 74 | then 75 | echo " 3. Cancel" 76 | echo -n "Enter Number (1-3):" 77 | 78 | newmode=$( get_number ) 79 | if [[ $newmode -ge 1 && $newmode -le 3 ]] 80 | then 81 | loopflag=0 82 | if [ $newmode -eq 3 ] 83 | then 84 | newmode=4 85 | fi 86 | fi 87 | 88 | else 89 | echo " 3. Uninstall Other Remote Buttons" 90 | echo " 4. Cancel" 91 | echo -n "Enter Number (1-4):" 92 | newmode=$( get_number ) 93 | if [[ $newmode -ge 1 && $newmode -le 4 ]] 94 | then 95 | loopflag=0 96 | fi 97 | fi 98 | else 99 | echo " 2. Cancel" 100 | echo -n "Enter Number (1-2):" 101 | 102 | newmode=$( get_number ) 103 | if [[ $newmode -ge 1 && $newmode -le 2 ]] 104 | then 105 | loopflag=0 106 | if [ $newmode -eq 2 ] 107 | then 108 | newmode=4 109 | fi 110 | fi 111 | fi 112 | done 113 | 114 | 115 | echo 116 | if [ $newmode -eq 3 ] 117 | then 118 | irtmpconfigfile=/dev/shm/argonirconfig.txt 119 | sudo systemctl stop irexec.service 120 | sudo systemctl disable irexec.service 121 | sudo pip3 uninstall lirc -y 122 | sudo apt-get -y remove lirc 123 | sudo rm $irexecshfile 124 | sudo rm $irdecodefile 125 | 126 | sudo cat /boot/config.txt | grep -v 'dtoverlay=gpio-ir,gpio_pin=23' > $irtmpconfigfile 127 | cat $irtmpconfigfile | sudo tee /boot/config.txt 1> /dev/null 128 | sudo rm $irtmpconfigfile 129 | 130 | echo "Uninstall Completed" 131 | echo "Please reboot for changes to take effect" 132 | exit 133 | elif [ $newmode -eq 1 ] 134 | then 135 | loopflag=1 136 | while [ $loopflag -eq 1 ] 137 | do 138 | echo 139 | echo "Select remote control to configure:" 140 | echo " 1. Use Argon Remote ON/OFF Button" 141 | echo " 2. Use Custom Remote ON/OFF Button" 142 | echo " 3. Cancel" 143 | echo -n "Enter Number (1-3):" 144 | newmode=$( get_number ) 145 | if [[ $newmode -ge 1 && $newmode -le 4 ]] 146 | then 147 | loopflag=0 148 | fi 149 | done 150 | 151 | if [ $newmode -eq 3 ] 152 | then 153 | echo "Cancelled" 154 | exit 155 | elif [ $newmode -eq 1 ] 156 | then 157 | remotemode="resetpower" 158 | elif [ $newmode -eq 2 ] 159 | then 160 | remotemode="power" 161 | fi 162 | elif [ $newmode -eq 2 ] 163 | then 164 | echo "-----------------------------" 165 | echo " Argon IR Configuration Tool" 166 | echo "-----------------------------" 167 | echo "WARNING: This will install LIRC" 168 | echo " and related libraries." 169 | echo -n "Press Y to agree:" 170 | read -n 1 confirm 171 | echo 172 | 173 | if [ "$confirm" = "y" ] 174 | then 175 | confirm="Y" 176 | fi 177 | 178 | if [ "$confirm" != "Y" ] 179 | then 180 | echo "Cancelled" 181 | exit 182 | fi 183 | echo "Thank you." 184 | 185 | 186 | needinstallation=1 187 | loopflag=1 188 | while [ $loopflag -eq 1 ] 189 | do 190 | echo 191 | echo "Select remote control to configure:" 192 | echo " 1. Use Argon Remote Buttons" 193 | echo " 2. Use Custom Remote Buttons" 194 | echo " 3. Cancel" 195 | echo -n "Enter Number (1-3):" 196 | newmode=$( get_number ) 197 | if [[ $newmode -ge 1 && $newmode -le 4 ]] 198 | then 199 | loopflag=0 200 | fi 201 | done 202 | 203 | if [ $newmode -eq 3 ] 204 | then 205 | echo "Cancelled" 206 | exit 207 | elif [ $newmode -eq 1 ] 208 | then 209 | remotemode="default" 210 | elif [ $newmode -eq 2 ] 211 | then 212 | remotemode="custom" 213 | fi 214 | else 215 | echo "Cancelled" 216 | exit 217 | fi 218 | 219 | needrestart=0 220 | if [ $needinstallation -eq 1 ] 221 | then 222 | if [ ! -f "$irexecshfile" ] 223 | then 224 | needrestart=1 225 | sudo apt-get -y update 226 | sudo apt-get -y install lirc 227 | 228 | sudo pip3 install lirc 229 | 230 | echo "dtoverlay=gpio-ir,gpio_pin=23" | sudo tee -a /boot/config.txt 1> /dev/null 231 | 232 | sudo /usr/share/lirc/lirc-old2new 233 | 234 | sudo systemctl daemon-reload 235 | sudo systemctl enable irexec.service 236 | sudo systemctl start irexec.service 237 | 238 | echo "" | sudo tee $irexecrcfile 1> /dev/null 239 | for keyname in UP DOWN LEFT RIGHT BACK PLAYPAUSE MENU HOME OK MUTE VOLUMEUP VOLUMEDOWN 240 | do 241 | echo 'begin' | sudo tee -a $irexecrcfile 1> /dev/null 242 | echo ' remote=argon' | sudo tee -a $irexecrcfile 1> /dev/null 243 | echo ' prog=irexec' | sudo tee -a $irexecrcfile 1> /dev/null 244 | echo ' button=KEY_'$keyname | sudo tee -a $irexecrcfile 1> /dev/null 245 | echo ' config='$irexecshfile' "'$keyname'"' | sudo tee -a $irexecrcfile 1> /dev/null 246 | echo 'end' | sudo tee -a $irexecrcfile 1> /dev/null 247 | done 248 | 249 | 250 | echo "#!/bin/bash" | sudo tee $irexecshfile 1> /dev/null 251 | 252 | echo '' | sudo tee -a $irexecshfile 1> /dev/null 253 | echo 'if [ -z "$1" ]' | sudo tee -a $irexecshfile 1> /dev/null 254 | echo 'then' | sudo tee -a $irexecshfile 1> /dev/null 255 | echo ' exit' | sudo tee -a $irexecshfile 1> /dev/null 256 | echo 'fi' | sudo tee -a $irexecshfile 1> /dev/null 257 | 258 | echo '' | sudo tee -a $irexecshfile 1> /dev/null 259 | 260 | echo '# Handlers for different key codes' | sudo tee -a $irexecshfile 1> /dev/null 261 | echo '# Key codes: UP DOWN LEFT RIGHT BACK PLAYPAUSE MENU HOME OK MUTE VOLUMEUP VOLUMEDOWN' | sudo tee -a $irexecshfile 1> /dev/null 262 | echo '' | sudo tee -a $irexecshfile 1> /dev/null 263 | 264 | echo 'amixerdevice=$(/usr/bin/amixer scontrols | sed -n "s/^.*'"'\(.*\)'"'.*$/\1/p")' | sudo tee -a $irexecshfile 1> /dev/null 265 | echo 'if [ $1 == "VOLUMEUP" ]' | sudo tee -a $irexecshfile 1> /dev/null 266 | echo 'then' | sudo tee -a $irexecshfile 1> /dev/null 267 | echo ' /usr/bin/amixer set $amixerdevice -- $[$(/usr/bin/amixer get $amixerdevice|grep -o [0-9]*%|sed '"'s/%//'"')+5]%' | sudo tee -a $irexecshfile 1> /dev/null 268 | echo 'elif [ $1 == "VOLUMEDOWN" ]' | sudo tee -a $irexecshfile 1> /dev/null 269 | echo 'then' | sudo tee -a $irexecshfile 1> /dev/null 270 | echo ' /usr/bin/amixer set $amixerdevice -- $[$(/usr/bin/amixer get $amixerdevice|grep -o [0-9]*%|sed '"'s/%//'"')-5]%' | sudo tee -a $irexecshfile 1> /dev/null 271 | echo 'elif [ $1 == "MUTE" ]' | sudo tee -a $irexecshfile 1> /dev/null 272 | echo 'then' | sudo tee -a $irexecshfile 1> /dev/null 273 | echo ' /usr/bin/amixer set $amixerdevice toggle' | sudo tee -a $irexecshfile 1> /dev/null 274 | echo 'fi' | sudo tee -a $irexecshfile 1> /dev/null 275 | 276 | echo '' | sudo tee -a $irexecshfile 1> /dev/null 277 | 278 | sudo chmod 755 $irexecshfile 279 | fi 280 | fi 281 | 282 | if [ ! -f "$irdecodefile" ] 283 | then 284 | sudo wget https://download.argon40.com/argonone-irdecoder.py -O $irdecodefile --quiet 285 | fi 286 | 287 | sudo python3 $irdecodefile $remotemode 288 | 289 | if [ ! -d $kodiuserdatafolder ] 290 | then 291 | if [ ! -d "$HOME/.kodi" ] 292 | then 293 | mkdir "$HOME/.kodi" 294 | fi 295 | mkdir $kodiuserdatafolder 296 | fi 297 | 298 | if [ -d $kodiuserdatafolder ] 299 | then 300 | echo "" | tee $kodilircmapfile 1> /dev/null 301 | echo ' ' | tee -a $kodilircmapfile 1> /dev/null 302 | echo ' KEY_LEFT' | tee -a $kodilircmapfile 1> /dev/null 303 | echo ' KEY_RIGHT' | tee -a $kodilircmapfile 1> /dev/null 304 | echo ' KEY_UP' | tee -a $kodilircmapfile 1> /dev/null 305 | echo ' KEY_DOWN' | tee -a $kodilircmapfile 1> /dev/null 306 | echo ' ' | tee -a $kodilircmapfile 1> /dev/null 307 | echo ' KEY_HOME' | tee -a $kodilircmapfile 1> /dev/null 308 | echo ' KEY_MENUBACK' | tee -a $kodilircmapfile 1> /dev/null 309 | echo ' KEY_VOLUMEUP' | tee -a $kodilircmapfile 1> /dev/null 310 | echo ' KEY_VOLUMEDOWN' | tee -a $kodilircmapfile 1> /dev/null 311 | echo ' ' | tee -a $kodilircmapfile 1> /dev/null 312 | echo '' | tee -a $kodilircmapfile 1> /dev/null 313 | fi 314 | 315 | echo 316 | echo "Thank you." 317 | if [ $needrestart -eq 1 ] 318 | then 319 | echo "Changes should take after reboot." 320 | elif [ $needinstallation -eq 1 ] 321 | then 322 | sudo systemctl restart lircd.service 323 | sudo systemctl restart irexec.service 324 | fi 325 | 326 | -------------------------------------------------------------------------------- /src/argonone-irdecoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Standard Headers 4 | import sys 5 | import smbus 6 | import RPi.GPIO as GPIO 7 | 8 | # For GPIO 9 | from datetime import datetime 10 | 11 | ### For pip3 lirc 12 | import os 13 | import time 14 | 15 | # Check if Lirc Lib is installed 16 | import importlib 17 | haslirclib = False 18 | try: 19 | lirclib = importlib.util.find_spec("lirc") 20 | haslirclib = lirclib is not None 21 | except: 22 | try: 23 | print("WARNING: Falling back to Deprecated module") 24 | lirclib = importlib.find_loader("lirc") 25 | haslirclib = lirclib is not None 26 | except: 27 | haslirclib = False 28 | 29 | if haslirclib: 30 | import lirc 31 | 32 | ######################### 33 | # Use GPIO 34 | irreceiver_pin = 23 # IR Receiver Pin 35 | GPIO.setmode(GPIO.BCM) 36 | GPIO.setup(irreceiver_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) 37 | 38 | def getGPIOPulseData(): 39 | # Counter 40 | ctr = 0 41 | 42 | try: 43 | # start at 0, wait for it if necessary 44 | value = GPIO.input(irreceiver_pin) 45 | if value: 46 | channel = GPIO.wait_for_edge(irreceiver_pin, GPIO.FALLING, timeout=PULSETIMEOUTMS); 47 | if channel is None: 48 | return []; 49 | except: 50 | # GPIO Error 51 | return [(-2, -2)] 52 | value = 0 53 | 54 | # mark time 55 | startTime = datetime.now() 56 | pulseTime = startTime 57 | 58 | # Pulse Data 59 | pulsedata = [] 60 | 61 | aborted = False 62 | while aborted == False: 63 | # Wait for transition 64 | if value: 65 | channel = GPIO.wait_for_edge(irreceiver_pin, GPIO.FALLING, timeout=PULSETIMEOUTMS); 66 | else: 67 | channel = GPIO.wait_for_edge(irreceiver_pin, GPIO.RISING, timeout=PULSETIMEOUTMS); 68 | 69 | if channel is None: 70 | if ctr == 0: 71 | continue 72 | else: 73 | aborted = True 74 | if len(pulsedata) == 0: 75 | # CTRL+C 76 | return [(-1, -1)] 77 | break; 78 | 79 | # high/low Length 80 | now = datetime.now() 81 | pulseLength = now - pulseTime 82 | pulseTime = now 83 | 84 | # Update value (changed triggered), this also inverts value before saving 85 | if value: 86 | value = 0 87 | else: 88 | value = 1 89 | pulsedata.append((value, pulseLength.microseconds)) 90 | 91 | ctr = ctr + 1 92 | if pulseLength.microseconds > PULSETAIL_MAXMICROS_NEC: 93 | break 94 | elif ctr > PULSEDATA_MAXCOUNT: 95 | break 96 | 97 | # Data is most likely incomplete 98 | if aborted == True: 99 | return [] 100 | elif ctr >= PULSEDATA_MAXCOUNT: 101 | print (" * Unable to decode. Please try again *") 102 | return [] 103 | return pulsedata 104 | 105 | 106 | ######################### 107 | # Use LIRC 108 | lircmode=0 109 | def getLIRCobj(): 110 | try: 111 | # Old method 112 | return lirc.Lirc() 113 | except: 114 | try: 115 | return lirc.Client() 116 | except: 117 | return None 118 | 119 | 120 | def getLIRCPulseData(): 121 | if haslirclib == False: 122 | print (" * LIRC Module not found, please reboot and try again *") 123 | return [] 124 | 125 | irlogfile = "/dev/shm/lircdecoder.log" 126 | 127 | # Clear old data if necessary 128 | if os.path.exists(irlogfile) == True: 129 | os.remove(irlogfile) 130 | # Start logging 131 | try: 132 | # Old method 133 | lirc.set_inputlog(irlogfile) 134 | except: 135 | try: 136 | lirc.start_logging(irlogfile) 137 | except: 138 | print (" * LIRC Module error, please make sure IR button is not already in use, or reboot and try again *") 139 | return [(-1, -1)] 140 | 141 | # Wait for first log 142 | logsize = 0 143 | while logsize == 0: 144 | if os.path.exists(irlogfile) == True: 145 | logsize = os.path.getsize(irlogfile) 146 | if logsize == 0: 147 | time.sleep(0.1) 148 | 149 | print(" Thank you") 150 | 151 | newlogsize = 0 152 | while logsize != newlogsize: 153 | logsize = newlogsize 154 | time.sleep(0.1) 155 | newlogsize = os.path.getsize(irlogfile) 156 | 157 | try: 158 | # Old method 159 | lirc.stop_inputlog() 160 | except: 161 | try: 162 | lirc.stop_logging() 163 | except: 164 | print (" * LIRC Module error, please make sure IR button is not already in use *") 165 | return [(-1, -1)] 166 | 167 | # Pulse Data 168 | pulsedata = [] 169 | 170 | terminated = False 171 | if os.path.exists(irlogfile) == True: 172 | ctr = 0 173 | fp = open(irlogfile, "r") 174 | for curline in fp: 175 | if len(curline) > 0: 176 | rowdata = curline.split(" ") 177 | if len(rowdata) == 2: 178 | duration = int(rowdata[1]) 179 | value = 0 180 | if rowdata[0] == "pulse": 181 | value = 1 182 | ctr = ctr + 1 183 | if value == 1 or ctr > 1: 184 | if len(pulsedata) > 0 and duration > PULSELEADER_MINMICROS_NEC: 185 | terminated = True 186 | break 187 | else: 188 | pulsedata.append((value, duration)) 189 | fp.close() 190 | os.remove(irlogfile) 191 | 192 | # Check if terminating pulse detected 193 | if terminated == False: 194 | print (" * Unable to decode. Please try again *") 195 | return [] 196 | return pulsedata 197 | 198 | 199 | ######################### 200 | # Common 201 | irconffile = "/etc/lirc/lircd.conf.d/argon.lircd.conf" 202 | 203 | # I2C 204 | address = 0x1a # I2C Address 205 | command = 0xaa # I2C Command 206 | 207 | # Constants 208 | PULSETIMEOUTMS = 1000 209 | VERIFYTARGET = 3 210 | PULSEDATA_MAXCOUNT = 200 # Fail safe 211 | 212 | # NEC Protocol Constants 213 | PULSEBIT_MAXMICROS_NEC = 2500 214 | PULSEBIT_ZEROMICROS_NEC = 1000 215 | 216 | PULSELEADER_MINMICROS_NEC = 8000 217 | PULSELEADER_MAXMICROS_NEC = 10000 218 | PULSETAIL_MAXMICROS_NEC = 12000 219 | 220 | # Standard Methods 221 | def getbytestring(pulsedata): 222 | outstring = "" 223 | for curbyte in pulsedata: 224 | tmpstr = hex(curbyte)[2:] 225 | while len(tmpstr) < 2: 226 | tmpstr = "0" + tmpstr 227 | outstring = outstring+tmpstr 228 | return outstring 229 | 230 | def displaybyte(pulsedata): 231 | print (getbytestring(pulsedata)) 232 | 233 | 234 | def pulse2byteNEC(pulsedata): 235 | outdata = [] 236 | bitdata = 1 237 | curbyte = 0 238 | bitcount = 0 239 | for (mode, duration) in pulsedata: 240 | if mode == 1: 241 | continue 242 | elif duration > PULSEBIT_MAXMICROS_NEC: 243 | continue 244 | elif duration > PULSEBIT_ZEROMICROS_NEC: 245 | curbyte = curbyte*2 + 1 246 | else: 247 | curbyte = curbyte*2 248 | 249 | bitcount = bitcount + 1 250 | if bitcount == 8: 251 | outdata.append(curbyte) 252 | curbyte = 0 253 | bitcount = 0 254 | # Shouldn't happen, but just in case 255 | if bitcount > 0: 256 | outdata.append(curbyte) 257 | 258 | return outdata 259 | 260 | 261 | def bytecompare(a, b): 262 | idx = 0 263 | maxidx = len(a) 264 | if maxidx != len(b): 265 | return 1 266 | while idx < maxidx: 267 | if a[idx] != b[idx]: 268 | return 1 269 | idx = idx + 1 270 | return 0 271 | 272 | 273 | # Main Flow 274 | mode = "custom" 275 | if len(sys.argv) > 1: 276 | mode = sys.argv[1] 277 | 278 | powerdata = [] 279 | buttonlist = ['POWER', 'UP', 'DOWN', 'LEFT', 'RIGHT', 280 | 'VOLUMEUP', 'VOLUMEDOWN', 'OK', 'HOME', 'MENU' 281 | 'BACK'] 282 | 283 | ircodelist = ['00ff39c6', '00ff53ac', '00ff4bb4', '00ff9966', '00ff837c', 284 | '00ff01fe', '00ff817e', '00ff738c', '00ffd32c', '00ffb946', 285 | '00ff09f6'] 286 | 287 | buttonidx = 0 288 | 289 | if mode == "power": 290 | buttonlist = ['POWER'] 291 | ircodelist = [''] 292 | elif mode == "resetpower": 293 | # Just Set the power so it won't create/update the conf file 294 | buttonlist = ['POWER'] 295 | mode = "default" 296 | elif mode == "custom": 297 | buttonlist = ['POWER', 'UP', 'DOWN', 'LEFT', 'RIGHT', 298 | 'VOLUMEUP', 'VOLUMEDOWN', 'OK', 'HOME', 'MENU' 299 | 'BACK'] 300 | ircodelist = ['', '', '', '', '', 301 | '', '', '', '', '', 302 | ''] 303 | #buttonlist = ['POWER', 'VOLUMEUP', 'VOLUMEDOWN'] 304 | #ircodelist = ['', '', ''] 305 | 306 | if mode == "default": 307 | # To skip the decoding loop 308 | buttonidx = len(buttonlist) 309 | # Set MCU IR code 310 | powerdata = [0x00, 0xff, 0x39, 0xc6] 311 | else: 312 | print ("************************************************") 313 | print ("* WARNING: Current buttons are still active. *") 314 | print ("* Please temporarily assign to a *") 315 | print ("* different button if you plan to *") 316 | print ("* reuse buttons. *") 317 | print ("* e.g. Power Button triggers shutdown *") 318 | print ("* *") 319 | print ("* PROCEED AT YOUR OWN RISK *") 320 | print ("* (Press CTRL+C to abort at any time) *") 321 | print ("************************************************") 322 | 323 | readaborted = False 324 | # decoding loop 325 | while buttonidx < len(buttonlist): 326 | print ("Press your button for "+buttonlist[buttonidx]+" (CTRL+C to abort)") 327 | irprotocol = "" 328 | outdata = [] 329 | verifycount = 0 330 | readongoing = True 331 | 332 | # Handles NEC protocol Only 333 | while readongoing == True: 334 | # Try GPIO-based reading, if it fails, fallback to LIRC 335 | pulsedata = getGPIOPulseData() 336 | if len(pulsedata) == 1: 337 | if pulsedata[0][0] == -2: 338 | if lircmode == 0: 339 | lirc = getLIRCobj() 340 | if lirc is None: 341 | readongoing = False 342 | readaborted = True 343 | buttonidx = len(buttonlist) 344 | print (" * Error initializing LIRC object") 345 | break; 346 | lircmode = 1 347 | pulsedata = getLIRCPulseData() 348 | 349 | # Aborted 350 | if len(pulsedata) == 1: 351 | if pulsedata[0][0] == -1: 352 | readongoing = False 353 | readaborted = True 354 | buttonidx = len(buttonlist) 355 | break 356 | # Ignore repeat code (NEC) 357 | if len(pulsedata) <= 4: 358 | continue 359 | 360 | # Get leading signal 361 | (mode, duration) = pulsedata[0] 362 | 363 | # Decode IR Protocols 364 | # https://www.sbprojects.net/knowledge/ir/index.php 365 | 366 | if duration >= PULSELEADER_MINMICROS_NEC and duration <= PULSELEADER_MAXMICROS_NEC: 367 | irprotocol = "NEC" 368 | # NEC has 9ms head, +/- 1ms 369 | curdata = pulse2byteNEC(pulsedata) 370 | if verifycount > 0: 371 | if bytecompare(outdata, curdata) == 0: 372 | verifycount = verifycount + 1 373 | else: 374 | verifycount = 0 375 | else: 376 | outdata = curdata 377 | verifycount = 1 378 | 379 | 380 | if verifycount >= VERIFYTARGET: 381 | readongoing = False 382 | print ("") 383 | elif verifycount == 0: 384 | print (" * IR code mismatch, please try again *") 385 | elif VERIFYTARGET - verifycount > 1: 386 | print (" Press the button "+ str(VERIFYTARGET - verifycount)+ " more times") 387 | else: 388 | print (" Press the button 1 more time") 389 | 390 | else: 391 | verifycount = 0 392 | print (" * Unable to decode. Please try again *") 393 | #curdata = pulse2byteLSB(pulsedata) 394 | #displaybyte(curdata) 395 | 396 | # Check for duplicates 397 | newircode = getbytestring(outdata) 398 | if verifycount > 0: 399 | checkidx = 0 400 | while checkidx < buttonidx and checkidx < len(buttonlist): 401 | if ircodelist[checkidx] == newircode: 402 | print (" Button already assigned. Please try again") 403 | verifycount = 0 404 | break 405 | checkidx = checkidx + 1 406 | 407 | # Store code, and power button code if applicable 408 | if verifycount > 0: 409 | if buttonidx == 0: 410 | powerdata = outdata 411 | if buttonidx < len(buttonlist): 412 | # Abort will cause out of bounds 413 | ircodelist[buttonidx] = newircode 414 | #print (buttonlist[buttonidx]+": "+ newircode) 415 | buttonidx = buttonidx + 1 416 | 417 | if len(powerdata) > 0 and readaborted == False: 418 | # Send to device if completed or reset mode 419 | #print("Writing " + getbytestring(powerdata)) 420 | print("Updating Device...") 421 | 422 | rev = GPIO.RPI_REVISION 423 | if rev == 2 or rev == 3: 424 | bus = smbus.SMBus(1) 425 | else: 426 | bus = smbus.SMBus(0) 427 | 428 | bus.write_i2c_block_data(address, command, powerdata) 429 | bus.close() 430 | 431 | # Update IR Conf if there are other button 432 | if buttonidx > 1: 433 | print("Updating Remote Control Codes...") 434 | fp = open(irconffile, "w") 435 | 436 | # Standard NEC conf header 437 | fp.write("#\n") 438 | fp.write("# Based on NEC templates at http://lirc.sourceforge.net/remotes/nec/\n") 439 | fp.write("# Configured codes based on data gathered\n") 440 | fp.write("#\n") 441 | fp.write("\n") 442 | fp.write("begin remote\n") 443 | fp.write(" name argon\n") 444 | fp.write(" bits 32\n") 445 | fp.write(" flags SPACE_ENC\n") 446 | fp.write(" eps 20\n") 447 | fp.write(" aeps 200\n") 448 | fp.write("\n") 449 | fp.write(" header 8800 4400\n") 450 | fp.write(" one 550 1650\n") 451 | fp.write(" zero 550 550\n") 452 | fp.write(" ptrail 550\n") 453 | fp.write(" repeat 8800 2200\n") 454 | fp.write(" gap 38500\n") 455 | fp.write(" toggle_bit 0\n") 456 | fp.write("\n") 457 | fp.write(" frequency 38000\n") 458 | fp.write("\n") 459 | fp.write(" begin codes\n") 460 | 461 | # Write Key Codes 462 | buttonidx = 1 463 | while buttonidx < len(buttonlist): 464 | fp.write(" KEY_"+buttonlist[buttonidx]+" 0x"+ircodelist[buttonidx]+"\n") 465 | buttonidx = buttonidx + 1 466 | fp.write(" end codes\n") 467 | fp.write("end remote\n") 468 | fp.close() 469 | 470 | 471 | 472 | ############### 473 | # GPIO-Only Cleanup 474 | GPIO.cleanup() 475 | 476 | 477 | -------------------------------------------------------------------------------- /src/argononed.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Argon One Fan Configuration 3 | # 4 | # List below the temperature (Celsius) and fan speed (in percent) pairs 5 | # Use the following form: 6 | # min.temperature=speed 7 | # 8 | # Example: 9 | # 55=10 10 | # 60=55 11 | # 65=100 12 | # 13 | # Above example sets the fan speed to 14 | # 15 | # NOTE: Lines begining with # are ignored 16 | # 17 | # Type the following at the command line for changes to take effect: 18 | # sudo systemctl restart '$daemonname'.service 19 | # 20 | # Start below: 21 | 55=10 22 | 60=55 23 | 65=100 24 | -------------------------------------------------------------------------------- /src/argononed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # 4 | # This script set fan speed and monitor power button events. 5 | # 6 | # Fan Speed is set by sending 0 to 100 to the MCU (Micro Controller Unit) 7 | # The values will be interpreted as the percentage of fan speed, 100% being maximum 8 | # 9 | # Power button events are sent as a pulse signal to BCM Pin 4 (BOARD P7) 10 | # A pulse width of 20-30ms indicates reboot request (double-tap) 11 | # A pulse width of 40-50ms indicates shutdown request (hold and release after 3 secs) 12 | # 13 | # Additional comments are found in each function below 14 | # 15 | # Standard Deployment/Triggers: 16 | # * Raspbian, OSMC: Runs as service via /lib/systemd/system/argononed.service 17 | # * lakka, libreelec: Runs as service via /storage/.config/system.d/argononed.service 18 | # * recalbox: Runs as service via /etc/init.d/ 19 | # 20 | 21 | # For Libreelec/Lakka, note that we need to add system paths 22 | # import sys 23 | # sys.path.append('/storage/.kodi/addons/virtual.rpi-tools/lib') 24 | import RPi.GPIO as GPIO 25 | 26 | import sys 27 | import os 28 | import time 29 | from threading import Thread 30 | from queue import Queue 31 | 32 | sys.path.append("/etc/argon/") 33 | from argonsysinfo import * 34 | # Initialize I2C Bus 35 | import smbus 36 | 37 | rev = GPIO.RPI_REVISION 38 | if rev == 2 or rev == 3: 39 | bus=smbus.SMBus(1) 40 | else: 41 | bus=smbus.SMBus(0) 42 | 43 | 44 | OLED_ENABLED=False 45 | 46 | if os.path.exists("/etc/argon/argoneonoled.py"): 47 | import datetime 48 | from argoneonoled import * 49 | OLED_ENABLED=True 50 | 51 | OLED_CONFIGFILE = "/etc/argoneonoled.conf" 52 | 53 | ADDR_FAN=0x1a 54 | PIN_SHUTDOWN=4 55 | 56 | GPIO.setwarnings(False) 57 | GPIO.setmode(GPIO.BCM) 58 | GPIO.setup(PIN_SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) 59 | 60 | 61 | # This function is the thread that monitors activity in our shutdown pin 62 | # The pulse width is measured, and the corresponding shell command will be issued 63 | 64 | def shutdown_check(writeq): 65 | while True: 66 | pulsetime = 1 67 | GPIO.wait_for_edge(PIN_SHUTDOWN, GPIO.RISING) 68 | time.sleep(0.01) 69 | while GPIO.input(PIN_SHUTDOWN) == GPIO.HIGH: 70 | time.sleep(0.01) 71 | pulsetime += 1 72 | if pulsetime >=2 and pulsetime <=3: 73 | # Testing 74 | #writeq.put("OLEDSWITCH") 75 | writeq.put("OLEDSTOP") 76 | os.system("reboot") 77 | elif pulsetime >=4 and pulsetime <=5: 78 | writeq.put("OLEDSTOP") 79 | os.system("shutdown now -h") 80 | elif pulsetime >=6 and pulsetime <=7: 81 | writeq.put("OLEDSWITCH") 82 | 83 | # This function converts the corresponding fanspeed for the given temperature 84 | # The configuration data is a list of strings in the form "=" 85 | 86 | def get_fanspeed(tempval, configlist): 87 | for curconfig in configlist: 88 | curpair = curconfig.split("=") 89 | tempcfg = float(curpair[0]) 90 | fancfg = int(float(curpair[1])) 91 | if tempval >= tempcfg: 92 | if fancfg < 25: 93 | return 25 94 | return fancfg 95 | return 0 96 | 97 | # This function retrieves the fanspeed configuration list from a file, arranged by temperature 98 | # It ignores lines beginning with "#" and checks if the line is a valid temperature-speed pair 99 | # The temperature values are formatted to uniform length, so the lines can be sorted properly 100 | 101 | def load_config(fname): 102 | newconfig = [] 103 | try: 104 | with open(fname, "r") as fp: 105 | for curline in fp: 106 | if not curline: 107 | continue 108 | tmpline = curline.strip() 109 | if not tmpline: 110 | continue 111 | if tmpline[0] == "#": 112 | continue 113 | tmppair = tmpline.split("=") 114 | if len(tmppair) != 2: 115 | continue 116 | tempval = 0 117 | fanval = 0 118 | try: 119 | tempval = float(tmppair[0]) 120 | if tempval < 0 or tempval > 100: 121 | continue 122 | except: 123 | continue 124 | try: 125 | fanval = int(float(tmppair[1])) 126 | if fanval < 0 or fanval > 100: 127 | continue 128 | except: 129 | continue 130 | newconfig.append( "{:5.1f}={}".format(tempval,fanval)) 131 | if len(newconfig) > 0: 132 | newconfig.sort(reverse=True) 133 | except: 134 | return [] 135 | return newconfig 136 | 137 | # Load OLED Config file 138 | def load_oledconfig(fname): 139 | output={} 140 | screenduration=-1 141 | screenlist=[] 142 | try: 143 | with open(fname, "r") as fp: 144 | for curline in fp: 145 | if not curline: 146 | continue 147 | tmpline = curline.strip() 148 | if not tmpline: 149 | continue 150 | if tmpline[0] == "#": 151 | continue 152 | tmppair = tmpline.split("=") 153 | if len(tmppair) != 2: 154 | continue 155 | if tmppair[0] == "switchduration": 156 | output['screenduration']=int(tmppair[1]) 157 | elif tmppair[0] == "screensaver": 158 | output['screensaver']=int(tmppair[1]) 159 | elif tmppair[0] == "screenlist": 160 | output['screenlist']=tmppair[1].replace("\"", "").split(" ") 161 | elif tmppair[0] == "enabled": 162 | output['enabled']=tmppair[1].replace("\"", "") 163 | except: 164 | return {} 165 | return output 166 | 167 | # This function is the thread that monitors temperature and sets the fan speed 168 | # The value is fed to get_fanspeed to get the new fan speed 169 | # To prevent unnecessary fluctuations, lowering fan speed is delayed by 30 seconds 170 | # 171 | # Location of config file varies based on OS 172 | # 173 | def temp_check(): 174 | fanconfig = ["65=100", "60=55", "55=10"] 175 | tmpconfig = load_config("/etc/argononed.conf") 176 | if len(tmpconfig) > 0: 177 | fanconfig = tmpconfig 178 | prevspeed=0 179 | while True: 180 | val = argonsysinfo_gettemp() 181 | newspeed = get_fanspeed(val, fanconfig) 182 | if newspeed < prevspeed: 183 | # Pause 30s if reduce to prevent fluctuations 184 | time.sleep(30) 185 | prevspeed = newspeed 186 | try: 187 | if newspeed > 0: 188 | # Spin up to prevent issues on older units 189 | bus.write_byte(ADDR_FAN,100) 190 | time.sleep(1) 191 | bus.write_byte(ADDR_FAN,newspeed) 192 | time.sleep(30) 193 | except IOError: 194 | time.sleep(60) 195 | 196 | # 197 | # This function is the thread that updates OLED 198 | # 199 | def display_loop(readq): 200 | weekdaynamelist = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 201 | monthlist = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] 202 | oledscreenwidth = oled_getmaxX() 203 | 204 | fontwdSml = 6 # Maps to 6x8 205 | fontwdReg = 8 # Maps to 8x16 206 | stdleftoffset = 54 207 | 208 | screensavermode = False 209 | screensaversec = 120 210 | screensaverctr = 0 211 | 212 | screenenabled = ["clock", "ip"] 213 | prevscreen = "" 214 | curscreen = "" 215 | screenid = 0 216 | screenjogtime = 0 217 | screenjogflag = 0 # start with screenid 0 218 | cpuusagelist = [] 219 | curlist = [] 220 | 221 | tmpconfig=load_oledconfig(OLED_CONFIGFILE) 222 | 223 | if "screensaver" in tmpconfig: 224 | screensaversec = tmpconfig["screensaver"] 225 | if "screenduration" in tmpconfig: 226 | screenjogtime = tmpconfig["screenduration"] 227 | if "screenlist" in tmpconfig: 228 | screenenabled = tmpconfig["screenlist"] 229 | 230 | if "enabled" in tmpconfig: 231 | if tmpconfig["enabled"] == "N": 232 | screenenabled = [] 233 | 234 | while len(screenenabled) > 0: 235 | if len(curlist) == 0 and screenjogflag == 1: 236 | # Reset Screen Saver 237 | screensavermode = False 238 | screensaverctr = 0 239 | 240 | # Update screen info 241 | screenid = screenid + screenjogflag 242 | if screenid >= len(screenenabled): 243 | screenid = 0 244 | prevscreen = curscreen 245 | curscreen = screenenabled[screenid] 246 | 247 | if screenjogtime == 0: 248 | # Resets jogflag (if switched manually) 249 | screenjogflag = 0 250 | else: 251 | screenjogflag = 1 252 | 253 | needsUpdate = False 254 | if curscreen == "cpu": 255 | # CPU Usage 256 | if len(curlist) == 0: 257 | try: 258 | if len(cpuusagelist) == 0: 259 | cpuusagelist = argonsysinfo_listcpuusage() 260 | curlist = cpuusagelist 261 | except: 262 | curlist = [] 263 | if len(curlist) > 0: 264 | oled_loadbg("bgcpu") 265 | 266 | # Display List 267 | yoffset = 0 268 | tmpmax = 4 269 | while tmpmax > 0 and len(curlist) > 0: 270 | curline = "" 271 | tmpitem = curlist.pop(0) 272 | curline = tmpitem["title"]+": "+str(tmpitem["value"])+"%" 273 | oled_writetext(curline, stdleftoffset, yoffset, fontwdSml) 274 | oled_drawfilledrectangle(stdleftoffset, yoffset+12, int((oledscreenwidth-stdleftoffset-4)*tmpitem["value"]/100), 2) 275 | tmpmax = tmpmax - 1 276 | yoffset = yoffset + 16 277 | 278 | needsUpdate = True 279 | else: 280 | # Next page due to error/no data 281 | screenjogflag = 1 282 | elif curscreen == "storage": 283 | # Storage Info 284 | if len(curlist) == 0: 285 | try: 286 | tmpobj = argonsysinfo_listhddusage() 287 | for curdev in tmpobj: 288 | curlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total']), "usage": int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']) }) 289 | #curlist = argonsysinfo_liststoragetotal() 290 | except: 291 | curlist = [] 292 | if len(curlist) > 0: 293 | oled_loadbg("bgstorage") 294 | 295 | yoffset = 16 296 | tmpmax = 3 297 | while tmpmax > 0 and len(curlist) > 0: 298 | tmpitem = curlist.pop(0) 299 | # Right column first, safer to overwrite white space 300 | oled_writetextaligned(tmpitem["value"], 77, yoffset, oledscreenwidth-77, 2, fontwdSml) 301 | oled_writetextaligned(str(tmpitem["usage"])+"%", 50, yoffset, 74-50, 2, fontwdSml) 302 | tmpname = tmpitem["title"] 303 | if len(tmpname) > 8: 304 | tmpname = tmpname[0:8] 305 | oled_writetext(tmpname, 0, yoffset, fontwdSml) 306 | 307 | tmpmax = tmpmax - 1 308 | yoffset = yoffset + 16 309 | needsUpdate = True 310 | else: 311 | # Next page due to error/no data 312 | screenjogflag = 1 313 | 314 | elif curscreen == "raid": 315 | # Raid Info 316 | if len(curlist) == 0: 317 | try: 318 | tmpobj = argonsysinfo_listraid() 319 | curlist = tmpobj['raidlist'] 320 | except: 321 | curlist = [] 322 | if len(curlist) > 0: 323 | oled_loadbg("bgraid") 324 | tmpitem = curlist.pop(0) 325 | oled_writetextaligned(tmpitem["title"], 0, 0, stdleftoffset, 1, fontwdSml) 326 | oled_writetextaligned(tmpitem["value"], 0, 8, stdleftoffset, 1, fontwdSml) 327 | oled_writetextaligned(argonsysinfo_kbstr(tmpitem["info"]["size"]), 0, 56, stdleftoffset, 1, fontwdSml) 328 | 329 | oled_writetext("Used:"+argonsysinfo_kbstr(tmpitem["info"]["used"]), stdleftoffset, 8, fontwdSml) 330 | oled_writetext(" "+str(int(100*tmpitem["info"]["used"]/tmpitem["info"]["size"]))+"%", stdleftoffset, 16, fontwdSml) 331 | oled_writetext("Active:"+str(int(tmpitem["info"]["active"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 32, fontwdSml) 332 | oled_writetext("Working:"+str(int(tmpitem["info"]["working"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 40, fontwdSml) 333 | oled_writetext("Failed:"+str(int(tmpitem["info"]["failed"]))+"/"+str(int(tmpitem["info"]["devices"])), stdleftoffset, 48, fontwdSml) 334 | needsUpdate = True 335 | else: 336 | # Next page due to error/no data 337 | screenjogflag = 1 338 | 339 | elif curscreen == "ram": 340 | # RAM 341 | try: 342 | oled_loadbg("bgram") 343 | tmpraminfo = argonsysinfo_getram() 344 | oled_writetextaligned(tmpraminfo[0], stdleftoffset, 8, oledscreenwidth-stdleftoffset, 1, fontwdReg) 345 | oled_writetextaligned("of", stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg) 346 | oled_writetextaligned(tmpraminfo[1], stdleftoffset, 40, oledscreenwidth-stdleftoffset, 1, fontwdReg) 347 | needsUpdate = True 348 | except: 349 | needsUpdate = False 350 | # Next page due to error/no data 351 | screenjogflag = 1 352 | elif curscreen == "temp": 353 | # Temp 354 | try: 355 | maxht = 21 356 | oled_loadbg("bgtemp") 357 | cval = argonsysinfo_gettemp() 358 | fval = 32+9*cval/5 359 | 360 | # 40C is min, 80C is max 361 | barht = int(maxht*(cval-40)/40) 362 | if barht > maxht: 363 | barht = maxht 364 | elif barht < 1: 365 | barht = 1 366 | 367 | tmpcstr = str(cval) 368 | if len(tmpcstr) > 4: 369 | tmpcstr = tmpcstr[0:4] 370 | tmpfstr = str(fval) 371 | if len(tmpfstr) > 5: 372 | tmpfstr = tmpfstr[0:5] 373 | 374 | oled_writetextaligned(tmpcstr+ chr(167) +"C", stdleftoffset, 16, oledscreenwidth-stdleftoffset, 1, fontwdReg) 375 | oled_writetextaligned(tmpfstr+ chr(167) +"F", stdleftoffset, 32, oledscreenwidth-stdleftoffset, 1, fontwdReg) 376 | 377 | oled_drawfilledrectangle(24, 20+(maxht-barht), 3, barht, 2) 378 | 379 | needsUpdate = True 380 | except: 381 | needsUpdate = False 382 | # Next page due to error/no data 383 | screenjogflag = 1 384 | elif curscreen == "ip": 385 | # IP Address 386 | try: 387 | oled_loadbg("bgip") 388 | oled_writetextaligned(argonsysinfo_getip(), 0, 8, oledscreenwidth, 1, fontwdReg) 389 | needsUpdate = True 390 | except: 391 | needsUpdate = False 392 | # Next page due to error/no data 393 | screenjogflag = 1 394 | else: 395 | try: 396 | oled_loadbg("bgtime") 397 | # Date and Time HH:MM 398 | curtime = datetime.datetime.now() 399 | 400 | # Month/Day 401 | outstr = str(curtime.day).strip() 402 | if len(outstr) < 2: 403 | outstr = " "+outstr 404 | outstr = monthlist[curtime.month-1]+outstr 405 | oled_writetextaligned(outstr, stdleftoffset, 8, oledscreenwidth-stdleftoffset, 1, fontwdReg) 406 | 407 | # Day of Week 408 | oled_writetextaligned(weekdaynamelist[curtime.weekday()], stdleftoffset, 24, oledscreenwidth-stdleftoffset, 1, fontwdReg) 409 | 410 | # Time 411 | outstr = str(curtime.minute).strip() 412 | if len(outstr) < 2: 413 | outstr = "0"+outstr 414 | outstr = str(curtime.hour)+":"+outstr 415 | if len(outstr) < 5: 416 | outstr = "0"+outstr 417 | oled_writetextaligned(outstr, stdleftoffset, 40, oledscreenwidth-stdleftoffset, 1, fontwdReg) 418 | 419 | needsUpdate = True 420 | except: 421 | needsUpdate = False 422 | # Next page due to error/no data 423 | screenjogflag = 1 424 | 425 | if needsUpdate == True: 426 | if screensavermode == False: 427 | # Update screen if not screen saver mode 428 | oled_power(True) 429 | oled_flushimage(prevscreen != curscreen) 430 | oled_reset() 431 | 432 | timeoutcounter = 0 433 | while timeoutcounter= 60 and screensavermode == False: 466 | # Refresh data every minute, unless screensaver got triggered 467 | screenjogflag = 0 468 | break 469 | display_defaultimg() 470 | 471 | def display_defaultimg(): 472 | # Load default image 473 | #oled_power(True) 474 | #oled_loadbg("bgdefault") 475 | #oled_flushimage() 476 | oled_fill(0) 477 | oled_reset() 478 | 479 | if len(sys.argv) > 1: 480 | cmd = sys.argv[1].upper() 481 | if cmd == "SHUTDOWN": 482 | # Signal poweroff 483 | bus.write_byte(ADDR_FAN,0xFF) 484 | 485 | 486 | elif cmd == "FANOFF": 487 | # Turn off fan 488 | bus.write_byte(ADDR_FAN,0) 489 | if OLED_ENABLED == True: 490 | display_defaultimg() 491 | 492 | elif cmd == "SERVICE": 493 | # Starts the power button and temperature monitor threads 494 | try: 495 | ipcq = Queue() 496 | t1 = Thread(target = shutdown_check, args =(ipcq, )) 497 | 498 | t2 = Thread(target = temp_check) 499 | if OLED_ENABLED == True: 500 | t3 = Thread(target = display_loop, args =(ipcq, )) 501 | 502 | t1.start() 503 | t2.start() 504 | if OLED_ENABLED == True: 505 | t3.start() 506 | ipcq.join() 507 | except: 508 | GPIO.cleanup() 509 | -------------------------------------------------------------------------------- /src/argononed.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Argon One Fan and Button Service 3 | After=multi-user.target 4 | [Service] 5 | Type=simple 6 | Restart=always 7 | RemainAfterExit=true 8 | ExecStart=/usr/bin/python3 /etc/argon/argononed.py SERVICE 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /src/argonsysinfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # 4 | # Misc methods to retrieve system information. 5 | # 6 | 7 | import os 8 | import time 9 | import socket 10 | 11 | def argonsysinfo_listcpuusage(sleepsec = 1): 12 | outputlist = [] 13 | curusage_a = argonsysinfo_getcpuusagesnapshot() 14 | time.sleep(sleepsec) 15 | curusage_b = argonsysinfo_getcpuusagesnapshot() 16 | 17 | for cpuname in curusage_a: 18 | if cpuname == "cpu": 19 | continue 20 | if curusage_a[cpuname]["total"] == curusage_b[cpuname]["total"]: 21 | outputlist.append({"title": cpuname, "value": "0%"}) 22 | else: 23 | total = curusage_b[cpuname]["total"]-curusage_a[cpuname]["total"] 24 | idle = curusage_b[cpuname]["idle"]-curusage_a[cpuname]["idle"] 25 | outputlist.append({"title": cpuname, "value": int(100*(total-idle)/(total))}) 26 | return outputlist 27 | 28 | def argonsysinfo_getcpuusagesnapshot(): 29 | cpupercent = {} 30 | errorflag = False 31 | try: 32 | cpuctr = 0 33 | # user, nice, system, idle, iowait, irc, softirq, steal, guest, guest nice 34 | tempfp = open("/proc/stat", "r") 35 | alllines = tempfp.readlines() 36 | for temp in alllines: 37 | temp = temp.replace('\t', ' ') 38 | temp = temp.strip() 39 | while temp.find(" ") >= 0: 40 | temp = temp.replace(" ", " ") 41 | if len(temp) < 3: 42 | cpuctr = cpuctr +1 43 | continue 44 | 45 | checkname = temp[0:3] 46 | if checkname == "cpu": 47 | infolist = temp.split(" ") 48 | idle = 0 49 | total = 0 50 | colctr = 1 51 | while colctr < len(infolist): 52 | curval = int(infolist[colctr]) 53 | if colctr == 4 or colctr == 5: 54 | idle = idle + curval 55 | total = total + curval 56 | colctr = colctr + 1 57 | if total > 0: 58 | cpupercent[infolist[0]] = {"total": total, "idle": idle} 59 | cpuctr = cpuctr +1 60 | 61 | tempfp.close() 62 | except IOError: 63 | errorflag = True 64 | return cpupercent 65 | 66 | 67 | def argonsysinfo_liststoragetotal(): 68 | outputlist = [] 69 | ramtotal = 0 70 | errorflag = False 71 | 72 | try: 73 | hddctr = 0 74 | tempfp = open("/proc/partitions", "r") 75 | alllines = tempfp.readlines() 76 | 77 | for temp in alllines: 78 | temp = temp.replace('\t', ' ') 79 | temp = temp.strip() 80 | while temp.find(" ") >= 0: 81 | temp = temp.replace(" ", " ") 82 | infolist = temp.split(" ") 83 | if len(infolist) >= 4: 84 | # Check if header 85 | if infolist[3] != "name": 86 | parttype = infolist[3][0:3] 87 | if parttype == "ram": 88 | ramtotal = ramtotal + int(infolist[2]) 89 | elif parttype[0:2] == "sd" or parttype[0:2] == "hd": 90 | lastchar = infolist[3][-1] 91 | if lastchar.isdigit() == False: 92 | outputlist.append({"title": infolist[3], "value": argonsysinfo_kbstr(int(infolist[2]))}) 93 | else: 94 | # SD Cards 95 | lastchar = infolist[3][-2] 96 | if lastchar[0] != "p": 97 | outputlist.append({"title": infolist[3], "value": argonsysinfo_kbstr(int(infolist[2]))}) 98 | 99 | tempfp.close() 100 | #outputlist.append({"title": "ram", "value": argonsysinfo_kbstr(ramtotal)}) 101 | except IOError: 102 | errorflag = True 103 | return outputlist 104 | 105 | def argonsysinfo_getram(): 106 | totalram = 0 107 | totalfree = 0 108 | tempfp = open("/proc/meminfo", "r") 109 | alllines = tempfp.readlines() 110 | 111 | for temp in alllines: 112 | temp = temp.replace('\t', ' ') 113 | temp = temp.strip() 114 | while temp.find(" ") >= 0: 115 | temp = temp.replace(" ", " ") 116 | infolist = temp.split(" ") 117 | if len(infolist) >= 2: 118 | if infolist[0] == "MemTotal:": 119 | totalram = int(infolist[1]) 120 | elif infolist[0] == "MemFree:": 121 | totalfree = totalfree + int(infolist[1]) 122 | elif infolist[0] == "Buffers:": 123 | totalfree = totalfree + int(infolist[1]) 124 | elif infolist[0] == "Cached:": 125 | totalfree = totalfree + int(infolist[1]) 126 | if totalram == 0: 127 | return "0%" 128 | return [str(int(100*totalfree/totalram))+"%", str((totalram+512*1024)>>20)+"GB"] 129 | 130 | 131 | def argonsysinfo_gettemp(): 132 | try: 133 | tempfp = open("/sys/class/thermal/thermal_zone0/temp", "r") 134 | temp = tempfp.readline() 135 | tempfp.close() 136 | return float(int(temp)/1000) 137 | except IOError: 138 | return 0 139 | cval = val/1000 140 | fval = 32+9*val/5000 141 | 142 | def argonsysinfo_getip(): 143 | ipaddr = "" 144 | st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 145 | try: 146 | # Connect to nonexistent device 147 | st.connect(('254.255.255.255', 1)) 148 | ipaddr = st.getsockname()[0] 149 | except Exception: 150 | ipaddr = 'N/A' 151 | finally: 152 | st.close() 153 | return ipaddr 154 | 155 | 156 | def argonsysinfo_getrootdev(): 157 | tmp = os.popen('mount').read() 158 | alllines = tmp.split("\n") 159 | 160 | for temp in alllines: 161 | temp = temp.replace('\t', ' ') 162 | temp = temp.strip() 163 | while temp.find(" ") >= 0: 164 | temp = temp.replace(" ", " ") 165 | infolist = temp.split(" ") 166 | if len(infolist) >= 3: 167 | 168 | if infolist[2] == "/": 169 | return infolist[0] 170 | return "" 171 | 172 | def argonsysinfo_listhddusage(): 173 | outputobj = {} 174 | raidlist = argonsysinfo_listraid() 175 | raiddevlist = [] 176 | raidctr = 0 177 | while raidctr < len(raidlist['raidlist']): 178 | raiddevlist.append(raidlist['raidlist'][raidctr]['title']) 179 | outputobj[raidlist['raidlist'][raidctr]['title']] = {"used":int(raidlist['raidlist'][raidctr]['info']['used']), "total":int(raidlist['raidlist'][raidctr]['info']['size'])} 180 | raidctr = raidctr + 1 181 | 182 | rootdev = argonsysinfo_getrootdev() 183 | 184 | tmp = os.popen('df').read() 185 | alllines = tmp.split("\n") 186 | 187 | for temp in alllines: 188 | temp = temp.replace('\t', ' ') 189 | temp = temp.strip() 190 | while temp.find(" ") >= 0: 191 | temp = temp.replace(" ", " ") 192 | infolist = temp.split(" ") 193 | if len(infolist) >= 6: 194 | if infolist[1] == "Size": 195 | continue 196 | if len(infolist[0]) < 5: 197 | continue 198 | elif infolist[0][0:5] != "/dev/": 199 | continue 200 | curdev = infolist[0] 201 | if curdev == "/dev/root" and rootdev != "": 202 | curdev = rootdev 203 | tmpidx = curdev.rfind("/") 204 | if tmpidx >= 0: 205 | curdev = curdev[tmpidx+1:] 206 | 207 | if curdev in raidlist['hddlist']: 208 | continue 209 | elif curdev in raiddevlist: 210 | continue 211 | elif curdev[0:2] == "sd" or curdev[0:2] == "hd": 212 | curdev = curdev[0:-1] 213 | else: 214 | curdev = curdev[0:-2] 215 | if curdev in outputobj: 216 | outputobj[curdev] = {"used":outputobj[curdev]['used']+int(infolist[2]), "total":outputobj[curdev]['total']+int(infolist[1])} 217 | else: 218 | outputobj[curdev] = {"used":int(infolist[2]), "total":int(infolist[1])} 219 | 220 | return outputobj 221 | 222 | def argonsysinfo_kbstr(kbval, wholenumbers = True): 223 | remainder = 0 224 | suffixidx = 0 225 | suffixlist = ["KB", "MB", "GB", "TB"] 226 | while kbval > 1023 and suffixidx < len(suffixlist): 227 | remainder = kbval & 1023 228 | kbval = kbval >> 10 229 | suffixidx = suffixidx + 1 230 | 231 | #return str(kbval)+"."+str(remainder) + suffixlist[suffixidx] 232 | remainderstr = "" 233 | if kbval < 100 and wholenumbers == False: 234 | remainder = int((remainder+50)/100) 235 | if remainder > 0: 236 | remainderstr = "."+str(remainder) 237 | elif remainder >= 500: 238 | kbval = kbval + 1 239 | return str(kbval)+remainderstr + suffixlist[suffixidx] 240 | 241 | def argonsysinfo_listraid(): 242 | hddlist = [] 243 | outputlist = [] 244 | # cat /proc/mdstat 245 | # multiple mdxx from mdstat 246 | # mdadm -D /dev/md1 247 | 248 | ramtotal = 0 249 | errorflag = False 250 | try: 251 | hddctr = 0 252 | tempfp = open("/proc/mdstat", "r") 253 | alllines = tempfp.readlines() 254 | for temp in alllines: 255 | temp = temp.replace('\t', ' ') 256 | temp = temp.strip() 257 | while temp.find(" ") >= 0: 258 | temp = temp.replace(" ", " ") 259 | infolist = temp.split(" ") 260 | if len(infolist) >= 4: 261 | 262 | # Check if raid info 263 | if infolist[0] != "Personalities" and infolist[1] == ":": 264 | devname = infolist[0] 265 | raidtype = infolist[3] 266 | raidstatus = infolist[2] 267 | hddctr = 4 268 | while hddctr < len(infolist): 269 | tmpdevname = infolist[hddctr] 270 | tmpidx = tmpdevname.find("[") 271 | if tmpidx >= 0: 272 | tmpdevname = tmpdevname[0:tmpidx] 273 | hddlist.append(tmpdevname) 274 | hddctr = hddctr + 1 275 | devdetail = argonsysinfo_getraiddetail(devname) 276 | outputlist.append({"title": devname, "value": raidtype, "info": devdetail}) 277 | 278 | tempfp.close() 279 | except IOError: 280 | # No raid 281 | errorflag = True 282 | 283 | return {"raidlist": outputlist, "hddlist": hddlist} 284 | 285 | 286 | def argonsysinfo_getraiddetail(devname): 287 | state = "" 288 | raidtype = "" 289 | size = 0 290 | used = 0 291 | total = 0 292 | working = 0 293 | active = 0 294 | failed = 0 295 | spare = 0 296 | tmp = os.popen('mdadm -D /dev/'+devname).read() 297 | alllines = tmp.split("\n") 298 | 299 | for temp in alllines: 300 | temp = temp.replace('\t', ' ') 301 | temp = temp.strip() 302 | while temp.find(" ") >= 0: 303 | temp = temp.replace(" ", " ") 304 | infolist = temp.split(" : ") 305 | if len(infolist) == 2: 306 | if infolist[0].lower() == "raid level": 307 | raidtype = infolist[1] 308 | elif infolist[0].lower() == "array size": 309 | tmpidx = infolist[1].find(" ") 310 | if tmpidx > 0: 311 | size = (infolist[1][0:tmpidx]) 312 | elif infolist[0].lower() == "used dev size": 313 | tmpidx = infolist[1].find(" ") 314 | if tmpidx > 0: 315 | used = (infolist[1][0:tmpidx]) 316 | elif infolist[0].lower() == "state": 317 | state = infolist[1] 318 | elif infolist[0].lower() == "total devices": 319 | total = infolist[1] 320 | elif infolist[0].lower() == "active devices": 321 | active = infolist[1] 322 | elif infolist[0].lower() == "working devices": 323 | working = infolist[1] 324 | elif infolist[0].lower() == "failed devices": 325 | failed = infolist[1] 326 | elif infolist[0].lower() == "spare devices": 327 | spare = infolist[1] 328 | return {"state": state, "raidtype": raidtype, "size": int(size), "used": int(used), "devices": int(total), "active": int(active), "working": int(working), "failed": int(failed), "spare": int(spare)} -------------------------------------------------------------------------------- /tutorials/fanspeed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import smbus 4 | import RPi.GPIO as GPIO 5 | 6 | rev = GPIO.RPI_REVISION 7 | if rev == 2 or rev == 3: 8 | bus = smbus.SMBus(1) 9 | else: 10 | bus = smbus.SMBus(0) 11 | 12 | argononeaddress = 0x1a 13 | 14 | # Change the value, 0 to 100 15 | fanspeed = 50 16 | 17 | bus.write_byte(argononeaddress,fanspeed) 18 | bus.close() 19 | print "Fan speed updated" 20 | -------------------------------------------------------------------------------- /tutorials/install-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | argon_check_pkg() { 4 | RESULT=$(dpkg-query -W -f='${Status}\n' "$1" 2> /dev/null | grep "installed") 5 | 6 | if [ "" == "$RESULT" ]; then 7 | echo "NG" 8 | else 9 | echo "OK" 10 | fi 11 | } 12 | 13 | pkglist=(raspi-gpio python-rpi.gpio python3-rpi.gpio python-smbus python3-smbus i2c-tools) 14 | for curpkg in ${pkglist[@]}; do 15 | sudo apt-get install -y $curpkg 16 | RESULT=$(argon_check_pkg "$curpkg") 17 | if [ "NG" == "$RESULT" ] 18 | then 19 | echo "********************************************************************" 20 | echo "Please also connect device to the internet and restart installation." 21 | echo "********************************************************************" 22 | exit 23 | fi 24 | done 25 | 26 | # Enable i2c and serial 27 | sudo raspi-config nonint do_i2c 0 28 | sudo raspi-config nonint do_serial 2 29 | -------------------------------------------------------------------------------- /tutorials/oledsample-apicalls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | if os.path.exists("/etc/argon/argoneonoled.py"): 5 | import sys 6 | sys.path.append("/etc/argon/") 7 | from argoneonoled import * 8 | else: 9 | print("Please install Argon script") 10 | exit() 11 | 12 | print("You may need to disable OLED from argon-config") 13 | 14 | 15 | # Get Screen dimensions 16 | oledht = oled_getmaxY() 17 | oledwd = oled_getmaxX() 18 | 19 | # Ensure display is on 20 | oled_power(True) 21 | 22 | # Clear OLED buffer 23 | oled_clearbuffer() 24 | 25 | # Write text 26 | xpos = 10 27 | ypos = 0 28 | charwd=12 # Values are 6, 12, 24, 48, these are 8, 16, 32, 64 px tall respectively 29 | oled_writetext("Hello!", xpos, ypos, charwd) 30 | 31 | # Draw rectangle 32 | xpos = 10 33 | ypos = 17 34 | rectwd = oledwd - 2*xpos 35 | rectht = 14 36 | oled_drawfilledrectangle(xpos, ypos, rectwd, rectht) 37 | 38 | # Draw Pixel (1 or 0) 39 | xpos = 15 40 | ypos = 35 41 | oled_writebuffer(xpos, ypos, 1) 42 | 43 | # Draw text 44 | xpos = 15 45 | ypos = 40 46 | charwd = 6 47 | oled_writetext("Dot is above!", xpos, ypos, charwd) 48 | 49 | 50 | # Draw pixels across the rectangle 51 | xpos = 15 52 | ypos = 17 53 | ctr = 0 54 | while ctr < rectht: 55 | oled_writebuffer(xpos+ctr, ypos+ctr, 0) 56 | # Second slash 57 | oled_writebuffer(xpos+ctr + rectht, ypos+ctr, 0) 58 | ctr = ctr + 1 59 | oled_writebuffer(xpos, ypos, 0) 60 | oled_writebuffer(xpos+2, ypos+2, 0) 61 | oled_writebuffer(xpos, ypos, 1) 62 | 63 | # Update OLED screen with buffer content 64 | oled_flushimage() 65 | 66 | # Reset OLED settings 67 | oled_reset() 68 | 69 | 70 | -------------------------------------------------------------------------------- /tutorials/oledsample-imageloader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from PIL import Image 4 | import math 5 | 6 | import os 7 | if os.path.exists("/etc/argon/argoneonoled.py"): 8 | import sys 9 | sys.path.append("/etc/argon/") 10 | from argoneonoled import * 11 | else: 12 | print("Please install Argon script") 13 | exit() 14 | 15 | print("You may need to disable OLED from argon-config") 16 | 17 | 18 | # Image path 19 | imgfname="/usr/share/plymouth/themes/pix/splash.png" 20 | 21 | 22 | # Get Screen dimensions 23 | oledht = oled_getmaxY() 24 | oledwd = oled_getmaxX() 25 | 26 | # Ensure display is on 27 | oled_power(True) 28 | 29 | # Clear OLED buffer 30 | oled_clearbuffer() 31 | 32 | 33 | # Load image 34 | imgdata = Image.open(imgfname) 35 | imgwd, imght = imgdata.size 36 | 37 | # Rescale image to fit screen 38 | scalefactor = oledwd/imgwd 39 | if scalefactor > (oledht/imght): 40 | scalefactor = oledht/imght 41 | imgwd = math.floor(imgwd*scalefactor) 42 | imght = math.floor(imght*scalefactor) 43 | imgdata = imgdata.resize((imgwd, imght), Image.ANTIALIAS) 44 | 45 | # Offsets to center image 46 | xoffset = math.floor((oledwd-imgwd)/2) 47 | yoffset = math.floor((oledht-imght)/2) 48 | 49 | xpos = 0 50 | while xpos < imgwd: 51 | ypos = 0 52 | while ypos < imght: 53 | r, g, b, p = imgdata.getpixel((xpos, ypos)) 54 | 55 | # Check if we'll write pixel 56 | if (r+g+b) >= 128: 57 | oled_writebuffer(xpos+xoffset, ypos+yoffset, 1) 58 | 59 | ypos = ypos + 1 60 | 61 | xpos = xpos + 1 62 | 63 | # Close image 64 | imgdata.close() 65 | 66 | # Update OLED screen with buffer content 67 | oled_flushimage() 68 | 69 | # Reset OLED settings 70 | oled_reset() 71 | -------------------------------------------------------------------------------- /tutorials/powerbuttonevents.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import RPi.GPIO as GPIO 4 | 5 | # Setup GPIO 6 | GPIO.setwarnings(False) 7 | GPIO.setmode(GPIO.BCM) 8 | powerbuttonevent_pin=4 9 | GPIO.setup(powerbuttonevent_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) 10 | 11 | 12 | print "Double Tap or Hold and Release Power Button after 3 seconds (CTRL+C to end)..." 13 | 14 | try: 15 | # Listen to GPIO BCM 4 / BOARD P7 16 | while True: 17 | pulsetime = 10 18 | GPIO.wait_for_edge(powerbuttonevent_pin, GPIO.RISING) 19 | time.sleep(0.01) 20 | while GPIO.input(powerbuttonevent_pin) == GPIO.HIGH: 21 | time.sleep(0.01) 22 | pulsetime += 10 23 | print pulsetime 24 | 25 | except KeyboardInterrupt: 26 | print "Done" 27 | except: 28 | print "Error" 29 | finally: 30 | GPIO.cleanup() # this ensures a clean exit 31 | -------------------------------------------------------------------------------- /tutorials/signalpowercut.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import smbus 4 | import RPi.GPIO as GPIO 5 | 6 | rev = GPIO.RPI_REVISION 7 | if rev == 2 or rev == 3: 8 | bus = smbus.SMBus(1) 9 | else: 10 | bus = smbus.SMBus(0) 11 | 12 | 13 | argononeaddress = 0x1a 14 | 15 | bus.write_byte(argononeaddress,0xFF) 16 | bus.close() 17 | print "Shutdown the system to cut power" 18 | --------------------------------------------------------------------------------