-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple Packets for One Array #34
Comments
It'll take me a while to figure out what is going on Python side. I'm guessing you didn't post your whole program and just the pieces that pertain to the USB logic, which is totally fine but it does make it more difficult to understand. Also, I would suggest using more whitespace, especially between the top of a some_code = "hi"
# block
# comment
more_code = "hello" For more guidelines on Python styling, check out PEP-8 Lastly, I would suggest finding a way to migrate the error handling up out of the function to the parent. Less redundant code and smaller functions is always better. I'll try to see about suggestions for preventing the actual transfer errors soon. |
Ah okay, I was hoping to just offer what was relevant so it was easier, sorry about that. I included the
Okay, got it. I definitely haven't been careful about complying with PEP-8 as you can see haha. I'll try and update my code to comply with it so it's easier to read.
Okay! I'll think more about this. I'm not so great at error handling yet.
Thank you so much! I'm sorry my code is confusing... If you want to wait until I make it more readable I can try my best to do that! Edit: I just installed Flake8 as a linter and it's very upset with me. |
Okay, so this morning I've been making individual functions for different numbers of packet transfers. Here's what I have that (I'm hoping!) is a lot nicer. The Arduino code is unchanged from before. Edit: I'm also working on making a couple functions that will send arbitrary numbers of single packet arrays/multi-packet arrays. But, for now, having a function for each array I want to send is the method I'm taking. ###############################################################################
# Import Packages
###############################################################################
# File Types
# Import JSON for configuration file
import json
# Trial Array Generation
# Import scipy.stats truncated normal distribution for ITI Array
from scipy.stats import truncnorm
# Import numpy for trial array generation/manipulation and Harvesters
import numpy as np
# Import numpy default_rng
from numpy.random import default_rng
# Serial Transfer
# Import pySerialTransfer for serial comms with Arduino
from pySerialTransfer import pySerialTransfer as txfer
# -----------------------------------------------------------------------------
# Multi-packet Generation: Trial Array
# -----------------------------------------------------------------------------
def gen_trialArray_multipacket(totalNumberOfTrials):
# Always initialize trial array with 3 reward trials
trialArray = [1, 1, 1]
# Define number of samples needed from generator
num_samples = totalNumberOfTrials - len(trialArray)
# Define probability that the animal will receive sucrose 50% of the time
sucrose_prob = 0.5
# Initialize random number generator with default_rng
rng = default_rng(2)
# Generate a random trial array with Generator.binomial. Use n=1 to pull
# one sample at a time and p=0.5 as probability of sucrose. Use
# num_samples to generate the correct number of trials. Finally, use
# tolist() to convert random_trials from an np.array to a list.
random_trials = rng.binomial(n=1, p=sucrose_prob, size=num_samples).tolist()
# Append the two arrays together
for i in random_trials:
trialArray.append(i)
# Use np.array_split() to divide list into smaller sizes
split_array = np.array_split(trialArray, 2)
# First index of split array is content of first packet
first_trialArray = split_array[0].tolist()
# Second index of split array is content of second packet
second_trialArray = split_array[1].tolist()
# TODO: Write out the trial array into JSON as part of experiment config
# Return trial arrays
return first_trialArray, second_trialArray
# -----------------------------------------------------------------------------
# Trial Array Transfer: Multi-packet
# -----------------------------------------------------------------------------
def serialtransfer_trialArray_multipacket(first_trialArray, second_trialArray):
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# Initialize first packet size of 0
first_trialArray_size = 0
# Stuff the packet with the first trial array
first_trialArray_size = link.tx_obj(first_trialArray)
# Open the communication link
link.open()
# Send the first packet with a packet_id of 0
link.send(first_trialArray_size, packet_id=0)
print("First Half of Trials Sent")
print(first_trialArray)
while not link.available():
pass
# Receive the first half of trials from Arduino
rxfirst_trialArray = link.rx_obj(obj_type=type(first_trialArray),
obj_byte_size=first_trialArray_size,
list_format='i')
print("First Half of Trials Received")
print(rxfirst_trialArray)
# TODO Move error checking outside this function
# # Confirm packet was sent correctly
# if first_trialArray == rxfirst_trialArray:
# print("First Half Trial Array Transfer Successful!")
# else:
# link.close()
# print("First Half Trial Array Transfer Failure!")
# print("Exiting...")
# sys.exit()
# Initialize second packet size of 0
second_trialArray_size = 0
# Stuff the packet with second trial array
second_trialArray_size = link.tx_obj(second_trialArray)
# Send the second packet with a packet_id of 1
link.send(second_trialArray_size, packet_id=1)
print("Second Half of Trials Sent")
print(second_trialArray)
# Receive second half of trials from Arduino
rxsecond_trialArray = link.rx_obj(obj_type=type(second_trialArray),
obj_byte_size=second_trialArray_size,
list_format='i')
print("Second Half of Trials Received")
print(rxsecond_trialArray)
# TODO Put error checking outside this function
# if second_trialArray == rxsecond_trialArray:
# print("Second Half Trial Array Transfer Successful!")
# else:
# link.close()
# print("Second Half Trial Array Transfer Failure!")
# print("Exiting...")
# sys.exit()
#
# link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass
###############################################################################
# Main Function
###############################################################################
if __name__ == "__main__":
# Give config file name
config_file = 'config.json'
# read JSON config file
with open(config_file, 'r') as inFile:
contents = inFile.read()
# Convert from JSON to Dictionary
config = json.loads(contents)
# Gather total number of trials
num_trials = config["metadata"]["totalNumberOfTrials"]["value"]
# If there's more than 45 trials, generate multipacket arrays
first_trialArray, second_trialArray = gen_trialArray_multipacket(num_trials)
# Use multipacket serial transfer for trial arrays
serialtransfer_trialArray_multipacket(first_trialArray, second_trialArray)
sys.exit() Edit again: Just realized having the config file here would probably be helpful! Sorry for not including it before. {"metadata": {
"totalNumberOfTrials": {"value": 46},
"punishTone": {"value": 2000},
"rewardTone": {"value": 18000},
"USDeliveryTime_Sucrose": {"value": 5},
"USDeliveryTime_Air": {"value": 20},
"USConsumptionTime_Sucrose": {"value": 1000}
}
} |
Thanks - the Python looks much better! Here is what's most likely causing the issue: # Send the second packet with a packet_id of 1
link.send(second_trialArray_size, packet_id=1)
print("Second Half of Trials Sent")
print(second_trialArray)
# while not link.available(): <------------------------ Missing in original script
# pass
# Receive second half of trials from Arduino
rxsecond_trialArray = link.rx_obj(obj_type=type(second_trialArray),
obj_byte_size=second_trialArray_size,
list_format='i') |
Hooray! I'm glad the Python looks better haha. I'll try my best to keep in line with PEP8 from now on. Thanks for the advice! This definitely seems to have been the source of the issue! Now I can send multipacket arrays no problem. You're awesome PowerBroker2. You should change your name to PowerBroker1 because you're number 1. One last thing for the error checking part, would it be acceptable to have a different function evaluate whether packets are transmitted correctly where the function returns |
Thanks man, always glad to help!
I think if you localize the transmissions to a single function or so, integrating error handling like you already have is fine. I do think it would be beneficial to have a function that |
Edit2: Hey PowerBroker! So I've gotten to the point where I've made the code a bit more modular and, I think, easier to understand. I've made a function that sends an individual packet and, depending on the size of the arrays I need to send, it either sends just one or multiple packets! I've turned this code into a module for my program so my # Bruker 2-Photon Serial Transfer Utils
# Jeremy Delahanty May 2021
# pySerialTransfer written by PowerBroker2
# https://github.com/PowerBroker2/pySerialTransfer
###############################################################################
# Import Packages
###############################################################################
# Serial Transfer
# Import pySerialTransfer for serial comms with Arduino
from pySerialTransfer import pySerialTransfer as txfer
# Import Numpy for splitting arrays
import numpy as np
###############################################################################
# Functions
###############################################################################
###############################################################################
# Serial Transfer to Arduino: One Packet
###############################################################################
# -----------------------------------------------------------------------------
# Send an Individual Packet
# -----------------------------------------------------------------------------
def transfer_packet(array, packet_id):
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# Initialize array_size of 0
array_size = 0
# Stuff packet with size of trialArray
array_size = link.tx_obj(array)
# Open communication link
link.open()
# Send array
link.send(array_size, packet_id=0)
print("Sent Array")
print(array)
while not link.available():
pass
# Receive trial array:
rxarray = link.rx_obj(obj_type=type(array),
obj_byte_size=array_size,
list_format='i')
print("Received Array")
print(rxarray)
# Close the communication link
link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass
# -----------------------------------------------------------------------------
# Configuration/Metadata File Transfer
# -----------------------------------------------------------------------------
# TODO: Add error checking function for configuration
def transfer_metadata(config):
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# stuff TX buffer (https://docs.python.org/3/library/struct.html#format-characters)
metaData_size = 0
metaData_size = link.tx_obj(config['metadata']['totalNumberOfTrials']['value'], metaData_size, val_type_override='B')
metaData_size = link.tx_obj(config['metadata']['punishTone']['value'], metaData_size, val_type_override='H')
metaData_size = link.tx_obj(config['metadata']['rewardTone']['value'], metaData_size, val_type_override='H')
metaData_size = link.tx_obj(config['metadata']['USDeliveryTime_Sucrose']['value'], metaData_size, val_type_override='B')
metaData_size = link.tx_obj(config['metadata']['USDeliveryTime_Air']['value'], metaData_size, val_type_override='B')
metaData_size = link.tx_obj(config['metadata']['USConsumptionTime_Sucrose']['value'], metaData_size, val_type_override='H')
# Open comms to Arudino
link.open()
# Send the metadata to the Arduino
link.send(metaData_size, packet_id=0)
# While sending the data, the link is unavailable. Pass this state
# until done.
while not link.available():
pass
# Receive packet from Arduino
# Create rxmetaData dictionary
rxmetaData = {}
# Start rxmetaData reception size of 0
rxmetaData_size = 0
# Receive each field from the Arduino
rxmetaData['totalNumberOfTrials'] = link.rx_obj(obj_type='B', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['B']
rxmetaData['punishTone'] = link.rx_obj(obj_type='H', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['H']
rxmetaData['rewardTone'] = link.rx_obj(obj_type='H', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['H']
rxmetaData['USDeliveryTime_Sucrose'] = link.rx_obj(obj_type='B', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['B']
rxmetaData['USDeliveryTime_Air'] = link.rx_obj(obj_type='B', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['B']
rxmetaData['USConsumptionTime_Sucrose'] = link.rx_obj(obj_type='H', start_pos=rxmetaData_size)
print(rxmetaData)
# Close comms to the Arduino
link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass
# TODO: Move error handling outside of function; need to learn how...
# if trialArray == rxtrialArray:
# print("Trial Array transfer successful!")
#
# else:
# link.close()
# print("Trial Array error! Exiting...")
# sys.exit()
# Close the communication link
# -----------------------------------------------------------------------------
# Trial Array Transfers: One Packet
# -----------------------------------------------------------------------------
def onepacket_transfers(array_list):
# Give each new packet an ID of 0. The link is closed per packet
# transmission.
packet_id = 0
# For each array in the list of arrays defining trial data
for array in array_list:
# Transfer the packet
transfer_packet(array, packet_id)
###############################################################################
# Serial Transfer to Arduino: Multi-packet
###############################################################################
# -----------------------------------------------------------------------------
# Trial Array Splitting for Multipacket Transfers
# -----------------------------------------------------------------------------
def split_multipacket_array(array):
# This function receives a large list of trial variables. It needs to be
# split into two arrays using np.array_split.
split_ndarray = np.array_split(array, 2)
# Return the split numpy arrays
return split_ndarray
# -----------------------------------------------------------------------------
# Trial Array Transfers: Multi-packet
# -----------------------------------------------------------------------------
def multipacket_transfer(array_list):
# For each array in the list of trial arrays
for array in array_list:
# Split the array into packets small enough to transfer
split_array = split_multipacket_array(array)
# Transfer the arrays as multiple packets
transfer_arrays_multipacket(split_array)
def transfer_arrays_multipacket(split_array):
# Initialize first packet with an ID of 0
packet_id = 0
# For each array received by the splitting function
for array in split_array:
# Save the array as a list for transfer
array = array.tolist()
# Send the array
transfer_packet(array, packet_id)
# Increment the packet number for sending next array
packet_id += 1 |
Edit: Here's the super hacky way (at least it feels that way...) that I've handled the problem. I've basically forced single packet transmissions to increment the if (metadata.totalNumberOfTrials > 45) {
transmissionStatus++;
}
else {
transmissionStatus++;
transmissionStatus++;
} Here's my attempt to control the Arduino's flow for multipacket transmissions better. I've introduced a ###############################################################################
# Serial Transfer to Arduino: Python Status
###############################################################################
def update_python_status():
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# Initialize array_size of 0
array_size = 0
# Stuff packet with size of trialArray
array_size = link.tx_obj(1)
# Open communication link
link.open()
# Send array
link.send(array_size, packet_id=0)
print("Sent END OF TRANSMISSION Status")
while not link.available():
pass
# Receive trial array:
rxarray = link.rx_obj(obj_type=type(1),
obj_byte_size=array_size,
list_format='i')
print("Received END OF TRANSMISSION Status")
print(rxarray)
# Close the communication link
link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass And here's how I've tried writing the Arduino code. INCLUDING UPDATE // Use package SerialTransfer.h from PowerBroker2 https://github.com/PowerBroker2/SerialTransfer
#include "SerialTransfer.h"
// Rename SerialTransfer to myTransfer
SerialTransfer myTransfer;
const int MAX_NUM_TRIALS = 60; // maximum number of trials possible; much larger than needed but smaller than max value of metadata.totalNumberOfTrials
struct __attribute__((__packed__)) metadata_struct {
uint8_t totalNumberOfTrials; // total number of trials for experiment
uint16_t punishTone; // airpuff frequency tone in Hz
uint16_t rewardTone; // sucrose frequency tone in Hz
uint8_t USDeliveryTime_Sucrose; // amount of time to open sucrose solenoid
uint8_t USDeliveryTime_Air; // amount of time to open air solenoid
uint16_t USConsumptionTime_Sucrose; // amount of time to wait for sucrose consumption
} metadata;
int32_t trialArray[MAX_NUM_TRIALS]; // create trial array
int32_t ITIArray[MAX_NUM_TRIALS]; // create ITI array
int32_t noiseArray[MAX_NUM_TRIALS]; // create noise array
int32_t transmissionStatus;
int32_t pythonGo;
boolean acquireMetaData = true;
boolean acquireTrials = false;
boolean acquireITI = false;
boolean acquireNoise = false;
boolean rx = true;
boolean pythonGoSignal = false;
boolean arduinoGoSignal = false;
void setup()
{
Serial.begin(115200);
Serial1.begin(115200);
myTransfer.begin(Serial1, true);
}
void loop(){
rx_function();
pythonGo_rx();
go_signal();
}
void rx_function() {
if (rx) {
metadata_rx();
trials_rx();
iti_rx();
noise_rx();
}
}
int metadata_rx() {
if (acquireMetaData && transmissionStatus == 0) {
if (myTransfer.available())
{
myTransfer.rxObj(metadata);
Serial.println("Received Metadata");
myTransfer.sendDatum(metadata);
Serial.println("Sent Metadata");
acquireMetaData = false;
transmissionStatus++;
Serial.println(transmissionStatus);
acquireTrials = true;
}
}
}
int trials_rx() {
if (acquireTrials && transmissionStatus >= 1 && transmissionStatus < 3) {
if (myTransfer.available())
{
myTransfer.rxObj(trialArray);
Serial.println("Received Trial Array");
myTransfer.sendDatum(trialArray);
Serial.println("Sent Trial Array");
if (metadata.totalNumberOfTrials > 45) { // Hacky way of forcing fix...
transmissionStatus++;
}
else {
transmissionStatus++;
transmissionStatus++;
}
Serial.println(transmissionStatus);
acquireITI = true;
}
}
}
int iti_rx() {
if (acquireITI && transmissionStatus >= 3 && transmissionStatus < 5) {
acquireTrials = false;
if (myTransfer.available())
{
myTransfer.rxObj(ITIArray);
Serial.println("Received ITI Array");
myTransfer.sendDatum(ITIArray);
Serial.println("Sent ITI Array");
if (metadata.totalNumberOfTrials > 45) { // Hacky way of forcing fix...
transmissionStatus++;
}
else {
transmissionStatus++;
transmissionStatus++;
}
Serial.println(transmissionStatus);
acquireNoise = true;
}
}
}
int noise_rx() {
if (acquireNoise && transmissionStatus >= 5 && transmissionStatus < 7) {
acquireITI = false;
if (myTransfer.available())
{
myTransfer.rxObj(noiseArray);
Serial.println("Received Noise Array");
myTransfer.sendDatum(noiseArray);
Serial.println("Sent Noise Array");
if (metadata.totalNumberOfTrials > 45) { // Hacky way of forcing fix...
transmissionStatus++;
}
else {
transmissionStatus++;
transmissionStatus++;
}
Serial.println(transmissionStatus);
pythonGoSignal = true;
}
}
}
void go_signal() {
if (arduinoGoSignal) {
Serial.println("GO!");
arduinoGoSignal = false;
for (byte i = 0; i <metadata.totalNumberOfTrials; i++) {
Serial.println(trialArray[i]);
}
for (byte i = 0; i <metadata.totalNumberOfTrials; i++) {
Serial.println(ITIArray[i]);
}
for (byte i = 0; i <metadata.totalNumberOfTrials; i++) {
Serial.println(noiseArray[i]);
}
}
}
int pythonGo_rx() {
if (pythonGoSignal && transmissionStatus == 7) {
if (myTransfer.available())
{
myTransfer.rxObj(pythonGo);
Serial.println("Received Python Status");
myTransfer.sendDatum(pythonGo);
Serial.println("Sent Python Status");
arduinoGoSignal = true;
}
}
}
This hacky fix seems to work, but I feel like I'm just doing something wrong... |
First, I'm sorry for all these new comments today and yesterday, I don't mean to spam you... I've run into a new problem with the transmission where, although the packets are being transmitted to and from the Arduino, the second set of trials are not being appended to the first one. For example, if I want to do an experiment of 46 trials, I'll split the array into two arrays of 23. The transfer goes along as normal and it says that everything has been parsed successfully. When I go through the experiment, however, the list runs out after the 23rd trial. It's like the second array is overwriting the first array. This is if I use the multipacket functions written above or if I do it like this: def multipacket_dev(split_array):
packet_id = 0
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
for array in split_array:
array = array.tolist()
# Initialize array_size of 0
array_size = 0
# Stuff packet with size of trialArray
array_size = link.tx_obj(array)
# Open communication link
link.open()
# Send array
link.send(array_size, packet_id=packet_id)
print("Sent Array")
print(array)
while not link.available():
pass
# Receive trial array:
rxarray = link.rx_obj(obj_type=type(array),
obj_byte_size=array_size,
list_format='i')
print("Received Array")
print(rxarray)
packet_id += 1
# Close the communication link
link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass Do you think that it's necessary to initialize two different arrays for each transmission on the Arduino side so things don't get written over on accident? I'm sure it's because I'm doing something wrong for storing the two transmissions to the same object... Edit: An additional thing that seems to happen is that if the array I'm building is too large, as in above 254 bytes, I can't send the array back to Python properly. It turns out that this maxes out at 60 trials before it's too large! Edit2: Maybe PowerBroker inspired brain blast. What if I specify the start and end of the larger arrays and have the packets organized that way? What I mean by this is that maybe I can set one object as requiring 2 packets to complete it. Something like |
I'll take a look at all this once I get the chance - either tomorrow or this weekend |
To make it a little easier for me, could you sum up all the edits into one reply? |
Hey PowerBroker! I apologize for the delay. I've been busy with some other stuff here in the lab and haven't had a chance yet to put it all into one comment that summarizes things correctly based on where I'm at. I'll do this probably on Tuesday afternoon/evening! In the meantime, I've been very successfully using what we have so far to run behavior with my labmates and have generated some really cool stuff. The lab is pretty happy with the system and it operates smoothly. I'll update you soon! Thanks again man! |
Hey PowerBroker! Thanks for your patience! Okay, here's a summary of what I have going on. There's a TLDR below too. I've made a module called If an array has more than 60 elements, I divide it in half and make a list consisting of both arrays called What I'm seeing is that, although the Arduino is receiving both halves of the large array, the first half is getting overwritten by the second half. For example, if I send 70 trials, the Arduino receives two halves of 35. When I print out each value of the array once the transfer is complete, the first 35 values are from the second packet. The second 35 values are all 0s. The first half seems to have disappeared or been overwritten! I've also had to create a Another issue that I'm thinking is maybe causing this all is that, when I want to confirm on the Python side that things have been received properly, the Arduino is trying to send the whole 70 element array which doesn't fit! The largest size I can send is 60! I thought I could accomplish this with -- TLDR -- Here's the code that I've been working with: // Use package SerialTransfer.h from PowerBroker2 https://github.com/PowerBroker2/SerialTransfer
#include "SerialTransfer.h"
// Rename SerialTransfer to myTransfer
SerialTransfer myTransfer;
const int MAX_NUM_TRIALS = 60; // maximum number of trials possible; much larger than needed but smaller than max value of metadata.totalNumberOfTrials
struct __attribute__((__packed__)) metadata_struct {
uint8_t totalNumberOfTrials; // total number of trials for experiment
uint16_t punishTone; // airpuff frequency tone in Hz
uint16_t rewardTone; // sucrose frequency tone in Hz
uint8_t USDeliveryTime_Sucrose; // amount of time to open sucrose solenoid
uint8_t USDeliveryTime_Air; // amount of time to open air solenoid
uint16_t USConsumptionTime_Sucrose; // amount of time to wait for sucrose consumption
} metadata;
int32_t trialArray[MAX_NUM_TRIALS]; // create trial array
int32_t ITIArray[MAX_NUM_TRIALS]; // create ITI array
int32_t noiseArray[MAX_NUM_TRIALS]; // create noise array
int32_t transmissionStatus;
int32_t pythonGo;
boolean acquireMetaData = true;
boolean acquireTrials = false;
boolean acquireITI = false;
boolean acquireNoise = false;
boolean rx = true;
boolean pythonGoSignal = false;
boolean arduinoGoSignal = false;
void setup()
{
Serial.begin(115200);
Serial1.begin(115200);
myTransfer.begin(Serial1, true);
}
void loop(){
rx_function();
pythonGo_rx();
go_signal();
}
void rx_function() {
if (rx) {
metadata_rx();
trials_rx();
iti_rx();
noise_rx();
}
}
int metadata_rx() {
if (acquireMetaData && transmissionStatus == 0) {
if (myTransfer.available())
{
myTransfer.rxObj(metadata);
Serial.println("Received Metadata");
myTransfer.sendDatum(metadata);
Serial.println("Sent Metadata");
acquireMetaData = false;
transmissionStatus++;
Serial.println(transmissionStatus);
acquireTrials = true;
}
}
}
int trials_rx() {
if (acquireTrials && transmissionStatus >= 1 && transmissionStatus < 3) {
if (myTransfer.available())
{
myTransfer.rxObj(trialArray);
Serial.println("Received Trial Array");
myTransfer.sendDatum(trialArray);
Serial.println("Sent Trial Array");
if (metadata.totalNumberOfTrials > 60) {
transmissionStatus++;
}
else {
transmissionStatus++;
transmissionStatus++;
}
Serial.println(transmissionStatus);
acquireITI = true;
}
}
}
int iti_rx() {
if (acquireITI && transmissionStatus >= 3 && transmissionStatus < 5) {
acquireTrials = false;
if (myTransfer.available())
{
myTransfer.rxObj(ITIArray);
Serial.println("Received ITI Array");
myTransfer.sendDatum(ITIArray);
Serial.println("Sent ITI Array");
if (metadata.totalNumberOfTrials > 60) {
transmissionStatus++;
}
else {
transmissionStatus++;
transmissionStatus++;
}
Serial.println(transmissionStatus);
acquireNoise = true;
}
}
}
int noise_rx() {
if (acquireNoise && transmissionStatus >= 5 && transmissionStatus < 7) {
acquireITI = false;
if (myTransfer.available())
{
myTransfer.rxObj(noiseArray);
Serial.println("Received Noise Array");
myTransfer.sendDatum(noiseArray);
Serial.println("Sent Noise Array");
if (metadata.totalNumberOfTrials > 60) {
transmissionStatus++;
}
else {
transmissionStatus++;
transmissionStatus++;
}
Serial.println(transmissionStatus);
pythonGoSignal = true;
}
}
}
// Python status function for flow control
int pythonGo_rx() {
if (pythonGoSignal && transmissionStatus == 7) {
if (myTransfer.available())
{
myTransfer.rxObj(pythonGo);
Serial.println("Received Python Status");
myTransfer.sendDatum(pythonGo);
Serial.println("Sent Python Status");
arduinoGoSignal = true;
}
}
}
void go_signal() {
if (arduinoGoSignal) {
Serial.println("GO!");
arduinoGoSignal = false;
for (byte i = 0; i <metadata.totalNumberOfTrials; i++) {
Serial.println(trialArray[i]);
}
for (byte i = 0; i <metadata.totalNumberOfTrials; i++) {
Serial.println(ITIArray[i]);
}
for (byte i = 0; i <metadata.totalNumberOfTrials; i++) {
Serial.println(noiseArray[i]);
}
Serial.println(sizeof(trialArray));
rx = true;
acquireMetaData = true;
}
} And here's my Python: # Bruker 2-Photon Serial Transfer Utils
# Jeremy Delahanty May 2021
# pySerialTransfer written by PowerBroker2
# https://github.com/PowerBroker2/pySerialTransfer
###############################################################################
# Import Packages
###############################################################################
# Serial Transfer
# Import pySerialTransfer for serial comms with Arduino
from pySerialTransfer import pySerialTransfer as txfer
# Import Numpy for splitting arrays
import numpy as np
# Import sys for exiting program safely
import sys
###############################################################################
# Functions
###############################################################################
###############################################################################
# Serial Transfer to Arduino: Error Checking
###############################################################################
def array_error_check(transmitted_array, received_array):
# If the transmitted array and received array are equal
if transmitted_array == received_array:
# Tell the user the transfer was successful
print("Successful Transfer")
# If the transmission failed
else:
# Tell the user an error occured
print("Transmission Error!")
# Tell the user the program is exiting
print("Exiting...")
# Exit the program
sys.exit()
###############################################################################
# Serial Transfer to Arduino: One Packet
###############################################################################
# -----------------------------------------------------------------------------
# Send an Individual Packet
# -----------------------------------------------------------------------------
def transfer_packet(array, packet_id):
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# Initialize array_size of 0
array_size = 0
# Stuff packet with size of trialArray
array_size = link.tx_obj(array)
# Open communication link
link.open()
# Send array
link.send(array_size, packet_id=packet_id)
print("Sent Array")
print(array)
while not link.available():
pass
# Receive trial array:
rxarray = link.rx_obj(obj_type=type(array),
obj_byte_size=array_size,
list_format='i')
print("Received Array")
print(rxarray)
# Close the communication link
link.close()
array_error_check(array, rxarray)
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass
# -----------------------------------------------------------------------------
# Configuration/Metadata File Transfer
# -----------------------------------------------------------------------------
# TODO: Add error checking function for configuration
def transfer_metadata(config):
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# stuff TX buffer (https://docs.python.org/3/library/struct.html#format-characters)
metaData_size = 0
metaData_size = link.tx_obj(config['metadata']['totalNumberOfTrials']['value'], metaData_size, val_type_override='B')
metaData_size = link.tx_obj(config['metadata']['punishTone']['value'], metaData_size, val_type_override='H')
metaData_size = link.tx_obj(config['metadata']['rewardTone']['value'], metaData_size, val_type_override='H')
metaData_size = link.tx_obj(config['metadata']['USDeliveryTime_Sucrose']['value'], metaData_size, val_type_override='B')
metaData_size = link.tx_obj(config['metadata']['USDeliveryTime_Air']['value'], metaData_size, val_type_override='B')
metaData_size = link.tx_obj(config['metadata']['USConsumptionTime_Sucrose']['value'], metaData_size, val_type_override='H')
# Open comms to Arudino
link.open()
# Send the metadata to the Arduino
link.send(metaData_size, packet_id=0)
# While sending the data, the link is unavailable. Pass this state
# until done.
while not link.available():
pass
# Receive packet from Arduino
# Create rxmetaData dictionary
rxmetaData = {}
# Start rxmetaData reception size of 0
rxmetaData_size = 0
# Receive each field from the Arduino
rxmetaData['totalNumberOfTrials'] = link.rx_obj(obj_type='B', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['B']
rxmetaData['punishTone'] = link.rx_obj(obj_type='H', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['H']
rxmetaData['rewardTone'] = link.rx_obj(obj_type='H', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['H']
rxmetaData['USDeliveryTime_Sucrose'] = link.rx_obj(obj_type='B', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['B']
rxmetaData['USDeliveryTime_Air'] = link.rx_obj(obj_type='B', start_pos=rxmetaData_size)
rxmetaData_size += txfer.ARRAY_FORMAT_LENGTHS['B']
rxmetaData['USConsumptionTime_Sucrose'] = link.rx_obj(obj_type='H', start_pos=rxmetaData_size)
print(rxmetaData)
# Close comms to the Arduino
link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass
# -----------------------------------------------------------------------------
# Trial Array Transfers: One Packet
# -----------------------------------------------------------------------------
def onepacket_transfers(array_list):
# Give each new packet an ID of 0. The link is closed per packet
# transmission.
packet_id = 0
# For each array in the list of arrays defining trial data
for array in array_list:
# Transfer the packet
transfer_packet(array, packet_id)
###############################################################################
# Serial Transfer to Arduino: Multi-packet
###############################################################################
# -----------------------------------------------------------------------------
# Trial Array Splitting for Multipacket Transfers
# -----------------------------------------------------------------------------
def split_multipacket_array(array):
# This function receives a large list of trial variables. It needs to be
# split into two arrays using np.array_split.
split_ndarray = np.array_split(array, 2)
# Return the split numpy arrays
return split_ndarray
# -----------------------------------------------------------------------------
# Trial Array Transfers: Multi-packet
# -----------------------------------------------------------------------------
def multipacket_transfer(array_list):
packet_id = 0
# For each array in the list of trial arrays
for array in array_list:
# Split the array into packets small enough to transfer
split_array = split_multipacket_array(array)
# Transfer the arrays as multiple packets
transfer_arrays_multipacket(split_array, packet_id)
# multipacket_dev(split_array, packet_id)
packet_id = 0
def transfer_arrays_multipacket(split_array, packet_id):
# For each array received by the splitting function
for array in split_array:
# Save the array as a list for transfer
array = array.tolist()
# Send the array
transfer_packet(array, packet_id)
# Increment the packet number for sending next array
packet_id += 1
def multipacket_dev(split_array, packet_id):
print(packet_id)
new_array = []
for array in split_array:
new_array.append(array.tolist())
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# Initialize array_size of 0
first_array_size = 0
second_array_size = 0
# Stuff packet with size of trialArray
first_array_size = link.tx_obj(new_array[0])
# Open communication link
link.open()
# Send array
link.send(first_array_size, packet_id=packet_id)
# print("Sent Array")
# print(new_array[0])
while not link.available():
pass
# Receive trial array:
first_rxarray = link.rx_obj(obj_type=type(new_array[0]),
obj_byte_size=first_array_size,
list_format='i')
# print("Received Array")
# print(first_rxarray)
packet_id += 1
second_array_size = link.tx_obj(new_array[1])
link.send(second_array_size, packet_id=packet_id)
while not link.available():
pass
second_rxarray = link.rx_obj(obj_type=type(new_array[1]),
obj_byte_size=second_array_size, list_format='i')
# print(second_rxarray)
# Close the communication link
link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass
###############################################################################
# Serial Transfer to Arduino: Python Status
###############################################################################
def update_python_status():
try:
# Initialize COM Port for Serial Transfer
link = txfer.SerialTransfer('COM12', 115200, debug=True)
# Initialize array_size of 0
array_size = 0
array = 1
# Stuff packet with size of trialArray
array_size = link.tx_obj(array)
# Open communication link
link.open()
# Send array
link.send(array_size, packet_id=0)
print("Sent END OF TRANSMISSION Status")
while not link.available():
pass
# Receive trial array:
rxarray = link.rx_obj(obj_type=type(array),
obj_byte_size=array_size,
list_format='i')
print("Received END OF TRANSMISSION Status")
array_error_check(array, rxarray)
# Close the communication link
link.close()
except KeyboardInterrupt:
try:
link.close()
except:
pass
except:
import traceback
traceback.print_exc()
try:
link.close()
except:
pass Finally, here's how I call for sending multi-packet objects in Python:
This is hopefully not too much of a mess, but if what I wrote is trash that's okay! I'll improve it until it's understandable enough for you to not have to hit your head over. |
Ok, I think I have an idea of what you're trying to do. One thing about this library is that it was built for speed, so if it finishes processing a packet and there's still serial data in the UART input buffer, it will basically overwrite the last packet with the newer packet. I'm not 100% sure if this is what's happening in your case, but try adding a small delay between transmissions. Also, you should only need to open the port once in the beginning of your program, but that's up to you. |
I will try that this week! I'm implementing a couple other things for the experiment that's higher priority for the lab.
I will also change my program so it does that, thanks for the tip! Keep brokering all that power. |
Okay, so I tried to introduce delays of different amounts of time on the Python side and I still seem to run into the same problem. I was wondering if it could be that when the Arduino tries to send the data back to Python after receiving the first packet, there's an error that's produced on the Python side. I think that the Since I have two packets on the Python side, should I make two |
That sounds like the right idea, however, the devil is always in the detail. Whatever the solution is, make sure it's scalable and relatively straight forward. Bandaids and hack solutions are terrible in the long run. Here's a few things to keep in mind:
I hope this ramble makes sense |
Got it, I'll be extra careful to make sure the solution is scalable. For your next two points, I'm pretty sure this makes sense. I will do my best to implement this by following your text examples, thanks for linking them! I'm pretty sure I understand the idea. Over the next few days, I'll try to implement this but it may be a little bit before I get there. There's other things I gotta get done first for the project. I'll keep you posted! Thanks man! |
Hey PowerBroker, First, I hope that things have been going well and that you have power in abundance! I just wanted to check in with how things are going. The system is still working great and we're collecting data nicely! I've been told that getting trial sizes greater than 60 isn't much of a priority and so I've had to work on other things over the past couple months. I plan on coming in this weekend to try and make it happen since it'd be cool, fun to learn, and is something I think about often since I feel like I'm so close! In the meantime, a different enhancement for the system is something I've been working on. I'm trying to get the Arduino to reset after a session is over on its own so I don't have to reupload the .ino file each time. I've used the button on the board and made a reset function that successfully restarts the Arduino, but it seems that it's retaining the previous session's information as if the reset doesn't flush/reinitialize variables. I was looking into how things are stored in Arduinos and I came across the terms EEPROM and flash memory, but I don't know how to tell where things are actually being stored on the board! Do you have any advice for resetting things properly/resources where I can learn where transfers are stored? |
Hey Jeremy, glad to hear things are working well for you!
Why do you need to reset the Arduino after a session? You should be able to have the code start over, wait for new config input from Python via USB, and then start the next session. I haven't looked at your codebase in a while, but I understand this change could be a hefty effort.
This is pretty strange. All variables are stored in SRAM unless you specifically tell the compiler to store the variable in flash via the PROGMEM modifier or use the EEPROM.h library to save values in EEPROM. Variables in SRAM get cleared/reinitialized during a reset while variables in flash and EEPROM retain their values. Are you sure the normal SRAM variables are retaining non-initialized values after button resets? What about hard resets? |
Hello! Things are definitely going well. Maybe I can broker power of my own one day if I continue learning from you!
I had thought that this is what was supposed to happen, but I couldn't get the code to start over from the very beginning of the sketch. It plows right through all the flags I thought would reset to their starting statuses it seems and starts giving trials. Could it be because I don't have those flags written in the
So I haven't verified that it is for sure retaining non-initialized values after reset which I can try this weekend when I'm hacking around. I assumed it was retaining them because it starts delivering stimuli once the reset is complete. I figured that an array that was allocated but not written to would crash the system rather than keep going (as in when I create The setup always starts with a sucrose trial and, after reset, it delivers sucrose as well. I'll try printing out the
I want to say it just started delivering trials right away after a hard reset now that you mention it, but it's been a while since I've tested. I'll try it out this weekend too. This makes me think that it probably is something wrong with my flags not being changed properly during/after the reset... |
Hey man, sorry for the late reply - I've been busy with a bunch of stuff lately. It seems like you already have the logic in place to reset the Arduino in software, so the hefty effort will likely be in the form of bug hunting instead of refactoring. At least one bug is causing your flags in the logic to be ignored.
It might be an issue if the flags aren't properly initialized. You also might need your own "setup" function that is called as soon as the trial is done. The function would then reset all the variables needing reset plus a flag to say "hey, tell Python I'm done with the trial and wait for more input". I think that would be a good way to do it. |
No problem at all!
Okay, that's super helpful! I'll try making a different setup function that reinitializes flags and see how that turns out. I'll keep you posted! Thanks man! |
Hello! Fun quick update, I got the reset function to work properly with your help! Thank you so much! It's going to make the running of our experiments easier to do for sure. I'm in the middle of refactoring things for the codebase and, once that's done, I'll keep working on multi-packet transmissions. You can check it out/show it to future employers if you'd like. Here's the repo!. The branch I'm working on is called "bruker_control_refactor". There's also some docs I tried writing but there's a few typos here. |
Nice work man! I love the auto documentation and website. I actually want to do something similar for both this repo and this other Python library, but it's not working right. I go into the docs folder, open cmd, I do
and make sure the index.rst has the following contents:
After this, I run in cmd After opening the WarThunder.html in the _build folder doesn't include all of the docstrings and stuff I need in there. Do you have any advice on how to fix it to make it look like your website? (except that I want to use the "classic" format) |
To be clear: I'm testing this documentation stuff with my other repo before I mess with this one |
Nvm, it was a real pain, but I got it to work with my other repo. Here's the site: https://warthunder.readthedocs.io/en/latest/ |
Wait, two of my modules aren't showing up in the website. Bruh, how did you figure out how to do this? |
Hey! It took a while of honestly just doing trial and error rewriting the documentation I had written in the files themselves I think when that happened. Let me take a peek really quick. From what I'm seeing in your doc strings and stuff everything looks like how I learned online, so I'm not sure why it's not populating all of your modules. I see that your docs have some additional files for what the modules themselves are and it's that modules.rst file that's referenced elsewhere which I didn't do. I think that's the only difference I see really... I can look more this evening and see what I can come up with! It was definitely a frustrating process for it to build "successfully" without errors only to load the page and see half my stuff was missing. So I'm totally guessing here, but I'm wondering if Sphinx knows how to handle multiple .rst files. Since there's a couple .rst files in the Another thing that could help is if you use the Also, I noticed that in your Lastly, there was a while where I was convinced one night that I was never going to get it right when I hit refresh out of frustration and suddenly all my stuff was there. Someone I chatted with mentioned that if you push a lot stuff to the readthedocs site quickly it can take a while to actually populate everything somehow... Another update! After updating some stuff for my own docs, I may have discovered an issue! I can't tell if this is what's happening with yours, but read the docs fails to build certain modules if it can't import a package that's used in your files successfully. Sphinx/rtd imports the code modules into the containers hosting your documentation. If there's an issue with getting that package installed on rtd's end, the system passes over it and builds the docs without the code you wrote. It's worth it to look at the |
Hey PowerBroker!
Things are moving along pretty smoothly with our project! I can send trial sets of 45 or smaller no problem and the lab is pretty happy with it. A new challenge has appeared for me, though. The lab would like it to be possible to double the amount of trials to 90 total as a definite ceiling.
I had thought that I was doing this correctly with what you have taught me so far but, after some testing, I've come to realize that while a second packet is getting sent by Python, my Arduino code doesn't seem to collecting the second packet's content. Here's an example of what I mean:
Any advice? I had thought it would collect the packet independently but I'm clearly misunderstanding how multiple packets for the same object work. Is this something I can do even or should I create a second array in the Arduino code to store the second half of trials?
Here's some full Python and Arduino code:
Python
Arduino
The text was updated successfully, but these errors were encountered: