├── firmware ├── UPS_PIco_0x59_26_01_2016.hex ├── UPS_PIco_0x5C_25_02_2016.hex ├── Simple_Setting_Guide_for_the_UPS_PIco.pdf ├── UPS_PIco_0x59_26_01_2016 recent changes.pdf └── UPS_PIco_0x5C_25_02_2016 recent changes.pdf ├── systemd ├── hwclock-start.service └── hwclock-stop.service ├── rc.local ├── picofssd.py ├── README.md ├── pico_status.py ├── pico_register_readout.py ├── picofssd_mail.py ├── picofu2.py └── picofu3.py /firmware/UPS_PIco_0x59_26_01_2016.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwire/upspico/HEAD/firmware/UPS_PIco_0x59_26_01_2016.hex -------------------------------------------------------------------------------- /firmware/UPS_PIco_0x5C_25_02_2016.hex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwire/upspico/HEAD/firmware/UPS_PIco_0x5C_25_02_2016.hex -------------------------------------------------------------------------------- /firmware/Simple_Setting_Guide_for_the_UPS_PIco.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwire/upspico/HEAD/firmware/Simple_Setting_Guide_for_the_UPS_PIco.pdf -------------------------------------------------------------------------------- /firmware/UPS_PIco_0x59_26_01_2016 recent changes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwire/upspico/HEAD/firmware/UPS_PIco_0x59_26_01_2016 recent changes.pdf -------------------------------------------------------------------------------- /firmware/UPS_PIco_0x5C_25_02_2016 recent changes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwire/upspico/HEAD/firmware/UPS_PIco_0x5C_25_02_2016 recent changes.pdf -------------------------------------------------------------------------------- /systemd/hwclock-start.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Set time from RTC on startup 3 | After=network.target 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/sbin/hwclock -s 8 | TimeoutSec=0 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /systemd/hwclock-stop.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Synchronise Hardware Clock to System Clock 3 | DefaultDependencies=no 4 | Before=shutdown.target 5 | 6 | [Service] 7 | Type=oneshot 8 | ExecStart=/sbin/hwclock --systohc 9 | 10 | [Install] 11 | WantedBy=reboot.target halt.target poweroff.target 12 | -------------------------------------------------------------------------------- /rc.local: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # 3 | # rc.local 4 | # 5 | # This script is executed at the end of each multiuser runlevel. 6 | # Make sure that the script will "exit 0" on success or any other 7 | # value on error. 8 | # 9 | # In order to enable or disable this script just change the execution 10 | # bits. 11 | # 12 | # By default this script does nothing. 13 | 14 | # Print the IP address 15 | _IP=$(hostname -I) || true 16 | if [ "$_IP" ]; then 17 | printf "My IP address is %s\n" "$_IP" 18 | fi 19 | 20 | # Run UPS Pico FSSD script and load the included emulated RTC 21 | sudo python /home/pi/picofssd.py & 22 | 23 | echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device 24 | ( sleep 4; hwclock -s ) & 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /picofssd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # Import the libraries to use time delays, send os commands and access GPIO pins 3 | import RPi.GPIO as GPIO 4 | import time 5 | import os 6 | 7 | GPIO.setmode(GPIO.BCM) # Set pin numbering to board numbering 8 | GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Setup pin 27 as an input 9 | GPIO.setup(22, GPIO.OUT) # Setup pin 22 as an output 10 | 11 | while True: # Setup a while loop to wait for a button press 12 | GPIO.output(22,True) 13 | time.sleep(0.25) # Allow a sleep time of 0.25 second to reduce CPU usage 14 | GPIO.output(22,False) 15 | if(GPIO.input(27)==0): # Setup an if loop to run a shutdown command when button press sensed 16 | os.system("sudo shutdown -h now") # Send shutdown command to os 17 | break 18 | 19 | time.sleep(0.25) # Allow a sleep time of 0.25 second to reduce CPU usage 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # upspico 2 | Scripts for the UPS PIco from http://www.pimodules.com/ 3 | 4 | These are tested and working on a Debian 8 (Stretch) system with systemd. 5 | 6 | ## Scripts - Descriptions 7 | * pico_register_readout.py: Read all registers and output 8 | * pico_status.py: Output status (voltage, onbattery etc. in the same format as apcaccess so you can use the same scripts to parse the output). 9 | * picofssd_mail.py: UPS monitoring script. Requires something like nullmailer to allow the system to actually send emails. Monitors the UPS, checks for charging and sends emails on status change. 10 | * picofu2.py: Firmware updater. 11 | 12 | ## Setup 13 | ``` 14 | mkdir /opt/upspico 15 | cd /opt/upspico 16 | git clone https://github.com/mattwire/upspico.git 17 | ``` 18 | ### To upload firmware: 19 | /opt/upspico/picofu2.py -v -f /opt/upspico/firmware/UPS_PIco_V1.0_10_11_2015_code_0x53.hex 20 | 21 | ### Edit /etc/rc.local and add the following line before exit 0: 22 | `/opt/upspico/picofssd_mail.py &` 23 | 24 | ### Enable serial port for upspico 25 | Comment out in /etc/inittab: 26 | `#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100` 27 | 28 | Edit /boot/cmdline.txt from: 29 | `dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait` 30 | 31 | To: 32 | `dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait` 33 | 34 | ### Setup hwclock with systemd 35 | Note that this requires a recent kernel with rtc-ds1307 module 36 | 37 | ``` 38 | cp /opt/upspico/systemd/*.service /lib/systemd/system 39 | systemctl enable hwclock-start hwclock-stop 40 | ``` 41 | 42 | Edit /etc/modules and add: 43 | ``` 44 | i2c-bcm2708 45 | i2c-dev 46 | rtc-ds1307 47 | ``` 48 | 49 | Add to /boot/config.txt: 50 | ``` 51 | dtparam=i2c_arm=on 52 | dtoverlay=ds1307-rtc 53 | dtparam=spi=on 54 | ``` 55 | 56 | ## Useful Commands 57 | ### Set on battery runtime to maximum 58 | `i2cset -y 1 0x6b 0x09 0xff` 59 | 60 | ### Update firmware 61 | `i2cset -y 1 0x6b 0x00 0xff && ./picofu3.py -v -f UPS_PIco.hex` 62 | -------------------------------------------------------------------------------- /pico_status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | # improved and completed by PiModules Version 1.0 29.08.2015 4 | # picoStatus-v3.py by KTB is based on upisStatus.py by Kyriakos Naziris 5 | # Kyriakos Naziris / University of Portsmouth / kyriakos@naziris.co.uk 6 | 7 | import sys 8 | import smbus 9 | import time 10 | import datetime 11 | 12 | # You can install psutil using: sudo pip install psutil 13 | #import psutil 14 | 15 | i2c = smbus.SMBus(1) 16 | 17 | def pwr_mode(): 18 | time.sleep(0.1) 19 | data = i2c.read_byte_data(0x69, 0x00) 20 | data = data & ~(1 << 7) 21 | if (data == 1): 22 | return "ONLINE" 23 | elif (data == 2): 24 | return "ONBATT" 25 | else: 26 | return "ERR" 27 | 28 | def bat_level(): 29 | time.sleep(0.1) 30 | data = i2c.read_word_data(0x69, 0x01) 31 | data = format(data,"02x") 32 | return (float(data) / 100) 33 | 34 | def rpi_level(): 35 | time.sleep(0.1) 36 | data = i2c.read_word_data(0x69, 0x03) 37 | data = format(data,"02x") 38 | return (float(data) / 100) 39 | 40 | def watchdog_val(): 41 | time.sleep(0.1) 42 | data = i2c.read_word_data(0x69, 0x0e) 43 | data = format(data,"02x") 44 | return data 45 | 46 | def charger_state(): 47 | time.sleep(0.1) 48 | data = i2c.read_byte_data(0x69, 0x10) 49 | data = data & ~(1 << 7) 50 | if (data == 1): 51 | return "CHARGING" 52 | elif (data == 0): 53 | return "NOT CHARGING" 54 | else: 55 | return "ERR" 56 | 57 | def fw_version(): 58 | time.sleep(0.1) 59 | data = i2c.read_byte_data(0x6b, 0x00) 60 | data = format(data,"02x") 61 | return data 62 | 63 | def sot23_temp(): 64 | time.sleep(0.1) 65 | data = i2c.read_byte_data(0x69, 0x0C) 66 | data = format(data,"02x") 67 | return data 68 | 69 | def to92_temp(): 70 | time.sleep(0.1) 71 | data = i2c.read_byte_data(0x69, 0x0d) 72 | data = format(data,"02x") 73 | return data 74 | 75 | def ad1_read(): 76 | time.sleep(0.1) 77 | data = i2c.read_word_data(0x69, 0x05) 78 | data = format(data,"02x") 79 | return (float(data) / 100) 80 | 81 | def ad2_read(): 82 | time.sleep(0.1) 83 | data = i2c.read_word_data(0x69, 0x07) 84 | data = format(data,"02x") 85 | return (float(data) / 100) 86 | 87 | def status(): 88 | status = "TIME : %s" % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S') \ 89 | + "\nFIRMWARE : %s" % fw_version() \ 90 | + "\nSTATUS : %s " % pwr_mode() \ 91 | + "\nSTATE : %s " % charger_state() \ 92 | + "\nBATTV : %.02f Volts" % bat_level() \ 93 | + "\nRPIV : %.02f Volts" % rpi_level() \ 94 | + "\nITEMP : %s C" % sot23_temp() \ 95 | + "\nA/D1 Voltage : %.02f V" % ad1_read() \ 96 | + "\nA/D2 Voltage : %.02f V" % ad2_read() \ 97 | + "\nWatchdog : %s" % watchdog_val() 98 | return status 99 | 100 | if __name__ == '__main__': 101 | print status() 102 | -------------------------------------------------------------------------------- /pico_register_readout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # pico_register_readout.py 4 | # Written by Pontus Petersson (pontus.pson@gmail.com) 5 | # Version 1: 2015-10-16 6 | 7 | import commands 8 | 9 | # RTC data 10 | year = commands.getstatusoutput("i2cget -y 1 0x6A 6") 11 | month = commands.getstatusoutput("i2cget -y 1 0x6A 5") 12 | day = commands.getstatusoutput("i2cget -y 1 0x6A 4") 13 | dow = commands.getstatusoutput("i2cget -y 1 0x6A 3") 14 | hour = commands.getstatusoutput("i2cget -y 1 0x6A 2") 15 | min = commands.getstatusoutput("i2cget -y 1 0x6A 1") 16 | sec = commands.getstatusoutput("i2cget -y 1 0x6A 0") 17 | ccf = commands.getstatusoutput("i2cget -y 1 0x6A 7") 18 | 19 | days_of_the_week = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 20 | 21 | YEAR = "20" + year[1][2:] 22 | MONTH = month[1][2:] 23 | DAY = day[1][2:] 24 | DOW = days_of_the_week[int(dow[1][3:])-1] 25 | HOUR = hour[1][2:] 26 | MIN = min[1][2:] 27 | SEC = sec[1][2:] 28 | CCF = ccf[1][2:] 29 | 30 | print("--- RTC data ---") 31 | print(YEAR+"-"+MONTH+"-"+DAY+" "+DOW+" "+HOUR+":"+MIN+":"+SEC+" /"+CCF+"\n") 32 | 33 | # Status registers 34 | mode = commands.getstatusoutput("i2cget -y 1 0x69 0") 35 | batlevel_1 = commands.getstatusoutput("i2cget -y 1 0x69 2") 36 | batlevel_2 = commands.getstatusoutput("i2cget -y 1 0x69 1") 37 | rpilevel_1 = commands.getstatusoutput("i2cget -y 1 0x69 4") 38 | rpilevel_2 = commands.getstatusoutput("i2cget -y 1 0x69 3") 39 | tmpcels = commands.getstatusoutput("i2cget -y 1 0x69 12") 40 | 41 | powering_modes = ['RPi', 'Bat'] 42 | 43 | MODE = powering_modes[int(mode[1][2:])-1] 44 | BATLEVEL = batlevel_1[1][3:]+"."+batlevel_2[1][2:]+" V" 45 | RPILEVEL = rpilevel_1[1][3:]+"."+rpilevel_2[1][2:]+" V" 46 | TMPCELS = tmpcels[1][2:]+" "+unichr(176)+"C" 47 | 48 | print("--- Status registers --- ") 49 | print("Mode\t\tBat_lvl\t\tRPi_lvl\t\tTemp") 50 | print(MODE+"\t\t"+BATLEVEL+"\t\t"+RPILEVEL+"\t\t"+TMPCELS+"\n") 51 | 52 | # Module commands 53 | version = commands.getstatusoutput("i2cget -y 1 0x6B 0") 54 | error_code = commands.getstatusoutput("i2cget -y 1 0x6B 1") 55 | rpi_serror_1 = commands.getstatusoutput("i2cget -y 1 0x6B 3") 56 | rpi_serror_2 = commands.getstatusoutput("i2cget -y 1 0x6B 2") 57 | bat_serror_1 = commands.getstatusoutput("i2cget -y 1 0x6B 5") 58 | bat_serror_2 = commands.getstatusoutput("i2cget -y 1 0x6B 4") 59 | tmp_serror_1 = commands.getstatusoutput("i2cget -y 1 0x6B 7") 60 | tmp_serror_2 = commands.getstatusoutput("i2cget -y 1 0x6B 6") 61 | sta_counter = commands.getstatusoutput("i2cget -y 1 0x6B 8") 62 | fssd_batime = commands.getstatusoutput("i2cget -y 1 0x6B 9") 63 | lprsta = commands.getstatusoutput("i2cget -y 1 0x6B 10") 64 | btto = commands.getstatusoutput("i2cget -y 1 0x6B 11") 65 | led_blue = commands.getstatusoutput("i2cget -y 1 0x6B 12") 66 | led_red = commands.getstatusoutput("i2cget -y 1 0x6B 13") 67 | buzmode = commands.getstatusoutput("i2cget -y 1 0x6B 14") 68 | fanmode = commands.getstatusoutput("i2cget -y 1 0x6B 15") 69 | fanspeed = commands.getstatusoutput("i2cget -y 1 0x6B 16") 70 | xbmc = commands.getstatusoutput("i2cget -y 1 0x6B 23") 71 | fssd_tout = commands.getstatusoutput("i2cget -y 1 0x6B 24") 72 | 73 | status = ['OFF', 'ON'] 74 | buz_fan_modes = ['Disabled', 'Enabled', 'Automatic', 'Unknown'] 75 | fan_speeds = ['0', '100', '25', '50', '75'] 76 | 77 | VERSION = version[1][2:] 78 | ERROR_CODE = '{0:08b}'.format(int(error_code[1][2:])) 79 | RPI_SERROR = rpi_serror_1[1][3:]+"."+rpi_serror_2[1][2:]+" V" 80 | BAT_SERROR = bat_serror_1[1][3:]+"."+bat_serror_2[1][2:]+" V" 81 | TMP_SERROR = tmp_serror_1[1][2:]+"."+tmp_serror_2[1][2:]+" "+unichr(176)+"C" 82 | STA_COUNTER = int(sta_counter[1], 16) 83 | FSSD_BATIME = int(fssd_batime[1], 16) 84 | LPRSTA = int(lprsta[1], 16) 85 | BTTO = int(btto[1], 16) 86 | LED_BLUE = status[int(led_blue[1][3:])] 87 | LED_RED = status[int(led_red[1][3:])] 88 | BUZMODE = buz_fan_modes[int(buzmode[1][3:])] 89 | FANMODE = buz_fan_modes[int(fanmode[1][3:])] 90 | FANSPEED = fan_speeds[int(fanspeed[1][3:])] 91 | XBMC = status[int(xbmc[1][3:])] 92 | FSSD_TOUT = int(fssd_tout[1], 16) 93 | 94 | print("--- Status commands ---") 95 | print("Firmware: %s" % VERSION) 96 | print("Error code: %s" % ERROR_CODE) 97 | print("rpi_serror: %s" % RPI_SERROR) 98 | print("bat_serror: %s" % BAT_SERROR) 99 | print("tmp_serror: %s" % TMP_SERROR) 100 | print("Still Alive Timeout Counter: %d s (255=disabled)" % STA_COUNTER) 101 | print("Battery Running Time: %d s (255=disabled)" % FSSD_BATIME) 102 | print("Low Power Restart Time: %d s" % LPRSTA) 103 | print("Battery Powering Testing Timeout: %d s" % BTTO) 104 | print("led_blue: %s" % LED_BLUE) 105 | print("led_red: %s" % LED_RED) 106 | print("Integrated Buzzer Mode: %s" % BUZMODE) 107 | print("Integrated Fan Mode: %s" % FANMODE) 108 | print("Integrated Fan Speed: %s %%" % FANSPEED) 109 | print("XBMC Mode: %s" % XBMC) 110 | print("FSSD Timeout: %d s" % FSSD_TOUT) 111 | -------------------------------------------------------------------------------- /picofssd_mail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # Manage the UPS pico. 3 | # Send emails, pulse train and trigger shutdown 4 | # Import the libraries to use time delays, send os commands and access GPIO pins 5 | import RPi.GPIO as GPIO 6 | import time 7 | import os 8 | import socket 9 | import smbus 10 | import datetime 11 | import pico_status 12 | 13 | # Configurable variables 14 | checkInterval=5 # Interval between checking powering mode in seconds 15 | shutdownDelay=1 # Delay before shutdown in minutes 16 | minBatteryLevel=3.4 # Minimum battery level before shutdown triggers 17 | 18 | # Mail configuration 19 | fqdn = socket.getfqdn() 20 | sendmail = "/usr/sbin/sendmail" 21 | sender = "root@" + fqdn 22 | receivers = ["root"] 23 | 24 | #-------------------------------------------------------- 25 | # msgid=START,ONBATT,CRIT,ONLINE,SHUTDOWN 26 | def mailMessage(msgid): 27 | if(msgid=="START"): 28 | subject = fqdn + " UPS system started" 29 | text = subject 30 | elif(msgid=="ONBATT"): 31 | subject = fqdn + " UPS Power Failure !!!" 32 | text = fqdn + " UPS power failure. Now running on Battery" 33 | elif (msgid=="CRIT"): 34 | subject = fqdn + " UPS Power Critical. Shutting down !!!" 35 | text = fqdn + " UPS Power Critical. System will shutdown and restart when power is restored" 36 | elif (msgid=="ONLINE"): 37 | subject = fqdn + " UPS Power Restored." 38 | text = fqdn + " UPS Power Restored. Now running on line" 39 | elif (msgid=="SHUTDOWN"): 40 | subject = fqdn + " UPS Triggered Shutdown" 41 | text = subject 42 | elif (msgid=="BATNOCHARGE"): 43 | subject = fqdn + " UPS Battery not charging !!!" 44 | text = subject 45 | message = """\ 46 | From: %s 47 | To: %s 48 | Subject: %s 49 | 50 | %s 51 | 52 | %s 53 | """ % (sender, ", ".join(receivers), subject, text, pico_status.status()) 54 | 55 | p = os.popen("%s -t -i" % sendmail, "w") 56 | p.write(message) 57 | status = p.close() 58 | if status: 59 | print "Sendmail exit status", status 60 | # mailMessage() 61 | #-------------------------------------------------------- 62 | 63 | def checkBatteryLevel(): 64 | if (pico_status.bat_level() < minBatteryLevel): 65 | return "LOW" 66 | else: 67 | return "OK" 68 | 69 | # Initialise board 70 | GPIO.setmode(GPIO.BCM) # Set pin numbering to board numbering 71 | GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Setup pin 27 as an input 72 | GPIO.setup(22, GPIO.OUT) # Setup pin 22 as an output 73 | i2c = smbus.SMBus(1) 74 | 75 | # Initialise variables 76 | # onbatt/online flags 77 | # 0=not, 1=first detect, 2=second detect, >2=message sent 78 | onbatt=0 79 | online=2 80 | battV=0 81 | minBattV=battV 82 | maxBattV=battV 83 | lastTime = datetime.datetime.now() 84 | started=False 85 | 86 | while True: # Setup a while loop to wait for a button press 87 | # Send keepalive pulse to ups 88 | GPIO.output(22,True) 89 | time.sleep(0.25) # Allow a sleep time of 0.25 second to reduce CPU usage 90 | GPIO.output(22,False) 91 | time.sleep(0.25) # Allow a sleep time of 0.25 second to reduce CPU usage 92 | 93 | # Every checkInterval seconds check powering mode 94 | if (datetime.datetime.now() > (lastTime + datetime.timedelta(seconds=checkInterval))): 95 | if not started: 96 | started=True 97 | # Send startup message after initial checkInterval 98 | mailMessage("START") 99 | 100 | lastTime = datetime.datetime.now() 101 | pwrmode=pico_status.pwr_mode() 102 | if(pwrmode=="ONBATT"): 103 | # Running On battery 104 | onbatt+=1 105 | online=0 106 | if (onbatt == 2): 107 | # Same state for 5 seconds or more 108 | print pwrmode 109 | mailMessage("ONBATT") 110 | elif (onbatt > 3): 111 | # No need to count above 3 112 | onbatt=3 113 | if (checkBatteryLevel()=="LOW"): 114 | # Report and shutdown 115 | mailMessage("CRIT") 116 | os.system("/sbin/shutdown -h +% 'UPS Battery Low. Shutting down.'" % shutdownDelay) 117 | 118 | elif(pwrmode=="ONLINE"): 119 | # Running On line 120 | online+=1 121 | onbatt=0 122 | if (online == 2): 123 | # Same state for 5 seconds or more 124 | print pwrmode 125 | mailMessage("ONLINE") 126 | elif (online > 2): 127 | # Check that battery is charging and UPS is alive 128 | battV=pico_status.bat_level() 129 | if (battV < minBattV): 130 | minBattV=battV 131 | if (battV > maxBattV): 132 | maxBattV=battV 133 | if (online == 102): 134 | # On the 102th cycle (5mins) maxBattV should be greater than minBattV if charging 135 | if ((maxBattV <= minBattV) and (minBattV < 4.1)): 136 | mailMessage("BATNOCHARGE") 137 | # Count 3 to 102 (100 cycles) to check V over one minute 138 | if (online > 101): 139 | online = 3 140 | 141 | # Setup an if loop to run a shutdown command when button press sensed 142 | if(GPIO.input(27)==0): 143 | mailMessage("SHUTDOWN") 144 | time.sleep(5) # Allow to actually send mail 145 | os.system("/sbin/shutdown -h now") # Send shutdown command to os 146 | break 147 | -------------------------------------------------------------------------------- /picofu2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: iso8859_2 -*- 3 | #=============================================================================== 4 | # 5 | # USAGE: picofu.py -f [ -v | -h | -s | -p serial | --force ] 6 | # 7 | # DESCRIPTION: 8 | # This script uploads firmware to UPS PIco. Only mandatory input is new UPS PIco firmware. 9 | # 10 | # RETURN CODES: 11 | # 0 - Sucessfull update 12 | # 1 - Failed to parse command line arguments 13 | # 2 - Failed to establish communication with the UPS PIco 14 | # 3 - Incompatible UPS PIco powering mode (DISABLED FOR NOW) 15 | # 4 - Failed to validate firmware file 16 | # 5 - Failed during the FW upload 17 | # 18 | # OPTIONS: --- 19 | # REQUIREMENTS: 20 | # python-serial 21 | # python-smbus 22 | # Propoer HW setup and setup of Pi to enable Serial/I2C communication based on the UPS PIco manual 23 | # BUGS: --- 24 | # NOTES: Updated for the UPS PIco by www.pimodules.com 25 | # AUTHOR: Vit SAFAR 26 | # VERSION: 1.4 adopted for UPS PIco December 2014 by PiModules 27 | # CREATED: 2.6.2014 28 | # REVISION: 29 | # v1.0 16.4.2014 - Vit SAFAR 30 | # - Initial release 31 | # v1.1 17.4.2014 - Vit SAFAR 32 | # - Added code documentation 33 | # - Some speed-up optimisations 34 | # v1.2 19.4.2014 - Vit SAFAR 35 | # - Disabled the power detection, until automatic switch to bootloader mode is enabled 36 | # v1.3 2.6.2014 - Vit SAFAR 37 | # - Fixed communication issue by adding dummy ';HELLO' command 38 | # 39 | # TODO: - Detect FW version 40 | # - Automatic switch to bootloader mode using @command when available 41 | # - Automatically enable of the I2C sw components in Pi (load kernel modules) if not done 42 | # - Perform optimisation of the FW file to speed up the upload process 43 | # - Make the switch to bootloader mode interactive for users who does not have the I2C interface available. 44 | # - Show UPS PIco @status after firmware update 45 | # - Detect progress of the factory reset, not just wait :) 46 | # - Set UPS PIco RTC clock after factory reset to the system time 47 | # 48 | #=============================================================================== 49 | import sys 50 | import time 51 | import datetime 52 | import os 53 | import re 54 | import getopt 55 | 56 | class FWUpdate(object): 57 | """ 58 | Only class performing the FW update 59 | The class performs following tasks 60 | 1) Check the command line arguments and performs validation of the expected/required parameters 61 | 2) Pereform detection of the Pi powering scheme via I2C or Serial interface 62 | 3) Perform validation of the FW file 63 | 4) Verify the connectivity to UPS PIco bootloader is working 64 | 5) Perform FW update 65 | 6) Perform UPS PIco factory reset 66 | 67 | """ 68 | 69 | # running in verbose mode 70 | verbose=False 71 | # force the FW update by skipping prechecks 72 | force=False 73 | # skip validation of the FW 74 | skip=False 75 | # firmware file 76 | filename=False 77 | # default serial port 78 | port='/dev/ttyAMA0' 79 | # serial connection established on bloader level 80 | seria_bloader=False 81 | # status of the i2c serial feature 82 | i2c=False 83 | # detected i2c bus 84 | i2c_bus=False 85 | # default I2C port of UPS PIco control interface 86 | i2c_port=0x69 87 | # is Pi powered via Pi or not 88 | power=False 89 | # if power not via Pi USB and already warned about via Pi powering requirement 90 | power_warned=False 91 | 92 | 93 | def __init__(self): 94 | 95 | # check if smbus module is deployed and load it if possible 96 | try: 97 | import smbus 98 | self.i2c=True 99 | self.smbus=smbus 100 | except: 101 | print 'WARNING: I2C support is missing. Please install smbus support for python to enable additional functionality! (sudo apt-get install python-smbus)' 102 | self.i2c=False 103 | 104 | # check if pyserial module is deployed and load it if possible 105 | try: 106 | import serial 107 | self.serial=serial 108 | except: 109 | print 'ERROR: Serial support is missing. Please install pyserial support for python to enable additional functionality! (sudo apt-get install python-serial)' 110 | sys.exit(2) 111 | 112 | # parse command line arguments 113 | try: 114 | opts, args = getopt.getopt(sys.argv[1:], 'vhf:sp:', ['help', 'force' ]) 115 | except getopt.GetoptError, err: 116 | # print help information and exit: 117 | print str(err) # will print something like "option -a not recognized" 118 | self.usage() 119 | sys.exit(1) 120 | for o, a in opts: 121 | # look for verbose argument 122 | if o =="-v": 123 | self.verbose = True 124 | # look for help argument 125 | elif o in ("-h", "--help"): 126 | self.usage() 127 | sys.exit(1) 128 | # look for fw filename argument 129 | elif o == "-f": 130 | self.filename = a 131 | # Check if fw filename really exists 132 | if not os.path.isfile(self.filename): 133 | print 'ERROR: Input file "'+str(self.filename)+'" cannot be found! Make sure file exists and is readable.' 134 | sys.exit(1) 135 | # look for force argument 136 | elif o == "--force": 137 | self.force = True 138 | # look for fw validation skip argument 139 | elif o == "-s": 140 | self.skip = True 141 | # look for serial port definition argument 142 | elif o == "-p": 143 | self.port = a 144 | if not os.path.exists(self.port): 145 | print 'ERROR: Serial port "'+str(self.port)+'" cannot be found! No need to change this value in most of the cases!' 146 | sys.exit(1) 147 | # in case of unknown argument 148 | else: 149 | assert False, "ERROR: Unknown option" 150 | sys.exit(1) 151 | 152 | # Check if serial port device exists 153 | if not os.path.exists(self.port): 154 | print 'ERROR: Serial port "'+str(self.port)+'" cannot be found!' 155 | sys.exit(1) 156 | 157 | # Check if fw filename is defined 158 | if not self.filename: 159 | print 'ERROR: Firmware filename has to be defined! :)' 160 | sys.exit(1) 161 | 162 | # check the powering option is ok 163 | ####self.power_detect() 164 | 165 | # validsate the provided firmware file 166 | if not self.skip: 167 | self.validate() 168 | else: 169 | if self.verbose: print 'WARNING: Skipping firmware validation' 170 | 171 | # verify bootloader connectivity 172 | self.serial_check() 173 | 174 | # launch FW upload 175 | self.fw_upload() 176 | 177 | # Execute factory reset of UPS PIco 178 | self.factory_reset() 179 | 180 | """ 181 | 2) Detects the powering status of the Pi 182 | a) Check the power status via I2C bus 0 and 1 (most common way to do it in the future?) 183 | b) In case that no answer found (yes or no), check via serial port. 184 | - We expect to have serial port in the bootloader mode at this time, so @command on serial interface is not available and it will fail in most of the cases 185 | 186 | """ 187 | def power_detect(self): 188 | if self.verbose: print 'INFO: Detecting power setup' 189 | 190 | # check if the system is powered via Pi USB connector 191 | if self.i2c: 192 | # it's I2C we expect somthing to go wrong :) 193 | try: 194 | if self.verbose: print 'INFO: Probing I2C bus 0' 195 | # open connection to the first I2C bus (applicable mainly for the Rev.1 Pi boards) 196 | bus = self.smbus.SMBus(0) 197 | # read the powering systus byte (reffer to the manual for the meaning) 198 | pwr=bus.read_byte_data(0x6a,0) 199 | # in case we got valid response (0 is not a vlid return value of this interface, so probably not connected :) ) 200 | if pwr>0: 201 | self.i2c_bus=0 202 | # if powered via Pi, than ok 203 | if pwr==3: 204 | if self.verbose: print 'INFO: (I2C bus 1) System is powered via the Pi USB port.' 205 | self.power=True 206 | # otherwise powered using unsupported mode... 207 | # if forced to skip this check, lets do it :) 208 | elif self.force: 209 | print 'WARNING: (I2C-0) System is not powered via Pi USB port. There is a UPS PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 210 | self.power_warned=True 211 | else: 212 | print 'ERROR: (I2C-0) System has to be powered via the Pi USB port. There is a PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 213 | sys.exit(3) 214 | except SystemExit as e: 215 | sys.exit(e) 216 | except: 217 | pass 218 | if not self.power: 219 | try: 220 | if self.verbose: print 'INFO: Probing I2C bus 1' 221 | # open connection to the first I2C bus (applicable mainly for the Rev.2 Pi boards) 222 | bus = self.smbus.SMBus(1) 223 | # read the powering systus byte (reffer to the manual for the meaning) 224 | pwr=bus.read_byte_data(0x6a,0) 225 | # in case we got valid response (0 is not a vlid return value of this interface, so probably not connected :) ) 226 | if pwr>0: 227 | self.i2c_bus=1 228 | # if powered via Pi, than ok 229 | if pwr==3: 230 | if self.verbose: print 'INFO: (I2C bus 1) System is powered via the Pi USB port.' 231 | self.power=True 232 | # otherwise powered using unsupported mode... 233 | # if forced to skip this check, lets do it :) 234 | elif self.force: 235 | print 'WARNING: (I2C-1) System is not powered via Pi USB port. There is a UPS PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 236 | self.power_warned=True 237 | else: 238 | print 'ERROR: (I2C-1) System has to be powered via the Pi USB port. There is a UPS PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 239 | sys.exit(3) 240 | except SystemExit as e: 241 | sys.exit(e) 242 | except: 243 | pass 244 | 245 | # in case power status not ok and we have not detected wrong power status already, check via Serial as a failback method (even though it is expected to fail also due to the bootloader mode requirement) 246 | if not self.power and not self.power_warned: 247 | if self.verbose: print 'INFO: Probing serial port' 248 | # Set up the connection to the UPS PIco 249 | PIco= self.serial.Serial(port=self.port,baudrate=38400,timeout=0.05,rtscts=0,xonxoff=0) 250 | # empty the input buffer 251 | for line in PIco: 252 | pass 253 | # get status of power via serial from PIco 254 | PIco.write('@PM\r') 255 | # wait for the answer 256 | time.sleep(0.5) 257 | # for each line in the output buffer (there are some newlines returned) 258 | for line in PIco: 259 | # get rid of the newline characters 260 | line=line.strip() 261 | # is it the answer we are looking for? (yep, should be regexp...) 262 | if line[:16] == 'Powering Source:': 263 | # get the power source (yep, should be regexp...) 264 | ret=line[16:20] 265 | # in case it is RPI, everything is ok :) 266 | if ret == 'RPI': 267 | self.power=True 268 | if self.verbose: print 'INFO: System is powered via the Pi USB port.' 269 | # otherwise powered using unsupported mode... 270 | # if forced to skip this check, lets do it :) 271 | elif self.force: 272 | if not self.power_warned: 273 | print 'WARNING: (Serial) System is not powered via Pi USB port. There is a PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 274 | self.power_warned=True 275 | else: 276 | print 'ERROR: (Serial) System has to be powered via the Pi USB port. There is a PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 277 | sys.exit(3) 278 | # close the connection to PIco via serial 279 | PIco.close() 280 | 281 | #print 'pwr:',self.power,' pwrw:',self.power_warned,' pwr',self.power 282 | # in case no power information gathered 283 | if not self.power: 284 | if self.force: 285 | if not self.power_warned: 286 | print 'WARNING: System powering mode not detected. There is a PIco reset after a FW update, that would perform hard reset of Pi! Use --force to disable this check.' 287 | else: 288 | print 'ERROR: System powering mode not detected. System has to be powered via the Pi USB port since here is a PIco reset after a FW update, that would perform hard reset of Pi! Make a proper HW/Pi setup of Serial interface or PiCo interface(I2C) to enable auto-detection. This can happen also in case that PIco is already in the bootload mode having PIco RED led lid. Use --force to disable this check.' 289 | sys.exit(3) 290 | 291 | 292 | """ 293 | 3) Check that there is a PIco bootloader connected to the other side of the serial interface :) 294 | - Send dummy command and get the confirmation from the bootloader 295 | 296 | """ 297 | def serial_check(self): 298 | print "Checking communication with bootloader:", 299 | status=False 300 | try: 301 | # Set up the connection to the PIco 302 | PIco = self.serial.Serial(port=self.port,baudrate=38400,timeout=0.05,rtscts=0,xonxoff=True) 303 | # empty the input buffer 304 | for line in PIco: 305 | pass 306 | # send dummy command 307 | PIco.write(":020000040000FA\r") 308 | except: 309 | print "KO\nERROR: Unable to establish communication with PIco bootloader via port:",self.port,'Please verify that the serial port is availble.' 310 | sys.exit(2) 311 | try: 312 | # set the wait iterations for the bootloader response 313 | rcnt=1000 314 | # loop and wait for the response 315 | while rcnt>0: 316 | # in case there is something waiting on the serial line 317 | for resp in PIco: 318 | # get rid of the nwlines 319 | resp=resp.strip() 320 | # check if the response is the expected value or not :) 321 | if ord(resp[0])==6: 322 | print "OK" 323 | status=True 324 | rcnt=1 325 | else: 326 | print "KO\nERROR: Invalid response from PIco:",ord(resp[0])," Please retry the FW upload process." 327 | sys.exit(2) 328 | break 329 | rcnt-=1 330 | except: 331 | print "KO\nERROR: Something wrong happened during verification of communication channel with PIco bootloader via port:",self.port,'Please verify that the serial port is availble and not used by some other application.' 332 | sys.exit(2) 333 | 334 | # in case communication not verified 335 | if not status: 336 | if self.force: 337 | print "KO\nWARNING: Unable to verify communication with bootloader in PIco. Is the PIco in the bootloader mode? (Red LED lid on PIco)" 338 | else: 339 | print "KO\nERROR: Failed to establish communication with bootloader in PIco. Is the PIco in the bootloader mode? (Red LED lid on PIco)" 340 | sys.exit(2) 341 | # close the channel to PIco 342 | PIco.close() 343 | 344 | 345 | """ 346 | 4) Verify the content of the provided FW file by: 347 | a) validating crc 348 | b) validating format 349 | c) validating passed data syntax 350 | """ 351 | def validate(self): 352 | print "Validating firmware:", 353 | valid=False 354 | #count number of lines 355 | lnum=1 356 | # open the FW file 357 | f = open(self.filename) 358 | # for each file line 359 | for line in f: 360 | #static LEN ADDR1 ADDR2 TYPE DATA CKSUM 361 | #: 04 05 00 00 50EF2EF0 9A 362 | # parse the line 363 | target = re.match( r"^:([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]*)([a-fA-F0-9]{2}).$", line, re.M|re.I|re.DOTALL) 364 | # in case the data field does not have correct size 365 | if len(target.group(5))%2!=0: 366 | print "KO\nLine",lnum,': Invalid bytecode message!' 367 | sys.exit(4) 368 | # get the CRC valucalculate CRC 369 | crc1=int(line[-4:-1],16) 370 | # calculate the CRC value of the data read 371 | crc2=0 372 | for i in range(1, len(line)-5, 2): 373 | #print line[i:i+2] 374 | crc2+=int(line[i:i+2],16) 375 | # python cannot simulate byte overruns, so ugly math to be done 376 | crc2%=256 377 | crc2=255-crc2+1 378 | crc2%=256 379 | # validate the CRC :) 380 | if crc1!=crc2: 381 | print "KO\nLine",lnum,': Invalid bytecode checksum! Defined:', crc1,'Calculated:', crc2 382 | sys.exit(4) 383 | 384 | # in case that the done command is detected, than finish 385 | if target.group(4)=='01': 386 | valid=True 387 | break 388 | lnum+=1 389 | # close the FW file 390 | f.close() 391 | if not valid: 392 | print "KO\n Termination signature not found in the firmware file." 393 | sys.exit(4) 394 | else: 395 | print 'OK' 396 | 397 | 398 | 399 | 400 | """ 401 | 5) Upload the FW to PIco 402 | 403 | """ 404 | def fw_upload(self): 405 | print "Uploading firmware: 0% ", 406 | # count the number fo lines in the file for the progress bar 407 | with open(self.filename) as f: 408 | lnum=len(list(f)) 409 | 410 | # open the FW file 411 | f = open(self.filename) 412 | # Set up the connection to the PIco 413 | PIco = self.serial.Serial(port=self.port,baudrate=38400,timeout=0.001,rtscts=0,xonxoff=True) 414 | # empty the input buffer 415 | for line in PIco: 416 | pass 417 | status=False 418 | # send the data to PIco 419 | PIco.write(";HELLO\r") 420 | rcnt=100 421 | # loop and wait for the response 422 | while rcnt>0: 423 | # in case there is something waiting on the serial line 424 | for resp in PIco: 425 | # get rid of the nwlines 426 | resp=resp.strip() 427 | # check if the response is the expected value or not :) 428 | if ord(resp[0])==6: 429 | #print "Response OK:",ord(resp) 430 | status=True 431 | rcnt=1 432 | else: 433 | print "KO\nERROR: Invalid status word from PIco (",ord(resp),') when processing initial line! Please retry the FW upload process.' 434 | sys.exit(5) 435 | break 436 | rcnt-=1 437 | if not status: 438 | print "KO\nERROR: No status word from PIco revcieved when processing initial line! Please check possible warnings above and retry the FW upload process." 439 | sys.exit(5) 440 | 441 | # calculate 5% progress bar step 442 | lnumx=lnum/100*5 443 | # count the processed lines 444 | lnum2=1 445 | # for each line in the FW file 446 | for line in f: 447 | status=False 448 | # strp the \r\n and add only \r 449 | line=line.strip()+"\r" 450 | # send the data to PIco 451 | PIco.write(line) 452 | #print "Written:",line 453 | # set the wait iterations for the bootloader response 454 | rcnt=100 455 | lrcnt=0 456 | # loop and wait for the response 457 | while rcnt>0: 458 | # in case there is something waiting on the serial line 459 | for resp in PIco: 460 | # get rid of the nwlines 461 | resp=resp.strip() 462 | # check if the response is the expected value or not :) 463 | if ord(resp[0])==6: 464 | #print "Response OK:",ord(resp) 465 | #print "Waited:",rcnt 466 | status=True 467 | lrcnt=rcnt 468 | rcnt=1 469 | else: 470 | print "KO\nERROR: Invalid status word from PIco (",ord(resp),') when processing line',lnum2,' Please retry the FW upload process.' 471 | sys.exit(5) 472 | break 473 | rcnt-=1 474 | if not status: 475 | print "KO\nERROR: No status word from PIco revcieved when processing line",lnum2,' Please check possible warnings above and retry the FW upload process.' 476 | sys.exit(5) 477 | # in case that the done command is detected, than finish 478 | if line[7:9]=='01': 479 | break 480 | lnum2+=1 481 | # show the update progress and show percentages of the process ssometimes 482 | if lnum2%lnumx==0: 483 | print ' '+str(round(float(100*lnum2/lnum)))+'% ', 484 | else: 485 | if lrcnt>80: 486 | sys.stdout.write('.') 487 | elif lrcnt>60: 488 | sys.stdout.write(',') 489 | elif lrcnt>40: 490 | sys.stdout.write('i') 491 | elif lrcnt>20: 492 | sys.stdout.write('|') 493 | else: 494 | sys.stdout.write('!') 495 | 496 | sys.stdout.flush() 497 | print ' Done uploading...' 498 | # close the FW file 499 | f.close() 500 | 501 | """ 502 | 6) Perform factory reset of the PIco 503 | 504 | """ 505 | def factory_reset(self): 506 | #time.sleep(1) 507 | print "Invoking factory reset of PIco..." 508 | time.sleep(5) 509 | status=False 510 | # Set up the connection to the PIco 511 | PIco = self.serial.Serial(port=self.port,baudrate=38400,timeout=0.05,rtscts=0,xonxoff=True) 512 | # empty the input buffer 513 | for line in PIco: 514 | pass 515 | # send factory reset command 516 | PIco.write('@factory\r') 517 | time.sleep(5) 518 | # close the channel to PIco 519 | PIco.close() 520 | print 'ALL Done :) Ready to go...' 521 | 522 | 523 | def usage(self): 524 | print "\n",sys.argv[0],' -f [ -v | -h | --force | -s | -p serial | -b i2c_bus_number ]',"\n" 525 | sys.exit(1) 526 | 527 | 528 | if __name__ == "__main__": 529 | FWUpdate() 530 | 531 | 532 | -------------------------------------------------------------------------------- /picofu3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: iso8859_2 -*- 3 | #=============================================================================== 4 | # 5 | # USAGE: picofu.py -f [ -v | -h | -s | -p serial | --force ] 6 | # 7 | # DESCRIPTION: 8 | # This script uploads firmware to UPS PIco. Only mandatory input is new UPS PIco firmware. 9 | # 10 | # RETURN CODES: 11 | # 0 - Sucessfull update 12 | # 1 - Failed to parse command line arguments 13 | # 2 - Failed to establish communication with the UPS PIco 14 | # 3 - Incompatible UPS PIco powering mode (DISABLED FOR NOW) 15 | # 4 - Failed to validate firmware file 16 | # 5 - Failed during the FW upload 17 | # 18 | # OPTIONS: --- 19 | # REQUIREMENTS: 20 | # python-serial 21 | # python-smbus 22 | # Propoer HW setup and setup of Pi to enable Serial/I2C communication based on the UPS PIco manual 23 | # BUGS: --- 24 | # NOTES: Updated for the UPS PIco by www.pimodules.com 25 | # AUTHOR: Vit SAFAR 26 | # VERSION: 1.4 adopted for UPS PIco December 2014 by PiModules 27 | # CREATED: 2.6.2014 28 | # REVISION: 29 | # v1.0 16.4.2014 - Vit SAFAR 30 | # - Initial release 31 | # v1.1 17.4.2014 - Vit SAFAR 32 | # - Added code documentation 33 | # - Some speed-up optimisations 34 | # v1.2 19.4.2014 - Vit SAFAR 35 | # - Disabled the power detection, until automatic switch to bootloader mode is enabled 36 | # v1.3 2.6.2014 - Vit SAFAR 37 | # - Fixed communication issue by adding dummy ';HELLO' command 38 | # 39 | # TODO: - Detect FW version 40 | # - Automatic switch to bootloader mode using @command when available 41 | # - Automatically enable of the I2C sw components in Pi (load kernel modules) if not done 42 | # - Perform optimisation of the FW file to speed up the upload process 43 | # - Make the switch to bootloader mode interactive for users who does not have the I2C interface available. 44 | # - Show UPS PIco @status after firmware update 45 | # - Detect progress of the factory reset, not just wait :) 46 | # - Set UPS PIco RTC clock after factory reset to the system time 47 | # 48 | #=============================================================================== 49 | import sys 50 | import time 51 | import datetime 52 | import os 53 | import re 54 | import getopt 55 | 56 | import smbus 57 | import time 58 | import datetime 59 | 60 | # You can install psutil using: sudo pip install psutil 61 | #import psutil 62 | 63 | i2c = smbus.SMBus(1) 64 | 65 | 66 | 67 | 68 | class FWUpdate(object): 69 | """ 70 | Only class performing the FW update 71 | The class performs following tasks 72 | 1) Check the command line arguments and performs validation of the expected/required parameters 73 | 2) Pereform detection of the Pi powering scheme via I2C or Serial interface 74 | 3) Perform validation of the FW file 75 | 4) Verify the connectivity to UPS PIco bootloader is working 76 | 5) Perform FW update 77 | 6) Perform UPS PIco factory reset 78 | 79 | """ 80 | 81 | # running in verbose mode 82 | verbose=False 83 | # force the FW update by skipping prechecks 84 | force=False 85 | # skip validation of the FW 86 | skip=False 87 | # firmware file 88 | filename=False 89 | # default serial port 90 | port='/dev/ttyAMA0' 91 | # serial connection established on bloader level 92 | seria_bloader=False 93 | # status of the i2c serial feature 94 | i2c=False 95 | # detected i2c bus 96 | i2c_bus=False 97 | # default I2C port of UPS PIco control interface 98 | i2c_port=0x69 99 | # is Pi powered via Pi or not 100 | power=False 101 | # if power not via Pi USB and already warned about via Pi powering requirement 102 | power_warned=False 103 | 104 | 105 | def __init__(self): 106 | 107 | # check if smbus module is deployed and load it if possible 108 | try: 109 | import smbus 110 | self.i2c=True 111 | self.smbus=smbus 112 | except: 113 | print 'WARNING: I2C support is missing. Please install smbus support for python to enable additional functionality! (sudo apt-get install python-smbus)' 114 | self.i2c=False 115 | 116 | # check if pyserial module is deployed and load it if possible 117 | try: 118 | import serial 119 | self.serial=serial 120 | except: 121 | print 'ERROR: Serial support is missing. Please install pyserial support for python to enable additional functionality! (sudo apt-get install python-serial)' 122 | sys.exit(2) 123 | 124 | # parse command line arguments 125 | try: 126 | opts, args = getopt.getopt(sys.argv[1:], 'vhf:sp:', ['help', 'force' ]) 127 | except getopt.GetoptError, err: 128 | # print help information and exit: 129 | print str(err) # will print something like "option -a not recognized" 130 | self.usage() 131 | sys.exit(1) 132 | for o, a in opts: 133 | # look for verbose argument 134 | if o =="-v": 135 | self.verbose = True 136 | # look for help argument 137 | elif o in ("-h", "--help"): 138 | self.usage() 139 | sys.exit(1) 140 | # look for fw filename argument 141 | elif o == "-f": 142 | self.filename = a 143 | # Check if fw filename really exists 144 | if not os.path.isfile(self.filename): 145 | print 'ERROR: Input file "'+str(self.filename)+'" cannot be found! Make sure file exists and is readable.' 146 | sys.exit(1) 147 | # look for force argument 148 | elif o == "--force": 149 | self.force = True 150 | # look for fw validation skip argument 151 | elif o == "-s": 152 | self.skip = True 153 | # look for serial port definition argument 154 | elif o == "-p": 155 | self.port = a 156 | if not os.path.exists(self.port): 157 | print 'ERROR: Serial port "'+str(self.port)+'" cannot be found! No need to change this value in most of the cases!' 158 | sys.exit(1) 159 | # in case of unknown argument 160 | else: 161 | assert False, "ERROR: Unknown option" 162 | sys.exit(1) 163 | 164 | # Check if serial port device exists 165 | if not os.path.exists(self.port): 166 | print 'ERROR: Serial port "'+str(self.port)+'" cannot be found!' 167 | sys.exit(1) 168 | 169 | # Check if fw filename is defined 170 | if not self.filename: 171 | print 'ERROR: Firmware filename has to be defined! :)' 172 | sys.exit(1) 173 | 174 | # check the powering option is ok 175 | ####self.power_detect() 176 | 177 | # validsate the provided firmware file 178 | if not self.skip: 179 | self.validate() 180 | else: 181 | if self.verbose: print 'WARNING: Skipping firmware validation' 182 | 183 | # verify bootloader connectivity 184 | self.serial_check() 185 | 186 | # launch FW upload 187 | self.fw_upload() 188 | 189 | # Execute factory reset of UPS PIco 190 | self.factory_reset() 191 | 192 | """ 193 | 2) Detects the powering status of the Pi 194 | a) Check the power status via I2C bus 0 and 1 (most common way to do it in the future?) 195 | b) In case that no answer found (yes or no), check via serial port. 196 | - We expect to have serial port in the bootloader mode at this time, so @command on serial interface is not available and it will fail in most of the cases 197 | 198 | """ 199 | def power_detect(self): 200 | if self.verbose: print 'INFO: Detecting power setup' 201 | 202 | # check if the system is powered via Pi USB connector 203 | if self.i2c: 204 | # it's I2C we expect somthing to go wrong :) 205 | try: 206 | if self.verbose: print 'INFO: Probing I2C bus 0' 207 | # open connection to the first I2C bus (applicable mainly for the Rev.1 Pi boards) 208 | bus = self.smbus.SMBus(0) 209 | # read the powering systus byte (reffer to the manual for the meaning) 210 | pwr=bus.read_byte_data(0x6a,0) 211 | # in case we got valid response (0 is not a vlid return value of this interface, so probably not connected :) ) 212 | if pwr>0: 213 | self.i2c_bus=0 214 | # if powered via Pi, than ok 215 | if pwr==3: 216 | if self.verbose: print 'INFO: (I2C bus 1) System is powered via the Pi USB port.' 217 | self.power=True 218 | # otherwise powered using unsupported mode... 219 | # if forced to skip this check, lets do it :) 220 | elif self.force: 221 | print 'WARNING: (I2C-0) System is not powered via Pi USB port. There is a UPS PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 222 | self.power_warned=True 223 | else: 224 | print 'ERROR: (I2C-0) System has to be powered via the Pi USB port. There is a PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 225 | sys.exit(3) 226 | except SystemExit as e: 227 | sys.exit(e) 228 | except: 229 | pass 230 | if not self.power: 231 | try: 232 | if self.verbose: print 'INFO: Probing I2C bus 1' 233 | # open connection to the first I2C bus (applicable mainly for the Rev.2 Pi boards) 234 | bus = self.smbus.SMBus(1) 235 | # read the powering systus byte (reffer to the manual for the meaning) 236 | pwr=bus.read_byte_data(0x6a,0) 237 | # in case we got valid response (0 is not a vlid return value of this interface, so probably not connected :) ) 238 | if pwr>0: 239 | self.i2c_bus=1 240 | # if powered via Pi, than ok 241 | if pwr==3: 242 | if self.verbose: print 'INFO: (I2C bus 1) System is powered via the Pi USB port.' 243 | self.power=True 244 | # otherwise powered using unsupported mode... 245 | # if forced to skip this check, lets do it :) 246 | elif self.force: 247 | print 'WARNING: (I2C-1) System is not powered via Pi USB port. There is a UPS PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 248 | self.power_warned=True 249 | else: 250 | print 'ERROR: (I2C-1) System has to be powered via the Pi USB port. There is a UPS PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 251 | sys.exit(3) 252 | except SystemExit as e: 253 | sys.exit(e) 254 | except: 255 | pass 256 | 257 | # in case power status not ok and we have not detected wrong power status already, check via Serial as a failback method (even though it is expected to fail also due to the bootloader mode requirement) 258 | if not self.power and not self.power_warned: 259 | if self.verbose: print 'INFO: Probing serial port' 260 | # Set up the connection to the UPS PIco 261 | PIco= self.serial.Serial(port=self.port,baudrate=38400,timeout=0.05,rtscts=0,xonxoff=0) 262 | # empty the input buffer 263 | for line in PIco: 264 | pass 265 | # get status of power via serial from PIco 266 | PIco.write('@PM\r') 267 | # wait for the answer 268 | time.sleep(0.5) 269 | # for each line in the output buffer (there are some newlines returned) 270 | for line in PIco: 271 | # get rid of the newline characters 272 | line=line.strip() 273 | # is it the answer we are looking for? (yep, should be regexp...) 274 | if line[:16] == 'Powering Source:': 275 | # get the power source (yep, should be regexp...) 276 | ret=line[16:20] 277 | # in case it is RPI, everything is ok :) 278 | if ret == 'RPI': 279 | self.power=True 280 | if self.verbose: print 'INFO: System is powered via the Pi USB port.' 281 | # otherwise powered using unsupported mode... 282 | # if forced to skip this check, lets do it :) 283 | elif self.force: 284 | if not self.power_warned: 285 | print 'WARNING: (Serial) System is not powered via Pi USB port. There is a PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 286 | self.power_warned=True 287 | else: 288 | print 'ERROR: (Serial) System has to be powered via the Pi USB port. There is a PIco reset after a FW update, that would perform hard reset of Pi! (use --force to disable this check)' 289 | sys.exit(3) 290 | # close the connection to PIco via serial 291 | PIco.close() 292 | 293 | #print 'pwr:',self.power,' pwrw:',self.power_warned,' pwr',self.power 294 | # in case no power information gathered 295 | if not self.power: 296 | if self.force: 297 | if not self.power_warned: 298 | print 'WARNING: System powering mode not detected. There is a PIco reset after a FW update, that would perform hard reset of Pi! Use --force to disable this check.' 299 | else: 300 | print 'ERROR: System powering mode not detected. System has to be powered via the Pi USB port since here is a PIco reset after a FW update, that would perform hard reset of Pi! Make a proper HW/Pi setup of Serial interface or PiCo interface(I2C) to enable auto-detection. This can happen also in case that PIco is already in the bootload mode having PIco RED led lid. Use --force to disable this check.' 301 | sys.exit(3) 302 | 303 | 304 | """ 305 | 3) Check that there is a PIco bootloader connected to the other side of the serial interface :) 306 | - Send dummy command and get the confirmation from the bootloader 307 | 308 | """ 309 | def serial_check(self): 310 | print "Checking communication with bootloader:", 311 | status=False 312 | try: 313 | # Set up the connection to the PIco 314 | PIco = self.serial.Serial(port=self.port,baudrate=38400,timeout=0.05,rtscts=0,xonxoff=True) 315 | # empty the input buffer 316 | for line in PIco: 317 | pass 318 | # send dummy command 319 | PIco.write(":020000040000FA\r") 320 | except: 321 | print "KO\nERROR: Unable to establish communication with PIco bootloader via port:",self.port,'Please verify that the serial port is availble.' 322 | sys.exit(2) 323 | try: 324 | # set the wait iterations for the bootloader response 325 | rcnt=1000 326 | # loop and wait for the response 327 | while rcnt>0: 328 | # in case there is something waiting on the serial line 329 | for resp in PIco: 330 | # get rid of the nwlines 331 | resp=resp.strip() 332 | # check if the response is the expected value or not :) 333 | if ord(resp[0])==6: 334 | print "OK" 335 | status=True 336 | rcnt=1 337 | else: 338 | print "KO\nERROR: Invalid response from PIco:",ord(resp[0])," Please retry the FW upload process." 339 | sys.exit(2) 340 | break 341 | rcnt-=1 342 | except: 343 | print "KO\nERROR: Something wrong happened during verification of communication channel with PIco bootloader via port:",self.port,'Please verify that the serial port is availble and not used by some other application.' 344 | sys.exit(2) 345 | 346 | # in case communication not verified 347 | if not status: 348 | if self.force: 349 | print "KO\nWARNING: Unable to verify communication with bootloader in PIco. Is the PIco in the bootloader mode? (Red LED lid on PIco)" 350 | else: 351 | print "KO\nERROR: Failed to establish communication with bootloader in PIco. Is the PIco in the bootloader mode? (Red LED lid on PIco)" 352 | sys.exit(2) 353 | # close the channel to PIco 354 | PIco.close() 355 | 356 | 357 | """ 358 | 4) Verify the content of the provided FW file by: 359 | a) validating crc 360 | b) validating format 361 | c) validating passed data syntax 362 | """ 363 | def validate(self): 364 | print "Validating firmware:", 365 | valid=False 366 | #count number of lines 367 | lnum=1 368 | # open the FW file 369 | f = open(self.filename) 370 | # for each file line 371 | for line in f: 372 | #static LEN ADDR1 ADDR2 TYPE DATA CKSUM 373 | #: 04 05 00 00 50EF2EF0 9A 374 | # parse the line 375 | target = re.match( r"^:([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]*)([a-fA-F0-9]{2}).$", line, re.M|re.I|re.DOTALL) 376 | # in case the data field does not have correct size 377 | if len(target.group(5))%2!=0: 378 | print "KO\nLine",lnum,': Invalid bytecode message!' 379 | sys.exit(4) 380 | # get the CRC valucalculate CRC 381 | crc1=int(line[-4:-1],16) 382 | # calculate the CRC value of the data read 383 | crc2=0 384 | for i in range(1, len(line)-5, 2): 385 | #print line[i:i+2] 386 | crc2+=int(line[i:i+2],16) 387 | # python cannot simulate byte overruns, so ugly math to be done 388 | crc2%=256 389 | crc2=255-crc2+1 390 | crc2%=256 391 | # validate the CRC :) 392 | if crc1!=crc2: 393 | print "KO\nLine",lnum,': Invalid bytecode checksum! Defined:', crc1,'Calculated:', crc2 394 | sys.exit(4) 395 | 396 | # in case that the done command is detected, than finish 397 | if target.group(4)=='01': 398 | valid=True 399 | break 400 | lnum+=1 401 | # close the FW file 402 | f.close() 403 | if not valid: 404 | print "KO\n Termination signature not found in the firmware file." 405 | sys.exit(4) 406 | else: 407 | print 'OK' 408 | 409 | 410 | 411 | 412 | """ 413 | 5) Upload the FW to PIco 414 | 415 | """ 416 | def fw_upload(self): 417 | print "Uploading firmware: 0% ", 418 | # count the number fo lines in the file for the progress bar 419 | with open(self.filename) as f: 420 | lnum=len(list(f)) 421 | 422 | # open the FW file 423 | f = open(self.filename) 424 | # Set up the connection to the PIco 425 | PIco = self.serial.Serial(port=self.port,baudrate=38400,timeout=0.001,rtscts=0,xonxoff=True) 426 | # empty the input buffer 427 | for line in PIco: 428 | pass 429 | status=False 430 | # send the data to PIco 431 | PIco.write(";HELLO\r") 432 | rcnt=100 433 | # loop and wait for the response 434 | while rcnt>0: 435 | # in case there is something waiting on the serial line 436 | for resp in PIco: 437 | # get rid of the nwlines 438 | resp=resp.strip() 439 | # check if the response is the expected value or not :) 440 | if ord(resp[0])==6: 441 | #print "Response OK:",ord(resp) 442 | status=True 443 | rcnt=1 444 | else: 445 | print "KO\nERROR: Invalid status word from PIco (",ord(resp),') when processing initial line! Please retry the FW upload process.' 446 | sys.exit(5) 447 | break 448 | rcnt-=1 449 | if not status: 450 | print "KO\nERROR: No status word from PIco revcieved when processing initial line! Please check possible warnings above and retry the FW upload process." 451 | sys.exit(5) 452 | 453 | # calculate 5% progress bar step 454 | lnumx=lnum/100*5 455 | # count the processed lines 456 | lnum2=1 457 | # for each line in the FW file 458 | for line in f: 459 | status=False 460 | # strp the \r\n and add only \r 461 | line=line.strip()+"\r" 462 | # send the data to PIco 463 | PIco.write(line) 464 | #print "Written:",line 465 | # set the wait iterations for the bootloader response 466 | rcnt=100 467 | lrcnt=0 468 | # loop and wait for the response 469 | while rcnt>0: 470 | # in case there is something waiting on the serial line 471 | for resp in PIco: 472 | # get rid of the nwlines 473 | resp=resp.strip() 474 | # check if the response is the expected value or not :) 475 | if ord(resp[0])==6: 476 | #print "Response OK:",ord(resp) 477 | #print "Waited:",rcnt 478 | status=True 479 | lrcnt=rcnt 480 | rcnt=1 481 | else: 482 | print "KO\nERROR: Invalid status word from PIco (",ord(resp),') when processing line',lnum2,' Please retry the FW upload process.' 483 | sys.exit(5) 484 | break 485 | rcnt-=1 486 | if not status: 487 | print "KO\nERROR: No status word from PIco revcieved when processing line",lnum2,' Please check possible warnings above and retry the FW upload process.' 488 | sys.exit(5) 489 | # in case that the done command is detected, than finish 490 | if line[7:9]=='01': 491 | break 492 | lnum2+=1 493 | # show the update progress and show percentages of the process ssometimes 494 | if lnum2%lnumx==0: 495 | print ' '+str(round(float(100*lnum2/lnum)))+'% ', 496 | else: 497 | if lrcnt>80: 498 | sys.stdout.write('.') 499 | elif lrcnt>60: 500 | sys.stdout.write(',') 501 | elif lrcnt>40: 502 | sys.stdout.write('i') 503 | elif lrcnt>20: 504 | sys.stdout.write('|') 505 | else: 506 | sys.stdout.write('!') 507 | 508 | sys.stdout.flush() 509 | print ' Done uploading...' 510 | # close the FW file 511 | f.close() 512 | 513 | """ 514 | 6) Perform factory reset of the PIco 515 | 516 | """ 517 | def factory_reset(self): 518 | #time.sleep(1) 519 | print "Invoking factory reset of PIco..." 520 | time.sleep(5) 521 | status=False 522 | # Set up the connection to the PIco 523 | PIco = self.serial.Serial(port=self.port,baudrate=38400,timeout=0.05,rtscts=0,xonxoff=True) 524 | # empty the input buffer 525 | for line in PIco: 526 | pass 527 | # send factory reset command 528 | #i2c.write_byte_data(0x6b, 0x00, 0xdd) 529 | time.sleep(5) 530 | i2c.write_byte_data(0x6b, 0x00, 0xdd) 531 | #PIco.write('@factory\r') 532 | time.sleep(5) 533 | # close the channel to PIco 534 | PIco.close() 535 | print 'ALL Done :) Ready to go...' 536 | 537 | 538 | def usage(self): 539 | print "\n",sys.argv[0],' -f [ -v | -h | --force | -s | -p serial | -b i2c_bus_number ]',"\n" 540 | sys.exit(1) 541 | 542 | 543 | if __name__ == "__main__": 544 | FWUpdate() 545 | 546 | 547 | --------------------------------------------------------------------------------