Skip to content

Commit

Permalink
refactor: getRotationSpeedForVirtualRemoteCommand to getRotationSpeed…
Browse files Browse the repository at this point in the history
…FromFanInfo
  • Loading branch information
jvandenaardweg committed Dec 30, 2022
1 parent c230fce commit 93e429a
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 37 deletions.
15 changes: 8 additions & 7 deletions src/fan-accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { IthoDaalderopAccessoryContext, IthoStatusSanitizedPayload } from './typ
import {
ACTIVE_SPEED_THRESHOLD,
DEFAULT_FAN_NAME,
FALLBACK_VIRTUAL_REMOTE_COMMAND,
FAN_INFO_KEY,
MANUFACTURER,
MAX_ROTATION_SPEED,
MQTT_STATE_TOPIC,
MQTT_STATUS_TOPIC,
} from './settings';
import {
getRotationSpeedForVirtualRemoteCommand,
getRotationSpeedFromFanInfo,
getVirtualRemoteCommandForRotationSpeed,
sanitizeStatusPayload,
} from './utils/api';
Expand Down Expand Up @@ -303,8 +303,8 @@ export class FanAccessory {

if (
(value === this.platform.Characteristic.Active.INACTIVE &&
this.lastStatusPayload?.FanInfo === 'auto') ||
this.lastStatusPayload?.FanInfo === 'medium'
this.lastStatusPayload?.[FAN_INFO_KEY] === 'auto') ||
this.lastStatusPayload?.[FAN_INFO_KEY] === 'medium'
) {
this.log.warn(
'Important, you are disabling the fan, but it is in auto/medium mode. So it will probably turn on again.',
Expand Down Expand Up @@ -572,10 +572,11 @@ export class FanAccessory {

async handleGetRotationSpeed(): Promise<Nullable<CharacteristicValue>> {
if (!this.allowsManualSpeedControl) {
const state = this.lastStatusPayload?.FanInfo || FALLBACK_VIRTUAL_REMOTE_COMMAND;
const rotationSpeed = getRotationSpeedForVirtualRemoteCommand(state);
const fanInfo = this.lastStatusPayload?.[FAN_INFO_KEY];

this.log.info(`RotationSpeed is ${rotationSpeed}/${MAX_ROTATION_SPEED} (${state})`);
const rotationSpeed = getRotationSpeedFromFanInfo(fanInfo);

this.log.info(`RotationSpeed is ${rotationSpeed}/${MAX_ROTATION_SPEED} (${fanInfo})`);

return rotationSpeed;
}
Expand Down
1 change: 1 addition & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ export const MAX_ROTATION_SPEED = 100;
export const ACTIVE_SPEED_THRESHOLD = 20;

export const CO2_LEVEL_SENSOR_KEY = 'CO2level (ppm)';
export const FAN_INFO_KEY = 'FanInfo';

export const FALLBACK_VIRTUAL_REMOTE_COMMAND = 'medium';
15 changes: 10 additions & 5 deletions src/utils/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FanInfo } from '@/types';
import {
getRotationSpeedForVirtualRemoteCommand,
getRotationSpeedFromFanInfo,
getVirtualRemoteCommandForRotationSpeed,
sanitizeStatusPayload,
} from './api';
Expand Down Expand Up @@ -45,19 +46,23 @@ describe('utils/api', () => {

describe('getRotationSpeedForVirtualRemoteCommand()', () => {
it('should return 33.333 when the virtual remote command is "low"', () => {
expect(getRotationSpeedForVirtualRemoteCommand('low')).toEqual(33.333333333333336);
expect(getRotationSpeedFromFanInfo('low')).toEqual(33.333333333333336);
});

it('should return 66.666 when the virtual remote command is "medium"', () => {
expect(getRotationSpeedForVirtualRemoteCommand('medium')).toEqual(66.66666666666667);
expect(getRotationSpeedFromFanInfo('medium')).toEqual(66.66666666666667);
});

it('should return 66.666 when the virtual remote command is "auto"', () => {
expect(getRotationSpeedForVirtualRemoteCommand('auto')).toEqual(66.66666666666667);
expect(getRotationSpeedFromFanInfo('auto')).toEqual(66.66666666666667);
});

it('should return 66.666 when the virtual remote command is not "low", "medium", "auto" or "high"', () => {
expect(getRotationSpeedFromFanInfo('unknown' as FanInfo)).toEqual(66.66666666666667);
});

it('should return 100 when the virtual remote command is "high"', () => {
expect(getRotationSpeedForVirtualRemoteCommand('high')).toEqual(100);
expect(getRotationSpeedFromFanInfo('high')).toEqual(100);
});
});
});
70 changes: 45 additions & 25 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
import { FALLBACK_VIRTUAL_REMOTE_COMMAND } from '@/settings';
import { FanInfo } from '@/types';

const supportedVirtualRemoteCommands = ['low', 'medium', 'high'] as const;
type SupportedVirtualRemoteCommands = typeof supportedVirtualRemoteCommands[number];
// type SupportedVirtualRemoteCommands = 'low' | 'medium' | 'high';
type VirtualRemoteMapping = Record<SupportedVirtualRemoteCommands, [number, number]>;

function getVirtualRemoteMapping(): VirtualRemoteMapping {
const min = 0;
const max = 100;
const oneThird = (max / 3) * 1;
const twoThirds = (max / 3) * 2;

const virtualRemoteMapping = {
low: [min, oneThird], // 0 - 33.333
medium: [oneThird, twoThirds], // 33.333 - 66.666
high: [twoThirds, max], // 66.666 - 100
} satisfies VirtualRemoteMapping;

return virtualRemoteMapping;
}

export function sanitizeStatusPayload<T>(message: string): T {
const data = JSON.parse(message);
Expand All @@ -17,44 +38,43 @@ export function sanitizeStatusPayload<T>(message: string): T {
return Object.fromEntries(sanitizedData) as T;
}

/**
* Get the virtual remote command for the given rotation speed
*
* Will default to the medium speed when no match is found
*/
export function getVirtualRemoteCommandForRotationSpeed(
rotationSpeed: number,
): 'low' | 'medium' | 'high' {
const min = 0;
const max = 100;
const oneThird = (max / 3) * 1;
const twoThirds = (max / 3) * 2;

const virtualRemoteMapping = {
low: [min, oneThird], // 0 - 33.333
medium: [oneThird, twoThirds], // 33.333 - 66.666
high: [twoThirds, max], // 66.666 - 100
} satisfies Record<'low' | 'medium' | 'high', [number, number]>;
): SupportedVirtualRemoteCommands {
const virtualRemoteMapping = getVirtualRemoteMapping();

const virtualRemoteCommand = Object.entries(virtualRemoteMapping).find(([, range]) => {
const [min, max] = range;

return rotationSpeed >= min && rotationSpeed <= max;
})?.[0] as 'low' | 'medium' | 'high';
})?.[0] as SupportedVirtualRemoteCommands;

return virtualRemoteCommand || FALLBACK_VIRTUAL_REMOTE_COMMAND; // fallback to medium when no match is found
}

export function getRotationSpeedForVirtualRemoteCommand(
virtualRemoteCommand: 'low' | 'medium' | 'auto' | 'high',
): number {
const min = 0;
const max = 100;
const oneThird = (max / 3) * 1;
const twoThirds = (max / 3) * 2;
/**
* Map the FanInfo value to the rotation speed
*
* Will default to the medium speed when the fan is in auto mode
* or the FanInfo is not a supported virtual remote command (low, medium, high)
*/
export function getRotationSpeedFromFanInfo(fanInfo?: FanInfo): number {
const virtualRemoteMapping = getVirtualRemoteMapping();

const virtualRemoteMapping = {
low: [min, oneThird], // 0 - 33.333
medium: [oneThird, twoThirds], // 33.333 - 66.666
auto: [oneThird, twoThirds], // 33.333 - 66.666
high: [twoThirds, max], // 66.666 - 100
} satisfies Record<'low' | 'medium' | 'auto' | 'high', [number, number]>;
let virtualRemoteCommand = FALLBACK_VIRTUAL_REMOTE_COMMAND;

// If the FanInfo is low, medium or high, we'll use that as the virtual remote command
// Any other value will fallback to medium, as defined above
if (supportedVirtualRemoteCommands.includes(fanInfo as never)) {
virtualRemoteCommand = fanInfo as SupportedVirtualRemoteCommands;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, maxSpeed] = virtualRemoteMapping[virtualRemoteCommand];

return maxSpeed;
Expand Down

0 comments on commit 93e429a

Please sign in to comment.