36 changed files with 445 additions and 494 deletions
@ -1,272 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2025 The Thingsboard Authors
|
|
||||
///
|
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||
/// you may not use this file except in compliance with the License.
|
|
||||
/// You may obtain a copy of the License at
|
|
||||
///
|
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
///
|
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||
/// See the License for the specific language governing permissions and
|
|
||||
/// limitations under the License.
|
|
||||
///
|
|
||||
|
|
||||
import { |
|
||||
Conversion, |
|
||||
TbMeasure, |
|
||||
TbUnitConvertor, |
|
||||
Unit, |
|
||||
UnitCache, |
|
||||
UnitDescription, |
|
||||
UnitDescriptionGroupByMeasure, |
|
||||
UnitSystem |
|
||||
} from '@shared/models/unit.models'; |
|
||||
import { AllMeasures } from '@core/services/unit/definitions/all'; |
|
||||
import { TranslateService } from '@ngx-translate/core'; |
|
||||
|
|
||||
type Entries<T, S extends keyof T> = [S, T[keyof T]]; |
|
||||
|
|
||||
export class Converter< |
|
||||
TMeasures extends AllMeasures, |
|
||||
TUnits extends string, |
|
||||
> { |
|
||||
private readonly measureData: Record<TMeasures, TbMeasure<TUnits>>; |
|
||||
private unitCache: Map< |
|
||||
string, |
|
||||
{ |
|
||||
system: UnitSystem; |
|
||||
measure: TMeasures; |
|
||||
unit: Unit; |
|
||||
abbr: TUnits; |
|
||||
} |
|
||||
>; |
|
||||
|
|
||||
constructor( |
|
||||
measures: Record<TMeasures, TbMeasure<TUnits>>, |
|
||||
unitCache: UnitCache<TMeasures, TUnits> |
|
||||
) { |
|
||||
this.measureData = measures; |
|
||||
this.unitCache = unitCache; |
|
||||
} |
|
||||
|
|
||||
convertor(from: TUnits | string, to: TUnits | string): TbUnitConvertor { |
|
||||
return (value: number) => this.convert(value, from, to); |
|
||||
} |
|
||||
|
|
||||
convert(value: number, from: TUnits | string, to: TUnits | string): number { |
|
||||
const origin = this.getUnit(from); |
|
||||
const destination = this.getUnit(to); |
|
||||
|
|
||||
if (!origin) { |
|
||||
throw new Error(`Unsupported unit: ${from}`); |
|
||||
} |
|
||||
if (!destination) { |
|
||||
throw new Error(`Unsupported unit: ${to}`); |
|
||||
} |
|
||||
if (origin.abbr === destination.abbr) { |
|
||||
return value; |
|
||||
} |
|
||||
if (destination.measure !== origin.measure) { |
|
||||
throw Error(`Cannot convert incompatible measures: ${origin.measure} to ${destination.measure}`); |
|
||||
} |
|
||||
let result = value * origin.unit.to_anchor; |
|
||||
if (origin.unit.anchor_shift) { |
|
||||
result -= origin.unit.anchor_shift; |
|
||||
} |
|
||||
if (origin.system !== destination.system) { |
|
||||
const measureUnits = this.measureData[origin.measure][origin.system]; |
|
||||
const transform = measureUnits?.transform; |
|
||||
const ratio = measureUnits?.ratio; |
|
||||
if (typeof transform === 'function') { |
|
||||
result = transform(result); |
|
||||
} else if (typeof ratio === 'number') { |
|
||||
result *= ratio; |
|
||||
} else { |
|
||||
throw Error('System anchor requires a defined ratio or transform function'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (destination.unit.anchor_shift) { |
|
||||
result += destination.unit.anchor_shift; |
|
||||
} |
|
||||
return result / destination.unit.to_anchor; |
|
||||
} |
|
||||
|
|
||||
getDefaultUnit(measureName: TMeasures | (string & {}), unitSystem: UnitSystem): TUnits { |
|
||||
if (!this.isMeasure(measureName)) { |
|
||||
return null; |
|
||||
} |
|
||||
const units = this.getUnitsForMeasure(measureName, unitSystem); |
|
||||
if (!units) { |
|
||||
return null; |
|
||||
} |
|
||||
for (const [abbr, unit] of Object.entries(units) as [TUnits, Unit][]) { |
|
||||
if (unit.to_anchor === 1 && (!unit.anchor_shift || unit.anchor_shift === 0)) { |
|
||||
return abbr; |
|
||||
} |
|
||||
} |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
getUnit(abbr: TUnits | (string & {})): Conversion<TMeasures, TUnits> | null { |
|
||||
return this.unitCache.get(abbr) ?? null; |
|
||||
} |
|
||||
|
|
||||
describe(abbr: TUnits | (string & {})): UnitDescription { |
|
||||
const unit = this.getUnit(abbr); |
|
||||
return unit ? this.describeUnit(unit) : null; |
|
||||
} |
|
||||
|
|
||||
list(measureName?: TMeasures, unitSystem?: UnitSystem): UnitDescription[] { |
|
||||
const results: UnitDescription[] = []; |
|
||||
|
|
||||
const measures = measureName |
|
||||
? { [measureName]: this.measureData[measureName] } as Record<TMeasures, TbMeasure<TUnits>> |
|
||||
: this.measureData; |
|
||||
|
|
||||
for (const [name, measure] of Object.entries(measures) as [TMeasures, TbMeasure<TUnits>][]) { |
|
||||
if (!this.isMeasure(name)) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
const systems = unitSystem |
|
||||
? [unitSystem] |
|
||||
: (Object.keys(measure) as UnitSystem[]); |
|
||||
|
|
||||
for (const system of systems) { |
|
||||
const units = this.getUnitsForMeasure(name, system); |
|
||||
if (!units) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
for (const [abbr, unit] of Object.entries(units) as [TUnits, Unit][]) { |
|
||||
results.push( |
|
||||
this.describeUnit({ |
|
||||
abbr, |
|
||||
measure: name as TMeasures, |
|
||||
system, |
|
||||
unit, |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return results; |
|
||||
} |
|
||||
|
|
||||
listGroupByMeasure(measureName?: TMeasures, unitSystem?: UnitSystem): UnitDescriptionGroupByMeasure<TMeasures> | never { |
|
||||
const results: UnitDescriptionGroupByMeasure<TMeasures> = {}; |
|
||||
|
|
||||
const measures = measureName |
|
||||
? { [measureName]: this.measureData[measureName]} as Record<TMeasures, TbMeasure<TUnits>> |
|
||||
: this.measureData; |
|
||||
|
|
||||
for (const [name, measure] of Object.entries(measures) as [TMeasures, TbMeasure<TUnits>][]) { |
|
||||
if (!this.isMeasure(name)) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
results[name] = []; |
|
||||
|
|
||||
const systems = unitSystem |
|
||||
? [unitSystem] |
|
||||
: (Object.keys(measure) as UnitSystem[]); |
|
||||
|
|
||||
for (const system of systems) { |
|
||||
const units = this.getUnitsForMeasure(name, system); |
|
||||
if (!units) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
for (const [abbr, unit] of Object.entries(units) as [TUnits, Unit][]) { |
|
||||
results[name].push( |
|
||||
this.describeUnit({ |
|
||||
abbr, |
|
||||
measure: name as TMeasures, |
|
||||
system, |
|
||||
unit, |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return results; |
|
||||
} |
|
||||
|
|
||||
private describeUnit(unit: Conversion<TMeasures, TUnits>): UnitDescription { |
|
||||
return { |
|
||||
abbr: unit.abbr, |
|
||||
measure: unit.measure, |
|
||||
system: unit.system, |
|
||||
name: unit.unit.name, |
|
||||
tags: unit.unit.tags |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
private isMeasure(measureName: string): measureName is TMeasures { |
|
||||
return measureName in this.measureData; |
|
||||
} |
|
||||
|
|
||||
private getUnitsForMeasure( |
|
||||
measureName: TMeasures, |
|
||||
unitSystem: UnitSystem |
|
||||
): Partial<Record<TUnits, Unit>> | null { |
|
||||
const measure = this.measureData[measureName]; |
|
||||
let system = unitSystem; |
|
||||
let units = measure[system]?.units; |
|
||||
if (!units && unitSystem === UnitSystem.IMPERIAL) { |
|
||||
system = UnitSystem.METRIC; |
|
||||
units = measure[system]?.units; |
|
||||
} |
|
||||
return units ?? null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function buildUnitCache< |
|
||||
TMeasures extends string, |
|
||||
TUnits extends string, |
|
||||
>(measures: Record<TMeasures, TbMeasure<TUnits>>, |
|
||||
translate: TranslateService |
|
||||
) { |
|
||||
const unitCache: UnitCache<TMeasures, TUnits> = new Map(); |
|
||||
for (const [measureName, measure] of Object.entries(measures) as Entries< |
|
||||
typeof measures, |
|
||||
TMeasures |
|
||||
>[]) { |
|
||||
for (const [systemName, system] of Object.entries( |
|
||||
measure |
|
||||
) as Entries<TbMeasure<TUnits>, UnitSystem>[]) { |
|
||||
for (const [testAbbr, unit] of Object.entries(system.units) as Entries< |
|
||||
Record<TUnits, Unit>, |
|
||||
TUnits |
|
||||
>[]) { |
|
||||
unit.name = translate.instant(unit.name); |
|
||||
unitCache.set(testAbbr, { |
|
||||
measure: measureName, |
|
||||
system: systemName, |
|
||||
abbr: testAbbr, |
|
||||
unit, |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return unitCache; |
|
||||
} |
|
||||
|
|
||||
export function configureMeasurements< |
|
||||
TMeasures extends AllMeasures, |
|
||||
TUnits extends string, |
|
||||
>( |
|
||||
measures: Record<TMeasures, TbMeasure<TUnits>>, |
|
||||
translate: TranslateService |
|
||||
): Converter<TMeasures, TUnits> { |
|
||||
if (typeof measures !== 'object') { |
|
||||
throw new TypeError('The measures argument needs to be an object'); |
|
||||
} |
|
||||
|
|
||||
const unitCache = buildUnitCache(measures, translate); |
|
||||
return new Converter<TMeasures, TUnits>(measures, unitCache); |
|
||||
} |
|
||||
@ -1,121 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2025 The Thingsboard Authors
|
|
||||
///
|
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||
/// you may not use this file except in compliance with the License.
|
|
||||
/// You may obtain a copy of the License at
|
|
||||
///
|
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
///
|
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||
/// See the License for the specific language governing permissions and
|
|
||||
/// limitations under the License.
|
|
||||
///
|
|
||||
|
|
||||
import { TbMeasure } from '@shared/models/unit.models'; |
|
||||
import acceleration, { AccelerationUnits } from '@core/services/unit/definitions/acceleration'; |
|
||||
import angle, { AngleUnits } from '@core/services/unit/definitions/angle'; |
|
||||
import angularAcceleration, { AngularAccelerationUnits } from '@core/services/unit/definitions/angular-acceleration'; |
|
||||
import area, { AreaUnits } from '@core/services/unit/definitions/area'; |
|
||||
import charge, { ChargeUnits } from '@core/services/unit/definitions/charge'; |
|
||||
import digital, { DigitalUnits } from '@core/services/unit/definitions/digital'; |
|
||||
import electricCurrent, { ElectricCurrentUnits } from '@core/services/unit/definitions/electric-current'; |
|
||||
import energy, { EnergyUnits } from '@core/services/unit/definitions/energy'; |
|
||||
import force, { ForceUnits } from '@core/services/unit/definitions/force'; |
|
||||
import frequency, { FrequencyUnits } from '@core/services/unit/definitions/frequency'; |
|
||||
import illuminance,{ IlluminanceUnits } from '@core/services/unit/definitions/illuminance'; |
|
||||
import length, { LengthUnits } from '@core/services/unit/definitions/length'; |
|
||||
import mass, { MassUnits } from '@core/services/unit/definitions/mass'; |
|
||||
import partsPer, { PartsPerUnits } from '@core/services/unit/definitions/parts-per'; |
|
||||
import power, { PowerUnits } from '@core/services/unit/definitions/power'; |
|
||||
import pressure, { PressureUnits } from '@core/services/unit/definitions/pressure'; |
|
||||
import speed, { SpeedUnits } from '@core/services/unit/definitions/speed'; |
|
||||
import temperature, { TemperatureUnits } from './temperature'; |
|
||||
import time, { TimeUnits } from './time'; |
|
||||
import torque, { TorqueUnits } from '@core/services/unit/definitions/torque'; |
|
||||
import voltage, { VoltageUnits } from '@core/services/unit/definitions/voltage'; |
|
||||
import volume, { VolumeUnits } from '@core/services/unit/definitions/volume'; |
|
||||
import volumeFlowRate, { VolumeFlowRateUnits } from '@core/services/unit/definitions/volume-flow-rate'; |
|
||||
|
|
||||
export type AllMeasuresUnits = |
|
||||
| AccelerationUnits |
|
||||
| AngleUnits |
|
||||
| AngularAccelerationUnits |
|
||||
| AreaUnits |
|
||||
| ChargeUnits |
|
||||
| DigitalUnits |
|
||||
| ElectricCurrentUnits |
|
||||
| EnergyUnits |
|
||||
| ForceUnits |
|
||||
| FrequencyUnits |
|
||||
| IlluminanceUnits |
|
||||
| LengthUnits |
|
||||
| MassUnits |
|
||||
| PartsPerUnits |
|
||||
| PowerUnits |
|
||||
| PressureUnits |
|
||||
| SpeedUnits |
|
||||
| TemperatureUnits |
|
||||
| TimeUnits |
|
||||
| TorqueUnits |
|
||||
| VoltageUnits |
|
||||
| VolumeUnits |
|
||||
| VolumeFlowRateUnits; |
|
||||
|
|
||||
export type AllMeasures = |
|
||||
| 'acceleration' |
|
||||
| 'angle' |
|
||||
| 'angular-acceleration' |
|
||||
| 'area' |
|
||||
| 'charge' |
|
||||
| 'digital' |
|
||||
| 'electric-current' |
|
||||
| 'energy' |
|
||||
| 'force' |
|
||||
| 'frequency' |
|
||||
| 'illuminance' |
|
||||
| 'length' |
|
||||
| 'mass' |
|
||||
| 'parts-per' |
|
||||
| 'power' |
|
||||
| 'pressure' |
|
||||
| 'speed' |
|
||||
| 'temperature' |
|
||||
| 'time' |
|
||||
| 'torque' |
|
||||
| 'voltage' |
|
||||
| 'volume' |
|
||||
| 'volume-flow-rate'; |
|
||||
|
|
||||
const allMeasures: Record< |
|
||||
AllMeasures, |
|
||||
TbMeasure<AllMeasuresUnits> |
|
||||
> = { |
|
||||
acceleration, |
|
||||
angle, |
|
||||
'angular-acceleration': angularAcceleration, |
|
||||
area, |
|
||||
charge, |
|
||||
digital, |
|
||||
'electric-current': electricCurrent, |
|
||||
energy, |
|
||||
force, |
|
||||
frequency, |
|
||||
illuminance, |
|
||||
length, |
|
||||
mass, |
|
||||
'parts-per': partsPer, |
|
||||
power, |
|
||||
pressure, |
|
||||
speed, |
|
||||
temperature, |
|
||||
time, |
|
||||
torque, |
|
||||
voltage, |
|
||||
volume, |
|
||||
'volume-flow-rate': volumeFlowRate, |
|
||||
}; |
|
||||
|
|
||||
export default allMeasures; |
|
||||
Loading…
Reference in new issue