Merge branch 'main' into webflow_edit_page

This commit is contained in:
Scott Shawcroft 2022-07-29 10:03:07 -07:00 committed by GitHub
commit 51006f5eeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 546 additions and 285 deletions

View File

@ -143,7 +143,7 @@ jobs:
mpy-cross-mac:
runs-on: macos-10.15
runs-on: macos-11
steps:
- name: Dump GitHub context
env:
@ -176,27 +176,25 @@ jobs:
run: make -C mpy-cross -j2
- uses: actions/upload-artifact@v2
with:
name: mpy-cross-macos-catalina
name: mpy-cross-macos-11-x64
path: mpy-cross/mpy-cross
- name: Select SDK for M1 build
run: sudo xcode-select -switch /Applications/Xcode_12.3.app
- name: Build mpy-cross (arm64)
run: make -C mpy-cross -j2 -f Makefile.m1 V=2
- uses: actions/upload-artifact@v2
with:
name: mpy-cross-macos-bigsur-arm64
name: mpy-cross-macos-11-arm64
path: mpy-cross/mpy-cross-arm64
- name: Make universal binary
run: lipo -create -output mpy-cross-macos-universal mpy-cross/mpy-cross mpy-cross/mpy-cross-arm64
run: lipo -create -output mpy-cross-macos-11-universal mpy-cross/mpy-cross mpy-cross/mpy-cross-arm64
- uses: actions/upload-artifact@v2
with:
name: mpy-cross-macos-universal
path: mpy-cross-macos-universal
name: mpy-cross-macos-11-universal
path: mpy-cross-macos-11-universal
- name: Upload mpy-cross build to S3
run: |
[ -z "$AWS_ACCESS_KEY_ID" ] || aws s3 cp mpy-cross-macos-universal s3://adafruit-circuit-python/bin/mpy-cross/mpy-cross-macos-universal-${{ env.CP_VERSION }} --no-progress --region us-east-1
[ -z "$AWS_ACCESS_KEY_ID" ] || aws s3 cp mpy-cross/mpy-cross-arm64 s3://adafruit-circuit-python/bin/mpy-cross/mpy-cross-macos-bigsur-${{ env.CP_VERSION }}-arm64 --no-progress --region us-east-1
[ -z "$AWS_ACCESS_KEY_ID" ] || aws s3 cp mpy-cross/mpy-cross s3://adafruit-circuit-python/bin/mpy-cross/mpy-cross-macos-catalina-${{ env.CP_VERSION }} --no-progress --region us-east-1
[ -z "$AWS_ACCESS_KEY_ID" ] || aws s3 cp mpy-cross-macos-universal s3://adafruit-circuit-python/bin/mpy-cross/mpy-cross-macos-11-${{ env.CP_VERSION }}-universal --no-progress --region us-east-1
[ -z "$AWS_ACCESS_KEY_ID" ] || aws s3 cp mpy-cross/mpy-cross-arm64 s3://adafruit-circuit-python/bin/mpy-cross/mpy-cross-macos-11-${{ env.CP_VERSION }}-arm64 --no-progress --region us-east-1
[ -z "$AWS_ACCESS_KEY_ID" ] || aws s3 cp mpy-cross/mpy-cross s3://adafruit-circuit-python/bin/mpy-cross/mpy-cross-macos-11-${{ env.CP_VERSION }}-x64 --no-progress --region us-east-1
env:
AWS_PAGER: ''
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}

23
conf.py
View File

@ -33,25 +33,6 @@ from sphinx import addnodes
tools_describe = str(pathlib.Path(__file__).parent / "tools/describe")
# Monkeypatch autoapi
def _format_args(args_info, include_annotations=True, ignore_self=None):
result = []
for i, (prefix, name, annotation, default) in enumerate(args_info):
if i == 0 and ignore_self is not None and name == ignore_self:
continue
formatted = "{}{}{}{}".format(
prefix or "",
name or "",
": {}".format(annotation) if annotation and include_annotations else "",
(" = {}" if annotation else "={}").format(default) if default else "",
)
result.append(formatted)
return ", ".join(result)
import autoapi.mappers.python.objects as objects
objects._format_args = _format_args
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@ -71,8 +52,8 @@ subprocess.check_output(["make", "stubs"])
#modules_support_matrix = shared_bindings_matrix.support_matrix_excluded_boards()
modules_support_matrix = shared_bindings_matrix.support_matrix_by_board()
modules_support_matrix_reverse = defaultdict(list)
for board, modules in modules_support_matrix.items():
for module in modules[0]:
for board, matrix_info in modules_support_matrix.items():
for module in matrix_info["modules"]:
modules_support_matrix_reverse[module].append(board)
modules_support_matrix_reverse = dict(

View File

@ -39,6 +39,10 @@ CIRCUITPY_WEB_API_PASSWORD
~~~~~~~~~~~~~~~~~~~~~~~~~~
Password required to make modifications to the board from the Web Workflow.
CIRCUITPY_WEB_API_PORT
~~~~~~~~~~~~~~~~~~~~~~
TCP port number used for the web HTTP API. Defaults to 80 when omitted.
CIRCUITPY_WIFI_PASSWORD
~~~~~~~~~~~~~~~~~~~~~~~
Wi-Fi password used to auto connect to CIRCUITPY_WIFI_SSID.

View File

@ -32,7 +32,7 @@ from concurrent.futures import ThreadPoolExecutor
SUPPORTED_PORTS = ['atmel-samd', 'broadcom', 'cxd56', 'espressif', 'litex', 'mimxrt10xx', 'nrf', 'raspberrypi', 'stm']
aliases_by_board = {
ALIASES_BY_BOARD = {
"circuitplayground_express": [
"circuitplayground_express_4h",
"circuitplayground_express_digikey_pycon2019",
@ -43,7 +43,7 @@ aliases_by_board = {
"pewpew10": ["pewpew13"],
}
aliases_brand_names = {
ALIASES_BRAND_NAMES = {
"circuitplayground_express_4h":
"Adafruit Circuit Playground Express 4-H",
"circuitplayground_express_digikey_pycon2019":
@ -58,7 +58,7 @@ aliases_brand_names = {
"PewPew 13",
}
additional_modules = {
ADDITIONAL_MODULES = {
"fontio": "CIRCUITPY_DISPLAYIO",
"terminalio": "CIRCUITPY_DISPLAYIO",
"adafruit_bus_device": "CIRCUITPY_BUSDEVICE",
@ -66,7 +66,7 @@ additional_modules = {
"usb": "CIRCUITPY_USB_HOST",
}
frozen_excludes = ["examples", "docs", "tests", "utils", "conf.py", "setup.py"]
FROZEN_EXCLUDES = ["examples", "docs", "tests", "utils", "conf.py", "setup.py"]
"""Files and dirs at the root of a frozen directory that should be ignored.
This is the same list as in the preprocess_frozen_modules script."""
@ -74,7 +74,7 @@ repository_urls = {}
"""Cache of repository URLs for frozen modules."""
def get_circuitpython_root_dir():
""" The path to the root './circuitpython' directory
""" The path to the root './circuitpython' directory.
"""
file_path = pathlib.Path(__file__).resolve()
root_dir = file_path.parent.parent
@ -82,12 +82,40 @@ def get_circuitpython_root_dir():
return root_dir
def get_shared_bindings():
""" Get a list of modules in shared-bindings based on folder names
""" Get a list of modules in shared-bindings based on folder names.
"""
shared_bindings_dir = get_circuitpython_root_dir() / "shared-bindings"
return [item.name for item in shared_bindings_dir.iterdir()] + ["binascii", "errno", "json", "re", "ulab"]
def get_board_mapping():
"""
Compiles the list of boards from the directories, with aliases and mapping
to the port.
"""
boards = {}
for port in SUPPORTED_PORTS:
board_path = os.path.join("../ports", port, "boards")
for board_path in os.scandir(board_path):
if board_path.is_dir():
board_files = os.listdir(board_path.path)
board_id = board_path.name
aliases = ALIASES_BY_BOARD.get(board_path.name, [])
boards[board_id] = {
"port": port,
"download_count": 0,
"aliases": aliases,
}
for alias in aliases:
boards[alias] = {
"port": port,
"download_count": 0,
"alias": True,
"aliases": [],
}
return boards
def read_mpconfig():
""" Open 'circuitpy_mpconfig.mk' and return the contents.
"""
@ -112,8 +140,8 @@ def build_module_map():
full_build = False
for module in modules:
full_name = module
if module in additional_modules:
search_identifier = additional_modules[module]
if module in ADDITIONAL_MODULES:
search_identifier = ADDITIONAL_MODULES[module]
else:
search_identifier = 'CIRCUITPY_'+module.lstrip("_").upper()
re_pattern = f"{re.escape(search_identifier)}\s*\??=\s*(.+)"
@ -204,27 +232,33 @@ def get_repository_url(directory):
repository_urls[directory] = path
return path
def frozen_modules_from_dirs(frozen_mpy_dirs):
def frozen_modules_from_dirs(frozen_mpy_dirs, withurl):
"""
Go through the list of frozen directories and extract the python modules.
Paths are of the type:
$(TOP)/frozen/Adafruit_CircuitPython_CircuitPlayground
$(TOP)/frozen/circuitpython-stage/meowbit
Python modules are at the root of the path, and are python files or directories
containing python files. Except the ones in the frozen_excludes list.
containing python files. Except the ones in the FROZEN_EXCLUDES list.
"""
frozen_modules = []
for frozen_path in filter(lambda x: x, frozen_mpy_dirs.split(" ")):
source_dir = get_circuitpython_root_dir() / frozen_path[7:]
url_repository = get_repository_url(source_dir)
for sub in source_dir.glob("*"):
if sub.name in frozen_excludes:
if sub.name in FROZEN_EXCLUDES:
continue
if sub.name.endswith(".py"):
frozen_modules.append((sub.name[:-3], url_repository))
if withurl:
frozen_modules.append((sub.name[:-3], url_repository))
else:
frozen_modules.append(sub.name[:-3])
continue
if next(sub.glob("**/*.py"), None): # tests if not empty
frozen_modules.append((sub.name, url_repository))
if withurl:
frozen_modules.append((sub.name, url_repository))
else:
frozen_modules.append(sub.name)
return frozen_modules
def lookup_setting(settings, key, default=''):
@ -244,7 +278,7 @@ def all_ports_all_boards(ports=SUPPORTED_PORTS):
continue
yield (port, entry)
def support_matrix_by_board(use_branded_name=True):
def support_matrix_by_board(use_branded_name=True, withurl=True):
""" Compiles a list of the available core modules available for each
board.
"""
@ -272,29 +306,49 @@ def support_matrix_by_board(use_branded_name=True):
board_modules.append(base[module]['name'])
board_modules.sort()
if "CIRCUITPY_BUILD_EXTENSIONS" in settings:
board_extensions = [
extension.strip() for extension in
settings["CIRCUITPY_BUILD_EXTENSIONS"].split(",")
]
else:
raise OSError(f"Board extensions undefined: {board_name}.")
frozen_modules = []
if "FROZEN_MPY_DIRS" in settings:
frozen_modules = frozen_modules_from_dirs(settings["FROZEN_MPY_DIRS"])
frozen_modules = frozen_modules_from_dirs(settings["FROZEN_MPY_DIRS"], withurl)
if frozen_modules:
frozen_modules.sort()
# generate alias boards too
board_matrix = [(board_name, (board_modules, frozen_modules))]
if entry.name in aliases_by_board:
for alias in aliases_by_board[entry.name]:
board_matrix = [(
board_name, {
"modules": board_modules,
"frozen_libraries": frozen_modules,
"extensions": board_extensions,
}
)]
if entry.name in ALIASES_BY_BOARD:
for alias in ALIASES_BY_BOARD[entry.name]:
if use_branded_name:
if alias in aliases_brand_names:
alias = aliases_brand_names[alias]
if alias in ALIASES_BRAND_NAMES:
alias = ALIASES_BRAND_NAMES[alias]
else:
alias = alias.replace("_"," ").title()
board_matrix.append( (alias, (board_modules, frozen_modules)) )
board_matrix.append((
alias, {
"modules": board_modules,
"frozen_libraries": frozen_modules,
"extensions": board_extensions,
},
))
return board_matrix # this is now a list of (board,modules)
executor = ThreadPoolExecutor(max_workers=os.cpu_count())
mapped_exec = executor.map(support_matrix, all_ports_all_boards())
# flatmap with comprehensions
boards = dict(sorted([board for matrix in mapped_exec for board in matrix]))
boards = dict(sorted([board for matrix in mapped_exec for board in matrix], key=lambda x: x[0]))
return boards

View File

@ -72,7 +72,7 @@ Read-only characteristic that returns the UTF-8 encoded version string.
The web workflow is depends on adding Wi-Fi credentials into the `/.env` file. The keys are
`CIRCUITPY_WIFI_SSID` and `CIRCUITPY_WIFI_PASSWORD`. Once these are defined, CircuitPython will
automatically connect to the network and start the webserver used for the workflow. The webserver
is on port 80. It also enables MDNS.
is on port 80 unless overridden by `CIRCUITPY_WEB_API_PORT`. It also enables MDNS.
Here is an example `/.env`:
@ -83,6 +83,8 @@ CIRCUITPY_WIFI_PASSWORD='secretpassword'
# To enable modifying files from the web. Change this too!
CIRCUITPY_WEB_API_PASSWORD='passw0rd'
CIRCUITPY_WEB_API_PORT=80
```
MDNS is used to resolve [`circuitpython.local`](http://circuitpython.local) to a device specific

@ -1 +1 @@
Subproject commit 3bdd335452ff14a53d1e840de043e3159cb3b829
Subproject commit f993d5fac69f3a0cfa33988268666c462b72c0ec

View File

@ -7,4 +7,4 @@ BUILD=build-arm64
include mpy-cross.mk
# Because mpy-cross.mk unconditionally overwrites CC for Darwin, we must set it BELOW the inclusion
CC := $(shell xcrun --sdk macosx11.1 --find clang) -isysroot $(shell xcrun --sdk macosx11.1 --show-sdk-path) -target arm64-apple-macos11
CC := $(shell xcrun --find clang) -isysroot $(shell xcrun --show-sdk-path) -target arm64-apple-macos11

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Arduino"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Arduino"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Arduino"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Arduino"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Adafruit Industries LLC"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Adafruit Industries LLC"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Adafruit Industries LLC"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Adafruit Industries LLC"
CHIP_VARIANT = SAMD21G18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -33,6 +33,8 @@ CIRCUITPY_USB_CDC = 0
CIRCUITPY_USB_HID = 0
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_VECTORIO = 0
CIRCUITPY_BUSDEVICE = 0
CIRCUITPY_BITMAPTOOLS = 0
CIRCUITPY_ANALOGIO = 1
CIRCUITPY_AUDIOIO = 1
@ -42,6 +44,7 @@ CIRCUITPY_KEYPAD = 1
CIRCUITPY_MATH = 1
CIRCUITPY_STAGE = 1
CIRCUITPY_SYNTHIO = 1
CIRCUITPY_ZLIB = 1
FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pewpew_m4
CIRCUITPY_DISPLAY_FONT = $(TOP)/ports/atmel-samd/boards/ugame10/brutalist-6.bdf

View File

@ -6,6 +6,8 @@ USB_MANUFACTURER = "Itaca Innovation"
CHIP_VARIANT = SAMD21E18A
CHIP_FAMILY = samd21
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1
LONGINT_IMPL = NONE
CIRCUITPY_FULL_BUILD = 0

View File

@ -133,3 +133,5 @@ CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO)
endif # same51
######################################################################
CIRCUITPY_BUILD_EXTENSIONS ?= uf2

View File

@ -4,3 +4,5 @@ USB_PRODUCT = "Zero"
USB_MANUFACTURER = "Raspberry Pi"
CHIP_VARIANT = "bcm2835"
CIRCUITPY_BUILD_EXTENSIONS = disk.img.zip,kernel.img

View File

@ -4,3 +4,5 @@ USB_PRODUCT = "Zero W"
USB_MANUFACTURER = "Raspberry Pi"
CHIP_VARIANT = "bcm2835"
CIRCUITPY_BUILD_EXTENSIONS = disk.img.zip,kernel.img

View File

@ -24,3 +24,5 @@ INTERNAL_FLASH_FILESYSTEM = 1
USB_NUM_ENDPOINT_PAIRS = 8
USB_HIGHSPEED = 1
CIRCUITPY_BUILD_EXTENSIONS ?= disk.img.zip,kernel8.img

View File

@ -25,3 +25,5 @@ CIRCUITPY_TOUCHIO = 0
CIRCUITPY_USB_HID = 0
CIRCUITPY_USB_MIDI = 0
INTERNAL_LIBM = 1
CIRCUITPY_BUILD_EXTENSIONS ?= spk

View File

@ -16,4 +16,4 @@ CIRCUITPY_ESP_FLASH_MODE = qio
CIRCUITPY_ESP_FLASH_FREQ = 40m
CIRCUITPY_ESP_FLASH_SIZE = 4MB
CIRCUITPY_PS2IO = 0
OPTIMIZATION_FLAGS = -Os

View File

@ -37,6 +37,8 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_BUTTON_D), MP_ROM_PTR(&pin_GPIO11) },
{ MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO11) },
{ MP_ROM_QSTR(MP_QSTR_BOOT0), MP_ROM_PTR(&pin_GPIO0) },
{ MP_ROM_QSTR(MP_QSTR_LIGHT), MP_ROM_PTR(&pin_GPIO3) },
{ MP_ROM_QSTR(MP_QSTR_A3), MP_ROM_PTR(&pin_GPIO3) },

View File

@ -0,0 +1,26 @@
#include "shared-bindings/microcontroller/Pin.h"
#include "supervisor/board.h"
#include "components/driver/include/driver/gpio.h"
void board_init(void) {
// Debug UART
#ifdef DEBUG
common_hal_never_reset_pin(&pin_GPIO20);
common_hal_never_reset_pin(&pin_GPIO21);
#endif
}
bool board_requests_safe_mode(void) {
return false;
}
bool espressif_board_reset_pin_number(gpio_num_t pin_number) {
return false;
}
void reset_board(void) {
}
void board_deinit(void) {
}

View File

@ -0,0 +1,14 @@
#define MICROPY_HW_BOARD_NAME "Seeed Studio XIAO ESP32C3"
#define MICROPY_HW_MCU_NAME "ESP32-C3FN4"
#define DEFAULT_I2C_BUS_SCL (&pin_GPIO7)
#define DEFAULT_I2C_BUS_SDA (&pin_GPIO6)
#define DEFAULT_SPI_BUS_SCK (&pin_GPIO8)
#define DEFAULT_SPI_BUS_MOSI (&pin_GPIO10)
#define DEFAULT_SPI_BUS_MISO (&pin_GPIO9)
#define DEFAULT_UART_BUS_RX (&pin_GPIO20)
#define DEFAULT_UART_BUS_TX (&pin_GPIO21)
#define CIRCUITPY_ESP_USB_SERIAL_JTAG (1)

View File

@ -0,0 +1,10 @@
CIRCUITPY_CREATOR_ID = 0x000C2886
CIRCUITPY_CREATION_ID = 0x00C30001
IDF_TARGET = esp32c3
INTERNAL_FLASH_FILESYSTEM = 1
CIRCUITPY_ESP_FLASH_MODE = qio
CIRCUITPY_ESP_FLASH_FREQ = 80m
CIRCUITPY_ESP_FLASH_SIZE = 4MB

View File

@ -0,0 +1,43 @@
#include "shared-bindings/board/__init__.h"
STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS
{ MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO2) },
{ MP_ROM_QSTR(MP_QSTR_A0), MP_ROM_PTR(&pin_GPIO2) },
{ MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO3) },
{ MP_ROM_QSTR(MP_QSTR_A1), MP_ROM_PTR(&pin_GPIO3) },
{ MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_GPIO4) },
{ MP_ROM_QSTR(MP_QSTR_A2), MP_ROM_PTR(&pin_GPIO4) },
{ MP_ROM_QSTR(MP_QSTR_D3), MP_ROM_PTR(&pin_GPIO5) },
{ MP_ROM_QSTR(MP_QSTR_A3), MP_ROM_PTR(&pin_GPIO5) },
{ MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO6) },
{ MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO6) },
{ MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO7) },
{ MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO7) },
{ MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO21) },
{ MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO21) },
{ MP_ROM_QSTR(MP_QSTR_D7), MP_ROM_PTR(&pin_GPIO20) },
{ MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO20) },
{ MP_ROM_QSTR(MP_QSTR_D8), MP_ROM_PTR(&pin_GPIO8) },
{ MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO8) },
{ MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) },
{ MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO9) },
{ MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO10) },
{ MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO10) },
{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) },
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) },
{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) },
};
MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table);

View File

@ -0,0 +1,5 @@
#
# LWIP
#
CONFIG_LWIP_LOCAL_HOSTNAME="seeed-xiao-esp32c3"
# end of LWIP

View File

@ -204,5 +204,9 @@ mp_obj_t common_hal_mdns_server_find(mdns_server_obj_t *self, const char *servic
}
void common_hal_mdns_server_advertise_service(mdns_server_obj_t *self, const char *service_type, const char *protocol, mp_int_t port) {
mdns_service_add(NULL, service_type, protocol, port, NULL, 0);
if (mdns_service_exists(service_type, protocol, NULL)) {
mdns_service_port_set(service_type, protocol, port);
} else {
mdns_service_add(NULL, service_type, protocol, port, NULL, 0);
}
}

View File

@ -60,32 +60,35 @@ STATIC void socket_select_task(void *arg) {
FD_SET(socket_change_fd, &errfds);
int max_fd = socket_change_fd;
for (size_t i = 0; i < MP_ARRAY_SIZE(open_socket_fds); i++) {
if (open_socket_fds[i] < 0) {
int sockfd = open_socket_fds[i];
if (sockfd < 0) {
continue;
}
max_fd = MAX(max_fd, open_socket_fds[i]);
FD_SET(open_socket_fds[i], &readfds);
FD_SET(open_socket_fds[i], &errfds);
max_fd = MAX(max_fd, sockfd);
FD_SET(sockfd, &readfds);
FD_SET(sockfd, &errfds);
}
int num_triggered = select(max_fd + 1, &readfds, NULL, &errfds, NULL);
if (num_triggered < 0) {
// Maybe bad file descriptor
for (size_t i = 0; i < MP_ARRAY_SIZE(open_socket_fds); i++) {
int sockfd = open_socket_fds[i];
if (sockfd < 0) {
continue;
}
if (FD_ISSET(sockfd, &errfds)) {
int err;
int optlen = sizeof(int);
int ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, (socklen_t *)&optlen);
if (ret < 0) {
open_socket_fds[i] = -1;
// Try again.
continue;
}
}
// Check for bad file descriptor and queue up the background task before
// circling around.
if (num_triggered == -1 && errno == EBADF) {
// One for the change fd and one for the closed socket.
num_triggered = 2;
}
// Try and find the bad file and remove it from monitoring.
for (size_t i = 0; i < MP_ARRAY_SIZE(open_socket_fds); i++) {
int sockfd = open_socket_fds[i];
if (sockfd < 0) {
continue;
}
int err;
int optlen = sizeof(int);
int ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, (socklen_t *)&optlen);
if (ret < 0) {
open_socket_fds[i] = -1;
// Raise num_triggered so that we skip the assert and queue the background task.
num_triggered = 2;
}
}
assert(num_triggered >= 0);
@ -117,13 +120,13 @@ void socket_user_reset(void) {
user_socket[i] = false;
}
socket_change_fd = eventfd(0, 0);
// This task runs at a lower priority than CircuitPython and is used to wake CircuitPython
// up when any open sockets have data to read. It allows us to sleep otherwise.
// Run this at the same priority as CP so that the web workflow background task can be
// queued while CP is running. Both tasks can still sleep and, therefore, sleep overall.
(void)xTaskCreateStaticPinnedToCore(socket_select_task,
"socket_select",
2 * configMINIMAL_STACK_SIZE,
NULL,
0, // Run this at IDLE priority. We only need it when CP isn't running (at 1).
uxTaskPriorityGet(NULL),
socket_select_stack,
&socket_select_task_handle,
xPortGetCoreID());

View File

@ -40,6 +40,7 @@ CIRCUITPY_PARALLELDISPLAY = 0
# Protomatter needs to support ESP32.
CIRCUITPY_RGBMATRIX = 0
CIRCUITPY_USB = 0
CIRCUITPY_BUILD_EXTENSIONS ?= bin
else ifeq ($(IDF_TARGET),esp32c3)
CIRCUITPY_AESIO = 0
@ -57,17 +58,20 @@ CIRCUITPY_ROTARYIO = 0
CIRCUITPY_TOUCHIO ?= 1
CIRCUITPY_TOUCHIO_USE_NATIVE = 0
CIRCUITPY_USB = 0
CIRCUITPY_BUILD_EXTENSIONS ?= bin
else ifeq ($(IDF_TARGET),esp32s3)
CIRCUITPY_BLEIO = 1
CIRCUITPY_BLEIO_HCI = 0
CIRCUITPY_IMAGECAPTURE = 0
CIRCUITPY_PARALLELDISPLAY = 0
CIRCUITPY_BUILD_EXTENSIONS ?= bin,uf2
else ifeq ($(IDF_TARGET),esp32s2)
# No BLE on S2
CIRCUITPY_BLEIO = 0
CIRCUITPY_BLEIO_HCI = 0
CIRCUITPY_BUILD_EXTENSIONS ?= bin,uf2
endif
# From ESP32-S2/S3 Technical Reference Manual:

View File

@ -98,14 +98,23 @@ bool usb_serial_jtag_bytes_available(void) {
}
void usb_serial_jtag_write(const char *text, uint32_t length) {
if (USB_SERIAL_JTAG.fram_num.sof_frame_index > 0) {
size_t total_written = 0;
uint32_t start_time = supervisor_ticks_ms32();
// Time out after 5 milliseconds in case usb isn't actually reading CDC.
while (total_written < length && start_time - supervisor_ticks_ms32() < 5) {
total_written += usb_serial_jtag_ll_write_txfifo((const uint8_t *)(text + total_written), length - total_written);
RUN_BACKGROUND_TASKS;
}
usb_serial_jtag_ll_txfifo_flush();
if (!usb_serial_jtag_connected()) {
return;
}
size_t total_written = 0;
while (total_written < length) {
uint32_t start_time = supervisor_ticks_ms32();
// Wait until we can write to the FIFO again. If it takes too long, then
// assume we're disconnected.
while (!usb_serial_jtag_ll_txfifo_writable()) {
uint32_t now = supervisor_ticks_ms32();
if (now - start_time > 200) {
connected = false;
return;
}
}
total_written += usb_serial_jtag_ll_write_txfifo((const uint8_t *)(text + total_written), length - total_written);
RUN_BACKGROUND_TASKS;
}
usb_serial_jtag_ll_txfifo_flush();
}

View File

@ -29,3 +29,5 @@ CIRCUITPY_SDCARDIO = 0
# Enable USB support
CIRCUITPY_USB_HID = 1
CIRCUITPY_USB_MIDI = 1
CIRCUITPY_BUILD_EXTENSIONS ?= dfu

View File

@ -21,3 +21,5 @@ CIRCUITPY_PULSEIO = 0
CIRCUITPY_ROTARYIO = 0
CIRCUITPY_USB_MIDI = 1
LONGINT_IMPL = MPZ
CIRCUITPY_BUILD_EXTENSIONS ?= hex,uf2

View File

@ -6,4 +6,4 @@ USB_MANUFACTURER = "Invector Labs AB"
MCU_CHIP = nrf52840
SPI_FLASH_FILESYSTEM = 1
EXTERNAL_FLASH_DEVICES = "W25Q16JVxQ,W25Q32FV"
EXTERNAL_FLASH_DEVICES = "W25Q16JVxQ,W25Q32FV,W25Q64FV"

View File

@ -5,6 +5,8 @@ USB_MANUFACTURER = "Electronut Labs"
MCU_CHIP = nrf52840
CIRCUITPY_BUILD_EXTENSIONS = hex
INTERNAL_FLASH_FILESYSTEM = 1
CIRCUITPY_AUDIOIO = 0
CIRCUITPY_DISPLAYIO = 1

View File

@ -5,5 +5,7 @@ USB_MANUFACTURER = "makerdiary"
MCU_CHIP = nrf52840
CIRCUITPY_BUILD_EXTENSIONS = hex
QSPI_FLASH_FILESYSTEM = 1
EXTERNAL_FLASH_DEVICES = "MX25R6435F"

View File

@ -5,4 +5,6 @@ USB_MANUFACTURER = "makerdiary"
MCU_CHIP = nrf52840
CIRCUITPY_BUILD_EXTENSIONS = hex,uf2
INTERNAL_FLASH_FILESYSTEM = 1

View File

@ -3,6 +3,8 @@ CIRCUITPY_CREATION_ID = 0x80D8
MCU_CHIP = nrf52833
CIRCUITPY_BUILD_EXTENSIONS = combined.hex
INTERNAL_FLASH_FILESYSTEM = 1
# USB pins aren't used.

View File

@ -5,5 +5,7 @@ USB_MANUFACTURER = "Nordic Semiconductor"
MCU_CHIP = nrf52840
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
QSPI_FLASH_FILESYSTEM = 1
EXTERNAL_FLASH_DEVICES = "MX25R6435F"

View File

@ -5,4 +5,6 @@ USB_MANUFACTURER = "Nordic Semiconductor"
MCU_CHIP = nrf52840
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2
INTERNAL_FLASH_FILESYSTEM = 1

View File

@ -4,6 +4,8 @@ LD_TEMPLATE_FILE = boards/common.template.ld
INTERNAL_LIBM = 1
CIRCUITPY_BUILD_EXTENSIONS ?= uf2
# Number of USB endpoint pairs.
USB_NUM_ENDPOINT_PAIRS = 8

View File

@ -22,6 +22,7 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_SW_A), MP_ROM_PTR(&pin_GPIO14) },
{ MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO16) },
{ MP_ROM_QSTR(MP_QSTR_LED_R), MP_ROM_PTR(&pin_GPIO16) },
{ MP_ROM_QSTR(MP_QSTR_LED_G), MP_ROM_PTR(&pin_GPIO17) },
{ MP_ROM_QSTR(MP_QSTR_LED_B), MP_ROM_PTR(&pin_GPIO18) },

View File

@ -36,6 +36,8 @@ CIRCUITPY_AUDIOMIXER = 1
INTERNAL_LIBM = 1
CIRCUITPY_BUILD_EXTENSIONS ?= uf2
# Number of USB endpoint pairs.
USB_NUM_ENDPOINT_PAIRS = 8

View File

@ -13,6 +13,8 @@ MCU_SERIES = F4
MCU_VARIANT = STM32F401xE
MCU_PACKAGE = LQFP64
CIRCUITPY_BUILD_EXTENSIONS = uf2
OPTIMIZATION_FLAGS = -Os
LD_COMMON = boards/common_default.ld

View File

@ -19,3 +19,5 @@ LD_BOOT = boards/STM32F405_boot.ld
UF2_OFFSET = 0x8010000
CIRCUITPY_RGBMATRIX ?= 1
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2

View File

@ -71,3 +71,5 @@ CIRCUITPY_BUSDEVICE = 0
CIRCUITPY_KEYPAD = 1
CIRCUITPY_RGBMATRIX = 0
CIRCUITPY_RTC = 1
CIRCUITPY_BUILD_EXTENSIONS = bin,uf2

View File

@ -88,3 +88,4 @@ ifeq ($(MCU_SERIES),L4)
endif
CIRCUITPY_PARALLELDISPLAY := 0
CIRCUITPY_BUILD_EXTENSIONS ?= bin

View File

@ -17,6 +17,7 @@ polib
# For pre-commit
pyyaml
black
pre-commit
# for combining the Nordic SoftDevice with CircuitPython
intelhex

View File

@ -161,6 +161,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mdns_server_find_obj, 1, _mdns_server_find);
//| def advertise_service(self, *, service_type: str, protocol: str, port: int) -> None:
//| """Respond to queries for the given service with the given port.
//|
//| ``service_type`` and ``protocol`` can only occur on one port. Any call after the first
//| will update the entry's port.
//|
//| :param str service_type: The service type such as "_http"
//| :param str protocol: The service protocol such as "_tcp"
//| :param int port: The port used by the service"""

View File

@ -21,9 +21,9 @@ capable board, as well as each :term:`frozen module` included on it.
{% for key, value in support_matrix|dictsort %}
{{ '.. _' ~ key|replace(" ", "-") ~ ':' }}
* - {{ key }}
- {{ ':py:mod:`' ~ value[0]|join("`, :py:mod:`") ~ '`' }}
- {{ ':py:mod:`' ~ value.modules|join("`, :py:mod:`") ~ '`' }}
{% for module in value[1] %}\
{% for module in value.frozen_libraries %}\
{% if loop.index == 1 %}**Frozen Modules:** {% endif %}\
{% if loop.index > 1 %}, {% endif %}\
{% if module[1] %}{{ '`' ~ module[0] ~ ' <' ~ module[1] ~ '>`__' }}\

View File

@ -177,6 +177,17 @@ int readline_process_char(int c) {
vstr_cut_tail_bytes(rl.line, rl.line->len - rl.cursor_pos);
// set redraw parameters
redraw_from_cursor = true;
#endif
} else if (c == CHAR_CTRL_L) {
// CTRL-L is clear screen / redraw. This specific sequence is used
// (instead of a slightly more minimal sequence) for compatibility
// with the built-in Terminal class
mp_hal_stdout_tx_str("\x1b[;H\x1b[2J");
mp_hal_stdout_tx_str(rl.prompt);
mp_hal_stdout_tx_strn(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
// set redraw parameters
redraw_from_cursor = true;
#if MICROPY_REPL_EMACS_KEYS
} else if (c == CHAR_CTRL_N) {
// CTRL-N is go to next line in history
goto down_arrow_key;

View File

@ -35,6 +35,7 @@
#define CHAR_CTRL_E (5)
#define CHAR_CTRL_F (6)
#define CHAR_CTRL_K (11)
#define CHAR_CTRL_L (12)
#define CHAR_CTRL_N (14)
#define CHAR_CTRL_P (16)
#define CHAR_CTRL_U (21)

View File

@ -4,6 +4,7 @@
<title></title>
<meta charset="UTF-8">
<script src="/directory.js" defer=true></script>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1><a href="/"><img src="/favicon.ico"/></a>&nbsp;<span id="path"></span></h1>

View File

@ -5,6 +5,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/serial.js" defer=true></script>
<link rel="stylesheet" href="/style.css">
</head>
<body style="flex-direction: column; display: flex; height: 100%; width: 100%; margin: 0; font-size: 1rem;">
<div style="flex: auto; display: flex; overflow: auto; flex-direction: column;">

View File

@ -0,0 +1,7 @@
body {
max-width: 960px;
margin: 20px auto;
font-size: 18px;
font-family: "Proxima Nova", Verdana, sans-serif;
line-height: 20.7px;
}

View File

@ -5,17 +5,28 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/welcome.js" defer=true></script>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1><a href="/"><img src="/favicon.ico"/></a>&nbsp;Welcome!</h1>
Welcome to CircuitPython's Web API. Go to the <a href="/fs/">file browser</a> to work with files in the CIRCUITPY drive. Go to the <a href="/cp/serial/">serial terminal</a> to see code output and interact with the REPL. Make sure you've set <code>CIRCUITPY_WEB_API_PASSWORD='somepassword'</code> in <code>/.env</code>. Provide the password when the browser prompts for it. Leave the username blank.
<h2>Device Info</h2>
Board: <a id="board"></a><br>
Version: <span id="version"></span><br>
Hostname: <a id="hostname"></a><br>
IP: <a id="ip"></a>
<h2>Other Devices</h2>
Here are other CircuitPython devices on your network:
<p>Welcome to CircuitPython's Web API. Go to the <a href="/fs/">file browser</a> to work with files in the CIRCUITPY drive. Go to the <a href="/cp/serial/">serial terminal</a> to see code output and interact with the REPL. Make sure you've set <code>CIRCUITPY_WEB_API_PASSWORD='somepassword'</code> in <code>/.env</code>. Provide the password when the browser prompts for it. <strong>Leave the username blank.</strong></p>
<h2>Device Info:</h2>
<dl>
<dt>Board:</dt>
<dd><a id="board"></a></dd>
<dt>Version:</dt>
<dd><span id="version"></span></dd>
<dt>Hostname:</dt>
<dd><a id="hostname"></a></dd>
<dt>IP:</dt>
<dd><a id="ip"></a></dd>
</dl>
<h2>Here are other CircuitPython devices on your network:</h2>
<ul id="devices">
</ul>
</body>

View File

@ -44,7 +44,7 @@ async function find_devices() {
li.appendChild(a);
var port = "";
if (device.port != 80) {
port = ":" + version_info.port;
port = ":" + device.port;
}
var server;
if (mdns_works) {

View File

@ -98,6 +98,7 @@ typedef struct {
static wifi_radio_error_t wifi_status = WIFI_RADIO_ERROR_NONE;
static mdns_server_obj_t mdns;
static uint32_t web_api_port = 80;
static socketpool_socketpool_obj_t pool;
static socketpool_socket_obj_t listening;
@ -189,6 +190,9 @@ void supervisor_web_workflow_status(void) {
}
mp_printf(&mp_plat_print, "%s", _our_ip_encoded);
if (web_api_port != 80) {
mp_printf(&mp_plat_print, ":%d", web_api_port);
}
// TODO: Use these unicode to show signal strength: ▂▄▆█
}
} else {
@ -199,11 +203,6 @@ void supervisor_web_workflow_status(void) {
void supervisor_start_web_workflow(void) {
#if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI
if (common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj) &&
wifi_radio_get_ipv4_address(&common_hal_wifi_radio_obj) != 0) {
// Already started.
return;
}
char ssid[33];
char password[64];
@ -218,8 +217,10 @@ void supervisor_start_web_workflow(void) {
password_len <= 0 || (size_t)password_len >= sizeof(password)) {
return;
}
common_hal_wifi_init(false);
common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true);
if (!common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj)) {
common_hal_wifi_init(false);
common_hal_wifi_radio_set_enabled(&common_hal_wifi_radio_obj, true);
}
// TODO: Do our own scan so that we can find the channel we want before calling connect.
// Otherwise, connect will do a full slow scan to pick the best AP.
@ -227,6 +228,9 @@ void supervisor_start_web_workflow(void) {
// NUL terminate the strings because dotenv doesn't.
ssid[ssid_len] = '\0';
password[password_len] = '\0';
// We can all connect again because it will return early if we're already connected to the
// network. If we are connected to a different network, then it will disconnect before
// attempting to connect to the given network.
wifi_status = common_hal_wifi_radio_connect(
&common_hal_wifi_radio_obj, (uint8_t *)ssid, ssid_len, (uint8_t *)password, password_len,
0, 0.1, NULL, 0);
@ -236,21 +240,47 @@ void supervisor_start_web_workflow(void) {
return;
}
mdns_server_construct(&mdns, true);
common_hal_mdns_server_set_instance_name(&mdns, MICROPY_HW_BOARD_NAME);
common_hal_mdns_server_advertise_service(&mdns, "_circuitpython", "_tcp", 80);
char port_encoded[6];
size_t port_len = 0;
size_t new_port = web_api_port;
#if CIRCUITPY_DOTENV
port_len = dotenv_get_key("/.env", "CIRCUITPY_WEB_API_PORT", port_encoded, sizeof(port_encoded) - 1);
#endif
if (0 < port_len && port_len < sizeof(port_encoded)) {
port_encoded[port_len] = '\0';
new_port = strtoul(port_encoded, NULL, 10);
}
pool.base.type = &socketpool_socketpool_type;
common_hal_socketpool_socketpool_construct(&pool, &common_hal_wifi_radio_obj);
bool first_start = mdns.base.type != &mdns_server_type;
bool port_changed = new_port != web_api_port;
ESP_LOGI(TAG, "Starting web workflow");
listening.base.type = &socketpool_socket_type;
socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, &listening);
common_hal_socketpool_socket_settimeout(&listening, 0);
// Bind to any ip.
// TODO: Make this port .env configurable.
common_hal_socketpool_socket_bind(&listening, "", 0, 80);
common_hal_socketpool_socket_listen(&listening, 1);
if (first_start) {
ESP_LOGI(TAG, "Starting web workflow");
mdns_server_construct(&mdns, true);
mdns.base.type = &mdns_server_type;
common_hal_mdns_server_set_instance_name(&mdns, MICROPY_HW_BOARD_NAME);
pool.base.type = &socketpool_socketpool_type;
common_hal_socketpool_socketpool_construct(&pool, &common_hal_wifi_radio_obj);
listening.base.type = &socketpool_socket_type;
active.base.type = &socketpool_socket_type;
active.num = -1;
active.connected = false;
websocket_init();
}
if (port_changed) {
common_hal_socketpool_socket_close(&listening);
}
if (first_start || port_changed) {
web_api_port = new_port;
common_hal_mdns_server_advertise_service(&mdns, "_circuitpython", "_tcp", web_api_port);
socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, &listening);
common_hal_socketpool_socket_settimeout(&listening, 0);
// Bind to any ip.
common_hal_socketpool_socket_bind(&listening, "", 0, web_api_port);
common_hal_socketpool_socket_listen(&listening, 1);
}
mp_int_t api_password_len = dotenv_get_key("/.env", "CIRCUITPY_WEB_API_PASSWORD", _api_password + 1, sizeof(_api_password) - 2);
if (api_password_len > 0) {
@ -259,12 +289,6 @@ void supervisor_start_web_workflow(void) {
_base64_in_place(_api_password, api_password_len + 1, sizeof(_api_password));
}
active.base.type = &socketpool_socket_type;
active.num = -1;
active.connected = false;
websocket_init();
// TODO:
// GET /cp/serial.txt
// - Most recent 1k of serial output.
@ -283,6 +307,10 @@ static void _send_raw(socketpool_socket_obj_t *socket, const uint8_t *buf, int l
}
}
STATIC void _print_raw(void *env, const char *str, size_t len) {
_send_raw((socketpool_socket_obj_t *)env, (const uint8_t *)str, (size_t)len);
}
static void _send_str(socketpool_socket_obj_t *socket, const char *str) {
_send_raw(socket, (const uint8_t *)str, strlen(str));
}
@ -301,14 +329,19 @@ static void _send_strs(socketpool_socket_obj_t *socket, ...) {
}
static void _send_chunk(socketpool_socket_obj_t *socket, const char *chunk) {
char encoded_len[sizeof(size_t) * 2 + 1];
int len = snprintf(encoded_len, sizeof(encoded_len), "%X", strlen(chunk));
_send_raw(socket, (const uint8_t *)encoded_len, len);
_send_raw(socket, (const uint8_t *)"\r\n", 2);
mp_print_t _socket_print = {socket, _print_raw};
mp_printf(&_socket_print, "%X\r\n", strlen(chunk));
_send_raw(socket, (const uint8_t *)chunk, strlen(chunk));
_send_raw(socket, (const uint8_t *)"\r\n", 2);
}
STATIC void _print_chunk(void *env, const char *str, size_t len) {
mp_print_t _socket_print = {env, _print_raw};
mp_printf(&_socket_print, "%X\r\n", len);
_send_raw((socketpool_socket_obj_t *)env, (const uint8_t *)str, len);
_send_raw((socketpool_socket_obj_t *)env, (const uint8_t *)"\r\n", 2);
}
// A bit of a misnomer because it sends all arguments as one chunk.
// The last argument must be NULL! Otherwise, it won't stop.
static void _send_chunks(socketpool_socket_obj_t *socket, ...) {
@ -326,9 +359,9 @@ static void _send_chunks(socketpool_socket_obj_t *socket, ...) {
}
va_end(strs_to_count);
char encoded_len[sizeof(size_t) * 2 + 1];
snprintf(encoded_len, sizeof(encoded_len), "%X", chunk_len);
_send_strs(socket, encoded_len, "\r\n", NULL);
mp_print_t _socket_print = {socket, _print_raw};
mp_printf(&_socket_print, "%X\r\n", chunk_len);
str = va_arg(strs_to_send, const char *);
while (str != NULL) {
@ -531,7 +564,12 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request,
_send_str(socket, "http");
}
_send_strs(socket, "://", hostname, ".local", path, "\r\n", NULL);
_send_strs(socket, "://", hostname, ".local", NULL);
if (web_api_port != 80) {
mp_print_t _socket_print = {socket, _print_raw};
mp_printf(&_socket_print, ":%d", web_api_port);
}
_send_strs(socket, path, "\r\n", NULL);
_cors_header(socket, request);
_send_str(socket, "\r\n");
}
@ -540,6 +578,7 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
socketpool_socket_send(socket, (const uint8_t *)OK_JSON, strlen(OK_JSON));
_cors_header(socket, request);
_send_str(socket, "\r\n");
mp_print_t _socket_print = {socket, _print_chunk};
_send_chunk(socket, "[");
bool first = true;
@ -560,7 +599,7 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
}
// We use nanoseconds past Jan 1, 1970 for consistency with BLE API and
// LittleFS.
_send_chunk(socket, ", \"modified_ns\": ");
_send_chunk(socket, ", ");
uint64_t truncated_time = timeutils_mktime(1980 + (file_info.fdate >> 9),
(file_info.fdate >> 5) & 0xf,
@ -569,15 +608,17 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
(file_info.ftime >> 5) & 0x1f,
(file_info.ftime & 0x1f) * 2) * 1000000000ULL;
char encoded_number[32];
snprintf(encoded_number, sizeof(encoded_number), "%lld", truncated_time);
_send_chunks(socket, encoded_number, ", \"file_size\": ", NULL);
// Use snprintf because mp_printf doesn't support 64 bit numbers by
// default.
char encoded_time[32];
snprintf(encoded_time, sizeof(encoded_time), "%llu", truncated_time);
mp_printf(&_socket_print, "\"modified_ns\": %s, ", encoded_time);
size_t file_size = 0;
if ((file_info.fattrib & AM_DIR) == 0) {
file_size = file_info.fsize;
}
snprintf(encoded_number, sizeof(encoded_number), "%d", file_size);
_send_chunks(socket, encoded_number, "}", NULL);
mp_printf(&_socket_print, "\"file_size\": %d }", file_size);
first = false;
res = f_readdir(dir, &file_info);
}
@ -587,12 +628,10 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
static void _reply_with_file(socketpool_socket_obj_t *socket, _request *request, const char *filename, FIL *active_file) {
uint32_t total_length = f_size(active_file);
char encoded_len[10];
snprintf(encoded_len, sizeof(encoded_len), "%d", total_length);
_send_strs(socket,
"HTTP/1.1 200 OK\r\n",
"Content-Length: ", encoded_len, "\r\n", NULL);
_send_str(socket, "HTTP/1.1 200 OK\r\n");
mp_print_t _socket_print = {socket, _print_raw};
mp_printf(&_socket_print, "Content-Length: %d\r\n", total_length);
// TODO: Make this a table to save space.
if (_endswith(filename, ".txt") || _endswith(filename, ".py")) {
_send_str(socket, "Content-Type: text/plain\r\n");
@ -640,27 +679,23 @@ static void _reply_with_devices_json(socketpool_socket_obj_t *socket, _request *
socketpool_socket_send(socket, (const uint8_t *)OK_JSON, strlen(OK_JSON));
_cors_header(socket, request);
_send_str(socket, "\r\n");
char total_encoded[4];
snprintf(total_encoded, sizeof(total_encoded), "%d", total_results);
_send_chunks(socket, "{\"total\": ", total_encoded, ", \"devices\": [", NULL);
mp_print_t _socket_print = {socket, _print_chunk};
mp_printf(&_socket_print, "{\"total\": %d, \"devices\": [", total_results);
for (size_t i = 0; i < count; i++) {
if (i > 0) {
_send_chunk(socket, ",");
}
const char *hostname = common_hal_mdns_remoteservice_get_hostname(&found_devices[i]);
const char *instance_name = common_hal_mdns_remoteservice_get_instance_name(&found_devices[i]);
char port_encoded[4];
int port = common_hal_mdns_remoteservice_get_port(&found_devices[i]);
snprintf(port_encoded, sizeof(port_encoded), "%d", port);
char ip_encoded[4 * 4];
uint32_t ipv4_address = mdns_remoteservice_get_ipv4_address(&found_devices[i]);
uint8_t *octets = (uint8_t *)&ipv4_address;
snprintf(ip_encoded, sizeof(ip_encoded), "%d.%d.%d.%d", octets[0], octets[1], octets[2], octets[3]);
_send_chunks(socket,
"{\"hostname\": \"", hostname, "\", ",
"\"instance_name\": \"", instance_name, "\", ",
"\"port\": ", port_encoded, ", ",
"\"ip\": \"", ip_encoded, "\"}", NULL);
mp_printf(&_socket_print,
"{\"hostname\": \"%s\", "
"\"instance_name\": \"%s\", "
"\"port\": %d, "
"\"ip\": \"%d.%d.%d.%d\"}", hostname, instance_name, port, octets[0], octets[1], octets[2], octets[3]);
common_hal_mdns_remoteservice_deinit(&found_devices[i]);
}
_send_chunk(socket, "]}");
@ -672,24 +707,22 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request *
_send_str(socket, OK_JSON);
_cors_header(socket, request);
_send_str(socket, "\r\n");
char encoded_creator_id[11]; // 2 ** 32 is 10 decimal digits plus one for \0
snprintf(encoded_creator_id, sizeof(encoded_creator_id), "%u", CIRCUITPY_CREATOR_ID);
char encoded_creation_id[11]; // 2 ** 32 is 10 decimal digits plus one for \0
snprintf(encoded_creation_id, sizeof(encoded_creation_id), "%u", CIRCUITPY_CREATION_ID);
mp_print_t _socket_print = {socket, _print_chunk};
const char *hostname = common_hal_mdns_server_get_hostname(&mdns);
_send_chunks(socket,
"{\"web_api_version\": 1, ",
"\"version\": \"", MICROPY_GIT_TAG, "\", ",
"\"build_date\": \"", MICROPY_BUILD_DATE, "\", ",
"\"board_name\": \"", MICROPY_HW_BOARD_NAME, "\", ",
"\"mcu_name\": \"", MICROPY_HW_MCU_NAME, "\", ",
"\"board_id\": \"", CIRCUITPY_BOARD_ID, "\", ",
"\"creator_id\": ", encoded_creator_id, ", ",
"\"creation_id\": ", encoded_creation_id, ", ",
"\"hostname\": \"", hostname, "\", ",
"\"port\": 80, ",
"\"ip\": \"", _our_ip_encoded,
"\"}", NULL);
// Note: this leverages the fact that C concats consecutive string literals together.
mp_printf(&_socket_print,
"{\"web_api_version\": 1, "
"\"version\": \"" MICROPY_GIT_TAG "\", "
"\"build_date\": \"" MICROPY_BUILD_DATE "\", "
"\"board_name\": \"" MICROPY_HW_BOARD_NAME "\", "
"\"mcu_name\": \"" MICROPY_HW_MCU_NAME "\", "
"\"board_id\": \"" CIRCUITPY_BOARD_ID "\", "
"\"creator_id\": %u, "
"\"creation_id\": %u, "
"\"hostname\": \"%s\", "
"\"port\": %d, "
"\"ip\": \"%s\"}", CIRCUITPY_CREATOR_ID, CIRCUITPY_CREATION_ID, hostname, web_api_port, _our_ip_encoded);
// Empty chunk signals the end of the response.
_send_chunk(socket, "");
}
@ -863,6 +896,7 @@ STATIC_FILE(welcome_html);
STATIC_FILE(welcome_js);
STATIC_FILE(edit_html);
STATIC_FILE(edit_js);
STATIC_FILE(style_css);
STATIC_FILE(serial_html);
STATIC_FILE(serial_js);
STATIC_FILE(blinka_16x16_ico);
@ -1084,6 +1118,8 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
_REPLY_STATIC(socket, request, serial_js);
} else if (strcmp(request->path, "/edit.js") == 0) {
_REPLY_STATIC(socket, request, edit_js);
} else if (strcmp(request->path, "/style.css") == 0) {
_REPLY_STATIC(socket, request, style_css);
} else if (strcmp(request->path, "/favicon.ico") == 0) {
// TODO: Autogenerate this based on the blinka bitmap and change the
// palette based on MAC address.
@ -1193,7 +1229,12 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
request->authenticated = strncmp(request->header_value, prefix, strlen(prefix)) == 0 &&
strcmp(_api_password, request->header_value + strlen(prefix)) == 0;
} else if (strcasecmp(request->header_key, "Host") == 0) {
request->redirect = strcmp(request->header_value, "circuitpython.local") == 0;
// Do a prefix check so that port is ignored. Length must be the same or the
// header ends in :.
const char *cp_local = "circuitpython.local";
request->redirect = strncmp(request->header_value, cp_local, strlen(cp_local)) == 0 &&
(strlen(request->header_value) == strlen(cp_local) ||
request->header_value[strlen(cp_local)] == ':');
} else if (strcasecmp(request->header_key, "Content-Length") == 0) {
request->content_length = strtoul(request->header_value, NULL, 10);
} else if (strcasecmp(request->header_key, "Expect") == 0) {
@ -1279,6 +1320,9 @@ void supervisor_web_workflow_background(void) {
// If we have a request in progress, continue working on it.
if (common_hal_socketpool_socket_get_connected(&active)) {
_process_request(&active, &active_request);
} else {
// Close the active socket if it is no longer connected.
common_hal_socketpool_socket_close(&active);
}
}

View File

@ -19,76 +19,11 @@ import adabot.github_requests as github
sys.path.append("../docs")
from shared_bindings_matrix import (
SUPPORTED_PORTS,
aliases_by_board,
support_matrix_by_board,
get_board_mapping,
)
BIN = ("bin",)
UF2 = ("uf2",)
BIN_UF2 = ("bin", "uf2")
HEX = ("hex",)
HEX_UF2 = ("hex", "uf2")
SPK = ("spk",)
DFU = ("dfu",)
BIN_DFU = ("bin", "dfu")
COMBINED_HEX = ("combined.hex",)
KERNEL8_IMG = ("disk.img.zip", "kernel8.img")
KERNEL_IMG = ("disk.img.zip", "kernel.img")
# Default extensions
extension_by_port = {
"atmel-samd": UF2,
"broadcom": KERNEL8_IMG,
"cxd56": SPK,
"espressif": BIN_UF2,
"litex": DFU,
"mimxrt10xx": HEX_UF2,
"nrf": UF2,
"raspberrypi": UF2,
"stm": BIN,
}
# Per board overrides
extension_by_board = {
# samd
"arduino_mkr1300": BIN_UF2,
"arduino_mkrzero": BIN_UF2,
"arduino_nano_33_iot": BIN_UF2,
"arduino_zero": BIN_UF2,
"feather_m0_adalogger": BIN_UF2,
"feather_m0_basic": BIN_UF2,
"feather_m0_rfm69": BIN_UF2,
"feather_m0_rfm9x": BIN_UF2,
"uchip": BIN_UF2,
# nRF52840 dev kits that may not have UF2 bootloaders,
"makerdiary_nrf52840_mdk": HEX,
"makerdiary_nrf52840_mdk_usb_dongle": HEX_UF2,
"pca10056": BIN_UF2,
"pca10059": BIN_UF2,
"electronut_labs_blip": HEX,
"microbit_v2": COMBINED_HEX,
# stm32
"meowbit_v121": UF2,
"sparkfun_stm32_thing_plus": BIN_UF2,
"swan_r5": BIN_UF2,
# esp32
"adafruit_feather_esp32_v2": BIN,
# esp32c3
"adafruit_qtpy_esp32c3": BIN,
"ai_thinker_esp32-c3s": BIN,
"ai_thinker_esp32-c3s-2m": BIN,
"beetle-esp32-c3": BIN,
"espressif_esp32c3_devkitm_1_n4": BIN,
"lilygo_ttgo_t-01c3": BIN,
"lolin_c3_mini": BIN,
"microdev_micro_c3": BIN,
"lilygo_ttgo_t-oi-plus": BIN,
# broadcom
"raspberrypi_zero": KERNEL_IMG,
"raspberrypi_zero_w": KERNEL_IMG,
}
language_allow_list = set(
LANGUAGE_ALLOW_LIST = set(
[
"ID",
"de_DE",
@ -117,38 +52,10 @@ def get_languages(list_all=False):
if f.name.endswith(".po"):
languages.add(f.name[:-3])
if not list_all:
languages = languages & language_allow_list
languages = languages & LANGUAGE_ALLOW_LIST
return sorted(list(languages), key=str.casefold)
def get_board_mapping():
boards = {}
for port in SUPPORTED_PORTS:
board_path = os.path.join("../ports", port, "boards")
for board_path in os.scandir(board_path):
if board_path.is_dir():
board_files = os.listdir(board_path.path)
board_id = board_path.name
extensions = extension_by_port[port]
extensions = extension_by_board.get(board_path.name, extensions)
aliases = aliases_by_board.get(board_path.name, [])
boards[board_id] = {
"port": port,
"extensions": extensions,
"download_count": 0,
"aliases": aliases,
}
for alias in aliases:
boards[alias] = {
"port": port,
"extensions": extensions,
"download_count": 0,
"alias": True,
"aliases": [],
}
return boards
def get_version_info():
version = None
sha = git("rev-parse", "--short", "HEAD").stdout.decode("utf-8")
@ -283,7 +190,7 @@ def generate_download_info():
languages = get_languages()
support_matrix = support_matrix_by_board(use_branded_name=False)
support_matrix = support_matrix_by_board(use_branded_name=False, withurl=False)
new_stable = "-" not in new_tag
@ -310,20 +217,19 @@ def generate_download_info():
board_files = os.listdir(board_path.path)
board_id = board_path.name
board_info = board_mapping[board_id]
for alias in [board_id] + board_info["aliases"]:
alias_info = board_mapping[alias]
if alias not in current_info:
changes["new_boards"].append(alias)
current_info[alias] = {"downloads": 0, "versions": []}
new_version = {
"stable": new_stable,
"version": new_tag,
"modules": support_matrix[alias][0],
"languages": languages,
"extensions": board_info["extensions"],
"frozen_libraries": [frozen[0] for frozen in support_matrix[alias][1]],
# add modules, extensions, frozen_libraries explicitly
"modules": support_matrix[alias]["modules"],
"extensions": support_matrix[alias]["extensions"],
"frozen_libraries": support_matrix[alias]["frozen_libraries"],
}
current_info[alias]["downloads"] = alias_info["download_count"]
current_info[alias]["versions"].append(new_version)
@ -333,9 +239,10 @@ def generate_download_info():
if changes["new_release"] and user:
create_pr(changes, current_info, git_info, user)
else:
print("No new release to update")
if "DEBUG" in os.environ:
print(create_json(current_info).decode("utf8"))
else:
print("No new release to update")
if __name__ == "__main__":

View File

@ -12,6 +12,9 @@ import shutil
import build_board_info as build_info
import time
sys.path.append("../docs")
from shared_bindings_matrix import get_settings_from_makefile
for port in build_info.SUPPORTED_PORTS:
result = subprocess.run("rm -rf ../ports/{port}/build*".format(port=port), shell=True)
@ -39,6 +42,7 @@ for board in build_boards:
bin_directory = "../bin/{}/".format(board)
os.makedirs(bin_directory, exist_ok=True)
board_info = all_boards[board]
board_settings = get_settings_from_makefile("../ports/" + board_info["port"], board)
for language in languages:
bin_directory = "../bin/{board}/{language}".format(board=board, language=language)
@ -82,8 +86,12 @@ for board in build_boards:
success = "\033[31mfailed\033[0m"
other_output = ""
extensions = [
extension.strip()
for extension in board_settings["CIRCUITPY_BUILD_EXTENSIONS"].split(",")
]
for extension in board_info["extensions"]:
for extension in extensions:
temp_filename = "../ports/{port}/{build}/firmware.{extension}".format(
port=board_info["port"], build=build_dir, extension=extension
)

View File

@ -21,6 +21,7 @@ import json
import yaml
import build_board_info
from shared_bindings_matrix import get_settings_from_makefile
PORT_TO_ARCH = {
"atmel-samd": "arm",
@ -53,6 +54,7 @@ def set_boards_to_build(build_all):
all_board_ids = set()
port_to_boards = {}
board_to_port = {}
board_settings = {}
for board_id in boards_info_json:
info = boards_info_json[board_id]
if info.get("alias", False):
@ -70,6 +72,9 @@ def set_boards_to_build(build_all):
boards_to_build = set()
board_pattern = re.compile(r"^ports/[^/]+/boards/([^/]+)/")
port_pattern = re.compile(r"^ports/([^/]+)/")
module_pattern = re.compile(
r"^(ports/[^/]+/common-hal|shared-bindings|shared-module)/([^/]+)/"
)
for p in changed_files:
# See if it is board specific
board_matches = board_pattern.search(p)
@ -80,7 +85,8 @@ def set_boards_to_build(build_all):
# See if it is port specific
port_matches = port_pattern.search(p)
if port_matches:
module_matches = module_pattern.search(p)
if port_matches and not module_matches:
port = port_matches.group(1)
if port != "unix":
boards_to_build.update(port_to_boards[port])
@ -94,6 +100,48 @@ def set_boards_to_build(build_all):
if p.startswith("tests"):
continue
# As a (nearly) last resort, for some certain files, we compute the settings from the
# makefile for each board and determine whether to build them that way.
if p.startswith("frozen") or p.startswith("supervisor") or module_matches:
for board in all_board_ids:
if board not in board_settings:
board_settings[board] = get_settings_from_makefile(
"../ports/" + board_to_port[board], board
)
settings = board_settings[board]
# Check frozen files to see if they are in each board.
frozen = settings.get("FROZEN_MPY_DIRS", "")
if frozen and p.startswith("frozen") and p in frozen:
boards_to_build.add(board)
continue
# Check supervisor files. This is useful for limiting workflow changes to the
# relevant boards.
supervisor = settings["SRC_SUPERVISOR"]
if p.startswith("supervisor"):
if p in supervisor:
boards_to_build.add(board)
continue
web_workflow = settings["CIRCUITPY_WEB_WORKFLOW"]
while web_workflow.startswith("$("):
web_workflow = settings[web_workflow[2:-1]]
if (
p.startswith("supervisor/shared/web_workflow/static/")
and web_workflow != "0"
):
boards_to_build.add(board)
continue
# Check module matches
if module_matches:
module = module_matches.group(2) + "/"
if module in settings["SRC_PATTERNS"]:
boards_to_build.add(board)
continue
continue
# Otherwise build it all
boards_to_build = all_board_ids
break
@ -101,7 +149,7 @@ def set_boards_to_build(build_all):
# Split boards by architecture.
print("Building boards:")
arch_to_boards = {"aarch": [], "arm": [], "riscv": [], "espressif": []}
for board in boards_to_build:
for board in sorted(boards_to_build):
print(" ", board)
port = board_to_port.get(board)
# A board can appear due to its _deletion_ (rare)