5 changed files with 1985 additions and 0 deletions
Binary file not shown.
@ -0,0 +1,889 @@ |
|||
# 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-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 |
|||
|
|||
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 random import randint |
|||
import time |
|||
from micropython import const |
|||
|
|||
from adafruit_bus_device.spi_device import SPIDevice |
|||
import adafruit_wiznet5k.adafruit_wiznet5k_dhcp as dhcp |
|||
import adafruit_wiznet5k.adafruit_wiznet5k_dns as dns |
|||
|
|||
|
|||
__version__ = "0.0.0-auto.0" |
|||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k.git" |
|||
|
|||
|
|||
# Wiznet5k Registers |
|||
REG_MR = const(0x0000) # Mode |
|||
REG_GAR = const(0x0001) # Gateway IP Address |
|||
REG_SUBR = const(0x0005) # Subnet Mask Address |
|||
REG_VERSIONR_W5500 = const(0x0039) # W5500 Silicon Version |
|||
REG_SHAR = const(0x0009) # Source Hardware Address |
|||
REG_SIPR = const(0x000F) # Source IP Address |
|||
REG_PHYCFGR = const(0x002E) # W5500 PHY Configuration |
|||
|
|||
# Wiznet5k Socket Registers |
|||
REG_SNMR = const(0x0000) # Socket n Mode |
|||
REG_SNCR = const(0x0001) # Socket n Command |
|||
REG_SNIR = const(0x0002) # Socket n Interrupt |
|||
REG_SNSR = const(0x0003) # Socket n Status |
|||
REG_SNPORT = const(0x0004) # Socket n Source Port |
|||
REG_SNDIPR = const(0x000C) # Destination IP Address |
|||
REG_SNDPORT = const(0x0010) # Destination Port |
|||
REG_SNRX_RSR = const(0x0026) # RX Free Size |
|||
REG_SNRX_RD = const(0x0028) # Read Size Pointer |
|||
REG_SNTX_FSR = const(0x0020) # Socket n TX Free Size |
|||
REG_SNTX_WR = const(0x0024) # TX Write Pointer |
|||
|
|||
# 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 |
|||
# 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 = (0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED) |
|||
|
|||
# Maximum number of sockets to support, differs between chip versions. |
|||
W5200_W5500_MAX_SOCK_NUM = const(0x08) |
|||
SOCKET_INVALID = const(255) |
|||
|
|||
# UDP socket struct. |
|||
UDP_SOCK = {"bytes_remaining": 0, "remote_ip": 0, "remote_port": 0} |
|||
|
|||
|
|||
class WIZNET5K: # pylint: disable=too-many-public-methods |
|||
"""Interface for WIZNET5K module. |
|||
:param ~busio.SPI spi_bus: The SPI bus the Wiznet module is connected to. |
|||
:param ~digitalio.DigitalInOut cs: Chip select pin. |
|||
:param ~digitalio.DigitalInOut rst: Optional reset pin. |
|||
:param bool is_dhcp: Whether to start DHCP automatically or not. |
|||
:param list mac: The Wiznet's MAC Address. |
|||
:param str hostname: The desired hostname, with optional {} to fill in MAC. |
|||
:param int dhcp_timeout: Timeout in seconds for DHCP response. |
|||
:param bool debug: Enable debugging output. |
|||
|
|||
""" |
|||
|
|||
TCP_MODE = const(0x21) |
|||
UDP_MODE = const(0x02) |
|||
TLS_MODE = const(0x03) # This is NOT currently implemented |
|||
|
|||
# pylint: disable=too-many-arguments |
|||
def __init__( |
|||
self, |
|||
spi_bus, |
|||
cs, |
|||
reset=None, |
|||
is_dhcp=True, |
|||
mac=DEFAULT_MAC, |
|||
hostname=None, |
|||
dhcp_timeout=30, |
|||
debug=False, |
|||
): |
|||
self._debug = debug |
|||
self._chip_type = None |
|||
self._device = SPIDevice(spi_bus, cs, baudrate=8000000, polarity=0, phase=0) |
|||
# init c.s. |
|||
self._cs = cs |
|||
|
|||
# reset wiznet module prior to initialization |
|||
if reset: |
|||
reset.value = True |
|||
time.sleep(0.1) |
|||
reset.value = False |
|||
time.sleep(0.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 |
|||
assert self._w5100_init() == 1, "Failed to initialize WIZnet module." |
|||
# Set MAC address |
|||
self.mac_address = mac |
|||
self._src_port = 0 |
|||
self._dns = 0 |
|||
|
|||
# Set DHCP |
|||
if is_dhcp: |
|||
ret = self.set_dhcp(hostname, dhcp_timeout) |
|||
assert ret == 0, "Failed to configure DHCP Server!" |
|||
|
|||
def set_dhcp(self, hostname=None, response_timeout=3): |
|||
"""Initializes the DHCP client and attempts to retrieve |
|||
and set network configuration from the DHCP server. |
|||
Returns True if DHCP configured, False otherwise. |
|||
:param str hostname: The desired hostname, with optional {} to fill in MAC. |
|||
:param int response_timeout: Time to wait for server to return packet, in seconds. |
|||
|
|||
""" |
|||
if self._debug: |
|||
print("* Initializing DHCP") |
|||
|
|||
# First, wait link status is on |
|||
# to avoid the code during DHCP - assert self.link_status, "Ethernet cable disconnected!" |
|||
start_time = time.monotonic() |
|||
while True: |
|||
if self.link_status or ((time.monotonic() - start_time) > 5) : |
|||
break |
|||
time.sleep(1) |
|||
if self._debug: |
|||
print("My Link is:", self.link_status) |
|||
|
|||
self._src_port = 68 |
|||
# Return IP assigned by DHCP |
|||
_dhcp_client = dhcp.DHCP( |
|||
self, self.mac_address, hostname, response_timeout, debug=self._debug |
|||
) |
|||
ret = _dhcp_client.request_dhcp_lease() |
|||
if ret == 1: |
|||
_ip = ( |
|||
_dhcp_client.local_ip[0], |
|||
_dhcp_client.local_ip[1], |
|||
_dhcp_client.local_ip[2], |
|||
_dhcp_client.local_ip[3], |
|||
) |
|||
|
|||
_subnet_mask = ( |
|||
_dhcp_client.subnet_mask[0], |
|||
_dhcp_client.subnet_mask[1], |
|||
_dhcp_client.subnet_mask[2], |
|||
_dhcp_client.subnet_mask[3], |
|||
) |
|||
|
|||
_gw_addr = ( |
|||
_dhcp_client.gateway_ip[0], |
|||
_dhcp_client.gateway_ip[1], |
|||
_dhcp_client.gateway_ip[2], |
|||
_dhcp_client.gateway_ip[3], |
|||
) |
|||
|
|||
self._dns = ( |
|||
_dhcp_client.dns_server_ip[0], |
|||
_dhcp_client.dns_server_ip[1], |
|||
_dhcp_client.dns_server_ip[2], |
|||
_dhcp_client.dns_server_ip[3], |
|||
) |
|||
self.ifconfig = (_ip, _subnet_mask, _gw_addr, self._dns) |
|||
if self._debug: |
|||
print("* Found DHCP Server:") |
|||
print( |
|||
"IP: {}\nSubnet Mask: {}\nGW Addr: {}\nDNS Server: {}".format( |
|||
_ip, _subnet_mask, _gw_addr, self._dns |
|||
) |
|||
) |
|||
self._src_port = 0 |
|||
return 0 |
|||
return -1 |
|||
|
|||
def get_host_by_name(self, hostname): |
|||
"""Convert a hostname to a packed 4-byte IP Address. |
|||
Returns a 4 bytearray. |
|||
""" |
|||
if self._debug: |
|||
print(hostname) |
|||
print("* Get host by name") |
|||
if isinstance(hostname, str): |
|||
hostname = bytes(hostname, "utf-8") |
|||
self._src_port = int(time.monotonic()) & 0xFFFF |
|||
# Return IP assigned by DHCP |
|||
_dns_client = dns.DNS(self, self._dns, debug=self._debug) |
|||
ret = _dns_client.gethostbyname(hostname) |
|||
if self._debug: |
|||
print("* Resolved IP: ", ret) |
|||
assert ret != -1, "Failed to resolve hostname!" |
|||
self._src_port = 0 |
|||
return ret |
|||
|
|||
@property |
|||
def max_sockets(self): |
|||
"""Returns max number of sockets supported by chip.""" |
|||
if self._chip_type == "w5500": |
|||
return W5200_W5500_MAX_SOCK_NUM |
|||
return -1 |
|||
|
|||
@property |
|||
def chip(self): |
|||
"""Returns the chip type.""" |
|||
return self._chip_type |
|||
|
|||
@property |
|||
def ip_address(self): |
|||
"""Returns the configured IP address.""" |
|||
return self.read(REG_SIPR, 0x00, 4) |
|||
|
|||
def pretty_ip(self, ip): # pylint: disable=no-self-use, invalid-name |
|||
"""Converts a bytearray IP address to a |
|||
dotted-quad string for printing |
|||
|
|||
""" |
|||
return "%d.%d.%d.%d" % (ip[0], ip[1], ip[2], ip[3]) |
|||
|
|||
def unpretty_ip(self, ip): # pylint: disable=no-self-use, invalid-name |
|||
"""Converts a dotted-quad string to a bytearray IP address""" |
|||
octets = [int(x) for x in ip.split(".")] |
|||
return bytes(octets) |
|||
|
|||
@property |
|||
def mac_address(self): |
|||
"""Returns the hardware's MAC address.""" |
|||
return self.read(REG_SHAR, 0x00, 6) |
|||
|
|||
@mac_address.setter |
|||
def mac_address(self, address): |
|||
"""Sets the hardware MAC address. |
|||
:param tuple address: Hardware MAC address. |
|||
|
|||
""" |
|||
self.write(REG_SHAR, 0x04, address) |
|||
|
|||
def pretty_mac(self, mac): # pylint: disable=no-self-use, invalid-name |
|||
"""Converts a bytearray MAC address to a |
|||
dotted-quad string for printing |
|||
|
|||
""" |
|||
return "%s:%s:%s:%s:%s:%s" % ( |
|||
hex(mac[0]), |
|||
hex(mac[1]), |
|||
hex(mac[2]), |
|||
hex(mac[3]), |
|||
hex(mac[4]), |
|||
hex(mac[5]), |
|||
) |
|||
|
|||
def remote_ip(self, socket_num): |
|||
"""Returns the IP address of the host who sent the current incoming packet. |
|||
:param int socket num: Desired socket. |
|||
|
|||
""" |
|||
if socket_num >= self.max_sockets: |
|||
return self._pbuff |
|||
for octet in range(0, 4): |
|||
self._pbuff[octet] = self._read_socket(socket_num, REG_SNDIPR + octet)[0] |
|||
return self.pretty_ip(self._pbuff) |
|||
|
|||
@property |
|||
def link_status(self): |
|||
""""Returns if the PHY is connected.""" |
|||
if self._chip_type == "w5500": |
|||
data = self.read(REG_PHYCFGR, 0x00) |
|||
return data[0] & 0x01 |
|||
return 0 |
|||
|
|||
def remote_port(self, socket_num): |
|||
"""Returns the port of the host who sent the current incoming packet.""" |
|||
if socket_num >= self.max_sockets: |
|||
return self._pbuff |
|||
for octet in range(0, 2): |
|||
self._pbuff[octet] = self._read_socket(socket_num, REG_SNDPORT + octet)[0] |
|||
return int((self._pbuff[0] << 8) | self._pbuff[0]) |
|||
|
|||
@property |
|||
def ifconfig(self): |
|||
"""Returns the network configuration as a tuple.""" |
|||
return ( |
|||
self.ip_address, |
|||
self.read(REG_SUBR, 0x00, 4), |
|||
self.read(REG_GAR, 0x00, 4), |
|||
self._dns, |
|||
) |
|||
|
|||
@ifconfig.setter |
|||
def ifconfig(self, params): |
|||
"""Sets network configuration to provided tuple in format: |
|||
(ip_address, subnet_mask, gateway_address, dns_server). |
|||
|
|||
""" |
|||
ip_address, subnet_mask, gateway_address, dns_server = params |
|||
|
|||
self.write(REG_SIPR, 0x04, ip_address) |
|||
self.write(REG_SUBR, 0x04, subnet_mask) |
|||
self.write(REG_GAR, 0x04, gateway_address) |
|||
|
|||
self._dns = dns_server |
|||
|
|||
def _w5100_init(self): |
|||
"""Initializes and detects a wiznet5k module.""" |
|||
time.sleep(1) |
|||
self._cs.switch_to_output() |
|||
self._cs.value = 1 |
|||
|
|||
# Detect if chip is Wiznet W5500 |
|||
if self.detect_w5500() == 1: |
|||
# perform w5500 initialization |
|||
for i in range(0, W5200_W5500_MAX_SOCK_NUM): |
|||
ctrl_byte = 0x0C + (i << 5) |
|||
self.write(0x1E, ctrl_byte, 2) |
|||
self.write(0x1F, ctrl_byte, 2) |
|||
else: |
|||
return 0 |
|||
return 1 |
|||
|
|||
def detect_w5500(self): |
|||
"""Detects W5500 chip.""" |
|||
assert self.sw_reset() == 0, "Chip not reset properly!" |
|||
self._write_mr(0x08) |
|||
assert self._read_mr()[0] == 0x08, "Expected 0x08." |
|||
|
|||
self._write_mr(0x10) |
|||
assert self._read_mr()[0] == 0x10, "Expected 0x10." |
|||
|
|||
self._write_mr(0x00) |
|||
assert self._read_mr()[0] == 0x00, "Expected 0x00." |
|||
|
|||
if self.read(REG_VERSIONR_W5500, 0x00)[0] != 0x04: |
|||
return -1 |
|||
self._chip_type = "w5500" |
|||
self._ch_base_msb = 0x10 |
|||
return 1 |
|||
|
|||
def sw_reset(self): |
|||
"""Performs a soft-reset on a Wiznet chip |
|||
by writing to its MR register reset bit. |
|||
|
|||
""" |
|||
mode_reg = self._read_mr() |
|||
self._write_mr(0x80) |
|||
mode_reg = self._read_mr() |
|||
if mode_reg[0] != 0x00: |
|||
return -1 |
|||
return 0 |
|||
|
|||
def _read_mr(self): |
|||
"""Reads from the Mode Register (MR).""" |
|||
res = self.read(REG_MR, 0x00) |
|||
return res |
|||
|
|||
def _write_mr(self, data): |
|||
"""Writes to the mode register (MR). |
|||
:param int data: Data to write to the mode register. |
|||
|
|||
""" |
|||
self.write(REG_MR, 0x04, data) |
|||
|
|||
def read(self, addr, callback, length=1, buffer=None): |
|||
"""Reads data from a register address. |
|||
:param int addr: Register address. |
|||
|
|||
""" |
|||
with self._device as bus_device: |
|||
bus_device.write(bytes([addr >> 8])) # pylint: disable=no-member |
|||
bus_device.write(bytes([addr & 0xFF])) # pylint: disable=no-member |
|||
bus_device.write(bytes([callback])) # pylint: disable=no-member |
|||
if buffer is None: |
|||
self._rxbuf = bytearray(length) |
|||
bus_device.readinto(self._rxbuf) # pylint: disable=no-member |
|||
return self._rxbuf |
|||
bus_device.readinto(buffer, end=length) # pylint: disable=no-member |
|||
return buffer |
|||
|
|||
def write(self, addr, callback, data): |
|||
"""Write data to a register address. |
|||
:param int addr: Destination address. |
|||
:param int callback: Callback reference. |
|||
:param int data: Data to write, as an integer. |
|||
:param bytearray data: Data to write, as a bytearray. |
|||
|
|||
""" |
|||
with self._device as bus_device: |
|||
bus_device.write(bytes([addr >> 8])) # pylint: disable=no-member |
|||
bus_device.write(bytes([addr & 0xFF])) # pylint: disable=no-member |
|||
bus_device.write(bytes([callback])) # pylint: disable=no-member |
|||
|
|||
if hasattr(data, "from_bytes"): |
|||
bus_device.write(bytes([data])) # pylint: disable=no-member |
|||
else: |
|||
for i, _ in enumerate(data): |
|||
bus_device.write(bytes([data[i]])) # pylint: disable=no-member |
|||
|
|||
# Socket-Register API |
|||
def udp_remaining(self): |
|||
"""Returns amount of bytes remaining in a udp socket.""" |
|||
if self._debug: |
|||
print("* UDP Bytes Remaining: ", UDP_SOCK["bytes_remaining"]) |
|||
return UDP_SOCK["bytes_remaining"] |
|||
|
|||
def socket_available(self, socket_num, sock_type=SNMR_TCP): |
|||
"""Returns the amount of bytes to be read from the socket. |
|||
|
|||
:param int socket_num: Desired socket to return bytes from. |
|||
:param int sock_type: Socket type, defaults to TCP. |
|||
""" |
|||
if self._debug: |
|||
print("* socket_available called with protocol", sock_type) |
|||
assert socket_num <= self.max_sockets, "Provided socket exceeds max_sockets." |
|||
|
|||
res = self._get_rx_rcv_size(socket_num) |
|||
|
|||
if sock_type == SNMR_TCP: |
|||
return res |
|||
if res > 0: |
|||
if UDP_SOCK["bytes_remaining"]: |
|||
return UDP_SOCK["bytes_remaining"] |
|||
# parse the udp rx packet |
|||
# read the first 8 header bytes |
|||
ret, self._pbuff = self.socket_read(socket_num, 8) |
|||
if ret > 0: |
|||
UDP_SOCK["remote_ip"] = self._pbuff[:4] |
|||
UDP_SOCK["remote_port"] = (self._pbuff[4] << 8) + self._pbuff[5] |
|||
UDP_SOCK["bytes_remaining"] = (self._pbuff[6] << 8) + self._pbuff[7] |
|||
ret = UDP_SOCK["bytes_remaining"] |
|||
return ret |
|||
return 0 |
|||
|
|||
def socket_status(self, socket_num): |
|||
"""Returns the 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. |
|||
""" |
|||
return self._read_snsr(socket_num) |
|||
|
|||
def socket_connect(self, socket_num, dest, port, conn_mode=SNMR_TCP): |
|||
"""Open and verify we've connected a socket to a dest IP address |
|||
or hostname. By default, we use 'conn_mode'= SNMR_TCP but we |
|||
may also use SNMR_UDP. |
|||
""" |
|||
assert self.link_status, "Ethernet cable disconnected!" |
|||
if self._debug: |
|||
print( |
|||
"* w5k socket connect, protocol={}, port={}, ip={}".format( |
|||
conn_mode, port, self.pretty_ip(dest) |
|||
) |
|||
) |
|||
# initialize a socket and set the mode |
|||
res = self.socket_open(socket_num, conn_mode=conn_mode) |
|||
if res == 1: |
|||
raise RuntimeError("Failed to initalize a connection with the socket.") |
|||
|
|||
# set socket destination IP and port |
|||
self._write_sndipr(socket_num, dest) |
|||
self._write_sndport(socket_num, port) |
|||
self._send_socket_cmd(socket_num, CMD_SOCK_CONNECT) |
|||
|
|||
if conn_mode == SNMR_TCP: |
|||
# wait for tcp connection establishment |
|||
while self.socket_status(socket_num)[0] != SNSR_SOCK_ESTABLISHED: |
|||
time.sleep(0.001) |
|||
if self._debug: |
|||
print("SN_SR:", self.socket_status(socket_num)[0]) |
|||
if self.socket_status(socket_num)[0] == SNSR_SOCK_CLOSED: |
|||
raise RuntimeError("Failed to establish connection.") |
|||
elif conn_mode == SNMR_UDP: |
|||
UDP_SOCK["bytes_remaining"] = 0 |
|||
return 1 |
|||
|
|||
def _send_socket_cmd(self, socket, cmd): |
|||
self._write_sncr(socket, cmd) |
|||
while self._read_sncr(socket) != b"\x00": |
|||
if self._debug: |
|||
print("waiting for sncr to clear...") |
|||
|
|||
def get_socket(self): |
|||
"""Requests, allocates and returns a socket from the W5k |
|||
chip. Returned socket number may not exceed max_sockets. |
|||
""" |
|||
if self._debug: |
|||
print("*** Get socket") |
|||
|
|||
sock = SOCKET_INVALID |
|||
for _sock in range(self.max_sockets): |
|||
status = self.socket_status(_sock)[0] |
|||
if status in ( |
|||
SNSR_SOCK_CLOSED, |
|||
SNSR_SOCK_TIME_WAIT, |
|||
SNSR_SOCK_FIN_WAIT, |
|||
SNSR_SOCK_CLOSE_WAIT, |
|||
SNSR_SOCK_CLOSING, |
|||
): |
|||
sock = _sock |
|||
break |
|||
|
|||
if self._debug: |
|||
print("Allocated socket #{}".format(sock)) |
|||
return sock |
|||
|
|||
def socket_listen(self, socket_num, port): |
|||
"""Start listening on a socket (TCP mode only). |
|||
:parm int socket_num: socket number |
|||
:parm int port: port to listen on |
|||
""" |
|||
assert self.link_status, "Ethernet cable disconnected!" |
|||
if self._debug: |
|||
print( |
|||
"* Listening on port={}, ip={}".format( |
|||
port, self.pretty_ip(self.ip_address) |
|||
) |
|||
) |
|||
# Initialize a socket and set the mode |
|||
self._src_port = port |
|||
res = self.socket_open(socket_num, conn_mode=SNMR_TCP) |
|||
if res == 1: |
|||
raise RuntimeError("Failed to initalize the socket.") |
|||
# Send listen command |
|||
self._send_socket_cmd(socket_num, CMD_SOCK_LISTEN) |
|||
# Wait until ready |
|||
status = [SNSR_SOCK_CLOSED] |
|||
while status[0] != SNSR_SOCK_LISTEN: |
|||
status = self._read_snsr(socket_num) |
|||
if status[0] == SNSR_SOCK_CLOSED: |
|||
raise RuntimeError("Listening socket closed.") |
|||
|
|||
def socket_accept(self, socket_num): |
|||
"""Gets the dest IP and port from an incoming connection. |
|||
Returns the next socket number so listening can continue |
|||
:parm int socket_num: socket number |
|||
""" |
|||
dest_ip = self.remote_ip(socket_num) |
|||
dest_port = self.remote_port(socket_num) |
|||
next_socknum = self.get_socket() |
|||
if self._debug: |
|||
print( |
|||
"* Dest is ({}, {}), Next listen socknum is #{}".format( |
|||
dest_ip, dest_port, next_socknum |
|||
) |
|||
) |
|||
return next_socknum, (dest_ip, dest_port) |
|||
|
|||
def socket_open(self, socket_num, conn_mode=SNMR_TCP): |
|||
"""Opens a TCP or UDP socket. By default, we use |
|||
'conn_mode'=SNMR_TCP but we may also use SNMR_UDP. |
|||
""" |
|||
assert self.link_status, "Ethernet cable disconnected!" |
|||
if self._debug: |
|||
print("*** Opening socket %d" % socket_num) |
|||
status = self._read_snsr(socket_num)[0] |
|||
if status in ( |
|||
SNSR_SOCK_CLOSED, |
|||
SNSR_SOCK_TIME_WAIT, |
|||
SNSR_SOCK_FIN_WAIT, |
|||
SNSR_SOCK_CLOSE_WAIT, |
|||
SNSR_SOCK_CLOSING, |
|||
): |
|||
if self._debug: |
|||
print("* Opening W5k Socket, protocol={}".format(conn_mode)) |
|||
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: |
|||
self._write_sock_port(socket_num, randint(49152, 65535)) |
|||
|
|||
# open socket |
|||
self._write_sncr(socket_num, CMD_SOCK_OPEN) |
|||
self._read_sncr(socket_num) |
|||
assert ( |
|||
self._read_snsr((socket_num))[0] == 0x13 |
|||
or self._read_snsr((socket_num))[0] == 0x22 |
|||
), "Could not open socket in TCP or UDP mode." |
|||
return 0 |
|||
return 1 |
|||
|
|||
def socket_close(self, socket_num): |
|||
"""Closes a socket.""" |
|||
if self._debug: |
|||
print("*** Closing socket #%d" % socket_num) |
|||
self._write_sncr(socket_num, CMD_SOCK_CLOSE) |
|||
self._read_sncr(socket_num) |
|||
|
|||
def socket_disconnect(self, socket_num): |
|||
"""Disconnect a TCP connection.""" |
|||
if self._debug: |
|||
print("*** Disconnecting socket #%d" % socket_num) |
|||
self._write_sncr(socket_num, CMD_SOCK_DISCON) |
|||
self._read_sncr(socket_num) |
|||
|
|||
def socket_read(self, socket_num, length): |
|||
"""Reads data from a socket into a buffer. |
|||
Returns buffer. |
|||
|
|||
""" |
|||
assert self.link_status, "Ethernet cable disconnected!" |
|||
assert socket_num <= self.max_sockets, "Provided socket exceeds max_sockets." |
|||
|
|||
# Check if there is data available on the socket |
|||
ret = self._get_rx_rcv_size(socket_num) |
|||
if self._debug: |
|||
print("Bytes avail. on sock: ", ret) |
|||
if ret == 0: |
|||
# no data on socket? |
|||
status = self._read_snsr(socket_num) |
|||
if status in (SNSR_SOCK_LISTEN, SNSR_SOCK_CLOSED, SNSR_SOCK_CLOSE_WAIT): |
|||
# remote end closed its side of the connection, EOF state |
|||
ret = 0 |
|||
resp = 0 |
|||
else: |
|||
# connection is alive, no data waiting to be read |
|||
ret = -1 |
|||
resp = -1 |
|||
elif ret > length: |
|||
# set ret to the length of buffer |
|||
ret = length |
|||
|
|||
if ret > 0: |
|||
if self._debug: |
|||
print("\t * Processing {} bytes of data".format(ret)) |
|||
# Read the starting save address of the received data |
|||
ptr = self._read_snrx_rd(socket_num) |
|||
|
|||
# Read data from the starting address of snrx_rd |
|||
ctrl_byte = 0x18 + (socket_num << 5) |
|||
|
|||
resp = self.read(ptr, ctrl_byte, ret) |
|||
|
|||
# After reading the received data, update Sn_RX_RD to the increased |
|||
# value as many as the reading size. |
|||
ptr = (ptr + ret) & 0xFFFF |
|||
self._write_snrx_rd(socket_num, ptr) |
|||
|
|||
# Notify the W5k of the updated Sn_Rx_RD |
|||
self._write_sncr(socket_num, CMD_SOCK_RECV) |
|||
self._read_sncr(socket_num) |
|||
return ret, resp |
|||
|
|||
def read_udp(self, socket_num, length): |
|||
"""Read UDP socket's remaining bytes.""" |
|||
if UDP_SOCK["bytes_remaining"] > 0: |
|||
if UDP_SOCK["bytes_remaining"] <= length: |
|||
ret, resp = self.socket_read(socket_num, UDP_SOCK["bytes_remaining"]) |
|||
else: |
|||
ret, resp = self.socket_read(socket_num, length) |
|||
if ret > 0: |
|||
UDP_SOCK["bytes_remaining"] -= ret |
|||
return ret, resp |
|||
return -1 |
|||
|
|||
def socket_write(self, socket_num, buffer, timeout=0): |
|||
"""Writes a bytearray to a provided socket.""" |
|||
assert self.link_status, "Ethernet cable disconnected!" |
|||
assert socket_num <= self.max_sockets, "Provided socket exceeds max_sockets." |
|||
status = 0 |
|||
ret = 0 |
|||
free_size = 0 |
|||
if len(buffer) > SOCK_SIZE: |
|||
ret = SOCK_SIZE |
|||
else: |
|||
ret = len(buffer) |
|||
stamp = time.monotonic() |
|||
|
|||
# if buffer is available, start the transfer |
|||
free_size = self._get_tx_free_size(socket_num) |
|||
while free_size < ret: |
|||
free_size = self._get_tx_free_size(socket_num) |
|||
status = self.socket_status(socket_num)[0] |
|||
if status not in (SNSR_SOCK_ESTABLISHED, SNSR_SOCK_CLOSE_WAIT) or ( |
|||
timeout and time.monotonic() - stamp > timeout |
|||
): |
|||
ret = 0 |
|||
break |
|||
|
|||
# Read the starting address for saving the transmitting data. |
|||
ptr = self._read_sntx_wr(socket_num) |
|||
offset = ptr & 0x07FF |
|||
dst_addr = offset + (socket_num * 2048 + 0x8000) |
|||
|
|||
# update sn_tx_wr to the value + data size |
|||
ptr = (ptr + len(buffer)) & 0xFFFF |
|||
self._write_sntx_wr(socket_num, ptr) |
|||
|
|||
cntl_byte = 0x14 + (socket_num << 5) |
|||
self.write(dst_addr, cntl_byte, buffer) |
|||
|
|||
self._write_sncr(socket_num, CMD_SOCK_SEND) |
|||
self._read_sncr(socket_num) |
|||
|
|||
# check data was transferred correctly |
|||
while ( |
|||
self._read_socket(socket_num, REG_SNIR)[0] & SNIR_SEND_OK |
|||
) != SNIR_SEND_OK: |
|||
if ( |
|||
self.socket_status(socket_num)[0] |
|||
in ( |
|||
SNSR_SOCK_CLOSED, |
|||
SNSR_SOCK_TIME_WAIT, |
|||
SNSR_SOCK_FIN_WAIT, |
|||
SNSR_SOCK_CLOSE_WAIT, |
|||
SNSR_SOCK_CLOSING, |
|||
) |
|||
or (timeout and time.monotonic() - stamp > timeout) |
|||
): |
|||
# self.socket_close(socket_num) |
|||
return 0 |
|||
time.sleep(0.01) |
|||
|
|||
self._write_snir(socket_num, SNIR_SEND_OK) |
|||
return ret |
|||
|
|||
# Socket-Register Methods |
|||
|
|||
def _get_rx_rcv_size(self, sock): |
|||
"""Get size of recieved 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 int.from_bytes(val, "b") |
|||
|
|||
def _get_tx_free_size(self, sock): |
|||
"""Get free size of sock'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 int.from_bytes(val, "b") |
|||
|
|||
def _read_snrx_rd(self, sock): |
|||
self._pbuff[0] = self._read_socket(sock, REG_SNRX_RD)[0] |
|||
self._pbuff[1] = self._read_socket(sock, REG_SNRX_RD + 1)[0] |
|||
return self._pbuff[0] << 8 | self._pbuff[1] |
|||
|
|||
def _write_snrx_rd(self, sock, data): |
|||
self._write_socket(sock, REG_SNRX_RD, data >> 8) |
|||
self._write_socket(sock, REG_SNRX_RD + 1, data & 0xFF) |
|||
|
|||
def _write_sntx_wr(self, sock, data): |
|||
self._write_socket(sock, REG_SNTX_WR, data >> 8) |
|||
self._write_socket(sock, REG_SNTX_WR + 1, data & 0xFF) |
|||
|
|||
def _read_sntx_wr(self, sock): |
|||
self._pbuff[0] = self._read_socket(sock, 0x0024)[0] |
|||
self._pbuff[1] = self._read_socket(sock, 0x0024 + 1)[0] |
|||
return self._pbuff[0] << 8 | self._pbuff[1] |
|||
|
|||
def _read_sntx_fsr(self, sock): |
|||
data = self._read_socket(sock, REG_SNTX_FSR) |
|||
data += self._read_socket(sock, REG_SNTX_FSR + 1) |
|||
return data |
|||
|
|||
def _read_snrx_rsr(self, sock): |
|||
data = self._read_socket(sock, REG_SNRX_RSR) |
|||
data += self._read_socket(sock, REG_SNRX_RSR + 1) |
|||
return data |
|||
|
|||
def _write_sndipr(self, sock, ip_addr): |
|||
"""Writes to socket destination IP Address.""" |
|||
for octet in range(0, 4): |
|||
self._write_socket(sock, REG_SNDIPR + octet, ip_addr[octet]) |
|||
|
|||
def _write_sndport(self, sock, port): |
|||
"""Writes to socket destination port.""" |
|||
self._write_socket(sock, REG_SNDPORT, port >> 8) |
|||
self._write_socket(sock, REG_SNDPORT + 1, port & 0xFF) |
|||
|
|||
def _read_snsr(self, sock): |
|||
"""Reads Socket n Status Register.""" |
|||
return self._read_socket(sock, REG_SNSR) |
|||
|
|||
def _write_snmr(self, sock, protocol): |
|||
"""Write to Socket n Mode Register.""" |
|||
self._write_socket(sock, REG_SNMR, protocol) |
|||
|
|||
def _write_snir(self, sock, data): |
|||
"""Write to Socket n Interrupt Register.""" |
|||
self._write_socket(sock, REG_SNIR, data) |
|||
|
|||
def _write_sock_port(self, sock, port): |
|||
"""Write to the socket port number.""" |
|||
self._write_socket(sock, REG_SNPORT, port >> 8) |
|||
self._write_socket(sock, REG_SNPORT + 1, port & 0xFF) |
|||
|
|||
def _write_sncr(self, sock, data): |
|||
self._write_socket(sock, REG_SNCR, data) |
|||
|
|||
def _read_sncr(self, sock): |
|||
return self._read_socket(sock, REG_SNCR) |
|||
|
|||
def _read_snmr(self, sock): |
|||
return self._read_socket(sock, REG_SNMR) |
|||
|
|||
def _write_socket(self, sock, address, data): |
|||
"""Write to a W5k socket register.""" |
|||
base = self._ch_base_msb << 8 |
|||
cntl_byte = (sock << 5) + 0x0C |
|||
return self.write(base + sock * CH_SIZE + address, cntl_byte, data) |
|||
|
|||
def _read_socket(self, sock, address): |
|||
"""Read a W5k socket register.""" |
|||
cntl_byte = (sock << 5) + 0x08 |
|||
return self.read(address, cntl_byte) |
@ -0,0 +1,415 @@ |
|||
# SPDX-FileCopyrightText: 2009 Jordan Terell (blog.jordanterrell.com) |
|||
# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries |
|||
# |
|||
# SPDX-License-Identifier: MIT |
|||
|
|||
""" |
|||
`adafruit_wiznet5k_dhcp` |
|||
================================================================================ |
|||
|
|||
Pure-Python implementation of Jordan Terrell's DHCP library v0.3 |
|||
|
|||
* Author(s): Jordan Terrell, Brent Rubell |
|||
|
|||
""" |
|||
import gc |
|||
import time |
|||
from random import randrange |
|||
from micropython import const |
|||
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket |
|||
from adafruit_wiznet5k.adafruit_wiznet5k_socket import htonl, htons |
|||
|
|||
|
|||
# DHCP State Machine |
|||
STATE_DHCP_START = const(0x00) |
|||
STATE_DHCP_DISCOVER = const(0x01) |
|||
STATE_DHCP_REQUEST = const(0x02) |
|||
STATE_DHCP_LEASED = const(0x03) |
|||
STATE_DHCP_REREQUEST = const(0x04) |
|||
STATE_DHCP_RELEASE = const(0x05) |
|||
|
|||
# DHCP Message Types |
|||
DHCP_DISCOVER = const(1) |
|||
DHCP_OFFER = const(2) |
|||
DHCP_REQUEST = const(3) |
|||
DHCP_DECLINE = const(4) |
|||
DHCP_ACK = const(5) |
|||
DHCP_NAK = const(6) |
|||
DHCP_RELEASE = const(7) |
|||
DHCP_INFORM = const(8) |
|||
|
|||
# DHCP Message OP Codes |
|||
DHCP_BOOT_REQUEST = const(0x01) |
|||
DHCP_BOOT_REPLY = const(0x02) |
|||
|
|||
DHCP_HTYPE10MB = const(0x01) |
|||
DHCP_HTYPE100MB = const(0x02) |
|||
|
|||
DHCP_HLENETHERNET = const(0x06) |
|||
DHCP_HOPS = const(0x00) |
|||
|
|||
MAGIC_COOKIE = const(0x63825363) |
|||
MAX_DHCP_OPT = const(0x10) |
|||
|
|||
# Default DHCP Server port |
|||
DHCP_SERVER_PORT = const(67) |
|||
# DHCP Lease Time, in seconds |
|||
DEFAULT_LEASE_TIME = const(900) |
|||
BROADCAST_SERVER_ADDR = "255.255.255.255" |
|||
|
|||
# DHCP Response Options |
|||
MSG_TYPE = 53 |
|||
SUBNET_MASK = 1 |
|||
ROUTERS_ON_SUBNET = 3 |
|||
DNS_SERVERS = 6 |
|||
DHCP_SERVER_ID = 54 |
|||
T1_VAL = 58 |
|||
T2_VAL = 59 |
|||
LEASE_TIME = 51 |
|||
OPT_END = 255 |
|||
|
|||
|
|||
_BUFF = bytearray(317) |
|||
|
|||
|
|||
class DHCP: |
|||
"""W5k DHCP Client implementation. |
|||
:param eth: Wiznet 5k object |
|||
:param list mac_address: Hardware MAC. |
|||
:param str hostname: The desired hostname, with optional {} to fill in MAC. |
|||
:param int response_timeout: DHCP Response timeout. |
|||
:param bool debug: Enable debugging output. |
|||
|
|||
""" |
|||
|
|||
# pylint: disable=too-many-arguments, too-many-instance-attributes, invalid-name |
|||
def __init__( |
|||
self, eth, mac_address, hostname=None, response_timeout=30, debug=False |
|||
): |
|||
self._debug = debug |
|||
self._response_timeout = response_timeout |
|||
self._mac_address = mac_address |
|||
|
|||
# Initalize a new UDP socket for DHCP |
|||
socket.set_interface(eth) |
|||
self._sock = socket.socket(type=socket.SOCK_DGRAM) |
|||
self._sock.settimeout(response_timeout) |
|||
|
|||
# DHCP state machine |
|||
self._dhcp_state = STATE_DHCP_START |
|||
self._initial_xid = 0 |
|||
self._transaction_id = 0 |
|||
|
|||
# DHCP server configuration |
|||
self.dhcp_server_ip = 0 |
|||
self.local_ip = 0 |
|||
self.gateway_ip = 0 |
|||
self.subnet_mask = 0 |
|||
self.dns_server_ip = 0 |
|||
|
|||
# Lease configuration |
|||
self._lease_time = 0 |
|||
self._last_check_lease_ms = 0 |
|||
self._renew_in_sec = 0 |
|||
self._rebind_in_sec = 0 |
|||
self._t1 = 0 |
|||
self._t2 = 0 |
|||
|
|||
# Host name |
|||
mac_string = "".join("{:02X}".format(o) for o in mac_address) |
|||
self._hostname = bytes( |
|||
(hostname or "WIZnet{}").split(".")[0].format(mac_string)[:42], "utf-8" |
|||
) |
|||
|
|||
def send_dhcp_message(self, state, time_elapsed): |
|||
"""Assemble and send a DHCP message packet to a socket. |
|||
:param int state: DHCP Message state. |
|||
:param float time_elapsed: Number of seconds elapsed since renewal. |
|||
|
|||
""" |
|||
# before making send packet, shoule init _BUFF. |
|||
# if not, DHCP sometimes fails, wrong padding, garbage bytes, ... |
|||
_BUFF[:] = b'\x00' * len(_BUFF) |
|||
|
|||
# OP |
|||
_BUFF[0] = DHCP_BOOT_REQUEST |
|||
# HTYPE |
|||
_BUFF[1] = DHCP_HTYPE10MB |
|||
# HLEN |
|||
_BUFF[2] = DHCP_HLENETHERNET |
|||
# HOPS |
|||
_BUFF[3] = DHCP_HOPS |
|||
|
|||
# Transaction ID (xid) |
|||
self._initial_xid = htonl(self._transaction_id) |
|||
self._initial_xid = self._initial_xid.to_bytes(4, "l") |
|||
_BUFF[4:7] = self._initial_xid |
|||
|
|||
# seconds elapsed |
|||
_BUFF[8] = (int(time_elapsed) & 0xFF00) >> 8 |
|||
_BUFF[9] = int(time_elapsed) & 0x00FF |
|||
|
|||
# flags |
|||
flags = htons(0x8000) |
|||
flags = flags.to_bytes(2, "b") |
|||
_BUFF[10] = flags[1] |
|||
_BUFF[11] = flags[0] |
|||
|
|||
# NOTE: Skipping cidaddr/yiaddr/siaddr/giaddr |
|||
# as they're already set to 0.0.0.0 |
|||
|
|||
# chaddr |
|||
_BUFF[28:34] = self._mac_address |
|||
|
|||
# NOTE: 192 octets of 0's, BOOTP legacy |
|||
|
|||
# Magic Cookie |
|||
_BUFF[236] = (MAGIC_COOKIE >> 24) & 0xFF |
|||
_BUFF[237] = (MAGIC_COOKIE >> 16) & 0xFF |
|||
_BUFF[238] = (MAGIC_COOKIE >> 8) & 0xFF |
|||
_BUFF[239] = MAGIC_COOKIE & 0xFF |
|||
|
|||
# Option - DHCP Message Type |
|||
_BUFF[240] = 53 |
|||
_BUFF[241] = 0x01 |
|||
_BUFF[242] = state |
|||
|
|||
# Option - Client Identifier |
|||
_BUFF[243] = 61 |
|||
# Length |
|||
_BUFF[244] = 0x07 |
|||
# HW Type - ETH |
|||
_BUFF[245] = 0x01 |
|||
# Client MAC Address |
|||
for mac in range(0, len(self._mac_address)): |
|||
_BUFF[246 + mac] = self._mac_address[mac] |
|||
|
|||
# Option - Host Name |
|||
_BUFF[252] = 12 |
|||
hostname_len = len(self._hostname) |
|||
after_hostname = 254 + hostname_len |
|||
_BUFF[253] = hostname_len |
|||
_BUFF[254:after_hostname] = self._hostname |
|||
|
|||
if state == DHCP_REQUEST: |
|||
# Set the parsed local IP addr |
|||
_BUFF[after_hostname] = 50 |
|||
_BUFF[after_hostname + 1] = 0x04 |
|||
|
|||
_BUFF[after_hostname + 2 : after_hostname + 6] = self.local_ip |
|||
# Set the parsed dhcp server ip addr |
|||
_BUFF[after_hostname + 6] = 54 |
|||
_BUFF[after_hostname + 7] = 0x04 |
|||
_BUFF[after_hostname + 8 : after_hostname + 12] = self.dhcp_server_ip |
|||
|
|||
_BUFF[after_hostname + 12] = 55 |
|||
_BUFF[after_hostname + 13] = 0x06 |
|||
# subnet mask |
|||
_BUFF[after_hostname + 14] = 1 |
|||
# routers on subnet |
|||
_BUFF[after_hostname + 15] = 3 |
|||
# DNS |
|||
_BUFF[after_hostname + 16] = 6 |
|||
# domain name |
|||
_BUFF[after_hostname + 17] = 15 |
|||
# renewal (T1) value |
|||
_BUFF[after_hostname + 18] = 58 |
|||
# rebinding (T2) value |
|||
_BUFF[after_hostname + 19] = 59 |
|||
_BUFF[after_hostname + 20] = 255 |
|||
|
|||
# Send DHCP packet |
|||
self._sock.send(_BUFF) |
|||
|
|||
def parse_dhcp_response( |
|||
self, response_timeout |
|||
): # pylint: disable=too-many-branches, too-many-statements |
|||
"""Parse DHCP response from DHCP server. |
|||
Returns DHCP packet type. |
|||
|
|||
:param int response_timeout: Time to wait for server to return packet, in seconds. |
|||
""" |
|||
start_time = time.monotonic() |
|||
packet_sz = self._sock.available() |
|||
while packet_sz <= 0: |
|||
packet_sz = self._sock.available() |
|||
if (time.monotonic() - start_time) > response_timeout: |
|||
return (255, 0) |
|||
time.sleep(0.05) |
|||
# store packet in buffer |
|||
_BUFF = self._sock.recv() |
|||
if self._debug: |
|||
print("DHCP Response: ", _BUFF) |
|||
|
|||
# -- Parse Packet, FIXED -- # |
|||
# Validate OP |
|||
assert ( |
|||
_BUFF[0] == DHCP_BOOT_REPLY |
|||
), "Malformed Packet - \ |
|||
DHCP message OP is not expected BOOT Reply." |
|||
|
|||
xid = _BUFF[4:8] |
|||
if bytes(xid) < self._initial_xid: |
|||
print("f") |
|||
return 0, 0 |
|||
|
|||
self.local_ip = _BUFF[16:20] |
|||
if _BUFF[28:34] == 0: |
|||
return 0, 0 |
|||
|
|||
if int.from_bytes(_BUFF[235:240], "l") != MAGIC_COOKIE: |
|||
return 0, 0 |
|||
|
|||
# -- Parse Packet, VARIABLE -- # |
|||
ptr = 240 |
|||
while _BUFF[ptr] != OPT_END: |
|||
if _BUFF[ptr] == MSG_TYPE: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += opt_len |
|||
msg_type = _BUFF[ptr] |
|||
ptr += 1 |
|||
elif _BUFF[ptr] == SUBNET_MASK: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
self.subnet_mask = _BUFF[ptr : ptr + opt_len] |
|||
ptr += opt_len |
|||
elif _BUFF[ptr] == DHCP_SERVER_ID: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
self.dhcp_server_ip = _BUFF[ptr : ptr + opt_len] |
|||
ptr += opt_len |
|||
elif _BUFF[ptr] == LEASE_TIME: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
self._lease_time = int.from_bytes(_BUFF[ptr : ptr + opt_len], "l") |
|||
ptr += opt_len |
|||
elif _BUFF[ptr] == ROUTERS_ON_SUBNET: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
self.gateway_ip = _BUFF[ptr : ptr + opt_len] |
|||
ptr += opt_len |
|||
elif _BUFF[ptr] == DNS_SERVERS: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
self.dns_server_ip = _BUFF[ptr : ptr + 4] |
|||
ptr += opt_len # still increment even though we only read 1 addr. |
|||
elif _BUFF[ptr] == T1_VAL: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
self._t1 = int.from_bytes(_BUFF[ptr : ptr + opt_len], "l") |
|||
ptr += opt_len |
|||
elif _BUFF[ptr] == T2_VAL: |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
self._t2 = int.from_bytes(_BUFF[ptr : ptr + opt_len], "l") |
|||
ptr += opt_len |
|||
elif _BUFF[ptr] == 0: |
|||
break |
|||
else: |
|||
# We're not interested in this option |
|||
ptr += 1 |
|||
opt_len = _BUFF[ptr] |
|||
ptr += 1 |
|||
# no-op |
|||
ptr += opt_len |
|||
|
|||
if self._debug: |
|||
print( |
|||
"Msg Type: {}\nSubnet Mask: {}\nDHCP Server ID:{}\nDNS Server IP:{}\ |
|||
\nGateway IP:{}\nT1:{}\nT2:{}\nLease Time:{}".format( |
|||
msg_type, |
|||
self.subnet_mask, |
|||
self.dhcp_server_ip, |
|||
self.dns_server_ip, |
|||
self.gateway_ip, |
|||
self._t1, |
|||
self._t2, |
|||
self._lease_time, |
|||
) |
|||
) |
|||
|
|||
gc.collect() |
|||
return msg_type, xid |
|||
|
|||
def request_dhcp_lease( |
|||
self, |
|||
): # pylint: disable=too-many-branches, too-many-statements |
|||
"""Request to renew or acquire a DHCP lease.""" |
|||
# select an initial transaction id |
|||
self._transaction_id = randrange(1, 2000) |
|||
|
|||
result = 0 |
|||
msg_type = 0 |
|||
start_time = time.monotonic() |
|||
|
|||
while self._dhcp_state != STATE_DHCP_LEASED: |
|||
if self._dhcp_state == STATE_DHCP_START: |
|||
self._transaction_id += 1 |
|||
self._sock.connect(((BROADCAST_SERVER_ADDR), DHCP_SERVER_PORT)) |
|||
if self._debug: |
|||
print("* DHCP: Discover") |
|||
self.send_dhcp_message( |
|||
STATE_DHCP_DISCOVER, ((time.monotonic() - start_time) / 1000) |
|||
) |
|||
self._dhcp_state = STATE_DHCP_DISCOVER |
|||
elif self._dhcp_state == STATE_DHCP_DISCOVER: |
|||
if self._debug: |
|||
print("* DHCP: Parsing OFFER") |
|||
msg_type, xid = self.parse_dhcp_response(self._response_timeout) |
|||
if msg_type == DHCP_OFFER: |
|||
# # use the _transaction_id the offer returned, |
|||
# # rather than the current one |
|||
# self._transaction_id = self._transaction_id.from_bytes(xid, "l") |
|||
if self._debug: |
|||
print("* DHCP: Request", xid) |
|||
self.send_dhcp_message( |
|||
DHCP_REQUEST, ((time.monotonic() - start_time) / 1000) |
|||
) |
|||
self._dhcp_state = STATE_DHCP_REQUEST |
|||
else: |
|||
print("* Received DHCP Message is not OFFER") |
|||
elif self._dhcp_state == STATE_DHCP_REQUEST: |
|||
if self._debug: |
|||
print("* DHCP: Parsing ACK") |
|||
msg_type, xid = self.parse_dhcp_response(self._response_timeout) |
|||
if msg_type == DHCP_ACK: |
|||
self._dhcp_state = STATE_DHCP_LEASED |
|||
result = 1 |
|||
if self._lease_time == 0: |
|||
self._lease_time = DEFAULT_LEASE_TIME |
|||
if self._t1 == 0: |
|||
# T1 is 50% of _lease_time |
|||
self._t1 = self._lease_time >> 1 |
|||
if self._t2 == 0: |
|||
# T2 is 87.5% of _lease_time |
|||
self._t2 = self._lease_time - (self._lease_time >> 3) |
|||
self._renew_in_sec = self._t1 |
|||
self._rebind_in_sec = self._t2 |
|||
elif msg_type == DHCP_NAK: |
|||
self._dhcp_state = STATE_DHCP_START |
|||
else: |
|||
print("* Received DHCP Message is not OFFER") |
|||
|
|||
if msg_type == 255: |
|||
msg_type = 0 |
|||
self._dhcp_state = STATE_DHCP_START |
|||
|
|||
if result != 1 and ( |
|||
(time.monotonic() - start_time > self._response_timeout) |
|||
): |
|||
break |
|||
|
|||
self._transaction_id += 1 |
|||
self._last_check_lease_ms = time.monotonic() |
|||
# close the socket, we're done with it |
|||
self._sock.close() |
|||
gc.collect() |
|||
return result |
@ -0,0 +1,253 @@ |
|||
# SPDX-FileCopyrightText: 2009-2010 MCQN Ltd |
|||
# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries |
|||
# |
|||
# SPDX-License-Identifier: MIT |
|||
|
|||
""" |
|||
`adafruit_wiznet5k_dns` |
|||
================================================================================ |
|||
|
|||
Pure-Python implementation of the Arduino DNS client for WIZnet 5k-based |
|||
ethernet modules. |
|||
|
|||
* Author(s): MCQN Ltd, Brent Rubell |
|||
|
|||
""" |
|||
import time |
|||
from random import getrandbits |
|||
from micropython import const |
|||
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket |
|||
from adafruit_wiznet5k.adafruit_wiznet5k_socket import htons |
|||
|
|||
|
|||
QUERY_FLAG = const(0x00) |
|||
OPCODE_STANDARD_QUERY = const(0x00) |
|||
RECURSION_DESIRED_FLAG = 1 << 8 |
|||
|
|||
TYPE_A = const(0x0001) |
|||
CLASS_IN = const(0x0001) |
|||
DATA_LEN = const(0x0004) |
|||
|
|||
# Return codes for gethostbyname |
|||
SUCCESS = const(1) |
|||
TIMED_OUT = const(-1) |
|||
INVALID_SERVER = const(-2) |
|||
TRUNCATED = const(-3) |
|||
INVALID_RESPONSE = const(-4) |
|||
|
|||
DNS_PORT = const(0x35) # port used for DNS request |
|||
|
|||
|
|||
class DNS: |
|||
"""W5K DNS implementation. |
|||
|
|||
:param iface: Network interface |
|||
""" |
|||
|
|||
def __init__(self, iface, dns_address, debug=False): |
|||
self._debug = debug |
|||
self._iface = iface |
|||
socket.set_interface(iface) |
|||
self._sock = socket.socket(type=socket.SOCK_DGRAM) |
|||
self._sock.settimeout(1) |
|||
|
|||
self._dns_server = dns_address |
|||
self._host = 0 |
|||
self._request_id = 0 # request identifier |
|||
self._pkt_buf = bytearray() |
|||
|
|||
def gethostbyname(self, hostname): |
|||
"""Translate a host name to IPv4 address format. |
|||
:param str hostname: Desired host name to connect to. |
|||
|
|||
Returns the IPv4 address as a bytearray if successful, -1 otherwise. |
|||
""" |
|||
if self._dns_server is None: |
|||
return INVALID_SERVER |
|||
self._host = hostname |
|||
# build DNS request packet |
|||
self._build_dns_header() |
|||
self._build_dns_question() |
|||
|
|||
# Send DNS request packet |
|||
self._sock.connect((self._dns_server, DNS_PORT)) |
|||
if self._debug: |
|||
print("* DNS: Sending request packet...") |
|||
self._sock.send(self._pkt_buf) |
|||
|
|||
# wait and retry 3 times for a response |
|||
retries = 0 |
|||
addr = -1 |
|||
while (retries < 5) and (addr == -1): |
|||
addr = self._parse_dns_response() |
|||
if addr == -1 and self._debug: |
|||
print("* DNS ERROR: Failed to resolve DNS response, retrying...") |
|||
retries += 1 |
|||
|
|||
self._sock.close() |
|||
return addr |
|||
|
|||
def _parse_dns_response( |
|||
self, |
|||
): # pylint: disable=too-many-return-statements, too-many-branches, too-many-statements, too-many-locals |
|||
"""Receives and parses DNS query response. |
|||
Returns desired hostname address if obtained, -1 otherwise. |
|||
|
|||
""" |
|||
# wait for a response |
|||
start_time = time.monotonic() |
|||
packet_sz = self._sock.available() |
|||
while packet_sz <= 0: |
|||
packet_sz = self._sock.available() |
|||
if (time.monotonic() - start_time) > 1.0: |
|||
if self._debug: |
|||
print("* DNS ERROR: Did not receive DNS response!") |
|||
return -1 |
|||
time.sleep(0.05) |
|||
# recv packet into buf |
|||
self._pkt_buf = self._sock.recv() |
|||
|
|||
if self._debug: |
|||
print("DNS Packet Received: ", self._pkt_buf) |
|||
|
|||
# Validate request identifier |
|||
xid = int.from_bytes(self._pkt_buf[0:2], "l") |
|||
if not xid == self._request_id: |
|||
if self._debug: |
|||
print( |
|||
"* DNS ERROR: Received request identifer {} \ |
|||
does not match expected {}".format( |
|||
xid, self._request_id |
|||
) |
|||
) |
|||
return -1 |
|||
# Validate flags |
|||
flags = int.from_bytes(self._pkt_buf[2:4], "l") |
|||
if not flags in (0x8180, 0x8580): |
|||
if self._debug: |
|||
print("* DNS ERROR: Invalid flags, ", flags) |
|||
return -1 |
|||
# Number of questions |
|||
qr_count = int.from_bytes(self._pkt_buf[4:6], "l") |
|||
if not qr_count >= 1: |
|||
if self._debug: |
|||
print("* DNS ERROR: Question count >=1, ", qr_count) |
|||
return -1 |
|||
# Number of answers |
|||
an_count = int.from_bytes(self._pkt_buf[6:8], "l") |
|||
if self._debug: |
|||
print("* DNS Answer Count: ", an_count) |
|||
if not an_count >= 1: |
|||
return -1 |
|||
|
|||
# Parse query |
|||
ptr = 12 |
|||
name_len = 1 |
|||
while name_len > 0: |
|||
# read the length of the name |
|||
name_len = self._pkt_buf[ptr] |
|||
if name_len == 0x00: |
|||
# we reached the end of this name |
|||
ptr += 1 # inc. pointer by 0x00 |
|||
break |
|||
# advance pointer |
|||
ptr += name_len + 1 |
|||
|
|||
# Validate Query is Type A |
|||
q_type = int.from_bytes(self._pkt_buf[ptr : ptr + 2], "l") |
|||
if not q_type == TYPE_A: |
|||
if self._debug: |
|||
print("* DNS ERROR: Incorrect Query Type: ", q_type) |
|||
return -1 |
|||
ptr += 2 |
|||
|
|||
# Validate Query is Type A |
|||
q_class = int.from_bytes(self._pkt_buf[ptr : ptr + 2], "l") |
|||
if not q_class == TYPE_A: |
|||
if self._debug: |
|||
print("* DNS ERROR: Incorrect Query Class: ", q_class) |
|||
return -1 |
|||
ptr += 2 |
|||
|
|||
# Let's take the first type-a answer |
|||
if self._pkt_buf[ptr] != 0xC0: |
|||
return -1 |
|||
ptr += 1 |
|||
|
|||
if self._pkt_buf[ptr] != 0xC: |
|||
return -1 |
|||
ptr += 1 |
|||
|
|||
# Validate Answer Type A |
|||
ans_type = int.from_bytes(self._pkt_buf[ptr : ptr + 2], "l") |
|||
if not ans_type == TYPE_A: |
|||
if self._debug: |
|||
print("* DNS ERROR: Incorrect Answer Type: ", ans_type) |
|||
return -1 |
|||
ptr += 2 |
|||
|
|||
# Validate Answer Class IN |
|||
ans_class = int.from_bytes(self._pkt_buf[ptr : ptr + 2], "l") |
|||
if not ans_class == TYPE_A: |
|||
if self._debug: |
|||
print("* DNS ERROR: Incorrect Answer Class: ", ans_class) |
|||
return -1 |
|||
ptr += 2 |
|||
|
|||
# skip over TTL |
|||
ptr += 4 |
|||
|
|||
# Validate addr is IPv4 |
|||
data_len = int.from_bytes(self._pkt_buf[ptr : ptr + 2], "l") |
|||
if not data_len == DATA_LEN: |
|||
if self._debug: |
|||
print("* DNS ERROR: Unexpected Data Length: ", data_len) |
|||
return -1 |
|||
ptr += 2 |
|||
# Return address |
|||
return self._pkt_buf[ptr : ptr + 4] |
|||
|
|||
def _build_dns_header(self): |
|||
"""Builds DNS header.""" |
|||
# generate a random, 16-bit, request identifier |
|||
self._request_id = getrandbits(16) |
|||
|
|||
# ID, 16-bit identifier |
|||
self._pkt_buf.append(self._request_id >> 8) |
|||
self._pkt_buf.append(self._request_id & 0xFF) |
|||
|
|||
# Flags (0x0100) |
|||
self._pkt_buf.append(0x01) |
|||
self._pkt_buf.append(0x00) |
|||
|
|||
# QDCOUNT |
|||
self._pkt_buf.append(0x00) |
|||
self._pkt_buf.append(0x01) |
|||
# ANCOUNT |
|||
self._pkt_buf.append(0x00) |
|||
self._pkt_buf.append(0x00) |
|||
# NSCOUNT |
|||
self._pkt_buf.append(0x00) |
|||
self._pkt_buf.append(0x00) |
|||
# ARCOUNT |
|||
self._pkt_buf.append(0x00) |
|||
self._pkt_buf.append(0x00) |
|||
|
|||
def _build_dns_question(self): |
|||
"""Build DNS question""" |
|||
host = self._host.decode("utf-8") |
|||
host = host.split(".") |
|||
# write out each section of host |
|||
for i, _ in enumerate(host): |
|||
# append the sz of the section |
|||
self._pkt_buf.append(len(host[i])) |
|||
# append the section data |
|||
self._pkt_buf += host[i] |
|||
# end of the name |
|||
self._pkt_buf.append(0x00) |
|||
# Type A record |
|||
self._pkt_buf.append(htons(TYPE_A) & 0xFF) |
|||
self._pkt_buf.append(htons(TYPE_A) >> 8) |
|||
# Class IN |
|||
self._pkt_buf.append(htons(CLASS_IN) & 0xFF) |
|||
self._pkt_buf.append(htons(CLASS_IN) >> 8) |
@ -0,0 +1,428 @@ |
|||
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries |
|||
# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries |
|||
# |
|||
# SPDX-License-Identifier: MIT |
|||
|
|||
""" |
|||
`adafruit_wiznet5k_socket` |
|||
================================================================================ |
|||
|
|||
A socket compatible interface with the Wiznet5k module. |
|||
|
|||
* Author(s): ladyada, Brent Rubell, Patrick Van Oosterwijck, Adam Cummick |
|||
|
|||
""" |
|||
import gc |
|||
import time |
|||
from micropython import const |
|||
import adafruit_wiznet5k as wiznet5k |
|||
|
|||
_the_interface = None # pylint: disable=invalid-name |
|||
|
|||
|
|||
def set_interface(iface): |
|||
"""Helper to set the global internet interface.""" |
|||
global _the_interface # pylint: disable=global-statement, invalid-name |
|||
_the_interface = iface |
|||
|
|||
|
|||
def htonl(x): |
|||
"""Convert 32-bit positive integers from host to network byte order.""" |
|||
return ( |
|||
((x) << 24 & 0xFF000000) |
|||
| ((x) << 8 & 0x00FF0000) |
|||
| ((x) >> 8 & 0x0000FF00) |
|||
| ((x) >> 24 & 0x000000FF) |
|||
) |
|||
|
|||
|
|||
def htons(x): |
|||
"""Convert 16-bit positive integers from host to network byte order.""" |
|||
return (((x) << 8) & 0xFF00) | (((x) >> 8) & 0xFF) |
|||
|
|||
|
|||
SOCK_STREAM = const(0x21) # TCP |
|||
TCP_MODE = 80 |
|||
SOCK_DGRAM = const(0x02) # UDP |
|||
AF_INET = const(3) |
|||
SOCKET_INVALID = const(255) |
|||
|
|||
|
|||
# pylint: disable=too-many-arguments, unused-argument |
|||
def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0): |
|||
"""Translate the host/port argument into a sequence of 5-tuples that |
|||
contain all the necessary arguments for creating a socket connected to that service. |
|||
|
|||
""" |
|||
if not isinstance(port, int): |
|||
raise RuntimeError("Port must be an integer") |
|||
if is_ipv4(host): |
|||
return [(AF_INET, socktype, proto, "", (host, port))] |
|||
return [(AF_INET, socktype, proto, "", (gethostbyname(host), port))] |
|||
|
|||
|
|||
def gethostbyname(hostname): |
|||
"""Translate a host name to IPv4 address format. The IPv4 address |
|||
is returned as a string. |
|||
:param str hostname: Desired hostname. |
|||
""" |
|||
addr = _the_interface.get_host_by_name(hostname) |
|||
addr = "{}.{}.{}.{}".format(addr[0], addr[1], addr[2], addr[3]) |
|||
return addr |
|||
|
|||
|
|||
def is_ipv4(host): |
|||
"""Checks if a host string is an IPv4 address. |
|||
:param str host: host's name or ip |
|||
""" |
|||
octets = host.split(".", 3) |
|||
if len(octets) != 4 or not "".join(octets).isdigit(): |
|||
return False |
|||
for octet in octets: |
|||
if int(octet) > 255: |
|||
return False |
|||
return True |
|||
|
|||
|
|||
# pylint: disable=invalid-name, too-many-public-methods |
|||
class socket: |
|||
"""A simplified implementation of the Python 'socket' class |
|||
for connecting to a Wiznet5k module. |
|||
:param int family: Socket address (and protocol) family. |
|||
:param int type: Socket type. |
|||
|
|||
""" |
|||
|
|||
# pylint: disable=redefined-builtin,unused-argument |
|||
def __init__( |
|||
self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, socknum=None |
|||
): |
|||
if family != AF_INET: |
|||
raise RuntimeError("Only AF_INET family supported by W5K modules.") |
|||
self._sock_type = type |
|||
self._buffer = b"" |
|||
self._timeout = 0 |
|||
self._listen_port = None |
|||
|
|||
self._socknum = _the_interface.get_socket() |
|||
if self._socknum == SOCKET_INVALID: |
|||
raise RuntimeError("Failed to allocate socket.") |
|||
|
|||
def __enter__(self): |
|||
return self |
|||
|
|||
def __exit__(self, exc_type, exc_val, exc_tb): |
|||
if self._sock_type == SOCK_STREAM: |
|||
self.disconnect() |
|||
stamp = time.monotonic() |
|||
while self.status == wiznet5k.adafruit_wiznet5k.SNSR_SOCK_FIN_WAIT: |
|||
if time.monotonic() - stamp > 1000: |
|||
raise RuntimeError("Failed to disconnect socket") |
|||
self.close() |
|||
stamp = time.monotonic() |
|||
while self.status != wiznet5k.adafruit_wiznet5k.SNSR_SOCK_CLOSED: |
|||
if time.monotonic() - stamp > 1000: |
|||
raise RuntimeError("Failed to close socket") |
|||
|
|||
@property |
|||
def socknum(self): |
|||
"""Returns the socket object's socket number.""" |
|||
return self._socknum |
|||
|
|||
@property |
|||
def status(self): |
|||
"""Returns the status of the socket""" |
|||
return _the_interface.socket_status(self.socknum)[0] |
|||
|
|||
@property |
|||
def connected(self): |
|||
"""Returns whether or not we are connected to the socket.""" |
|||
if self.socknum >= _the_interface.max_sockets: |
|||
return False |
|||
status = _the_interface.socket_status(self.socknum)[0] |
|||
if ( |
|||
status == wiznet5k.adafruit_wiznet5k.SNSR_SOCK_CLOSE_WAIT |
|||
and self.available() == 0 |
|||
): |
|||
result = False |
|||
else: |
|||
result = status not in ( |
|||
wiznet5k.adafruit_wiznet5k.SNSR_SOCK_CLOSED, |
|||
wiznet5k.adafruit_wiznet5k.SNSR_SOCK_LISTEN, |
|||
wiznet5k.adafruit_wiznet5k.SNSR_SOCK_TIME_WAIT, |
|||
wiznet5k.adafruit_wiznet5k.SNSR_SOCK_FIN_WAIT, |
|||
) |
|||
if not result and status != wiznet5k.adafruit_wiznet5k.SNSR_SOCK_LISTEN: |
|||
self.close() |
|||
return result |
|||
|
|||
def getpeername(self): |
|||
"""Return the remote address to which the socket is connected.""" |
|||
return _the_interface.remote_ip(self.socknum) |
|||
|
|||
def inet_aton(self, ip_string): |
|||
"""Convert an IPv4 address from dotted-quad string format. |
|||
:param str ip_string: IP Address, as a dotted-quad string. |
|||
|
|||
""" |
|||
self._buffer = b"" |
|||
self._buffer = [int(item) for item in ip_string.split(".")] |
|||
self._buffer = bytearray(self._buffer) |
|||
return self._buffer |
|||
|
|||
def bind(self, address): |
|||
"""Bind the socket to the listen port, if host is specified the interface |
|||
will be reconfigured to that IP. |
|||
:param tuple address: local socket as a (host, port) tuple. |
|||
""" |
|||
if address[0] is not None: |
|||
ip_address = _the_interface.unpretty_ip(address[0]) |
|||
current_ip, subnet_mask, gw_addr, dns = _the_interface.ifconfig |
|||
if ip_address != current_ip: |
|||
_the_interface.ifconfig = (ip_address, subnet_mask, gw_addr, dns) |
|||
self._listen_port = address[1] |
|||
|
|||
def listen(self, backlog=None): |
|||
"""Listen on the port specified by bind. |
|||
:param backlog: For compatibility but ignored. |
|||
""" |
|||
assert self._listen_port is not None, "Use bind to set the port before listen!" |
|||
_the_interface.socket_listen(self.socknum, self._listen_port) |
|||
self._buffer = b"" |
|||
|
|||
def accept(self): |
|||
"""Accept a connection. The socket must be bound to an address and listening for |
|||
connections. The return value is a pair (conn, address) where conn is a new |
|||
socket object usable to send and receive data on the connection, and address is |
|||
the address bound to the socket on the other end of the connection. |
|||
""" |
|||
stamp = time.monotonic() |
|||
while self.status not in ( |
|||
wiznet5k.adafruit_wiznet5k.SNSR_SOCK_SYNRECV, |
|||
wiznet5k.adafruit_wiznet5k.SNSR_SOCK_ESTABLISHED, |
|||
): |
|||
if self._timeout > 0 and time.monotonic() - stamp > self._timeout: |
|||
return None |
|||
if self.status == wiznet5k.adafruit_wiznet5k.SNSR_SOCK_CLOSED: |
|||
self.close() |
|||
self.listen() |
|||
|
|||
new_listen_socknum, addr = _the_interface.socket_accept(self.socknum) |
|||
current_socknum = self.socknum |
|||
# Create a new socket object and swap socket nums so we can continue listening |
|||
client_sock = socket() |
|||
client_sock._socknum = current_socknum # pylint: disable=protected-access |
|||
self._socknum = new_listen_socknum # pylint: disable=protected-access |
|||
self.bind((None, self._listen_port)) |
|||
self.listen() |
|||
while self.status != wiznet5k.adafruit_wiznet5k.SNSR_SOCK_LISTEN: |
|||
raise RuntimeError("Failed to open new listening socket") |
|||
return client_sock, addr |
|||
|
|||
def connect(self, address, conntype=None): |
|||
"""Connect to a remote socket at address. |
|||
:param tuple address: Remote socket as a (host, port) tuple. |
|||
""" |
|||
assert ( |
|||
conntype != 0x03 |
|||
), "Error: SSL/TLS is not currently supported by CircuitPython." |
|||
host, port = address |
|||
|
|||
if hasattr(host, "split"): |
|||
try: |
|||
host = tuple(map(int, host.split("."))) |
|||
except ValueError: |
|||
host = _the_interface.get_host_by_name(host) |
|||
if not _the_interface.socket_connect( |
|||
self.socknum, host, port, conn_mode=self._sock_type |
|||
): |
|||
raise RuntimeError("Failed to connect to host", host) |
|||
self._buffer = b"" |
|||
|
|||
def send(self, data): |
|||
"""Send data to the socket. The socket must be connected to |
|||
a remote socket. |
|||
:param bytearray data: Desired data to send to the socket. |
|||
""" |
|||
_the_interface.socket_write(self.socknum, data, self._timeout) |
|||
gc.collect() |
|||
|
|||
def sendto(self, data, address): |
|||
"""Send data to the socket. The socket must be connected to |
|||
a remote socket. |
|||
:param bytearray data: Desired data to send to the socket. |
|||
:param tuple address: Remote socket as a (host, port) tuple. |
|||
""" |
|||
self.connect(address) |
|||
return self.send(data) |
|||
|
|||
def recv(self, bufsize=0, flags=0): # pylint: disable=too-many-branches |
|||
"""Reads some bytes from the connected remote address. |
|||
:param int bufsize: Maximum number of bytes to receive. |
|||
:param int flags: ignored, present for compatibility. |
|||
""" |
|||
# print("Socket read", bufsize) |
|||
if bufsize == 0: |
|||
# read everything on the socket |
|||
while True: |
|||
avail = self.available() |
|||
if avail: |
|||
if self._sock_type == SOCK_STREAM: |
|||
self._buffer += _the_interface.socket_read(self.socknum, avail)[1] |
|||
elif self._sock_type == SOCK_DGRAM: |
|||
self._buffer += _the_interface.read_udp(self.socknum, avail)[1] |
|||
else: |
|||
break |
|||
gc.collect() |
|||
ret = self._buffer |
|||
self._buffer = b"" |
|||
gc.collect() |
|||
return ret |
|||
stamp = time.monotonic() |
|||
|
|||
to_read = bufsize - len(self._buffer) |
|||
received = [] |
|||
while to_read > 0: |
|||
# print("Bytes to read:", to_read) |
|||
avail = self.available() |
|||
if avail: |
|||
stamp = time.monotonic() |
|||
if self._sock_type == SOCK_STREAM: |
|||
recv = _the_interface.socket_read( |
|||
self.socknum, min(to_read, avail) |
|||
)[1] |
|||
elif self._sock_type == SOCK_DGRAM: |
|||
recv = _the_interface.read_udp(self.socknum, min(to_read, avail))[1] |
|||
recv = bytes(recv) |
|||
received.append(recv) |
|||
to_read -= len(recv) |
|||
gc.collect() |
|||
if self._timeout > 0 and time.monotonic() - stamp > self._timeout: |
|||
break |
|||
self._buffer += b"".join(received) |
|||
|
|||
ret = None |
|||
if len(self._buffer) == bufsize: |
|||
ret = self._buffer |
|||
self._buffer = b"" |
|||
else: |
|||
ret = self._buffer[:bufsize] |
|||
self._buffer = self._buffer[bufsize:] |
|||
gc.collect() |
|||
return ret |
|||
|
|||
def embed_recv(self, bufsize=0, flags=0): # pylint: disable=too-many-branches |
|||
"""Reads some bytes from the connected remote address and then return recv(). |
|||
:param int bufsize: Maximum number of bytes to receive. |
|||
:param int flags: ignored, present for compatibility. |
|||
""" |
|||
# print("Socket read", bufsize) |
|||
ret = None |
|||
avail = self.available() |
|||
if avail: |
|||
if self._sock_type == SOCK_STREAM: |
|||
self._buffer += _the_interface.socket_read(self.socknum, avail)[1] |
|||
elif self._sock_type == SOCK_DGRAM: |
|||
self._buffer += _the_interface.read_udp(self.socknum, avail)[1] |
|||
gc.collect() |
|||
ret = self._buffer |
|||
# print("RET ptr:", id(ret), id(self._buffer)) |
|||
self._buffer = b"" |
|||
gc.collect() |
|||
return ret |
|||
|
|||
def recvfrom(self, bufsize=0, flags=0): |
|||
"""Reads some bytes from the connected remote address. |
|||
:param int bufsize: Maximum number of bytes to receive. |
|||
:param int flags: ignored, present for compatibility. |
|||
:returns: a tuple (bytes, address) where address is a tuple (ip, port) |
|||
""" |
|||
return ( |
|||
self.recv(bufsize), |
|||
( |
|||
_the_interface.remote_ip(self.socknum), |
|||
_the_interface.remote_port(self.socknum), |
|||
), |
|||
) |
|||
|
|||
def recv_into(self, buf, nbytes=0, flags=0): |
|||
"""Reads some bytes from the connected remote address info the provided buffer. |
|||
:param bytearray buf: Data buffer |
|||
:param nbytes: Maximum number of bytes to receive |
|||
:param int flags: ignored, present for compatibility. |
|||
:returns: the number of bytes received |
|||
""" |
|||
if nbytes == 0: |
|||
nbytes = len(buf) |
|||
ret = self.recv(nbytes) |
|||
nbytes = len(ret) |
|||
buf[:nbytes] = ret |
|||
return nbytes |
|||
|
|||
def recvfrom_into(self, buf, nbytes=0, flags=0): |
|||
"""Reads some bytes from the connected remote address info the provided buffer. |
|||
:param bytearray buf: Data buffer |
|||
:param nbytes: Maximum number of bytes to receive |
|||
:param int flags: ignored, present for compatibility. |
|||
:returns a tuple (nbytes, address) where address is a tuple (ip, port) |
|||
""" |
|||
return ( |
|||
self.recv_into(buf, nbytes), |
|||
( |
|||
_the_interface.remote_ip(self.socknum), |
|||
_the_interface.remote_port(self.socknum), |
|||
), |
|||
) |
|||
|
|||
def readline(self): |
|||
"""Attempt to return as many bytes as we can up to \ |
|||
but not including '\r\n'. |
|||
|
|||
""" |
|||
stamp = time.monotonic() |
|||
while b"\r\n" not in self._buffer: |
|||
avail = self.available() |
|||
if avail: |
|||
if self._sock_type == SOCK_STREAM: |
|||
self._buffer += _the_interface.socket_read(self.socknum, avail)[1] |
|||
elif self._sock_type == SOCK_DGRAM: |
|||
self._buffer += _the_interface.read_udp(self.socknum, avail)[1] |
|||
if ( |
|||
not avail |
|||
and self._timeout > 0 |
|||
and time.monotonic() - stamp > self._timeout |
|||
): |
|||
self.close() |
|||
raise RuntimeError("Didn't receive response, failing out...") |
|||
firstline, self._buffer = self._buffer.split(b"\r\n", 1) |
|||
gc.collect() |
|||
return firstline |
|||
|
|||
def disconnect(self): |
|||
"""Disconnects a TCP socket.""" |
|||
assert self._sock_type == SOCK_STREAM, "Socket must be a TCP socket." |
|||
_the_interface.socket_disconnect(self.socknum) |
|||
|
|||
def close(self): |
|||
"""Closes the socket.""" |
|||
_the_interface.socket_close(self.socknum) |
|||
|
|||
def available(self): |
|||
"""Returns how many bytes of data are available to be read from the socket.""" |
|||
return _the_interface.socket_available(self.socknum, self._sock_type) |
|||
|
|||
def settimeout(self, value): |
|||
"""Sets socket read timeout. |
|||
:param int value: Socket read timeout, in seconds. |
|||
|
|||
""" |
|||
if value < 0: |
|||
raise Exception("Timeout period should be non-negative.") |
|||
self._timeout = value |
|||
|
|||
def gettimeout(self): |
|||
"""Return the timeout in seconds (float) associated |
|||
with socket operations, or None if no timeout is set. |
|||
|
|||
""" |
|||
return self._timeout |
Loading…
Reference in new issue