From 212e8da993a709e9dfbfaff610591d77bd3f533d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jul 2022 12:53:36 +0200 Subject: [PATCH 1/3] Refactor frequency plans definition --- README.md | 168 +++-- disable_dwell_time.yml | 3 - docs/frequency-plans.md | 26 + docs/images/end-device/EU_863_870.svg | 271 +++++++ docs/images/end-device/EU_863_870_TTN.svg | 271 +++++++ docs/images/gateway/EU_863_870.svg | 304 ++++++++ enable_dwell_time_400ms.yml | 4 - end-device/EU_863_870.yml | 46 ++ .../modifiers/rx2_default_data_rata_3.yml | 0 end-device/modifiers/schema.json | 204 ++++++ end-device/schema.json | 210 ++++++ frequency-plans.go | 36 + frequency-plans.yml | 646 +---------------- gateway/EU_863_870.yml | 74 ++ gateway/modifiers/schema.json | 225 ++++++ gateway/schema.json | 231 ++++++ go.mod | 45 ++ go.sum | 321 +++++++++ internal/docs/docs.go | 101 +++ internal/docs/end-device.go | 156 ++++ internal/docs/frequency-plans.md.tmpl | 26 + internal/docs/gateway.go | 180 +++++ internal/model/end-device-frequency-plan.go | 137 ++++ internal/model/frequency-plan-description.go | 117 +++ internal/model/gateway-frequency-plan.go | 108 +++ internal/model/model.go | 256 +++++++ ...equency-plans-description-schema.json.tmpl | 676 ++++++++++++++++++ ...lans-end-device-modifiers-schema.json.tmpl | 204 ++++++ ...requency-plans-end-device-schema.json.tmpl | 210 ++++++ ...y-plans-gateway-modifiers-schema.json.tmpl | 225 ++++++ .../frequency-plans-gateway-schema.json.tmpl | 231 ++++++ internal/schema/schema.go | 117 +++ internal/utils/utils.go | 77 ++ internal/validate/validate.go | 57 ++ lbt_80_over_128.yml | 4 - AS_920_923.yml => legacy/AS_920_923.yml | 0 .../AS_920_923_TTN_AU.yml | 0 .../AS_920_923_TTN_JP_1.yml | 0 .../AS_920_923_TTN_JP_1_LAND_MOBILE.yml | 0 .../AS_920_923_TTN_JP_2.yml | 0 .../AS_920_923_TTN_JP_3.yml | 0 .../AS_920_923_TTN_JP_3_LAND_MOBILE.yml | 0 AS_923.yml => legacy/AS_923.yml | 0 AS_923_2.yml => legacy/AS_923_2.yml | 0 AS_923_3.yml => legacy/AS_923_3.yml | 0 AS_923_4.yml => legacy/AS_923_4.yml | 0 AS_923_925.yml => legacy/AS_923_925.yml | 0 .../AS_923_925_TTN_AU.yml | 0 .../AU_915_928_FSB_1.yml | 0 .../AU_915_928_FSB_2.yml | 0 .../AU_915_928_FSB_3.yml | 0 .../AU_915_928_FSB_4.yml | 0 .../AU_915_928_FSB_5.yml | 0 .../AU_915_928_FSB_6.yml | 0 .../AU_915_928_FSB_7.yml | 0 .../AU_915_928_FSB_8.yml | 0 .../CN_470_510_FSB_1.yml | 0 .../CN_470_510_FSB_11.yml | 0 EU_433.yml => legacy/EU_433.yml | 0 EU_863_870.yml => legacy/EU_863_870.yml | 0 .../EU_863_870_ROAMING_DRAFT.yml | 0 legacy/EU_863_870_TTN.yml | 1 + IN_865_867.yml => legacy/IN_865_867.yml | 0 .../ISM_2400_3CH_DRAFT2.yml | 0 .../KR_920_923_TTN.yml | 0 .../MA_869_870_DRAFT.yml | 0 .../RU_864_870_TTN.yml | 0 .../US_902_928_FSB_1.yml | 0 .../US_902_928_FSB_2.yml | 0 .../US_902_928_FSB_3.yml | 0 .../US_902_928_FSB_4.yml | 0 .../US_902_928_FSB_5.yml | 0 .../US_902_928_FSB_6.yml | 0 .../US_902_928_FSB_7.yml | 0 .../US_902_928_FSB_8.yml | 0 legacy/frequency-plans.yml | 649 +++++++++++++++++ schema.json | 676 ++++++++++++++++++ 77 files changed, 6599 insertions(+), 694 deletions(-) delete mode 100644 disable_dwell_time.yml create mode 100644 docs/frequency-plans.md create mode 100644 docs/images/end-device/EU_863_870.svg create mode 100644 docs/images/end-device/EU_863_870_TTN.svg create mode 100644 docs/images/gateway/EU_863_870.svg delete mode 100644 enable_dwell_time_400ms.yml create mode 100644 end-device/EU_863_870.yml rename EU_863_870_TTN.yml => end-device/modifiers/rx2_default_data_rata_3.yml (100%) create mode 100644 end-device/modifiers/schema.json create mode 100644 end-device/schema.json create mode 100644 frequency-plans.go create mode 100644 gateway/EU_863_870.yml create mode 100644 gateway/modifiers/schema.json create mode 100644 gateway/schema.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/docs/docs.go create mode 100644 internal/docs/end-device.go create mode 100644 internal/docs/frequency-plans.md.tmpl create mode 100644 internal/docs/gateway.go create mode 100644 internal/model/end-device-frequency-plan.go create mode 100644 internal/model/frequency-plan-description.go create mode 100644 internal/model/gateway-frequency-plan.go create mode 100644 internal/model/model.go create mode 100644 internal/schema/frequency-plans-description-schema.json.tmpl create mode 100644 internal/schema/frequency-plans-end-device-modifiers-schema.json.tmpl create mode 100644 internal/schema/frequency-plans-end-device-schema.json.tmpl create mode 100644 internal/schema/frequency-plans-gateway-modifiers-schema.json.tmpl create mode 100644 internal/schema/frequency-plans-gateway-schema.json.tmpl create mode 100644 internal/schema/schema.go create mode 100644 internal/utils/utils.go create mode 100644 internal/validate/validate.go delete mode 100644 lbt_80_over_128.yml rename AS_920_923.yml => legacy/AS_920_923.yml (100%) rename AS_920_923_TTN_AU.yml => legacy/AS_920_923_TTN_AU.yml (100%) rename AS_920_923_TTN_JP_1.yml => legacy/AS_920_923_TTN_JP_1.yml (100%) rename AS_920_923_TTN_JP_1_LAND_MOBILE.yml => legacy/AS_920_923_TTN_JP_1_LAND_MOBILE.yml (100%) rename AS_920_923_TTN_JP_2.yml => legacy/AS_920_923_TTN_JP_2.yml (100%) rename AS_920_923_TTN_JP_3.yml => legacy/AS_920_923_TTN_JP_3.yml (100%) rename AS_920_923_TTN_JP_3_LAND_MOBILE.yml => legacy/AS_920_923_TTN_JP_3_LAND_MOBILE.yml (100%) rename AS_923.yml => legacy/AS_923.yml (100%) rename AS_923_2.yml => legacy/AS_923_2.yml (100%) rename AS_923_3.yml => legacy/AS_923_3.yml (100%) rename AS_923_4.yml => legacy/AS_923_4.yml (100%) rename AS_923_925.yml => legacy/AS_923_925.yml (100%) rename AS_923_925_TTN_AU.yml => legacy/AS_923_925_TTN_AU.yml (100%) rename AU_915_928_FSB_1.yml => legacy/AU_915_928_FSB_1.yml (100%) rename AU_915_928_FSB_2.yml => legacy/AU_915_928_FSB_2.yml (100%) rename AU_915_928_FSB_3.yml => legacy/AU_915_928_FSB_3.yml (100%) rename AU_915_928_FSB_4.yml => legacy/AU_915_928_FSB_4.yml (100%) rename AU_915_928_FSB_5.yml => legacy/AU_915_928_FSB_5.yml (100%) rename AU_915_928_FSB_6.yml => legacy/AU_915_928_FSB_6.yml (100%) rename AU_915_928_FSB_7.yml => legacy/AU_915_928_FSB_7.yml (100%) rename AU_915_928_FSB_8.yml => legacy/AU_915_928_FSB_8.yml (100%) rename CN_470_510_FSB_1.yml => legacy/CN_470_510_FSB_1.yml (100%) rename CN_470_510_FSB_11.yml => legacy/CN_470_510_FSB_11.yml (100%) rename EU_433.yml => legacy/EU_433.yml (100%) rename EU_863_870.yml => legacy/EU_863_870.yml (100%) rename EU_863_870_ROAMING_DRAFT.yml => legacy/EU_863_870_ROAMING_DRAFT.yml (100%) create mode 100644 legacy/EU_863_870_TTN.yml rename IN_865_867.yml => legacy/IN_865_867.yml (100%) rename ISM_2400_3CH_DRAFT2.yml => legacy/ISM_2400_3CH_DRAFT2.yml (100%) rename KR_920_923_TTN.yml => legacy/KR_920_923_TTN.yml (100%) rename MA_869_870_DRAFT.yml => legacy/MA_869_870_DRAFT.yml (100%) rename RU_864_870_TTN.yml => legacy/RU_864_870_TTN.yml (100%) rename US_902_928_FSB_1.yml => legacy/US_902_928_FSB_1.yml (100%) rename US_902_928_FSB_2.yml => legacy/US_902_928_FSB_2.yml (100%) rename US_902_928_FSB_3.yml => legacy/US_902_928_FSB_3.yml (100%) rename US_902_928_FSB_4.yml => legacy/US_902_928_FSB_4.yml (100%) rename US_902_928_FSB_5.yml => legacy/US_902_928_FSB_5.yml (100%) rename US_902_928_FSB_6.yml => legacy/US_902_928_FSB_6.yml (100%) rename US_902_928_FSB_7.yml => legacy/US_902_928_FSB_7.yml (100%) rename US_902_928_FSB_8.yml => legacy/US_902_928_FSB_8.yml (100%) create mode 100644 legacy/frequency-plans.yml create mode 100644 schema.json diff --git a/README.md b/README.md index aeeea67..9380d28 100644 --- a/README.md +++ b/README.md @@ -8,79 +8,134 @@ Frequency plans are defined for a band. Bands are specified by the LoRa Alliance ## File Format -Frequency plan are defined in YAML files. Most settings in the frequency plan are optional. When not specifying optional settings, the band defaults are used. +Frequency plan are defined in YAML files. Most settings in the frequency plan are optional. When not specifying optional settings, the band defaults are used. The definitions are split up into gateway and end-device plans: +gateways: ```yml -band-id: BAND_ID # ID of the band (needs to match band-id in the index) +band-id: BAND_ID # ID of the band (needs to match band-id in the index) sub-bands: -- min-frequency: 868000000 # Minimum frequency (Hz, inclusive) - max-frequency: 868600000 # Maximum frequency (Hz, inclusive) - duty-cycle: 0.01 # Duty cycle for this sub-band (optional; default: 1) - max-eirp: 16.15 # Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp) -uplink-channels: # List of uplink channels (zero indexed) -- frequency: 868100000 # Frequency (Hz) - min-data-rate: 0 # Mininum data rate index - max-data-rate: 5 # Maximum data rate index - radio: 0 # Radio index (see below) -downlink-channels: # List of downlink channels (zero indexed) -- frequency: 868100000 - min-data-rate: 0 - max-data-rate: 5 +- min-frequency: 868000000 # Minimum frequency (Hz, inclusive) + max-frequency: 868600000 # Maximum frequency (Hz, inclusive) + duty-cycle: 0.01 # Duty cycle for this sub-band (optional; default: 1) + max-eirp: 16.15 # Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp) +channels: # List of channels (zero indexed) +- uplink-frequency: 868100000 # Uplink frequency (Hz) + downlink-frequency: 868100000 # Downlink frequency (Hz) (optional) + min-data-rate: SF7BW125 # Minimum data rate + max-data-rate: SF11BW125 # Maximum data rate + radio: 0 # Radio index (see below) +lora-standard-channel: # LoRa standard channel (optional) + frequency: 863000000 + data-rate: SF7BW125 + radio: 0 +fsk-channel: # FSK channel (optional) + frequency: 868800000 + data-rate: SF7BW125 radio: 0 -lora-standard-channel: # LoRa standard channel (optional) +time-off-air: # Time-off-air (optional) + fraction: 0.1 # Minimum fraction of the emission time (optional) + duration: 1s # Minimum duration (optional) +dwell-time: # Dwell time (optional) + uplinks: true # Enabled for uplink (optional) + downlinks: true # Enabled for downlink (optional) + duration: 1s # Duration (optional) +listen-before-talk: # Listen-before-talk (optional) + rssi-offset: 0 # RSSI offset (dbm) + rssi-target: -80 # RSSI target (dbm) + scan-time: 128000 # Scan time (nanoseconds) +radios: # Radio configuration (zero indexed, optional) +- enable: true # Enable the radio + chip-type: SX1257 # Chip type + frequency: 867500000 # Frequency (Hz) + rssi-offset: -166 # RSSI offset (dbm) + tx: # Radio transmission configuration (optional) + min-frequency: 863000000 # Minimum frequency (Hz) + max-frequency: 867000000 # Maximum frequency (Hz) + notch-frequency: 129000 # Notch frequency 126000..250000 (Hz) +clock-source: 0 # Gateway clock source +max-eirp: 29.15 # Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default) +``` + +end-device: +```yml +band-id: BAND_ID # ID of the band (needs to match band-id in the index) +sub-bands: +- min-frequency: 868000000 # Minimum frequency (Hz, inclusive) + max-frequency: 868600000 # Maximum frequency (Hz, inclusive) + duty-cycle: 0.01 # Duty cycle for this sub-band (optional; default: 1) + max-eirp: 16.15 # Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp) +channels: # List of channels (zero indexed) +- uplink-frequency: 868100000 # Uplink frequency (Hz) + downlink-frequency: 868100000 # Downlink frequency (Hz) + min-data-rate: 0 # Minimum data rate index + max-data-rate: 5 # Maximum data rate index + default: false # Channel is defined by RP +lora-standard-channel: # LoRa standard channel (optional) frequency: 863000000 data-rate: 6 - radio: 0 -fsk-channel: # FSK channel (optional) +fsk-channel: # FSK channel (optional) frequency: 868800000 data-rate: 7 - radio: 0 -time-off-air: # Time-off-air (optional) - fraction: 0.1 # Minimum fraction of the emission time (optional) - duration: 1s # Minimum duration (optional) -dwell-time: # Dwell time (optional) - uplinks: true # Enabled for uplink (optional) - downlinks: true # Enabled for downlink (optional) - duration: 1s # Duration (optional) -listen-before-talk: # Listen-before-talk (optional) - rssi-offset: 0 # RSSI offset (dbm) - rssi-target: -80 # RSSI target (dbm) - scan-time: 128000 # Scan time (nanoseconds) -radios: # Radio configuration (zero indexed, optional) -- enable: true # Enable the radio - chip-type: SX1257 # Chip type - frequency: 867500000 # Frequency (Hz) - rssi-offset: -166 # RSSI offset (dbm) - tx: # Radio transmission configuration (optional) - min-frequency: 863000000 # Minimum frequency (Hz) - max-frequency: 867000000 # Maximum frequency (Hz) - notch-frequency: 129000 # Notch frequency 126000..250000 (Hz) -clock-source: 0 # Gateway clock source -ping-slot: # Class B ping slot settings (optional) +time-off-air: # Time-off-air (optional) + fraction: 0.1 # Minimum fraction of the emission time (optional) + duration: 1s # Minimum duration (optional) +dwell-time: # Dwell time (optional) + uplinks: true # Enabled for uplink (optional) + downlinks: true # Enabled for downlink (optional) + duration: 1s # Duration (optional) +listen-before-talk: # Listen-before-talk (optional) + rssi-offset: 0 # RSSI offset (dbm) + rssi-target: -80 # RSSI target (dbm) + scan-time: 128000 # Scan time (nanoseconds) +ping-slot: # Class B ping slot settings (optional) frequency: 869525000 min-data-rate: 0 max-data-rate: 5 - radio: 0 -ping-slot-default-data-rate: 3 # Default data rate index of class B ping slot (optional) -rx2-channel: # Rx2 channel (optional) +ping-slot-default-data-rate: 3 # Default data rate index of class B ping slot (optional) +rx2-channel: # Rx2 channel (optional) frequency: 869525000 min-data-rate: 0 max-data-rate: 5 - radio: 0 -rx2-default-data-rate: 0 # Default data rate index of Rx2 (optional) -max-eirp: 29.15 # Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default) +rx2-default-data-rate: 0 # Default data rate index of Rx2 (optional) +max-eirp: 29.15 # Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default) ``` -An index of frequency plans is in `frequency-plans.yml`: +An index of frequency plans is in `frequency-plans.yml`. This can take two forms depending on if it is a root frequency plan definition, or if it is a plan definition that modifies an existing plan. +Base plan definition +```yml +end-device-descriptions: + - id: EU_863_870 # ID of the frequency plan + band-id: EU_863_870 # ID of the LoRaWAN band (needs to match band-id in the definition) + name: Region 863-870 MHz # Name of the frequency plan, ending with frequency ranges + description: Default frequency plan for Europe # Description of the frequency plan + base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868, 915 or 2450) + country-codes: [] # List of 2-digit ISO country codes for countries where this plan can be used + file: EU_863_870.yml # Filename of the plan residing in the `end-device` folder + endorsed: true + +gateway-descriptions: + - id: EU_863_870 # ID of the frequency plan + band-id: EU_863_870 # ID of the LoRaWAN band (needs to match band-id in the definition) + name: Region 863-870 MHz # Name of the frequency plan, ending with frequency ranges + description: Default frequency plan for Europe # Description of the frequency plan + base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868, 915 or 2450) + country-codes: [] # List of 2-digit ISO country codes for countries where this plan can be used + file: EU_863_870.yml # Filename of the plan residing in the `gateway` folder + endorsed: true +``` + +Inherited definition ```yml - id: EU_863_870_TTN # ID of the frequency plan band-id: EU_863_870 # ID of the LoRaWAN band (needs to match band-id in the definition) base-id: EU_863_870 # ID that this frequency plan extends (refers to id of another frequency plan) name: Region 863-870 MHz # Name of the frequency plan, ending with frequency ranges - base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868 or 915) + description: Default frequency plan for Europe # Description of the frequency plan + base-frequency: 868 # Base frequency in MHz for hardware support (433, 470, 868, 915 or 2450) country-codes: [] # List of 2-digit ISO country codes for countries where this plan can be used - file: EU_863_870.yml # File of the frqeuency plan definition + modifiers: [] # Filename of the modifier residing in the respective `end-device/modifiers` or `gateway/modifiers` folder. + endorsed: true ``` > Country codes are taken from the [LoRaWAN Regional Parameters 1.0.1 Specification](https://lora-alliance.org/sites/default/files/2020-06/rp_2-1.0.1.pdf) @@ -91,6 +146,19 @@ Thank you for your interest in building this thing together with us. We're reall The Things Stack uses the `github.com/TheThingsNetwork/lorawan-frequency-plans` as default source for fetching frequency plans. Therefore, contributing to this open source repository makes frequency plans automatically available to Stack deployments with default settings. You can contribute by submitting pull requests. Are you new to GitHub? That's great! [Read here about pull requests](https://help.github.com/articles/about-pull-requests/). Please also use the editor settings as defined in `.editorconfig`. +There are json schemas available for all configuration files. These define files structure and make it easier to fill them in. To enable them in `VSCode` add the following to your `settings.json` file. +```json +{ + "yaml.schemas": { + "schema.json": "frequency-plans.yml", + "end-device/schema.json": "end-device/*.yml", + "end-device/modifiers/schema.json": "end-device/modifiers/*.yml", + "gateway/schema.json": "gateway/*.yml", + "gateway/modifiers/schema.json": "gateway/modifiers/*.yml" + } +} +``` + ### Local Regulations When submitting a new frequency plan or making changes to an existing frequency plan, please make sure that the band is allowed to be used in the concerning region and that settings respect regional regulations. When submitting a pull request for a new region, please upload or link to a document that describes the local regulations. diff --git a/disable_dwell_time.yml b/disable_dwell_time.yml deleted file mode 100644 index 478b07e..0000000 --- a/disable_dwell_time.yml +++ /dev/null @@ -1,3 +0,0 @@ -dwell-time: - uplinks: false - downlinks: false diff --git a/docs/frequency-plans.md b/docs/frequency-plans.md new file mode 100644 index 0000000..d20bfaa --- /dev/null +++ b/docs/frequency-plans.md @@ -0,0 +1,26 @@ +# LoRaWAN Frequency Plans for The Things Stack + +# End device frequency plans + +## [`EU_863_870`](../end-device/EU_863_870.yml): Europe 863-870 MHz + +>> Default frequency plan for Europe + +![EU_863_870](images/end-device/EU_863_870.svg) + +## `EU_863_870_TTN`: Europe 863-870 MHz +Based on [EU_863_870](##EU_863_870) and modified by [rx2_default_data_rata_3.yml](../end-device/modifiers/rx2_default_data_rata_3.yml) + + +>> TTN Community Network frequency plan for Europe, using SF9 for RX2 + +![EU_863_870_TTN](images/end-device/EU_863_870_TTN.svg) + +# Gateway frequency plans + + +## [`EU_863_870`](../gateway/EU_863_870.yml): Europe 863-870 MHz + +>> Default frequency plan for Europe + +![EU_863_870](images/gateway/EU_863_870.svg) diff --git a/docs/images/end-device/EU_863_870.svg b/docs/images/end-device/EU_863_870.svg new file mode 100644 index 0000000..1c36ed7 --- /dev/null +++ b/docs/images/end-device/EU_863_870.svg @@ -0,0 +1,271 @@ +\n867.1867.3867.5867.7867.9868.1868.3868.5868.8DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00868.1868.1868.3868.3868.5868.5867.1867.1867.3867.3867.5867.5867.7867.7867.9867.9868.8 (FSK)EU_863_870 \ No newline at end of file diff --git a/docs/images/end-device/EU_863_870_TTN.svg b/docs/images/end-device/EU_863_870_TTN.svg new file mode 100644 index 0000000..e3684ea --- /dev/null +++ b/docs/images/end-device/EU_863_870_TTN.svg @@ -0,0 +1,271 @@ +\n867.1867.3867.5867.7867.9868.1868.3868.5868.8DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00868.1868.1868.3868.3868.5868.5867.1867.1867.3867.3867.5867.5867.7867.7867.9867.9868.8 (FSK)EU_863_870_TTN \ No newline at end of file diff --git a/docs/images/gateway/EU_863_870.svg b/docs/images/gateway/EU_863_870.svg new file mode 100644 index 0000000..f516e19 --- /dev/null +++ b/docs/images/gateway/EU_863_870.svg @@ -0,0 +1,304 @@ +\n867.038867.1867.3867.5867.7867.9867.962868.038868.1868.3868.5868.8868.962DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00868.1868.1868.3868.3868.5868.5867.1867.1867.3867.3867.5867.5867.7867.7867.9867.9868.3 (Std)868.8 (FSK)Radio 0: 867.5Radio 1: 868.5EU_863_870 \ No newline at end of file diff --git a/enable_dwell_time_400ms.yml b/enable_dwell_time_400ms.yml deleted file mode 100644 index 53301f2..0000000 --- a/enable_dwell_time_400ms.yml +++ /dev/null @@ -1,4 +0,0 @@ -dwell-time: - uplinks: true - downlinks: true - duration: 400ms diff --git a/end-device/EU_863_870.yml b/end-device/EU_863_870.yml new file mode 100644 index 0000000..a44d8b9 --- /dev/null +++ b/end-device/EU_863_870.yml @@ -0,0 +1,46 @@ +band-id: EU_863_870 +sub-bands: + - min-frequency: 867000000 # TODO: Verify this + max-frequency: 869000000 +channels: +- uplink-frequency: 868100000 + downlink-frequency: 868100000 + min-data-rate: 0 + max-data-rate: 5 + default: true +- uplink-frequency: 868300000 + downlink-frequency: 868300000 + min-data-rate: 0 + max-data-rate: 5 + default: true +- uplink-frequency: 868500000 + downlink-frequency: 868500000 + min-data-rate: 0 + max-data-rate: 5 + default: true +- uplink-frequency: 867100000 + downlink-frequency: 867100000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 867300000 + downlink-frequency: 867300000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 867500000 + downlink-frequency: 867500000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 867700000 + downlink-frequency: 867700000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 867900000 + downlink-frequency: 867900000 + min-data-rate: 0 + max-data-rate: 5 +lora-standard-channel: + frequency: 868300000 + data-rate: 6 +fsk-channel: + frequency: 868800000 + data-rate: 7 diff --git a/EU_863_870_TTN.yml b/end-device/modifiers/rx2_default_data_rata_3.yml similarity index 100% rename from EU_863_870_TTN.yml rename to end-device/modifiers/rx2_default_data_rata_3.yml diff --git a/end-device/modifiers/schema.json b/end-device/modifiers/schema.json new file mode 100644 index 0000000..8d39039 --- /dev/null +++ b/end-device/modifiers/schema.json @@ -0,0 +1,204 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "ping-slot": { + "type": "object", + "description": "Class B ping slot settings (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "ping-slot-default-data-rate": { + "type": "integer", + "description": "Default data rate index of class B ping slot (optional)" + }, + "rx2-channel": { + "type": "object", + "description": "Rx2 channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "rx2-default-data-rate": { + "type": "integer", + "description": "Default data rate index of Rx2 (optional)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + } +} diff --git a/end-device/schema.json b/end-device/schema.json new file mode 100644 index 0000000..205e457 --- /dev/null +++ b/end-device/schema.json @@ -0,0 +1,210 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "band-id": { + "type": "string", + "description": "ID of the band (needs to match band-id in the index)", + "enum": ["AS_923","AS_923_2","AS_923_3","AS_923_4","AU_915_928","CN_470_510","CN_470_510_20_A","CN_470_510_20_B","CN_470_510_26_A","CN_470_510_26_B","CN_779_787","EU_433","EU_863_870","IN_865_867","ISM_2400","KR_920_923","MA_869_870_DRAFT","RU_864_870","US_902_928"] + }, + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "ping-slot": { + "type": "object", + "description": "Class B ping slot settings (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "ping-slot-default-data-rate": { + "type": "integer", + "description": "Default data rate index of class B ping slot (optional)" + }, + "rx2-channel": { + "type": "object", + "description": "Rx2 channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "rx2-default-data-rate": { + "type": "integer", + "description": "Default data rate index of Rx2 (optional)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + }, + "required": ["band-id", "sub-bands", "channels"] +} diff --git a/frequency-plans.go b/frequency-plans.go new file mode 100644 index 0000000..86b281d --- /dev/null +++ b/frequency-plans.go @@ -0,0 +1,36 @@ +package main + +import ( + "flag" + "log" + + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/docs" + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/schema" + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/validate" +) + +var ( + generateDocs = flag.Bool("docs", false, "Generate docs for the frequency-plans.") + generateSchema = flag.Bool("schema", false, "Generate the `schema.json` file.") +) + +func main() { + flag.Parse() + + if err := validate.Validate(); err != nil { + log.Fatal(err) + } + + if *generateDocs { + // TODO: update doc generation to support end-device and gateway folders + new structure + if err := docs.Generate("./frequency-plans.yml", "./docs"); err != nil { + log.Fatal(err) + } + } + + if *generateSchema { + if err := schema.Generate(); err != nil { + log.Fatal(err) + } + } +} diff --git a/frequency-plans.yml b/frequency-plans.yml index 255fadd..fbce7d6 100644 --- a/frequency-plans.yml +++ b/frequency-plans.yml @@ -1,649 +1,29 @@ +end-device-descriptions: - id: EU_863_870 band-id: EU_863_870 - name: Europe 863-870 MHz (SF12 for RX2) + name: Europe 863-870 MHz description: Default frequency plan for Europe base-frequency: 868 country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] file: EU_863_870.yml + endorsed: false - id: EU_863_870_TTN band-id: EU_863_870 - name: Europe 863-870 MHz (SF9 for RX2 - recommended) + base-id: EU_863_870 + name: Europe 863-870 MHz description: TTN Community Network frequency plan for Europe, using SF9 for RX2 base-frequency: 868 - base-id: EU_863_870 country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] - file: EU_863_870_TTN.yml + modifiers: [rx2_default_data_rata_3.yml] + endorsed: true -- id: EU_863_870_ROAMING_DRAFT +gateway-descriptions: +- id: EU_863_870 band-id: EU_863_870 - name: Europe 863-870 MHz, 6 channels for roaming (Draft) - description: European 6 channel plan used by major operators to support LoRaWAN Passive Roaming + name: Europe 863-870 MHz + description: Default frequency plan for Europe base-frequency: 868 - base-id: EU_863_870 country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] - file: EU_863_870_ROAMING_DRAFT.yml - -- id: EU_433 - band-id: EU_433 - name: Europe 433 MHz (ITU region 1) - description: Default frequency plan for worldwide 433MHz - base-frequency: 433 - file: EU_433.yml - -- id: US_902_928_FSB_1 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 1 - description: Default frequency plan for the United States and Canada, using sub-band 1 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_1.yml - -- id: US_902_928_FSB_2 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 2 (used by TTN) - description: TTN Community Network frequency plan for the United States and Canada, using sub-band 2 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_2.yml - -- id: US_902_928_FSB_3 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 3 - description: Default frequency plan for the United States and Canada, using sub-band 3 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_3.yml - -- id: US_902_928_FSB_4 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 4 - description: Default frequency plan for the United States and Canada, using sub-band 4 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_4.yml - -- id: US_902_928_FSB_5 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 5 - description: Default frequency plan for the United States and Canada, using sub-band 5 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_5.yml - -- id: US_902_928_FSB_6 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 6 - description: Default frequency plan for the United States and Canada, using sub-band 6 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_6.yml - -- id: US_902_928_FSB_7 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 7 - description: Default frequency plan for the United States and Canada, using sub-band 7 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_7.yml - -- id: US_902_928_FSB_8 - band-id: US_902_928 - name: United States 902-928 MHz, FSB 8 - description: Default frequency plan for the United States and Canada, using sub-band 8 - base-frequency: 915 - country-codes: [ca, cr, ec, gy, mx, pa, pr, us] - file: US_902_928_FSB_8.yml - -- id: AU_915_928_FSB_1 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 1 - description: Default frequency plan for Australia, using sub-band 1 - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_1.yml - -- id: AU_915_928_FSB_2 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 2 (used by TTN) - description: TTN Community Network frequency plan for Australia, using sub-band 2 - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_2.yml - -- id: AU_915_928_FSB_3 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 3 - description: Default frequency plan for Australia, using sub-band 3 - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_3.yml - -- id: AU_915_928_FSB_4 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 4 - description: Default frequency plan for Australia, using sub-band 4 - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_4.yml - -- id: AU_915_928_FSB_5 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 5 - description: Default frequency plan for Australia, using sub-band 5 - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_5.yml - -- id: AU_915_928_FSB_6 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 6 - description: Frequency plan for Australia, using sub-band 6, which overlaps with Asia 923-925 MHz - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_6.yml - -- id: AU_915_928_FSB_7 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 7 - description: Default frequency plan for Australia, using sub-band 7 - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_7.yml - -- id: AU_915_928_FSB_8 - band-id: AU_915_928 - name: Australia 915-928 MHz, FSB 8 - description: Default frequency plan for Australia, using sub-band 8 - base-frequency: 915 - country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] - file: AU_915_928_FSB_8.yml - -- id: CN_470_510_FSB_1 - band-id: CN_470_510 - name: China 470-510 MHz, FSB 1 - description: Default frequency plan for China, using sub-band 1 - base-frequency: 470 - country-codes: [cn] - file: CN_470_510_FSB_1.yml - -- id: CN_470_510_FSB_11 - band-id: CN_470_510 - name: China 470-510 MHz, FSB 11 (used by TTN) - description: TTN Community Network frequency plan for China, using sub-band 11 - base-frequency: 470 - country-codes: [cn] - file: CN_470_510_FSB_11.yml - -- id: AS_920_923 - band-id: AS_920_923 - name: Asia 920-923 MHz - description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz - base-frequency: 915 - country-codes: [my, sg] - file: AS_920_923.yml - -- id: AS_920_923_LBT - band-id: AS_920_923 - name: Asia 920-923 MHz with LBT - description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz with listen-before-talk - base-frequency: 915 - base-id: AS_920_923 - country-codes: [jp, my, sg] - file: lbt_80_over_128.yml - -- id: AS_920_923_TTN_JP_1 - name: Japan 920-923 MHz with LBT (channels 31-38) - description: Frequency plan for Japan, using continuous frequencies up to 923.4MHz with LBT. - base-frequency: 915 - country-codes: [jp] - file: AS_920_923_TTN_JP_1.yml - -- id: AS_920_923_TTN_JP_1_LAND_MOBILE - name: Japan 920-923 MHz with LBT (channels 31-38), Max EIRP 27 dBm - description: | - Frequency plan for Japanese land mobile station, using continuous frequencies up to 923.4MHz with MAX EIRP 27 dBm and LBT. - (note) A user who installs land mobile station in Japan must apply to Japanese Ministry of Internal Affairs and Communications. - base-frequency: 915 - base-id: AS_920_923_TTN_JP_1 - country-codes: [jp] - file: AS_920_923_TTN_JP_1_LAND_MOBILE.yml - -- id: AS_920_923_TTN_JP_2 - name: Japan 920-923 MHz with LBT (channels 24-27 and 35-38) - description: Frequency plan for Japan, using top and bottom frequencies ≤ 923.4 MHz with LBT. - base-frequency: 915 - country-codes: [jp] - file: AS_920_923_TTN_JP_2.yml - -- id: AS_920_923_TTN_JP_3 - name: Japan 920-923 MHz with LBT (channels 24-31) - description: Frequency plan for Japan (16 channels), using continuous frequencies from 920.6 MHz, expected to be used with AS_920_923_TTN_JP_1. - base-frequency: 915 - country-codes: [jp] - file: AS_920_923_TTN_JP_3.yml - -- id: AS_920_923_TTN_JP_3_LAND_MOBILE - name: Japan 920-923 MHz with LBT (channels 24-31), Max EIRP 27 dBm - description: | - Frequency plan for Japanese land mobile station (16 channels), using continuous frequencies from 920.6 MHz with MAX EIRP 27 dBm and LBT, expected to be used with AS_920_923_TTN_JP_1. - (note) A user who installs land mobile station in Japan must apply to Japanese Ministry of Internal Affairs and Communications. - base-frequency: 915 - base-id: AS_920_923_TTN_JP_3 - country-codes: [jp] - file: AS_920_923_TTN_JP_3_LAND_MOBILE.yml - -- id: AS_923 - band-id: AS_923 - name: Asia 915-928 MHz (AS923 Group 1) with only default channels - description: Compatibility frequency plan for Asian countries with common channels in the 923.0-923.5 MHz sub-band - base-frequency: 915 - file: AS_923.yml - -- id: AS_923_2 - band-id: AS_923_2 - name: Asia 920-923 MHz (AS923 Group 2) with only default channels - description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band - base-frequency: 915 - file: AS_923_2.yml - -- id: AS_923_3 - band-id: AS_923_3 - name: Asia 915-921 MHz (AS923 Group 3) with only default channels - description: Compatibility frequency plan for Asian countries with common channels in the 916.5-917.0 MHz sub-band - base-frequency: 915 - file: AS_923_3.yml - -- id: AS_923_4 - band-id: AS_923_4 - name: Asia 917-920 MHz (AS923 Group 4) with only default channels - description: Compatibility frequency plan for Asian countries with common channels in the 917.3-917.5 MHz sub-band - base-frequency: 915 - file: AS_923_4.yml - -- id: AS_923_NDT - band-id: AS_923 - name: Asia 915-928 MHz (AS923 Group 1) with only default channels and dwell time disabled - description: Compatibility frequency plan for Asian countries with common channels in the 923.0-923.5 MHz sub-band and dwell time disabled - base-frequency: 915 - base-id: AS_923 - file: disable_dwell_time.yml - -- id: AS_923_DT - band-id: AS_923 - name: Asia 915-928 MHz (AS923 Group 1) with only default channels and dwell time enabled - description: Compatibility frequency plan for Asian countries with common channels in the 923.0-923.5 MHz sub-band and dwell time enabled - base-frequency: 915 - base-id: AS_923 - file: enable_dwell_time_400ms.yml - -- id: AS_923_2_NDT - band-id: AS_923_2 - name: Asia 920-923 MHz (AS923 Group 2) with only default channels and dwell time disabled - description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time disabled - base-frequency: 915 - base-id: AS_923_2 - file: disable_dwell_time.yml - -- id: AS_923_2_DT - band-id: AS_923_2 - name: Asia 920-923 MHz (AS923 Group 2) with only default channels and dwell time enabled - description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time enabled - base-frequency: 915 - base-id: AS_923_2 - file: enable_dwell_time_400ms.yml - -- id: AS_923_3_NDT - band-id: AS_923_3 - name: Asia 920-923 MHz (AS923 Group 3) with only default channels and dwell time disabled - description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time disabled - base-frequency: 915 - base-id: AS_923_3 - file: disable_dwell_time.yml - -- id: AS_923_3_DT - band-id: AS_923_3 - name: Asia 920-923 MHz (AS923 Group 3) with only default channels and dwell time enabled - description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time enabled - base-frequency: 915 - base-id: AS_923_3 - file: enable_dwell_time_400ms.yml - -- id: AS_923_4_NDT - band-id: AS_923_4 - name: Asia 920-923 MHz (AS923 Group 4) with only default channels and dwell time disabled - description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time disabled - base-frequency: 915 - base-id: AS_923_4 - file: disable_dwell_time.yml - -- id: AS_923_4_DT - band-id: AS_923_4 - name: Asia 920-923 MHz (AS923 Group 4) with only default channels and dwell time enabled - description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time enabled - base-frequency: 915 - base-id: AS_923_4 - file: enable_dwell_time_400ms.yml - -- id: AS_923_925 - band-id: AS_923_925 - name: Asia 923-925 MHz - description: TTN Community Network frequency plan for Asian countries, using frequencies ≥ 923 MHz - base-frequency: 915 - country-codes: [bn, kh, hk, id, la, tw, th, vn] - file: AS_923_925.yml - -- id: AS_923_925_LBT - band-id: AS_923_925 - name: Asia 923-925 MHz with LBT - description: TTN Community Network frequency plan for Asian countries, using frequencies ≥ 923 MHz with listen-before-talk - base-frequency: 915 - base-id: AS_923_925 - country-codes: [bn, kh, hk, id, la, tw, th, vn] - file: lbt_80_over_128.yml - -- id: AS_920_923_TTN_AU - band-id: AS_923_925 - name: Asia 920-923 MHz (used by TTN Australia) - description: TTN Community Network frequency plan for Asia 920-923 MHz in Australia - base-frequency: 915 - base-id: AS_920_923 - country-codes: [au] - file: AS_920_923_TTN_AU.yml - -- id: AS_923_925_TTN_AU - band-id: AS_923_925 - name: Asia 923-925 MHz (used by TTN Australia - secondary channels) - description: TTN Community Network frequency plan for Asia 923-925 MHz in Australia. Secondary channels for 16 channel gateways. - base-frequency: 915 - base-id: AS_923_925 - country-codes: [au] - file: AS_923_925_TTN_AU.yml - -- id: KR_920_923_TTN - band-id: KR_920_923 - name: South Korea 920-923 MHz - description: TTN Community Network frequency plan for South Korea - base-frequency: 915 - country-codes: [kr] - file: KR_920_923_TTN.yml - -- id: MA_869_870_DRAFT - band-id: MA_869_870_DRAFT - name: Morocco 869-870 MHz - description: Draft frequency plan for Morocco, with 4 channels - base-frequency: 868 - country-codes: [ma] - file: MA_869_870_DRAFT.yml - -- id: IN_865_867 - band-id: IN_865_867 - name: India 865-867 MHz - description: Default frequency plan for India - base-frequency: 868 - country-codes: [in] - file: IN_865_867.yml - -- id: RU_864_870_TTN - band-id: RU_864_870 - name: Russia 864-870 MHz - description: TTN Community Network frequency plan for Russia - base-frequency: 868 - country-codes: [ru] - file: RU_864_870_TTN.yml - -- id: ISM_2400_3CH_DRAFT2 - band-id: ISM_2400 - name: LoRa 2.4 GHz with 3 channels (Draft 2) - description: Global 3 channel plan for LoRa 2.4 GHz (Draft 2) - base-frequency: 2450 - country-codes: - [ - af, - ax, - al, - dz, - as, - ad, - ao, - ai, - aq, - ag, - ar, - am, - aw, - au, - at, - az, - bs, - bh, - bd, - bb, - by, - be, - bz, - bj, - bm, - bt, - bo, - ba, - bw, - bv, - br, - io, - bn, - bg, - bf, - bi, - kh, - cm, - ca, - cv, - ky, - cf, - td, - cl, - cn, - cx, - cc, - co, - km, - cg, - cd, - ck, - cr, - ci, - hr, - cu, - cy, - cz, - dk, - dj, - dm, - do, - ec, - eg, - sv, - gq, - er, - ee, - et, - fk, - fo, - fj, - fi, - fr, - gf, - pf, - tf, - ga, - gm, - ge, - de, - gh, - gi, - gr, - gl, - gd, - gp, - gu, - gt, - gg, - gn, - gw, - gy, - ht, - hm, - va, - hn, - hk, - hu, - is, - in, - id, - ir, - iq, - ie, - im, - il, - it, - jm, - jp, - je, - jo, - kz, - ke, - ki, - kp, - kr, - kw, - kg, - la, - lv, - lb, - ls, - lr, - ly, - li, - lt, - lu, - mo, - mk, - mg, - mw, - my, - mv, - ml, - mt, - mh, - mq, - mr, - mu, - yt, - mx, - fm, - md, - mc, - mn, - me, - ms, - ma, - mz, - mm, - na, - nr, - np, - nl, - an, - nc, - nz, - ni, - ne, - ng, - nu, - nf, - mp, - no, - om, - pk, - pw, - ps, - pa, - pg, - py, - pe, - ph, - pn, - pl, - pt, - pr, - qa, - re, - ro, - ru, - rw, - bl, - sh, - kn, - lc, - mf, - pm, - vc, - ws, - sm, - st, - sa, - sn, - rs, - sc, - sl, - sg, - sk, - si, - sb, - so, - za, - gs, - es, - lk, - sd, - sr, - sj, - sz, - se, - ch, - sy, - tw, - tj, - tz, - th, - tl, - tg, - tk, - to, - tt, - tn, - tr, - tm, - tc, - tv, - ug, - ua, - ae, - gb, - us, - um, - uy, - uz, - vu, - ve, - vn, - vg, - vi, - wf, - eh, - ye, - zm, - zw, - ] - file: ISM_2400_3CH_DRAFT2.yml + file: EU_863_870.yml + endorsed: false diff --git a/gateway/EU_863_870.yml b/gateway/EU_863_870.yml new file mode 100644 index 0000000..1a81d97 --- /dev/null +++ b/gateway/EU_863_870.yml @@ -0,0 +1,74 @@ +band-id: EU_863_870 +sub-bands: + - min-frequency: 867000000 # TODO: Verify this + max-frequency: 869000000 +channels: +- uplink-frequency: 868100000 + downlink-frequency: 868100000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +- uplink-frequency: 868300000 + downlink-frequency: 868300000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +- uplink-frequency: 868500000 + downlink-frequency: 868500000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +- uplink-frequency: 867100000 + downlink-frequency: 867100000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +- uplink-frequency: 867300000 + downlink-frequency: 867300000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +- uplink-frequency: 867500000 + downlink-frequency: 867500000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +- uplink-frequency: 867700000 + downlink-frequency: 867700000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +- uplink-frequency: 867900000 + downlink-frequency: 867900000 + min-data-rate: SF12BW125 + max-data-rate: SF7BW125 + radio: 0 + default: true +lora-standard-channel: + frequency: 868300000 + data-rate: SF7BW250 + radio: 1 +fsk-channel: + frequency: 868800000 + data-rate: FSK50 # TODO: verify + radio: 1 +radios: +- enable: true + chip-type: SX1257 + frequency: 867500000 + rssi-offset: -166 + tx: + min-frequency: 863000000 + max-frequency: 870000000 +- enable: true + chip-type: SX1257 + frequency: 868500000 + rssi-offset: -166 +clock-source: 1 diff --git a/gateway/modifiers/schema.json b/gateway/modifiers/schema.json new file mode 100644 index 0000000..db386a6 --- /dev/null +++ b/gateway/modifiers/schema.json @@ -0,0 +1,225 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "string", + "description": "Minimum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "max-data-rate": { + "type": "string", + "description": "Maximum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate", "radio"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["FSK50"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "radios": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable": { + "type": "boolean", + "description": "Enable the radio" + }, + "chip-type": { + "type": "string", + "description": "Chip type", + "enum": ["SX1255", "SX1257"] + }, + "frequency": { + "type": "integer", + "description": "Center frequency [Hz]" + }, + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "tx": { + "type": "object", + "description": "Radio transmission configuration (optional)", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz]" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz]" + }, + "notch-frequency": { + "type": "integer", + "description": "Notch frequency 126000..250000 [Hz] (optional)", + "minimum": 126000, + "maximum": 250000 + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "required": ["enable", "chip-type", "frequency", "rssi-offset"] + } + }, + "clock-source": { + "type": "integer", + "description": "Gateway clock source" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + } +} diff --git a/gateway/schema.json b/gateway/schema.json new file mode 100644 index 0000000..a16ba53 --- /dev/null +++ b/gateway/schema.json @@ -0,0 +1,231 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "band-id": { + "type": "string", + "description": "ID of the band (needs to match band-id in the index)", + "enum": ["AS_923","AS_923_2","AS_923_3","AS_923_4","AU_915_928","CN_470_510","CN_470_510_20_A","CN_470_510_20_B","CN_470_510_26_A","CN_470_510_26_B","CN_779_787","EU_433","EU_863_870","IN_865_867","ISM_2400","KR_920_923","MA_869_870_DRAFT","RU_864_870","US_902_928"] + }, + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "string", + "description": "Minimum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "max-data-rate": { + "type": "string", + "description": "Maximum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate", "radio"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["FSK50"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "radios": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable": { + "type": "boolean", + "description": "Enable the radio" + }, + "chip-type": { + "type": "string", + "description": "Chip type", + "enum": ["SX1255", "SX1257"] + }, + "frequency": { + "type": "integer", + "description": "Center frequency [Hz]" + }, + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "tx": { + "type": "object", + "description": "Radio transmission configuration (optional)", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz]" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz]" + }, + "notch-frequency": { + "type": "integer", + "description": "Notch frequency 126000..250000 [Hz] (optional)", + "minimum": 126000, + "maximum": 250000 + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "required": ["enable", "chip-type", "frequency", "rssi-offset"] + } + }, + "clock-source": { + "type": "integer", + "description": "Gateway clock source" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + }, + "required": ["band-id", "sub-bands", "channels"] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..06b5226 --- /dev/null +++ b/go.mod @@ -0,0 +1,45 @@ +module github.com/TheThingsNetwork/lorawan-frequency-plans + +go 1.19 + +// Use our fork of grpc-gateway. +replace github.com/grpc-ecosystem/grpc-gateway => github.com/TheThingsIndustries/grpc-gateway v1.15.2-gogo + +// But the original grpc-gateway v2. +replace github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3 + +require ( + github.com/wcharczuk/go-chart/v2 v2.1.0 + go.thethings.network/lorawan-stack/v3 v3.22.2 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/TheThingsIndustries/protoc-gen-go-flags v1.0.0 // indirect + github.com/TheThingsIndustries/protoc-gen-go-json v1.4.0 // indirect + github.com/envoyproxy/protoc-gen-validate v0.6.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 // indirect + github.com/gotnospirit/messageformat v0.0.0-20190719172517-c1d0bdacdea2 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.0-00010101000000-000000000000 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/exp v0.0.0-20220706164943-b4a6d9510983 // indirect + golang.org/x/image v0.1.0 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect + golang.org/x/text v0.4.0 // indirect + google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect + google.golang.org/grpc v1.46.2 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d967461 --- /dev/null +++ b/go.sum @@ -0,0 +1,321 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.90.0 h1:MjvSkUq8RuAb+2JLDi5VQmmExRJPUQ3JLCWpRB6fmdw= +cloud.google.com/go/storage v1.16.0 h1:1UwAux2OZP4310YXg5ohqBEpV16Y93uZG4+qOX7K2Kg= +contrib.go.opencensus.io/exporter/prometheus v0.4.0 h1:0QfIkj9z/iVZgK31D9H9ohjjIDApI2GOPScCKwxedbs= +github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= +github.com/Azure/azure-storage-blob-go v0.10.0 h1:evCwGreYo3XLeBV4vSxLbLiYb6e0SzsJiXQVRGsRXxs= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE= +github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/TheThingsIndustries/grpc-gateway v1.15.2-gogo h1:rWB4sbEKoL7xUC9ixUkJOBlPOeF0hcwzXHTISXZM7eA= +github.com/TheThingsIndustries/grpc-gateway v1.15.2-gogo/go.mod h1:fU1VeKM8T+38FAMQNH0zO2BT6grnMyphff4CD9w1DTM= +github.com/TheThingsIndustries/protoc-gen-go-flags v1.0.0 h1:zU7Sp5BES8h9Qi3dXeIhSpTGxrjtaqbzpdIm2L+G+FQ= +github.com/TheThingsIndustries/protoc-gen-go-flags v1.0.0/go.mod h1:nlf5qiiSsW9OQfcSUUqzGv7v8OJg0MhI2rXvTiBf/fw= +github.com/TheThingsIndustries/protoc-gen-go-json v1.4.0 h1:51GcLhX2se5RbQYb+++HTP+7qsrEIa5tdUb9+FPuFhs= +github.com/TheThingsIndustries/protoc-gen-go-json v1.4.0/go.mod h1:7xYRiu4k/ioxM2/nqq2lyXLr4Lj4Nyx7AvYjr7rJSww= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aws/aws-sdk-go v1.42.53 h1:56T04NWcmc0ZVYFbUc6HdewDQ9iHQFlmS6hj96dRjJs= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.3 h1:HkntewfZJ9RofA/FX38zBCeIAqlLDFLbAI6eTpZqFJw= +github.com/envoyproxy/protoc-gen-validate v0.6.3/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 h1:b70jEaX2iaJSPZULSUxKtm73LBfsCrMsIlYCUgNGSIs= +github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976/go.mod h1:ZGQeOwybjD8lkCjIyJfqR5LD2wMVHJ31d6GdPxoTsWY= +github.com/gotnospirit/messageformat v0.0.0-20190719172517-c1d0bdacdea2 h1:yUr520KXfjzq/QTGZ2h+DvEydkyBfvifw6ksyDW3Lpg= +github.com/gotnospirit/messageformat v0.0.0-20190719172517-c1d0bdacdea2/go.mod h1:NO9UUa4C4cSmRsYSfZMAKhI5ifCRzOjSGe/pi7TKRvs= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3 h1:BGNSrTRW4rwfhJiFwvwF4XQ0Y72Jj9YEgxVrtovbD5o= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3/go.mod h1:VHn7KgNsRriXa4mcgtkpR00OXyQY6g67JWMvn+R27A4= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 h1:YuDUUFNM21CAbyPOpOP8BicaTD/0klJEKt5p8yuw+uY= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/mattn/go-ieproxy v0.0.3 h1:YkaHmK1CzE5C4O7A3hv3TCbfNDPSCf0RKZFX+VhBeYk= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/statsd_exporter v0.22.4 h1:bGhC0iI9DM8m9KIUlbcr3uzXnopagrajEKoP790256k= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/smartystreets/assertions v1.2.1 h1:bKNHfEv7tSIjZ8JbKaFjzFINljxG4lzZvmHUnElzOIg= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.1 h1:izYHOT71f9iZ7iq37Uqjael60/vYC6vMtzedudZ0zEk= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/throttled/throttled v2.2.5+incompatible h1:65UB52X0qNTYiT0Sohp8qLYVFwZQPDw85uSa65OljjQ= +github.com/throttled/throttled/v2 v2.7.1 h1:FnBysDX4Sok55bvfDMI0l2Y71V1vM2wi7O79OW7fNtw= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= +github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.thethings.network/lorawan-stack/v3 v3.22.2 h1:RiXdtbHbG0XC9tMG2kM3df+ntIX4qcHww3G3dfJ6a9E= +go.thethings.network/lorawan-stack/v3 v3.22.2/go.mod h1:htixulfSnn27vbVmOarvxcM5cu3XJkgdv2KSFDKuwcA= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +gocloud.dev v0.20.0 h1:mbEKMfnyPV7W1Rj35R1xXfjszs9dXkwSOq2KoFr25g8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20220706164943-b4a6d9510983 h1:sUweFwmLOje8KNfXAVqGGAsmgJ/F8jJ6wBLJDt4BTKY= +golang.org/x/exp v0.0.0-20220706164943-b4a6d9510983/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= +golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.61.0 h1:TXXKS1slM3b2bZNJwD5DV/Tp6/M2cLzLOLh9PjDhrw8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/docs/docs.go b/internal/docs/docs.go new file mode 100644 index 0000000..fee2a18 --- /dev/null +++ b/internal/docs/docs.go @@ -0,0 +1,101 @@ +package docs + +import ( + "bytes" + "embed" + "errors" + "fmt" + "os" + "strings" + "text/template" + + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model" +) + +//go:embed "*.tmpl" +var fsys embed.FS + +var tmpl = template.Must(template.ParseFS(fsys, "*.tmpl")) + +// Generate generates the documentation for the frequency-plans. +func Generate(sourceFile, destinationFolder string) error { + output, err := model.FrequencyPlanDescriptions{}.Parse(sourceFile) + if err != nil { + return err + } + descriptions := output.(model.FrequencyPlanDescriptions) + + if err := renderPlans("gateway", descriptions.GatewayDescriptions, model.FrequencyPlanGateway{}); err != nil { + return err + } + + if err := renderPlans("end-device", descriptions.EndDeviceDescriptions, model.FrequencyPlanEndDevice{}); err != nil { + return err + } + + var buf bytes.Buffer + if err := tmpl.ExecuteTemplate(&buf, "frequency-plans.md.tmpl", output); err != nil { + return err + } + if err := os.WriteFile(destinationFolder+"/frequency-plans.md", buf.Bytes(), 0o644); err != nil { + return err + } + return nil +} + +func formatFrequency(f float64) string { + return strings.TrimRight(fmt.Sprintf("%.3f", f/1_000_000), "0.") +} + +func renderPlans(folder string, descriptions []model.FrequencyPlanDescription, definition model.Definition) error { + for _, plan := range descriptions { + fileName := "" + if plan.HasModifiers() { + for _, description := range descriptions { + if description.ID == *plan.BaseID { + fileName = *description.File + break + } + } + } else { + fileName = *plan.File + } + basePlan, err := definition.Parse(folder + "/" + fileName) + if err != nil { + return err + } + if plan.HasModifiers() { + for _, modifierName := range *plan.Modifiers { + switch definition.(type) { + case model.FrequencyPlanEndDevice: + modifier, err := model.FrequencyPlanEndDeviceModifier{}.Parse(folder + "/modifiers/" + modifierName) + if err != nil { + return err + } + basePlan = basePlan.(model.FrequencyPlanEndDevice).Modify(modifier.(model.FrequencyPlanEndDeviceModifier)) + case model.FrequencyPlanGateway: + modifier, err := model.FrequencyPlanGatewayModifier{}.Parse(folder + "/modifiers/" + modifierName) + if err != nil { + return err + } + basePlan = basePlan.(model.FrequencyPlanGateway).Modify(modifier.(model.FrequencyPlanGatewayModifier)) + } + } + } + if err := render(plan.ID, basePlan); err != nil { + return err + } + } + return nil +} + +func render(id string, definition model.Definition) error { + switch mod := definition.(type) { + case model.FrequencyPlanGateway: + return renderGateway(id, mod) + case model.FrequencyPlanEndDevice: + return renderEndDevice(id, mod) + default: + return errors.New("unsupported type") + } +} diff --git a/internal/docs/end-device.go b/internal/docs/end-device.go new file mode 100644 index 0000000..393b074 --- /dev/null +++ b/internal/docs/end-device.go @@ -0,0 +1,156 @@ +package docs + +import ( + "bytes" + "os" + "sort" + + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model" + "github.com/wcharczuk/go-chart/v2" +) + +func renderEndDevice(id string, plan model.FrequencyPlanEndDevice) error { + frequencies := make(map[float64]string) + + graph := chart.Chart{ + Title: id, + Width: 1920, + Height: 1080, + DPI: 150, + } + + annotations := chart.AnnotationSeries{} + + for _, ch := range plan.Channels { + freq := float64(*ch.UplinkFrequency) + start, end := freq-62500, freq+62500 + frequencies[freq] = formatFrequency(freq) + color := chart.GetDefaultColor(0) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(1), float64(1)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: 1, + Label: formatFrequency(freq), + }) + + freq = float64(*ch.DownlinkFrequency) + start, end = freq-62500, freq+62500 + frequencies[freq] = formatFrequency(freq) + color = chart.GetDefaultColor(3) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(-1), float64(-1)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: -1, + Label: formatFrequency(freq), + }) + } + + if ch := plan.LoRaStandardChannel; ch != nil { + freq := float64(*ch.Frequency) + start, end := freq-125000, freq+125000 + frequencies[freq] = formatFrequency(freq) + color := chart.GetDefaultColor(1) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(2), float64(2)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: 2, + Label: formatFrequency(freq) + " (Std)", + }) + } + + if ch := plan.FSKChannel; ch != nil { + freq := float64(*ch.Frequency) + start, end := freq-62500, freq+62500 + frequencies[freq] = formatFrequency(freq) + color := chart.GetDefaultColor(2) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(2), float64(2)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: 2, + Label: formatFrequency(freq) + " (FSK)", + }) + } + + var frequencySlice []float64 + for frequency := range frequencies { + frequencySlice = append(frequencySlice, frequency) + } + sort.Float64s(frequencySlice) + + graph.XAxis = chart.XAxis{ + Range: &chart.ContinuousRange{ + Min: frequencySlice[0], + Max: frequencySlice[len(frequencySlice)-1], + }, + TickStyle: chart.Style{ + TextRotationDegrees: 45.0, + }, + } + + for _, freq := range frequencySlice { + graph.XAxis.Ticks = append(graph.XAxis.Ticks, chart.Tick{ + Value: freq, + Label: frequencies[freq], + }) + } + + graph.YAxis = chart.YAxis{ + Range: &chart.ContinuousRange{ + Min: -2, + Max: 3, + }, + Ticks: []chart.Tick{ + {Value: -2}, + {Value: -1, Label: "Downlink"}, + {Value: 0, Label: "Radio"}, + {Value: 1, Label: "Uplink"}, + {Value: 2, Label: "Std/FSK"}, + {Value: 3}, + }, + } + + graph.Series = append(graph.Series, annotations) + + var buf bytes.Buffer + + if err := graph.Render(chart.SVG, &buf); err != nil { + return err + } + if err := os.WriteFile("./docs/images/end-device/"+id+".svg", buf.Bytes(), 0o644); err != nil { + return err + } + + return nil +} diff --git a/internal/docs/frequency-plans.md.tmpl b/internal/docs/frequency-plans.md.tmpl new file mode 100644 index 0000000..98c48cb --- /dev/null +++ b/internal/docs/frequency-plans.md.tmpl @@ -0,0 +1,26 @@ +# LoRaWAN Frequency Plans for The Things Stack + +# End device frequency plans +{{- range .EndDeviceDescriptions }} +{{ if .BaseID }} +## `{{ .ID }}`: {{ .Name }} +Based on [{{ .BaseID }}](##{{ .BaseID }}) and modified by {{ range .Modifiers }}[{{ . }}](../end-device/modifiers/{{ . }}){{ end }} +{{ else }} +## [`{{ .ID }}`](../end-device/{{ .File }}): {{ .Name }}{{ end }} + +>> {{ .Description }} + +![{{ .ID }}](images/end-device/{{ .ID }}.svg){{ end }} + +# Gateway frequency plans +{{- range .GatewayDescriptions }} + +{{ if .BaseID }} +## `{{ .ID }}`: {{ .Name }} +Based on {{ .BaseID }} and modified by {{ range .Modifiers }}[{{ . }}](../gateway/modifiers/{{ . }}){{ end }} +{{ else }} +## [`{{ .ID }}`](../gateway/{{ .File }}): {{ .Name }}{{ end }} + +>> {{ .Description }} + +![{{ .ID }}](images/gateway/{{ .ID }}.svg){{ end }} diff --git a/internal/docs/gateway.go b/internal/docs/gateway.go new file mode 100644 index 0000000..0a3a4bf --- /dev/null +++ b/internal/docs/gateway.go @@ -0,0 +1,180 @@ +package docs + +import ( + "bytes" + "fmt" + "os" + "sort" + + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model" + "github.com/wcharczuk/go-chart/v2" +) + +func renderGateway(id string, plan model.FrequencyPlanGateway) error { + frequencies := make(map[float64]string) + + graph := chart.Chart{ + Title: id, + Width: 1920, + Height: 1080, + DPI: 150, + } + + annotations := chart.AnnotationSeries{} + + for _, ch := range plan.Channels { + freq := float64(*ch.UplinkFrequency) + start, end := freq-62500, freq+62500 + frequencies[freq] = formatFrequency(freq) + color := chart.GetDefaultColor(int(*ch.Radio)) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(1), float64(1)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: 1, + Label: formatFrequency(freq), + }) + + freq = float64(*ch.DownlinkFrequency) + start, end = freq-62500, freq+62500 + frequencies[freq] = formatFrequency(freq) + color = chart.GetDefaultColor(3) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(-1), float64(-1)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: -1, + Label: formatFrequency(freq), + }) + } + + if ch := plan.LoRaStandardChannel; ch != nil { + freq := float64(*ch.Frequency) + start, end := freq-125000, freq+125000 + frequencies[freq] = formatFrequency(freq) + color := chart.GetDefaultColor(int(*ch.Radio)) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(2), float64(2)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: 2, + Label: formatFrequency(freq) + " (Std)", + }) + } + + if ch := plan.FSKChannel; ch != nil { + freq := float64(*ch.Frequency) + start, end := freq-62500, freq+62500 + frequencies[freq] = formatFrequency(freq) + color := chart.GetDefaultColor(int(*ch.Radio)) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + FillColor: color, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(2), float64(2)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: 2, + Label: formatFrequency(freq) + " (FSK)", + }) + } + + for i, radio := range plan.Radios { + freq := float64(*radio.Frequency) + start, end := freq-462500, freq+462500 + frequencies[start] = formatFrequency(start) + frequencies[freq] = formatFrequency(freq) + frequencies[end] = formatFrequency(end) + color := chart.GetDefaultColor(i) + color.A = 128 + graph.Series = append(graph.Series, chart.ContinuousSeries{ + Style: chart.Style{ + StrokeColor: color, + StrokeWidth: 10, + }, + XValues: []float64{start, end}, + YValues: []float64{float64(0), float64(0)}, + }) + annotations.Annotations = append(annotations.Annotations, chart.Value2{ + XValue: freq, + YValue: 0, + Label: fmt.Sprintf("Radio %d: %s", i, formatFrequency(freq)), + }) + } + + var frequencySlice []float64 + for frequency := range frequencies { + frequencySlice = append(frequencySlice, frequency) + } + sort.Float64s(frequencySlice) + + graph.XAxis = chart.XAxis{ + Range: &chart.ContinuousRange{ + Min: frequencySlice[0], + Max: frequencySlice[len(frequencySlice)-1], + }, + TickStyle: chart.Style{ + TextRotationDegrees: 45.0, + }, + } + + for _, freq := range frequencySlice { + graph.XAxis.Ticks = append(graph.XAxis.Ticks, chart.Tick{ + Value: freq, + Label: frequencies[freq], + }) + } + + graph.YAxis = chart.YAxis{ + Range: &chart.ContinuousRange{ + Min: -2, + Max: 3, + }, + Ticks: []chart.Tick{ + {Value: -2}, + {Value: -1, Label: "Downlink"}, + {Value: 0, Label: "Radio"}, + {Value: 1, Label: "Uplink"}, + {Value: 2, Label: "Std/FSK"}, + {Value: 3}, + }, + } + + graph.Series = append(graph.Series, annotations) + + var buf bytes.Buffer + + if err := graph.Render(chart.SVG, &buf); err != nil { + return err + } + if err := os.WriteFile("./docs/images/gateway/"+id+".svg", buf.Bytes(), 0o644); err != nil { + return err + } + + return nil +} diff --git a/internal/model/end-device-frequency-plan.go b/internal/model/end-device-frequency-plan.go new file mode 100644 index 0000000..efd3705 --- /dev/null +++ b/internal/model/end-device-frequency-plan.go @@ -0,0 +1,137 @@ +package model + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v2" +) + +type FrequencyPlanEndDevice struct { + BandID string `yaml:"band-id"` + SubBands []SubBand `yaml:"sub-bands"` + Channels []Channel `yaml:"channels"` + LoRaStandardChannel *FrequencyDataRate `yaml:"lora-special-channel,omitempty"` + FSKChannel *FrequencyDataRate `yaml:"fsk-channel,omitempty"` + TimeOffAir *TimeOffAir `yaml:"time-off-air,omitempty"` + DwellTime *DwellTime `yaml:"dwell-time,omitempty"` + ListenBeforeTalk *ListenBeforeTalk `yaml:"listen-before-talk,omitempty"` + PingSlot *FrequencyMinMaxDataRate `yaml:"ping-slot,omitempty"` + PingSlotDefaultDataRate *string `yaml:"ping-slot-default-data-rate,omitempty"` + RX2Channel *FrequencyMinMaxDataRate `yaml:"rx2-channel,omitempty"` + RX2DefaultDataRate *string `yaml:"rx2-default-data-rate,omitempty"` + MaxEIRP *float32 `yaml:"max-eirp,omitempty"` +} + +func (f FrequencyPlanEndDevice) Parse(file string) (Definition, error) { + indexBytes, err := os.ReadFile(file) + if err != nil { + return FrequencyPlanEndDevice{}, err + } + err = yaml.Unmarshal(indexBytes, &f) + return f, err +} + +func (f FrequencyPlanEndDevice) Validate() error { + for i, subBand := range f.SubBands { + if err := subBand.Validate(); err != nil { + return fmt.Errorf("SubBand %d: %w", i, err) + } + } + for i, channel := range f.Channels { + if err := channel.Validate(); err != nil { + return fmt.Errorf("Channel %d: %w", i, err) + } + } + if f.LoRaStandardChannel != nil { + if err := f.LoRaStandardChannel.Validate(); err != nil { + return fmt.Errorf("LoRaStandardChannel: %w", err) + } + } + if f.FSKChannel != nil { + if err := f.FSKChannel.Validate(); err != nil { + return fmt.Errorf("FSKChannel: %w", err) + } + } + if f.TimeOffAir != nil { + if err := f.TimeOffAir.Validate(); err != nil { + return fmt.Errorf("TimeOffAir: %w", err) + } + } + if f.DwellTime != nil { + if err := f.DwellTime.Validate(); err != nil { + return fmt.Errorf("DwellTime: %w", err) + } + } + if f.ListenBeforeTalk != nil { + if err := f.ListenBeforeTalk.Validate(); err != nil { + return fmt.Errorf("ListenBeforeTalk: %w", err) + } + } + if f.PingSlot != nil { + if err := f.PingSlot.Validate(); err != nil { + return fmt.Errorf("PingSlot: %w", err) + } + } + if f.PingSlotDefaultDataRate != nil { + if err := validateDataRate(*f.PingSlotDefaultDataRate); err != nil { + return fmt.Errorf("PingSlotDefaultDataRate: %w", err) + } + } + if f.RX2Channel != nil { + if err := f.RX2Channel.Validate(); err != nil { + return fmt.Errorf("RX2Channel: %w", err) + } + } + if f.RX2DefaultDataRate != nil { + if err := validateDataRate(*f.RX2DefaultDataRate); err != nil { + return fmt.Errorf("RX2DefaultDataRate: %w", err) + } + } + return nil +} + +func (f FrequencyPlanEndDevice) Modify(modifier FrequencyPlanEndDeviceModifier) FrequencyPlanEndDevice { + modified := f + set(modifier.SubBands, modified.SubBands) + set(modifier.Channels, modified.Channels) + setPointer(modifier.LoRaStandardChannel, modified.LoRaStandardChannel) + setPointer(modifier.FSKChannel, modified.FSKChannel) + setPointer(modifier.TimeOffAir, modified.TimeOffAir) + setPointer(modifier.DwellTime, modified.DwellTime) + setPointer(modifier.ListenBeforeTalk, modified.ListenBeforeTalk) + setPointer(modifier.PingSlot, modified.PingSlot) + setPointer(modifier.PingSlotDefaultDataRate, modified.PingSlotDefaultDataRate) + setPointer(modifier.RX2Channel, modified.RX2Channel) + setPointer(modifier.RX2DefaultDataRate, modified.RX2DefaultDataRate) + setPointer(modifier.MaxEIRP, modified.MaxEIRP) + return modified +} + +type FrequencyPlanEndDeviceModifier struct { + SubBands *[]SubBand `yaml:"sub-bands,omitempty"` + Channels *[]Channel `yaml:"channels,omitempty"` + LoRaStandardChannel *FrequencyDataRate `yaml:"lora-special-channel,omitempty"` + FSKChannel *FrequencyDataRate `yaml:"fsk-channel,omitempty"` + TimeOffAir *TimeOffAir `yaml:"time-off-air,omitempty"` + DwellTime *DwellTime `yaml:"dwell-time,omitempty"` + ListenBeforeTalk *ListenBeforeTalk `yaml:"listen-before-talk,omitempty"` + PingSlot *FrequencyMinMaxDataRate `yaml:"ping-slot,omitempty"` + PingSlotDefaultDataRate *string `yaml:"ping-slot-default-data-rate,omitempty"` + RX2Channel *FrequencyMinMaxDataRate `yaml:"rx2-channel,omitempty"` + RX2DefaultDataRate *string `yaml:"rx2-default-data-rate,omitempty"` + MaxEIRP *float32 `yaml:"max-eirp,omitempty"` +} + +func (f FrequencyPlanEndDeviceModifier) Parse(file string) (Definition, error) { + indexBytes, err := os.ReadFile(file) + if err != nil { + return FrequencyPlanEndDeviceModifier{}, err + } + err = yaml.Unmarshal(indexBytes, &f) + return f, err +} + +func (f FrequencyPlanEndDeviceModifier) Validate() error { + return nil +} diff --git a/internal/model/frequency-plan-description.go b/internal/model/frequency-plan-description.go new file mode 100644 index 0000000..805e041 --- /dev/null +++ b/internal/model/frequency-plan-description.go @@ -0,0 +1,117 @@ +package model + +import ( + "fmt" + "os" + + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/utils" + "gopkg.in/yaml.v2" +) + +type FrequencyPlanDescriptions struct { + EndDeviceDescriptions []FrequencyPlanDescription `yaml:"end-device-descriptions"` + GatewayDescriptions []FrequencyPlanDescription `yaml:"gateway-descriptions"` +} + +func (f FrequencyPlanDescriptions) Parse(file string) (Definition, error) { + indexBytes, err := os.ReadFile(file) + if err != nil { + return FrequencyPlanDescriptions{}, err + } + err = yaml.Unmarshal(indexBytes, &f) + if err != nil { + return nil, err + } + return f, err +} + +func (f FrequencyPlanDescriptions) Validate() error { + for _, description := range f.EndDeviceDescriptions { + if err := description.Validate(EndDeviceFrequencyPlan); err != nil { + return err + } + } + for _, description := range f.GatewayDescriptions { + if err := description.Validate(GatewayFrequencyPlan); err != nil { + return err + } + } + return nil +} + +type FrequencyPlanType string + +var ( + GatewayFrequencyPlan FrequencyPlanType = "gateway" + EndDeviceFrequencyPlan FrequencyPlanType = "end-device" +) + +// EndDeviceFrequencyPlanDescription describes an end device frequency plan in the YAML format. +type FrequencyPlanDescription struct { + ID string `yaml:"id"` + BandID string `yaml:"band-id"` + BaseID *string `yaml:"base-id,omitempty"` + Name string `yaml:"name"` + Description string `yaml:"description"` + BaseFrequency uint16 `yaml:"base-frequency"` + CountryCodes []string `yaml:"country-codes"` + File *string `yaml:"file,omitempty"` + Modifiers *[]string `yaml:"modifiers,omitempty"` + Endorsed bool `yaml:"endorsed"` +} + +func (f FrequencyPlanDescription) Validate(source FrequencyPlanType) error { + bandIDs := utils.GetBandIDs() + if !bandIDs[f.BandID] { + return fmt.Errorf("Frequency plan %s: BandID is invalid", f.ID) + } + if f.BaseID != nil && f.File != nil { + return fmt.Errorf("Frequency plan %s: BaseID may only be defined with Modifiers and not files", f.ID) + } + if f.File != nil && f.Modifiers != nil { + return fmt.Errorf("Frequency plan %s: Either define files or modifiers", f.ID) + } + + EndDeviceBaseIDs, GatewayBaseIDs, err := utils.GetBaseIDs() + if err != nil { + return err + } + switch source { + case EndDeviceFrequencyPlan: + if f.BaseID != nil && !EndDeviceBaseIDs[*f.BaseID] { + return fmt.Errorf("Frequency plan %s: Set BaseID doesn't exist", f.ID) + } + case GatewayFrequencyPlan: + if f.BaseID != nil && !GatewayBaseIDs[*f.BaseID] { + return fmt.Errorf("Frequency plan %s: Set BaseID doesn't exist", f.ID) + } + } + + if f.Modifiers != nil { + modifiers, err := utils.GetYamlFileNames(string(source) + "/modifiers") + if err != nil { + return err + } + for _, modifier := range *f.Modifiers { + if !modifiers[modifier] { + return fmt.Errorf("Frequency plan %s: %s modifier %s doesn't exist", source, f.ID, modifier) + } + } + } + + if f.File != nil { + files, err := utils.GetYamlFileNames(string(source)) + if err != nil { + return err + } + if !files[*f.File] { + return fmt.Errorf("Frequency plan %s: %s file %s doesn't exist", source, f.ID, *f.File) + } + } + + return nil +} + +func (f FrequencyPlanDescription) HasModifiers() bool { + return f.BaseID != nil && f.Modifiers != nil +} diff --git a/internal/model/gateway-frequency-plan.go b/internal/model/gateway-frequency-plan.go new file mode 100644 index 0000000..4e27aa1 --- /dev/null +++ b/internal/model/gateway-frequency-plan.go @@ -0,0 +1,108 @@ +package model + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v2" +) + +type FrequencyPlanGateway struct { + BandID string `yaml:"band-id"` + SubBands []SubBand `yaml:"sub-bands"` + Channels []Channel `yaml:"channels"` + LoRaStandardChannel *FrequencyDataRate `yaml:"lora-standard-channel,omitempty"` + FSKChannel *FrequencyDataRate `yaml:"fsk-channel,omitempty"` + TimeOffAir *TimeOffAir `yaml:"time-off-air,omitempty"` + DwellTime *DwellTime `yaml:"dwell-time,omitempty"` + Radios []Radio `yaml:"radios"` + ClockSource int `yaml:"clock-source"` + MaxEIRP *float32 `yaml:"max-eirp,omitempty"` +} + +func (f FrequencyPlanGateway) Parse(file string) (Definition, error) { + indexBytes, err := os.ReadFile(file) + if err != nil { + return FrequencyPlanGateway{}, err + } + err = yaml.Unmarshal(indexBytes, &f) + return f, err +} + +func (f FrequencyPlanGateway) Validate() error { + for i, subBand := range f.SubBands { + if err := subBand.Validate(); err != nil { + return fmt.Errorf("SubBand %d: %w", i, err) + } + } + for i, channel := range f.Channels { + if err := channel.Validate(); err != nil { + return fmt.Errorf("Channel %d: %w", i, err) + } + } + if f.LoRaStandardChannel != nil { + if err := f.LoRaStandardChannel.Validate(); err != nil { + return fmt.Errorf("LoRaStandardChannel: %w", err) + } + } + if f.FSKChannel != nil { + if err := f.FSKChannel.Validate(); err != nil { + return fmt.Errorf("FSKChannel: %w", err) + } + } + if f.TimeOffAir != nil { + if err := f.TimeOffAir.Validate(); err != nil { + return fmt.Errorf("TimeOffAir: %w", err) + } + } + if f.DwellTime != nil { + if err := f.DwellTime.Validate(); err != nil { + return fmt.Errorf("DwellTime: %w", err) + } + } + for i, radio := range f.Radios { + if err := radio.Validate(); err != nil { + return fmt.Errorf("Radio %d: %w", i, err) + } + } + return nil +} + +func (f FrequencyPlanGateway) Modify(modifier FrequencyPlanGatewayModifier) FrequencyPlanGateway { + modified := f + set(modifier.SubBands, modified.SubBands) + set(modifier.Channels, modified.Channels) + set(modifier.Radios, modified.Radios) + set(modifier.ClockSource, modified.ClockSource) + setPointer(modifier.LoRaStandardChannel, modified.LoRaStandardChannel) + setPointer(modifier.FSKChannel, modified.FSKChannel) + setPointer(modifier.TimeOffAir, modified.TimeOffAir) + setPointer(modifier.DwellTime, modified.DwellTime) + setPointer(modifier.MaxEIRP, modified.MaxEIRP) + return modified +} + +type FrequencyPlanGatewayModifier struct { + SubBands *[]SubBand `yaml:"sub-bands,omitempty"` + Channels *[]Channel `yaml:"channels,omitempty"` + LoRaStandardChannel *FrequencyDataRate `yaml:"lora-standard-channel,omitempty"` + FSKChannel *FrequencyDataRate `yaml:"fsk-channel,omitempty"` + TimeOffAir *TimeOffAir `yaml:"time-off-air,omitempty"` + DwellTime *DwellTime `yaml:"dwell-time,omitempty"` + Radios *[]Radio `yaml:"radios,omitempty"` + ClockSource *int `yaml:"clock-source,omitempty"` + MaxEIRP *float32 `yaml:"max-eirp,omitempty"` +} + +func (f FrequencyPlanGatewayModifier) Parse(file string) (Definition, error) { + indexBytes, err := os.ReadFile(file) + if err != nil { + return FrequencyPlanGatewayModifier{}, err + } + err = yaml.Unmarshal(indexBytes, &f) + return f, err +} + +func (f FrequencyPlanGatewayModifier) Validate() error { + return nil +} diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..0546380 --- /dev/null +++ b/internal/model/model.go @@ -0,0 +1,256 @@ +package model + +import ( + "errors" + "fmt" + "strconv" + "time" +) + +var ( + dataRates = map[string]bool{ + "SF7BW125": true, + "SF7BW250": true, + "SF8BW125": true, + "SF8BW500": true, + "SF9BW125": true, + "SF9BW500": true, + "SF10BW125": true, + "SF10BW500": true, + "SF11BW125": true, + "SF11BW500": true, + "SF12BW125": true, + "SF12BW500": true, + "FSK50": true, + "M0CW137CR1/3": true, + "M0CW137CR2/3": true, + "M0CW336CR1/3": true, + "M0CW336CR2/3": true, + "M0CW1523CR1/3": true, + "M0CW1523CR2/3": true, + } + chipTypes = map[string]bool{ + "SX1255": true, + "SX1257": true, + } + mandatoryFieldsError = errors.New("all mandatory fields should be set") +) + +type Definition interface { + Parse(file string) (Definition, error) + Validate() error +} + +type SubBand struct { + MinFrequency *int `yaml:"min-frequency,omitempty"` + MaxFrequency *int `yaml:"max-frequency,omitempty"` + DutyCyle *float32 `yaml:"duty-cycle,omitempty"` + MaxEIRP *float32 `yaml:"max-eirp,omitempty"` +} + +func (s SubBand) Validate() error { + if s.MinFrequency == nil || s.MaxFrequency == nil { + return mandatoryFieldsError + } + if err := validateFrequency(*s.MinFrequency); err != nil { + return err + } + if err := validateFrequency(*s.MaxFrequency); err != nil { + return err + } + if *s.MinFrequency >= *s.MaxFrequency { + return errors.New("min-frequency should not be the same as or bigger than max-frequency") + } + return nil +} + +type Channel struct { + UplinkFrequency *int `yaml:"uplink-frequency"` + DownlinkFrequency *int `yaml:"downlink-frequency,omitempty"` + MinDataRate *string `yaml:"min-data-rate"` + MaxDataRate *string `yaml:"max-data-rate"` + Radio *int `yaml:"radio,omitempty"` + Default *bool `yaml:"default"` +} + +func (c Channel) Validate() error { + if c.UplinkFrequency == nil || c.MinDataRate == nil || c.MaxDataRate == nil { + return mandatoryFieldsError + } + if err := validateFrequency(*c.UplinkFrequency); err != nil { + return err + } + if c.DownlinkFrequency != nil { + if err := validateFrequency(*c.DownlinkFrequency); err != nil { + return err + } + } + if err := validateDataRate(*c.MinDataRate); err != nil { + return err + } + if err := validateDataRate(*c.MaxDataRate); err != nil { + return err + } + return nil +} + +type FrequencyDataRate struct { + Frequency *int `yaml:"frequency,omitempty"` + DataRate *string `yaml:"data-rate,omitempty"` + Radio *int `yaml:"radio,omitempty"` +} + +func (f FrequencyDataRate) Validate() error { + if f.Frequency == nil || f.DataRate == nil { + return mandatoryFieldsError + } + if err := validateFrequency(*f.Frequency); err != nil { + return err + } + if err := validateDataRate(*f.DataRate); err != nil { + return err + } + return nil +} + +type TimeOffAir struct { + Fraction *float32 `yaml:"fraction,omitempty"` + Duration *time.Duration `yaml:"duration,omitempty"` +} + +func (t TimeOffAir) Validate() error { + if t.Fraction == nil || t.Duration == nil { + return mandatoryFieldsError + } + return nil +} + +type DwellTime struct { + Uplinks *bool `yaml:"uplinks,omitempty"` + Downlinks *bool `yaml:"downlinks,omitempty"` + Duration *time.Duration `yaml:"duration,omitempty"` +} + +func (d DwellTime) Validate() error { + if d.Uplinks == nil || d.Downlinks == nil || d.Duration == nil { + return mandatoryFieldsError + } + return nil +} + +type ListenBeforeTalk struct { + RSSIOffset *int `yaml:"rssi-offset,omitempty"` + RSSITarget *int `yaml:"rssi-targer,omitempty"` + ScanTime *int `yaml:"scan-time,omitempty"` +} + +func (l ListenBeforeTalk) Validate() error { + if l.RSSIOffset == nil || l.RSSITarget == nil || l.ScanTime == nil { + return mandatoryFieldsError + } + return nil +} + +type Radio struct { + Enable *bool `yaml:"enable,omitempty"` + ChipType *string `yaml:"chip-type,omitempty"` + Frequency *int `yaml:"frequency,omitempty"` + RSSIOffset *int `yaml:"rssi-offset,omitempty"` + TX *TX `yaml:"tx,omitempty"` +} + +func (r Radio) Validate() error { + if r.Enable == nil || r.ChipType == nil || r.Frequency == nil || r.RSSIOffset == nil { + return mandatoryFieldsError + } + if !chipTypes[*r.ChipType] { + return errors.New("Unknown chip type") + } + if err := validateFrequency(*r.Frequency); err != nil { + return err + } + return nil +} + +type TX struct { + MinFrequency *int `yaml:"min-frequency,omitempty"` + MaxFrequency *int `yaml:"max-frequency,omitempty"` + NotchFrequency *int `yaml:"notch-frequency,omitempty"` +} + +func (tx TX) Validate() error { + if tx.MinFrequency == nil || tx.MaxFrequency == nil { + return mandatoryFieldsError + } + if err := validateFrequency(*tx.MinFrequency); err != nil { + return err + } + if err := validateFrequency(*tx.MaxFrequency); err != nil { + return err + } + if err := validateFrequencyRange(*tx.NotchFrequency, 126000, 250000); err != nil { + return err + } + return nil +} + +type FrequencyMinMaxDataRate struct { + Frequency *int `yaml:"frequency,omitempty"` + MinDataRate *string `yaml:"min-data-rate,omitempty"` + MaxDataRate *string `yaml:"max-data-rate,omitempty"` +} + +func (f FrequencyMinMaxDataRate) Validate() error { + if f.Frequency == nil || f.MaxDataRate == nil || f.MinDataRate == nil { + return mandatoryFieldsError + } + if err := validateFrequency(*f.Frequency); err != nil { + return err + } + if err := validateDataRate(*f.MinDataRate); err != nil { + return err + } + if err := validateDataRate(*f.MaxDataRate); err != nil { + return err + } + return nil +} + +func validateDataRate(dataRate string) error { + if _, err := strconv.Atoi(dataRate); err == nil || dataRates[dataRate] { + return nil + } + return fmt.Errorf("data rate %s is neither a known datarate nor an integer", dataRate) +} + +func validateFrequency(frequency int) error { + if frequency < 0 { + return errors.New("frequencies can't be negative") + } + return nil +} + +func validateFrequencyRange(frequency int, min, max int) error { + if err := validateFrequency(frequency); err != nil { + return err + } + if frequency < min || frequency > max { + return fmt.Errorf("frequency should be between %d and %d Hz", min, max) + } + return nil +} + +func set[T any](modifier *T, base T) { + if modifier != nil { + base = *modifier + } +} + +func setPointer[T any](modifier *T, base *T) { + if modifier != nil { + if base == nil { + base = new(T) + } + *base = *modifier + } +} diff --git a/internal/schema/frequency-plans-description-schema.json.tmpl b/internal/schema/frequency-plans-description-schema.json.tmpl new file mode 100644 index 0000000..517a93f --- /dev/null +++ b/internal/schema/frequency-plans-description-schema.json.tmpl @@ -0,0 +1,676 @@ +{ + "title": "Frequency Plan", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "end-device-descriptions": { + "type": "array", + "items": { + "$ref": "#/definitions/EndDeviceFrequencyPlan" + } + }, + "gateway-descriptions": { + "type": "array", + "items": { + "$ref": "#/definitions/GatewayFrequencyPlan" + } + } + }, + "required": ["end-device-descriptions", "gateway-descriptions"], + "definitions": { + "EndDeviceFrequencyPlan": { + "type": "object", + "additionalProperties": false, + "required": ["id", "band-id", "name", "description", "country-codes", "endorsed"], + "oneOf": [ + { + "required": ["file"], + "not": { + "anyOf": [{ "required": ["base-id"] }, { "required": ["modifiers"] }] + } + }, + { + "required": ["base-id", "modifiers"], + "not": { + "anyOf": [{ "required": ["file"] }] + } + } + ], + "properties": { + "id": { + "type": "string", + "description": "ID of the frequency plan." + }, + "band-id": { + "type": "string", + "description": "ID of the LoRaWAN band (needs to match band-id in the definition)", + "enum" : [{{.BandIDs}}] + }, + "name": { + "type": "string", + "description": "Name of the frequency plan. In the format '{Region} {Frequency range} MHz'." + }, + "description": { + "type": "string", + "description": "Description of the frequency plan." + }, + "base-frequency": { + "type": "integer", + "description": "Base frequency in MHz for hardware support (433, 470, 868 or 915) [MHz]", + "enum": [433, 470, 868, 915, 2450] + }, + "file": { + "type": "string", + "description": "File of the frequency plan definition.", + "enum": [{{.EndDeviceFiles}}] + }, + "base-id": { + "type": "string", + "description": "ID that this frequency plan extends (refers to id of another frequency plan).", + "enum" : [{{.EndDeviceBaseIDs}}] + }, + "modifiers": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum":[{{.EndDeviceModifiers}}], + "description": "Modifier addressed by filename." + }, + "description": "List of files containing the modifiers used to extend the `base-id` end-device." + }, + "endorsed": { + "type": "boolean", + "description": "This is one of the endorsed frequency plans for this band-id." + }, + "country-codes": { + "type": "array", + "uniqueItems": true, + "description": "Countries that this frequency plan can be used in.", + "items": { + "type": "string", + "enum": [ + "af", + "ax", + "al", + "dz", + "as", + "ad", + "ao", + "ai", + "aq", + "ag", + "ar", + "am", + "an", + "aw", + "au", + "at", + "az", + "bh", + "bs", + "bd", + "bb", + "by", + "be", + "bz", + "bj", + "bm", + "bt", + "bo", + "bq", + "ba", + "bw", + "bv", + "br", + "io", + "bn", + "bg", + "bf", + "bi", + "kh", + "cm", + "ca", + "cv", + "ky", + "cf", + "td", + "cl", + "cn", + "cx", + "cc", + "co", + "km", + "cg", + "cd", + "ck", + "cr", + "ci", + "hr", + "cu", + "cw", + "cy", + "cz", + "dk", + "dj", + "dm", + "do", + "ec", + "eg", + "sv", + "gq", + "er", + "ee", + "et", + "fk", + "fo", + "fj", + "fi", + "fr", + "gf", + "pf", + "tf", + "ga", + "gm", + "ge", + "de", + "gh", + "gi", + "gr", + "gl", + "gd", + "gp", + "gu", + "gt", + "gg", + "gn", + "gw", + "gy", + "ht", + "hm", + "va", + "hn", + "hk", + "hu", + "is", + "in", + "id", + "ir", + "iq", + "ie", + "im", + "il", + "it", + "jm", + "jp", + "je", + "jo", + "kz", + "ke", + "ki", + "kp", + "kr", + "kw", + "kg", + "la", + "lv", + "lb", + "ls", + "lr", + "ly", + "li", + "lt", + "lu", + "mo", + "mk", + "mg", + "mw", + "my", + "mv", + "ml", + "mt", + "mh", + "mq", + "mr", + "mu", + "yt", + "mx", + "fm", + "md", + "mc", + "mn", + "me", + "ms", + "ma", + "mz", + "mm", + "na", + "nr", + "np", + "nl", + "nc", + "nz", + "ni", + "ne", + "ng", + "nu", + "nf", + "mp", + "no", + "om", + "pk", + "pw", + "ps", + "pa", + "pg", + "py", + "pe", + "ph", + "pn", + "pl", + "pt", + "pr", + "qa", + "re", + "ro", + "ru", + "rw", + "bl", + "sh", + "kn", + "lc", + "mf", + "pm", + "vc", + "ws", + "sm", + "st", + "sa", + "sn", + "rs", + "sc", + "sl", + "sg", + "sx", + "sk", + "si", + "sb", + "so", + "za", + "gs", + "ss", + "es", + "lk", + "sd", + "sr", + "sj", + "sz", + "se", + "ch", + "sy", + "tw", + "tj", + "tz", + "th", + "tl", + "tg", + "tk", + "to", + "tt", + "tn", + "tr", + "tm", + "tc", + "tv", + "ug", + "ua", + "ae", + "gb", + "us", + "um", + "uy", + "uz", + "vu", + "ve", + "vn", + "vg", + "vi", + "wf", + "eh", + "ye", + "zm", + "zw" + ] + } + } + } + }, + "GatewayFrequencyPlan": { + "type": "object", + "additionalProperties": false, + "required": ["id", "band-id", "name", "description", "country-codes", "endorsed"], + "oneOf": [ + { + "required": ["file"], + "not": { + "anyOf": [{ "required": ["base-id"] }, { "required": ["modifiers"] }] + } + }, + { + "required": ["base-id", "modifiers"], + "not": { + "anyOf": [{ "required": ["file"] }] + } + } + ], + "properties": { + "id": { + "type": "string", + "description": "ID of the frequency plan." + }, + "band-id": { + "type": "string", + "description": "ID of the LoRaWAN band (needs to match band-id in the definition)", + "enum" : [{{.BandIDs}}] + }, + "name": { + "type": "string", + "description": "Name of the frequency plan. In the format '{Region} {Frequency range} MHz'." + }, + "description": { + "type": "string", + "description": "Description of the frequency plan." + }, + "base-frequency": { + "type": "integer", + "description": "Base frequency in MHz for hardware support (433, 470, 868 or 915) [MHz]", + "enum": [433, 470, 868, 915, 2450] + }, + "file": { + "type": "string", + "description": "File of the frequency plan definition.", + "enum": [{{.GatewayFiles}}] + }, + "base-id": { + "type": "string", + "description": "ID that this frequency plan extends (refers to id of another frequency plan).", + "enum" : [{{.GatewayBaseIDs}}] + }, + "modifiers": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum":[{{.GatewayModifiers}}], + "description": "Modifier addressed by filename." + }, + "description": "List of files containing the modifiers used to extend the `base-id` end-device." + }, + "endorsed": { + "type": "boolean", + "description": "This is one of the endorsed frequency plans for this band-id." + }, + "country-codes": { + "type": "array", + "uniqueItems": true, + "description": "Countries that this frequency plan can be used in.", + "items": { + "type": "string", + "enum": [ + "af", + "ax", + "al", + "dz", + "as", + "ad", + "ao", + "ai", + "aq", + "ag", + "ar", + "am", + "an", + "aw", + "au", + "at", + "az", + "bh", + "bs", + "bd", + "bb", + "by", + "be", + "bz", + "bj", + "bm", + "bt", + "bo", + "bq", + "ba", + "bw", + "bv", + "br", + "io", + "bn", + "bg", + "bf", + "bi", + "kh", + "cm", + "ca", + "cv", + "ky", + "cf", + "td", + "cl", + "cn", + "cx", + "cc", + "co", + "km", + "cg", + "cd", + "ck", + "cr", + "ci", + "hr", + "cu", + "cw", + "cy", + "cz", + "dk", + "dj", + "dm", + "do", + "ec", + "eg", + "sv", + "gq", + "er", + "ee", + "et", + "fk", + "fo", + "fj", + "fi", + "fr", + "gf", + "pf", + "tf", + "ga", + "gm", + "ge", + "de", + "gh", + "gi", + "gr", + "gl", + "gd", + "gp", + "gu", + "gt", + "gg", + "gn", + "gw", + "gy", + "ht", + "hm", + "va", + "hn", + "hk", + "hu", + "is", + "in", + "id", + "ir", + "iq", + "ie", + "im", + "il", + "it", + "jm", + "jp", + "je", + "jo", + "kz", + "ke", + "ki", + "kp", + "kr", + "kw", + "kg", + "la", + "lv", + "lb", + "ls", + "lr", + "ly", + "li", + "lt", + "lu", + "mo", + "mk", + "mg", + "mw", + "my", + "mv", + "ml", + "mt", + "mh", + "mq", + "mr", + "mu", + "yt", + "mx", + "fm", + "md", + "mc", + "mn", + "me", + "ms", + "ma", + "mz", + "mm", + "na", + "nr", + "np", + "nl", + "nc", + "nz", + "ni", + "ne", + "ng", + "nu", + "nf", + "mp", + "no", + "om", + "pk", + "pw", + "ps", + "pa", + "pg", + "py", + "pe", + "ph", + "pn", + "pl", + "pt", + "pr", + "qa", + "re", + "ro", + "ru", + "rw", + "bl", + "sh", + "kn", + "lc", + "mf", + "pm", + "vc", + "ws", + "sm", + "st", + "sa", + "sn", + "rs", + "sc", + "sl", + "sg", + "sx", + "sk", + "si", + "sb", + "so", + "za", + "gs", + "ss", + "es", + "lk", + "sd", + "sr", + "sj", + "sz", + "se", + "ch", + "sy", + "tw", + "tj", + "tz", + "th", + "tl", + "tg", + "tk", + "to", + "tt", + "tn", + "tr", + "tm", + "tc", + "tv", + "ug", + "ua", + "ae", + "gb", + "us", + "um", + "uy", + "uz", + "vu", + "ve", + "vn", + "vg", + "vi", + "wf", + "eh", + "ye", + "zm", + "zw" + ] + } + } + } + } + } +} diff --git a/internal/schema/frequency-plans-end-device-modifiers-schema.json.tmpl b/internal/schema/frequency-plans-end-device-modifiers-schema.json.tmpl new file mode 100644 index 0000000..8d39039 --- /dev/null +++ b/internal/schema/frequency-plans-end-device-modifiers-schema.json.tmpl @@ -0,0 +1,204 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "ping-slot": { + "type": "object", + "description": "Class B ping slot settings (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "ping-slot-default-data-rate": { + "type": "integer", + "description": "Default data rate index of class B ping slot (optional)" + }, + "rx2-channel": { + "type": "object", + "description": "Rx2 channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "rx2-default-data-rate": { + "type": "integer", + "description": "Default data rate index of Rx2 (optional)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + } +} diff --git a/internal/schema/frequency-plans-end-device-schema.json.tmpl b/internal/schema/frequency-plans-end-device-schema.json.tmpl new file mode 100644 index 0000000..e198c03 --- /dev/null +++ b/internal/schema/frequency-plans-end-device-schema.json.tmpl @@ -0,0 +1,210 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "band-id": { + "type": "string", + "description": "ID of the band (needs to match band-id in the index)", + "enum": [{{.BandIDs}}] + }, + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "integer", + "description": "Data rate index" + } + }, + "required": ["frequency", "data-rate"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "ping-slot": { + "type": "object", + "description": "Class B ping slot settings (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "ping-slot-default-data-rate": { + "type": "integer", + "description": "Default data rate index of class B ping slot (optional)" + }, + "rx2-channel": { + "type": "object", + "description": "Rx2 channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "min-data-rate": { + "type": "integer", + "description": "Minimum data rate index" + }, + "max-data-rate": { + "type": "integer", + "description": "Maximum data rate index" + } + }, + "required": ["frequency", "min-data-rate", "max-data-rate"] + }, + "rx2-default-data-rate": { + "type": "integer", + "description": "Default data rate index of Rx2 (optional)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + }, + "required": ["band-id", "sub-bands", "channels"] +} diff --git a/internal/schema/frequency-plans-gateway-modifiers-schema.json.tmpl b/internal/schema/frequency-plans-gateway-modifiers-schema.json.tmpl new file mode 100644 index 0000000..db386a6 --- /dev/null +++ b/internal/schema/frequency-plans-gateway-modifiers-schema.json.tmpl @@ -0,0 +1,225 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "string", + "description": "Minimum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "max-data-rate": { + "type": "string", + "description": "Maximum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate", "radio"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["FSK50"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "radios": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable": { + "type": "boolean", + "description": "Enable the radio" + }, + "chip-type": { + "type": "string", + "description": "Chip type", + "enum": ["SX1255", "SX1257"] + }, + "frequency": { + "type": "integer", + "description": "Center frequency [Hz]" + }, + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "tx": { + "type": "object", + "description": "Radio transmission configuration (optional)", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz]" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz]" + }, + "notch-frequency": { + "type": "integer", + "description": "Notch frequency 126000..250000 [Hz] (optional)", + "minimum": 126000, + "maximum": 250000 + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "required": ["enable", "chip-type", "frequency", "rssi-offset"] + } + }, + "clock-source": { + "type": "integer", + "description": "Gateway clock source" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + } +} diff --git a/internal/schema/frequency-plans-gateway-schema.json.tmpl b/internal/schema/frequency-plans-gateway-schema.json.tmpl new file mode 100644 index 0000000..ab1fb52 --- /dev/null +++ b/internal/schema/frequency-plans-gateway-schema.json.tmpl @@ -0,0 +1,231 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Frequency Plan", + "type": "object", + "additionalProperties": false, + "properties": { + "band-id": { + "type": "string", + "description": "ID of the band (needs to match band-id in the index)", + "enum": [{{.BandIDs}}] + }, + "sub-bands": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz] (inclusive)" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz] (inclusive)" + }, + "duty-cycle": { + "type": "number", + "description": "Duty cycle for this sub-band (optional; default: 1)" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp)" + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "channels": { + "type": "array", + "description": "List of uplink channels (zero indexed)", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "uplink-frequency": { + "type": "integer", + "description": "Uplink frequency [Hz]" + }, + "downlink-frequency": { + "type": "integer", + "description": "Downlink frequency [Hz] (optional)" + }, + "min-data-rate": { + "type": "string", + "description": "Minimum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "max-data-rate": { + "type": "string", + "description": "Maximum data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + }, + "default": { + "type": "boolean", + "description": "Channel is defined by RP" + } + }, + "required": ["uplink-frequency", "min-data-rate", "max-data-rate", "radio"] + } + }, + "lora-standard-channel": { + "type": "object", + "description": "LoRa standard channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["SF7BW125","SF7BW250","SF8BW125","SF8BW500","SF9BW125","SF9BW500","SF10BW125","SF10BW500","SF11BW125","SF11BW500","SF12BW125","SF12BW500"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "fsk-channel": { + "type": "object", + "description": "FSK channel (optional)", + "additionalProperties": false, + "properties": { + "frequency": { + "type": "integer", + "description": "Frequency [Hz]" + }, + "data-rate": { + "type": "string", + "description": "Data rate index", + "enum": ["FSK50"] + }, + "radio": { + "type": "integer", + "description": "Radio index" + } + }, + "required": ["frequency", "data-rate", "radio"] + }, + "time-off-air": { + "type": "object", + "description": "Time-off-air (optional)", + "additionalProperties": false, + "properties": { + "fraction": { + "type": "number", + "description": "Minimum fraction of the emission time (optional)" + }, + "duration": { + "type": "string", + "description": "Minimum duration (optional)" + } + } + }, + "dwell-time": { + "type": "object", + "description": "Dwell time (optional)", + "additionalProperties": false, + "properties": { + "uplinks": { + "type": "boolean", + "description": "Enabled for uplink" + }, + "downlinks": { + "type": "boolean", + "description": "Enabled for downlink" + }, + "duration": { + "type": "string", + "description": "Duration" + } + }, + "required": ["uplinks", "downlinks", "duration"] + }, + "listen-before-talk": { + "type": "object", + "description": "Listen-before-talk (optional)", + "additionalProperties": false, + "properties": { + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "rssi-target": { + "type": "integer", + "description": "RSSI target [dbm]" + }, + "scan-time": { + "type": "integer", + "description": "Scan time [nanoseconds]" + } + }, + "required": ["rssi-offset", "rssi-target", "scan-time"] + }, + "radios": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable": { + "type": "boolean", + "description": "Enable the radio" + }, + "chip-type": { + "type": "string", + "description": "Chip type", + "enum": ["SX1255", "SX1257"] + }, + "frequency": { + "type": "integer", + "description": "Center frequency [Hz]" + }, + "rssi-offset": { + "type": "integer", + "description": "RSSI offset [dbm]" + }, + "tx": { + "type": "object", + "description": "Radio transmission configuration (optional)", + "additionalProperties": false, + "properties": { + "min-frequency": { + "type": "integer", + "description": "Minimum frequency [Hz]" + }, + "max-frequency": { + "type": "integer", + "description": "Maximum frequency [Hz]" + }, + "notch-frequency": { + "type": "integer", + "description": "Notch frequency 126000..250000 [Hz] (optional)", + "minimum": 126000, + "maximum": 250000 + } + }, + "required": ["min-frequency", "max-frequency"] + } + }, + "required": ["enable", "chip-type", "frequency", "rssi-offset"] + } + }, + "clock-source": { + "type": "integer", + "description": "Gateway clock source" + }, + "max-eirp": { + "type": "number", + "description": "Maximum EIRP (optional; used when sub-bands do not have max-eirp, takes precedence over band's default)" + } + }, + "required": ["band-id", "sub-bands", "channels"] +} diff --git a/internal/schema/schema.go b/internal/schema/schema.go new file mode 100644 index 0000000..8c0e732 --- /dev/null +++ b/internal/schema/schema.go @@ -0,0 +1,117 @@ +package schema + +import ( + "bytes" + "embed" + "os" + "sort" + "text/template" + + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/utils" +) + +//go:embed "*.tmpl" +var fsys embed.FS + +var ( + tmpl = template.Must(template.ParseFS(fsys, "*.tmpl")) + schema = Schema{ + BandIDs: getBandIDs(), + PhyVersions: getPhyVersions(), + } +) + +type Schema struct { + BandIDs string + EndDeviceBaseIDs string + GatewayBaseIDs string + EndDeviceModifiers string + GatewayModifiers string + EndDeviceFiles string + GatewayFiles string + PhyVersions string +} + +func Generate() error { + var err error + schema.EndDeviceBaseIDs, schema.GatewayBaseIDs, err = getBaseIDs() + if err != nil { + return err + } + schema.EndDeviceModifiers, err = getYamlFileNames("end-device/modifiers") + if err != nil { + return err + } + schema.GatewayModifiers, err = getYamlFileNames("gateway/modifiers") + if err != nil { + return err + } + schema.EndDeviceFiles, err = getYamlFileNames("end-device") + if err != nil { + return err + } + schema.GatewayFiles, err = getYamlFileNames("gateway") + if err != nil { + return err + } + + executeTemplate("frequency-plans-description-schema.json.tmpl", "schema.json") + executeTemplate("frequency-plans-end-device-schema.json.tmpl", "end-device/schema.json") + executeTemplate("frequency-plans-end-device-modifiers-schema.json.tmpl", "end-device/modifiers/schema.json") + executeTemplate("frequency-plans-gateway-schema.json.tmpl", "gateway/schema.json") + executeTemplate("frequency-plans-gateway-modifiers-schema.json.tmpl", "gateway/modifiers/schema.json") + + return nil +} + +func executeTemplate(file, output string) error { + var buf bytes.Buffer + if err := tmpl.ExecuteTemplate(&buf, file, schema); err != nil { + return err + } + if err := os.WriteFile(output, buf.Bytes(), 0o644); err != nil { + return err + } + return nil +} + +func getBaseIDs() (string, string, error) { + endDevice, gateway, err := utils.GetBaseIDs() + if err != nil { + return "", "", err + } + return buildCSV(endDevice), buildCSV(gateway), nil +} + +func getBandIDs() string { + return buildCSV(utils.GetBandIDs()) +} + +func getYamlFileNames(folder string) (string, error) { + filenames, err := utils.GetYamlFileNames(folder) + if err != nil { + return "", err + } + return buildCSV(filenames), nil +} + +func getPhyVersions() string { + return buildCSV(utils.GetPhyVersions()) +} + +func buildCSV(inputs map[string]bool) (output string) { + keys := []string{} + for input := range inputs { + keys = append(keys, input) + } + sort.Strings(keys) + first := true + for _, key := range keys { + if !first { + output += "," + } + output += "\"" + key + "\"" + first = false + } + return output +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..46d5196 --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,77 @@ +package utils + +import ( + "os" + "strings" + + "go.thethings.network/lorawan-stack/v3/pkg/band" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + "gopkg.in/yaml.v2" +) + +// GetBaseIDs returns the base ids for end-devices and gateways +func GetBaseIDs() (map[string]bool, map[string]bool, error) { + plans := struct { + EndDevices []struct { + ID string `yaml:"id"` + } `yaml:"end-device-descriptions"` + Gateways []struct { + ID string `yaml:"id"` + } `yaml:"gateway-descriptions"` + }{} + indexBytes, err := os.ReadFile("frequency-plans.yml") + if err != nil { + return nil, nil, err + } + err = yaml.Unmarshal(indexBytes, &plans) + if err != nil { + return nil, nil, err + } + + EndDeviceBaseIDs := make(map[string]bool) + for _, description := range plans.EndDevices { + if description.ID == "" { + continue + } + EndDeviceBaseIDs[description.ID] = true + } + GatewayBaseIDs := make(map[string]bool) + for _, description := range plans.EndDevices { + if description.ID == "" { + continue + } + GatewayBaseIDs[description.ID] = true + } + return EndDeviceBaseIDs, GatewayBaseIDs, nil +} + +func GetBandIDs() map[string]bool { + bandIDs := make(map[string]bool) + for band := range band.All { + bandIDs[band] = true + } + return bandIDs +} + +func GetYamlFileNames(folder string) (map[string]bool, error) { + fileNames := make(map[string]bool) + files, err := os.ReadDir(folder) + if err != nil { + return nil, err + } + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".yml") { + continue + } + fileNames[file.Name()] = true + } + return fileNames, nil +} + +func GetPhyVersions() map[string]bool { + phyVersions := make(map[string]bool) + for _, phyVersion := range ttnpb.PHYVersion_name { + phyVersions[phyVersion] = true + } + return phyVersions +} diff --git a/internal/validate/validate.go b/internal/validate/validate.go new file mode 100644 index 0000000..08bb5b1 --- /dev/null +++ b/internal/validate/validate.go @@ -0,0 +1,57 @@ +package validate + +import ( + "fmt" + + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/model" + "github.com/TheThingsNetwork/lorawan-frequency-plans/internal/utils" +) + +// Validate validates the defined frequency-plans. +func Validate() error { + if err := validateFolder(model.FrequencyPlanEndDevice{}, "end-device"); err != nil { + return err + } + if err := validateFolder(model.FrequencyPlanEndDeviceModifier{}, "end-device/modifiers"); err != nil { + return err + } + if err := validateFolder(model.FrequencyPlanGateway{}, "gateway"); err != nil { + return err + } + if err := validateFolder(model.FrequencyPlanGatewayModifier{}, "gateway/modifiers"); err != nil { + return err + } + if err := validateFile(model.FrequencyPlanDescriptions{}, "frequency-plans.yml"); err != nil { + return err + } + return nil +} + +func validateFile(def model.Definition, file string) error { + definition, err := def.Parse(file) + if err != nil { + return err + } + return definition.Validate() +} + +func validateFolder(def model.Definition, folder string) error { + files, err := utils.GetYamlFileNames(folder) + if err != nil { + return err + } + for file := range files { + if file == "" { + continue + } + definition, err := def.Parse(folder + "/" + file) + if err != nil { + return err + } + err = definition.Validate() + if err != nil { + return fmt.Errorf("%s/%s: %s", folder, file, err) + } + } + return nil +} diff --git a/lbt_80_over_128.yml b/lbt_80_over_128.yml deleted file mode 100644 index 5da831d..0000000 --- a/lbt_80_over_128.yml +++ /dev/null @@ -1,4 +0,0 @@ -listen-before-talk: - rssi-offset: -4 - rssi-target: -80 - scan-time: 128000 diff --git a/AS_920_923.yml b/legacy/AS_920_923.yml similarity index 100% rename from AS_920_923.yml rename to legacy/AS_920_923.yml diff --git a/AS_920_923_TTN_AU.yml b/legacy/AS_920_923_TTN_AU.yml similarity index 100% rename from AS_920_923_TTN_AU.yml rename to legacy/AS_920_923_TTN_AU.yml diff --git a/AS_920_923_TTN_JP_1.yml b/legacy/AS_920_923_TTN_JP_1.yml similarity index 100% rename from AS_920_923_TTN_JP_1.yml rename to legacy/AS_920_923_TTN_JP_1.yml diff --git a/AS_920_923_TTN_JP_1_LAND_MOBILE.yml b/legacy/AS_920_923_TTN_JP_1_LAND_MOBILE.yml similarity index 100% rename from AS_920_923_TTN_JP_1_LAND_MOBILE.yml rename to legacy/AS_920_923_TTN_JP_1_LAND_MOBILE.yml diff --git a/AS_920_923_TTN_JP_2.yml b/legacy/AS_920_923_TTN_JP_2.yml similarity index 100% rename from AS_920_923_TTN_JP_2.yml rename to legacy/AS_920_923_TTN_JP_2.yml diff --git a/AS_920_923_TTN_JP_3.yml b/legacy/AS_920_923_TTN_JP_3.yml similarity index 100% rename from AS_920_923_TTN_JP_3.yml rename to legacy/AS_920_923_TTN_JP_3.yml diff --git a/AS_920_923_TTN_JP_3_LAND_MOBILE.yml b/legacy/AS_920_923_TTN_JP_3_LAND_MOBILE.yml similarity index 100% rename from AS_920_923_TTN_JP_3_LAND_MOBILE.yml rename to legacy/AS_920_923_TTN_JP_3_LAND_MOBILE.yml diff --git a/AS_923.yml b/legacy/AS_923.yml similarity index 100% rename from AS_923.yml rename to legacy/AS_923.yml diff --git a/AS_923_2.yml b/legacy/AS_923_2.yml similarity index 100% rename from AS_923_2.yml rename to legacy/AS_923_2.yml diff --git a/AS_923_3.yml b/legacy/AS_923_3.yml similarity index 100% rename from AS_923_3.yml rename to legacy/AS_923_3.yml diff --git a/AS_923_4.yml b/legacy/AS_923_4.yml similarity index 100% rename from AS_923_4.yml rename to legacy/AS_923_4.yml diff --git a/AS_923_925.yml b/legacy/AS_923_925.yml similarity index 100% rename from AS_923_925.yml rename to legacy/AS_923_925.yml diff --git a/AS_923_925_TTN_AU.yml b/legacy/AS_923_925_TTN_AU.yml similarity index 100% rename from AS_923_925_TTN_AU.yml rename to legacy/AS_923_925_TTN_AU.yml diff --git a/AU_915_928_FSB_1.yml b/legacy/AU_915_928_FSB_1.yml similarity index 100% rename from AU_915_928_FSB_1.yml rename to legacy/AU_915_928_FSB_1.yml diff --git a/AU_915_928_FSB_2.yml b/legacy/AU_915_928_FSB_2.yml similarity index 100% rename from AU_915_928_FSB_2.yml rename to legacy/AU_915_928_FSB_2.yml diff --git a/AU_915_928_FSB_3.yml b/legacy/AU_915_928_FSB_3.yml similarity index 100% rename from AU_915_928_FSB_3.yml rename to legacy/AU_915_928_FSB_3.yml diff --git a/AU_915_928_FSB_4.yml b/legacy/AU_915_928_FSB_4.yml similarity index 100% rename from AU_915_928_FSB_4.yml rename to legacy/AU_915_928_FSB_4.yml diff --git a/AU_915_928_FSB_5.yml b/legacy/AU_915_928_FSB_5.yml similarity index 100% rename from AU_915_928_FSB_5.yml rename to legacy/AU_915_928_FSB_5.yml diff --git a/AU_915_928_FSB_6.yml b/legacy/AU_915_928_FSB_6.yml similarity index 100% rename from AU_915_928_FSB_6.yml rename to legacy/AU_915_928_FSB_6.yml diff --git a/AU_915_928_FSB_7.yml b/legacy/AU_915_928_FSB_7.yml similarity index 100% rename from AU_915_928_FSB_7.yml rename to legacy/AU_915_928_FSB_7.yml diff --git a/AU_915_928_FSB_8.yml b/legacy/AU_915_928_FSB_8.yml similarity index 100% rename from AU_915_928_FSB_8.yml rename to legacy/AU_915_928_FSB_8.yml diff --git a/CN_470_510_FSB_1.yml b/legacy/CN_470_510_FSB_1.yml similarity index 100% rename from CN_470_510_FSB_1.yml rename to legacy/CN_470_510_FSB_1.yml diff --git a/CN_470_510_FSB_11.yml b/legacy/CN_470_510_FSB_11.yml similarity index 100% rename from CN_470_510_FSB_11.yml rename to legacy/CN_470_510_FSB_11.yml diff --git a/EU_433.yml b/legacy/EU_433.yml similarity index 100% rename from EU_433.yml rename to legacy/EU_433.yml diff --git a/EU_863_870.yml b/legacy/EU_863_870.yml similarity index 100% rename from EU_863_870.yml rename to legacy/EU_863_870.yml diff --git a/EU_863_870_ROAMING_DRAFT.yml b/legacy/EU_863_870_ROAMING_DRAFT.yml similarity index 100% rename from EU_863_870_ROAMING_DRAFT.yml rename to legacy/EU_863_870_ROAMING_DRAFT.yml diff --git a/legacy/EU_863_870_TTN.yml b/legacy/EU_863_870_TTN.yml new file mode 100644 index 0000000..ca5239f --- /dev/null +++ b/legacy/EU_863_870_TTN.yml @@ -0,0 +1 @@ +rx2-default-data-rate: 3 diff --git a/IN_865_867.yml b/legacy/IN_865_867.yml similarity index 100% rename from IN_865_867.yml rename to legacy/IN_865_867.yml diff --git a/ISM_2400_3CH_DRAFT2.yml b/legacy/ISM_2400_3CH_DRAFT2.yml similarity index 100% rename from ISM_2400_3CH_DRAFT2.yml rename to legacy/ISM_2400_3CH_DRAFT2.yml diff --git a/KR_920_923_TTN.yml b/legacy/KR_920_923_TTN.yml similarity index 100% rename from KR_920_923_TTN.yml rename to legacy/KR_920_923_TTN.yml diff --git a/MA_869_870_DRAFT.yml b/legacy/MA_869_870_DRAFT.yml similarity index 100% rename from MA_869_870_DRAFT.yml rename to legacy/MA_869_870_DRAFT.yml diff --git a/RU_864_870_TTN.yml b/legacy/RU_864_870_TTN.yml similarity index 100% rename from RU_864_870_TTN.yml rename to legacy/RU_864_870_TTN.yml diff --git a/US_902_928_FSB_1.yml b/legacy/US_902_928_FSB_1.yml similarity index 100% rename from US_902_928_FSB_1.yml rename to legacy/US_902_928_FSB_1.yml diff --git a/US_902_928_FSB_2.yml b/legacy/US_902_928_FSB_2.yml similarity index 100% rename from US_902_928_FSB_2.yml rename to legacy/US_902_928_FSB_2.yml diff --git a/US_902_928_FSB_3.yml b/legacy/US_902_928_FSB_3.yml similarity index 100% rename from US_902_928_FSB_3.yml rename to legacy/US_902_928_FSB_3.yml diff --git a/US_902_928_FSB_4.yml b/legacy/US_902_928_FSB_4.yml similarity index 100% rename from US_902_928_FSB_4.yml rename to legacy/US_902_928_FSB_4.yml diff --git a/US_902_928_FSB_5.yml b/legacy/US_902_928_FSB_5.yml similarity index 100% rename from US_902_928_FSB_5.yml rename to legacy/US_902_928_FSB_5.yml diff --git a/US_902_928_FSB_6.yml b/legacy/US_902_928_FSB_6.yml similarity index 100% rename from US_902_928_FSB_6.yml rename to legacy/US_902_928_FSB_6.yml diff --git a/US_902_928_FSB_7.yml b/legacy/US_902_928_FSB_7.yml similarity index 100% rename from US_902_928_FSB_7.yml rename to legacy/US_902_928_FSB_7.yml diff --git a/US_902_928_FSB_8.yml b/legacy/US_902_928_FSB_8.yml similarity index 100% rename from US_902_928_FSB_8.yml rename to legacy/US_902_928_FSB_8.yml diff --git a/legacy/frequency-plans.yml b/legacy/frequency-plans.yml new file mode 100644 index 0000000..255fadd --- /dev/null +++ b/legacy/frequency-plans.yml @@ -0,0 +1,649 @@ +- id: EU_863_870 + band-id: EU_863_870 + name: Europe 863-870 MHz (SF12 for RX2) + description: Default frequency plan for Europe + base-frequency: 868 + country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] + file: EU_863_870.yml + +- id: EU_863_870_TTN + band-id: EU_863_870 + name: Europe 863-870 MHz (SF9 for RX2 - recommended) + description: TTN Community Network frequency plan for Europe, using SF9 for RX2 + base-frequency: 868 + base-id: EU_863_870 + country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] + file: EU_863_870_TTN.yml + +- id: EU_863_870_ROAMING_DRAFT + band-id: EU_863_870 + name: Europe 863-870 MHz, 6 channels for roaming (Draft) + description: European 6 channel plan used by major operators to support LoRaWAN Passive Roaming + base-frequency: 868 + base-id: EU_863_870 + country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] + file: EU_863_870_ROAMING_DRAFT.yml + +- id: EU_433 + band-id: EU_433 + name: Europe 433 MHz (ITU region 1) + description: Default frequency plan for worldwide 433MHz + base-frequency: 433 + file: EU_433.yml + +- id: US_902_928_FSB_1 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 1 + description: Default frequency plan for the United States and Canada, using sub-band 1 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_1.yml + +- id: US_902_928_FSB_2 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 2 (used by TTN) + description: TTN Community Network frequency plan for the United States and Canada, using sub-band 2 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_2.yml + +- id: US_902_928_FSB_3 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 3 + description: Default frequency plan for the United States and Canada, using sub-band 3 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_3.yml + +- id: US_902_928_FSB_4 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 4 + description: Default frequency plan for the United States and Canada, using sub-band 4 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_4.yml + +- id: US_902_928_FSB_5 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 5 + description: Default frequency plan for the United States and Canada, using sub-band 5 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_5.yml + +- id: US_902_928_FSB_6 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 6 + description: Default frequency plan for the United States and Canada, using sub-band 6 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_6.yml + +- id: US_902_928_FSB_7 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 7 + description: Default frequency plan for the United States and Canada, using sub-band 7 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_7.yml + +- id: US_902_928_FSB_8 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 8 + description: Default frequency plan for the United States and Canada, using sub-band 8 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_8.yml + +- id: AU_915_928_FSB_1 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 1 + description: Default frequency plan for Australia, using sub-band 1 + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_1.yml + +- id: AU_915_928_FSB_2 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 2 (used by TTN) + description: TTN Community Network frequency plan for Australia, using sub-band 2 + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_2.yml + +- id: AU_915_928_FSB_3 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 3 + description: Default frequency plan for Australia, using sub-band 3 + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_3.yml + +- id: AU_915_928_FSB_4 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 4 + description: Default frequency plan for Australia, using sub-band 4 + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_4.yml + +- id: AU_915_928_FSB_5 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 5 + description: Default frequency plan for Australia, using sub-band 5 + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_5.yml + +- id: AU_915_928_FSB_6 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 6 + description: Frequency plan for Australia, using sub-band 6, which overlaps with Asia 923-925 MHz + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_6.yml + +- id: AU_915_928_FSB_7 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 7 + description: Default frequency plan for Australia, using sub-band 7 + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_7.yml + +- id: AU_915_928_FSB_8 + band-id: AU_915_928 + name: Australia 915-928 MHz, FSB 8 + description: Default frequency plan for Australia, using sub-band 8 + base-frequency: 915 + country-codes: [ar, au, bo, br, ca, cl, co, do, nz, py, pe, sr, uy] + file: AU_915_928_FSB_8.yml + +- id: CN_470_510_FSB_1 + band-id: CN_470_510 + name: China 470-510 MHz, FSB 1 + description: Default frequency plan for China, using sub-band 1 + base-frequency: 470 + country-codes: [cn] + file: CN_470_510_FSB_1.yml + +- id: CN_470_510_FSB_11 + band-id: CN_470_510 + name: China 470-510 MHz, FSB 11 (used by TTN) + description: TTN Community Network frequency plan for China, using sub-band 11 + base-frequency: 470 + country-codes: [cn] + file: CN_470_510_FSB_11.yml + +- id: AS_920_923 + band-id: AS_920_923 + name: Asia 920-923 MHz + description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz + base-frequency: 915 + country-codes: [my, sg] + file: AS_920_923.yml + +- id: AS_920_923_LBT + band-id: AS_920_923 + name: Asia 920-923 MHz with LBT + description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz with listen-before-talk + base-frequency: 915 + base-id: AS_920_923 + country-codes: [jp, my, sg] + file: lbt_80_over_128.yml + +- id: AS_920_923_TTN_JP_1 + name: Japan 920-923 MHz with LBT (channels 31-38) + description: Frequency plan for Japan, using continuous frequencies up to 923.4MHz with LBT. + base-frequency: 915 + country-codes: [jp] + file: AS_920_923_TTN_JP_1.yml + +- id: AS_920_923_TTN_JP_1_LAND_MOBILE + name: Japan 920-923 MHz with LBT (channels 31-38), Max EIRP 27 dBm + description: | + Frequency plan for Japanese land mobile station, using continuous frequencies up to 923.4MHz with MAX EIRP 27 dBm and LBT. + (note) A user who installs land mobile station in Japan must apply to Japanese Ministry of Internal Affairs and Communications. + base-frequency: 915 + base-id: AS_920_923_TTN_JP_1 + country-codes: [jp] + file: AS_920_923_TTN_JP_1_LAND_MOBILE.yml + +- id: AS_920_923_TTN_JP_2 + name: Japan 920-923 MHz with LBT (channels 24-27 and 35-38) + description: Frequency plan for Japan, using top and bottom frequencies ≤ 923.4 MHz with LBT. + base-frequency: 915 + country-codes: [jp] + file: AS_920_923_TTN_JP_2.yml + +- id: AS_920_923_TTN_JP_3 + name: Japan 920-923 MHz with LBT (channels 24-31) + description: Frequency plan for Japan (16 channels), using continuous frequencies from 920.6 MHz, expected to be used with AS_920_923_TTN_JP_1. + base-frequency: 915 + country-codes: [jp] + file: AS_920_923_TTN_JP_3.yml + +- id: AS_920_923_TTN_JP_3_LAND_MOBILE + name: Japan 920-923 MHz with LBT (channels 24-31), Max EIRP 27 dBm + description: | + Frequency plan for Japanese land mobile station (16 channels), using continuous frequencies from 920.6 MHz with MAX EIRP 27 dBm and LBT, expected to be used with AS_920_923_TTN_JP_1. + (note) A user who installs land mobile station in Japan must apply to Japanese Ministry of Internal Affairs and Communications. + base-frequency: 915 + base-id: AS_920_923_TTN_JP_3 + country-codes: [jp] + file: AS_920_923_TTN_JP_3_LAND_MOBILE.yml + +- id: AS_923 + band-id: AS_923 + name: Asia 915-928 MHz (AS923 Group 1) with only default channels + description: Compatibility frequency plan for Asian countries with common channels in the 923.0-923.5 MHz sub-band + base-frequency: 915 + file: AS_923.yml + +- id: AS_923_2 + band-id: AS_923_2 + name: Asia 920-923 MHz (AS923 Group 2) with only default channels + description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band + base-frequency: 915 + file: AS_923_2.yml + +- id: AS_923_3 + band-id: AS_923_3 + name: Asia 915-921 MHz (AS923 Group 3) with only default channels + description: Compatibility frequency plan for Asian countries with common channels in the 916.5-917.0 MHz sub-band + base-frequency: 915 + file: AS_923_3.yml + +- id: AS_923_4 + band-id: AS_923_4 + name: Asia 917-920 MHz (AS923 Group 4) with only default channels + description: Compatibility frequency plan for Asian countries with common channels in the 917.3-917.5 MHz sub-band + base-frequency: 915 + file: AS_923_4.yml + +- id: AS_923_NDT + band-id: AS_923 + name: Asia 915-928 MHz (AS923 Group 1) with only default channels and dwell time disabled + description: Compatibility frequency plan for Asian countries with common channels in the 923.0-923.5 MHz sub-band and dwell time disabled + base-frequency: 915 + base-id: AS_923 + file: disable_dwell_time.yml + +- id: AS_923_DT + band-id: AS_923 + name: Asia 915-928 MHz (AS923 Group 1) with only default channels and dwell time enabled + description: Compatibility frequency plan for Asian countries with common channels in the 923.0-923.5 MHz sub-band and dwell time enabled + base-frequency: 915 + base-id: AS_923 + file: enable_dwell_time_400ms.yml + +- id: AS_923_2_NDT + band-id: AS_923_2 + name: Asia 920-923 MHz (AS923 Group 2) with only default channels and dwell time disabled + description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time disabled + base-frequency: 915 + base-id: AS_923_2 + file: disable_dwell_time.yml + +- id: AS_923_2_DT + band-id: AS_923_2 + name: Asia 920-923 MHz (AS923 Group 2) with only default channels and dwell time enabled + description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time enabled + base-frequency: 915 + base-id: AS_923_2 + file: enable_dwell_time_400ms.yml + +- id: AS_923_3_NDT + band-id: AS_923_3 + name: Asia 920-923 MHz (AS923 Group 3) with only default channels and dwell time disabled + description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time disabled + base-frequency: 915 + base-id: AS_923_3 + file: disable_dwell_time.yml + +- id: AS_923_3_DT + band-id: AS_923_3 + name: Asia 920-923 MHz (AS923 Group 3) with only default channels and dwell time enabled + description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time enabled + base-frequency: 915 + base-id: AS_923_3 + file: enable_dwell_time_400ms.yml + +- id: AS_923_4_NDT + band-id: AS_923_4 + name: Asia 920-923 MHz (AS923 Group 4) with only default channels and dwell time disabled + description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time disabled + base-frequency: 915 + base-id: AS_923_4 + file: disable_dwell_time.yml + +- id: AS_923_4_DT + band-id: AS_923_4 + name: Asia 920-923 MHz (AS923 Group 4) with only default channels and dwell time enabled + description: Compatibility frequency plan for Asian countries with common channels in the 921.4-922.0 MHz sub-band and dwell time enabled + base-frequency: 915 + base-id: AS_923_4 + file: enable_dwell_time_400ms.yml + +- id: AS_923_925 + band-id: AS_923_925 + name: Asia 923-925 MHz + description: TTN Community Network frequency plan for Asian countries, using frequencies ≥ 923 MHz + base-frequency: 915 + country-codes: [bn, kh, hk, id, la, tw, th, vn] + file: AS_923_925.yml + +- id: AS_923_925_LBT + band-id: AS_923_925 + name: Asia 923-925 MHz with LBT + description: TTN Community Network frequency plan for Asian countries, using frequencies ≥ 923 MHz with listen-before-talk + base-frequency: 915 + base-id: AS_923_925 + country-codes: [bn, kh, hk, id, la, tw, th, vn] + file: lbt_80_over_128.yml + +- id: AS_920_923_TTN_AU + band-id: AS_923_925 + name: Asia 920-923 MHz (used by TTN Australia) + description: TTN Community Network frequency plan for Asia 920-923 MHz in Australia + base-frequency: 915 + base-id: AS_920_923 + country-codes: [au] + file: AS_920_923_TTN_AU.yml + +- id: AS_923_925_TTN_AU + band-id: AS_923_925 + name: Asia 923-925 MHz (used by TTN Australia - secondary channels) + description: TTN Community Network frequency plan for Asia 923-925 MHz in Australia. Secondary channels for 16 channel gateways. + base-frequency: 915 + base-id: AS_923_925 + country-codes: [au] + file: AS_923_925_TTN_AU.yml + +- id: KR_920_923_TTN + band-id: KR_920_923 + name: South Korea 920-923 MHz + description: TTN Community Network frequency plan for South Korea + base-frequency: 915 + country-codes: [kr] + file: KR_920_923_TTN.yml + +- id: MA_869_870_DRAFT + band-id: MA_869_870_DRAFT + name: Morocco 869-870 MHz + description: Draft frequency plan for Morocco, with 4 channels + base-frequency: 868 + country-codes: [ma] + file: MA_869_870_DRAFT.yml + +- id: IN_865_867 + band-id: IN_865_867 + name: India 865-867 MHz + description: Default frequency plan for India + base-frequency: 868 + country-codes: [in] + file: IN_865_867.yml + +- id: RU_864_870_TTN + band-id: RU_864_870 + name: Russia 864-870 MHz + description: TTN Community Network frequency plan for Russia + base-frequency: 868 + country-codes: [ru] + file: RU_864_870_TTN.yml + +- id: ISM_2400_3CH_DRAFT2 + band-id: ISM_2400 + name: LoRa 2.4 GHz with 3 channels (Draft 2) + description: Global 3 channel plan for LoRa 2.4 GHz (Draft 2) + base-frequency: 2450 + country-codes: + [ + af, + ax, + al, + dz, + as, + ad, + ao, + ai, + aq, + ag, + ar, + am, + aw, + au, + at, + az, + bs, + bh, + bd, + bb, + by, + be, + bz, + bj, + bm, + bt, + bo, + ba, + bw, + bv, + br, + io, + bn, + bg, + bf, + bi, + kh, + cm, + ca, + cv, + ky, + cf, + td, + cl, + cn, + cx, + cc, + co, + km, + cg, + cd, + ck, + cr, + ci, + hr, + cu, + cy, + cz, + dk, + dj, + dm, + do, + ec, + eg, + sv, + gq, + er, + ee, + et, + fk, + fo, + fj, + fi, + fr, + gf, + pf, + tf, + ga, + gm, + ge, + de, + gh, + gi, + gr, + gl, + gd, + gp, + gu, + gt, + gg, + gn, + gw, + gy, + ht, + hm, + va, + hn, + hk, + hu, + is, + in, + id, + ir, + iq, + ie, + im, + il, + it, + jm, + jp, + je, + jo, + kz, + ke, + ki, + kp, + kr, + kw, + kg, + la, + lv, + lb, + ls, + lr, + ly, + li, + lt, + lu, + mo, + mk, + mg, + mw, + my, + mv, + ml, + mt, + mh, + mq, + mr, + mu, + yt, + mx, + fm, + md, + mc, + mn, + me, + ms, + ma, + mz, + mm, + na, + nr, + np, + nl, + an, + nc, + nz, + ni, + ne, + ng, + nu, + nf, + mp, + no, + om, + pk, + pw, + ps, + pa, + pg, + py, + pe, + ph, + pn, + pl, + pt, + pr, + qa, + re, + ro, + ru, + rw, + bl, + sh, + kn, + lc, + mf, + pm, + vc, + ws, + sm, + st, + sa, + sn, + rs, + sc, + sl, + sg, + sk, + si, + sb, + so, + za, + gs, + es, + lk, + sd, + sr, + sj, + sz, + se, + ch, + sy, + tw, + tj, + tz, + th, + tl, + tg, + tk, + to, + tt, + tn, + tr, + tm, + tc, + tv, + ug, + ua, + ae, + gb, + us, + um, + uy, + uz, + vu, + ve, + vn, + vg, + vi, + wf, + eh, + ye, + zm, + zw, + ] + file: ISM_2400_3CH_DRAFT2.yml diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..06c3ee6 --- /dev/null +++ b/schema.json @@ -0,0 +1,676 @@ +{ + "title": "Frequency Plan", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "end-device-descriptions": { + "type": "array", + "items": { + "$ref": "#/definitions/EndDeviceFrequencyPlan" + } + }, + "gateway-descriptions": { + "type": "array", + "items": { + "$ref": "#/definitions/GatewayFrequencyPlan" + } + } + }, + "required": ["end-device-descriptions", "gateway-descriptions"], + "definitions": { + "EndDeviceFrequencyPlan": { + "type": "object", + "additionalProperties": false, + "required": ["id", "band-id", "name", "description", "country-codes", "endorsed"], + "oneOf": [ + { + "required": ["file"], + "not": { + "anyOf": [{ "required": ["base-id"] }, { "required": ["modifiers"] }] + } + }, + { + "required": ["base-id", "modifiers"], + "not": { + "anyOf": [{ "required": ["file"] }] + } + } + ], + "properties": { + "id": { + "type": "string", + "description": "ID of the frequency plan." + }, + "band-id": { + "type": "string", + "description": "ID of the LoRaWAN band (needs to match band-id in the definition)", + "enum" : ["AS_923","AS_923_2","AS_923_3","AS_923_4","AU_915_928","CN_470_510","CN_470_510_20_A","CN_470_510_20_B","CN_470_510_26_A","CN_470_510_26_B","CN_779_787","EU_433","EU_863_870","IN_865_867","ISM_2400","KR_920_923","MA_869_870_DRAFT","RU_864_870","US_902_928"] + }, + "name": { + "type": "string", + "description": "Name of the frequency plan. In the format '{Region} {Frequency range} MHz'." + }, + "description": { + "type": "string", + "description": "Description of the frequency plan." + }, + "base-frequency": { + "type": "integer", + "description": "Base frequency in MHz for hardware support (433, 470, 868 or 915) [MHz]", + "enum": [433, 470, 868, 915, 2450] + }, + "file": { + "type": "string", + "description": "File of the frequency plan definition.", + "enum": ["EU_863_870.yml"] + }, + "base-id": { + "type": "string", + "description": "ID that this frequency plan extends (refers to id of another frequency plan).", + "enum" : ["EU_863_870","EU_863_870_TTN"] + }, + "modifiers": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum":["rx2_default_data_rata_3.yml"], + "description": "Modifier addressed by filename." + }, + "description": "List of files containing the modifiers used to extend the `base-id` end-device." + }, + "endorsed": { + "type": "boolean", + "description": "This is one of the endorsed frequency plans for this band-id." + }, + "country-codes": { + "type": "array", + "uniqueItems": true, + "description": "Countries that this frequency plan can be used in.", + "items": { + "type": "string", + "enum": [ + "af", + "ax", + "al", + "dz", + "as", + "ad", + "ao", + "ai", + "aq", + "ag", + "ar", + "am", + "an", + "aw", + "au", + "at", + "az", + "bh", + "bs", + "bd", + "bb", + "by", + "be", + "bz", + "bj", + "bm", + "bt", + "bo", + "bq", + "ba", + "bw", + "bv", + "br", + "io", + "bn", + "bg", + "bf", + "bi", + "kh", + "cm", + "ca", + "cv", + "ky", + "cf", + "td", + "cl", + "cn", + "cx", + "cc", + "co", + "km", + "cg", + "cd", + "ck", + "cr", + "ci", + "hr", + "cu", + "cw", + "cy", + "cz", + "dk", + "dj", + "dm", + "do", + "ec", + "eg", + "sv", + "gq", + "er", + "ee", + "et", + "fk", + "fo", + "fj", + "fi", + "fr", + "gf", + "pf", + "tf", + "ga", + "gm", + "ge", + "de", + "gh", + "gi", + "gr", + "gl", + "gd", + "gp", + "gu", + "gt", + "gg", + "gn", + "gw", + "gy", + "ht", + "hm", + "va", + "hn", + "hk", + "hu", + "is", + "in", + "id", + "ir", + "iq", + "ie", + "im", + "il", + "it", + "jm", + "jp", + "je", + "jo", + "kz", + "ke", + "ki", + "kp", + "kr", + "kw", + "kg", + "la", + "lv", + "lb", + "ls", + "lr", + "ly", + "li", + "lt", + "lu", + "mo", + "mk", + "mg", + "mw", + "my", + "mv", + "ml", + "mt", + "mh", + "mq", + "mr", + "mu", + "yt", + "mx", + "fm", + "md", + "mc", + "mn", + "me", + "ms", + "ma", + "mz", + "mm", + "na", + "nr", + "np", + "nl", + "nc", + "nz", + "ni", + "ne", + "ng", + "nu", + "nf", + "mp", + "no", + "om", + "pk", + "pw", + "ps", + "pa", + "pg", + "py", + "pe", + "ph", + "pn", + "pl", + "pt", + "pr", + "qa", + "re", + "ro", + "ru", + "rw", + "bl", + "sh", + "kn", + "lc", + "mf", + "pm", + "vc", + "ws", + "sm", + "st", + "sa", + "sn", + "rs", + "sc", + "sl", + "sg", + "sx", + "sk", + "si", + "sb", + "so", + "za", + "gs", + "ss", + "es", + "lk", + "sd", + "sr", + "sj", + "sz", + "se", + "ch", + "sy", + "tw", + "tj", + "tz", + "th", + "tl", + "tg", + "tk", + "to", + "tt", + "tn", + "tr", + "tm", + "tc", + "tv", + "ug", + "ua", + "ae", + "gb", + "us", + "um", + "uy", + "uz", + "vu", + "ve", + "vn", + "vg", + "vi", + "wf", + "eh", + "ye", + "zm", + "zw" + ] + } + } + } + }, + "GatewayFrequencyPlan": { + "type": "object", + "additionalProperties": false, + "required": ["id", "band-id", "name", "description", "country-codes", "endorsed"], + "oneOf": [ + { + "required": ["file"], + "not": { + "anyOf": [{ "required": ["base-id"] }, { "required": ["modifiers"] }] + } + }, + { + "required": ["base-id", "modifiers"], + "not": { + "anyOf": [{ "required": ["file"] }] + } + } + ], + "properties": { + "id": { + "type": "string", + "description": "ID of the frequency plan." + }, + "band-id": { + "type": "string", + "description": "ID of the LoRaWAN band (needs to match band-id in the definition)", + "enum" : ["AS_923","AS_923_2","AS_923_3","AS_923_4","AU_915_928","CN_470_510","CN_470_510_20_A","CN_470_510_20_B","CN_470_510_26_A","CN_470_510_26_B","CN_779_787","EU_433","EU_863_870","IN_865_867","ISM_2400","KR_920_923","MA_869_870_DRAFT","RU_864_870","US_902_928"] + }, + "name": { + "type": "string", + "description": "Name of the frequency plan. In the format '{Region} {Frequency range} MHz'." + }, + "description": { + "type": "string", + "description": "Description of the frequency plan." + }, + "base-frequency": { + "type": "integer", + "description": "Base frequency in MHz for hardware support (433, 470, 868 or 915) [MHz]", + "enum": [433, 470, 868, 915, 2450] + }, + "file": { + "type": "string", + "description": "File of the frequency plan definition.", + "enum": ["EU_863_870.yml"] + }, + "base-id": { + "type": "string", + "description": "ID that this frequency plan extends (refers to id of another frequency plan).", + "enum" : ["EU_863_870","EU_863_870_TTN"] + }, + "modifiers": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum":[], + "description": "Modifier addressed by filename." + }, + "description": "List of files containing the modifiers used to extend the `base-id` end-device." + }, + "endorsed": { + "type": "boolean", + "description": "This is one of the endorsed frequency plans for this band-id." + }, + "country-codes": { + "type": "array", + "uniqueItems": true, + "description": "Countries that this frequency plan can be used in.", + "items": { + "type": "string", + "enum": [ + "af", + "ax", + "al", + "dz", + "as", + "ad", + "ao", + "ai", + "aq", + "ag", + "ar", + "am", + "an", + "aw", + "au", + "at", + "az", + "bh", + "bs", + "bd", + "bb", + "by", + "be", + "bz", + "bj", + "bm", + "bt", + "bo", + "bq", + "ba", + "bw", + "bv", + "br", + "io", + "bn", + "bg", + "bf", + "bi", + "kh", + "cm", + "ca", + "cv", + "ky", + "cf", + "td", + "cl", + "cn", + "cx", + "cc", + "co", + "km", + "cg", + "cd", + "ck", + "cr", + "ci", + "hr", + "cu", + "cw", + "cy", + "cz", + "dk", + "dj", + "dm", + "do", + "ec", + "eg", + "sv", + "gq", + "er", + "ee", + "et", + "fk", + "fo", + "fj", + "fi", + "fr", + "gf", + "pf", + "tf", + "ga", + "gm", + "ge", + "de", + "gh", + "gi", + "gr", + "gl", + "gd", + "gp", + "gu", + "gt", + "gg", + "gn", + "gw", + "gy", + "ht", + "hm", + "va", + "hn", + "hk", + "hu", + "is", + "in", + "id", + "ir", + "iq", + "ie", + "im", + "il", + "it", + "jm", + "jp", + "je", + "jo", + "kz", + "ke", + "ki", + "kp", + "kr", + "kw", + "kg", + "la", + "lv", + "lb", + "ls", + "lr", + "ly", + "li", + "lt", + "lu", + "mo", + "mk", + "mg", + "mw", + "my", + "mv", + "ml", + "mt", + "mh", + "mq", + "mr", + "mu", + "yt", + "mx", + "fm", + "md", + "mc", + "mn", + "me", + "ms", + "ma", + "mz", + "mm", + "na", + "nr", + "np", + "nl", + "nc", + "nz", + "ni", + "ne", + "ng", + "nu", + "nf", + "mp", + "no", + "om", + "pk", + "pw", + "ps", + "pa", + "pg", + "py", + "pe", + "ph", + "pn", + "pl", + "pt", + "pr", + "qa", + "re", + "ro", + "ru", + "rw", + "bl", + "sh", + "kn", + "lc", + "mf", + "pm", + "vc", + "ws", + "sm", + "st", + "sa", + "sn", + "rs", + "sc", + "sl", + "sg", + "sx", + "sk", + "si", + "sb", + "so", + "za", + "gs", + "ss", + "es", + "lk", + "sd", + "sr", + "sj", + "sz", + "se", + "ch", + "sy", + "tw", + "tj", + "tz", + "th", + "tl", + "tg", + "tk", + "to", + "tt", + "tn", + "tr", + "tm", + "tc", + "tv", + "ug", + "ua", + "ae", + "gb", + "us", + "um", + "uy", + "uz", + "vu", + "ve", + "vn", + "vg", + "vi", + "wf", + "eh", + "ye", + "zm", + "zw" + ] + } + } + } + } + } +} From 152782b93deeb7e11bbb1f7dba765551b8c55202 Mon Sep 17 00:00:00 2001 From: Yorick Smilda Date: Thu, 2 Feb 2023 13:05:47 +0100 Subject: [PATCH 2/3] Add validation for modifiers --- internal/model/end-device-frequency-plan.go | 59 +++++++++++++++++++++ internal/model/gateway-frequency-plan.go | 41 ++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/internal/model/end-device-frequency-plan.go b/internal/model/end-device-frequency-plan.go index efd3705..e30f635 100644 --- a/internal/model/end-device-frequency-plan.go +++ b/internal/model/end-device-frequency-plan.go @@ -133,5 +133,64 @@ func (f FrequencyPlanEndDeviceModifier) Parse(file string) (Definition, error) { } func (f FrequencyPlanEndDeviceModifier) Validate() error { + if f.SubBands != nil { + for i, subBand := range *f.SubBands { + if err := subBand.Validate(); err != nil { + return fmt.Errorf("SubBand %d: %w", i, err) + } + } + } + if f.Channels != nil { + for i, channel := range *f.Channels { + if err := channel.Validate(); err != nil { + return fmt.Errorf("Channel %d: %w", i, err) + } + } + } + if f.LoRaStandardChannel != nil { + if err := f.LoRaStandardChannel.Validate(); err != nil { + return fmt.Errorf("LoRaStandardChannel: %w", err) + } + } + if f.FSKChannel != nil { + if err := f.FSKChannel.Validate(); err != nil { + return fmt.Errorf("FSKChannel: %w", err) + } + } + if f.TimeOffAir != nil { + if err := f.TimeOffAir.Validate(); err != nil { + return fmt.Errorf("TimeOffAir: %w", err) + } + } + if f.DwellTime != nil { + if err := f.DwellTime.Validate(); err != nil { + return fmt.Errorf("DwellTime: %w", err) + } + } + if f.ListenBeforeTalk != nil { + if err := f.ListenBeforeTalk.Validate(); err != nil { + return fmt.Errorf("ListenBeforeTalk: %w", err) + } + } + if f.PingSlot != nil { + if err := f.PingSlot.Validate(); err != nil { + return fmt.Errorf("PingSlot: %w", err) + } + } + if f.PingSlotDefaultDataRate != nil { + if err := validateDataRate(*f.PingSlotDefaultDataRate); err != nil { + return fmt.Errorf("PingSlotDefaultDataRate: %w", err) + } + } + if f.RX2Channel != nil { + if err := f.RX2Channel.Validate(); err != nil { + return fmt.Errorf("RX2Channel: %w", err) + } + } + if f.RX2DefaultDataRate != nil { + if err := validateDataRate(*f.RX2DefaultDataRate); err != nil { + return fmt.Errorf("RX2DefaultDataRate: %w", err) + } + } return nil } diff --git a/internal/model/gateway-frequency-plan.go b/internal/model/gateway-frequency-plan.go index 4e27aa1..8fda494 100644 --- a/internal/model/gateway-frequency-plan.go +++ b/internal/model/gateway-frequency-plan.go @@ -104,5 +104,46 @@ func (f FrequencyPlanGatewayModifier) Parse(file string) (Definition, error) { } func (f FrequencyPlanGatewayModifier) Validate() error { + if f.SubBands != nil { + for i, subBand := range *f.SubBands { + if err := subBand.Validate(); err != nil { + return fmt.Errorf("SubBand %d: %w", i, err) + } + } + } + if f.Channels != nil { + for i, channel := range *f.Channels { + if err := channel.Validate(); err != nil { + return fmt.Errorf("Channel %d: %w", i, err) + } + } + } + if f.LoRaStandardChannel != nil { + if err := f.LoRaStandardChannel.Validate(); err != nil { + return fmt.Errorf("LoRaStandardChannel: %w", err) + } + } + if f.FSKChannel != nil { + if err := f.FSKChannel.Validate(); err != nil { + return fmt.Errorf("FSKChannel: %w", err) + } + } + if f.TimeOffAir != nil { + if err := f.TimeOffAir.Validate(); err != nil { + return fmt.Errorf("TimeOffAir: %w", err) + } + } + if f.DwellTime != nil { + if err := f.DwellTime.Validate(); err != nil { + return fmt.Errorf("DwellTime: %w", err) + } + } + if f.Radios != nil { + for i, radio := range *f.Radios { + if err := radio.Validate(); err != nil { + return fmt.Errorf("Radio %d: %w", i, err) + } + } + } return nil } From 5450c88226a97dc31bd516ed6dc5b4efb4887a5a Mon Sep 17 00:00:00 2001 From: Yorick Smilda Date: Thu, 23 Feb 2023 11:44:42 +0100 Subject: [PATCH 3/3] Convert existing plans to new structure --- README.md | 4 +- docs/frequency-plans.md | 55 +++- docs/images/end-device/AS_920_923.svg | 271 ++++++++++++++++ docs/images/end-device/AS_920_923_LBT.svg | 271 ++++++++++++++++ docs/images/end-device/EU_433.svg | 258 +++++++++++++++ .../end-device/EU_863_870_ROAMING_DRAFT.svg | 210 ++++++++++++ docs/images/end-device/US_902_928_FSB_1.svg | 170 ++++++++++ docs/images/gateway/AS_920_923.svg | 306 ++++++++++++++++++ docs/images/gateway/AS_920_923_LBT.svg | 306 ++++++++++++++++++ docs/images/gateway/EU_433.svg | 297 +++++++++++++++++ end-device/AS_920_923.yml | 43 +++ end-device/EU_433.yml | 43 +++ end-device/EU_863_870_ROAMING_DRAFT.yml | 31 ++ end-device/US_902_928_FSB_1.yml | 36 +++ end-device/modifiers/disable_dwell_time.yml | 4 + .../modifiers/enable_dwell_time_400ms.yml | 4 + end-device/modifiers/lbt_80_over_128.yml | 4 + end-device/schema.json | 2 +- frequency-plans.yml | 74 +++++ gateway/AS_920_923.yml | 66 ++++ gateway/EU_433.yml | 64 ++++ gateway/modifiers/disable_dwell_time.yml | 4 + gateway/modifiers/enable_dwell_time_400ms.yml | 4 + gateway/modifiers/lbt_80_over_128.yml | 4 + gateway/schema.json | 2 +- internal/docs/end-device.go | 3 + internal/docs/frequency-plans.md.tmpl | 4 +- internal/model/end-device-frequency-plan.go | 24 +- internal/model/gateway-frequency-plan.go | 18 +- internal/model/model.go | 18 +- ...equency-plans-description-schema.json.tmpl | 2 + ...requency-plans-end-device-schema.json.tmpl | 2 +- .../frequency-plans-gateway-schema.json.tmpl | 2 +- schema.json | 14 +- 34 files changed, 2569 insertions(+), 51 deletions(-) create mode 100644 docs/images/end-device/AS_920_923.svg create mode 100644 docs/images/end-device/AS_920_923_LBT.svg create mode 100644 docs/images/end-device/EU_433.svg create mode 100644 docs/images/end-device/EU_863_870_ROAMING_DRAFT.svg create mode 100644 docs/images/end-device/US_902_928_FSB_1.svg create mode 100644 docs/images/gateway/AS_920_923.svg create mode 100644 docs/images/gateway/AS_920_923_LBT.svg create mode 100644 docs/images/gateway/EU_433.svg create mode 100644 end-device/AS_920_923.yml create mode 100644 end-device/EU_433.yml create mode 100644 end-device/EU_863_870_ROAMING_DRAFT.yml create mode 100644 end-device/US_902_928_FSB_1.yml create mode 100644 end-device/modifiers/disable_dwell_time.yml create mode 100644 end-device/modifiers/enable_dwell_time_400ms.yml create mode 100644 end-device/modifiers/lbt_80_over_128.yml create mode 100644 gateway/AS_920_923.yml create mode 100644 gateway/EU_433.yml create mode 100644 gateway/modifiers/disable_dwell_time.yml create mode 100644 gateway/modifiers/enable_dwell_time_400ms.yml create mode 100644 gateway/modifiers/lbt_80_over_128.yml diff --git a/README.md b/README.md index 9380d28..0d78d5a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ sub-bands: max-eirp: 16.15 # Maximum EIRP for this sub-band (optional; takes precedence over frequency plan's max-eirp) channels: # List of channels (zero indexed) - uplink-frequency: 868100000 # Uplink frequency (Hz) - downlink-frequency: 868100000 # Downlink frequency (Hz) (optional) + downlink-frequency: 868100000 # Downlink frequency (Hz) min-data-rate: SF7BW125 # Minimum data rate max-data-rate: SF11BW125 # Maximum data rate radio: 0 # Radio index (see below) @@ -159,6 +159,8 @@ There are json schemas available for all configuration files. These define files } ``` +Make sure to run `go run . -schema -docs` after changes or additions to the frequency plans. This ensures that the schemas and documentation stay up to date. + ### Local Regulations When submitting a new frequency plan or making changes to an existing frequency plan, please make sure that the band is allowed to be used in the concerning region and that settings respect regional regulations. When submitting a pull request for a new region, please upload or link to a document that describes the local regulations. diff --git a/docs/frequency-plans.md b/docs/frequency-plans.md index d20bfaa..38e6416 100644 --- a/docs/frequency-plans.md +++ b/docs/frequency-plans.md @@ -9,18 +9,67 @@ ![EU_863_870](images/end-device/EU_863_870.svg) ## `EU_863_870_TTN`: Europe 863-870 MHz -Based on [EU_863_870](##EU_863_870) and modified by [rx2_default_data_rata_3.yml](../end-device/modifiers/rx2_default_data_rata_3.yml) - +Based on [EU_863_870](##EU_863_870) and modified by [rx2_default_data_rata_3.yml](../end-device/modifiers/rx2_default_data_rata_3.yml) >> TTN Community Network frequency plan for Europe, using SF9 for RX2 ![EU_863_870_TTN](images/end-device/EU_863_870_TTN.svg) -# Gateway frequency plans +## [`EU_863_870_ROAMING_DRAFT`](../end-device/EU_863_870_ROAMING_DRAFT.yml): Europe 863-870 MHz, 6 channels for roaming (Draft) + +>> European 6 channel plan used by major operators to support LoRaWAN Passive Roaming + +![EU_863_870_ROAMING_DRAFT](images/end-device/EU_863_870_ROAMING_DRAFT.svg) + +## [`EU_433`](../end-device/EU_433.yml): Europe 433 MHz (ITU region 1) + +>> Default frequency plan for worldwide 433MHz + +![EU_433](images/end-device/EU_433.svg) + +## [`US_902_928_FSB_1`](../end-device/US_902_928_FSB_1.yml): United States 902-928 MHz, FSB 1 + +>> Default frequency plan for the United States and Canada, using sub-band 1 + +![US_902_928_FSB_1](images/end-device/US_902_928_FSB_1.svg) + +## [`AS_920_923`](../end-device/AS_920_923.yml): Asia 920-923 MHz + +>> TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz + +![AS_920_923](images/end-device/AS_920_923.svg) + +## `AS_920_923_LBT`: Asia 920-923 MHz with LBT +Based on [AS_920_923](##AS_920_923) and modified by [lbt_80_over_128.yml](../end-device/modifiers/lbt_80_over_128.yml) +>> TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz with listen-before-talk + +![AS_920_923_LBT](images/end-device/AS_920_923_LBT.svg) + +# Gateway frequency plans ## [`EU_863_870`](../gateway/EU_863_870.yml): Europe 863-870 MHz >> Default frequency plan for Europe ![EU_863_870](images/gateway/EU_863_870.svg) + +## [`EU_433`](../gateway/EU_433.yml): Europe 433 MHz (ITU region 1) + +>> Default frequency plan for worldwide 433MHz + +![EU_433](images/gateway/EU_433.svg) + +## [`AS_920_923`](../gateway/AS_920_923.yml): Asia 920-923 MHz + +>> TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz + +![AS_920_923](images/gateway/AS_920_923.svg) + +## `AS_920_923_LBT`: Asia 920-923 MHz with LBT +Based on AS_920_923 and modified by [lbt_80_over_128.yml](../gateway/modifiers/lbt_80_over_128.yml) + + +>> TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz with listen-before-talk + +![AS_920_923_LBT](images/gateway/AS_920_923_LBT.svg) diff --git a/docs/images/end-device/AS_920_923.svg b/docs/images/end-device/AS_920_923.svg new file mode 100644 index 0000000..3153466 --- /dev/null +++ b/docs/images/end-device/AS_920_923.svg @@ -0,0 +1,271 @@ +\n921.8922922.2922.4922.6922.8923923.2923.4DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00923.2923.2923.4923.4922.2922.2922.4922.4922.6922.6922.8922.8923923922922921.8 (FSK)AS_920_923 \ No newline at end of file diff --git a/docs/images/end-device/AS_920_923_LBT.svg b/docs/images/end-device/AS_920_923_LBT.svg new file mode 100644 index 0000000..9d13638 --- /dev/null +++ b/docs/images/end-device/AS_920_923_LBT.svg @@ -0,0 +1,271 @@ +\n921.8922922.2922.4922.6922.8923923.2923.4DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00923.2923.2923.4923.4922.2922.2922.4922.4922.6922.6922.8922.8923923922922921.8 (FSK)AS_920_923_LBT \ No newline at end of file diff --git a/docs/images/end-device/EU_433.svg b/docs/images/end-device/EU_433.svg new file mode 100644 index 0000000..b6c86e7 --- /dev/null +++ b/docs/images/end-device/EU_433.svg @@ -0,0 +1,258 @@ +\n433.175433.375433.575433.775433.975434.175434.375434.575DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00433.175433.175433.375433.375433.575433.575433.775433.775433.975433.975434.175434.175434.375434.375434.575434.575EU_433 \ No newline at end of file diff --git a/docs/images/end-device/EU_863_870_ROAMING_DRAFT.svg b/docs/images/end-device/EU_863_870_ROAMING_DRAFT.svg new file mode 100644 index 0000000..1c4a6b6 --- /dev/null +++ b/docs/images/end-device/EU_863_870_ROAMING_DRAFT.svg @@ -0,0 +1,210 @@ +\n867.1867.3867.9868.1868.3868.5DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00868.1868.1868.3868.3868.5868.5867.1867.1867.3867.3867.9867.9EU_863_870_ROAMING_DRAFT \ No newline at end of file diff --git a/docs/images/end-device/US_902_928_FSB_1.svg b/docs/images/end-device/US_902_928_FSB_1.svg new file mode 100644 index 0000000..f176b3a --- /dev/null +++ b/docs/images/end-device/US_902_928_FSB_1.svg @@ -0,0 +1,170 @@ +\n902.3902.5902.7902.9903.1903.3903.5903.7DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00902.3902.5902.7902.9903.1903.3903.5903.7US_902_928_FSB_1 \ No newline at end of file diff --git a/docs/images/gateway/AS_920_923.svg b/docs/images/gateway/AS_920_923.svg new file mode 100644 index 0000000..e769514 --- /dev/null +++ b/docs/images/gateway/AS_920_923.svg @@ -0,0 +1,306 @@ +\n921.638921.8922922.1922.2922.4922.538922.562922.6922.8923923.2923.4923.462DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00923.2923.2923.4923.4922.2922.2922.4922.4922.6922.6922.8922.8923923922922922.1 (Std)921.8 (FSK)Radio 0: 923Radio 1: 922.1AS_920_923 \ No newline at end of file diff --git a/docs/images/gateway/AS_920_923_LBT.svg b/docs/images/gateway/AS_920_923_LBT.svg new file mode 100644 index 0000000..e4763d8 --- /dev/null +++ b/docs/images/gateway/AS_920_923_LBT.svg @@ -0,0 +1,306 @@ +\n921.638921.8922922.1922.2922.4922.538922.562922.6922.8923923.2923.4923.462DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00923.2923.2923.4923.4922.2922.2922.4922.4922.6922.6922.8922.8923923922922922.1 (Std)921.8 (FSK)Radio 0: 923Radio 1: 922.1AS_920_923_LBT \ No newline at end of file diff --git a/docs/images/gateway/EU_433.svg b/docs/images/gateway/EU_433.svg new file mode 100644 index 0000000..e3c54ad --- /dev/null +++ b/docs/images/gateway/EU_433.svg @@ -0,0 +1,297 @@ +\n433.012433.175433.375433.475433.575433.775433.812433.938433.975434.075434.175434.275434.375434.575434.738DownlinkRadioUplinkStd/FSK0.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.000.00433.175433.175433.375433.375433.575433.575433.775433.775433.975433.975434.175434.175434.375434.375434.575434.575434.075 (Std)Radio 0: 433.475Radio 1: 434.275EU_433 \ No newline at end of file diff --git a/end-device/AS_920_923.yml b/end-device/AS_920_923.yml new file mode 100644 index 0000000..bd3236d --- /dev/null +++ b/end-device/AS_920_923.yml @@ -0,0 +1,43 @@ +band-id: AS_923 +sub-bands: +- min-frequency: 922000000 + max-frequency: 923400000 +channels: +- uplink-frequency: 923200000 + downlink-frequency: 923200000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 923400000 + downlink-frequency: 923400000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 922200000 + downlink-frequency: 922200000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 922400000 + downlink-frequency: 922400000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 922600000 + downlink-frequency: 922600000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 922800000 + downlink-frequency: 922800000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 923000000 + downlink-frequency: 923000000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 922000000 + downlink-frequency: 922000000 + min-data-rate: 0 + max-data-rate: 5 +lora-standard-channel: + frequency: 922100000 + data-rate: 6 +fsk-channel: + frequency: 921800000 + data-rate: 7 diff --git a/end-device/EU_433.yml b/end-device/EU_433.yml new file mode 100644 index 0000000..5b5aaa3 --- /dev/null +++ b/end-device/EU_433.yml @@ -0,0 +1,43 @@ +band-id: EU_433 +sub-bands: +- min-frequency: 433050000 + max-frequency: 434790000 + duty-cycle: 0.1 # NOTE: ETSI EN300220 limit is 10%; LoRaWAN limit for end-devices is 1% + max-eirp: 12.15 +channels: +- uplink-frequency: 433175000 + downlink-frequency: 433175000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 433375000 + downlink-frequency: 433375000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 433575000 + downlink-frequency: 433575000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 433775000 + downlink-frequency: 433775000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 433975000 + downlink-frequency: 433975000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 434175000 + downlink-frequency: 434175000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 434375000 + downlink-frequency: 434375000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 434575000 + downlink-frequency: 434575000 + min-data-rate: 0 + max-data-rate: 5 +lora-standard-channel: + frequency: 434075000 + data-rate: 6 + diff --git a/end-device/EU_863_870_ROAMING_DRAFT.yml b/end-device/EU_863_870_ROAMING_DRAFT.yml new file mode 100644 index 0000000..e097000 --- /dev/null +++ b/end-device/EU_863_870_ROAMING_DRAFT.yml @@ -0,0 +1,31 @@ + +band-id: EU_863_870 +sub-bands: + - min-frequency: 867100000 + max-frequency: 868500000 +channels: +- uplink-frequency: 868100000 + downlink-frequency: 868100000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 868300000 + downlink-frequency: 868300000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 868500000 + downlink-frequency: 868500000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 867100000 + downlink-frequency: 867100000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 867300000 + downlink-frequency: 867300000 + min-data-rate: 0 + max-data-rate: 5 +- uplink-frequency: 867900000 + downlink-frequency: 867900000 + min-data-rate: 0 + max-data-rate: 5 + diff --git a/end-device/US_902_928_FSB_1.yml b/end-device/US_902_928_FSB_1.yml new file mode 100644 index 0000000..a2faff0 --- /dev/null +++ b/end-device/US_902_928_FSB_1.yml @@ -0,0 +1,36 @@ +band-id: US_902_928 +sub-bands: + - min-frequency: 902300000 + max-frequency: 903700000 +channels: +- uplink-frequency: 902300000 + min-data-rate: 0 + max-data-rate: 3 +- uplink-frequency: 902500000 + min-data-rate: 0 + max-data-rate: 3 +- uplink-frequency: 902700000 + min-data-rate: 0 + max-data-rate: 3 +- uplink-frequency: 902900000 + min-data-rate: 0 + max-data-rate: 3 +- uplink-frequency: 903100000 + min-data-rate: 0 + max-data-rate: 3 +- uplink-frequency: 903300000 + min-data-rate: 0 + max-data-rate: 3 +- uplink-frequency: 903500000 + min-data-rate: 0 + max-data-rate: 3 +- uplink-frequency: 903700000 + min-data-rate: 0 + max-data-rate: 3 +lora-standard-channel: + frequency: 903000000 + data-rate: 12 +dwell-time: + uplinks: true + downlinks: false + duration: 400ms diff --git a/end-device/modifiers/disable_dwell_time.yml b/end-device/modifiers/disable_dwell_time.yml new file mode 100644 index 0000000..4debef4 --- /dev/null +++ b/end-device/modifiers/disable_dwell_time.yml @@ -0,0 +1,4 @@ +dwell-time: + uplinks: false + downlinks: false + duration: 0ms \ No newline at end of file diff --git a/end-device/modifiers/enable_dwell_time_400ms.yml b/end-device/modifiers/enable_dwell_time_400ms.yml new file mode 100644 index 0000000..291fe42 --- /dev/null +++ b/end-device/modifiers/enable_dwell_time_400ms.yml @@ -0,0 +1,4 @@ +dwell-time: + uplinks: true + downlinks: true + duration: 400ms \ No newline at end of file diff --git a/end-device/modifiers/lbt_80_over_128.yml b/end-device/modifiers/lbt_80_over_128.yml new file mode 100644 index 0000000..e1d43b8 --- /dev/null +++ b/end-device/modifiers/lbt_80_over_128.yml @@ -0,0 +1,4 @@ +listen-before-talk: + rssi-offset: -4 + rssi-target: -80 + scan-time: 128000 \ No newline at end of file diff --git a/end-device/schema.json b/end-device/schema.json index 205e457..9e493f7 100644 --- a/end-device/schema.json +++ b/end-device/schema.json @@ -48,7 +48,7 @@ }, "downlink-frequency": { "type": "integer", - "description": "Downlink frequency [Hz] (optional)" + "description": "Downlink frequency [Hz]" }, "min-data-rate": { "type": "integer", diff --git a/frequency-plans.yml b/frequency-plans.yml index fbce7d6..8aeb7d4 100644 --- a/frequency-plans.yml +++ b/frequency-plans.yml @@ -18,6 +18,52 @@ end-device-descriptions: modifiers: [rx2_default_data_rata_3.yml] endorsed: true +- id: EU_863_870_ROAMING_DRAFT + band-id: EU_863_870 + name: Europe 863-870 MHz, 6 channels for roaming (Draft) + description: European 6 channel plan used by major operators to support LoRaWAN Passive Roaming + base-frequency: 868 + country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] + file: EU_863_870_ROAMING_DRAFT.yml + endorsed: false + +- id: EU_433 + band-id: EU_433 + name: Europe 433 MHz (ITU region 1) + description: Default frequency plan for worldwide 433MHz + base-frequency: 433 + country-codes: [worldwide] + file: EU_433.yml + endorsed: true + +- id: US_902_928_FSB_1 + band-id: US_902_928 + name: United States 902-928 MHz, FSB 1 + description: Default frequency plan for the United States and Canada, using sub-band 1 + base-frequency: 915 + country-codes: [ca, cr, ec, gy, mx, pa, pr, us] + file: US_902_928_FSB_1.yml + endorsed: true + +- id: AS_920_923 + band-id: AS_923 + name: Asia 920-923 MHz + description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz + base-frequency: 915 + country-codes: [my, sg] + file: AS_920_923.yml + endorsed: true + +- id: AS_920_923_LBT + band-id: AS_923 + base-id: AS_920_923 + name: Asia 920-923 MHz with LBT + description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz with listen-before-talk + base-frequency: 915 + country-codes: [jp, my, sg] + modifiers: [lbt_80_over_128.yml] + endorsed: false + gateway-descriptions: - id: EU_863_870 band-id: EU_863_870 @@ -27,3 +73,31 @@ gateway-descriptions: country-codes: [al, ad, ao, at, bh, be, ba, bw, bg, cg, hr, cy, cz, dk, ee, sz, fi, fr, gr, hu, is, ie, it, lv, ls, li, lt, lu, mg, mw, mt, mu, md, me, mz, na, nl, mk, ph, pl, pt, ro, ru, sa, rs, sc, sk, si, za, es, se, ch, tz, tr, ae, gb, va, zm, zw] file: EU_863_870.yml endorsed: false + +- id: EU_433 + band-id: EU_433 + name: Europe 433 MHz (ITU region 1) + description: Default frequency plan for worldwide 433MHz + base-frequency: 433 + country-codes: [worldwide] + file: EU_433.yml + endorsed: true + +- id: AS_920_923 + band-id: AS_923 + name: Asia 920-923 MHz + description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz + base-frequency: 915 + country-codes: [my, sg] + file: AS_920_923.yml + endorsed: true + +- id: AS_920_923_LBT + band-id: AS_923 + base-id: AS_920_923 + name: Asia 920-923 MHz with LBT + description: TTN Community Network frequency plan for Asian countries, using frequencies ≤ 923 MHz with listen-before-talk + base-frequency: 915 + country-codes: [jp, my, sg] + modifiers: [lbt_80_over_128.yml] + endorsed: false diff --git a/gateway/AS_920_923.yml b/gateway/AS_920_923.yml new file mode 100644 index 0000000..6a11d90 --- /dev/null +++ b/gateway/AS_920_923.yml @@ -0,0 +1,66 @@ +band-id: AS_923 +sub-bands: +- min-frequency: 922000000 + max-frequency: 923400000 +channels: +- uplink-frequency: 923200000 + downlink-frequency: 923200000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 923400000 + downlink-frequency: 923400000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 922200000 + downlink-frequency: 922200000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 1 +- uplink-frequency: 922400000 + downlink-frequency: 922400000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 1 +- uplink-frequency: 922600000 + downlink-frequency: 922600000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 922800000 + downlink-frequency: 922800000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 923000000 + downlink-frequency: 923000000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 922000000 + downlink-frequency: 922000000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 1 +lora-standard-channel: + frequency: 922100000 + data-rate: SF7BW250 + radio: 1 +fsk-channel: + frequency: 921800000 + data-rate: FSK50 + radio: 1 +radios: +- enable: true + chip-type: SX1257 + frequency: 923000000 + rssi-offset: -166 + tx: + min-frequency: 920000000 + max-frequency: 923400000 +- enable: true + chip-type: SX1257 + frequency: 922100000 + rssi-offset: -166 +clock-source: 1 diff --git a/gateway/EU_433.yml b/gateway/EU_433.yml new file mode 100644 index 0000000..9790f28 --- /dev/null +++ b/gateway/EU_433.yml @@ -0,0 +1,64 @@ +band-id: EU_433 +sub-bands: +- min-frequency: 433050000 + max-frequency: 434790000 + duty-cycle: 0.1 # NOTE: ETSI EN300220 limit is 10%; LoRaWAN limit for end-devices is 1% + max-eirp: 12.15 +channels: +- uplink-frequency: 433175000 + downlink-frequency: 433175000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 433375000 + downlink-frequency: 433375000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 433575000 + downlink-frequency: 433575000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 433775000 + downlink-frequency: 433775000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 0 +- uplink-frequency: 433975000 + downlink-frequency: 433975000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 1 +- uplink-frequency: 434175000 + downlink-frequency: 434175000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 1 +- uplink-frequency: 434375000 + downlink-frequency: 434375000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 1 +- uplink-frequency: 434575000 + downlink-frequency: 434575000 + min-data-rate: SF7BW125 + max-data-rate: SF12BW125 + radio: 1 +lora-standard-channel: + frequency: 434075000 + data-rate: SF7BW250 + radio: 1 +radios: +- enable: true + chip-type: SX1255 + frequency: 433475000 + rssi-offset: -176 + tx: + min-frequency: 433050000 + max-frequency: 434790000 +- enable: true + chip-type: SX1255 + frequency: 434275000 + rssi-offset: -176 +clock-source: 1 diff --git a/gateway/modifiers/disable_dwell_time.yml b/gateway/modifiers/disable_dwell_time.yml new file mode 100644 index 0000000..4debef4 --- /dev/null +++ b/gateway/modifiers/disable_dwell_time.yml @@ -0,0 +1,4 @@ +dwell-time: + uplinks: false + downlinks: false + duration: 0ms \ No newline at end of file diff --git a/gateway/modifiers/enable_dwell_time_400ms.yml b/gateway/modifiers/enable_dwell_time_400ms.yml new file mode 100644 index 0000000..291fe42 --- /dev/null +++ b/gateway/modifiers/enable_dwell_time_400ms.yml @@ -0,0 +1,4 @@ +dwell-time: + uplinks: true + downlinks: true + duration: 400ms \ No newline at end of file diff --git a/gateway/modifiers/lbt_80_over_128.yml b/gateway/modifiers/lbt_80_over_128.yml new file mode 100644 index 0000000..e1d43b8 --- /dev/null +++ b/gateway/modifiers/lbt_80_over_128.yml @@ -0,0 +1,4 @@ +listen-before-talk: + rssi-offset: -4 + rssi-target: -80 + scan-time: 128000 \ No newline at end of file diff --git a/gateway/schema.json b/gateway/schema.json index a16ba53..c171887 100644 --- a/gateway/schema.json +++ b/gateway/schema.json @@ -48,7 +48,7 @@ }, "downlink-frequency": { "type": "integer", - "description": "Downlink frequency [Hz] (optional)" + "description": "Downlink frequency [Hz]" }, "min-data-rate": { "type": "string", diff --git a/internal/docs/end-device.go b/internal/docs/end-device.go index 393b074..07f4807 100644 --- a/internal/docs/end-device.go +++ b/internal/docs/end-device.go @@ -41,6 +41,9 @@ func renderEndDevice(id string, plan model.FrequencyPlanEndDevice) error { Label: formatFrequency(freq), }) + if ch.DownlinkFrequency == nil { + continue + } freq = float64(*ch.DownlinkFrequency) start, end = freq-62500, freq+62500 frequencies[freq] = formatFrequency(freq) diff --git a/internal/docs/frequency-plans.md.tmpl b/internal/docs/frequency-plans.md.tmpl index 98c48cb..0d415ca 100644 --- a/internal/docs/frequency-plans.md.tmpl +++ b/internal/docs/frequency-plans.md.tmpl @@ -4,8 +4,7 @@ {{- range .EndDeviceDescriptions }} {{ if .BaseID }} ## `{{ .ID }}`: {{ .Name }} -Based on [{{ .BaseID }}](##{{ .BaseID }}) and modified by {{ range .Modifiers }}[{{ . }}](../end-device/modifiers/{{ . }}){{ end }} -{{ else }} +Based on [{{ .BaseID }}](##{{ .BaseID }}) and modified by {{ range .Modifiers }}[{{ . }}](../end-device/modifiers/{{ . }}){{ end }}{{ else }} ## [`{{ .ID }}`](../end-device/{{ .File }}): {{ .Name }}{{ end }} >> {{ .Description }} @@ -14,7 +13,6 @@ Based on [{{ .BaseID }}](##{{ .BaseID }}) and modified by {{ range .Modifiers }} # Gateway frequency plans {{- range .GatewayDescriptions }} - {{ if .BaseID }} ## `{{ .ID }}`: {{ .Name }} Based on {{ .BaseID }} and modified by {{ range .Modifiers }}[{{ . }}](../gateway/modifiers/{{ . }}){{ end }} diff --git a/internal/model/end-device-frequency-plan.go b/internal/model/end-device-frequency-plan.go index e30f635..07af927 100644 --- a/internal/model/end-device-frequency-plan.go +++ b/internal/model/end-device-frequency-plan.go @@ -93,18 +93,18 @@ func (f FrequencyPlanEndDevice) Validate() error { func (f FrequencyPlanEndDevice) Modify(modifier FrequencyPlanEndDeviceModifier) FrequencyPlanEndDevice { modified := f - set(modifier.SubBands, modified.SubBands) - set(modifier.Channels, modified.Channels) - setPointer(modifier.LoRaStandardChannel, modified.LoRaStandardChannel) - setPointer(modifier.FSKChannel, modified.FSKChannel) - setPointer(modifier.TimeOffAir, modified.TimeOffAir) - setPointer(modifier.DwellTime, modified.DwellTime) - setPointer(modifier.ListenBeforeTalk, modified.ListenBeforeTalk) - setPointer(modifier.PingSlot, modified.PingSlot) - setPointer(modifier.PingSlotDefaultDataRate, modified.PingSlotDefaultDataRate) - setPointer(modifier.RX2Channel, modified.RX2Channel) - setPointer(modifier.RX2DefaultDataRate, modified.RX2DefaultDataRate) - setPointer(modifier.MaxEIRP, modified.MaxEIRP) + set(modifier.SubBands, &modified.SubBands) + set(modifier.Channels, &modified.Channels) + set(modifier.LoRaStandardChannel, modified.LoRaStandardChannel) + set(modifier.FSKChannel, modified.FSKChannel) + set(modifier.TimeOffAir, modified.TimeOffAir) + set(modifier.DwellTime, modified.DwellTime) + set(modifier.ListenBeforeTalk, modified.ListenBeforeTalk) + set(modifier.PingSlot, modified.PingSlot) + set(modifier.PingSlotDefaultDataRate, modified.PingSlotDefaultDataRate) + set(modifier.RX2Channel, modified.RX2Channel) + set(modifier.RX2DefaultDataRate, modified.RX2DefaultDataRate) + set(modifier.MaxEIRP, modified.MaxEIRP) return modified } diff --git a/internal/model/gateway-frequency-plan.go b/internal/model/gateway-frequency-plan.go index 8fda494..f333fa9 100644 --- a/internal/model/gateway-frequency-plan.go +++ b/internal/model/gateway-frequency-plan.go @@ -70,15 +70,15 @@ func (f FrequencyPlanGateway) Validate() error { func (f FrequencyPlanGateway) Modify(modifier FrequencyPlanGatewayModifier) FrequencyPlanGateway { modified := f - set(modifier.SubBands, modified.SubBands) - set(modifier.Channels, modified.Channels) - set(modifier.Radios, modified.Radios) - set(modifier.ClockSource, modified.ClockSource) - setPointer(modifier.LoRaStandardChannel, modified.LoRaStandardChannel) - setPointer(modifier.FSKChannel, modified.FSKChannel) - setPointer(modifier.TimeOffAir, modified.TimeOffAir) - setPointer(modifier.DwellTime, modified.DwellTime) - setPointer(modifier.MaxEIRP, modified.MaxEIRP) + set(modifier.SubBands, &modified.SubBands) + set(modifier.Channels, &modified.Channels) + set(modifier.Radios, &modified.Radios) + set(modifier.ClockSource, &modified.ClockSource) + set(modifier.LoRaStandardChannel, modified.LoRaStandardChannel) + set(modifier.FSKChannel, modified.FSKChannel) + set(modifier.TimeOffAir, modified.TimeOffAir) + set(modifier.DwellTime, modified.DwellTime) + set(modifier.MaxEIRP, modified.MaxEIRP) return modified } diff --git a/internal/model/model.go b/internal/model/model.go index 0546380..e5ef00b 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -140,7 +140,7 @@ func (d DwellTime) Validate() error { type ListenBeforeTalk struct { RSSIOffset *int `yaml:"rssi-offset,omitempty"` - RSSITarget *int `yaml:"rssi-targer,omitempty"` + RSSITarget *int `yaml:"rssi-target,omitempty"` ScanTime *int `yaml:"scan-time,omitempty"` } @@ -240,17 +240,9 @@ func validateFrequencyRange(frequency int, min, max int) error { return nil } -func set[T any](modifier *T, base T) { - if modifier != nil { - base = *modifier - } -} - -func setPointer[T any](modifier *T, base *T) { - if modifier != nil { - if base == nil { - base = new(T) - } - *base = *modifier +func set[T any](modifier *T, base *T) { + if modifier == nil { + return } + *base = *modifier } diff --git a/internal/schema/frequency-plans-description-schema.json.tmpl b/internal/schema/frequency-plans-description-schema.json.tmpl index 517a93f..ba2e366 100644 --- a/internal/schema/frequency-plans-description-schema.json.tmpl +++ b/internal/schema/frequency-plans-description-schema.json.tmpl @@ -90,6 +90,7 @@ "items": { "type": "string", "enum": [ + "worldwide", "af", "ax", "al", @@ -417,6 +418,7 @@ "items": { "type": "string", "enum": [ + "worldwide", "af", "ax", "al", diff --git a/internal/schema/frequency-plans-end-device-schema.json.tmpl b/internal/schema/frequency-plans-end-device-schema.json.tmpl index e198c03..807380b 100644 --- a/internal/schema/frequency-plans-end-device-schema.json.tmpl +++ b/internal/schema/frequency-plans-end-device-schema.json.tmpl @@ -48,7 +48,7 @@ }, "downlink-frequency": { "type": "integer", - "description": "Downlink frequency [Hz] (optional)" + "description": "Downlink frequency [Hz]" }, "min-data-rate": { "type": "integer", diff --git a/internal/schema/frequency-plans-gateway-schema.json.tmpl b/internal/schema/frequency-plans-gateway-schema.json.tmpl index ab1fb52..1faeb9b 100644 --- a/internal/schema/frequency-plans-gateway-schema.json.tmpl +++ b/internal/schema/frequency-plans-gateway-schema.json.tmpl @@ -48,7 +48,7 @@ }, "downlink-frequency": { "type": "integer", - "description": "Downlink frequency [Hz] (optional)" + "description": "Downlink frequency [Hz]" }, "min-data-rate": { "type": "string", diff --git a/schema.json b/schema.json index 06c3ee6..aff0b51 100644 --- a/schema.json +++ b/schema.json @@ -62,19 +62,19 @@ "file": { "type": "string", "description": "File of the frequency plan definition.", - "enum": ["EU_863_870.yml"] + "enum": ["AS_920_923.yml","EU_433.yml","EU_863_870.yml","EU_863_870_ROAMING_DRAFT.yml","US_902_928_FSB_1.yml"] }, "base-id": { "type": "string", "description": "ID that this frequency plan extends (refers to id of another frequency plan).", - "enum" : ["EU_863_870","EU_863_870_TTN"] + "enum" : ["AS_920_923","AS_920_923_LBT","EU_433","EU_863_870","EU_863_870_ROAMING_DRAFT","EU_863_870_TTN","US_902_928_FSB_1"] }, "modifiers": { "type": "array", "uniqueItems": true, "items": { "type": "string", - "enum":["rx2_default_data_rata_3.yml"], + "enum":["disable_dwell_time.yml","enable_dwell_time_400ms.yml","lbt_80_over_128.yml","rx2_default_data_rata_3.yml"], "description": "Modifier addressed by filename." }, "description": "List of files containing the modifiers used to extend the `base-id` end-device." @@ -90,6 +90,7 @@ "items": { "type": "string", "enum": [ + "worldwide", "af", "ax", "al", @@ -389,19 +390,19 @@ "file": { "type": "string", "description": "File of the frequency plan definition.", - "enum": ["EU_863_870.yml"] + "enum": ["AS_920_923.yml","EU_433.yml","EU_863_870.yml"] }, "base-id": { "type": "string", "description": "ID that this frequency plan extends (refers to id of another frequency plan).", - "enum" : ["EU_863_870","EU_863_870_TTN"] + "enum" : ["AS_920_923","AS_920_923_LBT","EU_433","EU_863_870","EU_863_870_ROAMING_DRAFT","EU_863_870_TTN","US_902_928_FSB_1"] }, "modifiers": { "type": "array", "uniqueItems": true, "items": { "type": "string", - "enum":[], + "enum":["disable_dwell_time.yml","enable_dwell_time_400ms.yml","lbt_80_over_128.yml"], "description": "Modifier addressed by filename." }, "description": "List of files containing the modifiers used to extend the `base-id` end-device." @@ -417,6 +418,7 @@ "items": { "type": "string", "enum": [ + "worldwide", "af", "ax", "al",