func GetRouterConfig(bandID string, fps map[string]*frequencyplans.FrequencyPlan, isProd bool, dlTime time.Time) (RouterConfig, error) {
	for _, fp := range fps {
		if err := fp.Validate(); err != nil {
			return RouterConfig{}, errFrequencyPlan.New()
		}
	}
	conf := RouterConfig{}
	conf.JoinEUI = nil
	conf.NetID = nil
	phy, err := band.GetLatest(bandID)
	if err != nil {
		return RouterConfig{}, errFrequencyPlan.New()
	}
	s := strings.Split(phy.ID, "_")
	if len(s) < 2 {
		return RouterConfig{}, errFrequencyPlan.New()
	}
	conf.Region = fmt.Sprintf("%s%s", s[0], s[1])
	min, max, err := getMinMaxFrequencies(fps)
	conf.FrequencyRange = []int{
		int(min),
		int(max),
	}
	conf.HardwareSpec = fmt.Sprintf("%s/%d", configHardwareSpecPrefix, len(fps))
	drs, err := getDataRatesFromBandID(bandID)
	if err != nil {
		return RouterConfig{}, errFrequencyPlan.New()
	}
	conf.DataRates = drs
	conf.NoCCA = !isProd
	conf.NoDutyCycle = !isProd
	conf.NoDwellTime = !isProd
	for _, fp := range fps {
		if len(fp.Radios) == 0 {
			continue
		}
		sx1301Conf, err := shared.BuildSX1301Config(fp)
		if err != nil {
			return RouterConfig{}, err
		}
		var lbsSX1301Config LBSSX1301Config
		err = lbsSX1301Config.fromSX1301Conf(*sx1301Conf)
		if err != nil {
			return RouterConfig{}, err
		}
		conf.SX1301Config = append(conf.SX1301Config, lbsSX1301Config)
	}
	conf.MuxTime = float64(dlTime.Unix()) + float64(dlTime.Nanosecond())/(1e9)
	return conf, nil
}
GetRouterConfig returns the routerconfig message to be sent to the gateway.
Currently as per the basic station docs, all frequency plans have to be from the same region (band) https://doc.sm.tc/station/tcproto.html#router-config-message.
package lbslns
import (
	"bytes"
	"encoding/json"
	"fmt"
	"math"
	"strings"
	"time"
	"go.thethings.network/lorawan-stack/v3/pkg/band"
	"go.thethings.network/lorawan-stack/v3/pkg/errors"
	"go.thethings.network/lorawan-stack/v3/pkg/frequencyplans"
	"go.thethings.network/lorawan-stack/v3/pkg/pfconfig/shared"
)
const (
	configHardwareSpecPrefix = "sx1301"
)
var (
	errFrequencyPlan = errors.DefineInvalidArgument("frequency_plan", "invalid frequency plan `{name}`")
	errInvalidKey    = errors.DefineInvalidArgument("invalid_key", "key `{key}` invalid")
)
type kv struct {
	key   string
	value interface{}
}
type orderedMap struct {
	kv []kv
}
func (m *orderedMap) add(k string, v interface{}) {
	m.kv = append(m.kv, kv{key: k, value: v})
}
func (m orderedMap) MarshalJSON() ([]byte, error) {
	var b bytes.Buffer
	b.WriteString("{")
	for i, kv := range m.kv {
		if i != 0 {
			b.WriteString(",")
		}
		key, err := json.Marshal(kv.key)
		if err != nil {
			return nil, err
		}
		b.Write(key)
		b.WriteString(":")
		val, err := json.Marshal(kv.value)
		if err != nil {
			return nil, err
		}
		b.Write(val)
	}
	b.WriteString("}")
	return b.Bytes(), nil
}
type DataRates [16][3]int
type LBSRFConfig struct {
	Enable    bool   `json:"enable"`
	Frequency uint64 `json:"freq"`
}
type LBSSX1301Config struct {
	LBTConfig           *shared.LBTConfig
	Radios              []LBSRFConfig
	Channels            []shared.IFConfig
	LoRaStandardChannel *shared.IFConfig
	FSKChannel          *shared.IFConfig
}
func (c LBSSX1301Config) MarshalJSON() ([]byte, error) {
	var m orderedMap
	if c.LBTConfig != nil {
		m.add("lbt_cfg", *c.LBTConfig)
	}
	for i, radio := range c.Radios {
		m.add(fmt.Sprintf("radio_%d", i), radio)
	}
	for i, channel := range c.Channels {
		m.add(fmt.Sprintf("chan_multiSF_%d", i), channel)
	}
	if c.LoRaStandardChannel != nil {
		m.add("chan_Lora_std", *c.LoRaStandardChannel)
	}
	if c.FSKChannel != nil {
		m.add("chan_FSK", *c.FSKChannel)
	}
	return json.Marshal(m)
}
func (c *LBSSX1301Config) fromSX1301Conf(sx1301Conf shared.SX1301Config) error {
	c.LoRaStandardChannel = sx1301Conf.LoRaStandardChannel
	c.FSKChannel = sx1301Conf.FSKChannel
	c.LBTConfig = sx1301Conf.LBTConfig
	for _, radio := range sx1301Conf.Radios {
		c.Radios = append(c.Radios, LBSRFConfig{
			Enable:    radio.Enable,
			Frequency: radio.Frequency,
		})
	}
	for _, channel := range sx1301Conf.Channels {
		c.Channels = append(c.Channels, channel)
	}
	return nil
}
func (c *LBSSX1301Config) UnmarshalJSON(msg []byte) error {
	var root map[string]json.RawMessage
	if err := json.Unmarshal(msg, &root); err != nil {
		return err
	}
	radioMap, chanMap := make(map[int]LBSRFConfig), make(map[int]shared.IFConfig)
	for key, value := range root {
		switch {
		case key == "lbt_cfg":
			if err := json.Unmarshal(value, &c.LBTConfig); err != nil {
				return err
			}
		case key == "chan_Lora_std":
			if err := json.Unmarshal(value, &c.LoRaStandardChannel); err != nil {
				return err
			}
		case key == "chan_FSK":
			if err := json.Unmarshal(value, &c.FSKChannel); err != nil {
				return err
			}
		case strings.HasPrefix(key, "chan_multiSF_"):
			var channel shared.IFConfig
			if err := json.Unmarshal(value, &channel); err != nil {
				return err
			}
			var index int
			if _, err := fmt.Sscanf(key, "chan_multiSF_%d", &index); err == nil {
				chanMap[index] = channel
			} else {
				return err
			}
		case strings.HasPrefix(key, "radio_"):
			var radio LBSRFConfig
			if err := json.Unmarshal(value, &radio); err != nil {
				return err
			}
			var index int
			if _, err := fmt.Sscanf(key, "radio_%d", &index); err == nil {
				radioMap[index] = radio
			} else {
				return err
			}
		}
	}
	c.Radios, c.Channels = make([]LBSRFConfig, len(radioMap)), make([]shared.IFConfig, len(chanMap))
	for key, value := range radioMap {
		c.Radios[key] = value
	}
	for key, value := range chanMap {
		c.Channels[key] = value
	}
	return nil
}
type RouterConfig struct {
	NetID          []int             `json:"NetID"`
	JoinEUI        [][]int           `json:"JoinEui"`
	Region         string            `json:"region"`
	HardwareSpec   string            `json:"hwspec"`
	FrequencyRange []int             `json:"freq_range"`
	DataRates      DataRates         `json:"DRs"`
	SX1301Config   []LBSSX1301Config `json:"sx1301_conf"`
	NoCCA       bool `json:"nocca"`
	NoDutyCycle bool `json:"nodc"`
	NoDwellTime bool `json:"nodwell"`
	MuxTime float64 `json:"MuxTime"`
}
func (conf RouterConfig) MarshalJSON() ([]byte, error) {
	type Alias RouterConfig
	return json.Marshal(struct {
		Type string `json:"msgtype"`
		Alias
	}{
		Type:  "router_config",
		Alias: Alias(conf),
	})
}