Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit e5f77ab

Browse files
XykonoligaucIslam Wahdanrobert-hhhusigeza
authored
MicroPython release 1.20.2.rc2 based on Espressif IDF 3.3.1 (#394)
* BLE: Allow static passwords, remove bonded when pin changes * mods/pybadc.c: Fix the argument handling of bits=x for adc.init() Before, the only accepted argument & value for adc.init() was bits=12, and even that was discarded. With that change, the function meets the documentation and works as expected. * mods/pyblte.c: Fix the lenght field for AT commands At three places the length field was not set when AT commands werde issued, causing lte.attach() with the band=xx option to fail. Also, affected: lte_get_modem_version() * Support Sigfox registration for Pybytes * PYFW-390: Update .gitignore to allow esp32/lib libraries to be updated * MDNS advertisement works * Adding text to advertisement works * Query works * Free up internal query results * Change host_name to hostname * Rename text to txt * Adding libmdns.a to esp32/lib * Adding scripts for PyJTAG and short Readme * update sigfox libraries * Replace xQueueSendFromISR call with xQueueSend when called from BLE event Use the callback queue to notify the LoRa Timer Thread Do context switch in TimerCallback only if needed * Replacing "switch-case" with "if-else if" in Region.c to avoid generated Assembly instructions which are reading the Flash from IRAM functions causing bad00bad issue * BLE characteristic update messages are lost if they sent too frequently In modbt.c it can happen that received packets are lost, because the user registered callback is not called fast enough (via gatts_char_callback_handler()), which means it can happen that the value of a characteristic is updated 2 times in a row (via gatts_event_handler()) without giving the possibility to the callback to run and fetch the new value. In this specific example the first callback will show the 2nd change, and the 2nd callback will show that actually nothing happened. The 1st change gets lost. The fix of this issue breaks backward compatibility, because the prototype of the user registered Python callback is changed: OLD: callback(characteristic) NEW: callback(characteristic, (event, value)) → (event, value) is a tuple, where data is bytearray containing the value of the request if the “event” is WRITE, otherwise “value” is “None”. “event” shows the correct event any time. * PYFW-394: mod_ssl_setup_socket() allocates memory while GIL is not locked * PYFW-401: Mutex of LFS can be locked in wlan_read_file or mod_ssl_read_file * PYFW-402: wlan_do_connect() allocates memory while GIL is not locked PYFW-403: mod_network_register_nic() using MicroPython functions outside of GIL in wlan_setup_ap() * PYFW-404: bt_connect_helper() uses MicroPython functions outside of GIL * PYFW-405: lte_send_at_cmd() uses MicroPython functions outside of GIL * refactor lte_add_band() out of lte_attach() * add bands=() parameter to lte.attach() this way the user can select a set of bands they want to use when attaching * PYFW-391: Add changes needed for esp-idf v3.3.1 * Updating README.md * Update sdkconfig.h and libraries when secure boot is ON, but CONFIG_SECURE_BOOT_ENABLED is removed from sdkconfig.h * Add checker which forbids compilation when CONFIG_SECURE_BOOT_ENABLED is defined but SECURE=on is not * Add pybytes_on_boot functionality (#87) * Update pycom_version.h Update version to 1.20.2.rc2 for public release * Update Pybytes to version 1.3.1 Co-authored-by: oligauc <oliver@pycom.io> Co-authored-by: Islam Wahdan <islam@pycom.io> Co-authored-by: robert-hh <robert@hammelrath.com> Co-authored-by: Géza Husi <husigeza91@gmail.com> Co-authored-by: Jirka Krepl <jiri.krepl@gmail.com> Co-authored-by: doniks <p.putz@yahoo.de>
1 parent 78777bd commit e5f77ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+2732
-1262
lines changed

Jenkinsfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ node {
99
stage('Checkout') {
1010
checkout scm
1111
sh 'rm -rf esp-idf'
12-
sh 'git clone --depth=1 --recursive -b idf_v3.2 https://github.com/pycom/pycom-esp-idf.git esp-idf'
12+
sh 'git clone --depth=1 --recursive -b idf_v3.3.1 https://github.com/pycom/pycom-esp-idf.git esp-idf'
1313
}
1414

1515
stage('git-tag') {

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ board (PyBoard), the officially supported reference electronic circuit board.
2626
The following components are actively maintained by Pycom:
2727
- py/ -- the core Python implementation, including compiler, runtime, and
2828
core library.
29-
- exp32/ -- a version of MicroPython that runs on the ESP32 based boards from Pycom.
29+
- esp32/ -- a version of MicroPython that runs on the ESP32 based boards from Pycom.
3030
- tests/ -- test framework and test scripts.
3131

3232
Additional components:
@@ -74,7 +74,7 @@ Then when you need the toolchain you can type ``get_esp32`` on the command line
7474
You also need the ESP IDF along side this repository in order to build the ESP32 port.
7575
To get it:
7676

77-
$ git clone --recursive -b idf_v3.2 https://github.com/pycom/pycom-esp-idf.git
77+
$ git clone --recursive -b idf_v3.3.1 https://github.com/pycom/pycom-esp-idf.git
7878

7979
After cloning, if you did not specify the --recursive option, make sure to checkout all the submodules:
8080

esp32/Makefile

+6-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ ifeq ($(wildcard boards/$(BOARD)/.),)
1414
$(error Invalid BOARD specified)
1515
endif
1616

17-
IDF_VERSION=3.2
17+
IDF_VERSION=3.3.1
1818

1919
TARGET ?= boot_app
2020

@@ -103,17 +103,10 @@ LIBS = -L$(ESP_IDF_COMP_PATH)/esp32/lib -L$(ESP_IDF_COMP_PATH)/esp32/ld -L$(ESP_
103103
$(ESP_IDF_COMP_PATH)/newlib/lib/libc-psram-workaround.a \
104104
-lfreertos -ljson -ljsmn -llwip -lnewlib -lvfs -lopenssl -lmbedtls -lwpa_supplicant \
105105
-lxtensa-debug-module -lbt -lsdmmc -lsoc -lheap -lbootloader_support -lmicro-ecc \
106-
-u ld_include_panic_highint_hdl -lsmartconfig_ack -lmesh -lesp_ringbuf -lcoap
107-
ifeq ($(BOARD), $(filter $(BOARD), FIPY))
108-
LIBS += sigfox/modsigfox_fipy.a
109-
endif
110-
111-
ifeq ($(BOARD), $(filter $(BOARD), LOPY4))
112-
LIBS += sigfox/modsigfox_lopy4.a
113-
endif
114-
115-
ifeq ($(BOARD), $(filter $(BOARD), SIPY))
116-
LIBS += sigfox/modsigfox_sipy.a
106+
-u ld_include_panic_highint_hdl -lsmartconfig_ack -lmesh -lesp_ringbuf -lcoap -lmdns -lefuse -lespcoredump -lapp_update
107+
ifeq ($(BOARD), $(filter $(BOARD), SIPY LOPY4 FIPY))
108+
LIBS += sigfox/modsigfox_$(BOARD).a
109+
$(BUILD)/application.elf: sigfox/modsigfox_$(BOARD).a
117110
endif
118111

119112
ifeq ($(OPENTHREAD), on)
@@ -134,7 +127,7 @@ endif #ifeq ($(LTE_LOG_BUFF),1)
134127

135128
B_LIBS = -Lbootloader/lib -Lbootloader -L$(BUILD)/bootloader -L$(ESP_IDF_COMP_PATH)/esp32/ld \
136129
-L$(ESP_IDF_COMP_PATH)/esp32/lib -llog -lcore -lbootloader_support \
137-
-lspi_flash -lsoc -lmicro-ecc -lgcc -lstdc++ -lgcov
130+
-lspi_flash -lsoc -lmicro-ecc -lgcc -lstdc++ -lgcov -lefuse
138131

139132
# objcopy paramters, to transform a binary file into an object file
140133
OBJCOPY_EMBED_ARGS = --input-target binary --output-target elf32-xtensa-le --binary-architecture xtensa --rename-section .data=.rodata.embedded

esp32/PyJTAG/Readme.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Short readme for how to use the PyJTAG
2+
3+
Create the firmware with `BTYPE=debug` flag.
4+
Do not use the default pins assigned to UART, SPI, CAN because they are used by the JTAG. Pins not to be used: P4, P9, P10, P23
5+
6+
Detailed information are here: https://pycomiot.atlassian.net/wiki/spaces/FIR/pages/966295564/Usage+of+PyJTAG
7+
8+
Setup the PyJTAG board's switches:
9+
* ESP32 JTAG: all turned ON
10+
* ESP32 B.LOADER: all turned ON except SAFE_BOOT_SW which is OFF
11+
* TO LTE UART 1/2: does not matter
12+
* CURRENT SHUNTS: connected
13+
14+
Place the Pycom board with the reset button towards the Current Shunts.
15+
16+
Generally follow these rules to setup JTAG debugging on your OS: https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html
17+
18+
(Download link of OpenOCD for ESP32 from Espressif: https://github.com/espressif/openocd-esp32/releases)
19+
20+
Connect the PyJTAG via usb. You see four new USB devices:
21+
```
22+
$ lsusb -d 0403:
23+
Bus 001 Device 010: ID 0403:6011 Future Technology Devices International, Ltd FT4232H Quad HS USB-UART/FIFO IC
24+
$ ls /dev/ttyUSB?
25+
/dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 /dev/ttyUSB3
26+
```
27+
28+
Go to `esp32` folder in Firmware-Development repository and run:
29+
```
30+
PATH_TO_OPENOCD/bin/openocd -s PATH_TO_OPENOCD/share/openocd/scripts -s PyJTAG -f PyJTAG/interface/ftdi/esp32-pycom.cfg -f PyJTAG/board/esp32-pycom.cfg
31+
```
32+
33+
Output should be like:
34+
```
35+
Open On-Chip Debugger v0.10.0-esp32-20191114 (2019-11-14-14:15)
36+
Licensed under GNU GPL v2
37+
For bug reports, read
38+
http://openocd.org/doc/doxygen/bugs.html
39+
none separate
40+
adapter speed: 20000 kHz
41+
Info : Configured 2 cores
42+
Info : Listening on port 6666 for tcl connections
43+
Info : Listening on port 4444 for telnet connections
44+
Error: type 'esp32' is missing virt2phys
45+
Info : ftdi: if you experience problems at higher adapter clocks, try the command "ftdi_tdo_sample_edge falling"
46+
Info : clock speed 20000 kHz
47+
Info : JTAG tap: esp32.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
48+
Info : JTAG tap: esp32.cpu1 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
49+
Info : Listening on port 3333 for gdb connections
50+
```
51+
52+
When OpenOCD is running, start GDB from `esp32` folder. Assuming you have a FIPY:
53+
```
54+
xtensa-esp32-elf-gdb -x PyJTAG/gdbinit build/FIPY/debug/application.elf
55+
```
56+
57+
In `PyJTAG/gdbinit` a breakpoint is configured at `TASK_Micropython`, so execution should stop there first.

esp32/PyJTAG/board/esp32-pycom.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
set ESP32_FLASH_VOLTAGE 3.3
2+
source [find target/esp32-pycom.cfg]

esp32/PyJTAG/gdbinit

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
target remote :3333
2+
mon reset halt
3+
flushregs
4+
thb TASK_Micropython
5+
c
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#
2+
# Driver for the FT4232HL JTAG chip on the Pycom's PyJTAG board
3+
#
4+
5+
6+
interface ftdi
7+
ftdi_vid_pid 0x0403 0x6011
8+
9+
# interface 1 is the uart
10+
ftdi_channel 0
11+
12+
# TCK, TDI, TDO, TMS: ADBUS0-3
13+
# LEDs: ACBUS4-7
14+
15+
ftdi_layout_init 0x0008 0xf00b
16+
#ftdi_layout_signal LED -data 0x1000
17+
#ftdi_layout_signal LED2 -data 0x2000
18+
#ftdi_layout_signal LED3 -data 0x4000
19+
#ftdi_layout_signal LED4 -data 0x8000
20+
21+
# ESP32 series chips do not have a TRST input, and the SRST line is connected
22+
# to the EN pin.
23+
# The target code doesn't handle SRST reset properly yet, so this is
24+
# commented out:
25+
# ftdi_layout_signal nSRST -oe 0x0020
26+
27+
reset_config none
28+
29+
# The speed of the JTAG interface, in KHz. If you get DSR/DIR errors (and they
30+
# do not relate to OpenOCD trying to read from a memory range without physical
31+
# memory being present there), you can try lowering this.
32+
#
33+
# On DevKit-J, this can go as high as 20MHz if CPU frequency is 80MHz, or 26MHz
34+
# if CPU frequency is 160MHz or 240MHz.
35+
adapter_khz 20000

esp32/PyJTAG/target/esp32-pycom.cfg

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# The ESP32 only supports JTAG.
2+
transport select jtag
3+
4+
# Source the ESP common configuration file
5+
source [find target/esp_common.cfg]
6+
7+
if { [info exists CHIPNAME] } {
8+
set _CHIPNAME $CHIPNAME
9+
} else {
10+
set _CHIPNAME esp32
11+
}
12+
13+
if { [info exists CPUTAPID] } {
14+
set _CPUTAPID $CPUTAPID
15+
} else {
16+
set _CPUTAPID 0x120034e5
17+
}
18+
19+
if { [info exists ESP32_ONLYCPU] } {
20+
set _ONLYCPU $ESP32_ONLYCPU
21+
} else {
22+
set _ONLYCPU 3
23+
}
24+
25+
if { [info exists ESP32_FLASH_VOLTAGE] } {
26+
set _FLASH_VOLTAGE $ESP32_FLASH_VOLTAGE
27+
} else {
28+
set _FLASH_VOLTAGE 3.3
29+
}
30+
31+
set _TARGETNAME $_CHIPNAME
32+
set _CPU0NAME cpu0
33+
set _CPU1NAME cpu1
34+
set _TAPNAME $_CHIPNAME.$_CPU0NAME
35+
36+
jtag newtap $_CHIPNAME $_CPU0NAME -irlen 5 -expected-id $_CPUTAPID
37+
if { $_ONLYCPU != 1 } {
38+
jtag newtap $_CHIPNAME $_CPU1NAME -irlen 5 -expected-id $_CPUTAPID
39+
} else {
40+
jtag newtap $_CHIPNAME $_CPU1NAME -irlen 5 -disable -expected-id $_CPUTAPID
41+
}
42+
43+
if { $_RTOS == "none" } {
44+
target create $_TARGETNAME esp32 -endian little -chain-position $_TAPNAME
45+
} else {
46+
target create $_TARGETNAME esp32 -endian little -chain-position $_TAPNAME -rtos $_RTOS
47+
}
48+
49+
configure_esp_workarea $_TARGETNAME 0x40090000 0x3400 0x3FFC0000 0x6000
50+
configure_esp_flash_bank $_TARGETNAME $_TARGETNAME $_FLASH_SIZE
51+
52+
esp32 flashbootstrap $_FLASH_VOLTAGE
53+
esp32 maskisr on
54+
if { $_SEMIHOST_BASEDIR != "" } {
55+
esp32 semihost_basedir $_SEMIHOST_BASEDIR
56+
}
57+
if { $_FLASH_SIZE == 0 } {
58+
gdb_breakpoint_override hard
59+
}
60+
61+
# special function to program ESP32, it differs from the original 'program' that
62+
# it verifies written image by reading flash directly, instead of reading memory mapped flash regions
63+
proc program_esp32 {filename args} {
64+
program_esp $filename $args
65+
}
66+
67+
add_help_text program_esp32 "write an image to flash, address is only required for binary images. verify, reset, exit are optional"
68+
add_usage_text program_esp32 "<filename> \[address\] \[verify\] \[reset\] \[exit\]"
69+

esp32/application.mk

+20-5
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ APP_INC += -I$(ESP_IDF_COMP_PATH)/coap/libcoap/include/coap
7777
APP_INC += -I$(ESP_IDF_COMP_PATH)/coap/libcoap/examples
7878
APP_INC += -I$(ESP_IDF_COMP_PATH)/coap/port/include
7979
APP_INC += -I$(ESP_IDF_COMP_PATH)/coap/port/include/coap
80+
APP_INC += -I$(ESP_IDF_COMP_PATH)/mdns/include
8081
APP_INC += -I../lib/mp-readline
8182
APP_INC += -I../lib/netutils
8283
APP_INC += -I../lib/oofatfs
@@ -160,6 +161,7 @@ APP_MODS_SRC_C = $(addprefix mods/,\
160161
lwipsocket.c \
161162
machtouch.c \
162163
modcoap.c \
164+
modmdns.c \
163165
)
164166

165167
APP_MODS_LORA_SRC_C = $(addprefix mods/,\
@@ -183,6 +185,7 @@ APP_UTIL_SRC_C = $(addprefix util/,\
183185
mpsleep.c \
184186
timeutils.c \
185187
esp32chipinfo.c \
188+
pycom_general_util.c \
186189
)
187190

188191
APP_FATFS_SRC_C = $(addprefix fatfs/src/,\
@@ -245,7 +248,7 @@ APP_SX1276_SRC_C = $(addprefix drivers/sx127x/,\
245248
sx1276/sx1276.c \
246249
)
247250

248-
APP_SIGFOX_SRC_SIPY_C = $(addprefix sigfox/,\
251+
APP_SIGFOX_SRC_SIPY_C = $(addprefix sigfox/src/,\
249252
manufacturer_api.c \
250253
radio.c \
251254
ti_aes_128.c \
@@ -254,7 +257,7 @@ APP_SIGFOX_SRC_SIPY_C = $(addprefix sigfox/,\
254257
modsigfox.c \
255258
)
256259

257-
APP_SIGFOX_SRC_FIPY_LOPY4_C = $(addprefix sigfox/,\
260+
APP_SIGFOX_SRC_FIPY_LOPY4_C = $(addprefix sigfox/src/,\
258261
manufacturer_api.c \
259262
radio_sx127x.c \
260263
ti_aes_128.c \
@@ -267,7 +270,7 @@ APP_SIGFOX_MOD_SRC_C = $(addprefix mods/,\
267270
modsigfox_api.c \
268271
)
269272

270-
APP_SIGFOX_TARGET_SRC_C = $(addprefix sigfox/targets/,\
273+
APP_SIGFOX_TARGET_SRC_C = $(addprefix sigfox/src/targets/,\
271274
cc112x_spi.c \
272275
hal_int.c \
273276
hal_spi_rf_trxeb.c \
@@ -366,7 +369,7 @@ SRC_QSTR_AUTO_DEPS +=
366369
BOOT_LDFLAGS = $(LDFLAGS) -T esp32.bootloader.ld -T esp32.rom.ld -T esp32.peripherals.ld -T esp32.bootloader.rom.ld -T esp32.rom.spiram_incompatible_fns.ld
367370

368371
# add the application linker script(s)
369-
APP_LDFLAGS += $(LDFLAGS) -T esp32_out.ld -T esp32.common.ld -T esp32.rom.ld -T esp32.peripherals.ld -T wifi_iram.ld
372+
APP_LDFLAGS += $(LDFLAGS) -T esp32_out.ld -T esp32.project.ld -T esp32.rom.ld -T esp32.peripherals.ld
370373

371374
# add the application specific CFLAGS
372375
CFLAGS += $(APP_INC) -DMICROPY_NLR_SETJMP=1 -DMBEDTLS_CONFIG_FILE='"mbedtls/esp_config.h"' -DHAVE_CONFIG_H -DESP_PLATFORM -DFFCONF_H=\"lib/oofatfs/ffconf.h\" -DWITH_POSIX
@@ -512,6 +515,9 @@ endif
512515
ifeq ($(TARGET), boot_app)
513516
all: $(BOOT_BIN) $(APP_BIN)
514517
endif
518+
ifeq ($(TARGET), sigfox)
519+
include sigfox.mk
520+
endif
515521
.PHONY: all CHECK_DEP
516522

517523
$(info $(VARIANT) Variant)
@@ -525,6 +531,14 @@ CFLAGS += -DCONFIG_FLASH_ENCRYPTION_ENABLED=1
525531
# it can also be added permanently in sdkconfig.h
526532
CFLAGS += -DCONFIG_SECURE_BOOT_ENABLED=1
527533

534+
define resolvepath
535+
$(abspath $(foreach dir,$(1),$(if $(filter /%,$(dir)),$(dir),$(subst //,/,$(2)/$(dir)))))
536+
endef
537+
538+
define dequote
539+
$(subst ",,$(1))
540+
endef
541+
528542
# find the configured private key file
529543
ORIG_SECURE_KEY := $(call resolvepath,$(call dequote,$(SECURE_KEY)),$(PROJECT_PATH))
530544

@@ -639,7 +653,7 @@ $(BUILD)/application.a: $(OBJ)
639653
$(ECHO) "AR $@"
640654
$(Q) rm -f $@
641655
$(Q) $(AR) cru $@ $^
642-
$(BUILD)/application.elf: $(BUILD)/application.a $(BUILD)/esp32_out.ld $(SECURE_BOOT_VERIFICATION_KEY)
656+
$(BUILD)/application.elf: $(BUILD)/application.a $(BUILD)/esp32_out.ld esp32.project.ld $(SECURE_BOOT_VERIFICATION_KEY)
643657
ifeq ($(SECURE), on)
644658
# unpack libbootloader_support.a, and archive again using the right key for verifying signatures
645659
$(ECHO) "Inserting verification key $(SECURE_BOOT_VERIFICATION_KEY) in $@"
@@ -807,6 +821,7 @@ $(OBJ): | $(GEN_PINS_HDR)
807821
CHECK_DEP:
808822
$(Q) bash tools/idfVerCheck.sh $(IDF_PATH) "$(IDF_VERSION)"
809823
$(Q) bash tools/mpy-build-check.sh $(BOARD) $(BTYPE) $(VARIANT)
824+
$(Q) $(PYTHON) check_secure_boot.py --SECURE $(SECURE)
810825
ifeq ($(COPY_IDF_LIB), 1)
811826
$(ECHO) "COPY IDF LIBRARIES"
812827
$(Q) $(PYTHON) get_idf_libs.py --idflibs $(IDF_PATH)/examples/wifi/scan/build
12.9 KB
Binary file not shown.

esp32/bootloader/lib/libefuse.a

113 KB
Binary file not shown.

esp32/bootloader/lib/liblog.a

128 Bytes
Binary file not shown.

esp32/bootloader/lib/libmicro-ecc.a

72 Bytes
Binary file not shown.

esp32/bootloader/lib/libsoc.a

2.7 KB
Binary file not shown.

esp32/bootloader/lib/libspi_flash.a

88 Bytes
Binary file not shown.

esp32/check_secure_boot.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import argparse
2+
3+
4+
def main():
5+
cmd_parser = argparse.ArgumentParser()
6+
cmd_parser.add_argument('--SECURE', default=None)
7+
cmd_args = cmd_parser.parse_args()
8+
9+
secure = cmd_args.SECURE
10+
with open("sdkconfig.h") as sdkconfig:
11+
if any("CONFIG_SECURE_BOOT_ENABLED" in l for l in sdkconfig.readlines()):
12+
if(secure != "on"):
13+
print("If CONFIG_SECURE_BOOT_ENABLED is defined in sdkconfig.h, the SECURE=on must be used when building the Firmware!")
14+
# Non zero exit code means error
15+
exit(1)
16+
17+
if __name__ == "__main__":
18+
main()

0 commit comments

Comments
 (0)