W5500 CircuitPython 驱动测试知多少?

Adafruit CircuitPython 10.1.3 on 2026-02-21; VCC-GND YD-ESP32-S3 (N16R8) with ESP32S3

| 版本 | 日期 | 说明 |

|------|------|------|

| v1.0 | 2026-05 | 初始版本 |

| v1.1 | 2026-05 | 增加 Socket 状态机和 accept() 方法说明 |

| v1.2 | 2026-05 | 完善 SocketPool accept() 流程详解 |

| v1.3 | 2026-05 | 新增 W5500 硬件架构和 Socket 0-7 详细说明 |

| v1.4 | 2026-05 | 新增 Socket 0 MacRAW 详解、寄存器地址映射、TX/RX 缓冲区寻址;解释为什么 TelnetServer 不使用 Socket 0 |

| v1.5 | 2026-05 | 命令输出全面中文化;debug 命令增强显示 socket 编号和状态码(中文) |

| v1.6 | 2026-05 | 新增 SocketPool 完整测试日志及 Socket 状态变化时序图解 |

| v1.7 | 2026-05 | 新增 Socket 编号交换机制详解,明确 TelnetServerAlone 与 SocketPool 行为一致性 |

Telnet Server 测试文件说明

本项目包含两个 Telnet 服务器测试文件,用于演示在 CircuitPython 环境下使用 W5500 以太网控制器实现 Telnet 服务:

| 文件 | 实现方式 | 特点 |

|------|---------|------|

| `TelnetServerAlone.py` | 直接操作底层驱动 | 精细控制硬件资源,适合深入理解 W5500 工作原理 |

| `TelnetServerPool.py` | 使用 SocketPool 抽象层 | 标准 socket API,开发更便捷,代码更简洁 |

复制代码
# TelnetServerAlone.py - 独立运行的Telnet服务器
"""
Telnet服务器 - 直接使用W5500 Socket接口
仅支持W5500有线网卡
"""

import time
from adafruit_wiznet5k import WIZNET5K

SNSR_SOCK_ESTABLISHED = 0x17
SNSR_SOCK_LISTEN = 0x14
SNSR_SOCK_CLOSED = 0x00

class TelnetServer:
    """Telnet服务器"""

    def __init__(self, nic, port=23):
        self.nic = nic
        self.port = port
        self.listen_socket = None
        self.client_socket = None
        self.client_info = {}
        self.running = False

    def setup(self):
        """初始化Telnet服务器"""
        try:
            self.listen_socket = 1
            self.nic.socket_close(self.listen_socket)
            time.sleep(0.1)
            self.nic.socket_listen(self.listen_socket, self.port)
            time.sleep(0.1)
            status = self.nic.socket_status(self.listen_socket)
            if status == SNSR_SOCK_LISTEN:
                print("[Telnet] ✅ 监听端口 {} (socket {})".format(self.port, self.listen_socket))
                self.running = True
                return True
            else:
                print("[Telnet] ❌ 监听失败,状态: 0x{:02x}".format(status))
                return False
        except Exception as e:
            print("[Telnet] ❌ 初始化失败: {}".format(e))
            return False

    def accept_client(self):
        """接受新客户端"""
        if self.listen_socket is None:
            return False

        try:
            status = self.nic.socket_status(self.listen_socket)

            if status == SNSR_SOCK_ESTABLISHED:
                if self.client_socket is not None:
                    return False

                client_ip = self.nic.remote_ip(self.listen_socket)
                client_port = self.nic.remote_port(self.listen_socket)

                if client_ip == "0.0.0.0" or client_port == 0:
                    return False

                self.client_socket = self.listen_socket
                self.client_info = {
                    'ip': client_ip, 'port': client_port,
                    'start_time': time.monotonic(), 'data_count': 0,
                    'echo_enabled': True, 'line_buffer': ''
                }

                print("[Telnet] 客户端连接 {}:{} (socket={})".format(
                    client_ip, client_port, self.client_socket))

                result = self._send_welcome()
                if not result:
                    print("[Telnet] 发送欢迎消息失败")
                    self._close_client()
                    return False

                self._create_new_listen_socket()
                return True
        except Exception as e:
            print("[Telnet] 接受客户端失败: {}".format(e))
        return False

    def _create_new_listen_socket(self):
        """创建新的监听socket"""
        old_listen = self.listen_socket
        new_socket = (self.listen_socket + 1) % 8
        if new_socket == 0:
            new_socket = 2

        if new_socket == self.client_socket:
            new_socket = (new_socket + 1) % 8
            if new_socket == 0:
                new_socket = 2

        try:
            self.nic.socket_close(new_socket)
            time.sleep(0.2)
            self.nic.socket_listen(new_socket, self.port)
            time.sleep(0.1)
            status = self.nic.socket_status(new_socket)
            if status == SNSR_SOCK_LISTEN:
                self.listen_socket = new_socket
                print("[Telnet] 新监听socket: {} (client=socket={})".format(
                    new_socket, old_listen))
            else:
                print("[Telnet] 新监听socket失败,状态: 0x{:02x}".format(status))
        except Exception as e:
            print("[Telnet] 新监听socket异常: {}".format(e))

    def _send_welcome(self):
        """发送欢迎消息"""
        try:
            welcome = "欢迎使用 W5500 Telnet 服务器\r\n"
            welcome += "输入 'help' 查看可用命令\r\n"
            welcome += "输入 'exit' 或 'quit' 断开连接\r\n"
            if not self._send_data(welcome):
                return False
            if not self._send_data("> "):
                return False
            return True
        except Exception as e:
            print("[Telnet] _send_welcome异常: {}".format(e))
            return False

    def _send_prompt(self):
        """发送命令提示符"""
        return self._send_data("> ")

    def _send_data(self, data):
        """发送数据到客户端"""
        if self.client_socket is None:
            return False
        try:
            if isinstance(data, str):
                data = data.encode('utf-8')
            total_sent = 0
            data_len = len(data)
            while total_sent < data_len:
                remaining = data_len - total_sent
                chunk = data[total_sent:total_sent + min(1024, remaining)]
                sent = self.nic.socket_write(self.client_socket, chunk)
                if sent > 0:
                    total_sent += sent
                else:
                    time.sleep(0.01)
            return total_sent == data_len
        except Exception as e:
            print("[Telnet] _send_data异常: {} (client_socket={})".format(e, self.client_socket))
            self._close_client()
            return False

    def process_client(self):
        """处理客户端数据"""
        if self.client_socket is None:
            return

        try:
            status = self.nic.socket_status(self.client_socket)

            if status == 0x00:
                duration = time.monotonic() - self.client_info.get('start_time', time.monotonic())
                print("[Telnet] 客户端断开 (socket={}, {:.1f}秒)".format(
                    self.client_socket, duration))
                self._close_client()
                return

            if status != SNSR_SOCK_ESTABLISHED:
                print("[Telnet] 连接异常关闭 (socket={}, status=0x{:02x})".format(
                    self.client_socket, status))
                self._close_client()
                return

            available = self.nic.socket_available(self.client_socket)

            if available > 0:
                ret, data = self.nic.socket_read(self.client_socket, available)

                if ret > 0:
                    self.client_info['data_count'] = self.client_info.get('data_count', 0) + 1
                    self._process_data(data)
        except Exception as e:
            print("[Telnet] process_client异常: {}".format(e))
            print("[Telnet] 异常类型: {}".format(type(e).__name__))
            print("[Telnet] client_socket={}, listen_socket={}".format(
                self.client_socket, self.listen_socket))
            self._close_client()

    def _process_data(self, data):
        """处理接收到的数据"""
        if isinstance(data, str):
            byte_data = [ord(c) for c in data]
        else:
            byte_data = list(data)

        for byte in byte_data:
            if byte == 13 or byte == 10:
                current_cmd = self.client_info.get('line_buffer', '').strip()
                if current_cmd:
                    print("[Telnet] 命令: '{}'".format(current_cmd))
                    response = self._execute_command(current_cmd)
                    if response:
                        self._send_data(response + "\r\n")
                self._send_prompt()
                self.client_info['line_buffer'] = ''
            elif byte == 127 or byte == 8:
                buffer = self.client_info.get('line_buffer', '')
                if buffer:
                    self.client_info['line_buffer'] = buffer[:-1]
                    if self.client_info.get('echo_enabled', True):
                        self._send_data("\b \b")
            elif byte >= 32 and byte < 127:
                char = chr(byte)
                self.client_info['line_buffer'] = self.client_info.get('line_buffer', '') + char
                if self.client_info.get('echo_enabled', True):
                    self._send_data(char)
            elif byte == 3:
                self.client_info['line_buffer'] = ''

    def _execute_command(self, cmd):
        """执行命令"""
        cmd = cmd.strip().lower()
        if cmd == 'help':
            return self._get_help()
        elif cmd == 'status':
            return self._get_status()
        elif cmd == 'debug':
            return self._get_debug()
        elif cmd == 'uptime':
            return self._get_uptime()
        elif cmd == 'echo':
            self.client_info['echo_enabled'] = not self.client_info.get('echo_enabled', True)
            return "回显: " + ("开" if self.client_info.get('echo_enabled') else "关")
        elif cmd in ('exit', 'quit'):
            self._send_data("再见!\r\n")
            self._close_client()
            return None
        else:
            return "Unknown command: {}\r\nType 'help' for available commands".format(cmd)

    def _get_help(self):
        return "可用命令:\r\n  help   - 显示帮助\r\n  status - 服务器状态\r\n  debug  - 调试信息\r\n  uptime - 运行时间\r\n  echo   - 切换回显\r\n  exit   - 断开连接"

    def _get_status(self):
        uptime = time.monotonic() - self.client_info.get('start_time', time.monotonic())
        return "服务器状态:\r\n  端口: {}\r\n  客户端: {}\r\n  运行时间: {:.1f}秒\r\n  数据包: {}".format(
            self.port, 1 if self.client_socket else 0,
            uptime, self.client_info.get('data_count', 0))

    def _get_debug(self):
        debug = "=== 调试信息 ===\r\n"
        if self.listen_socket is not None:
            listen_status = self.nic.socket_status(self.listen_socket)
            listen_status_cn = self._status_to_chinese(listen_status)
            debug += "监听 socket: #{} 状态: {} (0x{:02x})\r\n".format(
                self.listen_socket, listen_status_cn, listen_status)
        else:
            debug += "监听 socket: 未启用\r\n"

        if self.client_socket is not None:
            client_status = self.nic.socket_status(self.client_socket)
            client_status_cn = self._status_to_chinese(client_status)
            debug += "客户端 socket: #{} 状态: {} (0x{:02x})\r\n".format(
                self.client_socket, client_status_cn, client_status)
            debug += "远程地址: {}:{}\r\n".format(
                self.client_info.get('ip', '?'),
                self.client_info.get('port', '?'))
        else:
            debug += "客户端 socket: 无连接\r\n"
        return debug

    def _status_to_chinese(self, status):
        status_map = {
            0x00: "关闭",
            0x13: "初始化",
            0x14: "监听中",
            0x15: "连接中",
            0x17: "已连接",
            0x1C: "等待关闭",
            0x22: "UDP模式",
        }
        return status_map.get(status, "未知(0x{:02x})".format(status))

    def _get_uptime(self):
        uptime = time.monotonic()
        hours = int(uptime // 3600)
        minutes = int((uptime % 3600) // 60)
        seconds = int(uptime % 60)
        return "运行时间: {}:{:02}:{:02}".format(hours, minutes, seconds)

    def _close_client(self):
        """关闭客户端连接"""
        if self.client_socket:
            try:
                self.nic.socket_close(self.client_socket)
            except:
                pass
            self.client_socket = None
            self.client_info = {}

    def run_once(self):
        """执行一次服务器循环"""
        if not self.running:
            return
        self.accept_client()
        self.process_client()

    def stop(self):
        """停止服务器"""
        self.running = False
        self._close_client()
        if self.listen_socket is not None:
            try:
                self.nic.socket_close(self.listen_socket)
            except:
                pass
            self.listen_socket = None


def init_w5500():
    """初始化W5500网络"""
    import board
    import busio
    import digitalio
    print("=" * 50)
    print("初始化W5500网络...")
    spi = busio.SPI(board.GPIO12, MOSI=board.GPIO11, MISO=board.GPIO13)
    cs = digitalio.DigitalInOut(board.GPIO10)
    rst = digitalio.DigitalInOut(board.GPIO14)
    nic = WIZNET5K(spi, cs, rst, is_dhcp=False,
                   mac=(0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED), debug=False)
    nic.ifconfig = ('172.16.30.75', '255.255.255.0', '172.16.30.254', '8.8.8.8')
    print("W5500初始化完成")
    print("IP: {}".format(nic.pretty_ip(nic.ip_address)))
    print("=" * 50)
    return nic


def main():
    nic = init_w5500()
    server = TelnetServer(nic, port=23)
    if server.setup():
        print("Telnet服务器已启动,监听端口 23")
        print("用Telnet客户端连接 172.16.30.75:23")
        print("按 Ctrl+C 停止")
        print("=" * 50)
        try:
            while True:
                server.run_once()
                time.sleep(0.01)
        except KeyboardInterrupt:
            print("\n停止服务器...")
    else:
        print("服务器启动失败")
    server.stop()
    print("服务器已停止")


if __name__ == "__main__":
    main()

"""
TelnetServerPool - 使用 SocketPool 抽象层的 Telnet 服务器测试
"""

import time

# SocketPool 抽象层
from lib.adafruit_wiznet5k.adafruit_wiznet5k_socketpool import SocketPool

# W5500 底层驱动
from lib.adafruit_wiznet5k import adafruit_wiznet5k


def init_w5500():
    """初始化W5500网络"""
    import board
    import busio
    import digitalio

    print("=" * 50)
    print("初始化W5500网络...")

    # 使用 SPI 连接 W5500 - 使用正确的 GPIO 引脚
    spi = busio.SPI(board.GPIO12, MOSI=board.GPIO11, MISO=board.GPIO13)
    cs = digitalio.DigitalInOut(board.GPIO10)
    rst = digitalio.DigitalInOut(board.GPIO14)

    try:
        nic = adafruit_wiznet5k.WIZNET5K(spi, cs, rst, is_dhcp=False,
                                       mac=(0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED), debug=False)
        nic.ifconfig = ('172.16.30.75', '255.255.255.0', '172.16.30.254', '8.8.8.8')
        print("W5500初始化完成")
        print("IP: {}".format(nic.pretty_ip(nic.ip_address)))
        print("=" * 50)
        return nic
    except Exception as e:
        print("W5500初始化失败: {}".format(e))
        raise


class TelnetServerPool:
    """使用 SocketPool 抽象层的 Telnet 服务器"""

    def __init__(self, nic, port=23):
        self.nic = nic
        self.port = port
        self.socket_pool = SocketPool(nic)
        self.listen_socket = None
        self.client_socket = None
        self.client_address = None
        self.client_info = {}
        self.running = False

    def setup(self):
        """初始化Telnet服务器"""
        try:
            # 使用 SocketPool 创建监听socket
            self.listen_socket = self.socket_pool.socket()
            # W5500 只有一个 IP,绑定到 None 使用默认 IP
            self.listen_socket.bind((None, self.port))
            self.listen_socket.listen()

            print("[Telnet] ✅ 监听端口 {} (SocketPool)".format(self.port))
            self.running = True
            return True
        except Exception as e:
            print("[Telnet] ❌ 初始化失败: {}".format(e))
            return False

    def accept_client(self):
        """接受新客户端连接"""
        if self.listen_socket is None or self.client_socket is not None:
            return False

        try:
            # 使用 accept() 接受连接 - 返回新的socket对象
            conn, addr = self.listen_socket.accept()
            self.client_socket = conn
            self.client_address = addr
            self.client_info = {
                'start_time': time.monotonic(),
                'line_buffer': '',
                'data_count': 0,
                'echo_enabled': True
            }
            print("[Telnet] 客户端连接 {}:{}".format(addr[0], addr[1]))
            self._send_welcome()
            self._send_prompt()
            return True
        except OSError as e:
            # 如果没有连接,OSError是正常的
            if e.errno != 11:  # EAGAIN
                print("[Telnet] accept异常: {}".format(e))
            return False

    def process_client(self):
        """处理客户端数据"""
        if self.client_socket is None:
            return

        try:
            # 检查连接状态
            if not self.client_socket._connected:
                self._close_client()
                return

            # 尝试接收数据
            try:
                data = self.client_socket.recv(1024)
            except OSError as e:
                if e.errno != 11:  # EAGAIN
                    raise
                return

            if data:
                self.client_info['data_count'] = self.client_info.get('data_count', 0) + 1
                self._process_data(data)
            else:
                # 客户端断开连接
                self._close_client()

        except Exception as e:
            print("[Telnet] process_client异常: {}".format(e))
            self._close_client()

    def _process_data(self, data):
        """处理接收到的数据"""
        if isinstance(data, str):
            byte_data = [ord(c) for c in data]
        else:
            byte_data = list(data)

        for byte in byte_data:
            if byte == 13 or byte == 10:
                current_cmd = self.client_info.get('line_buffer', '').strip()
                if current_cmd:
                    print("[Telnet] 命令: '{}'".format(current_cmd))
                    response = self._execute_command(current_cmd)
                    if response:
                        self._send_data(response + "\r\n")
                self._send_prompt()
                self.client_info['line_buffer'] = ''
            elif byte == 127 or byte == 8:
                buffer = self.client_info.get('line_buffer', '')
                if buffer:
                    self.client_info['line_buffer'] = buffer[:-1]
                    if self.client_info.get('echo_enabled', True):
                        self._send_data("\b \b")
            elif byte >= 32 and byte < 127:
                char = chr(byte)
                self.client_info['line_buffer'] = self.client_info.get('line_buffer', '') + char
                if self.client_info.get('echo_enabled', True):
                    self._send_data(char)
            elif byte == 3:
                self.client_info['line_buffer'] = ''

    def _execute_command(self, cmd):
        """执行命令"""
        cmd = cmd.strip().lower()

        if cmd == 'help':
            return self._get_help()
        elif cmd == 'status':
            return self._get_status()
        elif cmd == 'debug':
            return self._get_debug()
        elif cmd == 'uptime':
            return self._get_uptime()
        elif cmd == 'echo':
            self.client_info['echo_enabled'] = not self.client_info.get('echo_enabled', True)
            return "回显: " + ("开" if self.client_info.get('echo_enabled') else "关")
        elif cmd == 'exit' or cmd == 'quit':
            self._send_data("再见!\r\n")
            self._close_client()
            return None
        else:
            return "未知命令: {}\r\n输入 'help' 查看可用命令".format(cmd)

    def _get_help(self):
        return "可用命令:\r\n  help   - 显示帮助\r\n  status - 服务器状态\r\n  debug  - 调试信息\r\n  uptime - 运行时间\r\n  echo   - 切换回显\r\n  exit   - 断开连接"

    def _get_status(self):
        uptime = time.monotonic() - self.client_info.get('start_time', time.monotonic())
        return "服务器状态:\r\n  端口: {}\r\n  客户端: {}\r\n  运行时间: {:.1f}秒\r\n  数据包: {}".format(
            self.port, 1 if self.client_socket else 0,
            uptime, self.client_info.get('data_count', 0))

    def _get_debug(self):
        debug = "=== 调试信息 ===\r\n"

        if self.listen_socket is not None:
            try:
                listen_socknum = self.listen_socket._socknum
                listen_status = self.nic.socket_status(listen_socknum)
                listen_status_cn = self._status_to_chinese(listen_status)
                debug += "监听 socket: #{} 状态: {} (0x{:02x})\r\n".format(
                    listen_socknum, listen_status_cn, listen_status)
            except:
                debug += "监听 socket: 已开启\r\n"
        else:
            debug += "监听 socket: 未启用\r\n"

        if self.client_socket is not None:
            try:
                client_socknum = self.client_socket._socknum
                client_status = self.nic.socket_status(client_socknum)
                client_status_cn = self._status_to_chinese(client_status)
                debug += "客户端 socket: #{} 状态: {} (0x{:02x})\r\n".format(
                    client_socknum, client_status_cn, client_status)
                debug += "远程地址: {}:{}\r\n".format(
                    self.client_address[0] if self.client_address else '?',
                    self.client_address[1] if self.client_address else '?')
            except:
                debug += "客户端 socket: 已连接\r\n"
        else:
            debug += "客户端 socket: 无连接\r\n"
        return debug

    def _status_to_chinese(self, status):
        status_map = {
            0x00: "关闭",
            0x13: "初始化",
            0x14: "监听中",
            0x15: "连接中",
            0x17: "已连接",
            0x1C: "等待关闭",
            0x22: "UDP模式",
        }
        return status_map.get(status, "未知(0x{:02x})".format(status))

    def _get_uptime(self):
        uptime = time.monotonic()
        hours = int(uptime // 3600)
        minutes = int((uptime % 3600) // 60)
        seconds = int(uptime % 60)
        return "运行时间: {}:{:02}:{:02}".format(hours, minutes, seconds)

    def _send_welcome(self):
        """发送欢迎消息"""
        welcome = "欢迎使用 W5500 Telnet 服务器\r\n"
        welcome += "输入 'help' 查看可用命令\r\n"
        welcome += "输入 'exit' 或 'quit' 断开连接\r\n"
        self._send_data(welcome)
        self._send_prompt()

    def _send_prompt(self):
        """发送命令提示符"""
        return self._send_data("> ")

    def _send_data(self, data):
        """发送数据到客户端"""
        if self.client_socket is None:
            return False
        try:
            if isinstance(data, str):
                data = data.encode('utf-8')
            self.client_socket.send(data)
            return True
        except Exception as e:
            print("[Telnet] _send_data异常: {}".format(e))
            self._close_client()
            return False

    def _close_client(self):
        """关闭客户端连接"""
        if self.client_socket:
            try:
                duration = time.monotonic() - self.client_info.get('start_time', time.monotonic())
                print("[Telnet] 客户端断开 ({}:{}, {:.1f}秒)".format(
                    self.client_address[0] if self.client_address else '?',
                    self.client_address[1] if self.client_address else '?',
                    duration))
                self.client_socket.close()
            except Exception as e:
                print("[Telnet] 关闭客户端异常: {}".format(e))
            self.client_socket = None
            self.client_address = None
            self.client_info = {}

    def run_once(self):
        """执行一次服务器循环"""
        if not self.running:
            return
        self.accept_client()
        self.process_client()

    def stop(self):
        """停止服务器"""
        self.running = False
        self._close_client()
        if self.listen_socket:
            try:
                self.listen_socket.close()
            except:
                pass
            self.listen_socket = None


def main():
    """主函数"""
    try:
        nic = init_w5500()

        server = TelnetServerPool(nic, port=23)
        if not server.setup():
            return

        print("Telnet服务器已启动,监听端口 {}".format(server.port))
        print("用Telnet客户端连接 {}:{}".format(nic.pretty_ip(nic.ip_address), server.port))
        print("按 Ctrl+C 停止")
        print("=" * 50)

        while True:
            server.run_once()
            time.sleep(0.01)

    except KeyboardInterrupt:
        print("\n[Telnet] 服务器停止")
    except Exception as e:
        print("[Telnet] 异常: {}".format(e))


if __name__ == "__main__":
    main()

# SPDX-FileCopyrightText: 2010 WIZnet
# SPDX-FileCopyrightText: 2010 Arduino LLC
# SPDX-FileCopyrightText: 2008 Bjoern Hartmann
# SPDX-FileCopyrightText: 2018 Paul Stoffregen
# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
# SPDX-FileCopyrightText: 2021 Patrick Van Oosterwijck
# SPDX-FileCopyrightText: 2021 Adam Cummick
# SPDX-FileCopyrightText: 2023 Martin Stephens
#
# SPDX-License-Identifier: MIT
"""
`adafruit_wiznet5k`
================================================================================

Pure-Python interface for WIZNET 5k ethernet modules.

* Author(s): WIZnet, Arduino LLC, Bjoern Hartmann, Paul Stoffregen, Brent Rubell,
  Patrick Van Oosterwijck, Martin Stephens

Implementation Notes
--------------------

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
  https://github.com/adafruit/circuitpython/releases

* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""

from __future__ import annotations

try:
    from typing import TYPE_CHECKING, Optional, Tuple, Union

    if TYPE_CHECKING:
        import busio
        import digitalio
        from circuitpython_typing import WriteableBuffer

        IpAddress4Raw = Union[bytes, Tuple[int, int, int, int]]
        MacAddressRaw = Union[bytes, Tuple[int, int, int, int, int, int]]
except ImportError:
    pass

__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k.git"

import gc
import time
from random import randint

from adafruit_bus_device.spi_device import SPIDevice
from adafruit_ticks import ticks_diff, ticks_ms
from micropython import const

import adafruit_wiznet5k.adafruit_wiznet5k_dhcp as dhcp
import adafruit_wiznet5k.adafruit_wiznet5k_dns as dns
from adafruit_wiznet5k.adafruit_wiznet5k_debug import debug_msg

# *** Wiznet Common Registers ***
_REG_MR = {"w5100s": const(0x0000), "w5500": const(0x0000)}
# Gateway IPv4 Address.
_REG_GAR = {
    "w5100s": const(0x0001),
    "w5500": const(0x0001),
    "w6100": const(0x4130),
    "w6300": const(0x4130),
}
# Subnet Mask Address
_REG_SUBR = {
    "w5100s": const(0x0005),
    "w5500": const(0x0005),
    "w6100": const(0x4134),
    "w6300": const(0x4134),
}
# Chip version.
_REG_VERSIONR = {
    "w5100s": const(0x0080),
    "w5500": const(0x0039),
    "w6100": const(0x0000),
    "w6300": const(0x0000),
}
# Source Hardware Address
_REG_SHAR = {
    "w5100s": const(0x0009),
    "w5500": const(0x0009),
    "w6100": const(0x4120),
    "w6300": const(0x4120),
}
# Source IP Address
_REG_SIPR = {
    "w5100s": const(0x000F),
    "w5500": const(0x000F),
    "w6100": const(0x4138),
    "w6300": const(0x4138),
}
# Register with link status flag (PHYCFGR for 5xxxx, PHYSR for 6100).
_REG_LINK_FLAG = {
    "w5100s": const(0x003C),
    "w5500": const(0x002E),
    "w6100": const(0x3000),
    "w6300": const(0x3000),
}
_REG_RCR = {
    "w5100s": const(0x0019),
    "w5500": const(0x001B),
    "w6100": const(0x4204),
    "w6300": const(0x4204),
}
_REG_RTR = {
    "w5100s": const(0x0017),
    "w5500": const(0x0019),
    "w6100": const(0x4200),
    "w6300": const(0x4200),
}

# *** Wiznet Socket Registers ***
# Socket n Mode.
_REG_SNMR = const(0x0000)
# Socket n Command.
_REG_SNCR = {
    "w5100s": const(0x0001),
    "w5500": const(0x0001),
    "w6100": const(0x0010),
    "w6300": const(0x0010),
}
# Socket n Interrupt.
_REG_SNIR = {
    "w5100s": const(0x0002),
    "w5500": const(0x0002),
    "w6100": const(0x0020),
    "w6300": const(0x0020),
}
# Socket n Status.
_REG_SNSR = {
    "w5100s": const(0x0003),
    "w5500": const(0x0003),
    "w6100": const(0x0030),
    "w6300": const(0x0030),
}
# Socket n Source Port.
_REG_SNPORT = {
    "w5100s": const(0x0004),
    "w5500": const(0x0004),
    "w6100": const(0x0114),
    "w6300": const(0x0114),
}
# Destination IPv4 Address.
_REG_SNDIPR = {
    "w5100s": const(0x000C),
    "w5500": const(0x000C),
    "w6100": const(0x0120),
    "w6300": const(0x0120),
}
# Destination Port.
_REG_SNDPORT = {
    "w5100s": const(0x0010),
    "w5500": const(0x0010),
    "w6100": const(0x0140),
    "w6300": const(0x0140),
}
# RX Free Size.
_REG_SNRX_RSR = {
    "w5100s": const(0x0026),
    "w5500": const(0x0026),
    "w6100": const(0x0224),
    "w6300": const(0x0224),
}
# Read Size Pointer.
_REG_SNRX_RD = {
    "w5100s": const(0x0028),
    "w5500": const(0x0028),
    "w6100": const(0x0228),
    "w6300": const(0x0228),
}
# Socket n TX Free Size.
_REG_SNTX_FSR = {
    "w5100s": const(0x0020),
    "w5500": const(0x0020),
    "w6100": const(0x0204),
    "w6300": const(0x0204),
}
# TX Write Pointer.
_REG_SNTX_WR = {
    "w5100s": const(0x0024),
    "w5500": const(0x0024),
    "w6100": const(0x020C),
    "w6300": const(0x020C),
}

# SNSR Commands
SNSR_SOCK_CLOSED = const(0x00)
_SNSR_SOCK_INIT = const(0x13)
SNSR_SOCK_LISTEN = const(0x14)
_SNSR_SOCK_SYNSENT = const(0x15)
SNSR_SOCK_SYNRECV = const(0x16)
SNSR_SOCK_ESTABLISHED = const(0x17)
SNSR_SOCK_FIN_WAIT = const(0x18)
_SNSR_SOCK_CLOSING = const(0x1A)
SNSR_SOCK_TIME_WAIT = const(0x1B)
SNSR_SOCK_CLOSE_WAIT = const(0x1C)
_SNSR_SOCK_LAST_ACK = const(0x1D)
_SNSR_SOCK_UDP = const(0x22)
_SNSR_SOCK_IPRAW = const(0x32)
_SNSR_SOCK_MACRAW = const(0x42)
_SNSR_SOCK_PPPOE = const(0x5F)

# Sock Commands (CMD)
_CMD_SOCK_OPEN = const(0x01)
_CMD_SOCK_LISTEN = const(0x02)
_CMD_SOCK_CONNECT = const(0x04)
_CMD_SOCK_DISCON = const(0x08)
_CMD_SOCK_CLOSE = const(0x10)
_CMD_SOCK_SEND = const(0x20)
_CMD_SOCK_SEND_MAC = const(0x21)
_CMD_SOCK_SEND_KEEP = const(0x22)
_CMD_SOCK_RECV = const(0x40)

# Socket n Interrupt Register
_SNIR_SEND_OK = const(0x10)
SNIR_TIMEOUT = const(0x08)
_SNIR_RECV = const(0x04)
SNIR_DISCON = const(0x02)
_SNIR_CON = const(0x01)

_CH_SIZE = const(0x100)
_SOCK_SIZE = const(0x800)  # MAX W5k socket size
_SOCK_MASK = const(0x7FF)
# Register commands
_MR_RST = const(0x80)  # Mode Register RST
# Socket mode register
_SNMR_CLOSE = const(0x00)
_SNMR_TCP = const(0x21)
SNMR_UDP = const(0x02)
_SNMR_IPRAW = const(0x03)
_SNMR_MACRAW = const(0x04)
_SNMR_PPPOE = const(0x05)

_MAX_PACKET = const(4000)
_LOCAL_PORT = const(0x400)
# Default hardware MAC address
_DEFAULT_MAC = "DE:AD:BE:EF:FE:ED"

# Maximum number of sockets to support, differs between chip versions.
_MAX_SOCK_NUM = {
    "w5100s": const(0x04),
    "w5500": const(0x08),
    "w6100": const(0x08),
    "w6300": const(0x08),
}
_SOCKET_INVALID = const(0xFF)


def _unprettyfy(data: str, seperator: str, correct_length: int) -> bytes:
    """Helper for converting . or : delimited strings to bytes objects."""
    data = bytes(int(x) for x in data.split(seperator))
    if len(data) == correct_length:
        return data
    raise ValueError("Invalid IP or MAC address.")


class WIZNET5K:
    """Interface for WIZNET5K module."""

    _sockets_reserved = []

    def __init__(
        self,
        spi_bus: busio.SPI,
        cs: digitalio.DigitalInOut,
        reset: Optional[digitalio.DigitalInOut] = None,
        is_dhcp: bool = True,
        mac: Union[MacAddressRaw, str] = _DEFAULT_MAC,
        hostname: Optional[str] = None,
        debug: bool = False,
        spi_baudrate: int = 8000000,
    ) -> None:
        """
        :param busio.SPI spi_bus: The SPI bus the Wiznet module is connected to.
        :param digitalio.DigitalInOut cs: Chip select pin.
        :param digitalio.DigitalInOut reset: Optional reset pin, defaults to None.
        :param bool is_dhcp: Whether to start DHCP automatically or not, defaults to True.
        :param Union[MacAddressRaw, str] mac: The Wiznet's MAC Address, defaults to
            (0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED).
        :param str hostname: The desired hostname, with optional {} to fill in the MAC
            address, defaults to None.
        :param bool debug: Enable debugging output, defaults to False.
        :param int spi_baudrate: The SPI clock frequency, defaults to 8MHz.
            Might not be the actual baudrate, dependent on the hardware.
        """
        self._debug = debug
        self._chip_type = None
        self._device = SPIDevice(spi_bus, cs, baudrate=spi_baudrate, polarity=0, phase=0)
        # init c.s.
        self._cs = cs
        # Socket status tracking for debug
        self._last_snsr = {}

        # Reset wiznet module prior to initialization.
        if reset:
            debug_msg("* Resetting WIZnet chip", self._debug)
            reset.switch_to_output()
            reset.value = False
            time.sleep(0.1)
            reset.value = True
            time.sleep(5)

        # Setup chip_select pin.
        time.sleep(1)
        self._cs.switch_to_output()
        self._cs.value = 1

        # Buffer for reading params from module
        self._pbuff = bytearray(8)
        self._rxbuf = bytearray(_MAX_PACKET)

        # attempt to initialize the module
        self._ch_base_msb = 0
        self._src_ports_in_use = []
        self._wiznet_chip_init()

        # Set MAC address
        self.mac_address = mac
        self.src_port = 0
        self._dns = b"\x00\x00\x00\x00"
        # udp related
        self.udp_from_ip = [b"\x00\x00\x00\x00"] * self.max_sockets
        self.udp_from_port = [0] * self.max_sockets

        # Wait to give the Ethernet link to initialise.
        start_time = ticks_ms()
        timeout = 5000
        while ticks_diff(ticks_ms(), start_time) < timeout:
            if self.link_status:
                break
            debug_msg("Ethernet link is down...", self._debug)
            time.sleep(0.5)
        self._dhcp_client = None

        # Set DHCP
        if is_dhcp:
            self.set_dhcp(hostname)

    def set_dhcp(self, hostname: Optional[str] = None) -> None:
        """
        Initialize the DHCP client and attempt to retrieve and set network
        configuration from the DHCP server.

        :param Optional[str] hostname: The desired hostname for the DHCP server with
            optional {} to fill in the MAC address, defaults to None.

        :raises RuntimeError: If DHCP lease cannot be established.
        """
        debug_msg("* Initializing DHCP", self._debug)
        self._dhcp_client = dhcp.DHCP(self, self.mac_address, hostname, self._debug)
        if self._dhcp_client.request_dhcp_lease():
            debug_msg(
                "Found DHCP Server:\nIP: {}\n  Subnet Mask: {}\n  GW Addr: {}"
                "\n  DNS Server: {}".format(*self.ifconfig),
                self._debug,
            )
        else:
            self._dhcp_client = None
            raise RuntimeError("Failed to configure DHCP Server!")

    def maintain_dhcp_lease(self) -> None:
        """Maintain the DHCP lease."""
        if self._dhcp_client:
            self._dhcp_client.maintain_dhcp_lease()

    def get_host_by_name(self, hostname: str) -> bytes:
        """
        Convert a hostname to a packed 4-byte IP Address.

        :param str hostname: The host name to be converted.

        :return bytes: The IPv4 address as a 4 byte array.

        :raises RuntimeError: If the DNS lookup fails.
        """
        debug_msg("Get host by name", self._debug)
        if isinstance(hostname, str):
            hostname = bytes(hostname, "utf-8")
        # Return IP assigned by DHCP
        _dns_client = dns.DNS(self, self.pretty_ip(bytearray(self._dns)), debug=self._debug)
        ipv4 = _dns_client.gethostbyname(hostname)
        debug_msg(f"* Resolved IP: {ipv4}", self._debug)
        if ipv4 == -1:
            raise RuntimeError("Failed to resolve hostname!")
        return ipv4

    @property
    def max_sockets(self) -> int:
        """
        Maximum number of sockets supported by chip.

        :return int: Maximum supported sockets.
        """
        return _MAX_SOCK_NUM[self._chip_type]

    @property
    def chip(self) -> str:
        """
        Ethernet controller chip type.

        :return str: The chip type.
        """
        return self._chip_type

    @property
    def ip_address(self) -> bytes:
        """
        Configured IP address for the WIZnet Ethernet hardware.

        :return bytes: IP address as four bytes.
        """
        if self._chip_type == "w6300":
            return self._read(_REG_SIPR[self._chip_type], 0x80, 4)
        else:
            return self._read(_REG_SIPR[self._chip_type], 0x00, 4)

    @property
    def ipv4_address(self) -> str:
        """
        Configured IP address for the WIZnet Ethernet hardware.

        :return str: The IP address (a string of the form '255.255.255.255')
        """
        return self.pretty_ip(self.ip_address)

    @staticmethod
    def pretty_ip(ipv4: bytes) -> str:
        """
        Convert a 4 byte IP address to a dotted-quad string for printing.

        :param bytearray ipv4: A four byte IP address.

        :return str: The IP address (a string of the form '255.255.255.255').

        :raises ValueError: If IP address is not 4 bytes.
        """
        if len(ipv4) != 4:
            raise ValueError("Wrong length for IPv4 address.")
        return ".".join(f"{byte}" for byte in ipv4)

    @staticmethod
    def unpretty_ip(ipv4: str) -> bytes:
        """
        Convert a dotted-quad string to a four byte IP address.

        :param str ipv4: IPv4 address (a string of the form '255.255.255.255') to be converted.

        :return bytes: IPv4 address in four bytes.

        :raises ValueError: If IPv4 address is not 4 bytes.
        """
        return _unprettyfy(ipv4, ".", 4)

    @property
    def mac_address(self) -> bytes:
        """
        The WIZnet Ethernet hardware MAC address.

        :return bytes: Six byte MAC address.
        """
        if self._chip_type == "w6300":
            return self._read(_REG_SHAR[self._chip_type], 0x80, 6)
        else:
            return self._read(_REG_SHAR[self._chip_type], 0x00, 6)

    @mac_address.setter
    def mac_address(self, address: Union[MacAddressRaw, str]) -> None:
        """
        Set the WIZnet hardware MAC address.

        :param Union[MacAddressRaw, str] address: A hardware MAC address.

        :raises ValueError: If the MAC address is invalid
        """
        try:
            # 检查 address 是否为字符串
            if isinstance(address, str):
                address = [int(x, 16) for x in address.split(":")]
        except TypeError:
            pass
        try:
            if len(address) != 6:
                raise ValueError()
            # Bytes conversion will raise ValueError if values are not 0-255
            if self._chip_type == "w6300":
                self._write(_REG_SHAR[self._chip_type], 0xA0, bytes(address))
            else:
                self._write(_REG_SHAR[self._chip_type], 0x04, bytes(address))
        except ValueError:
            raise ValueError("Invalid MAC address.")

    @staticmethod
    def pretty_mac(mac: bytes) -> str:
        """
        Convert a bytes MAC address to a ':' seperated string for display.

        :param bytes mac: The MAC address.

        :return str: Mac Address in the form 00:00:00:00:00:00

        :raises ValueError: If MAC address is not 6 bytes.
        """
        if len(mac) != 6:
            raise ValueError("Incorrect length for MAC address.")
        return ":".join(f"{byte:02x}" for byte in mac)

    def remote_ip(self, socket_num: int) -> str:
        """
        IP address of the host which sent the current incoming packet.

        :param int socket_num: ID number of the socket to check.

        :return str: The IPv4 address.

        :raises ValueError: If the socket number is out of range.
        """
        self._sock_num_in_range(socket_num)
        for octet in range(4):
            self._pbuff[octet] = self._read_socket_register(
                socket_num, _REG_SNDIPR[self._chip_type] + octet
            )
        return self.pretty_ip(self._pbuff[:4])

    def remote_port(self, socket_num: int) -> int:
        """
        Port number of the host which sent the current incoming packet.

        :param int socket_num: ID number of the socket to check.

        :return int: The incoming port number of the socket connection.

        :raises ValueError: If the socket number is out of range.
        """
        self._sock_num_in_range(socket_num)
        return self._read_two_byte_sock_reg(socket_num, _REG_SNDPORT[self._chip_type])

    @property
    def link_status(self) -> bool:
        """
        Physical hardware (PHY) connection status.

        Whether the WIZnet hardware is physically connected to an Ethernet network.

        :return bool: True if the link is up, False if the link is down.
        """
        if self._chip_type == "w6300":
            # w6300 uses PHYCFGR register for link status
            return bool(
                int.from_bytes(self._read(_REG_LINK_FLAG[self._chip_type], 0x80), "big") & 0x01
            )
        else:
            return bool(
                int.from_bytes(self._read(_REG_LINK_FLAG[self._chip_type], 0x00), "big") & 0x01
            )

    @property
    def ifconfig(self) -> Tuple[bytes, bytes, bytes, bytes]:
        """
        Network configuration information.

        :return Tuple[bytes, bytes, bytes, bytes]: The IP address, subnet mask, gateway
            address and DNS server address.
        """
        if self._chip_type == "w6300":
            return (
                self.ip_address,
                self._read(_REG_SUBR[self._chip_type], 0x80, 4),
                self._read(_REG_GAR[self._chip_type], 0x80, 4),
                self._dns,
            )
        else:
            return (
                self.ip_address,
                self._read(_REG_SUBR[self._chip_type], 0x00, 4),
                self._read(_REG_GAR[self._chip_type], 0x00, 4),
                self._dns,
            )

    @ifconfig.setter
    def ifconfig(
        self, params: Tuple[IpAddress4Raw, IpAddress4Raw, IpAddress4Raw, IpAddress4Raw]
    ) -> None:
        """
        Set network configuration.

        :param Tuple[Address4Bytes, Address4Bytes, Address4Bytes, Address4Bytes]: Configuration
            settings - (ip_address, subnet_mask, gateway_address, dns_server).
        """
        processed_params = []
        for param in params:
            # 检查 param 是否为字符串
            if isinstance(param, str):
                # 将字符串转换为字节对象
                param = self.unpretty_ip(param)
            processed_params.append(param)
        
        for param in processed_params:
            if len(param) != 4:
                raise ValueError("IPv4 address must be 4 bytes.")
        ip_address, subnet_mask, gateway_address, dns_server = processed_params

        if self._chip_type == "w6300":
            self._write(_REG_SIPR[self._chip_type], 0xA0, bytes(ip_address))
        else:
            self._write(_REG_SIPR[self._chip_type], 0x04, bytes(ip_address))
        if self._chip_type == "w6300":
            self._write(_REG_SUBR[self._chip_type], 0xA0, bytes(subnet_mask))
        else:
            self._write(_REG_SUBR[self._chip_type], 0x04, bytes(subnet_mask))
        if self._chip_type == "w6300":
            self._write(_REG_GAR[self._chip_type], 0xA0, bytes(gateway_address))
        else:
            self._write(_REG_GAR[self._chip_type], 0x04, bytes(gateway_address))

        self._dns = bytes(dns_server)

    # *** Public Socket Methods ***

    def socket_available(self, socket_num: int, sock_type: int = _SNMR_TCP) -> int:
        """
        Number of bytes available to be read from the socket.

        :param int socket_num: Socket to check for available bytes.
        :param int sock_type: Socket type. Use SNMR_TCP for TCP or SNMR_UDP for UDP,
            defaults to SNMR_TCP.

        :return int: Number of bytes available to read.

        :raises ValueError: If the socket number is out of range.
        :raises ValueError: If the number of bytes on a UDP socket is negative.
        """
        self._sock_num_in_range(socket_num)

        number_of_bytes = self._get_rx_rcv_size(socket_num)
        if self._read_snsr(socket_num) == SNMR_UDP:
            number_of_bytes -= 8  # Subtract UDP header from packet size.
        if number_of_bytes < 0:
            raise ValueError("Negative number of bytes found on socket.")
        if self._debug and number_of_bytes > 0:
            print("[LIB_DEBUG] socket_available: sock={}, bytes={}".format(socket_num, number_of_bytes))
        return number_of_bytes

    def socket_status(self, socket_num: int) -> int:
        """
        Socket connection status.

        Can be: SNSR_SOCK_CLOSED, SNSR_SOCK_INIT, SNSR_SOCK_LISTEN, SNSR_SOCK_SYNSENT,
        SNSR_SOCK_SYNRECV, SNSR_SYN_SOCK_ESTABLISHED, SNSR_SOCK_FIN_WAIT,
        SNSR_SOCK_CLOSING, SNSR_SOCK_TIME_WAIT, SNSR_SOCK_CLOSE_WAIT, SNSR_LAST_ACK,
        SNSR_SOCK_UDP, SNSR_SOCK_IPRAW, SNSR_SOCK_MACRAW, SNSR_SOCK_PPOE.

        :param int socket_num: ID of socket to check.

        :return int: The connection status.
        """
        return self._read_snsr(socket_num)

    def socket_connect(
        self,
        socket_num: int,
        dest: IpAddress4Raw,
        port: int,
        conn_mode: int = _SNMR_TCP,
    ) -> int:
        """
        Open and verify a connection from a socket to a destination IPv4 address
        or hostname. A TCP connection is made by default. A UDP connection can also
        be made.

        :param int socket_num: ID of the socket to be connected.
        :param IpAddress4Raw dest: The destination as a host name or IP address.
        :param int port: Port to connect to (0 - 65,535).
        :param int conn_mode: The connection mode. Use SNMR_TCP for TCP or SNMR_UDP for UDP,
            defaults to SNMR_TCP.

        :raises ValueError: if the socket number is out of range.
        :raises ConnectionError: If the connection to the socket cannot be established.
        """
        self._sock_num_in_range(socket_num)
        self._check_link_status()
        debug_msg(
            f"W5K socket connect, protocol={conn_mode}, port={port}, ip={self.pretty_ip(dest)}",
            self._debug,
        )
        # initialize a socket and set the mode
        self.socket_open(socket_num, conn_mode=conn_mode)
        # set socket destination IP and port
        self._write_sndipr(socket_num, dest)
        self._write_sndport(socket_num, port)
        self._write_sncr(socket_num, _CMD_SOCK_CONNECT)

        if conn_mode == _SNMR_TCP:
            # wait for tcp connection establishment
            while self.socket_status(socket_num) != SNSR_SOCK_ESTABLISHED:
                time.sleep(0.001)
                debug_msg(f"SNSR: {self.socket_status(socket_num)}", self._debug)
                if self.socket_status(socket_num) == SNSR_SOCK_CLOSED:
                    raise ConnectionError("Failed to establish connection.")
        return 1

    def get_socket(self, *, reserve_socket=False) -> int:
        """
        Request, allocate and return a socket from the WIZnet 5k chip.

        Cycle through the sockets to find the first available one. If the called with
        reserve_socket=True, update the list of reserved sockets (intended to be used with
        socket.socket()). Note that reserved sockets must be released by calling
        release_socket() once they are no longer needed.

        If all sockets are reserved, no sockets are available for DNS calls, etc. Therefore,
        one socket cannot be reserved. Since socket 0 is the only socket that is capable of
        operating in MacRAW mode, it is the non-reservable socket.

        :param bool reserve_socket: Whether to reserve the socket.

        :returns int: The first available socket.

        :raises RuntimeError: If no socket is available.
        """
        debug_msg("*** Get socket.", self._debug)
        # Prefer socket zero for none reserved calls as it cannot be reserved.
        if not reserve_socket and self.socket_status(0) == SNSR_SOCK_CLOSED:
            debug_msg("Allocated socket # 0", self._debug)
            return 0
        # Then check the other sockets.

        #  Call garbage collection to encourage socket.__del__() be called to on any
        #  destroyed instances. Not at all guaranteed to work!
        gc.collect()
        debug_msg(f"Reserved sockets: {WIZNET5K._sockets_reserved}", self._debug)

        for socket_number, reserved in enumerate(WIZNET5K._sockets_reserved, start=1):
            if not reserved and self.socket_status(socket_number) == SNSR_SOCK_CLOSED:
                if reserve_socket:
                    WIZNET5K._sockets_reserved[socket_number - 1] = True
                    debug_msg(
                        f"Allocated socket # {socket_number}.",
                        self._debug,
                    )
                return socket_number
        raise RuntimeError("All sockets in use.")

    def release_socket(self, socket_number):
        """
        Update the socket reservation list when a socket is no longer reserved.

        :param int socket_number: The socket to release.

        :raises ValueError: If the socket number is out of range.
        """
        self._sock_num_in_range(socket_number)
        WIZNET5K._sockets_reserved[socket_number - 1] = False

    def socket_listen(self, socket_num: int, port: int, conn_mode: int = _SNMR_TCP) -> None:
        """
        Listen on a socket's port.

        :param int socket_num: ID of socket to listen on.
        :param int port: Port to listen on (0 - 65,535).
        :param int conn_mode: Connection mode SNMR_TCP for TCP or SNMR_UDP for
            UDP, defaults to SNMR_TCP.

        :raises ValueError: If the socket number is out of range.
        :raises ConnectionError: If the Ethernet link is down.
        :raises RuntimeError: If unable to connect to a hardware socket.
        """
        self._sock_num_in_range(socket_num)
        self._check_link_status()
        debug_msg(
            f"* Listening on port={port}, ip={self.pretty_ip(self.ip_address)}",
            self._debug,
        )
        # Initialize a socket and set the mode
        self.src_port = port
        self.socket_open(socket_num, conn_mode=conn_mode)
        self.src_port = 0
        # Send listen command
        self._write_sncr(socket_num, _CMD_SOCK_LISTEN)
        # Wait until ready
        status = SNSR_SOCK_CLOSED
        while status not in {
            SNSR_SOCK_LISTEN,
            SNSR_SOCK_ESTABLISHED,
            _SNSR_SOCK_UDP,
        }:
            status = self._read_snsr(socket_num)
            if status == SNSR_SOCK_CLOSED:
                raise RuntimeError("Listening socket closed.")

    def socket_accept(self, socket_num: int) -> Tuple[int, Tuple[str, int]]:
        """
        Destination IPv4 address and port from an incoming connection.

        Return the next socket number so listening can continue, along with
        the IP address and port of the incoming connection.

        :param int socket_num: Socket number with connection to check.

        :return Tuple[int, Tuple[Union[str, bytearray], Union[int, bytearray]]]:
            If successful, the next (socket number, (destination IP address, destination port)).

        :raises ValueError: If the socket number is out of range.
        """
        self._sock_num_in_range(socket_num)
        dest_ip = self.remote_ip(socket_num)
        dest_port = self.remote_port(socket_num)
        next_socknum = self.get_socket()
        debug_msg(
            f"Dest is ({dest_ip}, {dest_port}), Next listen socknum is #{next_socknum}",
            self._debug,
        )
        return next_socknum, (dest_ip, dest_port)

    def socket_open(self, socket_num: int, conn_mode: int = _SNMR_TCP) -> None:
        """
        Open an IP socket.

        The socket may connect via TCP or UDP protocols.

        :param int socket_num: The socket number to open.
        :param int conn_mode: The protocol to use. Use SNMR_TCP for TCP or SNMR_UDP for \
            UDP, defaults to SNMR_TCP.

        :raises ValueError: If the socket number is out of range.
        :raises ConnectionError: If the Ethernet link is down or no connection to socket.
        :raises RuntimeError: If unable to open a socket in UDP or TCP mode.
        """
        self._sock_num_in_range(socket_num)
        self._check_link_status()
        debug_msg(f"*** Opening socket {socket_num}", self._debug)
        if self._read_snsr(socket_num) not in {
            SNSR_SOCK_CLOSED,
            SNSR_SOCK_TIME_WAIT,
            SNSR_SOCK_FIN_WAIT,
            SNSR_SOCK_CLOSE_WAIT,
            _SNSR_SOCK_CLOSING,
            _SNSR_SOCK_UDP,
        }:
            raise ConnectionError("Failed to initialize a connection with the socket.")
        debug_msg(f"* Opening W5k Socket, protocol={conn_mode}", self._debug)
        time.sleep(0.00025)

        self._write_snmr(socket_num, conn_mode)
        self.write_snir(socket_num, 0xFF)

        if self.src_port > 0:
            # write to socket source port
            self._write_sock_port(socket_num, self.src_port)
        else:
            s_port = randint(49152, 65535)
            while s_port in self._src_ports_in_use:
                s_port = randint(49152, 65535)
            self._write_sock_port(socket_num, s_port)
            self._src_ports_in_use[socket_num] = s_port

        # open socket
        self._write_sncr(socket_num, _CMD_SOCK_OPEN)
        if self._read_snsr(socket_num) not in {_SNSR_SOCK_INIT, _SNSR_SOCK_UDP}:
            raise RuntimeError("Could not open socket in TCP or UDP mode.")

    def socket_close(self, socket_num: int) -> None:
        """
        Close a socket.

        :param int socket_num: The socket to close.

        :raises ValueError: If the socket number is out of range.
        """
        debug_msg(f"*** Closing socket {socket_num}", self._debug)
        self._sock_num_in_range(socket_num)
        self._write_sncr(socket_num, _CMD_SOCK_CLOSE)
        debug_msg("  Waiting for socket to close...", self._debug)
        start = ticks_ms()
        timeout = 5000
        while self._read_snsr(socket_num) != SNSR_SOCK_CLOSED:
            if ticks_diff(ticks_ms(), start) > timeout:
                raise RuntimeError(
                    f"Wiznet5k failed to close socket, status = {self._read_snsr(socket_num)}."
                )
            time.sleep(0.0001)
        debug_msg("  Socket has closed.", self._debug)

    def socket_disconnect(self, socket_num: int) -> None:
        """
        Disconnect a TCP or UDP connection.

        :param int socket_num: The socket to close.

        :raises ValueError: If the socket number is out of range.
        """
        debug_msg(f"*** Disconnecting socket {socket_num}", self._debug)
        self._sock_num_in_range(socket_num)
        self._write_sncr(socket_num, _CMD_SOCK_DISCON)

    def socket_read(self, socket_num: int, length: int) -> Tuple[int, bytes]:
        """
        Read data from a hardware socket. Called directly by TCP socket objects and via
        read_udp() for UDP socket objects.

        :param int socket_num: The socket to read data from.
        :param int length: The number of bytes to read from the socket.

        :returns Tuple[int, bytes]: If the read was successful then the first
            item of the tuple is the length of the data and the second is the data.
            If the read was unsuccessful then 0, b"" is returned.

        :raises ValueError: If the socket number is out of range.
        :raises ConnectionError: If the Ethernet link is down.
        :raises RuntimeError: If the socket connection has been lost.
        """
        self._sock_num_in_range(socket_num)
        self._check_link_status()

        # Check if there is data available on the socket
        bytes_on_socket = self._get_rx_rcv_size(socket_num)
        debug_msg(f"Bytes avail. on sock: {bytes_on_socket}", self._debug)
        if bytes_on_socket:
            bytes_on_socket = length if bytes_on_socket > length else bytes_on_socket
            debug_msg(f"* Processing {bytes_on_socket} bytes of data", self._debug)
            # Read the starting save address of the received data.
            pointer = self._read_snrx_rd(socket_num)
            # Read data from the hardware socket.
            bytes_read = self._chip_socket_read(socket_num, pointer, bytes_on_socket)
            # After reading the received data, update Sn_RX_RD register.
            pointer = (pointer + bytes_on_socket) & 0xFFFF
            self._write_snrx_rd(socket_num, pointer)
            self._write_sncr(socket_num, _CMD_SOCK_RECV)
        else:
            # no data on socket
            if self._read_snmr(socket_num) in {
                SNSR_SOCK_LISTEN,
                SNSR_SOCK_CLOSED,
                SNSR_SOCK_CLOSE_WAIT,
            }:
                raise RuntimeError("Lost connection to peer.")
            bytes_read = b""
        return bytes_on_socket, bytes_read

    def read_udp(self, socket_num: int, length: int) -> Tuple[int, bytes]:
        """
        Read UDP socket's current message bytes.

        :param int socket_num: The socket to read data from.
        :param int length: The number of bytes to read from the socket.

        :return Tuple[int, bytes]: If the read was successful then the first
            item of the tuple is the length of the data and the second is the data.
            If the read was unsuccessful then (0, b"") is returned.

        :raises ValueError: If the socket number is out of range.
        """
        self._sock_num_in_range(socket_num)
        bytes_on_socket, bytes_read = 0, b""
        # Parse the UDP packet header.
        header_length, self._pbuff[:8] = self.socket_read(socket_num, 8)
        if header_length:
            if header_length != 8:
                raise ValueError("Invalid UDP header.")
            data_length = self._chip_parse_udp_header(socket_num)
            # Read the UDP packet data.
            if data_length:
                if data_length <= length:
                    bytes_on_socket, bytes_read = self.socket_read(socket_num, data_length)
                else:
                    bytes_on_socket, bytes_read = self.socket_read(socket_num, length)
                    # just consume the rest, it is lost to the higher layers
                    self.socket_read(socket_num, data_length - length)
        return bytes_on_socket, bytes_read

    def socket_write(self, socket_num: int, buffer: bytearray, timeout: float = 0.0) -> int:
        """
        Write data to a socket.

        :param int socket_num: The socket to write to.
        :param bytearray buffer: The data to write to the socket.
        :param float timeout: Write data timeout in seconds, defaults to 0.0 which waits
            indefinitely.

        :return int: The number of bytes written to the socket.

        :raises ConnectionError: If the Ethernet link is down.
        :raises ValueError: If the socket number is out of range.
        :raises RuntimeError: If the data cannot be sent.
        """
        self._sock_num_in_range(socket_num)
        self._check_link_status()
        if len(buffer) > _SOCK_SIZE:
            bytes_to_write = _SOCK_SIZE
        else:
            bytes_to_write = len(buffer)
        start_time = ticks_ms()

        # If buffer is available, start the transfer
        free_size = self._get_tx_free_size(socket_num)
        while free_size < bytes_to_write:
            free_size = self._get_tx_free_size(socket_num)
            status = self.socket_status(socket_num)
            if status not in {SNSR_SOCK_ESTABLISHED, SNSR_SOCK_CLOSE_WAIT} or (
                timeout and ticks_diff(ticks_ms(), start_time) / 1000 > timeout
            ):
                raise RuntimeError("Unable to write data to the socket.")

        # Read the starting address for saving the transmitting data.
        pointer = self._read_sntx_wr(socket_num)
        offset = pointer & _SOCK_MASK
        self._chip_socket_write(socket_num, offset, bytes_to_write, buffer)
        # update sn_tx_wr to the value + data size
        pointer = (pointer + bytes_to_write) & 0xFFFF
        self._write_sntx_wr(socket_num, pointer)
        self._write_sncr(socket_num, _CMD_SOCK_SEND)

        # check data was  transferred correctly
        while not self.read_snir(socket_num) & _SNIR_SEND_OK:
            if self.socket_status(socket_num) in {
                SNSR_SOCK_CLOSED,
                SNSR_SOCK_TIME_WAIT,
                SNSR_SOCK_FIN_WAIT,
                SNSR_SOCK_CLOSE_WAIT,
                _SNSR_SOCK_CLOSING,
            }:
                raise RuntimeError("No data was sent, socket was closed.")
            if timeout and ticks_diff(ticks_ms(), start_time) / 1000 > timeout:
                raise RuntimeError("Operation timed out. No data sent.")
            if self.read_snir(socket_num) & SNIR_TIMEOUT:
                self.write_snir(socket_num, SNIR_TIMEOUT)
                # TCP sockets are closed by the hardware timeout
                # so that will be caught at the while statement.
                # UDP sockets are 1:many so not closed thus return 0.
                if self._read_snmr(socket_num) == SNMR_UDP:
                    return 0
            time.sleep(0.001)
        self.write_snir(socket_num, _SNIR_SEND_OK)
        return bytes_to_write

    def sw_reset(self) -> None:
        """
        Soft reset and reinitialize the WIZnet chip.

        :raises RuntimeError: If reset fails.
        """
        self._wiznet_chip_init()

    def _sw_reset_5x00(self) -> bool:
        """
        Perform a soft reset on the WIZnet 5100s and 5500 chips.

        :returns bool: True if reset was success
        """
        self._write_mr(_MR_RST)
        time.sleep(0.05)
        return self._read_mr() == {"w5500": 0x00, "w5100s": 0x03}[self._chip_type]

    def _wiznet_chip_init(self) -> None:
        """
        Detect and initialize a WIZnet 5k Ethernet module.

        :raises RuntimeError: If no WIZnet chip is detected.
        """

        def _setup_sockets() -> None:
            """Initialise sockets for w5500 and w6100 chips."""
            for sock_num in range(_MAX_SOCK_NUM[self._chip_type]):
                if self._chip_type == "w6300":
                    ctrl_byte = 0xA1 + (sock_num << 2)
                    self._write(0x0220, ctrl_byte, 2)
                    self._write(0x0200, ctrl_byte, 2)
                else:
                    ctrl_byte = 0x0C + (sock_num << 5)
                    self._write(0x1E, ctrl_byte, 2)
                    self._write(0x1F, ctrl_byte, 2)

            self._ch_base_msb = 0x00
            WIZNET5K._sockets_reserved = [False] * (_MAX_SOCK_NUM[self._chip_type] - 1)
            self._src_ports_in_use = [0] * _MAX_SOCK_NUM[self._chip_type]

        def _detect_and_reset_w6100() -> bool:
            """
            Detect and reset a W6100 chip. Called at startup to initialize the
            interface hardware.

            :return bool: True if a W6100 chip is detected, False if not.
            """
            self._chip_type = "w6100"

            # Reset w6100
            self._write(0x41F4, 0x04, 0xCE)  # Unlock chip settings.
            time.sleep(0.05)  # Wait for unlock.
            self._write(0x2004, 0x04, 0x00)  # Reset chip.
            time.sleep(0.05)  # Wait for reset.

            if self._read(_REG_VERSIONR[self._chip_type], 0x00)[0] != 0x61:
                return False
            # Initialize w6100.
            self._write(0x41F5, 0x04, 0x3A)  # Unlock network settings.
            _setup_sockets()

            return True

        def _detect_and_reset_w6300() -> bool:
            """
            Detect and reset a W6300 chip. Called at startup to initialize the
            interface hardware.

            :return bool: True if a W6300 chip is detected, False if not.
            """
            self._chip_type = "w6300"

            self._write(0x41F4, 0xA0, 0xCE)  # Unlock chip settings.
            time.sleep(0.05)  # Wait for unlock.
            self._write(0x41F5, 0xA0, 0x3A)  # Unlock network settings.
            time.sleep(0.05)  # Wait for unlock.
            self._write(0x41F6, 0xA0, 0x53)  # PHY Unlock.
            time.sleep(0.05)  # Wait for unlock.

            if self._read(_REG_VERSIONR[self._chip_type], 0x80)[0] != 0x61:
                return False

            # Initialize w6300.
            _setup_sockets()
            return True

        def _detect_and_reset_w5500() -> bool:
            """
            Detect and reset a W5500 chip. Called at startup to initialize the
            interface hardware.

            :return bool: True if a W5500 chip is detected, False if not.
            """
            self._chip_type = "w5500"
            if not self._sw_reset_5x00():
                return False

            self._write_mr(0x08)
            if self._read_mr() != 0x08:
                return False

            self._write_mr(0x10)
            if self._read_mr() != 0x10:
                return False

            self._write_mr(0x00)
            if self._read_mr() != 0x00:
                return False

            if self._read(_REG_VERSIONR[self._chip_type], 0x00)[0] != 0x04:
                return False
            # Initialize w5500
            _setup_sockets()
            return True

        def _detect_and_reset_w5100s() -> bool:
            """
            Detect and reset a W5100S chip. Called at startup to initialize the
            interface hardware.

            :return bool: True if a W5100 chip is detected, False if not.
            """
            self._chip_type = "w5100s"
            if not self._sw_reset_5x00():
                return False

            if self._read(_REG_VERSIONR[self._chip_type], 0x00)[0] != 0x51:
                return False

            # Initialise w5100s
            self._ch_base_msb = 0x0400
            WIZNET5K._sockets_reserved = [False] * (_MAX_SOCK_NUM[self._chip_type] - 1)
            self._src_ports_in_use = [0] * _MAX_SOCK_NUM[self._chip_type]
            return True

        for func in [
            _detect_and_reset_w5100s,
            _detect_and_reset_w5500,
            _detect_and_reset_w6100,
            _detect_and_reset_w6300,
        ]:
            if func():
                return
        self._chip_type = None
        raise RuntimeError("Failed to initialize WIZnet module.")

    def _sock_num_in_range(self, sock: int) -> None:
        """Check that the socket number is in the range 0 - maximum sockets."""
        if not 0 <= sock < self.max_sockets:
            raise ValueError("Socket number out of range.")

    def _check_link_status(self):
        """Raise an exception if the link is down."""
        if not self.link_status:
            raise ConnectionError("The Ethernet connection is down.")

    @staticmethod
    def _read_socket_reservations() -> list[int]:
        """Return the list of reserved sockets."""
        return WIZNET5K._sockets_reserved

    def _read_mr(self) -> int:
        """Read from the Mode Register (MR)."""
        return int.from_bytes(self._read(_REG_MR[self._chip_type], 0x00), "big")

    def _write_mr(self, data: int) -> None:
        """Write to the mode register (MR)."""
        self._write(_REG_MR[self._chip_type], 0x04, data)

    # *** Low Level Methods ***

    def _read(
        self,
        addr: int,
        callback: int,
        length: int = 1,
    ) -> bytes:
        """
        Read data from a register address.

        :param int addr: Register address to read.
        :param int callback: Callback reference.
        :param int length: Number of bytes to read from the register, defaults to 1.

        :return bytes: Data read from the chip.
        """
        with self._device as bus_device:
            self._chip_read(bus_device, addr, callback)
            self._rxbuf = bytearray(length)
            bus_device.readinto(self._rxbuf)
            return bytes(self._rxbuf)

    def _write(self, addr: int, callback: int, data: Union[int, bytes]) -> None:
        """
        Write data to a register address.

        :param int addr: Destination address.
        :param int callback: Callback reference.
        :param Union[int, bytes] data: Data to write to the register address, if data
            is an integer, it must be 1 or 2 bytes.

        :raises OverflowError: if integer data is more than 2 bytes.
        """
        with self._device as bus_device:
            self._chip_write(bus_device, addr, callback)
            try:
                data = data.to_bytes(1, "big")
            except OverflowError:
                data = data.to_bytes(2, "big")
            except AttributeError:
                pass
            bus_device.write(data)

    def _read_two_byte_sock_reg(self, sock: int, reg_address: int) -> int:
        """Read a two byte socket register."""
        register = self._read_socket_register(sock, reg_address) << 8
        register += self._read_socket_register(sock, reg_address + 1)
        return register

    def _write_two_byte_sock_reg(self, sock: int, reg_address: int, data: int) -> None:
        """Write to a two byte socket register."""
        self._write_socket_register(sock, reg_address, data >> 8 & 0xFF)
        self._write_socket_register(sock, reg_address + 1, data & 0xFF)

    # *** Socket Register Methods ***

    def _get_rx_rcv_size(self, sock: int) -> int:
        """Size of received and saved in socket buffer."""
        val = 0
        val_1 = self._read_snrx_rsr(sock)
        while val != val_1:
            val_1 = self._read_snrx_rsr(sock)
            if val_1 != 0:
                val = self._read_snrx_rsr(sock)
        return val

    def _get_tx_free_size(self, sock: int) -> int:
        """Free size of socket's tx buffer block."""
        val = 0
        val_1 = self._read_sntx_fsr(sock)
        while val != val_1:
            val_1 = self._read_sntx_fsr(sock)
            if val_1 != 0:
                val = self._read_sntx_fsr(sock)
        return val

    def _read_snrx_rd(self, sock: int) -> int:
        """Read socket n RX Read Data Pointer Register."""
        return self._read_two_byte_sock_reg(sock, _REG_SNRX_RD[self._chip_type])

    def _write_snrx_rd(self, sock: int, data: int) -> None:
        """Write socket n RX Read Data Pointer Register."""
        self._write_two_byte_sock_reg(sock, _REG_SNRX_RD[self._chip_type], data)

    def _read_sntx_wr(self, sock: int) -> int:
        """Read the socket write buffer pointer for socket `sock`."""
        return self._read_two_byte_sock_reg(sock, _REG_SNTX_WR[self._chip_type])

    def _write_sntx_wr(self, sock: int, data: int) -> None:
        """Write the socket write buffer pointer for socket `sock`."""
        self._write_two_byte_sock_reg(sock, _REG_SNTX_WR[self._chip_type], data)

    def _read_sntx_fsr(self, sock: int) -> int:
        """Read socket n TX Free Size Register"""
        return self._read_two_byte_sock_reg(sock, _REG_SNTX_FSR[self._chip_type])

    def _read_snrx_rsr(self, sock: int) -> int:
        """Read socket n Received Size Register"""
        return self._read_two_byte_sock_reg(sock, _REG_SNRX_RSR[self._chip_type])

    def _read_sndipr(self, sock) -> bytes:
        """Read socket destination IP address."""
        data = []
        for offset in range(4):
            data.append(self._read_socket_register(sock, _REG_SNDIPR[self._chip_type] + offset))
        return bytes(data)

    def _write_sndipr(self, sock: int, ip_addr: bytes) -> None:
        """Write to socket destination IP Address."""
        for offset, value in enumerate(ip_addr):
            self._write_socket_register(sock, _REG_SNDIPR[self._chip_type] + offset, value)

    def _read_sndport(self, sock: int) -> int:
        """Read socket destination port."""
        return self._read_two_byte_sock_reg(sock, _REG_SNDPORT[self._chip_type])

    def _write_sndport(self, sock: int, port: int) -> None:
        """Write to socket destination port."""
        self._write_two_byte_sock_reg(sock, _REG_SNDPORT[self._chip_type], port)

    def _read_snsr(self, sock: int) -> int:
        """Read Socket n Status Register."""
        result = self._read_socket_register(sock, _REG_SNSR[self._chip_type])
        old_status = self._last_snsr.get(sock)
        if old_status != result:
            old_hex = "0x{:02x}".format(old_status) if old_status is not None else "None"
            print("[LIB_DEBUG] socket {} status: {} -> 0x{:02x}".format(
                sock, old_hex, result))
            self._last_snsr[sock] = result
        return result

    def read_snir(self, sock: int) -> int:
        """Read Socket n Interrupt Register."""
        return self._read_socket_register(sock, _REG_SNIR[self._chip_type])

    def write_snir(self, sock: int, data: int) -> None:
        """Write to Socket n Interrupt Register."""
        self._write_socket_register(sock, _REG_SNIR[self._chip_type], data)

    def _read_snmr(self, sock: int) -> int:
        """Read the socket MR register."""
        return self._read_socket_register(sock, _REG_SNMR)

    def _write_snmr(self, sock: int, protocol: int) -> None:
        """Write to Socket n Mode Register."""
        self._write_socket_register(sock, _REG_SNMR, protocol)

    def _write_sock_port(self, sock: int, port: int) -> None:
        """Write to the socket port number."""
        self._write_two_byte_sock_reg(sock, _REG_SNPORT[self._chip_type], port)

    def _write_sncr(self, sock: int, data: int) -> None:
        """Write to socket command register."""
        self._write_socket_register(sock, _REG_SNCR[self._chip_type], data)
        # Wait for command to complete before continuing.
        while self._read_socket_register(sock, _REG_SNCR[self._chip_type]):
            pass

    @property
    def rcr(self) -> int:
        """Retry count register."""
        return int.from_bytes(self._read(_REG_RCR[self._chip_type], 0x00), "big")

    @rcr.setter
    def rcr(self, retry_count: int) -> None:
        """Retry count register."""
        if 0 > retry_count > 255:
            raise ValueError("Retries must be from 0 to 255.")
        if self._chip_type == "w6300":
            self._write(_REG_RCR[self._chip_type], 0xA0, retry_count)
        else:
            self._write(_REG_RCR[self._chip_type], 0x04, retry_count)

    @property
    def rtr(self) -> int:
        """Retry time register."""
        return int.from_bytes(self._read(_REG_RTR[self._chip_type], 0x00, 2), "big")

    @rtr.setter
    def rtr(self, retry_time: int) -> None:
        """Retry time register."""
        if 0 > retry_time >= 2**16:
            raise ValueError("Retry time must be from 0 to 65535")
        if self._chip_type == "w6300":
            self._write(_REG_RCR[self._chip_type], 0xA0, retry_time)
        else:
            self._write(_REG_RTR[self._chip_type], 0x04, retry_time)

    # *** Chip Specific Methods ***

    def _chip_read(self, device: busio.SPI, address: int, call_back: int) -> None:
        """Chip specific calls for _read method."""
        if self._chip_type in {"w5500", "w6100"}:
            device.write((address >> 8).to_bytes(1, "big"))
            device.write((address & 0xFF).to_bytes(1, "big"))
            device.write(call_back.to_bytes(1, "big"))
        elif self._chip_type == "w5100s":
            device.write((0x0F).to_bytes(1, "big"))
            device.write((address >> 8).to_bytes(1, "big"))
            device.write((address & 0xFF).to_bytes(1, "big"))
        elif self._chip_type == "w6300":
            device.write(call_back.to_bytes(1, "big"))
            device.write((address >> 8).to_bytes(1, "big"))
            device.write((address & 0xFF).to_bytes(1, "big"))
            device.write((0x00).to_bytes(1, "big"))

    def _chip_write(self, device: busio.SPI, address: int, call_back: int) -> None:
        """Chip specific calls for _write."""
        if self._chip_type in {"w5500", "w6100"}:
            device.write((address >> 8).to_bytes(1, "big"))
            device.write((address & 0xFF).to_bytes(1, "big"))
            device.write(call_back.to_bytes(1, "big"))
        elif self._chip_type == "w5100s":
            device.write((0xF0).to_bytes(1, "big"))
            device.write((address >> 8).to_bytes(1, "big"))
            device.write((address & 0xFF).to_bytes(1, "big"))
        elif self._chip_type == "w6300":
            device.write(call_back.to_bytes(1, "big"))
            device.write((address >> 8).to_bytes(1, "big"))
            device.write((address & 0xFF).to_bytes(1, "big"))
            device.write((0x00).to_bytes(1, "big"))

    def _chip_socket_read(self, socket_number, pointer, bytes_to_read):
        """Chip specific calls for socket_read."""
        if self._chip_type in {"w5500", "w6100"}:
            # Read data from the starting address of snrx_rd
            ctrl_byte = 0x18 + (socket_number << 5)
            bytes_read = self._read(pointer, ctrl_byte, bytes_to_read)
        elif self._chip_type == "w5100s":
            offset = pointer & _SOCK_MASK
            src_addr = offset + (socket_number * _SOCK_SIZE + 0x6000)
            if offset + bytes_to_read > _SOCK_SIZE:
                split_point = _SOCK_SIZE - offset
                bytes_read = self._read(src_addr, 0x00, split_point)
                split_point = bytes_to_read - split_point
                src_addr = socket_number * _SOCK_SIZE + 0x6000
                bytes_read += self._read(src_addr, 0x00, split_point)
            else:
                bytes_read = self._read(src_addr, 0x00, bytes_to_read)
        elif self._chip_type == "w6300":
            ctrl_byte = 0x83 + (socket_number << 2)
            bytes_read = self._read(pointer, ctrl_byte, bytes_to_read)
        return bytes_read

    def _chip_socket_write(
        self, socket_number: int, offset: int, bytes_to_write: int, buffer: bytes
    ):
        """Chip specific calls for socket_write."""
        if self._chip_type in {"w5500", "w6100"}:
            dst_addr = offset + (socket_number * _SOCK_SIZE + 0x8000)
            cntl_byte = 0x14 + (socket_number << 5)
            self._write(dst_addr, cntl_byte, buffer[:bytes_to_write])

        elif self._chip_type == "w5100s":
            dst_addr = offset + (socket_number * _SOCK_SIZE + 0x4000)

            if offset + bytes_to_write > _SOCK_SIZE:
                split_point = _SOCK_SIZE - offset
                self._write(dst_addr, 0x00, buffer[:split_point])
                dst_addr = socket_number * _SOCK_SIZE + 0x4000
                self._write(dst_addr, 0x00, buffer[split_point:bytes_to_write])
            else:
                self._write(dst_addr, 0x00, buffer[:bytes_to_write])

        elif self._chip_type == "w6300":
            dst_addr = offset + (socket_number * _SOCK_SIZE + 0x8000)
            cntl_byte = 0xA2 + (socket_number << 2)
            self._write(dst_addr, cntl_byte, buffer[:bytes_to_write])

    def _chip_parse_udp_header(self, socket_num) -> int:
        """
        Parse chip specific UDP header data for IPv4 packets.

        Sets the source IPv4 address and port number and returns the UDP data length.

        :return int: The UDP data length.
        """
        if self._chip_type in {"w5100s", "w5500"}:
            self.udp_from_ip[socket_num] = self._pbuff[:4]
            self.udp_from_port[socket_num] = int.from_bytes(self._pbuff[4:6], "big")
            return int.from_bytes(self._pbuff[6:], "big")
        if self._chip_type == "w6100":
            self.udp_from_ip[socket_num] = self._pbuff[3:7]
            self.udp_from_port[socket_num] = int.from_bytes(self._pbuff[6:], "big")
            return int.from_bytes(self._pbuff[:2], "big") & 0x07FF
        if self._chip_type == "w6300":
            self.udp_from_ip[socket_num] = self._pbuff[2:6]
            self.udp_from_port[socket_num] = int.from_bytes(self._pbuff[6:8], "big")
            return int.from_bytes(self._pbuff[:2], "big") & 0x07FF
        raise ValueError("Unsupported chip type.")

    def _write_socket_register(self, sock: int, address: int, data: int) -> None:
        """Write to a WIZnet 5k socket register."""
        if self._chip_type in {"w5500", "w6100"}:
            cntl_byte = (sock << 5) + 0x0C
            self._write(address, cntl_byte, data)
        elif self._chip_type == "w5100s":
            cntl_byte = 0
            self._write(self._ch_base_msb + sock * _CH_SIZE + address, cntl_byte, data)
        elif self._chip_type == "w6300":
            cntl_byte = (sock << 2) + 0xA1
            self._write(address, cntl_byte, data)

    def _read_socket_register(self, sock: int, address: int) -> int:
        """Read a WIZnet 5k socket register."""
        if self._chip_type in {"w5500", "w6100"}:
            cntl_byte = (sock << 5) + 0x08
            register = self._read(address, cntl_byte)
        elif self._chip_type == "w5100s":
            cntl_byte = 0
            register = self._read(self._ch_base_msb + sock * _CH_SIZE + address, cntl_byte)
        elif self._chip_type == "w6300":
            cntl_byte = (sock << 2) + 0x81
            register = self._read(address, cntl_byte)
        return int.from_bytes(register, "big")

W5500 硬件架构

芯片简介

W5500 是 WIZnet 公司生产的 TCP/IP 以太网控制器芯片,集成 TCP/IP 协议栈、10/100M 以太网 PHY 和 8 个独立的硬件 Socket。

核心特性

| 特性 | 说明 |

|------|------|

| **物理 Socket 数量** | 8 个独立硬件 Socket |

| **协议支持** | TCP、UDP、IPv4、ICMP、ARP |

| **最大并发连接** | 8 个(每个 Socket 可建立一条 TCP 连接) |

| **存储缓冲** | 32KB TX/RX 缓冲区(共享) |

| **接口** | SPI 时钟最高 80MHz |

Socket 0-7 硬件特性

```

┌─────────────────────────────────────────────────────────────────────┐

│ W5500 芯片内部结构 │

├─────────────────────────────────────────────────────────────────────┤

│ │

│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │

│ │Socket 0 │ │Socket 1 │ │Socket 2 │ │Socket 3 │ │

│ │ │ │ │ │ │ │ │ │

│ │寄存器组 │ │寄存器组 │ │寄存器组 │ │寄存器组 │ │

│ │+缓冲器 │ │+缓冲器 │ │+缓冲器 │ │+缓冲器 │ │

│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │

│ │

│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │

│ │Socket 4 │ │Socket 5 │ │Socket 6 │ │Socket 7 │ │

│ │ │ │ │ │ │ │ │ │

│ │寄存器组 │ │寄存器组 │ │寄存器组 │ │寄存器组 │ │

│ │+缓冲器 │ │+缓冲器 │ │+缓冲器 │ │+缓冲器 │ │

│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │

│ │

│ ┌─────────────────────────────────────────┐ │

│ │ 公共寄存器 + TX/RX 缓冲区 │ │

│ └─────────────────────────────────────────┘ │

│ │

└─────────────────────────────────────────────────────────────────────┘

```

Socket 编号分配策略

| Socket | 用途 | 说明 |

|--------|------|------|

| **Socket 0** | 特殊用途 | **唯一支持 MacRAW 模式的 Socket**,不能被预留 |

| **Socket 1-7** | 通用 Socket | 可用于 TCP/UDP 连接,可被预留 |

为什么 socket 0 不能被预留?

```python

摘自 adafruit_wiznet5k.py - get_socket() 方法

def get_socket(self, *, reserve_socket=False) -> int:

优先使用 socket 0(不可预留)

if not reserve_socket and self.socket_status(0) == SNSR_SOCK_CLOSED:

return 0

socket 1-7 可以被预留

for socket_number, reserved in enumerate(WIZNET5K._sockets_reserved, start=1):

if not reserved and self.socket_status(socket_number) == SNSR_SOCK_CLOSED:

if reserve_socket:

WIZNET5K._sockets_reserved[socket_number - 1] = True

return socket_number

```

**原因**:Socket 0 具备其他 Socket 没有的能力 - 支持 **MacRAW 模式**,可以原始帧捕获,用于实现类似 Wireshark 的网络抓包功能。

为什么 TelnetServer 不使用 Socket 0?

观察 TelnetServerAlone.py 的日志可以发现,Socket 1 和 Socket 2 交替使用,但 **Socket 0 从未参与**:

```

LIB_DEBUG\] socket 0 status: None -\> 0x00 # 仅初始化时检查过一次 \[LIB_DEBUG\] socket 1 status: 0x14 -\> 0x17 # Socket 1 监听/连接 \[LIB_DEBUG\] socket 2 status: None -\> 0x00 # Socket 2 作为新监听 ... \`\`\` \*\*代码中的显式跳过:\*\* \`\`\`python # TelnetServerAlone.py - _create_new_listen_socket() new_socket = (self.listen_socket + 1) % 8 if new_socket == 0: new_socket = 2 # 跳过 Socket 0 \`\`\` \*\*设计意图:\*\* 1. \*\*保留 Socket 0 供 MacRAW 使用\*\* - Socket 0 是唯一支持 MacRAW 模式的 Socket 2. \*\*驱动层优先使用 Socket 0\*\* - \`get_socket()\` 在未指定 \`reserve_socket=True\` 时优先返回 Socket 0 3. \*\*Telnet 只需要 2 个 Socket\*\* - 一个监听,一个连接,交替使用 Socket 1 和 2 已足够 \*\*Socket 分配策略总结:\*\* \| Socket \| 保留给 \| TelnetServer 使用 \| \|--------\|--------\|-------------------\| \| Socket 0 \| MacRAW 原始帧捕获 \| ❌ 不使用 \| \| Socket 1 \| 通用 TCP/UDP \| ✅ 监听/连接 \| \| Socket 2 \| 通用 TCP/UDP \| ✅ 监听/连接 \| \| Socket 3-7 \| 通用 TCP/UDP \| ❌ 未使用 \| ### Socket 0 MacRAW 模式详解 Socket 0 是 W5500 中唯一支持 MacRAW 模式的 Socket,其特性如下: \| 特性 \| 说明 \| \|------\|------\| \| \*\*协议类型\*\* \| RAW (原始 MAC 帧) \| \| \*\*用途\*\* \| 网络监控、协议分析、 ARPANET 透明传输 \| \| \*\*帧长度\*\* \| 1514 字节 (含 MAC 头) \| \| \*\*与 Socket 1-7 关系\*\* \| MacRAW 与 TCP/UDP 不可同时使用 \| \*\*Sn_MR 寄存器 - Socket 0 协议选择:\*\* \| Sn_MR 值 \| 协议 \| 适用 Socket \| \|----------\|------\|-------------\| \| 0x01 \| TCP \| Socket 1-7 \| \| 0x02 \| UDP \| Socket 1-7 \| \| 0x04 \| \*\*MacRAW\*\* \| \*\*仅 Socket 0\*\* \| \| 0x10 \| IPRAW \| Socket 1-7 \| \| 0x20 \| PPPoE \| Socket 1-7 \| ### Socket 寄存器组详解 每个 Socket 都有独立的寄存器组,地址基于 Socket 编号计算: \| 寄存器 \| 地址偏移 \| 功能 \| 访问 \| \|--------\|----------\|------\|------\| \| \*\*Sn_MR\*\* \| 0x0000 \| Socket Mode Register - 设置协议模式 \| R/W \| \| \*\*Sn_CR\*\* \| 0x0001 \| Socket Command Register - 发送命令 \| R/W \| \| \*\*Sn_IR\*\* \| 0x0002 \| Socket Interrupt Register - 中断标志 \| R/W \| \| \*\*Sn_SR\*\* \| 0x0003 \| Socket Status Register - 当前状态 \| R \| \| \*\*Sn_PORT\*\* \| 0x0004-0x0005 \| Source Port \| R/W \| \| \*\*Sn_DHAR\*\* \| 0x0006-0x000B \| Destination MAC Address \| R/W \| \| \*\*Sn_DIPR\*\* \| 0x000C-0x000F \| Destination IP Address \| R/W \| \| \*\*Sn_DPORT\*\* \| 0x0010-0x0011 \| Destination Port \| R/W \| \| \*\*Sn_TXBUF\*\* \| 0x0012-0x0013 \| TX Buffer Pointer (读) / Size (写) \| R/W \| \| \*\*Sn_RXBUF\*\* \| 0x0014-0x0015 \| RX Buffer Pointer (读) / Size (写) \| R/W \| \*\*寄存器地址计算公式:\*\* \`\`\` Socket N 寄存器地址 = 0x4000 + (N × 0x0100) + 寄存器偏移 \`\`\` 例如:Socket 1 的 Sn_SR = 0x4000 + (1 × 0x0100) + 0x0003 = 0x4103 ### Socket TX/RX 缓冲区寻址 \`\`\` W5500 内存映射 (16KB 共享 SRAM) ┌─────────────────────────────────────────────────────────────┐ │ 公共区域 │ Socket 0 │ Socket 1 │ ... │ Socket 7 │ │ (留作他用) │ 4KB │ 4KB │ │ 4KB │ ├─────────────────────────────────────────────────────────────┤ │ │ TX: 2KB │ TX: 2KB │ │ TX: 2KB │ │ │ RX: 2KB │ RX: 2KB │ │ RX: 2KB │ └─────────────────────────────────────────────────────────────┘ TX Buffer 基址: 0x4000 (Socket 0) → 0x5000 (Socket 1) → ... → 0x4E00 (Socket 7) RX Buffer 基址: 0x6000 (Socket 0) → 0x7000 (Socket 1) → ... → 0x6E00 (Socket 7) \`\`\` ### Socket 内存布局 \`\`\` W5500 内部 SRAM (共 16KB) ┌──────────────────────────────────────────────────────────┐ │ 共享缓冲区 (TX/RX 各 8KB) │ ├──────────────────────────────────────────────────────────┤ │ Socket 0 │ 2KB TX │ 2KB RX │ │ Socket 1 │ 2KB TX │ 2KB RX │ │ Socket 2 │ 2KB TX │ 2KB RX │ │ Socket 3 │ 2KB TX │ 2KB RX │ │ Socket 4 │ 2KB TX │ 2KB RX │ │ Socket 5 │ 2KB TX │ 2KB RX │ │ Socket 6 │ 2KB TX │ 2KB RX │ │ Socket 7 │ 2KB TX │ 2KB RX │ └──────────────────────────────────────────────────────────┘ 注意:每个 Socket 的缓冲区大小可通过寄存器配置 \`\`\` ### Socket 状态机 ### W5500 Socket 状态定义 \| 状态码 \| 状态名 \| 说明 \| \|--------\|--------\|------\| \| 0x00 \| CLOSED \| Socket 关闭 \| \| 0x13 \| INIT \| Socket 初始化完成 \| \| 0x14 \| LISTEN \| 正在监听连接 \| \| 0x17 \| ESTABLISHED \| 连接已建立 \| \| 0x1C \| CLOSE_WAIT \| 等待关闭 \| \| 0x22 \| UDP \| UDP 模式 \| ### TCP 连接状态转换流程 \`\`\` 客户端 服务器Socket \| \| \| -------- TCP SYN ------------\>\| \| \| INIT → LISTEN → SYNRECV \| \<------ TCP SYN+ACK ---------\| \| -------- TCP ACK ------------\>\| \| \| LISTEN → ESTABLISHED \|========== 连接建立 ===========\| \| \| \| -------- 数据传输 ------------\>\| \| \<------- TCP ACK -------------\| \| \| \| -------- FIN --------------- \>\| \| \| ESTABLISHED → CLOSE_WAIT \| \<------- TCP ACK -------------\| (或直接 → CLOSED) \| \| \| \<------- FIN ---------------\| \| -------- TCP ACK -----------\>\| \| \| CLOSE_WAIT → CLOSED \|========== 连接关闭 ===========\| \`\`\` --- ## SocketPool accept() 详解 ### 核心概念:Socket 编号交换 W5500 的监听机制通过 \*\*Socket 编号交换\*\* 实现: \> \*\*硬件层面\*\*:W5500 每个 Socket 在同一时刻只能有一种状态 \> \*\*代码层面\*\*:通过编号交换实现了"一个 socket 对象支持持续监听" \> \*\*实际效果\*\*:用户感知上是同一个 socket 在持续监听,但底层硬件 socket 编号已经发生了变化 ### accept() 工作流程 \`\`\` ┌─────────────────────────────────────────────────────────────────────┐ │ accept() 执行过程 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ accept() 调用前: │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Socket 1 │ │ Socket 2 │ │ │ │ LISTEN │ │ CLOSED │ │ │ │ (监听中) │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ ▲ │ │ │ │ │ │ accept() │ │ │ │ │ ┌────┴─────┐ ┌─────────────┐ │ │ │ Socket 1 │ │ Socket 2 │ │ │ │ ESTABLISHED│ │ LISTEN │ │ │ │ (已连接) │ │ (新监听) │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ accept() 返回 │ │ ▼ │ │ ┌─────────────────────────────────────┐ │ │ │ 返回 (conn, addr) │ │ │ │ conn = Socket 1 的封装对象 │ │ │ │ addr = 客户端地址 │ │ │ └─────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ \`\`\` ### 状态转换详细时序 \`\`\` 时间轴 Socket 1 Socket 2 说明 ──────────────────────────────────────────────────────────────────────── T0 CLOSED(0x00) 初始状态 T1 INIT(0x13) T2 LISTEN(0x14) 监听启动 T3 ESTABLISHED(0x17) CLOSED(0x00) accept()时:监听→连接 T4 INIT(0x13) 自动创建新监听 T5 LISTEN(0x14) T6 CLOSED(0x00) ESTABLISHED(0x17) 客户端断开 T7 INIT(0x13) 监听socket恢复 T8 LISTEN(0x14) T9 CLOSED(0x00) 数据socket关闭 T10 INIT(0x13) 复用socket恢复监听 T11 LISTEN(0x14) \`\`\` ### SocketPool accept() 源代码解析 \`\`\`python # adafruit_wiznet5k_socketpool.py - Socket.accept() 方法 def accept(self): """接受连接""" # 1. 轮询等待连接建立 while True: status = self._interface.socket_status(self._socknum) if status in {SNSR_SOCK_SYNRECV, SNSR_SOCK_ESTABLISHED, SNSR_SOCK_LISTEN}: break # ... 超时处理 ... # 2. 获取客户端信息 peer_ip = self._interface.remote_ip(self._socknum) peer_port = self._interface.remote_port(self._socknum) # 3. 获取当前监听 socket 的硬件编号 current_socknum = self._socknum # 4. 创建新的 Socket 对象 client_sock = Socket(self._socket_pool) # 5. 交换 socket 编号:原监听 socket 的编号给客户端 socket,新分配的编号给监听 socket self._socknum = client_sock._socknum client_sock._socknum = current_socknum # 6. 在新编号的 socket 上启动监听 self._bind((None, self._listen_port)) self.listen() # 7. 返回客户端 socket 和地址 return client_sock, (peer_ip, peer_port) \`\`\` \*\*关键机制\*\*:通过交换两个 Python Socket 对象的 \`_socknum\`,实现了: - 返回的 \`client_sock\` 使用原监听 socket 的硬件编号(用于数据连接) - \`self\`(新的监听 socket)使用新分配的硬件编号 ### accept() 关键特性 \| 特性 \| 说明 \| \|------\|------\| \| \*\*编号交换\*\* \| 原监听 socket 转为 ESTABLISHED,自动分配新 socket 用于监听 \| \| \*\*对象分离\*\* \| Python Socket 对象与硬件 socket 编号分离 \| \| \*\*自动监听\*\* \| 新 socket 自动进入 LISTEN 状态 \| \| \*\*资源复用\*\* \| 断开后原 socket 自动恢复监听,无需手动管理 \| ### TelnetServerAlone vs SocketPool accept() 对比 \| 步骤 \| TelnetServerAlone \| SocketPool \| \|------\|-------------------\|------------\| \| 1. 检测连接 \| 手动检查 \`socket_status() == ESTABLISHED\` \| 自动轮询等待 \| \| 2. 获取客户端信息 \| 手动调用 \`remote_ip()\`, \`remote_port()\` \| 自动获取 \| \| 3. 创建新监听 \| 手动调用 \`_create_new_listen_socket()\` \| 自动调用 \`get_socket()\` + \`socket_listen()\` \| \| 4. 返回结果 \| 无返回值,设置 \`self.client_socket\` \| 返回 \`(conn, addr)\` 元组 \| ### TelnetServerAlone.py 手动实现 SocketPool 逻辑 \`\`\`python # TelnetServerAlone.py 的 accept_client() 等价于 SocketPool 的 accept() def accept_client(self): # 1. 检查监听socket是否变为ESTABLISHED status = self.nic.socket_status(self.listen_socket) if status == SNSR_SOCK_ESTABLISHED: # 2. 获取客户端信息 client_ip = self.nic.remote_ip(self.listen_socket) client_port = self.nic.remote_port(self.listen_socket) # 3. 设置客户端socket (复用监听socket) self.client_socket = self.listen_socket # 4. 创建新的监听socket (等价于SocketPool的get_socket) self._create_new_listen_socket() return True def _create_new_listen_socket(self): """创建新监听socket - SocketPool自动完成的工作""" # 分配新socket new_socket = (self.listen_socket + 1) % 8 if new_socket == 0: new_socket = 2 # 关闭并重新监听 self.nic.socket_close(new_socket) self.nic.socket_listen(new_socket, self.port) self.listen_socket = new_socket \`\`\` --- ## TelnetServerAlone.py 状态观察日志 \`\`\` \[LIB_DEBUG\] socket 1 status: None -\> 0x00 # 初始关闭 \[LIB_DEBUG\] socket 1 status: 0x00 -\> 0x13 # 初始化 \[LIB_DEBUG\] socket 1 status: 0x13 -\> 0x14 # 开始监听 \[Telnet\] ✅ 监听端口 23 (socket 1) # 客户端连接 \[LIB_DEBUG\] socket 1 status: 0x14 -\> 0x17 # 连接建立 (LISTEN → ESTABLISHED) \[Telnet\] 客户端连接 172.16.30.185:xxxxx (socket=1) # 新建监听socket \[LIB_DEBUG\] socket 2 status: None -\> 0x00 \[LIB_DEBUG\] socket 2 status: 0x00 -\> 0x13 \[LIB_DEBUG\] socket 2 status: 0x13 -\> 0x14 \[Telnet\] 新监听socket: 2 (client=socket=1) # 客户端断开 \[LIB_DEBUG\] socket 1 status: 0x17 -\> 0x1C # ESTABLISHED → CLOSE_WAIT \[LIB_DEBUG\] socket 1 status: 0x1C -\> 0x00 # CLOSE_WAIT → CLOSED \[Telnet\] 客户端断开 (socket=1, x.x秒) \`\`\` ### TelnetServerPool.py 完整测试日志 \`\`\` W5500初始化完成 IP: 172.16.30.75 ================================================== \[LIB_DEBUG\] socket 1 status: None -\> 0x00 # Socket 1: 初始关闭 \[LIB_DEBUG\] socket 1 status: 0x00 -\> 0x13 # Socket 1: 初始化 (INIT) \[LIB_DEBUG\] socket 1 status: 0x13 -\> 0x14 # Socket 1: 开始监听 (LISTEN) \[Telnet\] ✅ 监听端口 23 (SocketPool) Telnet服务器已启动,监听端口 23 用Telnet客户端连接 172.16.30.75:23 按 Ctrl+C 停止 ================================================== \[LIB_DEBUG\] socket 0 status: None -\> 0x00 # Socket 0: 被 SocketPool 预分配检查 # === 第一次客户端连接 === \[LIB_DEBUG\] socket 1 status: 0x14 -\> 0x17 # Socket 1: LISTEN → ESTABLISHED (连接建立) \[LIB_DEBUG\] socket 2 status: None -\> 0x00 # Socket 2: 新socket分配 \[LIB_DEBUG\] socket 2 status: 0x00 -\> 0x13 # Socket 2: 初始化 (INIT) \[LIB_DEBUG\] socket 2 status: 0x13 -\> 0x14 # Socket 2: 开始监听 (LISTEN) \[Telnet\] 客户端连接 172.16.30.185:49572 \[Telnet\] 命令: 'help' \[Telnet\] 命令: 'debug' \[Telnet\] 命令: 'quit' \[Telnet\] 客户端断开 (172.16.30.185:49572, 37.9秒) \[LIB_DEBUG\] socket 1 status: 0x17 -\> 0x00 # Socket 1: ESTABLISHED → CLOSED (连接关闭) # === 第二次客户端连接 === \[LIB_DEBUG\] socket 2 status: 0x14 -\> 0x17 # Socket 2: LISTEN → ESTABLISHED (新连接) \[LIB_DEBUG\] socket 1 status: 0x00 -\> 0x13 # Socket 1: CLOSED → INIT (恢复监听准备) \[LIB_DEBUG\] socket 1 status: 0x13 -\> 0x14 # Socket 1: INIT → LISTEN (恢复监听) \[Telnet\] 客户端连接 172.16.30.185:49575 \[Telnet\] 命令: 'quit' \[Telnet\] 客户端断开 (172.16.30.185:49575, 17.4秒) \[LIB_DEBUG\] socket 2 status: 0x17 -\> 0x00 # Socket 2: ESTABLISHED → CLOSED \`\`\` ### Socket 状态变化解释 \`\`\` 时间轴 Socket 0 Socket 1 Socket 2 说明 ────────────────────────────────────────────────────────────────────────────── T0 CLOSED(0x00) 系统启动后检查 Socket 0 T1 INIT(0x13) T2 LISTEN(0x14) 监听启动 (端口 23) T3 ESTABLISHED(0x17) 第一次客户端连接建立 T4 CLOSED(0x00) ↓ INIT(0x13) SocketPool 分配新 socket T5 LISTEN(0x14) 新监听就绪 T6 CLOSED(0x00) 第一次连接关闭 T7 ESTABLISHED 第二次连接建立 T8 INIT(0x13) Socket 1 恢复准备 T9 LISTEN(0x14) Socket 1 恢复监听 T10 CLOSED(0x00) 第二次连接关闭 \`\`\` ### Socket 分配策略说明 \| 阶段 \| Socket 0 \| Socket 1 \| Socket 2 \| 说明 \| \|------\|----------\|----------\|----------\|------\| \| 启动 \| 检查 \| LISTEN \| - \| Socket 1 作为监听 socket \| \| 连接1 \| - \| ESTABLISHED \| LISTEN \| Socket 1 处理连接,Socket 2 接管监听 \| \| 断开1 \| - \| CLOSED \| ESTABLISHED \| 连接断开,等待新连接 \| \| 恢复1 \| - \| LISTEN \| ESTABLISHED \| Socket 1 恢复监听 \| \| 连接2 \| - \| LISTEN \| ESTABLISHED \| Socket 2 处理新连接 \| \| 断开2 \| - \| LISTEN \| CLOSED \| 最终状态 \| \*\*关键观察\*\*: - Socket 0 仅在启动时被检查一次(预分配机制),不参与实际通信 - Socket 1 和 Socket 2 交替作为监听和连接 socket - 每次断开后,原 socket 会自动恢复监听,实现持续服务 --- ## accept() 方法详解 ### 方法签名 \`\`\`python Socket.accept() -\> Tuple\[Socket, Tuple\[str, int\]

```

返回值

| 返回值 | 类型 | 说明 |

|--------|------|------|

| `conn` | Socket | 新的 socket 对象,用于与客户端通信 |

| `addr` | Tuple[str, int] | 客户端地址,格式为 `(IP字符串, 端口号)` |

使用示例

```python

TelnetServerPool.py 中的使用

def accept_client(self):

if self.listen_socket is None or self.client_socket is not None:

return False

try:

conn, addr = self.listen_socket.accept()

self.client_socket = conn

self.client_address = addr

print("[Telnet] 客户端连接 {}:{}".format(addr[0], addr[1]))

...

except OSError as e:

EAGAIN 表示暂无连接

if e.errno != 11:

print("[Telnet] accept异常: {}".format(e))

return False

```


快速开始

硬件要求

  • ESP32-S3 开发板

  • W5500 以太网模块

  • SPI 连接:SCK(GPIO12), MOSI(GPIO11), MISO(GPIO13), CS(GPIO10), RST(GPIO14)

软件要求

  • CircuitPython 10.1.3+

  • adafruit_wiznet5k 库

使用方法

```python

方法1:使用直接驱动方式

import TelnetServerAlone

TelnetServerAlone.main()

方法2:使用 SocketPool 抽象层

import TelnetServerPool

TelnetServerPool.main()

```

连接方式

```bash

使用 Telnet 客户端连接

telnet 172.16.30.75 23

```


API 说明

TelnetServerAlone 类

初始化与启动

```python

def init(self, nic, port=23):

"""初始化服务器"""

def setup(self):

"""初始化监听Socket"""

def run_once(self):

"""执行一次服务器循环"""

def main():

"""主入口函数"""

```

核心方法

| 方法 | 功能 |

|------|------|

| `accept_client()` | 接受新客户端连接 |

| `process_client()` | 处理客户端数据 |

| `_process_data(data)` | 解析接收到的字节数据 |

| `_execute_command(cmd)` | 执行用户命令 |

| `_send_data(data)` | 发送数据到客户端 |

| `_close_client()` | 关闭客户端连接 |

TelnetServerPool 类

初始化与启动

```python

def init(self, nic, port=23):

"""初始化服务器"""

def setup(self):

"""初始化监听Socket"""

def run_once(self):

"""执行一次服务器循环"""

def main():

"""主入口函数"""

```


实现对比

Socket 管理对比

| 操作 | TelnetServerAlone | TelnetServerPool |

|------|-------------------|------------------|

| **创建监听** | 手动指定 socket=1 | `socket_pool.socket()` 自动分配 |

| **接受连接** | 手动轮换 socket 编号 | `accept()` 自动处理编号交换 |

| **新建监听** | 手动调用 `_create_new_listen_socket()` | `accept()` 内部自动完成 |

| **状态跟踪** | 手动检查 socket_status | 自动管理连接状态 |

| **资源释放** | 手动调用 socket_close | 自动释放(`close()`/`del`) |

代码复杂度对比

**TelnetServerAlone.py - 监听初始化**

```python

self.listen_socket = 1

self.nic.socket_close(self.listen_socket)

self.nic.socket_listen(self.listen_socket, self.port)

```

**TelnetServerPool.py - 监听初始化**

```python

self.listen_socket = self.socket_pool.socket()

self.listen_socket.bind((None, self.port))

self.listen_socket.listen()

```


支持的命令

| 命令 | 说明 | 示例 |

|------|------|------|

| `help` | 显示帮助信息 | `> help` |

| `status` | 显示服务器状态 | `> status` |

| `debug` | 显示调试信息 | `> debug` |

| `uptime` | 显示系统运行时间 | `> uptime` |

| `echo` | 切换回显模式 | `> echo` |

| `exit`/`quit` | 断开连接 | `> exit` |

命令输出示例

```

欢迎使用 W5500 Telnet 服务器

输入 'help' 查看可用命令

输入 'exit' 或 'quit' 断开连接

> help

可用命令:

help - 显示帮助

status - 服务器状态

debug - 调试信息

uptime - 运行时间

echo - 切换回显

exit - 断开连接

> status

服务器状态:

端口: 23

客户端: 1

运行时间: 22.1秒

数据包: 3

> debug

=== 调试信息 ===

监听 socket: #1 状态: 监听中 (0x14)

客户端 socket: #2 状态: 已连接 (0x17)

远程地址: 172.16.30.185:64748

> uptime

运行时间: 0:22:43

> echo

回显: 开

> quit

再见!

```


网络配置

IP 地址固定配置为:

  • IP: `172.16.30.75`

  • 子网掩码: `255.255.255.0`

  • 网关: `172.16.30.254`

  • DNS: `8.8.8.8`

  • 监听端口: `23`


故障排除

常见问题

| 错误 | 原因 | 解决方案 |

|------|------|---------|

| `'module' object has no attribute 'SCK'` | 引脚命名错误 | 使用 `board.GPIOxx` 格式 |

| `function doesn't take keyword arguments` | CircuitPython 不支持关键字参数 | 使用位置参数 |

| `UnicodeError` | 字节解码问题 | 使用 `decode('utf-8', 'ignore')` |

| `All sockets in use` | Socket 资源泄漏 | 确保正确关闭连接 |

| `bind failed: The IPv4 address requested must match...` | IP地址验证失败 | 使用 `None` 代替 `'0.0.0.0'` |

调试技巧

  1. **查看 Socket 状态**:启用驱动调试模式观察状态变化

  2. **检查连接**:使用 `status` 命令查看连接状态

  3. **重启测试**:修改代码后需要重启单片机重新导入模块


选择建议

使用 TelnetServerAlone.py 当:

  • 需要深入理解 W5500 硬件工作原理

  • 需要精细控制 Socket 资源

  • 需要观察底层状态变化

使用 TelnetServerPool.py 当:

  • 追求开发效率

  • 代码需要跨平台兼容

  • 希望使用标准 socket API

  • 关注代码可读性和可维护性

相关推荐
辰尘_星启2 天前
【Linux】Python Socket编程指南
linux·python·socket·系统·通信
SoveTingღ4 天前
【问题解析】Socket已经关闭了,但是端口还处于listening状态?
linux·服务器·c++·qt·socket
H Journey8 天前
Linux网络编程,高性能 IO 多路复用服务器:向 epoll 监控器注册要监听的 socket 和事件
网络·socket·多路复用·事件注册
H Journey9 天前
网络编程-创建SOCKET套接字
网络·socket
qdprobot11 天前
ESP32S3 AiTall V3 Mixly 图形化编程开发AI小智 MCP AIOT大模型对话开发视频教程Micropython小智AI系统
人工智能·micropython·esp32s3·图形化编程·mcp·mixly小智ai·大模型对话
crazin1 个月前
MimiClaw网络教程踩坑记录
esp32s3·嵌入式ai·小龙虾·mimiclaw
sichuanwww1 个月前
套接字Socket编程样例
udp·socket·tcp
闻道且行之2 个月前
libhv 安装与使用全流程教程
c++·http·socket·libhv·c/c++
jianqiang.xue2 个月前
ESP32-S3 运行 Linux 全指南:从 RISC-V 模拟器移植到 8 秒快速启动
linux·stm32·单片机·mongodb·risc-v·esp32s3