Skip to content

Commit

Permalink
Add support for multi-channel Zigbee device (#26)
Browse files Browse the repository at this point in the history
By default device will use all channels,
but can be limited down with a config option.
  • Loading branch information
ffenix113 authored Feb 11, 2024
1 parent 0e50bdf commit f268329
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 72 deletions.
5 changes: 4 additions & 1 deletion cli/cmd/zigbee/firmware/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ func buildFirmware(ctx *cli.Context) error {
workDir = "."
}

generator := generate.NewGenerator(cfg)
generator, err := generate.NewGenerator(cfg)
if err != nil {
return fmt.Errorf("new generator: %w", err)
}

if err := generator.Generate(workDir, cfg); err != nil {
return fmt.Errorf("generate base: %w", err)
Expand Down
3 changes: 3 additions & 0 deletions cli/config/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type General struct {
// Zephyr name for the board
Board string
RunEvery time.Duration
// ZigbeeChannels will define which endpoints device should try to use.
// By default device will try all available channels.
ZigbeeChannels []int `yaml:"zigbee_channels"`
// Flasher defines the way the board should be flashed.
Flasher string
FlasherOptions map[string]any
Expand Down
128 changes: 66 additions & 62 deletions cli/generate/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,82 @@ import (
"github.com/ffenix113/zigbee_home/cli/types/source"
)

// TODO: Define some "modifier"/"injector" interface
// that will allow to update generator state.
// It is important that this interface be able to add
// new files to generated source output!
//
// This is useful for common functionality for
// sensors and peripherals.
// I.e. to enable some sensors it is neeeded:
// - update proj.conf to add CONFIG_SENSOR=y
// - - maybe also add CONFIG_I2C=y
// - add include <zephyr/drivers/sensor.h>
//
// Sensors should also be able to tell that
// they want to use such "modifier"/"injector"

type Generator struct {
AppConfig *appconfig.AppConfig
DeviceTree *devicetree.DeviceTree
Source *source.Source
}

func NewGenerator(device *config.Device) *Generator {
func NewGenerator(device *config.Device) (*Generator, error) {
appConfig, err := appconfig.NewDefaultAppConfig(
appconfig.DefaultAppConfigOptions{
IsRouter: device.Board.IsRouter,
ZigbeeChannels: device.General.ZigbeeChannels,
},
)
if err != nil {
return nil, fmt.Errorf("default app config: %w", err)
}

return &Generator{
AppConfig: appconfig.NewDefaultAppConfig(device.Board.IsRouter),
AppConfig: appConfig,
DeviceTree: devicetree.NewDeviceTree(),
Source: source.NewSource(),
}
}, nil
}

func (g *Generator) Generate(workDir string, device *config.Device) error {
providedExtenders, err := getExtenders(device)
if err != nil {
return fmt.Errorf("get extenders: %w", err)
}

// Write devicetree overlay (app.overlay)
if err := updateDeviceTree(device, g.DeviceTree, providedExtenders); err != nil {
return fmt.Errorf("update overlay: %w", err)
}

overlayFile, err := os.Create(workDir + "/app.overlay")
if err != nil {
return fmt.Errorf("create overlay file: %w", err)
}

defer overlayFile.Close()

if err := g.DeviceTree.WriteTo(overlayFile); err != nil {
return fmt.Errorf("write overlay: %w", err)
}

// Write app config (prj.conf)
if err := updateAppConfig(device, g.AppConfig, providedExtenders); err != nil {
return fmt.Errorf("update app config: %w", err)
}

appConfigFile, err := os.Create(workDir + "/prj.conf")
if err != nil {
return fmt.Errorf("create app config file: %w", err)
}

defer appConfigFile.Close()

if err := g.AppConfig.WriteTo(appConfigFile); err != nil {
return fmt.Errorf("write app config: %w", err)
}

// Write app source
srcDir := workDir + "/src"
if err := os.Mkdir(srcDir, os.ModeDir|0o775); err != nil && !os.IsExist(err) {
return fmt.Errorf("create src dir: %w", err)
}

if err := g.Source.WriteTo(srcDir, device, providedExtenders); err != nil {
return fmt.Errorf("write app src: %w", err)
}

return nil
}

func getExtenders(device *config.Device) ([]generator.Extender, error) {
var providedExtenders []generator.Extender
uniqueExtenders := map[string]struct{}{}

Expand All @@ -56,7 +102,7 @@ func (g *Generator) Generate(workDir string, device *config.Device) error {
forcedBootloader)

if forcedBootloader && bootloaderConfig == nil {
return fmt.Errorf("Bootloader %q was forced, but is not found in known bootloaders", device.Board.Bootloader)
return nil, fmt.Errorf("Bootloader %q was forced, but is not found in known bootloaders", device.Board.Bootloader)
}

if bootloaderConfig != nil {
Expand Down Expand Up @@ -97,49 +143,7 @@ func (g *Generator) Generate(workDir string, device *config.Device) error {
providedExtenders = append(providedExtenders, extenders.NewLEDs(device.Board.LEDs...))
}

// Write devicetree overlay (app.overlay)
if err := updateDeviceTree(device, g.DeviceTree, providedExtenders); err != nil {
return fmt.Errorf("update overlay: %w", err)
}

overlayFile, err := os.Create(workDir + "/app.overlay")
if err != nil {
return fmt.Errorf("create overlay file: %w", err)
}

defer overlayFile.Close()

if err := g.DeviceTree.WriteTo(overlayFile); err != nil {
return fmt.Errorf("write overlay: %w", err)
}

// Write app config (prj.conf)
if err := updateAppConfig(device, g.AppConfig, providedExtenders); err != nil {
return fmt.Errorf("update app config: %w", err)
}

appConfigFile, err := os.Create(workDir + "/prj.conf")
if err != nil {
return fmt.Errorf("create app config file: %w", err)
}

defer appConfigFile.Close()

if err := g.AppConfig.WriteTo(appConfigFile); err != nil {
return fmt.Errorf("write app config: %w", err)
}

// Write app source
srcDir := workDir + "/src"
if err := os.Mkdir(srcDir, os.ModeDir|0o775); err != nil && !os.IsExist(err) {
return fmt.Errorf("create src dir: %w", err)
}

if err := g.Source.WriteTo(srcDir, device, providedExtenders); err != nil {
return fmt.Errorf("write app src: %w", err)
}

return nil
return providedExtenders, nil
}

func updateDeviceTree(device *config.Device, deviceTree *devicetree.DeviceTree, extenders []generator.Extender) error {
Expand Down
38 changes: 34 additions & 4 deletions cli/types/appconfig/appconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"sort"
"strconv"
)

type Provider interface {
Expand Down Expand Up @@ -84,13 +85,19 @@ func NewEmptyAppConfig() *AppConfig {
}
}

func NewDefaultAppConfig(isRouter bool) *AppConfig {
type DefaultAppConfigOptions struct {
IsRouter bool
ZigbeeChannels []int
}

func NewDefaultAppConfig(opts DefaultAppConfigOptions) (*AppConfig, error) {
appConfig := NewEmptyAppConfig().AddValue(
CONFIG_USB_DEVICE_STACK,
CONFIG_DK_LIBRARY,
CONFIG_ZIGBEE,
CONFIG_ZIGBEE_APP_UTILS,
CONFIG_ZIGBEE_CHANNEL,
CONFIG_ZIGBEE_CHANNEL_MASK,
CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI,
CONFIG_CRYPTO,
CONFIG_CRYPTO_NRF_ECB,
CONFIG_CRYPTO_INIT_PRIORITY,
Expand All @@ -105,11 +112,26 @@ func NewDefaultAppConfig(isRouter bool) *AppConfig {
)

deviceRole := CONFIG_ZIGBEE_ROLE_END_DEVICE
if isRouter {
if opts.IsRouter {
deviceRole = CONFIG_ZIGBEE_ROLE_ROUTER
}

return appConfig.AddValue(deviceRole)
appConfig = appConfig.AddValue(deviceRole)

if len(opts.ZigbeeChannels) != 0 {
channel := int32(0)
for _, chann := range opts.ZigbeeChannels {
if chann < 11 || chann > 26 {
return nil, fmt.Errorf("zigbee channels must be in range [11, 26], but have %d", chann)
}

channel |= 1 << chann
}

appConfig = appConfig.AddValue(CONFIG_ZIGBEE_CHANNEL_MASK.Required("0x" + strconv.FormatInt(int64(channel), 16)))
}

return appConfig, nil
}

func (c *AppConfig) AddValue(configValues ...ConfigValue) *AppConfig {
Expand All @@ -133,6 +155,14 @@ func (c *AppConfig) AddValue(configValues ...ConfigValue) *AppConfig {
}
}

if val, ok := c.values[configValue.Name]; ok {
if val.RequiredValue != "" &&
configValue.RequiredValue != "" &&
configValue.RequiredValue != val.RequiredValue {
panic(fmt.Sprintf("config value %q already has required value %q, but new added value requires it to be %q", val.Name, val.RequiredValue, configValue.Name, configValue.RequiredValue))
}
}

c.values[configValue.Name] = configValue
}

Expand Down
11 changes: 6 additions & 5 deletions cli/types/appconfig/known.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ var (
CONFIG_DK_LIBRARY = NewValue("CONFIG_DK_LIBRARY").Default(Yes)

// Zigbee
CONFIG_ZIGBEE = NewValue("CONFIG_ZIGBEE").Default(Yes)
CONFIG_ZIGBEE_APP_UTILS = NewValue("CONFIG_ZIGBEE_APP_UTILS").Default(Yes)
CONFIG_ZIGBEE_CHANNEL = NewValue("CONFIG_ZIGBEE_CHANNEL").Default(`11`)
CONFIG_ZIGBEE_ROLE_END_DEVICE = NewValue("CONFIG_ZIGBEE_ROLE_END_DEVICE").Default(Yes)
CONFIG_ZIGBEE_ROLE_ROUTER = NewValue("CONFIG_ZIGBEE_ROLE_ROUTER").Default(Yes)
CONFIG_ZIGBEE = NewValue("CONFIG_ZIGBEE").Default(Yes)
CONFIG_ZIGBEE_APP_UTILS = NewValue("CONFIG_ZIGBEE_APP_UTILS").Default(Yes)
CONFIG_ZIGBEE_CHANNEL_MASK = NewValue("CONFIG_ZIGBEE_CHANNEL_MASK").Default("0x7FFF800")
CONFIG_ZIGBEE_ROLE_END_DEVICE = NewValue("CONFIG_ZIGBEE_ROLE_END_DEVICE").Default(Yes)
CONFIG_ZIGBEE_ROLE_ROUTER = NewValue("CONFIG_ZIGBEE_ROLE_ROUTER").Default(Yes)
CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI = NewValue("CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI").Default(Yes)

// Cryptography
CONFIG_CRYPTO = NewValue("CONFIG_CRYPTO").Default(Yes)
Expand Down
5 changes: 5 additions & 0 deletions cli/zigbee.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ general:
#ncs_toolchain_base: ~/toolchains/7795df4459/
#zephyr_base: ~/ncs/zephyr

# zigbee_channels will provide optional list of channels to use.
# Note: if not defined device will use all available channels,
# so it is not needed to define this if not specifically necessary.
zigbee_channels: [11,13,15,16,17]

# Flasher tells which flashing method to use.
# Currently `nrfutil`, `mcuboot` and `west`
# are defined(but not equally tested). Nrfutil works though.
Expand Down

0 comments on commit f268329

Please sign in to comment.