def file_transfer(nodeID, model, credentials, progress_tag):
import ftplib
import os
import StringIO
import socket
import uuid
system.util.getLogger("FTPTransfer").info("Updating CCU software on {}".format(nodeID))
#Set a 10-minute timeout for FTP operations
socket.setdefaulttimeout(600)
batchID = str(uuid.uuid4())
try:
# Image File Source Directory
source_dir = r"C:/Ignition/"+ model +"/"
# Find the only .ci3 file
ci3_files = [f for f in os.listdir(source_dir) if f.endswith(".ci3")]
if len(ci3_files) != 1:
raise Exception("Expected only one .ci3 file, found {}".format(len(ci3_files)))
# Store the current database filename for logging to the unit as the tag CCU.Statuses.CCU_Software_Rev
original_filename = ci3_files[0]
source_filepath = os.path.join(source_dir, original_filename)
source_filesize = os.path.getsize(source_filepath)
# Extract credentials for the specified device
host = credentials.getValueAt(0,0)
password = credentials.getValueAt(0,1)
user = 'admin'
activeTag = '[default]' + nodeID + '/Version_Info/Software/CCU_Update_Active'
# Location to copy the image file to on the unit (SD card)
if model == "MAX":
directory = "/DRIVE-E/"
port = 21
else:
directory = "/"
port = 2111
if not all([host, user, password]):
raise Exception("Missing FTP credentials for device '{}'.".format(nodeID))
#Establish FTP connection
ftp = ftplib.FTP()
ftp.connect(host, port, timeout = 60) #Time out after 60 seconds
ftp.login(user, password)
#Set 'Passive' to false, ensuring it is an active connection
ftp.set_pasv(False)
#Set the working directory
ftp.cwd(directory)
ftp.voidcmd("TYPE I")
# Try to read CCU_Software_Rev.txt from the edge node
# This file contains the filename of the last database image file that was transferred to the unit via this script
# A mismatch between what is in this file and the image file saved at C:/Ignition/ will trigger a transfer
# The contents of this file are linked to the tag CCU.Statuses.CCU_Software_Rev
rev_lines = []
try:
ftp.retrlines("RETR CCU_Software_Rev.txt", rev_lines.append)
edge_rev = rev_lines[0].strip() if rev_lines else ""
except:
edge_rev = "" # File doesn't exist or can't be read
# Compare device CCU software rev against current released CCU software rev
if edge_rev != original_filename:
# Update status to 1 in the file 'CCU_Software_Update_Status.txt'
# This initiates notification popups on the unit HMI for the user to acknowledge
status = "1"
status_file = StringIO.StringIO(status + "\n")
ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
message = "Revision mismatch or missing. Updating CCU software on {} from {} to {}.".format(nodeID,edge_rev, original_filename)
system.util.getLogger("FTPTransfer").info(message)
#Set the Progress Active Flag to true
#This ensures the progress bar on the Unit Dashboard becomes visible
system.tag.writeAsync(activeTag, True)
#Inititate Manifest Log for the update instance
newVersion = original_filename
Units.CCU_Update.beginCCUmanifest(nodeID, batchID, newVersion, status, message)
# Attempt the image file transfer
try:
#set up the Callback function to be used for status updates on the file transfer
progress = Units.CCU_Update.progressCallback(source_filesize, progress_tag)
with open(source_filepath, "rb") as file:
ftp.storbinary("STOR image.ci3", file, blocksize = 32768, callback = progress)
# Transfer succeeded — update status to 2 and write the current revision to CCU_Software_Rev.txt
status = "2"
status_file = StringIO.StringIO(status + "\n")
ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
txt_content = StringIO.StringIO(original_filename + "\n")
ftp.storlines("STOR CCU_Software_Rev.txt", txt_content)
message = "Transfer complete. {} CCU software updated to: {}.".format(nodeID, original_filename)
system.util.getLogger("FTPTransfer").info(message)
#Completed CCU Manifest
Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
except socket.timeout as e:
message = "CCU software update on {} timed out: {}".format(nodeID,e)
system.util.getLogger("FTPTransfer").error(message)
# Transfer failed due to timeout — set status to 3
status = "3"
status_file = StringIO.StringIO(status + "\n")
try:
ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
message = "Timeout occurred while updating CCU software on {}. Status set to '3'.".format(nodeID)
system.util.getLogger("FTPTransfer").info(message)
Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
except Exception as inner_e:
message = "Failed to reset status after timeout on {}: {} ".format(nodeID, inner_e)
system.util.getLogger("FTPTransfer").error(message)
Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
else:
# Transfer not needed, software already up to date — reset status to 0
status = "0"
status_file = StringIO.StringIO(status + "\n")
ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
message = "No update needed. {} already running {}.".format(nodeID,edge_rev)
system.util.getLogger("FTPTransfer").info(message)
Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
ftp.quit()
#Set Progress Tag to False
#Ensure progress bar visibility is disabled on the Unit Dashboard
system.tag.writeAsync(activeTag, False)
except Exception as e:
status = '3'
message = "CCU software update on {} failed: ".format(nodeID) + str(e)
system.util.getLogger("FTPTransfer").error(message)
Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
#Close the socket
socket.close()
return