diff --git a/cli/cmd/zigbee/firmware/build.go b/cli/cmd/zigbee/firmware/build.go index 093cfdc..9d7d162 100644 --- a/cli/cmd/zigbee/firmware/build.go +++ b/cli/cmd/zigbee/firmware/build.go @@ -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) diff --git a/cli/config/device.go b/cli/config/device.go index b66f99e..c4fd835 100644 --- a/cli/config/device.go +++ b/cli/config/device.go @@ -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 diff --git a/cli/generate/generator.go b/cli/generate/generator.go index 4547d77..d37d275 100644 --- a/cli/generate/generator.go +++ b/cli/generate/generator.go @@ -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 -// -// 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{}{} @@ -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 { @@ -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 { diff --git a/cli/types/appconfig/appconfig.go b/cli/types/appconfig/appconfig.go index 52341f7..a1d3743 100644 --- a/cli/types/appconfig/appconfig.go +++ b/cli/types/appconfig/appconfig.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "sort" + "strconv" ) type Provider interface { @@ -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, @@ -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 { @@ -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 } diff --git a/cli/types/appconfig/known.go b/cli/types/appconfig/known.go index 7d7b249..a0c36ef 100644 --- a/cli/types/appconfig/known.go +++ b/cli/types/appconfig/known.go @@ -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) diff --git a/cli/zigbee.yml b/cli/zigbee.yml index c0c1bf6..62c9ed2 100644 --- a/cli/zigbee.yml +++ b/cli/zigbee.yml @@ -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.