Trying to connect to my p1s with python. I can connect just fine with FileZilla (telling it to use FTP over TLS and accepting the cert). When I try with either python or powershell, I get a timeout when I try to connect. No funky errors … just times out. I want to run a script on a schedule to connect to my printers and do some cleanup (download timelapse video then delete, clean off old models, etc). I can’t figure out why it won’t connect, My script uses the same port, forces the FTP over TLS, even auto accepts any cert but it just hangs and times out when I attempt to connect, I even verify the port is open before I attempt to connect. Here is my code:
import ftplib
import ssl
import socket
# Define FTP server details
ftp_server = '192.168.86.180'
ftp_user = 'bblp'
ftp_password = '35868996'
# Create an SSL context to accept any certificates
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
try:
# Check network connectivity
print(f"Checking connectivity to {ftp_server}...")
socket.create_connection((ftp_server, 990), timeout=10)
print("Network connectivity check passed.")
# Connect to the FTP server using implicit FTP over TLS with a timeout
ftp = ftplib.FTP_TLS(context=context)
print(f"Connecting to {ftp_server} on port 990...")
ftp.connect(ftp_server, 990, timeout=20) # Increase timeout to 20 seconds
ftp.login(ftp_user, ftp_password)
# Force the control connection to be encrypted
ftp.prot_p()
print("Connection established and secured with FTP over TLS")
# Perform FTP operations here
# Example: List directory contents
ftp.retrlines('LIST')
# Close the connection
ftp.quit()
except ftplib.all_errors as e:
print(f"FTP error: {e}")
except socket.timeout:
print("Connection timed out")
except Exception as e:
print(f"An error occurred: {e}")
FYI, I figured it out. I can now connect to my p1s via python and do any ftp command I want (including downloading files). Here is my sample script to connect and download the latest timelapse video. This makes sure it’s the latest video by checking to see if it has a corresponding thumbnail. If it doesn’t that means the timelapse is not finished (print is still active). Once it gets the newest file it. downloads it locally. This is just an example.
import ftplib
import ssl
import os
from datetime import datetime
class ImplicitFTP_TLS(ftplib.FTP_TLS):
"""FTP_TLS subclass that automatically wraps sockets in SSL to support implicit FTPS."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._sock = None
@property
def sock(self):
"""Return the socket."""
return self._sock
@sock.setter
def sock(self, value):
"""When modifying the socket, ensure that it is ssl wrapped."""
if value is not None and not isinstance(value, ssl.SSLSocket):
value = self.context.wrap_socket(value)
self._sock = value
from ftplib import all_errors
def parse_ftp_listing(line):
"""Parse a line from an FTP LIST command."""
parts = line.split(maxsplit=8)
if len(parts) < 9:
return None
return {
'permissions': parts[0],
'links': int(parts[1]),
'owner': parts[2],
'group': parts[3],
'size': int(parts[4]),
'month': parts[5],
'day': int(parts[6]),
'time_or_year': parts[7],
'name': parts[8]
}
def get_base_name(filename):
return filename.rsplit('.', 1)[0]
def parse_date(item):
"""Parse the date and time from the FTP listing item."""
try:
date_str = f"{item['month']} {item['day']} {item['time_or_year']}"
return datetime.strptime(date_str, "%b %d %H:%M")
except ValueError:
return None
ftp = ImplicitFTP_TLS()
ftp.set_pasv(True)
ftp.connect(host='192.168.86.180', port=990, timeout=5, source_address=None)
ftp.login('bblp', '35868996')
ftp.prot_p()
try:
tldirlist = []
tltndirlist = []
ftp.cwd('/timelapse')
ftp.retrlines('LIST', tldirlist.append)
tldirlist = [parse_ftp_listing(line) for line in tldirlist if parse_ftp_listing(line)]
ftp.cwd('/timelapse/thumbnail')
ftp.retrlines('LIST', tltndirlist.append)
tltndirlist = [parse_ftp_listing(line) for line in tltndirlist if parse_ftp_listing(line)]
# Compare the lists and create a new list with matching files (ignoring extensions)
tldirlist_dict = {get_base_name(item['name']): item for item in tldirlist}
tltndirlist_set = {get_base_name(item['name']) for item in tltndirlist}
matching_files = [tldirlist_dict[base_name] for base_name in tldirlist_dict if base_name in tltndirlist_set]
# Find the newest file based on the time and date fields
newest_file = None
newest_time = None
for item in matching_files:
file_time = parse_date(item)
if file_time and (newest_time is None or file_time > newest_time):
newest_time = file_time
newest_file = item
if newest_file:
print(f'Newest file: {newest_file["name"]}')
# Download the newest file
local_filename = os.path.join(os.getcwd(), newest_file["name"])
with open(local_filename, 'wb') as f:
ftp.retrbinary(f'RETR /timelapse/{newest_file["name"]}', f.write)
print(f'File downloaded: {local_filename}')
else:
print('No matching files found.')
except all_errors as ex:
print(ex)
finally:
ftp.quit()
print('FTP closed.')
I’m going to expand on this script to do clean-up. If anyone does some cool stuff, I’d appreciate it if they posted it so I can steal some of your ideas for my code.