From a4aaca534a5d53c57e7139f2c1c91aac7639a241 Mon Sep 17 00:00:00 2001
From: Zhirong Zhang <zhirong.zhang@cern.ch>
Date: Fri, 6 Sep 2024 16:01:01 -0700
Subject: [PATCH 1/5] Add a script to interpose powerboards on itkpd.

---
 database/pbv3_add_interposer.py | 168 ++++++++++++++++++++++++++++++++
 1 file changed, 168 insertions(+)
 create mode 100644 database/pbv3_add_interposer.py

diff --git a/database/pbv3_add_interposer.py b/database/pbv3_add_interposer.py
new file mode 100644
index 0000000..e6d8198
--- /dev/null
+++ b/database/pbv3_add_interposer.py
@@ -0,0 +1,168 @@
+import os
+import itkdb
+import csv
+import argparse
+import sys
+
+'''
+Run this script to manually add an interposer component to existing powerboards,
+given their parent panel ID.
+Assume that the user has configurated their itkpd account.
+'''
+
+
+# ==========================================
+# Pre-req: set up client and retrive the interposer 
+# componet template.
+# ==========================================
+c = itkdb.Client(expires_after={"days": 7})
+
+# Bypass all cache
+c.headers.update({"cache-control": "no-cache"})
+
+interposer_template = c.get('generateComponentTypeDtoSample', json={'project': 'S', 'code': 'PWB_INTERPOSER'})
+
+
+# ==========================================
+# Functions
+# ==========================================
+
+# ------------------------------------------
+# Function: read_PWB_from_csv
+# Description: temp function for reading CSV files
+# Arguments: path to the csv file
+# Returns: a list of PWB ID
+# ------------------------------------------
+def read_PWB_from_csv(csv_file_path):
+    loaded_PWB = []
+
+    with open(csv_file_path, mode='r', newline='') as file:
+        reader = csv.reader(file)
+
+        # Skip the first row (header)
+        next(reader)
+
+        for row in reader:
+            # Extract the fourth to thirteenth columns (index 3 to 12)
+            selected_columns = row[3:13]
+            # Filter out blank entries
+            filtered_columns = [f'20USBP0{value}' for value in selected_columns if value.strip()]
+            # Only append rows that have non-blank entries
+            if filtered_columns:
+                loaded_PWB.extend(filtered_columns)
+
+    return loaded_PWB
+
+
+# ------------------------------------------
+# Function: read_panel
+# Description: use panel ID to search for PWB
+# Arguments: a list of panel ID
+# Returns: a list of child PWB ID
+# ------------------------------------------
+def read_panel(panel_ls):
+    PWB_ls = []
+
+    for panel_ID in panel_ls:
+        panel = c.get("getComponent", json={"component": panel_ID})
+
+        child_PWB = [
+            child["component"]["id"]
+            for child in panel.get("children", [])
+            if (child["component"] and child['componentType']['code']=="PWB")
+        ]
+            
+        PWB_ls.extend(child_PWB)
+        print(f"Panel {panel_ID} has been processed and {child_PWB} are added to the list.")
+
+    return PWB_ls
+
+# ------------------------------------------
+# Function: load_interposer
+# Description: create and load an interpsoer for each of the PWB
+# Arguments: a list of PWB ID
+# Returns: NA
+# ------------------------------------------
+def load_interposer(PWB_ls, component_template=interposer_template, check_interposer=True):
+
+    for PWB_ID in PWB_ls:
+
+        PWB = c.get("getComponent", json={"component": PWB_ID})
+        if PWB.get('inTransit', False) == True:
+            print(f"PWB {PWB['serialNumber']} is in transit and cannot be loaded with an interposer.")
+            continue
+
+        if check_interposer:
+            if any((PWBchild['componentType']['code'] == 'PWB_INTERPOSER' and PWBchild['component']) for PWBchild in PWB.get("children", [])):
+             print(f"PWB {PWB['serialNumber']} has already been loaded with an interposer.")
+             continue
+
+        try:
+            new_interposer = c.post(
+                "registerComponent", 
+                json={
+                    **component_template,
+                    'subproject': 'SB',
+                    'institution': 'LBNL_STRIP_POWERBOARDS',
+                }
+            )
+
+            # Hard coded parameters.
+            output = c.post(
+                "assembleComponent", 
+                json={
+                    "parent": PWB['serialNumber'], 
+                    "child": new_interposer['component']['code'],
+                    "properties": {
+                        "GLASS_BEADS_USED": True,
+                        'INTERPOSER_MATERIAL': 'Kapton',
+                        'INTERPOSER_THICKNESS': 50,
+                        'INTERPOSER_ADHESIVE': 'SE4445',
+                        'ADHESIVE_THICKNESS': 110,
+                        'INTERPOSER_SURFACE_PREP': 'IPA',
+                    },
+                }
+            )
+            print(f"Interposer {new_interposer['component']['code']} has been created and mounted on PWB {output['serialNumber']}.")
+
+        except Exception as e:
+            print(f"An error occurred while processing PWB {PWB['serialNumber']}: {e}")
+
+
+
+
+# ==========================================
+# Work space below
+# ==========================================
+
+# Create the parser
+parser = argparse.ArgumentParser(description="Process either PWB number or Panel number, but not neither.")
+
+# Add optional arguments for PWB and Panel numbers
+parser.add_argument('-p', '--pwbSN', nargs='+', help="PWB serial number(s)")
+parser.add_argument('-c', '--panel', nargs='+', help="Panel serial number(s)")
+
+# Parse the arguments
+args = parser.parse_args()
+
+# Check if either PWB number or Panel number is provided (but not both or neither)
+if not args.pwbSN and not args.panel:
+    print("Error: You must provide either a PWB number or a Panel number.")
+    parser.print_help()  # Print the help message
+    sys.exit(1)  # Exit the script with an error status
+elif args.pwbSN and args.panel:
+    print("Error: You cannot provide both a PWB number and a Panel number.")
+    parser.print_help()
+    sys.exit(1)
+
+pwb_ls = []
+
+if args.pwbSN:
+    print(f"PWB number(s) provided: {args.pwbSN}")
+    pwb_ls = args.pwbSN
+    
+if args.panel:
+    print(f"Panel number(s) provided: {args.panel}")
+    pwb_ls = read_panel(args.panel)
+
+load_interposer(pwb_ls)
-- 
GitLab


From 40ffef967c71ec6ed6b53d71f6f84cd43f343232 Mon Sep 17 00:00:00 2001
From: Zhirong Zhang <zhirong.zhang@cern.ch>
Date: Mon, 21 Oct 2024 11:08:17 -0700
Subject: [PATCH 2/5] Modified so that it can used in autoRegister

---
 database/pbv3_add_interposer.py | 109 +++++++++++++++-----------------
 1 file changed, 51 insertions(+), 58 deletions(-)

diff --git a/database/pbv3_add_interposer.py b/database/pbv3_add_interposer.py
index e6d8198..f4cd7a6 100644
--- a/database/pbv3_add_interposer.py
+++ b/database/pbv3_add_interposer.py
@@ -4,29 +4,14 @@ import csv
 import argparse
 import sys
 
+from database import pwbdbtools
+
 '''
 Run this script to manually add an interposer component to existing powerboards,
 given their parent panel ID.
 Assume that the user has configurated their itkpd account.
 '''
 
-
-# ==========================================
-# Pre-req: set up client and retrive the interposer 
-# componet template.
-# ==========================================
-c = itkdb.Client(expires_after={"days": 7})
-
-# Bypass all cache
-c.headers.update({"cache-control": "no-cache"})
-
-interposer_template = c.get('generateComponentTypeDtoSample', json={'project': 'S', 'code': 'PWB_INTERPOSER'})
-
-
-# ==========================================
-# Functions
-# ==========================================
-
 # ------------------------------------------
 # Function: read_PWB_from_csv
 # Description: temp function for reading CSV files
@@ -60,18 +45,18 @@ def read_PWB_from_csv(csv_file_path):
 # Arguments: a list of panel ID
 # Returns: a list of child PWB ID
 # ------------------------------------------
-def read_panel(panel_ls):
+def read_panel(client, panel_ls):
     PWB_ls = []
 
     for panel_ID in panel_ls:
-        panel = c.get("getComponent", json={"component": panel_ID})
+        panel = client.get("getComponent", json={"component": panel_ID})
 
         child_PWB = [
             child["component"]["id"]
             for child in panel.get("children", [])
             if (child["component"] and child['componentType']['code']=="PWB")
         ]
-            
+
         PWB_ls.extend(child_PWB)
         print(f"Panel {panel_ID} has been processed and {child_PWB} are added to the list.")
 
@@ -83,23 +68,25 @@ def read_panel(panel_ls):
 # Arguments: a list of PWB ID
 # Returns: NA
 # ------------------------------------------
-def load_interposer(PWB_ls, component_template=interposer_template, check_interposer=True):
+def load_interposer(client, PWB_ls, check_interposer=True):
 
+    component_template = client.get('generateComponentTypeDtoSample', json={'project': 'S', 'code': 'PWB_INTERPOSER'})
     for PWB_ID in PWB_ls:
 
-        PWB = c.get("getComponent", json={"component": PWB_ID})
-        if PWB.get('inTransit', False) == True:
-            print(f"PWB {PWB['serialNumber']} is in transit and cannot be loaded with an interposer.")
-            continue
+        PWB = client.get("getComponent", json={"component": PWB_ID})
 
         if check_interposer:
             if any((PWBchild['componentType']['code'] == 'PWB_INTERPOSER' and PWBchild['component']) for PWBchild in PWB.get("children", [])):
              print(f"PWB {PWB['serialNumber']} has already been loaded with an interposer.")
              continue
 
+        if PWB.get('inTransit', False) == True:
+            print(f"PWB {PWB['serialNumber']} is in transit and cannot be loaded with an interposer.")
+            continue
+
         try:
-            new_interposer = c.post(
-                "registerComponent", 
+            new_interposer = client.post(
+                "registerComponent",
                 json={
                     **component_template,
                     'subproject': 'SB',
@@ -108,10 +95,10 @@ def load_interposer(PWB_ls, component_template=interposer_template, check_interp
             )
 
             # Hard coded parameters.
-            output = c.post(
-                "assembleComponent", 
+            output = client.post(
+                "assembleComponent",
                 json={
-                    "parent": PWB['serialNumber'], 
+                    "parent": PWB['serialNumber'],
                     "child": new_interposer['component']['code'],
                     "properties": {
                         "GLASS_BEADS_USED": True,
@@ -128,41 +115,47 @@ def load_interposer(PWB_ls, component_template=interposer_template, check_interp
         except Exception as e:
             print(f"An error occurred while processing PWB {PWB['serialNumber']}: {e}")
 
+# ------------------------------------------
+# Function: load_interposer_for_panel
+# Description: use panel number to add interposer for all pwb's on this panel 
+# Arguments: panel id
+# Returns: NA
+# ------------------------------------------
+def load_interposer_for_panel(client, panel_id, check_interposer_in=True):
+    pwb_ls_in = read_panel(client, [panel_id])
+    load_interposer(client, pwb_ls_in, check_interposer=check_interposer_in)
 
 
 
-# ==========================================
-# Work space below
-# ==========================================
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Process either PWB number or Panel number, but not neither.")
 
-# Create the parser
-parser = argparse.ArgumentParser(description="Process either PWB number or Panel number, but not neither.")
+    # Take in PWB and Panel numbers
+    parser.add_argument('-p', '--pwbSN', nargs='+', help="PWB serial number(s)")
+    parser.add_argument('-c', '--panel', nargs='+', help="Panel serial number(s)")
 
-# Add optional arguments for PWB and Panel numbers
-parser.add_argument('-p', '--pwbSN', nargs='+', help="PWB serial number(s)")
-parser.add_argument('-c', '--panel', nargs='+', help="Panel serial number(s)")
+    args = parser.parse_args()
 
-# Parse the arguments
-args = parser.parse_args()
+    # Check if either PWB number or Panel number is provided (but not both or neither)
+    if not args.pwbSN and not args.panel:
+        print("Error: You must provide either a PWB number or a Panel number.")
+        parser.print_help()
+        sys.exit(1)
+    elif args.pwbSN and args.panel:
+        print("Error: You cannot provide both a PWB number and a Panel number.")
+        parser.print_help()
+        sys.exit(1)
 
-# Check if either PWB number or Panel number is provided (but not both or neither)
-if not args.pwbSN and not args.panel:
-    print("Error: You must provide either a PWB number or a Panel number.")
-    parser.print_help()  # Print the help message
-    sys.exit(1)  # Exit the script with an error status
-elif args.pwbSN and args.panel:
-    print("Error: You cannot provide both a PWB number and a Panel number.")
-    parser.print_help()
-    sys.exit(1)
+    c = pwbdbtools.get_db_client()
+    
+    pwb_ls = []
 
-pwb_ls = []
+    if args.pwbSN:
+        print(f"PWB number(s) provided: {args.pwbSN}")
+        pwb_ls = args.pwbSN
 
-if args.pwbSN:
-    print(f"PWB number(s) provided: {args.pwbSN}")
-    pwb_ls = args.pwbSN
-    
-if args.panel:
-    print(f"Panel number(s) provided: {args.panel}")
-    pwb_ls = read_panel(args.panel)
+    if args.panel:
+        print(f"Panel number(s) provided: {args.panel}")
+        pwb_ls = read_panel(c, args.panel)
 
-load_interposer(pwb_ls)
+    load_interposer(c, pwb_ls)
-- 
GitLab


From 4db18c23ac250484ef8018f77f2f2394535c4e1b Mon Sep 17 00:00:00 2001
From: Zhirong Zhang <zhirong.zhang@cern.ch>
Date: Mon, 21 Oct 2024 12:02:54 -0700
Subject: [PATCH 3/5] Insert interposer

---
 database/pbv3_autoRegister.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/database/pbv3_autoRegister.py b/database/pbv3_autoRegister.py
index e94da98..23adc00 100755
--- a/database/pbv3_autoRegister.py
+++ b/database/pbv3_autoRegister.py
@@ -15,6 +15,7 @@ from database import pbv3_panel_loadshieldbox
 from database import pbv3_panel_singulate
 from database import pbv3_panel_loadhvmux
 from database import pbv3_panel_loadamac
+from database import pbv3_add_interposer
 
 import gspread
 import glob
@@ -328,7 +329,10 @@ def updatePanel(sheet, irow, client, checkTests = False, checkAMAC = False, flex
                     pbv3_panel_loadamac.loadwafer(client, panelid, icol-icol_wafer-1, wafer, die_num, amac_sn)
                 except Exception:
                     print("Ooooops.... Failed to load amac "+str(die_num))
-            
+
+            print('====== Next step: load interposer...')
+            pbv3_add_interposer.load_interposer_for_panel(client, panelid)
+
             panel = client.get('getComponent', json={'component':panelid, 'alternativeIdentifier':True})
             currentStage = panel['currentStage']['code']
         if currentStage == "LOADED" or currentStage == "HYBBURN": # PB is no longer on panel anymore
-- 
GitLab


From ea92a684d2d0c69c00bd47c40776b6cc0a0677e9 Mon Sep 17 00:00:00 2001
From: Zhirong Zhang <zhirong.zhang@cern.ch>
Date: Thu, 24 Oct 2024 13:41:55 -0700
Subject: [PATCH 4/5] Read in flex thickness and has interposer for flex
 properties

---
 database/pbv3_autoRegister.py | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/database/pbv3_autoRegister.py b/database/pbv3_autoRegister.py
index 23adc00..70f0af6 100755
--- a/database/pbv3_autoRegister.py
+++ b/database/pbv3_autoRegister.py
@@ -247,7 +247,18 @@ def updatePanel(sheet, irow, client, checkTests = False, checkAMAC = False, flex
 
         if flexArray is None: 
             print('Register powerboard flex array for '+panelid+" now...")
-            pwbdbtools.registerFlexArray(client, panelid, flex_array_batch, arr_type='PB_FLEX_ARRAY_3V2')
+            flexThickness = None
+            hasInterposer = None
+            cell_interposer = sheet.find("Has Interposer?")
+            cell_flexThickness = sheet.find("Flex Thickness")
+            if (cell_interposer is not None):
+                if sheet.cell(irow, cell_ready.col).value == "Yes":
+                    hasInterposer = True
+                elif sheet.cell(irow, cell_ready.col).value == "No":
+                    hasInterposer = False
+            if (cell_flexThickness is not None) and (sheet.cell(irow, cell_ready.col).value):
+                flexThickness = sheet.cell(irow, cell_ready.col).value
+            pwbdbtools.registerFlexArray(client, panelid, flex_array_batch, arr_type='PB_FLEX_ARRAY_3V2', flex_thickness=flexThickness, has_interposer=hasInterposer)
             flexArray = client.get('getComponent', json={'component':'20USBPF'+panelid})
         print('Found flex array: '+flexArray['code'])
         currentStage = flexArray['currentStage']['code']
@@ -330,8 +341,10 @@ def updatePanel(sheet, irow, client, checkTests = False, checkAMAC = False, flex
                 except Exception:
                     print("Ooooops.... Failed to load amac "+str(die_num))
 
-            print('====== Next step: load interposer...')
-            pbv3_add_interposer.load_interposer_for_panel(client, panelid)
+            cell_interposer = sheet.find("Has Interposer?")
+            if (cell_interposer is not None) and (sheet.cell(irow, cell_ready.col).value == "Yes"):
+                print('====== Next step: load interposer...')
+                pbv3_add_interposer.load_interposer_for_panel(client, panelid)
 
             panel = client.get('getComponent', json={'component':panelid, 'alternativeIdentifier':True})
             currentStage = panel['currentStage']['code']
-- 
GitLab


From a361474e2d3fa4b50912a899d4d462800b2483b8 Mon Sep 17 00:00:00 2001
From: Zhirong Zhang <zhirong.zhang@cern.ch>
Date: Thu, 24 Oct 2024 13:43:02 -0700
Subject: [PATCH 5/5] modify the flex registration function to accommodate for
 flex thickness and has interposer

---
 database/pwbdbtools.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/database/pwbdbtools.py b/database/pwbdbtools.py
index bbe6f9b..6a121b2 100644
--- a/database/pwbdbtools.py
+++ b/database/pwbdbtools.py
@@ -208,9 +208,15 @@ def getLatestTestRunByComponent(client, component, testType, stage=None):
     return testRun
 
 
-def registerFlexArray(client, panelid, flex_batch, arr_type='PB_FLEX_ARRAY_3V2'):
+def registerFlexArray(client, panelid, flex_batch, arr_type='PB_FLEX_ARRAY_3V2', flex_thickness = None, has_interposer = None):
     version, batch, number = parse_serial(panelid, fullsn=False)
 
+    properties_lib = {'VERSION':str(version), 'BATCH':batch, 'NUMBER':number}
+    if flex_thickness is not None:
+        properties_lib.update({'FLEX_THICKNESS': flex_thickness})
+    if has_interposer is not None:
+        properties_lib.update({'HAS_INTERPOSER': has_interposer})
+
     regdata = {
         'project':'S',
         'subproject':'SB',
@@ -219,7 +225,7 @@ def registerFlexArray(client, panelid, flex_batch, arr_type='PB_FLEX_ARRAY_3V2')
         'type':arr_type,
         'serialNumber':'20USBPF'+panelid,
         'batches':{'PWB_FLEX_BATCH':flex_batch},
-        'properties':{'VERSION':str(version), 'BATCH':batch, 'NUMBER':number}
+        'properties': properties_lib
     }
 
     client.post('registerComponent', json=regdata)
-- 
GitLab