diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfbe5d709..5b2b80dd6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ For details about compatibility between different releases, see the **Commitment ### Added +- Support fine-grained NbTrans controls while using Dynamic ADR mode in the Console. + ### Changed ### Deprecated diff --git a/pkg/webui/components/form/field/index.js b/pkg/webui/components/form/field/index.js index 41adb999ef..9e2776a02f 100644 --- a/pkg/webui/components/form/field/index.js +++ b/pkg/webui/components/form/field/index.js @@ -191,7 +191,10 @@ const FormField = props => { const disabled = inputDisabled || formDisabled const hasTooltip = Boolean(tooltipId) const hasTitle = Boolean(title) - const showError = touched && !isEmpty(errors) + const showError = + touched && + !isEmpty(errors) && + Boolean(errors[0].message?.id || errors[0].id || typeof errors[0] === 'string') const showWarning = !showError && Boolean(warning) const error = showError && errors[0] const showDescription = !showError && !showWarning && Boolean(description) diff --git a/pkg/webui/console/components/events/previews/application-uplink.js b/pkg/webui/console/components/events/previews/application-uplink.js index dfbe519603..d5371c6e0d 100644 --- a/pkg/webui/console/components/events/previews/application-uplink.js +++ b/pkg/webui/console/components/events/previews/application-uplink.js @@ -14,11 +14,13 @@ import React from 'react' -import { getDataRate, getSignalInformation } from '@console/components/events/utils' +import { getSignalInformation } from '@console/components/events/utils' import PropTypes from '@ttn-lw/lib/prop-types' import sharedMessages from '@ttn-lw/lib/shared-messages' +import getDataRate from '@console/lib/data-rate-utils' + import messages from '../messages' import DescriptionList from './shared/description-list' diff --git a/pkg/webui/console/components/mac-settings-section/index.js b/pkg/webui/console/components/mac-settings-section/index.js index 0c0260e4d4..a6faf3e57d 100644 --- a/pkg/webui/console/components/mac-settings-section/index.js +++ b/pkg/webui/console/components/mac-settings-section/index.js @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react' +import React, { useCallback } from 'react' import { defineMessages } from 'react-intl' +import { createSelector } from 'reselect' +import { useSelector } from 'react-redux' +import { get, set } from 'lodash' import Form, { useFormContext } from '@ttn-lw/components/form' import Select from '@ttn-lw/components/select' @@ -22,6 +25,8 @@ import Input from '@ttn-lw/components/input' import KeyValueMap from '@ttn-lw/components/key-value-map' import Radio from '@ttn-lw/components/radio-button' import UnitInput from '@ttn-lw/components/unit-input' +import Button from '@ttn-lw/components/button' +import Icon from '@ttn-lw/components/icon' import Message from '@ttn-lw/lib/components/message' @@ -36,6 +41,9 @@ import { fCntWidthDecode, parseLorawanMacVersion, } from '@console/lib/device-utils' +import getDataRate from '@console/lib/data-rate-utils' + +import { selectDataRates } from '@console/store/selectors/configuration' const m = defineMessages({ delayValue: '{count, plural, one {{count} second} other {{count} seconds}}', @@ -67,6 +75,18 @@ const m = defineMessages({ adrAckValue: '{count, plural, one {every message} other {every {count} messages}}', statusCountPeriodicity: 'Status count periodicity', statusTimePeriodicity: 'Status time periodicity', + dataRate: 'Data Rate {n}', + dataRatePlaceholder: 'Data Rate', + minNbTrans: 'Min. NbTrans', + maxNbTrans: 'Max. NbTrans', + useDefaultNbTrans: 'Use default settings for number of retransmissions', + adrNbTrans: 'ADR number of retransmissions (NbTrans)', + overrideNbTrans: 'Override server defaults for NbTrans (all data rates)', + defaultForAllRates: '(Default for all data rates)', + defaultNbTransMessage: + 'Overriding the default is not required for using data rate overrides (below)', + specificOverrides: 'Data rate specific overrides', + addSpecificOverride: 'Add data rate specific override', }) // 0...7 @@ -115,10 +135,32 @@ const MacSettingsSection = props => { lorawanVersion, isClassB, isClassC, + bandId, } = props - const { values } = useFormContext() + const { values, setFieldValue, setFieldTouched } = useFormContext() const { mac_settings } = values + const alreadySelectedDataRates = Object.keys(mac_settings?.adr?.dynamic?.overrides || []) + const dataRateOverrideOptions = useSelector( + createSelector( + state => selectDataRates(state, bandId, values.lorawan_phy_version), + dataRates => + Object.keys(dataRates).reduce( + (result, key) => + result.concat({ + label: getDataRate({ settings: { data_rate: dataRates[key].rate } }), + value: `data_rate_${key}`, + }), + [], + ), + ), + ) + // Filter out the already selected data rate indices. + const dataRateFilterOption = useCallback( + option => !alreadySelectedDataRates.includes(option.value), + [alreadySelectedDataRates], + ) + const isNewLorawanVersion = parseLorawanMacVersion(lorawanVersion) >= 110 const isABP = activationMode === ACTIVATION_MODES.ABP const isMulticast = activationMode === ACTIVATION_MODES.MULTICAST @@ -151,6 +193,51 @@ const MacSettingsSection = props => { } }, [handleIsCollapsedChange, isABP, isClassB, isCollapsed, isMulticast, pingPeriodicityRequired]) + const adrOverrides = mac_settings.adr.dynamic.overrides + const showEditNbTrans = !values.mac_settings?.adr.dynamic._use_default_nb_trans + const defaultNbTransDisabled = !values.mac_settings?.adr.dynamic._override_nb_trans_defaults + const addOverride = React.useCallback(() => { + const newOverride = { _data_rate_index: '', min_nb_trans: '', max_nb_trans: '' } + setFieldValue( + 'mac_settings.adr.dynamic.overrides', + adrOverrides + ? { ...adrOverrides, [`_empty-${Date.now()}`]: newOverride } + : { [`_empty-${Date.now()}`]: newOverride }, + ) + setFieldTouched('mac_settings.adr.dynamic._overrides', true) + }, [setFieldValue, adrOverrides, setFieldTouched]) + const handleRemoveButtonClick = useCallback( + (_, index) => { + setFieldValue( + 'mac_settings.adr.dynamic.overrides', + Object.keys(adrOverrides) + .filter(key => key !== index) + .reduce((acc, key) => ({ ...acc, [key]: adrOverrides[key] }), {}), + ) + }, + [adrOverrides, setFieldValue], + ) + + // Define a value setter for the data rate index field which + // handles setting the object keys correctly, since the index + // is set as the object key in the API schema. + // A similar result could be done without pseudo values, purely + // with decoder/encoder, but it would make error mapping + // more complex. + const dataRateValueSetter = useCallback( + ({ setValues }, { name, value }) => { + const index = name.split('.').slice(-2)[0] // Would be: data_rate_{x}. + const oldOverride = get(values, `mac_settings.adr.dynamic.overrides.${index}`, {}) + const overrides = { ...get(values, 'mac_settings.adr.dynamic.overrides', {}) } + // Empty data rate index objects, are stored with a pseudo key. Remove it. + delete overrides[index] + // Move the existing values to the new data rate key. + overrides[value] = { ...oldOverride, _data_rate_index: value } + setValues(values => set(values, 'mac_settings.adr.dynamic.overrides', overrides)) + }, + [values], + ) + return ( { {isDynamicAdr && ( - + <> + + + {showEditNbTrans && ( + <> + + + + + + + {!defaultNbTransDisabled && ( +
+ + +
+ )} + + {adrOverrides && + Object.keys(adrOverrides).map(index => ( + + + + +