mirror of
https://github.com/esphome/esphome.git
synced 2025-04-09 12:20:30 +01:00
add zephyr
This commit is contained in:
parent
c5b70bad84
commit
52cf8a1cf5
187
esphome/ble/ble_interface.py
Normal file
187
esphome/ble/ble_interface.py
Normal file
@ -0,0 +1,187 @@
|
||||
from bleak import BleakClient, BleakScanner
|
||||
from bleak.backends.characteristic import BleakGATTCharacteristic
|
||||
from ble_serial.bluetooth.constants import ble_chars
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class BLE_interface:
|
||||
def __init__(self, adapter: str, service: str):
|
||||
self._send_queue = asyncio.Queue()
|
||||
|
||||
self.scan_args = dict(adapter=adapter)
|
||||
if service:
|
||||
self.scan_args["service_uuids"] = [service]
|
||||
|
||||
async def connect(self, addr_str: str, addr_type: str, timeout: float):
|
||||
if addr_str:
|
||||
device = await BleakScanner.find_device_by_address(
|
||||
addr_str, timeout=timeout, **self.scan_args
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
"Picking first device with matching service, "
|
||||
"consider passing a specific device address, especially if there could be multiple devices"
|
||||
)
|
||||
device = await BleakScanner.find_device_by_filter(
|
||||
lambda dev, ad: True, timeout=timeout, **self.scan_args
|
||||
)
|
||||
|
||||
assert device, "No matching device found!"
|
||||
|
||||
# address_type used only in Windows .NET currently
|
||||
self.dev = BleakClient(
|
||||
device,
|
||||
address_type=addr_type,
|
||||
timeout=timeout,
|
||||
disconnected_callback=self.handle_disconnect,
|
||||
)
|
||||
|
||||
logging.info(f"Trying to connect with {device}")
|
||||
await self.dev.connect()
|
||||
logging.info(f"Device {self.dev.address} connected")
|
||||
|
||||
async def setup_chars(self, write_uuid: str, read_uuid: str, mode: str):
|
||||
self.read_enabled = "r" in mode
|
||||
self.write_enabled = "w" in mode
|
||||
|
||||
if self.write_enabled:
|
||||
self.write_char = self.find_char(
|
||||
write_uuid, ["write", "write-without-response"]
|
||||
)
|
||||
else:
|
||||
logging.info("Writing disabled, skipping write UUID detection")
|
||||
|
||||
if self.read_enabled:
|
||||
self.read_char = self.find_char(read_uuid, ["notify", "indicate"])
|
||||
await self.dev.start_notify(self.read_char, self.handle_notify)
|
||||
else:
|
||||
logging.info("Reading disabled, skipping read UUID detection")
|
||||
|
||||
def find_char(
|
||||
self, uuid: Optional[str], req_props: [str]
|
||||
) -> BleakGATTCharacteristic:
|
||||
name = req_props[0]
|
||||
|
||||
# Use user supplied UUID first, otherwise try included list
|
||||
if uuid:
|
||||
uuid_candidates = [uuid]
|
||||
else:
|
||||
uuid_candidates = ble_chars
|
||||
logging.debug(f"No {name} uuid specified, trying builtin list")
|
||||
|
||||
results = []
|
||||
for srv in self.dev.services:
|
||||
for c in srv.characteristics:
|
||||
if c.uuid in uuid_candidates:
|
||||
results.append(c)
|
||||
|
||||
if uuid:
|
||||
assert (
|
||||
len(results) > 0
|
||||
), f"No characteristic with specified {name} UUID {uuid} found!"
|
||||
else:
|
||||
assert (
|
||||
len(results) > 0
|
||||
), f"""No characteristic in builtin {name} list {uuid_candidates} found!
|
||||
Please specify one with {'-w/--write-uuid' if name == 'write' else '-r/--read-uuid'}, see also --help"""
|
||||
|
||||
res_str = "\n".join(f"\t{c} {c.properties}" for c in results)
|
||||
logging.debug(f"Characteristic candidates for {name}: \n{res_str}")
|
||||
|
||||
# Check if there is a intersection of permission flags
|
||||
results[:] = [c for c in results if set(c.properties) & set(req_props)]
|
||||
|
||||
assert len(results) > 0, f"No characteristic with {req_props} property found!"
|
||||
|
||||
assert (
|
||||
len(results) == 1
|
||||
), f"Multiple matching {name} characteristics found, please specify one"
|
||||
|
||||
# must be valid here
|
||||
found = results[0]
|
||||
logging.info(f"Found {name} characteristic {found.uuid} (H. {found.handle})")
|
||||
return found
|
||||
|
||||
def set_receiver(self, callback):
|
||||
self._cb = callback
|
||||
logging.info("Receiver set up")
|
||||
|
||||
async def send_loop(self):
|
||||
assert hasattr(self, "_cb"), "Callback must be set before receive loop!"
|
||||
while True:
|
||||
data = await self._send_queue.get()
|
||||
if data is None:
|
||||
break # Let future end on shutdown
|
||||
if not self.write_enabled:
|
||||
logging.warning(f"Ignoring unexpected write data: {data}")
|
||||
continue
|
||||
logging.debug(f"Sending {data}")
|
||||
await self.dev.write_gatt_char(self.write_char, data)
|
||||
|
||||
def stop_loop(self):
|
||||
logging.info("Stopping Bluetooth event loop")
|
||||
self._send_queue.put_nowait(None)
|
||||
|
||||
async def disconnect(self):
|
||||
if hasattr(self, "dev") and self.dev.is_connected:
|
||||
if hasattr(self, "read_char"):
|
||||
await self.dev.stop_notify(self.read_char)
|
||||
await self.dev.disconnect()
|
||||
logging.info("Bluetooth disconnected")
|
||||
|
||||
def queue_send(self, data: bytes):
|
||||
self._send_queue.put_nowait(data)
|
||||
|
||||
def handle_notify(self, handle: int, data: bytes):
|
||||
logging.debug(f"Received notify from {handle}: {data}")
|
||||
if not self.read_enabled:
|
||||
logging.warning(f"Read unexpected data, dropping: {data}")
|
||||
return
|
||||
self._cb(data)
|
||||
|
||||
def handle_disconnect(self, client: BleakClient):
|
||||
logging.warning(f"Device {client.address} disconnected")
|
||||
self.stop_loop()
|
||||
|
||||
|
||||
def receive_callback(value: bytes):
|
||||
print("Received:", value)
|
||||
|
||||
|
||||
async def hello_sender(ble: BLE_interface):
|
||||
while True:
|
||||
await asyncio.sleep(1 / 100)
|
||||
# print("Sending...")
|
||||
ble.queue_send(
|
||||
b"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
|
||||
)
|
||||
# ble.queue_send(b"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n")
|
||||
# ble.queue_send(b"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n")
|
||||
|
||||
|
||||
async def main():
|
||||
# None uses default/autodetection, insert values if needed
|
||||
ADAPTER = "hci0"
|
||||
SERVICE_UUID = "6ba1b218-15a8-461f-9fa8-5dcae273eafd"
|
||||
WRITE_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
|
||||
# READ_UUID = '2c55e69e-4993-11ed-b878-0242ac120002'
|
||||
READ_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
|
||||
DEVICE = "EF:45:95:65:46:FD"
|
||||
|
||||
ble = BLE_interface(ADAPTER, SERVICE_UUID)
|
||||
ble.set_receiver(receive_callback)
|
||||
|
||||
try:
|
||||
await ble.connect(DEVICE, "public", 10.0)
|
||||
await ble.setup_chars(WRITE_UUID, READ_UUID, "rw")
|
||||
|
||||
await asyncio.gather(ble.send_loop(), hello_sender(ble))
|
||||
finally:
|
||||
await ble.disconnect()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
asyncio.run(main())
|
53
esphome/components/ipsp/__init__.py
Normal file
53
esphome/components/ipsp/__init__.py
Normal file
@ -0,0 +1,53 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
)
|
||||
from esphome.components.nrf52 import add_zephyr_prj_conf_option
|
||||
|
||||
ipsp_ns = cg.esphome_ns.namespace("ipsp")
|
||||
Network = ipsp_ns.class_("Network", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Network),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_zephyr,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
add_zephyr_prj_conf_option("CONFIG_BT", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_BT_SMP", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_BT_PERIPHERAL", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_BT_CENTRAL", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_BT_L2CAP_DYNAMIC_CHANNEL", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_BT_DEVICE_NAME", "Test IPSP node") # TODO
|
||||
add_zephyr_prj_conf_option("CONFIG_NETWORKING", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_IPV6", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_IPV4", False)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_UDP", False)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_TCP", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_TEST_RANDOM_GENERATOR", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_L2_BT", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_INIT_STACKS", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_PKT_RX_COUNT", 10)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_PKT_TX_COUNT", 10)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_BUF_RX_COUNT", 20)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_BUF_TX_COUNT", 20)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT", 3)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT", 4)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_MAX_CONTEXTS", 6)
|
||||
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_AUTO_INIT", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_SETTINGS", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_MY_IPV6_ADDR", "2001:db8::1")
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_PEER_IPV6_ADDR", "2001:db8::2")
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_BT_NODE", True)
|
||||
if True:
|
||||
add_zephyr_prj_conf_option("CONFIG_LOG", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_LOG", True)
|
31
esphome/components/ipsp/network.cpp
Normal file
31
esphome/components/ipsp/network.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "network.h"
|
||||
#include <zephyr/net/net_if.h>
|
||||
|
||||
|
||||
/* Define my IP address where to expect messages */
|
||||
#define MY_IP6ADDR { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, \
|
||||
0, 0, 0, 0, 0, 0, 0, 0x1 } } }
|
||||
|
||||
static struct in6_addr in6addr_my = MY_IP6ADDR;
|
||||
|
||||
namespace esphome {
|
||||
namespace ipsp {
|
||||
|
||||
void Network::setup() {
|
||||
if (net_addr_pton(AF_INET6,
|
||||
CONFIG_NET_CONFIG_MY_IPV6_ADDR,
|
||||
&in6addr_my) < 0) {
|
||||
// LOG_ERR("Invalid IPv6 address %s",
|
||||
// CONFIG_NET_CONFIG_MY_IPV6_ADDR);
|
||||
}
|
||||
|
||||
do {
|
||||
struct net_if_addr *ifaddr;
|
||||
|
||||
ifaddr = net_if_ipv6_addr_add(net_if_get_default(),
|
||||
&in6addr_my, NET_ADDR_MANUAL, 0);
|
||||
} while (0);
|
||||
}
|
||||
|
||||
} // namespace shell
|
||||
} // namespace esphome
|
10
esphome/components/ipsp/network.h
Normal file
10
esphome/components/ipsp/network.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ipsp {
|
||||
class Network : public Component {
|
||||
void setup() override;
|
||||
};
|
||||
}
|
||||
}
|
@ -27,8 +27,10 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_NRF52
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Adafruit_TinyUSB.h> // for Serial
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
@ -64,12 +66,16 @@ void Logger::write_header_(int level, const char *tag, int line) {
|
||||
|
||||
const char *color = LOG_LEVEL_COLORS[level];
|
||||
const char *letter = LOG_LEVEL_LETTERS[level];
|
||||
#ifdef USE_ARDUINO
|
||||
void *current_task = xTaskGetCurrentTaskHandle();
|
||||
if (current_task == main_task) {
|
||||
#endif
|
||||
this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line);
|
||||
#ifdef USE_ARDUINO
|
||||
} else {
|
||||
this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), pcTaskGetName(current_task), color);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||
@ -230,7 +236,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
||||
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
|
||||
// add 1 to buffer size for null terminator
|
||||
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
|
||||
#ifdef USE_ARDUINO
|
||||
this->main_task = xTaskGetCurrentTaskHandle();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef USE_LIBRETINY
|
||||
|
@ -3,34 +3,47 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_FRAMEWORK,
|
||||
KEY_CORE,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_NRF52,
|
||||
CONF_TYPE,
|
||||
CONF_FRAMEWORK,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.helpers import (
|
||||
write_file_if_changed,
|
||||
)
|
||||
from typing import Union
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import nrf52_pin_to_code # noqa
|
||||
|
||||
AUTO_LOAD = ["nrf52_nrfx"]
|
||||
# def AUTO_LOAD():
|
||||
# # if CORE.using_arduino:
|
||||
# return ["nrf52_nrfx"]
|
||||
# # return []
|
||||
|
||||
KEY_NRF52 = "nrf52"
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
CORE.data[KEY_NRF52] = {}
|
||||
CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS] = {}
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_NRF52
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = config[CONF_FRAMEWORK][CONF_TYPE]
|
||||
return config
|
||||
|
||||
|
||||
# https://github.com/platformio/platform-nordicnrf52/releases
|
||||
ARDUINO_PLATFORM_VERSION = cv.Version(10, 3, 0)
|
||||
NORDICNRF52_PLATFORM_VERSION = cv.Version(10, 3, 0)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
def _platform_check_versions(value):
|
||||
value = value.copy()
|
||||
value[CONF_PLATFORM_VERSION] = value.get(
|
||||
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
|
||||
CONF_PLATFORM_VERSION,
|
||||
_parse_platform_version(str(NORDICNRF52_PLATFORM_VERSION)),
|
||||
)
|
||||
return value
|
||||
|
||||
@ -46,20 +59,32 @@ def _parse_platform_version(value):
|
||||
|
||||
CONF_PLATFORM_VERSION = "platform_version"
|
||||
|
||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||
PLATFORM_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
}
|
||||
),
|
||||
_arduino_check_versions,
|
||||
_platform_check_versions,
|
||||
)
|
||||
|
||||
FRAMEWORK_ZEPHYR = "zephyr"
|
||||
FRAMEWORK_ARDUINO = "arduino"
|
||||
FRAMEWORK_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
FRAMEWORK_ZEPHYR: PLATFORM_FRAMEWORK_SCHEMA,
|
||||
FRAMEWORK_ARDUINO: PLATFORM_FRAMEWORK_SCHEMA,
|
||||
},
|
||||
lower=True,
|
||||
space="-",
|
||||
default_type=FRAMEWORK_ARDUINO,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BOARD): cv.string_strict,
|
||||
cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
|
||||
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
|
||||
}
|
||||
),
|
||||
set_core_data,
|
||||
@ -68,31 +93,94 @@ CONFIG_SCHEMA = cv.All(
|
||||
nrf52_ns = cg.esphome_ns.namespace("nrf52")
|
||||
|
||||
|
||||
PrjConfValueType = Union[bool, str, int]
|
||||
KEY_PRJ_CONF_OPTIONS = "prj_conf_options"
|
||||
|
||||
|
||||
def add_zephyr_prj_conf_option(name: str, value: PrjConfValueType):
|
||||
"""Set an zephyr prj conf value."""
|
||||
if not CORE.using_zephyr:
|
||||
raise ValueError("Not an zephyr project")
|
||||
if name in CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS]:
|
||||
old_value = CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS][name]
|
||||
if old_value != value:
|
||||
raise ValueError(
|
||||
f"{name} alread set with value {old_value}, new value {value}"
|
||||
)
|
||||
CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS][name] = value
|
||||
|
||||
|
||||
@coroutine_with_priority(1000)
|
||||
async def to_code(config):
|
||||
cg.add(nrf52_ns.setup_preferences())
|
||||
if config[CONF_BOARD] == "nrf52840":
|
||||
# it has most generic GPIO mapping
|
||||
config[CONF_BOARD] = "nrf52840_dk_adafruit"
|
||||
if CORE.using_zephyr:
|
||||
# this board works with https://github.com/adafruit/Adafruit_nRF52_Bootloader
|
||||
config[CONF_BOARD] = "adafruit_itsybitsy_nrf52840"
|
||||
elif CORE.using_arduino:
|
||||
# it has most generic GPIO mapping
|
||||
config[CONF_BOARD] = "nrf52840_dk_adafruit"
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_build_flag("-DUSE_NRF52")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", "nrf52840")
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_platformio_option(CONF_FRAMEWORK, conf[CONF_TYPE])
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
# make sure that firmware.zip is created
|
||||
# for Adafruit_nRF52_Bootloader
|
||||
cg.add_platformio_option("board_upload.protocol", "nrfutil")
|
||||
cg.add_platformio_option("board_upload.use_1200bps_touch", "true")
|
||||
cg.add_platformio_option("board_upload.require_upload_port", "true")
|
||||
cg.add_platformio_option("board_upload.wait_for_upload_port", "true")
|
||||
# watchdog
|
||||
cg.add_build_flag("-DNRFX_WDT_ENABLED=1")
|
||||
cg.add_build_flag("-DNRFX_WDT0_ENABLED=1")
|
||||
cg.add_build_flag("-DNRFX_WDT_CONFIG_NO_IRQ=1")
|
||||
# prevent setting up GPIO PINs
|
||||
cg.add_platformio_option("board_build.variant", "nrf52840")
|
||||
cg.add_platformio_option(
|
||||
"board_build.variants_dir", os.path.dirname(os.path.realpath(__file__))
|
||||
)
|
||||
if CORE.using_arduino:
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
# watchdog
|
||||
cg.add_build_flag("-DNRFX_WDT_ENABLED=1")
|
||||
cg.add_build_flag("-DNRFX_WDT0_ENABLED=1")
|
||||
cg.add_build_flag("-DNRFX_WDT_CONFIG_NO_IRQ=1")
|
||||
# prevent setting up GPIO PINs
|
||||
cg.add_platformio_option("board_build.variant", "nrf52840")
|
||||
cg.add_platformio_option(
|
||||
"board_build.variants_dir", os.path.dirname(os.path.realpath(__file__))
|
||||
)
|
||||
elif CORE.using_zephyr:
|
||||
cg.add_build_flag("-DUSE_ZEPHYR")
|
||||
cg.add_platformio_option(
|
||||
"platform_packages", ["framework-zephyr@~2.30400.230914"]
|
||||
)
|
||||
# cpp support
|
||||
add_zephyr_prj_conf_option("CONFIG_NEWLIB_LIBC", False)
|
||||
add_zephyr_prj_conf_option("CONFIG_NEWLIB_LIBC_NANO", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NEWLIB_LIBC_FLOAT_PRINTF", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_CPLUSPLUS", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_LIB_CPLUSPLUS", True)
|
||||
# watchdog
|
||||
add_zephyr_prj_conf_option("CONFIG_WATCHDOG", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_WDT_DISABLE_AT_BOOT", False)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _format_prj_conf_val(value: PrjConfValueType) -> str:
|
||||
if isinstance(value, bool):
|
||||
return "y" if value else "n"
|
||||
if isinstance(value, int):
|
||||
return str(value)
|
||||
if isinstance(value, str):
|
||||
return f'"{value}"'
|
||||
raise ValueError
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files():
|
||||
if CORE.using_zephyr:
|
||||
want_opts = CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS]
|
||||
contents = (
|
||||
"\n".join(
|
||||
f"{name}={_format_prj_conf_val(value)}"
|
||||
for name, value in sorted(want_opts.items())
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
write_file_if_changed(CORE.relative_build_path("zephyr/prj.conf"), contents)
|
||||
|
@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
#ifdef USE_NRF52
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Arduino.h>
|
||||
#include "Adafruit_nRFCrypto.h"
|
||||
#include "nrfx_wdt.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void yield() { ::yield(); }
|
||||
uint32_t millis() { return ::millis(); }
|
||||
void delay(uint32_t ms) { ::delay(ms); }
|
||||
@ -58,7 +57,7 @@ void nrf52GetMacAddr(uint8_t *mac)
|
||||
mac[1] = src[4];
|
||||
mac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_RP2040
|
45
esphome/components/nrf52/core_zephyr.cpp
Normal file
45
esphome/components/nrf52/core_zephyr.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#ifdef USE_NRF52
|
||||
#ifdef USE_ZEPHYR
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
namespace esphome {
|
||||
void yield() { ::k_yield(); }
|
||||
uint32_t millis() { return ::k_ticks_to_ms_floor32(k_uptime_ticks()); }
|
||||
void delay(uint32_t ms) { ::k_msleep(ms); }
|
||||
uint32_t micros() { return ::k_ticks_to_us_floor32(k_uptime_ticks()); }
|
||||
|
||||
void arch_init() {
|
||||
// TODO
|
||||
}
|
||||
void arch_feed_wdt() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void nrf52GetMacAddr(uint8_t *mac)
|
||||
{
|
||||
const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR;
|
||||
mac[5] = src[0];
|
||||
mac[4] = src[1];
|
||||
mac[3] = src[2];
|
||||
mac[2] = src[3];
|
||||
mac[1] = src[4];
|
||||
mac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
int main(){
|
||||
setup();
|
||||
while(1) {
|
||||
loop();
|
||||
esphome::yield();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // USE_RP2040
|
@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#ifdef USE_ZEPHYR
|
||||
struct device;
|
||||
#endif
|
||||
namespace esphome {
|
||||
namespace nrf52 {
|
||||
|
||||
@ -14,7 +14,7 @@ class NRF52GPIOPin : public InternalGPIOPin {
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
void setup() override { pin_mode(flags_); }
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
@ -30,6 +30,9 @@ class NRF52GPIOPin : public InternalGPIOPin {
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
#ifdef USE_ZEPHYR
|
||||
const device * gpio_ = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace nrf52
|
||||
|
@ -1,9 +1,11 @@
|
||||
#ifdef USE_NRF52
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "Arduino.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nrf52 {
|
||||
@ -77,6 +79,9 @@ void NRF52GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Inter
|
||||
irq_arg = arg;
|
||||
attachInterrupt(pin_, pin_irq, mode);
|
||||
}
|
||||
|
||||
void NRF52GPIOPin::setup() { pin_mode(flags_); }
|
||||
|
||||
void NRF52GPIOPin::pin_mode(gpio::Flags flags) {
|
||||
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
|
||||
}
|
||||
@ -109,4 +114,5 @@ bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_NRF52
|
143
esphome/components/nrf52/gpio_zephyr.cpp
Normal file
143
esphome/components/nrf52/gpio_zephyr.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
#ifdef USE_NRF52
|
||||
#ifdef USE_ZEPHYR
|
||||
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
// #include <functional>
|
||||
// #include <vector>
|
||||
// #include <device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace nrf52 {
|
||||
|
||||
static const char *const TAG = "nrf52";
|
||||
|
||||
static int flags_to_mode(gpio::Flags flags, uint8_t pin) {
|
||||
if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
|
||||
return GPIO_INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return GPIO_OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
return GPIO_INPUT | GPIO_PULL_UP;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||
return GPIO_INPUT | GPIO_PULL_DOWN;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return GPIO_OUTPUT | GPIO_OPEN_DRAIN;
|
||||
} else {
|
||||
return GPIO_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
struct ISRPinArg {
|
||||
uint8_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
//TODO implement
|
||||
//TODO test
|
||||
// void (*irq_cb)(void *);
|
||||
// void* irq_arg;
|
||||
// static void pin_irq(void){
|
||||
// irq_cb(irq_arg);
|
||||
// }
|
||||
|
||||
ISRInternalGPIOPin NRF52GPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void NRF52GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
// uint32_t mode = ISR_DEFERRED;
|
||||
// switch (type) {
|
||||
// case gpio::INTERRUPT_RISING_EDGE:
|
||||
// mode |= inverted_ ? FALLING : RISING;
|
||||
// break;
|
||||
// case gpio::INTERRUPT_FALLING_EDGE:
|
||||
// mode |= inverted_ ? RISING : FALLING;
|
||||
// break;
|
||||
// case gpio::INTERRUPT_ANY_EDGE:
|
||||
// mode |= CHANGE;
|
||||
// break;
|
||||
// default:
|
||||
// return;
|
||||
// }
|
||||
|
||||
// irq_cb = func;
|
||||
// irq_arg = arg;
|
||||
// attachInterrupt(pin_, pin_irq, mode);
|
||||
}
|
||||
|
||||
void NRF52GPIOPin::setup() {
|
||||
const struct device * gpio = nullptr;
|
||||
if(pin_ < 32) {
|
||||
#define GPIO0 DT_NODELABEL(gpio0)
|
||||
#if DT_NODE_HAS_STATUS(GPIO0, okay)
|
||||
gpio = DEVICE_DT_GET(GPIO0);
|
||||
#else
|
||||
#error "gpio0 is disabled"
|
||||
#endif
|
||||
} else {
|
||||
#define GPIO1 DT_NODELABEL(gpio1)
|
||||
#if DT_NODE_HAS_STATUS(GPIO1, okay)
|
||||
gpio = DEVICE_DT_GET(GPIO1);
|
||||
#else
|
||||
#error "gpio1 is disabled"
|
||||
#endif
|
||||
}
|
||||
//TODO why no ready??
|
||||
// if (!device_is_ready(gpio)) {
|
||||
gpio_ = gpio;
|
||||
// } else {
|
||||
// ESP_LOGE(TAG, "gpio %u is not ready.", pin_);
|
||||
// }
|
||||
gpio_ = gpio;
|
||||
pin_mode(flags_);
|
||||
}
|
||||
|
||||
void NRF52GPIOPin::pin_mode(gpio::Flags flags) {
|
||||
if(nullptr == gpio_) {
|
||||
return;
|
||||
}
|
||||
gpio_pin_configure(gpio_, pin_, flags_to_mode(flags, pin_));
|
||||
}
|
||||
|
||||
std::string NRF52GPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool NRF52GPIOPin::digital_read() {
|
||||
if(nullptr == gpio_) {
|
||||
//TODO invert ??
|
||||
return false;
|
||||
}
|
||||
return bool(gpio_pin_get(gpio_, pin_)) != inverted_; // NOLINT
|
||||
}
|
||||
void NRF52GPIOPin::digital_write(bool value) {
|
||||
if(nullptr == gpio_) {
|
||||
return;
|
||||
}
|
||||
gpio_pin_set(gpio_, pin_, value != inverted_ ? 1 : 0);
|
||||
}
|
||||
void NRF52GPIOPin::detach_interrupt() const {
|
||||
// detachInterrupt(pin_);
|
||||
}
|
||||
|
||||
} // namespace nrf52
|
||||
|
||||
// using namespace nrf52;
|
||||
|
||||
// TODO seems to not work???
|
||||
// bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
// auto *arg = reinterpret_cast<nrf52::ISRPinArg *>(arg_);
|
||||
// return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
|
||||
// }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_NRF52
|
31
esphome/components/shell/__init__.py
Normal file
31
esphome/components/shell/__init__.py
Normal file
@ -0,0 +1,31 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
)
|
||||
from esphome.components.nrf52 import add_zephyr_prj_conf_option
|
||||
|
||||
shell_ns = cg.esphome_ns.namespace("shell")
|
||||
Shell = shell_ns.class_("Shell", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Shell),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_zephyr,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
add_zephyr_prj_conf_option("CONFIG_SHELL", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_SHELL_BACKENDS", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_SHELL_BACKEND_SERIAL", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_SHELL_DYN_CMD_COMPLETION", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_SHELL", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_GPIO_SHELL", True)
|
||||
# add_zephyr_prj_conf_option("CONFIG_ADC_SHELL", True)
|
||||
add_zephyr_prj_conf_option("CONFIG_NET_L2_BT_SHELL", True)
|
21
esphome/components/shell/shell.cpp
Normal file
21
esphome/components/shell/shell.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "shell.h"
|
||||
#include <zephyr/usb/usb_device.h>
|
||||
|
||||
struct device;
|
||||
|
||||
namespace esphome {
|
||||
namespace shell {
|
||||
|
||||
void Shell::setup() {
|
||||
#if DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_shell_uart), zephyr_cdc_acm_uart)
|
||||
const device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
|
||||
if (!device_is_ready(dev) || usb_enable(NULL)) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
#error "shell is not set in device tree"
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace shell
|
||||
} // namespace esphome
|
10
esphome/components/shell/shell.h
Normal file
10
esphome/components/shell/shell.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace shell {
|
||||
class Shell : public Component {
|
||||
void setup() override;
|
||||
};
|
||||
}
|
||||
}
|
@ -599,6 +599,7 @@ only_on_rp2040 = only_on(PLATFORM_RP2040)
|
||||
only_on_nrf52 = only_on(PLATFORM_NRF52)
|
||||
only_with_arduino = only_with_framework("arduino")
|
||||
only_with_esp_idf = only_with_framework("esp-idf")
|
||||
only_with_zephyr = only_with_framework("zephyr")
|
||||
|
||||
|
||||
# Adapted from:
|
||||
|
@ -676,6 +676,10 @@ class EsphomeCore:
|
||||
def using_esp_idf(self):
|
||||
return self.target_framework == "esp-idf"
|
||||
|
||||
@property
|
||||
def using_zephyr(self):
|
||||
return self.target_framework == "zephyr"
|
||||
|
||||
def add_job(self, func, *args, **kwargs):
|
||||
self.event_loop.add_job(func, *args, **kwargs)
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <strings.h>
|
||||
|
||||
#if defined(USE_ESP8266)
|
||||
#include <osapi.h>
|
||||
@ -48,7 +49,9 @@
|
||||
#endif
|
||||
|
||||
#ifdef USE_NRF52
|
||||
#ifdef USE_ARDUINO
|
||||
#include "Adafruit_nRFCrypto.h"
|
||||
#endif
|
||||
#include "esphome/components/nrf52/core.h"
|
||||
#endif
|
||||
|
||||
@ -246,7 +249,11 @@ bool random_bytes(uint8_t *data, size_t len) {
|
||||
fclose(fp);
|
||||
return true;
|
||||
#elif defined(USE_NRF52)
|
||||
#ifdef USE_ARDUINO
|
||||
return nRFCrypto.Random.generate(data, len);
|
||||
#elif USE_ZEPHYR
|
||||
//TODO
|
||||
#endif
|
||||
#else
|
||||
#error "No random source available for this configuration."
|
||||
#endif
|
||||
@ -518,13 +525,14 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
|
||||
}
|
||||
|
||||
// System APIs
|
||||
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST)
|
||||
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST) || defined(USE_NRF52)
|
||||
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
|
||||
Mutex::Mutex() {}
|
||||
void Mutex::lock() {}
|
||||
bool Mutex::try_lock() { return true; }
|
||||
void Mutex::unlock() {}
|
||||
#elif defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_NRF52)
|
||||
//TODO
|
||||
#elif defined(USE_ESP32) || defined(USE_LIBRETINY) /*|| defined(USE_NRF52)*/
|
||||
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
|
||||
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
|
||||
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
|
||||
|
@ -21,8 +21,10 @@
|
||||
#include <FreeRTOS.h>
|
||||
#include <semphr.h>
|
||||
#elif defined(USE_NRF52)
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define HOT __attribute__((hot))
|
||||
#define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg)))
|
||||
@ -548,7 +550,8 @@ class Mutex {
|
||||
Mutex &operator=(const Mutex &) = delete;
|
||||
|
||||
private:
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_NRF52)
|
||||
//TODO
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) /*|| defined(USE_NRF52)*/
|
||||
SemaphoreHandle_t handle_;
|
||||
#endif
|
||||
};
|
||||
|
@ -310,6 +310,10 @@ def copy_src_tree():
|
||||
CORE.relative_src_path("esphome.h"),
|
||||
ESPHOME_H_FORMAT.format(include_s + '\n#include "pio_includes.h"'),
|
||||
)
|
||||
elif CORE.is_nrf52:
|
||||
from esphome.components.nrf52 import copy_files
|
||||
|
||||
copy_files()
|
||||
|
||||
|
||||
def generate_defines_h():
|
||||
|
Loading…
x
Reference in New Issue
Block a user