Skip to content

Commit

Permalink
Showing 22 changed files with 2,592 additions and 20 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Build CI

on: [pull_request, push]

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Translate Repo Name For Build Tools filename_prefix
id: repo-name
run: |
echo ::set-output name=repo-name::$(
echo ${{ github.repository }} |
awk -F '\/' '{ print tolower($2) }' |
tr '_' '-'
)
- name: Set up Python 3.6
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Versions
run: |
python3 --version
- name: Checkout Current Repo
uses: actions/checkout@v1
with:
submodules: true
- name: Checkout tools repo
uses: actions/checkout@v2
with:
repository: adafruit/actions-ci-circuitpython-libs
path: actions-ci
- name: Install deps
run: |
source actions-ci/install.sh
- name: Library version
run: git describe --dirty --always --tags
- name: PyLint
run: |
pylint $( find . -path './adafruit*.py' )
([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace $( find . -path "./examples/*.py" ))
- name: Build assets
run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location .
- name: Build docs
working-directory: docs
run: sphinx-build -E -W -b html . _build/html
81 changes: 81 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Release Actions

on:
release:
types: [published]

jobs:
upload-release-assets:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Translate Repo Name For Build Tools filename_prefix
id: repo-name
run: |
echo ::set-output name=repo-name::$(
echo ${{ github.repository }} |
awk -F '\/' '{ print tolower($2) }' |
tr '_' '-'
)
- name: Set up Python 3.6
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Versions
run: |
python3 --version
- name: Checkout Current Repo
uses: actions/checkout@v1
with:
submodules: true
- name: Checkout tools repo
uses: actions/checkout@v2
with:
repository: adafruit/actions-ci-circuitpython-libs
path: actions-ci
- name: Install deps
run: |
source actions-ci/install.sh
- name: Build assets
run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location .
- name: Upload Release Assets
# the 'official' actions version does not yet support dynamically
# supplying asset names to upload. @csexton's version chosen based on
# discussion in the issue below, as its the simplest to implement and
# allows for selecting files with a pattern.
# https://github.com/actions/upload-release-asset/issues/4
#uses: actions/upload-release-asset@v1.0.1
uses: csexton/release-asset-action@master
with:
pattern: "bundles/*"
github-token: ${{ secrets.GITHUB_TOKEN }}

upload-pypi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Check For setup.py
id: need-pypi
run: |
echo ::set-output name=setup-py::$( find . -wholename './setup.py' )
- name: Set up Python
if: contains(steps.need-pypi.outputs.setup-py, 'setup.py')
uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Install dependencies
if: contains(steps.need-pypi.outputs.setup-py, 'setup.py')
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
if: contains(steps.need-pypi.outputs.setup-py, 'setup.py')
env:
TWINE_USERNAME: ${{ secrets.pypi_username }}
TWINE_PASSWORD: ${{ secrets.pypi_password }}
run: |
python setup.py sdist
twine upload dist/*
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
*.mpy
.idea
__pycache__
_build
*.pyc
.env
build*/
bundles
*.DS_Store
.eggs
dist
**/*.egg-info
.vscode
433 changes: 433 additions & 0 deletions .pylintrc

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
python:
version: 3
requirements_file: requirements.txt
127 changes: 127 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Adafruit Community Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and leaders pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level or type of
experience, education, socio-economic status, nationality, personal appearance,
race, religion, or sexual identity and orientation.

## Our Standards

We are committed to providing a friendly, safe and welcoming environment for
all.

Examples of behavior that contributes to creating a positive environment
include:

* Be kind and courteous to others
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Collaborating with other community members
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and sexual attention or advances
* The use of inappropriate images, including in a community member's avatar
* The use of inappropriate language, including in a community member's nickname
* Any spamming, flaming, baiting or other attention-stealing behavior
* Excessive or unwelcome helping; answering outside the scope of the question
asked
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate

The goal of the standards and moderation guidelines outlined here is to build
and maintain a respectful community. We ask that you don’t just aim to be
"technically unimpeachable", but rather try to be your best self.

We value many things beyond technical expertise, including collaboration and
supporting others within our community. Providing a positive experience for
other community members can have a much more significant impact than simply
providing the correct answer.

## Our Responsibilities

Project leaders are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project leaders have the right and responsibility to remove, edit, or
reject messages, comments, commits, code, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any community member for other behaviors that they deem
inappropriate, threatening, offensive, or harmful.

## Moderation

Instances of behaviors that violate the Adafruit Community Code of Conduct
may be reported by any member of the community. Community members are
encouraged to report these situations, including situations they witness
involving other community members.

You may report in the following ways:

In any situation, you may send an email to <support@adafruit.com>.

On the Adafruit Discord, you may send an open message from any channel
to all Community Helpers by tagging @community moderators. You may also send an
open message from any channel, or a direct message to @kattni#1507,
@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or
@Andon#8175.

Email and direct message reports will be kept confidential.

In situations on Discord where the issue is particularly egregious, possibly
illegal, requires immediate action, or violates the Discord terms of service,
you should also report the message directly to Discord.

These are the steps for upholding our community’s standards of conduct.

1. Any member of the community may report any situation that violates the
Adafruit Community Code of Conduct. All reports will be reviewed and
investigated.
2. If the behavior is an egregious violation, the community member who
committed the violation may be banned immediately, without warning.
3. Otherwise, moderators will first respond to such behavior with a warning.
4. Moderators follow a soft "three strikes" policy - the community member may
be given another chance, if they are receptive to the warning and change their
behavior.
5. If the community member is unreceptive or unreasonable when warned by a
moderator, or the warning goes unheeded, they may be banned for a first or
second offense. Repeated offenses will result in the community member being
banned.

## Scope

This Code of Conduct and the enforcement policies listed above apply to all
Adafruit Community venues. This includes but is not limited to any community
spaces (both public and private), the entire Adafruit Discord server, and
Adafruit GitHub repositories. Examples of Adafruit Community spaces include
but are not limited to meet-ups, audio chats on the Adafruit Discord, or
interaction at a conference.

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. As a community
member, you are representing our community, and are expected to behave
accordingly.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 1.4, available at
<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>,
and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html).

For other projects adopting the Adafruit Community Code of
Conduct, please contact the maintainers of those projects for enforcement.
If you wish to use this code of conduct for your own project, consider
explicitly mentioning your moderation policy or making a copy with your
own moderation policy so as to avoid confusion.
22 changes: 4 additions & 18 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
MIT License
Copyright (c) 2010 Arduino LLC. All right reserved.

Copyright (c) 2020 Adafruit Industries
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2 changes: 0 additions & 2 deletions README.md

This file was deleted.

125 changes: 125 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
Introduction
============

.. image:: https://readthedocs.org/projects/adafruit-circuitpython-wiznet5k/badge/?version=latest
:target: https://circuitpython.readthedocs.io/projects/wiznet5k/en/latest/
:alt: Documentation Status

.. image:: https://img.shields.io/discord/327254708534116352.svg
:target: https://discord.gg/nBQh6qu
:alt: Discord

.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k/workflows/Build%20CI/badge.svg
:target: https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k/actions
:alt: Build Status

Pure-Python interface for WIZNET 5k ethernet modules.

NOTE: This library does not currently contain a DNS client. You will need to include a server ip address and destination port in your code.

Dependencies
=============
This driver depends on:

* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
* `Bus Device <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_

Please ensure all dependencies are available on the CircuitPython filesystem.
This is easily achieved by downloading
`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_.

Installing from PyPI
=====================
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
PyPI <https://pypi.org/project/adafruit-circuitpython-wiznet5k/>`_. To install for current user:

.. code-block:: shell
pip3 install adafruit-circuitpython-wiznet5k
To install system-wide (this may be required in some cases):

.. code-block:: shell
sudo pip3 install adafruit-circuitpython-wiznet5k
To install in a virtual environment in your current project:

.. code-block:: shell
mkdir project-name && cd project-name
python3 -m venv .env
source .env/bin/activate
pip3 install adafruit-circuitpython-wiznet5k
Usage Example
=============
This example demonstrates making a HTTP GET request to
wifitest.adafruit.com.

.. code-block:: python
import time
import board
import busio
import digitalio
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket
# Name address for wifitest.adafruit.com
SERVER_ADDRESS = (('104.236.193.178'), 80)
cs = digitalio.DigitalInOut(board.D10)
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
# Initialize ethernet interface with DHCP
eth = WIZNET(spi_bus, cs)
print("DHCP Assigned IP: ", eth.pretty_ip(eth.ip_address))
socket.set_interface(eth)
# Create a new socket
sock = socket.socket()
print("Connecting to: ", SERVER_ADDRESS[0])
sock.connect(SERVER_ADDRESS)
print("Connected to ", sock.getpeername())
# Make a HTTP Request
sock.send(b"GET /testwifi/index.html HTTP/1.1\n")
sock.send(b"Host: 104.236.193.178\n")
sock.send(b"Connection: close\n\n")
bytes_avail = 0
while not bytes_avail:
bytes_avail = sock.available()
if bytes_avail > 0:
data = sock.recv(bytes_avail)
print(data)
break
time.sleep(0.05)
Contributing
============

Contributions are welcome! Please read our `Code of Conduct
<https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k/blob/master/CODE_OF_CONDUCT.md>`_
before contributing to help this project stay welcoming.

Documentation
=============

For information on building library documentation, please check out `this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.

License
=============

This library was written by `Arduino LLC <https://github.com/arduino-libraries/Ethernet/blob/master/AUTHORS>`_. We've converted it to work
with `CircuitPython <https://circuitpython.org/>`_ and made changes so it works similarly to `CircuitPython's WIZNET5k wrapper for the WIZnet
5500 Ethernet interface <https://circuitpython.readthedocs.io/en/latest/shared-bindings/wiznet/wiznet5k.html>`_ and CPython's `Socket low-level
networking interface module <https://docs.python.org/3.8/library/socket.html>`_.

This open source code is licensed under the LGPL license (see LICENSE for details).
Empty file added adafruit_wiznet5k/__init__.py
Empty file.
800 changes: 800 additions & 0 deletions adafruit_wiznet5k/adafruit_wiznet5k.py

Large diffs are not rendered by default.

325 changes: 325 additions & 0 deletions adafruit_wiznet5k/adafruit_wiznet5k_dhcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
# The MIT License (MIT)
#
# Copyright (c) April 25, 2009 Jordan Terrell (blog.jordanterrell.com)
# Modified by Brent Rubell for Adafruit Industries, 2020
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
`adafruit_wiznet5k_dhcp`
================================================================================
Pure-Python implementation of Jordan Terrell's DHCP library v0.3
* Author(s): Jordan Terrell, Brent Rubell
"""
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

# pylint: disable=bad-whitespace

# 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

# pylint: enable=bad-whitespace
_BUFF = bytearray(317)

class DHCP:
"""W5k DHCP Client implementation.
:param eth: Wiznet 5k object
:param list mac_address: Hardware MAC.
:param int timeout: Packet parsing timeout.
:param int timeout_response: 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, timeout=1, timeout_response=1):
self._timeout = timeout
self._response_timeout = timeout_response
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(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

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 client attempted to renew lease.
"""
# 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
_BUFF[253] = len(b"Wiznet") + 6
_BUFF[254:260] = b"WIZnet"

for mac in range(0, 5):
_BUFF[260+mac] = self._mac_address[mac]

if state == DHCP_REQUEST:
# Set the parsed local IP addr
_BUFF[266] = 50
_BUFF[267] = 0x04

_BUFF[268:272] = self.local_ip
# Set the parsed dhcp server ip addr
_BUFF[272] = 54
_BUFF[273] = 0x04
_BUFF[274:278] = self.dhcp_server_ip

_BUFF[278] = 55
_BUFF[279] = 0x06
# subnet mask
_BUFF[280] = 1
# routers on subnet
_BUFF[281] = 3
# DNS
_BUFF[282] = 6
# domain name
_BUFF[283] = 15
# renewal (T1) value
_BUFF[284] = 58
# rebinding (T2) value
_BUFF[285] = 59
_BUFF[286] = 255

# Send DHCP packet
self._sock.send(_BUFF)

def parse_dhcp_response(self, response_timeout):
"""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
time.sleep(0.05)
# store packet in buffer
_BUFF = self._sock.recv(packet_sz)[0]

# Check OP, if valid, let's parse the packet out!
assert _BUFF[0] == DHCP_BOOT_REPLY, "Malformed Packet - \
DHCP message OP is not expected BOOT Reply."

# Client Hardware Address (CHADDR)
chaddr = bytearray(6)
for mac, _ in enumerate(chaddr):
chaddr[mac] = _BUFF[28+mac]

if chaddr != 0:
xid = _BUFF[4:8]
if bytes(xid) < self._initial_xid:
return 0, 0

# Your IP Address (YIADDR)
self.local_ip = _BUFF[16:20]

# Gateway IP Address (GIADDR)
self.gateway_ip = _BUFF[20:24]

# NOTE: Next 192 octets are 0's for BOOTP legacy

# DHCP Message Type
msg_type = _BUFF[242]
# DHCP Server ID
self.dhcp_server_ip = _BUFF[245:249]
# Lease Time, in seconds
self._lease_time = int.from_bytes(_BUFF[251:255], 'l')
# T1 value
self._t1 = int.from_bytes(_BUFF[257:261], 'l')
# T2 value
self._t2 = int.from_bytes(_BUFF[263:267], 'l')
# Subnet Mask
self.subnet_mask = _BUFF[269:273]
# DNS Server
self.dns_server_ip = _BUFF[285:289]

return msg_type, xid

def request_dhcp_lease(self):
"""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))
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:
msg_type, xid = self.parse_dhcp_response(self._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')
self.send_dhcp_message(DHCP_REQUEST, ((time.monotonic() - start_time) / 1000))
self._dhcp_state = STATE_DHCP_REQUEST
elif STATE_DHCP_REQUEST:
msg_type, xid = self.parse_dhcp_response(self._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

if msg_type == 255:
msg_type = 0
self._dhcp_state = STATE_DHCP_START

if (result != 1 and ((time.monotonic() - start_time > self._timeout))):
break

self._transaction_id += 1
self._last_check_lease_ms = time.monotonic()
return result
241 changes: 241 additions & 0 deletions adafruit_wiznet5k/adafruit_wiznet5k_socket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# The MIT License (MIT)
#
# Copyright (c) 2019 ladyada for Adafruit Industries
# Modified by Brent Rubell for Adafruit Industries, 2020
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
`adafruit_wiznet5k_socket`
================================================================================
A socket compatible interface with the Wiznet5k module.
* Author(s): ladyada, Brent Rubell
"""
import gc
import time
from micropython import const
from adafruit_wiznet5k import adafruit_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)

# pylint: disable=bad-whitespace
SOCK_STREAM = const(0x21) # TCP
SOCK_DGRAM = const(0x02) # UDP
AF_INET = const(3)
NO_SOCKET_AVAIL = const(255)
# pylint: enable=bad-whitespace

# keep track of sockets we allocate
SOCKETS = []

#pylint: disable=invalid-name
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
def __init__(self, family=AF_INET, type=SOCK_STREAM):
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._socknum = _the_interface.get_socket(SOCKETS)
SOCKETS.append(self._socknum)
self.settimeout(1)

@property
def socknum(self):
"""Returns the socket object's socket number."""
return self._socknum

@property
def connected(self):
"""Returns whether or not we are connected to the socket."""
if self.socknum >= _the_interface.max_sockets:
return 0
status = _the_interface.socket_status(self.socknum)[0]
if status == adafruit_wiznet5k.SNSR_SOCK_CLOSE_WAIT and self.available()[0] == 0:
result = False
result = status not in (adafruit_wiznet5k.SNSR_SOCK_CLOSED,
adafruit_wiznet5k.SNSR_SOCK_LISTEN,
adafruit_wiznet5k.SNSR_SOCK_CLOSE_WAIT,
adafruit_wiznet5k.SNSR_SOCK_FIN_WAIT)
if not result:
self.close()
return result
return result

def getpeername(self):
"""Return the remote address to which the socket is connected."""
return _the_interface.remote_ip(self.socknum)

def gethostbyname(self, address):
"""Translate a host name to IPv4 address format."""
raise NotImplementedError("Not implemented in this version of Wiznet5k.")

def connect(self, address):
"""Connect to a remote socket at address. (The format of address depends
on the address family — see above.)
:param tuple address: Remote socket as a (host, port) tuple.
"""
host, port = address

if hasattr(host, 'split'):
host = tuple(map(int, host.split('.')))

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)
gc.collect()

def recv(self, bufsize=0): #pylint: disable=too-many-branches
"""Reads some bytes from the connected remote address.
:param int bufsize: Maximum number of bytes to receive.
"""
assert _the_interface.link_status, "Ethernet cable disconnected!"
if bufsize == 0:
# read everything on the socket
while True:
if self._sock_type == SOCK_STREAM:
avail = self.available()
elif self._sock_type == SOCK_DGRAM:
avail = _the_interface.udp_remaining()
if avail:
if self._sock_type == SOCK_STREAM:
self._buffer += _the_interface.socket_read(self.socknum, avail)
elif self._sock_type == SOCK_DGRAM:
self._buffer += _the_interface.read_udp(self.socknum, avail)
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:
if self._sock_type == SOCK_STREAM:
avail = self.available()
elif self._sock_type == SOCK_DGRAM:
avail = _the_interface.udp_remaining()
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]
received.append(recv)
to_read -= len(recv)
gc.collect()
if self._timeout > 0 and time.monotonic() - stamp > self._timeout:
break
self._buffer = 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 readline(self):
"""Attempt to return as many bytes as we can up to
but not including \n"""
stamp = time.monotonic()
while b'\n' not in self._buffer:
if self._sock_type == SOCK_STREAM:
avail = self.available()
self._buffer += _the_interface.read(self.socknum, avail)[1]
elif self._sock_type == SOCK_DGRAM:
avail = _the_interface.udp_remaining()
self._buffer += _the_interface.read_udp(self.socknum, avail)[1]
elif self._timeout > 0 and time.monotonic() - stamp > self._timeout:
self.close()
raise RuntimeError("Didn't receive response, failing out...")
firstline = self._buffer.split(b'\n', 1)
gc.collect()
# clear tmp data buffer
self._buffer = b''
return firstline[0]

def close(self):
"""Closes the socket.
"""
_the_interface.socket_close(self.socknum)
SOCKETS.remove(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
43 changes: 43 additions & 0 deletions code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import time

import board
import busio
import digitalio
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket

# Static IP Configuration
MY_IP = (192, 168, 0, 105)
MY_SUBNET_ADDR = (255, 255, 255, 0)
MY_GW_ADDR = (192, 168, 0, 1)
MY_DNS = (192, 168, 0, 1)


# Destination server address
#SERVER_ADDR = 74,125,232,128
SERVER_ADDR = 192,168,0,170
PORT = 2399

cs = digitalio.DigitalInOut(board.D10)
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Initialize wiznet5k module interface
eth = WIZNET(spi_bus, cs, dhcp=False)

# Check if ethernet cable is connected
assert eth.link_status == 1, "Link down. Please connect an ethernet cable."

# Set ifconfig
eth.ifconfig = ((MY_IP, MY_SUBNET_ADDR, MY_GW_ADDR, MY_DNS))



socket.set_interface(eth)

sock = socket.socket()
print(sock._socknum)

sock.connect((SERVER_ADDR, PORT))

data = bytearray(b'Hello CircuitPython')
eth.socket_write(sock._socknum, data)
Binary file added docs/_static/favicon.ico
Binary file not shown.
14 changes: 14 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

.. If you created a package, create one automodule per module in the package.
.. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py)
.. use this format as the module name: "adafruit_foo.foo"
.. automodule:: adafruit_wiznet5k.adafruit_wiznet5k
:members:

.. automodule:: adafruit_wiznet5k.adafruit_wiznet5k_socket
:members:

.. automodule:: adafruit_wiznet5k.adafruit_wiznet5k_dhcp
:members:
160 changes: 160 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-

import os
import sys
sys.path.insert(0, os.path.abspath('..'))

# -- General configuration ------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon',
'sphinx.ext.todo',
]

# TODO: Please Read!
# Uncomment the below if you use native CircuitPython modules such as
# digitalio, micropython and busio. List the modules you use. Without it, the
# autodoc module docs will fail to generate with a warning.
autodoc_mock_imports = ["digitalio", "busio", "adafruit_bus_device"]


intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'BusDevice': ('https://circuitpython.readthedocs.io/projects/busdevice/en/latest/', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)}

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'Adafruit Wiznet5k Library'
copyright = u'2020 Brent Rubell'
author = u'Brent Rubell'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u'1.0'
# The full version, including alpha/beta/rc tags.
release = u'1.0'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.env', 'CODE_OF_CONDUCT.md']

# The reST default role (used for this markup: `text`) to use for all
# documents.
#
default_role = "any"

# If true, '()' will be appended to :func: etc. cross-reference text.
#
add_function_parentheses = True

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# If this is True, todo emits a warning for each TODO entries. The default is False.
todo_emit_warnings = True

napoleon_numpy_docstring = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'

if not on_rtd: # only import and set the theme if we're building docs locally
try:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.']
except:
html_theme = 'default'
html_theme_path = ['.']
else:
html_theme_path = ['.']

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
html_favicon = '_static/favicon.ico'

# Output file base name for HTML help builder.
htmlhelp_basename = 'AdafruitWiznet5kLibrarydoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',

# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'AdafruitWiznet5kLibrary.tex', u'AdafruitWiznet5k Library Documentation',
author, 'manual'),
]

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'AdafruitWiznet5klibrary', u'Adafruit Wiznet5k Library Documentation',
[author], 1)
]

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'AdafruitWiznet5kLibrary', u'Adafruit Wiznet5k Library Documentation',
author, 'AdafruitWiznet5kLibrary', 'One line description of project.',
'Miscellaneous'),
]
8 changes: 8 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Simple test
------------

Ensure your device works with this simple test.

.. literalinclude:: ../examples/wiznet5k_simpletest.py
:caption: examples/wiznet5k_simpletest.py
:linenos:
47 changes: 47 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.. include:: ../README.rst

Table of Contents
=================

.. toctree::
:maxdepth: 4
:hidden:

self

.. toctree::
:caption: Examples

examples

.. toctree::
:caption: API Reference
:maxdepth: 3

api

.. toctree::
:caption: Tutorials


.. toctree::
:caption: Related Products


.. toctree::
:caption: Other Links

Download <https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k/releases/latest>
CircuitPython Reference Documentation <https://circuitpython.readthedocs.io>
CircuitPython Support Forum <https://forums.adafruit.com/viewforum.php?f=60>
Discord Chat <https://adafru.it/discord>
Adafruit Learning System <https://learn.adafruit.com>
Adafruit Blog <https://blog.adafruit.com>
Adafruit Store <https://www.adafruit.com>

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
51 changes: 51 additions & 0 deletions examples/wiznet5k_simpletest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import time

import board
import busio
import digitalio
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket

# Name address for wifitest.adafruit.com
SERVER_ADDRESS = (('104.236.193.178'), 80)

cs = digitalio.DigitalInOut(board.D10)
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Initialize ethernet interface with DHCP
eth = WIZNET5K(spi_bus, cs)

print("DHCP Assigned IP: ", eth.pretty_ip(eth.ip_address))

socket.set_interface(eth)

# Create a new socket
sock = socket.socket()

print("Connecting to: ", SERVER_ADDRESS[0])
sock.connect(SERVER_ADDRESS)

print("Connected to ", sock.getpeername())

# Make a HTTP Request
sock.send(b"GET /testwifi/index.html HTTP/1.1\n")
sock.send(b"Host: 104.236.193.178\n")
sock.send(b"Connection: close\n\n")

# Start transmission timer
start = time.monotonic()

bytes_avail = 0
while not bytes_avail:
bytes_avail = sock.available()
if bytes_avail > 0:
data = sock.recv(bytes_avail)
print(data[0])
break
time.sleep(0.05)

end = time.monotonic()
print("Received: %d bytes"%bytes_avail)
end = end - start / 1000000.0
rate = bytes_avail / end / 1000.0
print("Rate = %0.5f kbytes/second"%rate)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Adafruit-Blinka
adafruit-circuitpython-busdevice
65 changes: 65 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""A setuptools based setup module.
See:
https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject
"""

from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path

here = path.abspath(path.dirname(__file__))

# Get the long description from the README file
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
long_description = f.read()

setup(
name='adafruit-circuitpython-wiznet5k',

use_scm_version=True,
setup_requires=['setuptools_scm'],

description='Pure-Python interface for WIZNET 5k ethernet modules.',
long_description=long_description,
long_description_content_type='text/x-rst',

# The project's main homepage.
url='https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k',

# Author details
author='Adafruit Industries',
author_email='circuitpython@adafruit.com',

install_requires=[
'Adafruit-Blinka',
'adafruit-circuitpython-busdevice'
],

# Choose your license
license='MIT',

# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Topic :: Software Development :: Libraries',
'Topic :: System :: Hardware',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
],

# What does your project relate to?
keywords='adafruit blinka circuitpython micropython wiznet5k ethernet, wiznet, w5500, '
'w5200, internet, iot',

# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
# TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER,
# CHANGE `py_modules=['...']` TO `packages=['...']`
py_modules=['adafruit_wiznet5k'],
)

0 comments on commit 3bbecee

Please sign in to comment.