pvblocks 0.1.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pvblocks might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pvblocks
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python package to directly control pvblocks modules
5
5
  Author-email: Erik Haverkamp <erik@rera.nl>
6
6
  Project-URL: Homepage, https://github.com/rerasolutions/pvblocks-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pvblocks"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  authors = [
5
5
  { name="Erik Haverkamp", email="erik@rera.nl" },
6
6
  ]
@@ -1,2 +1,2 @@
1
- VERSION = '0.1.1'
1
+ VERSION = '0.1.2'
2
2
  __version__ = VERSION
@@ -183,7 +183,7 @@ class IvMpp(PvBlock):
183
183
  voltage = 0.0;
184
184
  self.open()
185
185
  bytes = list(((int)(1000 * voltage)).to_bytes(4, "little"))
186
- self.uart.write(serial.to_bytes([2, constants.Rr1700Command.VoltageCommand, bytes[0], bytes[1], bytes[2], bytes[3]]))
186
+ self.uart.write(serial.to_bytes([2, constants.VOLTAGE_COMMAND, bytes[0], bytes[1], bytes[2], bytes[3]]))
187
187
  sleep(0.5)
188
188
  self.close()
189
189
 
@@ -191,7 +191,7 @@ class IvMpp(PvBlock):
191
191
  self.open()
192
192
  bytes = list(((int)(1000 * voltage)).to_bytes(4, "little"))
193
193
  self.uart.write(
194
- serial.to_bytes([2, constants.Rr1700Command.VoltageCommand, bytes[0], bytes[1], bytes[2], bytes[3]]))
194
+ serial.to_bytes([2, constants.VOLTAGE_COMMAND, bytes[0], bytes[1], bytes[2], bytes[3]]))
195
195
  sleep(0.5)
196
196
  self.close()
197
197
 
@@ -217,7 +217,6 @@ class IvMpp(PvBlock):
217
217
  status = self.read_statusbyte()
218
218
 
219
219
  self.close()
220
-
221
220
  points_measured = status.statusbytes[0]
222
221
  curve = self.transfer_curve(points_measured)
223
222
 
@@ -239,7 +238,6 @@ class IvMpp(PvBlock):
239
238
  voltages = []
240
239
  currents = []
241
240
 
242
- print(availablebytes)
243
241
  for i in range(1, int((availablebytes - 1)/8)):
244
242
  index = (i * 8) + 1
245
243
  voltages.append(int.from_bytes(bts[index:(index+4)], "little") / 10000.0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pvblocks
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python package to directly control pvblocks modules
5
5
  Author-email: Erik Haverkamp <erik@rera.nl>
6
6
  Project-URL: Homepage, https://github.com/rerasolutions/pvblocks-python
@@ -10,4 +10,5 @@ src/pvblocks.egg-info/PKG-INFO
10
10
  src/pvblocks.egg-info/SOURCES.txt
11
11
  src/pvblocks.egg-info/dependency_links.txt
12
12
  src/pvblocks.egg-info/top_level.txt
13
- tests/test.py
13
+ tests/test.py
14
+ tests/testui.py
@@ -1,10 +1,12 @@
1
1
  from pvblocks import pvblocks_system
2
2
  from time import sleep
3
+ from pvlib.ivtools.utils import rectify_iv_curve
4
+ import numpy as np
3
5
 
4
6
  print("PV-Blocks version: " + pvblocks_system.show_version())
5
7
 
6
8
  pvblocks = pvblocks_system.PvBlocks('COM16')
7
-
9
+ import matplotlib.pyplot as plt
8
10
 
9
11
  if pvblocks.init_system():
10
12
  print("init ok")
@@ -38,5 +40,16 @@ if len(pvblocks.IvMppBlocks) > 0:
38
40
  ivpoint = iv_mpp.read_ivpoint()
39
41
  print(ivpoint)
40
42
  curve = iv_mpp.measure_ivcurve(100, 20, 0)
43
+ (v, i) = rectify_iv_curve(curve['voltages'], curve['currents'])
44
+ p = v * i
45
+ voc = v[-1]
46
+ isc = i[0]
47
+ index_max = np.argmax(p)
48
+ impp = i[index_max]
49
+ vmpp = v[index_max]
50
+ pmax = p[index_max]
51
+ ff = pmax/(voc * isc)
52
+ pmax = p[index_max]
53
+
41
54
 
42
55
  pvblocks.close_system()
@@ -0,0 +1,395 @@
1
+ import datetime
2
+ import pathlib
3
+ import ttkbootstrap as ttk
4
+ from ttkbootstrap.constants import *
5
+ from ttkbootstrap import utility
6
+ from matplotlib.figure import Figure
7
+ from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
8
+ import serial.tools.list_ports
9
+ import pickle
10
+ import os
11
+ from pvblocks import pvblocks_system
12
+
13
+
14
+ class MainApp(ttk.Frame):
15
+
16
+ def __init__(self, master):
17
+ super().__init__(master, padding=15)
18
+ self.pack(fill=BOTH, expand=YES)
19
+
20
+
21
+ self.master = master
22
+
23
+ self.online = False
24
+ self.is_active = False
25
+ self.voltages = []
26
+ self.currents = []
27
+ self.pvblocks = None
28
+ self.iv_mpp = None
29
+ self.load_settings()
30
+
31
+
32
+
33
+ self.fig = Figure((6, 4), dpi=100)
34
+ # application variables
35
+ self.points_var = ttk.StringVar(value=self.settings['points'])
36
+ self.delay_var = ttk.StringVar(value=self.settings['delay_ms'])
37
+ self.mode_var = ttk.StringVar(value='voc')
38
+ self.sweepstyle_var = ttk.StringVar(value=self.settings['sweepstyle'])
39
+ self.ivpoint_var = ttk.StringVar(value='(,)')
40
+
41
+ self.isc_var = ttk.StringVar(value='0.000')
42
+ self.voc_var = ttk.StringVar(value='0.000')
43
+ self.impp_var = ttk.StringVar(value='0.000')
44
+ self.vmpp_var = ttk.StringVar(value='0.000')
45
+ self.ff_var = ttk.StringVar(value='0.000')
46
+ self.power_var = ttk.StringVar(value='0.000')
47
+
48
+
49
+ self.create_menu()
50
+ # header and labelframe option container
51
+ option_text = "IV/MPP Load control"
52
+ self.option_lf = ttk.Labelframe(self, text=option_text, padding=15)
53
+ self.option_lf.pack(fill=X, expand=YES, anchor=N)
54
+
55
+ data_text = "Measurement"
56
+ self.data_lf = ttk.Labelframe(self, text=data_text, padding=15)
57
+ self.data_lf.pack(fill=X, expand=YES, anchor=N)
58
+
59
+
60
+ self.create_mode_row()
61
+ self.create_curveinput_row()
62
+ self.create_ivpoint_row()
63
+
64
+ self.create_graph()
65
+ self.create_parameters_table('Isc:', self.isc_var)
66
+ self.create_parameters_table('Voc:', self.voc_var)
67
+ self.create_parameters_table('Impp:', self.impp_var)
68
+ self.create_parameters_table('Vmpp:', self.vmpp_var)
69
+ self.create_parameters_table('Power:', self.power_var)
70
+ self.create_parameters_table('FF:', self.ff_var)
71
+ self.create_save_button()
72
+
73
+
74
+ self.draw_empty_figure()
75
+ self.update_controls()
76
+
77
+
78
+
79
+
80
+ def create_menu(self):
81
+ self.menu = ttk.Menu(self.master)
82
+ self.master.config(menu=self.menu)
83
+ self.connect_menu = ttk.Menu(self.menu)
84
+ self.menu.add_cascade(label='File', menu=self.connect_menu)
85
+ self.connect_menu.add_command(label='Connect...', command=self.connect)
86
+ self.connect_menu.add_command(label='Disconnect...', command=self.disconnect, state='disabled')
87
+ self.connect_menu.add_separator()
88
+ self.connect_menu.add_command(label='Exit', command=self.exit_app)
89
+ self.configmenu = ttk.Menu(self.menu)
90
+ self.menu.add_cascade(label='Configure', menu=self.configmenu)
91
+ self.configmenu.add_command(label='Select serialport', command=self.open_comport_window)
92
+ self.helpmenu = ttk.Menu(self.menu)
93
+ self.menu.add_cascade(label='Help', menu=self.helpmenu)
94
+ self.helpmenu.add_command(label='About')
95
+
96
+ def create_parameters_table(self, caption, variable):
97
+ isc_row = ttk.Frame(self.data_lf)
98
+ isc_row.pack(side=TOP, expand=YES, pady=1)
99
+ lbl = ttk.Label(master=isc_row, text=caption, width=10)
100
+ lbl.pack(side=LEFT, padx=5)
101
+ isc_ent = ttk.Entry(master=isc_row, textvariable=variable)
102
+ isc_ent.pack(side=LEFT, padx=5, fill=X, expand=YES)
103
+
104
+ def create_save_button(self):
105
+ isc_row = ttk.Frame(self.data_lf)
106
+ isc_row.pack(side=TOP, fill=X, pady=1)
107
+ self.save_btn = ttk.Button(
108
+ master=isc_row,
109
+ text="Save data",
110
+ command=self.save_data
111
+ )
112
+ self.save_btn.pack(side=TOP, fill=X, padx=5)
113
+
114
+
115
+
116
+ def create_ivpoint_row(self):
117
+ """Add path row to labelframe"""
118
+ ivpoint_row = ttk.Frame(self.option_lf)
119
+ ivpoint_row.pack(fill=X, expand=YES)
120
+ ivpoint_lbl = ttk.Label(ivpoint_row, text="IV-Point", width=8)
121
+ ivpoint_lbl.pack(side=LEFT, padx=(15, 0))
122
+ ivpoint_ent = ttk.Entry(ivpoint_row, textvariable=self.ivpoint_var)
123
+ ivpoint_ent.pack(side=LEFT, fill=X, expand=YES, padx=5)
124
+ self.measure_ivpoint_btn = ttk.Button(
125
+ master=ivpoint_row,
126
+ text="Measure point",
127
+ command = self.measure_ivpoint,
128
+ width=18
129
+ )
130
+ self.measure_ivpoint_btn.pack(side=LEFT, padx=5)
131
+
132
+ def create_curveinput_row(self):
133
+ """Add term row to labelframe"""
134
+ term_row = ttk.Frame(self.option_lf)
135
+ term_row.pack(fill=X, expand=YES, pady=15)
136
+ points_lbl = ttk.Label(term_row, text="Points", width=8)
137
+ points_lbl.pack(side=LEFT, padx=(15, 0))
138
+ points_ent = ttk.Entry(term_row, textvariable=self.points_var)
139
+ points_ent.pack(side=LEFT, fill=X, expand=YES, padx=5)
140
+ delay_lbl = ttk.Label(term_row, text="Delay [ms]", width=8)
141
+ delay_lbl.pack(side=LEFT, padx=(15, 0))
142
+ delay_ent = ttk.Entry(term_row, textvariable=self.delay_var)
143
+ delay_ent.pack(side=LEFT, fill=X, expand=YES, padx=5)
144
+
145
+ sweepstyle_ent = ttk.Combobox(term_row, state="readonly", textvariable=self.sweepstyle_var,
146
+ values=("Isc to Voc", "Voc to Isc", "Isc to Voc to Isc", "Voc to Isc to Voc"))
147
+ sweepstyle_ent.pack(side=LEFT, fill=X, expand=YES)
148
+
149
+
150
+
151
+ self.measureCurveBtn = ttk.Button(
152
+ master=term_row,
153
+ text="Measure Curve",
154
+ command = self.system_measure_curve,
155
+ width=18
156
+ )
157
+ self.measureCurveBtn.pack(side=LEFT, padx=5)
158
+
159
+ def create_mode_row(self):
160
+ """Add type row to labelframe"""
161
+ type_row = ttk.Frame(self.option_lf)
162
+ type_row.pack(fill=X, expand=YES, pady=15)
163
+ type_lbl = ttk.Label(type_row, text="Mode", width=8)
164
+ type_lbl.pack(side=LEFT, padx=(15, 0))
165
+
166
+ voc_opt = ttk.Radiobutton(
167
+ master=type_row,
168
+ text="Voc mode",
169
+ variable=self.mode_var,
170
+ value="voc"
171
+ )
172
+ voc_opt.pack(side=LEFT)
173
+
174
+ isc_opt = ttk.Radiobutton(
175
+ master=type_row,
176
+ text="Isc mode",
177
+ variable=self.mode_var,
178
+ value="isc"
179
+ )
180
+ isc_opt.pack(side=LEFT, padx = 5)
181
+ voc_opt.invoke()
182
+
183
+ self.apply_btn = ttk.Button(
184
+ master=type_row,
185
+ text="Apply",
186
+ command=self.on_apply,
187
+ width=8
188
+ )
189
+ self.apply_btn.pack(side=LEFT, padx=5)
190
+
191
+ def create_graph(self):
192
+ print("Create graph")
193
+ canvas = FigureCanvasTkAgg(self.fig, master=self.data_lf)
194
+ canvas.get_tk_widget().pack(side=LEFT)
195
+
196
+ def on_apply(self):
197
+ print(self.mode_var.get())
198
+ if self.mode_var.get() == 'isc':
199
+ self.iv_mpp.ApplyIsc()
200
+ else:
201
+ self.iv_mpp.ApplyVoc()
202
+
203
+ def system_measure_curve(self):
204
+
205
+ print('measure curve')
206
+ self.save_btn.configure(state=ttk.DISABLED)
207
+ self.is_active = True
208
+ self.update_controls()
209
+ self.voltages = []
210
+ self.currents = []
211
+ self.draw_empty_figure()
212
+ self.master.update()
213
+ self.save_settings()
214
+
215
+ sweepstl = 0
216
+
217
+ if self.sweepstyle_var.get() == 'Isc to Voc':
218
+ sweepstl = 0
219
+ if self.sweepstyle_var.get() == 'Voc to Isc':
220
+ sweepstl = 1
221
+ if self.sweepstyle_var.get() == 'Isc to Voc to Isc':
222
+ sweepstl = 8
223
+ if self.sweepstyle_var.get() == 'Voc to Isc to Voc':
224
+ sweepstl = 4
225
+
226
+ curve = self.iv_mpp.measure_ivcurve(int(self.points_var.get()), int(self.delay_var.get()), sweepstl)
227
+
228
+ self.voltages = curve['voltages']
229
+ self.currents = curve['currents']
230
+ self.refresh_figure()
231
+ self.is_active = False
232
+ self.update_controls()
233
+ self.save_btn.configure(state=ttk.NORMAL)
234
+
235
+
236
+ def measure_ivpoint(self):
237
+ print('measure point')
238
+ self.is_active = True
239
+ self.update_controls()
240
+ ivpoint = self.iv_mpp.read_ivpoint()
241
+ voltage = ivpoint.voltage
242
+ current = ivpoint.current
243
+ self.ivpoint_var.set('Voltage: %f V, Current: %f A, Power: %f W' % (voltage, current, voltage * current))
244
+ self.is_active = False
245
+ self.update_controls()
246
+ pass
247
+
248
+
249
+ def system_connect(self):
250
+ if self.online:
251
+ self.disconnect()
252
+ else:
253
+ self.connect()
254
+
255
+ def exit_app(self):
256
+ print('exit app')
257
+ if self.online:
258
+ self.disconnect()
259
+ self.master.quit()
260
+
261
+ def connect(self):
262
+ self.online = False
263
+ print('connect pvblocks using: ' + self.settings['serialport'])
264
+ self.pvblocks = pvblocks_system.PvBlocks(self.settings['serialport'])
265
+ if self.pvblocks.init_system():
266
+ print("init ok")
267
+ print("scanning available blocks")
268
+
269
+ if self.pvblocks.scan_blocks():
270
+ print("scan_blocks returns ok")
271
+ self.iv_mpp = None
272
+ if len(self.pvblocks.IvMppBlocks) > 0:
273
+ self.iv_mpp = self.pvblocks.IvMppBlocks[0]
274
+ self.online = True
275
+ print("Found IV-MPP block")
276
+ else:
277
+ print("scan_blocks failed")
278
+
279
+ else:
280
+ print("failed")
281
+
282
+ self.update_controls()
283
+
284
+ def disconnect(self):
285
+ print('disconnect pvblocks')
286
+ self.pvblocks.close_system()
287
+ self.online = False
288
+ self.update_controls()
289
+
290
+ def refresh_figure(self):
291
+ self.fig.clear()
292
+ ax = self.fig.add_subplot(111)
293
+ ax.scatter(self.voltages, self.currents)
294
+ self.fig.canvas.draw_idle()
295
+
296
+ def draw_empty_figure(self):
297
+ y = []
298
+ x = []
299
+ ax = self.fig.add_subplot(111)
300
+ ax.scatter(x, y)
301
+ self.fig.canvas.draw()
302
+
303
+ def save_data(self):
304
+ pass
305
+
306
+
307
+ def open_comport_window(self):
308
+ # Create a new top-level window
309
+ comport_window = ttk.Toplevel(self.master)
310
+ comport_window.title("Select Serial Port")
311
+ comport_window.geometry("300x150")
312
+
313
+ # Make the window modal
314
+ comport_window.grab_set()
315
+
316
+ # Label instructing the user
317
+ label = ttk.Label(comport_window, text="Select a serial port:")
318
+ label.pack(pady=10)
319
+
320
+ # List of COM ports - customizable
321
+ com_ports = []
322
+ for c in serial.tools.list_ports.comports():
323
+ com_ports.append(c.device)
324
+
325
+ # Variable to hold the selected port
326
+ selected_port = ttk.StringVar()
327
+
328
+ # Combobox for selecting COM port
329
+ combo = ttk.Combobox(comport_window, values=com_ports, textvariable=selected_port)
330
+ combo.pack(pady=5)
331
+ combo.current(0) # Set default selection
332
+
333
+ # Function to handle selection
334
+ def select_port():
335
+ self.settings['serialport'] = selected_port.get()
336
+ self.save_settings()
337
+ comport_window.grab_release()
338
+ comport_window.destroy()
339
+
340
+ # Button to confirm selection
341
+ select_button = ttk.Button(comport_window, text="Select", command=select_port)
342
+ select_button.pack(pady=10)
343
+
344
+ def update_controls(self):
345
+ if self.online:
346
+ self.connect_menu.entryconfig(0, state=ttk.DISABLED)
347
+ self.connect_menu.entryconfig(1, state=ttk.NORMAL)
348
+ else:
349
+ self.connect_menu.entryconfig(0, state=ttk.NORMAL)
350
+ self.connect_menu.entryconfig(1, state=ttk.DISABLED)
351
+ self.save_btn.configure(state=ttk.DISABLED)
352
+
353
+
354
+ if self.is_active:
355
+ self.measureCurveBtn.configure(state=ttk.DISABLED)
356
+ self.measure_ivpoint_btn.configure(state=ttk.DISABLED)
357
+ self.apply_btn.configure(state=ttk.DISABLED)
358
+ else:
359
+ if self.online:
360
+ self.measureCurveBtn.configure(state=ttk.NORMAL)
361
+ self.measure_ivpoint_btn.configure(state=ttk.NORMAL)
362
+ self.apply_btn.configure(state=ttk.NORMAL)
363
+ else:
364
+ self.measureCurveBtn.configure(state=ttk.DISABLED)
365
+ self.measure_ivpoint_btn.configure(state=ttk.DISABLED)
366
+ self.apply_btn.configure(state=ttk.DISABLED)
367
+
368
+ self.master.update()
369
+
370
+ def save_settings(self):
371
+ self.settings['points'] = self.points_var.get()
372
+ self.settings['delay_ms'] = self.delay_var.get()
373
+ self.settings['sweepstyle'] = self.sweepstyle_var.get()
374
+ with open('settings.pkl', 'wb') as f:
375
+ pickle.dump(self.settings, f)
376
+
377
+ def load_settings(self):
378
+ self.settings = {'serialport': 'COM1', 'points': '100', 'delay_ms': '20', 'sweepstyle': 'Isc to Voc'}
379
+
380
+ if os.path.isfile('settings.pkl'):
381
+ with open('settings.pkl', 'rb') as f:
382
+ self.settings = pickle.load(f)
383
+ else:
384
+ with open('settings.pkl', 'wb') as f:
385
+ pickle.dump(self.settings, f)
386
+
387
+ for key in self.settings.keys():
388
+ print(self.settings[key])
389
+
390
+
391
+ if __name__ == '__main__':
392
+
393
+ app = ttk.Window("PVBlocks IV/MPP Control", "journal")
394
+ MainApp(app)
395
+ app.mainloop()
File without changes
File without changes
File without changes