|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +""" |
| 3 | +Graphical interface for UPS-2 UART serial mode. |
| 4 | +
|
| 5 | +Usage: |
| 6 | + * For autostart: put this script in /etc/xdg/lxsession/LXDE-pi/autostart |
| 7 | + * This script needs PySimpleGUI.py found on https://github.com/PySimpleGUI/PySimpleGUI |
| 8 | + * Raspberry serial interface must be enabled (with raspi-config) i.e. |
| 9 | + * ups2_serial.py worker service must be started (instructions see docstring) |
| 10 | +""" |
| 11 | +#Todo: abs |
| 12 | +# calendar timed functions |
| 13 | +# email and/or other messaging functions |
| 14 | + |
| 15 | +import PySimpleGUI as sg |
| 16 | +import os |
| 17 | +import ups2_Interface as ups |
| 18 | +import ups2_FirmwareUpd as FW_upd |
| 19 | + |
| 20 | +GUI_Version = 'UPS GUI V0.8 ' |
| 21 | + |
| 22 | +# ------ Menu Definition ------ # |
| 23 | +menu_def = [['File ', ['Quit',]], |
| 24 | + ['PowerControl ', ['Power OFF', 'Restart', 'Standby']], |
| 25 | + ['About ', ['UPS-2 GUI', 'UPS-2 Firmware', 'PySimpleGUI']], |
| 26 | + ['Firmware_Update', ['Browse...']]] |
| 27 | + |
| 28 | + |
| 29 | +print('PySimpleGUI Version', sg.version) |
| 30 | + |
| 31 | +sg.theme('lightGreen') |
| 32 | +sg.SetOptions(button_element_size=(9,1), auto_size_buttons=False, font='Helvetica 11') |
| 33 | +ser = ups.ecInitSerial() |
| 34 | +fT = open("/sys/class/thermal/thermal_zone0/temp", "r") #Pi processor temperature |
| 35 | + |
| 36 | +#globals |
| 37 | +bgOFF = 'lightgrey' |
| 38 | +bgOK = '#A2C477' |
| 39 | +bgLOW = 'lightblue' |
| 40 | +bgHIGH = 'orange' |
| 41 | + |
| 42 | +keyMain = 'K_MAIN_V' |
| 43 | +keyBatt = 'K_BATT_V' |
| 44 | +keyUSB = 'K_USB' |
| 45 | +keyCPU = 'K_CPU_T' |
| 46 | +keyPiCPU = 'K_PI_T' |
| 47 | + |
| 48 | +def ecPopWin(count, title = 'ECOM UPS2'): |
| 49 | + '''Opens a modal popup for count seconds and returns "shutdown" or "cancel" |
| 50 | +
|
| 51 | + Args: |
| 52 | + param count: countdown time in seconds |
| 53 | + param title: String to display in popup banner |
| 54 | + |
| 55 | + ''' |
| 56 | + |
| 57 | + devider = count * 10 |
| 58 | + layout_shutDown = [[sg.Text('Pi shuddown in'), |
| 59 | + sg.Text('', key='barValue', size=(3,1), pad=(0,0)), sg.Text('seconds', pad=(0,0))], |
| 60 | + [sg.ProgressBar(devider, orientation='h', size=(20, 20), key='progressbar', |
| 61 | + bar_color=['lightgreen','grey'])], |
| 62 | + [sg.Button('Cancel'), sg.Button('Shutdown NOW', size=(12,1), focus=True)]] |
| 63 | + popWin = sg.Window(title , layout_shutDown) |
| 64 | + |
| 65 | + while True: |
| 66 | + ev2, values2 = popWin.read(timeout=100) |
| 67 | + devider -=1 |
| 68 | + if devider: |
| 69 | + popWin['progressbar'].UpdateBar((devider)) |
| 70 | + popWin['barValue'].update(devider/10) |
| 71 | + # popWin.set_title('123') |
| 72 | + if (ev2 == sg.WIN_CLOSED or ev2 == 'Cancel'): |
| 73 | + print('CANCELLED') |
| 74 | + action = 'cancel' |
| 75 | + popWin.close() |
| 76 | + return action |
| 77 | + elif ev2 == 'Shutdown NOW' or devider < 2: |
| 78 | + print('SHUTTING DOWN') |
| 79 | + action = 'shutdown' |
| 80 | + popWin.close() |
| 81 | + return action |
| 82 | + |
| 83 | + |
| 84 | + |
| 85 | +def ecFormatDisplay(sysStatus, window): |
| 86 | + '''Set display attributes according to system state. |
| 87 | + |
| 88 | + This function refreshes the display depending on the system state |
| 89 | + Must be periodically called by the main control loop |
| 90 | +
|
| 91 | + Args: |
| 92 | + sysStatus: string received from UPS-2 hardware |
| 93 | + window: initialized main window |
| 94 | + ''' |
| 95 | + |
| 96 | + s = int(sysStatus, 16) #sysStatus comes as string |
| 97 | + |
| 98 | + #default values. In dictionary in order to easily update depending on status |
| 99 | + mainState = dict(key = keyMain, bgColor = bgOFF, borderW = 1, stat = 'OFF', selected = '') |
| 100 | + battState = dict(key = keyBatt, bgColor = bgOFF, borderW = 1, stat = 'OFF', selected = '') |
| 101 | + usbState = dict(key = keyUSB, text = '---', bgColor = bgOFF, borderW = 1, stat = 'OK', selected = '') |
| 102 | + # cpuState = dict(key = keyCPU, bgColor = bgOFF, borderW = 1, stat = 'OFF', selected = '') |
| 103 | + # piState = dict(key = keyPiCPU, bgColor = bgOFF, borderW = 1, stat = 'OFF', selected = '') |
| 104 | + |
| 105 | + #decode status message from UPS |
| 106 | + if s & 0x0001: #main active |
| 107 | + mainState.update(selected = '@', bgColor = bgOK, borderW = 2) |
| 108 | + elif s & 0x0002: #batt active |
| 109 | + battState.update(selected = '@', bgColor = bgOK, borderW = 2) |
| 110 | + elif s & 0x0004: #usb active |
| 111 | + usbState.update(selected = '@', text = 'active', bgColor = bgOK, borderW = 2) |
| 112 | + if s & 0x0010: #main V present |
| 113 | + mainState.update(bgColor = bgOK, stat = 'OK') |
| 114 | + if s & 0x0020: #batt V present |
| 115 | + battState.update(bgColor = bgOK, stat = 'OK') |
| 116 | + if s & 0x0040: #usb V present |
| 117 | + usbState.update(bgColor = bgOK, text = 'active') |
| 118 | + if (s & 0x0110) == 0x0110: #main V LOW |
| 119 | + mainState.update(stat = 'LOW', bgColor = bgLOW) |
| 120 | + if s & 0x0200: #main V HIGH |
| 121 | + mainState.update(stat = 'HIGH', bgColor = bgHIGH) |
| 122 | + if (s & 0x0420) == 0x0420: #batt V LOW |
| 123 | + battState.update(stat = 'LOW', bgColor = bgLOW) |
| 124 | + if s & 0x0800: #batt V HIGH |
| 125 | + battState.update(stat = 'HIGH', bgColor = bgHIGH) |
| 126 | + if s & 0x1000: #CPU temp HIGH |
| 127 | + battState.update(stat = 'HIGH', bgColor = bgHIGH) |
| 128 | + |
| 129 | + #adapt value displays |
| 130 | + window['K_MAIN_V'].update(background_color = mainState['bgColor']) |
| 131 | + window['K_MAIN_V'].Widget.configure(borderwidth = mainState['borderW']) #use underlying element |
| 132 | + window['K_MAIN_STATE'].update(mainState['stat']) |
| 133 | + window['K_BATT_V'].update(background_color = battState['bgColor']) |
| 134 | + window['K_BATT_V'].Widget.configure(borderwidth = battState['borderW']) #use underlying element |
| 135 | + window['K_BATT_STATE'].update(battState['stat']) |
| 136 | + window['K_USB'].update(background_color = usbState['bgColor']) |
| 137 | + window['K_USB'].update(usbState['text']) |
| 138 | + #adapt power button |
| 139 | + |
| 140 | + if s & 0x0004: |
| 141 | + # window['Power OFF'].set_tooltip('Disabled, if USB 5V supplied') |
| 142 | + window['Power OFF'].update(disabled=True, disabled_button_color=('lightgrey', 'none')) |
| 143 | + |
| 144 | + else: |
| 145 | + window['Power OFF'].update(disabled=False) |
| 146 | + # window['Power OFF'].SetTooltip('') |
| 147 | + |
| 148 | +# Main window column layout |
| 149 | +col1 = [[sg.Text(text='Main supply', key="K_MAIN_LBL", size=(10,0), justification='right')], |
| 150 | + [sg.Text(text='Batt supply', key='K_BATT_LBL', size=(10,1), justification='right')], |
| 151 | + [sg.Text(text='Pi USB', key='K_USB_LBL', size=(10,1),justification='right')]] |
| 152 | + |
| 153 | +col2 = [[sg.Text(text='12.2V', key="K_MAIN_V", relief=sg.RELIEF_SOLID, border_width=1, justification='right', background_color='#A2C477',size=(6,1))], |
| 154 | + [sg.Text(text='5.1V', key="K_BATT_V", relief=sg.RELIEF_SOLID, border_width=1, justification='right', background_color='#A2C477',size=(6,1))], |
| 155 | + [sg.Text(text=' ', key="K_USB", relief=sg.RELIEF_SOLID, justification='center', background_color='lightgrey', size=(6,1))]] |
| 156 | + |
| 157 | +col3 = [[sg.Text('OK', key='K_MAIN_STATE', size=(6,1))], |
| 158 | + [sg.Text('LOW', key='K_BATT_STATE', size=(6,1))], |
| 159 | + [sg.Text('')]] |
| 160 | + |
| 161 | +col4 = [[sg.Text('UPS CPU', size=(12,1), justification='right')], |
| 162 | + [sg.Text('Pi CPU', size=(12,1), justification='right')]] |
| 163 | + |
| 164 | +col5 = [[sg.Text(text=' °C', key='K_CPU_T',relief=sg.RELIEF_SOLID, justification='right', background_color='#A2C477',size=(5,1))], |
| 165 | + [sg.Text(text=' °C', key='K_PI_T',relief=sg.RELIEF_SOLID, justification='right', background_color='#A2C477',size=(5,1))]] |
| 166 | + |
| 167 | +#frame layout around readouts |
| 168 | +mainFrame = [[ sg.Column(col1), sg.Column(col2), sg.Column(col3), sg.Column(col4), sg.Column(col5)]] |
| 169 | + |
| 170 | +#buttons to appear in frame below |
| 171 | +keypanel = [[sg.Button('Power OFF', tooltip='Disabled if USB powered'), sg.Button('Restart'), sg.Button('Standby'), sg.Button('Quit')]] |
| 172 | + |
| 173 | +#frame layout around buttons |
| 174 | +layout = [[sg.Menu(menu_def)], |
| 175 | + [sg.Frame('Power Supply Overview', mainFrame)], |
| 176 | + [sg.Frame('', keypanel)]] |
| 177 | + |
| 178 | + |
| 179 | + |
| 180 | +# Display, get values |
| 181 | +#window = sg.Window('ECOM UPS GUI V0.8', layout, location=(350,350)) |
| 182 | +ups2Values = ups.ecGetUPSValues(ser) |
| 183 | +ups2Version = ups.ecGetUPSVersion(ser) |
| 184 | +print('UPS Status', ups2Values) |
| 185 | +analogStr = ups.ecFormatAnalog(ups2Values[1]) |
| 186 | + |
| 187 | +#titlebar = sg.Titlebar(title = 'hello') |
| 188 | +window = sg.Window(GUI_Version + " | " + ups2Version, layout) |
| 189 | + |
| 190 | + |
| 191 | +piTemp = '0' |
| 192 | +counter = 1 |
| 193 | +divider = 0 |
| 194 | +while 1: |
| 195 | + event, values = window.read(timeout=10) #time.sleep() may be used instead |
| 196 | + if event in (None, 'Quit'): |
| 197 | + break |
| 198 | + if event == 'Power OFF': |
| 199 | + if ecPopWin(5, 'POWER OFF') != 'cancel': |
| 200 | + ups.ecReqUPSPowerDown(ser) |
| 201 | + os.system('sudo shutdown -P now\n') |
| 202 | + elif event == 'Restart': |
| 203 | + if ecPopWin(5, 'Restart') != 'cancel': |
| 204 | + os.system('sudo shutdown -r now\n') |
| 205 | + elif event == 'Standby': |
| 206 | + if ecPopWin(5, 'Standby') != 'cancel': |
| 207 | + os.system('sudo shutdown now\n') |
| 208 | + elif event == 'About': |
| 209 | + print('About event') |
| 210 | + elif event == 'Browse...': |
| 211 | + print ('FW update') |
| 212 | + window.hide() |
| 213 | + FW_upd.GetFileDialog() |
| 214 | + window.un_hide() #todo: close, if not cancelled |
| 215 | + |
| 216 | + if divider < 100: #main processing tick = 1s |
| 217 | + divider +=1 |
| 218 | + |
| 219 | + else: |
| 220 | + divider = 0 |
| 221 | + counter += 1 |
| 222 | + #real time value updates |
| 223 | + ups2Values = ups.ecGetUPSValues(ser) |
| 224 | + sysStatus = ups2Values[0] |
| 225 | + print (sysStatus) |
| 226 | + AnalogList = ups.ecFormatAnalog(ups2Values[1]) |
| 227 | + piTemp = fT.readline() |
| 228 | + fT.seek(0) #reset to first line |
| 229 | + piTemp = piTemp[0:2] + '°C' |
| 230 | + #set display attributes |
| 231 | + ecFormatDisplay(sysStatus, window) |
| 232 | + #update values |
| 233 | + window['K_MAIN_V'].update(AnalogList[0]) |
| 234 | + window['K_BATT_V'].update(AnalogList[1]) |
| 235 | + window['K_CPU_T'].update(AnalogList[2]) |
| 236 | + window['K_PI_T'].update(piTemp) |
| 237 | + |
| 238 | +# time.sleep(.5) |
| 239 | +fT.close() |
| 240 | +ser.close() |
| 241 | +window.close() |
0 commit comments