mirror of
https://github.com/esphome/esphome.git
synced 2026-05-20 17:52:00 +08:00
[libretiny] Regenerate boards, enable Cortex-M4 atomics, and consolidate platform code
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
- [ ] RP2040
|
||||
- [ ] BK72xx
|
||||
- [ ] RTL87xx
|
||||
- [ ] LN882x
|
||||
- [ ] nRF52840
|
||||
|
||||
## Example entry for `config.yaml`:
|
||||
|
||||
@@ -27,6 +27,7 @@ COMPONENT_DATA = LibreTinyComponent(
|
||||
board_pins=BK72XX_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
supports_atomics=False,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+647
-647
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ from .const import (
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_COMPONENT,
|
||||
FAMILY_FRIENDLY,
|
||||
FAMILY_RTL8710B,
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENT,
|
||||
KEY_COMPONENT_DATA,
|
||||
@@ -278,11 +279,23 @@ async def component_to_code(config):
|
||||
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])
|
||||
# LibreTiny uses MULTI_NO_ATOMICS because platforms like BK7231N (ARM968E-S) lack
|
||||
# exclusive load/store (no LDREX/STREX). std::atomic RMW operations require libatomic,
|
||||
# which is not linked to save flash (4-8KB). Even if linked, libatomic would use locks
|
||||
# (ATOMIC_INT_LOCK_FREE=1), so explicit FreeRTOS mutexes are simpler and equivalent.
|
||||
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
|
||||
# Set threading model based on chip architecture
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
if component.supports_atomics:
|
||||
# RTL87xx (Cortex-M4) and LN882x (Cortex-M4F) have LDREX/STREX
|
||||
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
||||
else:
|
||||
# BK72xx uses ARM968E-S (ARMv5TE) which lacks LDREX/STREX.
|
||||
# std::atomic RMW operations would require libatomic (not linked to save
|
||||
# 4-8KB flash). Even if linked, it would use locks, so explicit FreeRTOS
|
||||
# mutexes are simpler and equivalent.
|
||||
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
|
||||
|
||||
# RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake
|
||||
# required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220)
|
||||
# RTL8720C (ambz2) requires FreeRTOS 10.x so this only applies to RTL8710B
|
||||
if config[CONF_FAMILY] == FAMILY_RTL8710B:
|
||||
cg.add_platformio_option("custom_versions.freertos", "8.2.3")
|
||||
|
||||
# force using arduino framework
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
|
||||
@@ -11,6 +11,7 @@ class LibreTinyComponent:
|
||||
board_pins: dict[str, dict[str, int]]
|
||||
pin_validation: Callable[[int], int]
|
||||
usage_validation: Callable[[dict], dict]
|
||||
supports_atomics: bool = False # True for Cortex-M4(F) with LDREX/STREX
|
||||
|
||||
|
||||
CONF_LIBRETINY = "libretiny"
|
||||
|
||||
@@ -33,6 +33,7 @@ from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
COMPONENT_DATA = LibreTinyComponent(
|
||||
name=COMPONENT_{COMPONENT},
|
||||
@@ -40,6 +41,7 @@ COMPONENT_DATA = LibreTinyComponent(
|
||||
board_pins={COMPONENT}_BOARD_PINS,
|
||||
pin_validation={PIN_VALIDATION},
|
||||
usage_validation={USAGE_VALIDATION},
|
||||
supports_atomics={SUPPORTS_ATOMICS},
|
||||
)
|
||||
|
||||
|
||||
@@ -97,6 +99,14 @@ COMPONENT_MAP = {
|
||||
"ln882x": "lightning-ln882x",
|
||||
}
|
||||
|
||||
# Components with Cortex-M4(F) have LDREX/STREX for native atomic support.
|
||||
# BK72xx uses ARM968E-S (ARMv5TE) which lacks these instructions.
|
||||
COMPONENT_SUPPORTS_ATOMICS = {
|
||||
"rtl87xx": True, # Cortex-M4
|
||||
"ln882x": True, # Cortex-M4F
|
||||
"bk72xx": False, # ARM968E-S
|
||||
}
|
||||
|
||||
|
||||
def subst(code: str, key: str, value: str) -> str:
|
||||
return code.replace(f"{{{key}}}", value)
|
||||
@@ -147,6 +157,7 @@ def write_component_code(
|
||||
PIN_SCHEMA=PIN_SCHEMA_BASE,
|
||||
PIN_VALIDATION="None",
|
||||
USAGE_VALIDATION="None",
|
||||
SUPPORTS_ATOMICS=str(COMPONENT_SUPPORTS_ATOMICS.get(component, False)),
|
||||
)
|
||||
|
||||
# parse gpio.py file to find custom validators
|
||||
|
||||
@@ -17,7 +17,7 @@ from esphome.core import CORE
|
||||
|
||||
from .boards import LN882X_BOARD_PINS, LN882X_BOARDS
|
||||
|
||||
CODEOWNERS = ["@lamauny"]
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
@@ -27,6 +27,7 @@ COMPONENT_DATA = LibreTinyComponent(
|
||||
board_pins=LN882X_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
supports_atomics=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+194
-123
@@ -4,6 +4,14 @@
|
||||
from esphome.components.libretiny.const import FAMILY_LN882H
|
||||
|
||||
LN882X_BOARDS = {
|
||||
"generic-ln882hki": {
|
||||
"name": "Generic - LN882HKI",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
"wb02a": {
|
||||
"name": "WB02A Wi-Fi/BLE Module",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
"wl2s": {
|
||||
"name": "WL2S Wi-Fi/BLE Module",
|
||||
"family": FAMILY_LN882H,
|
||||
@@ -12,13 +20,195 @@ LN882X_BOARDS = {
|
||||
"name": "LN-02 Wi-Fi/BLE Module",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
"generic-ln882hki": {
|
||||
"name": "Generic - LN882HKI",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
}
|
||||
|
||||
LN882X_BOARD_PINS = {
|
||||
"generic-ln882hki": {
|
||||
"WIRE0_SCL_0": 0,
|
||||
"WIRE0_SCL_1": 1,
|
||||
"WIRE0_SCL_2": 2,
|
||||
"WIRE0_SCL_3": 3,
|
||||
"WIRE0_SCL_4": 4,
|
||||
"WIRE0_SCL_5": 5,
|
||||
"WIRE0_SCL_6": 6,
|
||||
"WIRE0_SCL_7": 7,
|
||||
"WIRE0_SCL_8": 8,
|
||||
"WIRE0_SCL_9": 9,
|
||||
"WIRE0_SCL_10": 10,
|
||||
"WIRE0_SCL_11": 11,
|
||||
"WIRE0_SCL_12": 12,
|
||||
"WIRE0_SCL_13": 19,
|
||||
"WIRE0_SCL_14": 20,
|
||||
"WIRE0_SCL_15": 21,
|
||||
"WIRE0_SCL_16": 22,
|
||||
"WIRE0_SCL_17": 23,
|
||||
"WIRE0_SCL_18": 24,
|
||||
"WIRE0_SCL_19": 25,
|
||||
"WIRE0_SDA_0": 0,
|
||||
"WIRE0_SDA_1": 1,
|
||||
"WIRE0_SDA_2": 2,
|
||||
"WIRE0_SDA_3": 3,
|
||||
"WIRE0_SDA_4": 4,
|
||||
"WIRE0_SDA_5": 5,
|
||||
"WIRE0_SDA_6": 6,
|
||||
"WIRE0_SDA_7": 7,
|
||||
"WIRE0_SDA_8": 8,
|
||||
"WIRE0_SDA_9": 9,
|
||||
"WIRE0_SDA_10": 10,
|
||||
"WIRE0_SDA_11": 11,
|
||||
"WIRE0_SDA_12": 12,
|
||||
"WIRE0_SDA_13": 19,
|
||||
"WIRE0_SDA_14": 20,
|
||||
"WIRE0_SDA_15": 21,
|
||||
"WIRE0_SDA_16": 22,
|
||||
"WIRE0_SDA_17": 23,
|
||||
"WIRE0_SDA_18": 24,
|
||||
"WIRE0_SDA_19": 25,
|
||||
"SERIAL0_RX": 3,
|
||||
"SERIAL0_TX": 2,
|
||||
"SERIAL1_RX": 24,
|
||||
"SERIAL1_TX": 25,
|
||||
"ADC2": 0,
|
||||
"ADC3": 1,
|
||||
"ADC4": 4,
|
||||
"ADC5": 19,
|
||||
"ADC6": 20,
|
||||
"ADC7": 21,
|
||||
"PA00": 0,
|
||||
"PA0": 0,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA05": 5,
|
||||
"PA5": 5,
|
||||
"PA06": 6,
|
||||
"PA6": 6,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA08": 8,
|
||||
"PA8": 8,
|
||||
"PA09": 9,
|
||||
"PA9": 9,
|
||||
"PA10": 10,
|
||||
"PA11": 11,
|
||||
"PA12": 12,
|
||||
"PB03": 19,
|
||||
"PB3": 19,
|
||||
"PB04": 20,
|
||||
"PB4": 20,
|
||||
"PB05": 21,
|
||||
"PB5": 21,
|
||||
"PB06": 22,
|
||||
"PB6": 22,
|
||||
"PB07": 23,
|
||||
"PB7": 23,
|
||||
"PB08": 24,
|
||||
"PB8": 24,
|
||||
"PB09": 25,
|
||||
"PB9": 25,
|
||||
"RX0": 3,
|
||||
"RX1": 24,
|
||||
"TX0": 2,
|
||||
"TX1": 25,
|
||||
"D0": 0,
|
||||
"D1": 1,
|
||||
"D2": 2,
|
||||
"D3": 3,
|
||||
"D4": 4,
|
||||
"D5": 5,
|
||||
"D6": 6,
|
||||
"D7": 7,
|
||||
"D8": 8,
|
||||
"D9": 9,
|
||||
"D10": 10,
|
||||
"D11": 11,
|
||||
"D12": 12,
|
||||
"D13": 19,
|
||||
"D14": 20,
|
||||
"D15": 21,
|
||||
"D16": 22,
|
||||
"D17": 23,
|
||||
"D18": 24,
|
||||
"D19": 25,
|
||||
"A2": 0,
|
||||
"A3": 1,
|
||||
"A4": 4,
|
||||
"A5": 19,
|
||||
"A6": 20,
|
||||
"A7": 21,
|
||||
},
|
||||
"wb02a": {
|
||||
"WIRE0_SCL_0": 7,
|
||||
"WIRE0_SCL_1": 5,
|
||||
"WIRE0_SCL_2": 3,
|
||||
"WIRE0_SCL_3": 10,
|
||||
"WIRE0_SCL_4": 2,
|
||||
"WIRE0_SCL_5": 1,
|
||||
"WIRE0_SCL_6": 4,
|
||||
"WIRE0_SCL_7": 5,
|
||||
"WIRE0_SCL_8": 9,
|
||||
"WIRE0_SCL_9": 24,
|
||||
"WIRE0_SCL_10": 25,
|
||||
"WIRE0_SDA_0": 7,
|
||||
"WIRE0_SDA_1": 5,
|
||||
"WIRE0_SDA_2": 3,
|
||||
"WIRE0_SDA_3": 10,
|
||||
"WIRE0_SDA_4": 2,
|
||||
"WIRE0_SDA_5": 1,
|
||||
"WIRE0_SDA_6": 4,
|
||||
"WIRE0_SDA_7": 5,
|
||||
"WIRE0_SDA_8": 9,
|
||||
"WIRE0_SDA_9": 24,
|
||||
"WIRE0_SDA_10": 25,
|
||||
"SERIAL0_RX": 3,
|
||||
"SERIAL0_TX": 2,
|
||||
"SERIAL1_RX": 24,
|
||||
"SERIAL1_TX": 25,
|
||||
"ADC3": 1,
|
||||
"ADC4": 4,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA05": 5,
|
||||
"PA5": 5,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA09": 9,
|
||||
"PA9": 9,
|
||||
"PA10": 10,
|
||||
"PB08": 24,
|
||||
"PB8": 24,
|
||||
"PB09": 25,
|
||||
"PB9": 25,
|
||||
"RX0": 3,
|
||||
"RX1": 24,
|
||||
"SCL0": 25,
|
||||
"SDA0": 25,
|
||||
"TX0": 2,
|
||||
"TX1": 25,
|
||||
"D0": 7,
|
||||
"D1": 5,
|
||||
"D2": 3,
|
||||
"D3": 10,
|
||||
"D4": 2,
|
||||
"D5": 1,
|
||||
"D6": 4,
|
||||
"D7": 9,
|
||||
"D8": 24,
|
||||
"D9": 25,
|
||||
"A0": 1,
|
||||
"A1": 4,
|
||||
},
|
||||
"wl2s": {
|
||||
"WIRE0_SCL_0": 7,
|
||||
"WIRE0_SCL_1": 12,
|
||||
@@ -161,125 +351,6 @@ LN882X_BOARD_PINS = {
|
||||
"A1": 1,
|
||||
"A2": 0,
|
||||
},
|
||||
"generic-ln882hki": {
|
||||
"WIRE0_SCL_0": 0,
|
||||
"WIRE0_SCL_1": 1,
|
||||
"WIRE0_SCL_2": 2,
|
||||
"WIRE0_SCL_3": 3,
|
||||
"WIRE0_SCL_4": 4,
|
||||
"WIRE0_SCL_5": 5,
|
||||
"WIRE0_SCL_6": 6,
|
||||
"WIRE0_SCL_7": 7,
|
||||
"WIRE0_SCL_8": 8,
|
||||
"WIRE0_SCL_9": 9,
|
||||
"WIRE0_SCL_10": 10,
|
||||
"WIRE0_SCL_11": 11,
|
||||
"WIRE0_SCL_12": 12,
|
||||
"WIRE0_SCL_13": 19,
|
||||
"WIRE0_SCL_14": 20,
|
||||
"WIRE0_SCL_15": 21,
|
||||
"WIRE0_SCL_16": 22,
|
||||
"WIRE0_SCL_17": 23,
|
||||
"WIRE0_SCL_18": 24,
|
||||
"WIRE0_SCL_19": 25,
|
||||
"WIRE0_SDA_0": 0,
|
||||
"WIRE0_SDA_1": 1,
|
||||
"WIRE0_SDA_2": 2,
|
||||
"WIRE0_SDA_3": 3,
|
||||
"WIRE0_SDA_4": 4,
|
||||
"WIRE0_SDA_5": 5,
|
||||
"WIRE0_SDA_6": 6,
|
||||
"WIRE0_SDA_7": 7,
|
||||
"WIRE0_SDA_8": 8,
|
||||
"WIRE0_SDA_9": 9,
|
||||
"WIRE0_SDA_10": 10,
|
||||
"WIRE0_SDA_11": 11,
|
||||
"WIRE0_SDA_12": 12,
|
||||
"WIRE0_SDA_13": 19,
|
||||
"WIRE0_SDA_14": 20,
|
||||
"WIRE0_SDA_15": 21,
|
||||
"WIRE0_SDA_16": 22,
|
||||
"WIRE0_SDA_17": 23,
|
||||
"WIRE0_SDA_18": 24,
|
||||
"WIRE0_SDA_19": 25,
|
||||
"SERIAL0_RX": 3,
|
||||
"SERIAL0_TX": 2,
|
||||
"SERIAL1_RX": 24,
|
||||
"SERIAL1_TX": 25,
|
||||
"ADC2": 0,
|
||||
"ADC3": 1,
|
||||
"ADC4": 4,
|
||||
"ADC5": 19,
|
||||
"ADC6": 20,
|
||||
"ADC7": 21,
|
||||
"PA00": 0,
|
||||
"PA0": 0,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA05": 5,
|
||||
"PA5": 5,
|
||||
"PA06": 6,
|
||||
"PA6": 6,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA08": 8,
|
||||
"PA8": 8,
|
||||
"PA09": 9,
|
||||
"PA9": 9,
|
||||
"PA10": 10,
|
||||
"PA11": 11,
|
||||
"PA12": 12,
|
||||
"PB03": 19,
|
||||
"PB3": 19,
|
||||
"PB04": 20,
|
||||
"PB4": 20,
|
||||
"PB05": 21,
|
||||
"PB5": 21,
|
||||
"PB06": 22,
|
||||
"PB6": 22,
|
||||
"PB07": 23,
|
||||
"PB7": 23,
|
||||
"PB08": 24,
|
||||
"PB8": 24,
|
||||
"PB09": 25,
|
||||
"PB9": 25,
|
||||
"RX0": 3,
|
||||
"RX1": 24,
|
||||
"TX0": 2,
|
||||
"TX1": 25,
|
||||
"D0": 0,
|
||||
"D1": 1,
|
||||
"D2": 2,
|
||||
"D3": 3,
|
||||
"D4": 4,
|
||||
"D5": 5,
|
||||
"D6": 6,
|
||||
"D7": 7,
|
||||
"D8": 8,
|
||||
"D9": 9,
|
||||
"D10": 10,
|
||||
"D11": 11,
|
||||
"D12": 12,
|
||||
"D13": 19,
|
||||
"D14": 20,
|
||||
"D15": 21,
|
||||
"D16": 22,
|
||||
"D17": 23,
|
||||
"D18": 24,
|
||||
"D19": 25,
|
||||
"A2": 0,
|
||||
"A3": 1,
|
||||
"A4": 4,
|
||||
"A5": 19,
|
||||
"A6": 20,
|
||||
"A7": 21,
|
||||
},
|
||||
}
|
||||
|
||||
BOARDS = LN882X_BOARDS
|
||||
|
||||
@@ -6,13 +6,10 @@
|
||||
# in schema.py file in this directory.
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import libretiny
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_RTL87XX,
|
||||
FAMILY_RTL8710B,
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_FAMILY,
|
||||
KEY_LIBRETINY,
|
||||
LibreTinyComponent,
|
||||
)
|
||||
@@ -24,13 +21,13 @@ CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
|
||||
COMPONENT_DATA = LibreTinyComponent(
|
||||
name=COMPONENT_RTL87XX,
|
||||
boards=RTL87XX_BOARDS,
|
||||
board_pins=RTL87XX_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
supports_atomics=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -48,11 +45,6 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+
|
||||
# https://github.com/esphome/esphome/issues/10220
|
||||
# Only for RTL8710B (ambz) - RTL8720C (ambz2) requires FreeRTOS 10.x
|
||||
if CORE.data[KEY_LIBRETINY][KEY_FAMILY] == FAMILY_RTL8710B:
|
||||
cg.add_platformio_option("custom_versions.freertos", "8.2.3")
|
||||
return await libretiny.component_to_code(config)
|
||||
|
||||
|
||||
|
||||
+1014
-942
File diff suppressed because it is too large
Load Diff
@@ -612,8 +612,9 @@ uint64_t Scheduler::millis_64_(uint32_t now) {
|
||||
// THREAD SAFETY NOTE:
|
||||
// This function has three implementations, based on the precompiler flags
|
||||
// - ESPHOME_THREAD_SINGLE - Runs on single-threaded platforms (ESP8266, RP2040, etc.)
|
||||
// - ESPHOME_THREAD_MULTI_NO_ATOMICS - Runs on multi-threaded platforms without atomics (LibreTiny)
|
||||
// - ESPHOME_THREAD_MULTI_ATOMICS - Runs on multi-threaded platforms with atomics (ESP32, HOST, etc.)
|
||||
// - ESPHOME_THREAD_MULTI_NO_ATOMICS - Runs on multi-threaded platforms without atomics (LibreTiny BK72xx)
|
||||
// - ESPHOME_THREAD_MULTI_ATOMICS - Runs on multi-threaded platforms with atomics (ESP32, HOST, LibreTiny
|
||||
// RTL87xx/LN882x, etc.)
|
||||
//
|
||||
// Make sure all changes are synchronized if you edit this function.
|
||||
//
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- lambda: |-
|
||||
int x = 100;
|
||||
x = clamp(x, 50, 90);
|
||||
assert(x == 90);
|
||||
x = clamp_at_least(x, 95);
|
||||
assert(x == 95);
|
||||
x = clamp_at_most(x, 40);
|
||||
assert(x == 40);
|
||||
@@ -0,0 +1,13 @@
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- lambda: |-
|
||||
int x = 100;
|
||||
x = clamp(x, 50, 90);
|
||||
assert(x == 90);
|
||||
x = clamp_at_least(x, 95);
|
||||
assert(x == 95);
|
||||
x = clamp_at_most(x, 40);
|
||||
assert(x == 40);
|
||||
Reference in New Issue
Block a user