-
Notifications
You must be signed in to change notification settings - Fork 21
/
pip.py
executable file
·246 lines (214 loc) · 8.42 KB
/
pip.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# *****BatteryMonitor main file batteries.py*****
# Copyright (C) 2014 Simon Richard Matthews
# Project loaction https://github.com/simat/BatteryMonitor
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#!/usr/bin/python
import sys
import time
import serial
import binascii
import glob
from threading import Thread
from config import config
numcells = config['battery']['numcells']
import logger
log = logger.logging.getLogger(__name__)
log.setLevel(logger.logging.DEBUG)
log.addHandler(logger.errfile)
initrawdat ={'DataValid':False,'BInI':0.0,'BOutI':0.0,'BV':0.0,'PVI':0.0,'PVW':0,\
'ACW':0.0,'ibat':0.0,'ipv':0.0,'iload':0.0,'ChgStat':b'00'}
class Rawdat():
"""class for obtaining data from and controlling indidvdual PIP inverters.
When class is instantiated the SN of the PIP is used to tie the instance of
the class to the particular machine"""
def __init__(self,sn,InterfacesInUse):
self.rawdat = dict.copy(initrawdat)
self.reply ='' # placeholder for reply from sendcmd
self.pipdown=0.0
self.sn=sn
self.timeslaveon=0 # time slave inverter was turned on
self.numinvon=1 # number of inverters currently on
try:
self.findpip(InterfacesInUse)
except IOError as err:
self.pipdown=time.time() # flag pip is down
log.error(err)
self.stashok=False
self.floatv=48.0
self.bulkv=48.0
self.rechargev=48.0
self.lowv=44.0
self.stashok=False
self.command=b'' # last command sent to PIP
self.acloadav=0.0
self.timeoverload=0.0 #time overload started
self.time=0.0
def findpip(self,InterfacesInUse):
"""Scan ports to find PIP port"""
self.pipport=""
for dev in glob.glob(config['Ports']['pipport']):
if dev not in InterfacesInUse:
# print(dev)
for i in range(2):
try:
self.openpip(dev)
self.sendcmd("QID")
if self.reply[1:15].decode('ascii','strict')==str(self.sn):
self.pipport=dev
break
except IOError:
pass
finally:
self.port.close
if self.pipport!="":
break
if self.pipport=="":
raise IOError("Couldn't find PIP sn {}".format(self.sn))
def openpip(self,port):
self.port = serial.Serial(port,baudrate=2400,timeout=1) # open serial port
def crccalc(self,command):
"""returns crc as integer from binary string command"""
crc=binascii.crc_hqx(command,0)
crchi=crc>>8
crclo=crc&255
if crchi == 0x28 or crchi==0x0d or crchi==0x0a:
crc+=256
if crclo == 0x28 or crclo==0x0d or crclo==0x0a:
crc+=1
return crc
def sendcmd(self,command):
"""send command/query to Pip4048, return reply"""
self.command=command.encode('ascii','strict')
crc=self.crccalc(self.command)
self.command=self.command+crc.to_bytes(2, byteorder='big')+b'\r'
self.port.reset_input_buffer()
self.port.write(self.command)
self.reply=self.port.readline()
# print('command {} reply {}'.format(self.command,self.reply))
if self.crccalc(self.reply[0:-3]) != int.from_bytes(self.reply[-3:-1],byteorder='big'):
raise IOError('CRC error in reply')
def setparam(self,command):
# time.sleep(5.0)
self.sendcmd(command)
if self.reply[1:4]!=b'ACK':
log.error('Bad Reply {} to command {}'.format(self.reply,command))
raise IOError('Bad Parameters')
def opensetparam(self,command):
"""open port, send set parameter command to pip, close port"""
self.openpip(self.pipport)
self.setparam(command)
self.port.close
def setparamnoerror(self,command): # set parameter, ignore errors
try:
self.opensetparam(command)
except IOError:
pass
def backgroundswapinv(self):
"""Turns slave pip inverter on, waits for powerup and them turns
master pip inverter off"""
try:
self.opensetparam('MNCHGC1498')
except IOError:
pass
else:
time.sleep(20) # wait for second inverter to power up and syncronise
try:
self.opensetparam('MNCHGC0497')
except IOError:
pass
def swapinverter(self):
"""turns on inverter controlled by Pi pin number if on arg and turns off
inverter controlled by Pi pin number in off arg"""
if self.timeoverload==0: # only swap inverters if no overload
Thread(target=self.backgroundswapinv).start()
def slaveinvon(self):
"""turns on slave inverter, set overload timer, timer set to zero when
power draw less than 60% of inverter load for half an hour"""
self.timeoverload=time.time()
try:
self.opensetparam('MNCHGC1498') # turn on slave
except IOError:
pass
else:
self.numinvon=2
def slaveinvoff(self):
"""turn slave inverter off"""
try:
self.opensetparam('MNCHGC1497') # turn off slave
except IOError:
pass
else:
self.numinvon=1
def getdata(self):
"""returns dictionary with data from Pip4048"""
# log.debug('open')
self.rawdat = dict.copy(initrawdat)
if self.pipdown==0.0:
for i in range(5):
try:
self.openpip(self.pipport)
self.sendcmd('QPIGS')
self.rawdat['BInI']=float(self.reply[47:50].decode('ascii','strict'))
self.rawdat['BOutI']=float(self.reply[77:82].decode('ascii','strict'))
self.rawdat['PVI']=float(self.reply[60:64].decode('ascii','strict'))
self.rawdat['BV']=float(self.reply[41:46].decode('ascii','strict'))
self.rawdat['ACW']=float(self.reply[28:32].decode('ascii','strict')) \
*config['MPPSolar']['acwcal']
self.sendcmd('Q1')
if len(self.reply) == 91:
self.rawdat['ChgStat']=self.reply[69:71]
self.rawdat['PVW']=float(self.reply[53:57].decode('ascii','strict'))
elif len(self.reply) == 51:
self.rawdat['ChgStat']=self.reply[46:48]
self.rawdat['PVW']=float(self.reply[41:45].decode('ascii','strict'))
self.rawdat['PVW']=self.rawdat['PVW']*config['MPPSolar']['pvwcal']
self.rawdat['ibat']=self.rawdat['BOutI']-self.rawdat['BInI']
self.rawdat['ipv']=self.rawdat['PVW']/self.rawdat['BV']
self.rawdat['iload']=self.rawdat['ACW']/self.rawdat['BV']
self.rawdat['DataValid']=True
break
except ValueError as err:
log.error('PIP bad response{} to command {}'.format(self.reply,self.command))
time.sleep(0.5)
if i==4:
self.pipdown=time.time() # flag pip is down
log.error("PIP sn {} interface down".format(self.sn))
except IOError as err:
log.error('PIP interface error {}'.format(err))
time.sleep(0.5)
if i==4:
self.pipdown=time.time() # flag pip is down
log.error("PIP sn {} interface down".format(self.sn))
finally:
self.port.close()
else:
missedsamples=(time.time()-self.pipdown)//config['sampling']['sampletime']
if missedsamples%(600/config['sampling']['sampletime'])==0: #retry interface every 10 minutes
try:
self.findpip([])
except IOError:
pass
# if downtime>3600: # upgrade error if more than one hour
# raise
else:
self.pipdown=0.0
log.info("PIP sn {} interface back up".format(self.sn))
self.acloadav = (self.acloadav*2 + self.rawdat['ACW'])/3 # running average
print ('acloadav {} ACW {}'.format(self.acloadav,self.rawdat['ACW']))
if self.timeoverload !=0.0:
self.time=time.time()
if self.acloadav*config['Inverters']['numinverters']>config['Inverters']['turnonslave'] \
or self.rawdat['ACW']*config['Inverters']['numinverters']>config['Inverters']['turnonslave']*1.3:
self.timeoverload=self.time
if self.time-self.timeoverload > config['Inverters']['minruntime']:
self.timeoverload =0.0