I use python3.7 pyusb1.0.2 and libusb-win32-devel-filter-1.2.6.0 in windows 10.
My FPGA send data to my computer in 10MB/s through USB interface after it received 0x1111. It lasts ten seconds. I found my python program just can receive part of data, about 4MB.
Here is the code:
import usb.core
import usb.util
filename = r'E:\FlowFluorescence\1.mywork\experiment\data\201901290000.txt'
file = open(filename,'wb')
dev = usb.core.find(idVendor=0x04b4, idProduct=0x00f1)
if dev is None:
raise ValueError('Device not found')
dev.set_configuration()
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
data = b''
dev.write(0x02, b'\x11\x11')
while True:
try:
data = data + dev.read(0x86,512,100)
except usb.core.USBError:
break
file.write(data)
file.close()
How can I improve the pyusb reading speed? I need 10MB/s at least. Hoping for your answer.
I found this way is faster.
while True:
try:
#data = data + dev.read(0x86,512,100)
data = dev.read(0x86,512,100)
file.write(data)
except usb.core.USBError:
break
In this way, it read 28MB at last.
Related
I'm been trying to create a csv file from this code, but it fails every time, I have tried different ways to place it inside the code but nothing has work so far.
I'm new to python and to Stack overflow.
If somebody can explain what I'm doing wrong it will be helpful.
Thanks in advance for any help.
from time import sleep
import os
import sys
from bleson import get_provider, Observer, UUID16
import csv
GOVEE_BT_mac_OUI_PREFIX = "A4:C1:38"
H5075_UPDATE_UUID16 = UUID16(0xEC88)
govee_devices = {}
# ###########################################################################
FORMAT_PRECISION = ".2f"
# Decode H5075 Temperature into degrees Fahrenheit
def decode_temp_in_f(encoded_data):
return format((((encoded_data / 10000) * 1.8) + 32), FORMAT_PRECISION)
# Decode H5075 percent humidity
def decode_humidity(encoded_data):
return format(((encoded_data % 1000) / 10), FORMAT_PRECISION)
#focus here
with open('temp.csv','w',newline='') as record:
record = csv.writer(record)
record.writerow(['Device Name','Device Address','Temp','Humidity'])
def print_values(mac):
govee_device = govee_devices[mac]
print(govee_device['name'],govee_device['address'],govee_device['tempInF'],govee_device['humidity'],govee_device['battery'])
record.writerow(govee_device['name'])
# On BLE advertisement callback
def on_advertisement(advertisement):
if advertisement.address.address.startswith(GOVEE_BT_mac_OUI_PREFIX):
mac = advertisement.address.address
if mac not in govee_devices:
govee_devices[mac] = {}
if H5075_UPDATE_UUID16 in advertisement.uuid16s:
# HACK: Proper decoding is done in bleson > 0.10
name = advertisement.name.split("'")[0]
encoded_data = int(advertisement.mfg_data.hex()[6:12], 16)
battery = int(advertisement.mfg_data.hex()[12:14], 16)
govee_devices[mac]["address"] = mac
govee_devices[mac]["name"] = name
govee_devices[mac]["mfg_data"] = advertisement.mfg_data
govee_devices[mac]["data"] = encoded_data
govee_devices[mac]["tempInF"] = decode_temp_in_f(encoded_data)
govee_devices[mac]["humidity"] = decode_humidity(encoded_data)
govee_devices[mac]["battery"] = battery
print_values(mac)
if advertisement.rssi is not None and advertisement.rssi != 0:
govee_devices[mac]["rssi"] = advertisement.rssi
# ###########################################################################
adapter = get_provider().get_adapter()
observer = Observer(adapter)
observer.on_advertising_data = on_advertisement
try:
while True:
observer.start()
sleep(2)
observer.stop()
except KeyboardInterrupt:
try:
observer.stop()
sys.exit(0)
except SystemExit:
observer.stop()
os._exit(0)
Error that Im getting is:
File "/home/pi/GoveeWatcher-master/python/goveeWatcher.py", line 37, in print_values
record.writerow(govee_device['name'])
ValueError: I/O operation on closed file.
I would be tempted to put the CSV writing functionality inside of the print_values function so it opens the file, writes the data, and then closes the file on each value that is found by the observer.
For example:
#focus here
def print_values(mac):
govee_device = govee_devices[mac]
print(govee_device['name'], govee_device['tempInF'])
with open('temp.csv','a',newline='') as record:
writer = csv.DictWriter(record, fieldnames=govee_device.keys())
writer.writerow(govee_device)
I am trying to write some non-blocking FIFO code with kqueue on my BSD machine. Here's the small server code: server.py
import os
import selectors
sel = selectors.KqueueSelector()
TMP_PATH="/tmp/myfifo"
def fifo_read(fd, mask):
data = os.read(fd, 8)
print("fd:{} gives:{} \n", fd, data)
sel.unregister(fd)
print("unregistered")
def fifo_accept(listen_fd, mask):
print("accepted {}".format(listen_fd))
fd = os.dup(listen_fd)
print("duped to {}".format(fd))
sel.register(fd, selectors.EVENT_READ, fifo_read)
if __name__ == "__main__":
try:
os.unlink(TMP_PATH)
except:
pass
os.mkfifo(TMP_PATH)
listen_fd = os.open(TMP_PATH, os.O_RDONLY, mode=0o600)
sel.register(listen_fd, selectors.EVENT_READ, fifo_accept)
while True:
events = sel.select()
for key, mask in events:
cb = key.data
cb(key.fileobj, mask)
sel.close()
Now, when I run a client.py as:
import os
TMP_PATH="/tmp/myfifo"
fd = os.open(TMP_PATH, os.O_WRONLY, mode=0o600)
res = os.write(fd, b"1234567")
print("sent {}".format(res))
When I run the client, I get:
sent 7
But on server, it runs to inifinite loop. Now I understand why the infinite loop is happening. I actually tried mimicking the socket way of using selectors in this Python Docs example.
Here's what I have tried:
I did try the code without duplicating the fd, but it's still in infinite loop.
I tried calling sel.unregister on the original listen_fd, but in this case, running the client the second time doesn't work (which is expected).
Can anyone please let me know if I'm missing something?
So I found one solution to this problem. With sockets, we get a new socket object on accept. So we need to emulate that behaviour by calling unregister on the original fileobj, open again and call register on that.
Fixed code:
import os
import selectors
sel = selectors.KqueueSelector()
try:
os.unlink("./myfifo")
except:
pass
os.mkfifo("./myfifo", 0o600)
def cb(fp):
sel.unregister(fp)
print(f"got {fp.read()}")
fp.close()
fp2 = open("./myfifo", "rb")
sel.register(fp2, selectors.EVENT_READ, cb)
if __name__ == "__main__":
orig_fp = open("./myfifo", "rb")
print("open done")
ev = sel.register(orig_fp, selectors.EVENT_READ, cb)
print(f"registration done for {ev}")
while True:
events = sel.select()
print(events)
for key, mask in events:
key.data(key.fileobj)
I'm trying to read non-blocking from subprocess pipe on windows python3.
I made an implementation that pipes a FFMPEG video stream. It works on Linux but since FNCTL doesn't work on windows I'm looking at other ways of avoiding read/write blocking.
Since readline() isn't blocking the read stream this solution should work, however I need to recreate the read(bytes) function with readline(). I need to be able to read entire frames of size X (1920*1080*3) from the stream with readline. I'm guessing '\n' occurences needs to be handled aswell.
The code so far:
def enqueue_output(self, out, queue):
# This function should read(SIZE) from stream and add it to queue
# IT DOES NOT CURRENTLY WORK
temp = bytes()
SIZE = 1920*1080*3
while True:
for line in iter(out.readline, b''):
if len(temp) >= SIZE:
queue.put(line)
temp += line
ffmpeg = sp.Popen(ffmpegCmd, stdin=sp.PIPE,
stdout=sp.PIPE,stderr=sp.PIPE, close_fds=ON_POSIX, shell=True)
q = Queue()
t = Thread(target=self.enqueue_output, args=(ffmpeg.stdout, q))
t.daemon = True # thread dies with the program
t.start()
The Read function. Follow question, would it be better to have the byte assemble code in the read function?
img_thread = threading.Thread(target=self.reader,
args=(q,))
img_thread.start()
def reader(self, q): # TODO : catch exceptions
s_buf = ''
while True:
# read line without blocking
try: line = q.get_nowait() # or q.get(timeout=.1)
except Empty:
print('no output yet')
else: # got line
print(line)
try:
img = np.frombuffer(line, dtype=np.uint8)
img = img.reshape((1080, 1920, 3))
except Exception as e:
print(e)
pass
else:
cv2.imshow("test", img)
cv2.waitKey(1)
Some Example data of how it looks when i just send readline() :
b'\x05\x07\t\x06\x08\n'
b'\x05\x07\t\x06\x08\n'
b'\x07\t\x0b\x05\x07\t\t\x0b\r\x06\x08\n'
b'\x00\x02\x04\x01\x03\x05\x05\x07\t\x05\x07\t\x05\x07\t\x05\x07\t\x01\x03\x05\x00\x02\x04\t\x0b\r\x06\x08\n'
b'\x05\x08\x08\x05\x08\x08\x03\x06\x06\x01\x04\x04\x02\x05\x05\x06\t\t\x05\x08\x08\x05\x08\x08\x05\x08\x08\x05\x08\x08\x05\x08\x08\x05\x08\x08\x05\x08\x08\x05\x08\x08\x02\x04\x06\x05\x07\t\x05\x07\t\x02\x04\x06\x05\x07\t\x05\x07\t\x05\x07\t\x05\x07\t\x05\x07\t\x06\x08\n'
b'\x05\x07\t\x00\x00\x02\x07\t\x0b\x03\x05\x07\x00\x00\x00\x00\x00\x00\x03\x02\x04\x02\x01\x03\t\x08\n'
b'\x0c\x0b\r\x08\x07\t\x06\x05\x07\x04\x03\x05\x00\x00\x00\x05\x04\x06\x05\x04\x06\x05\x04\x06\x05\x04\x06\x06\x05\x07\x06\x05\x07\x06\x05\x07\x06\x05\x07\x06\x05\x07\x05\x04\x06\x06\x05\x07\x06\x05\x07\x05\x04\x06\x05\x04\x06\x05\x04\x06\x06\x05\x07\x05\x04\x06\x08\x07\t\t\x08\n'
b'\t\x08\n'
b'\x08\x07\t\x06\x05\x07\x05\x04\x06\x06\x05\x07\x08\x07\t\x02\x01\x03\x02\x01\x03\x08\x07\t\x06\x05\x07\x06\x05\x07\x06\x05\x07\x08\x07\t\x06\x05\x07\x06\x05\x07\x06\x05\x07\x06\x05\x07\x05\x04\x06\x05\x04\x06\x05\x04\x06\x05\x04\x06\x04\x04\x04\x05\x05\x05\x04\x04\x04\x04\x04\x04\x05\x05\x05\x05\x05\x05\x04\x04\x04\x05\x05\x05\x03\x03\x03\x03\x03\x03\x05\x05\x05\x06\x06\x06\x08\x08\x08\x06\x06\x06\x05\x05\x05\x02\x02\x02\x00\x00\x00\x04\x00\x03\x08\x04\x07\n'
b'\x06\t\x08\x04\x07\x07\x03\x06\x07\x03\x06\x07\x03\x06\x07\x03\x06\x07\x03\x06\x06\x02\x05\x05\x01\x04\x06\x02\x05\x06\x02\x05\x07\x03\x06\x08\x04\x07\x03\x03\x03\n'
b'\n'
b'\n'
b'\t\t\t\x06\x06\x06\t\t\t\x05\x05\x05\x04\x04\x04\t\t\t\x06\x06\x06\x05\x05\x05\x05\x05\x05\x06\x06\x06\x06\x06\x06\x08\x08\x08\x08\x08\x08\x08\x08\x08\x06\x06\x06\x06\x06\x06\x05\x05\x05\x04\x04\x04\x04\x04\x04\x05\x05\x05\x05\x05\x05\x05\x05\x05\x01\x01\x01\x05\x05\x05\x05\x05\x05\x02\x02\x02\x03\x03\x03\x08\x08\x08\x08\x08\x08\x04\x04\x04\x06\x03\x04\x06\x03\x04\x05\x02\x03\x05\x02\x03\x06\x03\x04\x06\x03\x04\x05\x02\x03\x05\x02\x03\x05\x02\x03\x06\x03\x04\x06\x03\x04\x05\x02\x03\x05\x02\x03\x05\x02\x03\x05\x02\x03\x04\x01\x02\x01\x01\x01\x00\x00\x00\x03\x03\x03\x05\x05\x05\x06\x06\x06\x08\x08\x08\x06\x06\x06\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x04\x04\x04\x05\x05\x05\x05\x05\x05\x05\x05\x05\x06\x06\x06\x06\x06\x06\x05\x05\x05\x05\x05\x05\x04\x04\x04\x04\x04\x04\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x06\x06\x06\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x04\x04\x04\x04\x04\x04\x03\x03\x03\x03\x03\x03\x06\x06\x06\x08\x08\x08\x02\x02\x02\x05\x05\x05\r\r\r\x11\x11\x11\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x07\x04\x05\x06\x03\x04\x06\x03\x04\x04\x01\x02\x06\x03\x04\x0b\x08\t\x08\x05\x06\x04\x01\x02\x07\x04\x05\x06\x03\x04\x06\x03\x04\x06\x03\x04\x06\x03\x04\x06\x03\x04\x06\x03\x04\x06\x03\x04\x04\x04\x04\x04\x04\x04\x00\x03\x03\x00\x03\x03\x00\x03\x03\x00\x03\x03\x03\x06\x06\x03\x06\x06\x02\x05\x05\x02\x05\x05\x01\x04\x04\x01\x04\x04\x01\x04\x04\x01\x04\x04\x04\x04\x04\x04\x04\x04\x05\x02\x03\x05\x02\x03\x05\x02\x03\x05\x02\x03\x06\x03\x04\x06\x03\x04\x06\x03\x04\x08\x05\x06\x08\x05\x06\x08\x05\x06\x04\x01\x02\x04\x01\x02\x05\x02\x03\x06\x03\x04\x07\x04\x05\x07\x04\x05\x05\x05\x05\x05\x05\x05\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x04\x04\x04\x06\x06\x06\x05\x05\x05\x04\x04\x04\x03\x03\x03\x04\x04\x04\x05\x05\x05\x05\x05\x05\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x01\x04\x04\x01\x04\x04\x01\x04\x04\x00\x03\x03\x02\x05\x05\x00\x01\x01\x00\x01\x01\x02\x05\x05\x03\x06\x06\x02\x05\x05\x00\x02\x02\x00\x03\x03\x00\x03\x03\x01\x04\x04\x02\x05\x05\x02\x05\x05\x01\x03\x05\x01\x03\x05\x00\x01\x06\x00\x01\x06\x01\x02\x07\x01\x02\x07\x01\x02\x07\x02\x03\x08\x02\x03\x08\x02\x03\x08\x00\x01\x06\x00\x01\x06\x01\x04\x04\x01\x04\x04\x01\x04\x04\x02\x05\x05\x02\x05\x05\x03\x06\x06\x02\x05\x05\x02\x05\x05\x00\x03\x03\x00\x03\x03\x00\x03\x03\x00\x03\x03\x00\x03\x03\x00\x03\x03\x00\x02\x02\x00\x02\x02\x00\x01\x01\x00\x02\x02\x02\x05\x05\x01\x04\x04\x04\x04\x04\x03\x03\x03\x04\x04\x04\x03\x03\x03\x04\x04\x04\x04\x04\x04\x02\x05\x05\x02\x05\x05\x01\x04\x04\x05\x08\x08\x07\x0c\x0c\x06\x0b\x0b\x04\t\t\x06\x0b\x0b\x05\n'
b'\n'
b'\x00\x02\x02\x03\x08\x08\x03\x08\x08\x03\x08\x08\x03\x08\x08\x03\x08\x08\x03\x08\x08\x01\x06\x06\x01\x06\x06\x03\x08\x08\x03\x08\x08\x04\t\t\x04\t\t\x03\x08\x08\x03\x08\x08\x03\x08\x08\x03\x08\x08\x04\t\t\x04\t\t\x04\t\t\x04\t\t\x04\t\t\x04\t\t\x04\t\t\x04\t\t\x03\x08\x08\x03\x08\x08\x03\x08\x08\x03\x08\x08\x04\t\t\x04\t\t\x04\t\t\x05\n'
b'\n'
b'\t\x0c\x0c\t\x0c\x0c\x03\x06\x06\x02\x05\x05\x03\x06\x06\x06\t\t\t\x0c\x0c\x08\x0b\x0b\x08\x0b\x0b\x06\t\t\x05\x08\x08\x02\x05\x05\x00\x03\x03\x03\x06\x06\x06\t\t\x03\x06\x06\x06\t\t\x01\x04\x04\x01\x04\x04\x05\x08\x08\x06\t\t\x02\x05\x05\x05\x08\x08\n'
b'\r\r\x02\x05\x05\x06\t\t\x08\x0b\x0b\x06\t\t\x05\x08\x08\x07\n'
Any help would be appreciated!
If anyone has a better advice on how to avoid the read/write block on subprocess for windows that would be great too.
The code below used to work in spyder to communicate over serial with an arduino. In the console window of spyder, I would see lines of data being printed out:
78.7,77.9,100,80
78.7,77.9,100,80
78.7,77.9,100,80
78.7,77.9,100,80 ...
Data is from two temperature probes, a flowmeter, and the thermostat set temp.
I upgraded my Kubuntu 18.04 system to all things python3. Now, the code runs, but the spyder3 console window shows no visible characters, but scrolls blank lines. The rest of my python code for parsing and plotting this data doesn't work.
I've spent all day trying to fix this with no luck. I'm guessing it's a simple fix for someone with more experience than me.
The only difference between the old working code and the code below is that the print statements have parentheses added to remove the syntax error.
python
""" This code was originally copied from:
Listen to serial, return most recent numeric values
Lots of help from here:
http://stackoverflow.com/questions/1093598/pyserial-how-to-read-last-line-sent-from-serial-device
"""
from threading import Thread
import time
import serial
last_received = ''
def receiving(ser):
global last_received
buffer = ''
while True:
buffer = buffer + ser.read(ser.inWaiting())
if '\n' in buffer:
lines = buffer.split('\n') # Guaranteed to have at least 2 entries
last_received = lines[-2]
#If the Arduino sends lots of empty lines, you'll lose the
#last filled line, so you could make the above statement conditional
#like so: if lines[-2]: last_received = lines[-2]
buffer = lines[-1]
class SerialData(object):
def __init__(self, init=50):
try:
self.ser = serial.Serial(
port='/dev/ttyACM0',
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.1,
xonxoff=0,
rtscts=0,
interCharTimeout=None
)
except serial.serialutil.SerialException:
#no serial connection
self.ser = None
else:
Thread(target=receiving, args=(self.ser,)).start()
def next(self):
if not self.ser:
return '81.3,78.1,10.0,60.0,0' #100 #return anything so we can test when Arduino isn't connected
#return a float value or try a few times until we get one
for i in range(40):
raw_line = last_received
try:
# return float(raw_line.strip())
return str(raw_line.strip())
except ValueError:
print('bogus data',raw_line)
time.sleep(.005)
return 0.
def __del__(self):
if self.ser:
self.ser.close()
def write(self,val):
self.ser.write(val)
if __name__=='__main__':
s = SerialData()
for i in range(500):
time.sleep(.015)
print( s.next())
One of the most significant differences between Python 2.x and 3.x is the way text strings are encoded. For Python 3.x everything is Unicode, as compared to ASCII for 2.x, so you just need to decode the raw bytes you read from the serial port:
buffer = buffer + ser.read(ser.inWaiting()).decode('utf-8')
EDIT: now you seem to have a different problem involving an exception. It looks like your port is open, to be sure you can change the way you handle the exception when you instantiate the port:
except serial.serialutil.SerialException as e:
print(e)
self.ser = None
Once you know the error you should be able to handle it. Quite likely your port was not properly closed in an earlier session.
Bingo! That last suggestion fixed the program and the python GUI interface I wrote for it is working with these corrections.
python
from threading import Thread
import time
import serial
last_received = ''
def receiving(ser):
global last_received
buffer = ''
while True:
buffer = buffer + ser.read(ser.inWaiting()).decode('utf-8')
if '\n' in buffer:
lines = buffer.split('\n') # Guaranteed to have at least 2 entries
last_received = lines[-2]
#If the Arduino sends lots of empty lines, you'll lose the
#last filled line, so you could make the above statement conditional
#like so: if lines[-2]: last_received = lines[-2]
buffer = lines[-1]
class SerialData(object):
def __init__(self, init=50):
try:
self.ser = serial.Serial(
port='/dev/ttyACM1',
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.1,
xonxoff=0,
rtscts=0,
interCharTimeout=None
)
except serial.serialutil.SerialException as e:
print(e)
#no serial connection
self.ser = None
else:
Thread(target=receiving, args=(self.ser,)).start()
def next(self):
# if not self.ser:
# return '81.3,78.1,10.0,60.0,0' #100 #return anything so we can test when Arduino isn't connected
#return a float value or try a few times until we get one
for i in range(40):
raw_line = last_received
try:
# return float(raw_line.strip())
return str(raw_line.strip())
except ValueError:
print('bogus data',raw_line)
time.sleep(.005)
return 0.
def __del__(self):
if self.ser:
self.ser.close()
def write(self,val):
self.ser.write(val)
if __name__=='__main__':
s = SerialData()
for i in range(500):
time.sleep(.015)
print (s.next())
I am using the IDLE editor and Python 3.7, and I would like to know why my code is not playing multiple audio files (sequentially) and sometimes not playing audio at all:
import re
import wave
import pyaudio
import _thread
import time
class TextToSpeech:
CHUNK = 1024
def __init__(self, words_pron_dict:str = 'cmudict-0.7b.txt'):
self._l = {}
self._load_words(words_pron_dict)
def _load_words(self, words_pron_dict:str):
with open(words_pron_dict, 'r') as file:
for line in file:
if not line.startswith(';;;'):
key, val = line.split(' ',2)
self._l[key] = re.findall(r"[A-Z]+",val)
def get_pronunciation(self, str_input):
list_pron = []
for word in re.findall(r"[\w']+",str_input.upper()):
if word in self._l:
list_pron += self._l[word]
print(list_pron)
delay=0
for pron in list_pron:
_thread.start_new_thread( TextToSpeech._play_audio, (pron,delay,))
delay += 0.145
def _play_audio(sound, delay):
try:
time.sleep(delay)
wf = wave.open("sounds/"+sound+".wav", 'rb')
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(TextToSpeech.CHUNK)
while data:
stream.write(data)
data = wf.readframes(TextToSpeech.CHUNK)
stream.stop_stream()
stream.close()
p.terminate()
return
except:
pass
if __name__ == '__main__':
tts = TextToSpeech()
while True:
tts.get_pronunciation(input('Enter a word or phrase: '))
I have a list of audio files that will play in a certain order, depending on what word I type in, when running the code. The code has no errors, but when I run it, when I type in a word, it only plays the first audio file needed (Example: When I type in "buy" it requires these two sounds: "b" and "ie" played together), but it only plays the first sound, "b", and sometimes no sound at all.
Why isn't it playing multiple audio files? I know that lots of people have been having this issue, but haven't been able to solve it.
Thank you for your help in advance, it is greatly appreciated :)