Skip to content

Commit

Permalink
Add support for brightness message (#45)
Browse files Browse the repository at this point in the history
* Add support for brightness message
Fixes #44

* add slider
  • Loading branch information
evertonstz authored Apr 29, 2024
1 parent e6e44d7 commit fc9c520
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/web-bluetooth": "^0.0.20",
Expand Down
54 changes: 54 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions src/components/MainContent/SelectedDevice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Pax } from '@/pax';
import { post } from '@/pax/containers/api';
import {
BatteryPercentageMessage,
BrightnessMessage,
ColorThemeMessage,
} from '@/pax/core/messages';
import { ColorTheme } from '@/pax/shared/types';
Expand All @@ -16,6 +17,7 @@ import TemperatureProgress from '../TemperatureProgress';
import { ThemePicker } from '../ThemePicker';
import { hardcodedThemes } from '../ThemePicker/colors';
import { Button } from '../ui/button';
import { Slider } from '../ui/slider';
import { Connect } from './SelectedDevice/Connect';

interface SelectedDeviceProps {
Expand Down Expand Up @@ -53,6 +55,9 @@ export const SelectedDevice = ({ currentDevice }: SelectedDeviceProps) => {
if (message instanceof BatteryPercentageMessage) {
actions.setBatteryPercentage(message.percentage);
}
if (message instanceof BrightnessMessage) {
actions.setBrightness(message.brightness);
}
})
.catch(e => {
if (e instanceof BaseBluetoothException) {
Expand Down Expand Up @@ -96,6 +101,22 @@ export const SelectedDevice = ({ currentDevice }: SelectedDeviceProps) => {
actualTemperature={state.actualTemperature}
unit={'C'}
/>
<Slider
disabled={!bluetoothState.connected && !state.brightness}
max={1}
step={0.1}
value={state.brightness ? [state.brightness] : [0]}
onValueChange={value => {
actions.setBrightness(value[0]);
}}
onValueCommit={value => {
const toPost = post(
BrightnessMessage.createWithBrightness(value[0]),
currentDevice,
);
void bluetoothState.writeToMainService(toPost.packet);
}}
/>
<HeaterStatus heaterStatus={state.heatingSate} />
<h1>Device: {!currentDevice ? '' : currentDevice.serial}</h1>
<Button
Expand Down
34 changes: 34 additions & 0 deletions src/components/ui/slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { cn } from '@/lib/utils';
import * as SliderPrimitive from '@radix-ui/react-slider';
import * as React from 'react';

const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
'relative flex w-full touch-none select-none items-center',
className,
)}
{...props}
>
<SliderPrimitive.Track
className={`relative h-2 w-full grow overflow-hidden
rounded-full bg-neutral-100 dark:bg-neutral-800`}
>
<SliderPrimitive.Range className="absolute h-full bg-neutral-900 dark:bg-neutral-50" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb
className={`block h-5 w-5 rounded-full border-2 border-neutral-900
bg-white ring-offset-white transition-colors focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2
disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-50 dark:bg-neutral-950
dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300`}
/>
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;

export { Slider };
3 changes: 3 additions & 0 deletions src/pax/containers/api/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ColorThemeMessage } from '@/pax/core/messages/ColorThemeMessage';
import {
ActualTemperatureMessage,
BatteryPercentageMessage,
BrightnessMessage,
HeaterSetPointMessage,
HeatingStateMessage,
MessageAbs,
Expand Down Expand Up @@ -36,6 +37,8 @@ export const decodeDecryptedPacket = (
return ColorThemeMessage.createWithPacket(packet);
case Messages.ATTRIBUTE_BATTERY:
return new BatteryPercentageMessage(packet);
case Messages.ATTRIBUTE_BRIGHTNESS:
return BrightnessMessage.createWithPacket(packet);
default:
return new UnknownMessage(messageType, packet);
}
Expand Down
112 changes: 112 additions & 0 deletions src/pax/core/messages/BrightnessMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { PaxDecryptedPacket } from '@/pax/containers/lib';
import { Messages } from '@/pax/shared/enums';

import { MessageAbs } from './MessageAbs';
import { ReadAndWriteMessageAbs } from './ReadAndWriteMessageAbs';

export class BrightnessMessage
extends ReadAndWriteMessageAbs
implements MessageAbs
{
readonly brightness: number;
readonly messageType: Messages;
readonly packet: PaxDecryptedPacket;

readonly PaxBrightnessMin = 0;
readonly PaxBrightnessMax = 128;

constructor(
builder:
| BrightnessMessageBuilderFromPacket<BrightnessMessage>
| BrightnessMessageBuilderFromValue<BrightnessMessage>,
) {
super();
this.messageType = Messages.ATTRIBUTE_BRIGHTNESS;
if (builder instanceof BrightnessMessageBuilderFromPacket) {
this.packet = builder.getPacket();
const absoluteBrightness = this.packet.getUint8(1);
const brightnessPercentage =
this.brightnessToPercentage(absoluteBrightness);

this.brightness = brightnessPercentage;
} else if (builder instanceof BrightnessMessageBuilderFromValue) {
this.brightness = builder.getBrightness();
const buffer = new ArrayBuffer(16);
const view = new PaxDecryptedPacket(buffer);
view.setUint8(0, this.messageType);
view.setUint8(1, this.brightnessToAbsolute(this.brightness));
this.packet = view;
} else {
throw new Error('Invalid builder');
}
}

private brightnessToPercentage(brightness: number): number {
return (
(brightness - this.PaxBrightnessMin) /
(this.PaxBrightnessMax - this.PaxBrightnessMin)
);
}

private brightnessToAbsolute(brightness: number): number {
return brightness * (this.PaxBrightnessMax - this.PaxBrightnessMin);
}

static createWithPacket(packet: PaxDecryptedPacket): BrightnessMessage {
const builder = new BrightnessMessageBuilderFromPacket<BrightnessMessage>();
builder.setPacket(packet);
return new BrightnessMessage(builder);
}

static createWithBrightness(brightness: number): BrightnessMessage {
const builder = new BrightnessMessageBuilderFromValue<BrightnessMessage>();
builder.setBrightness(brightness);
return new BrightnessMessage(builder);
}
}

export class BrightnessMessageBuilderFromPacket<T extends BrightnessMessage> {
private packet?: PaxDecryptedPacket;

setPacket(packet: PaxDecryptedPacket): BrightnessMessageBuilderFromPacket<T> {
this.packet = packet;
return this;
}

getPacket(): PaxDecryptedPacket {
if (!this.packet) {
throw new Error('Packet is not set');
}
return this.packet;
}

build(ctor: new (builder: BrightnessMessageBuilderFromPacket<T>) => T): T {
if (!this.packet) {
throw new Error('Packet is not set');
}
return new ctor(this);
}
}

export class BrightnessMessageBuilderFromValue<T extends BrightnessMessage> {
private brightness?: number;

setBrightness(brightness: number): BrightnessMessageBuilderFromValue<T> {
this.brightness = brightness;
return this;
}

getBrightness(): number {
if (!this.brightness) {
throw new Error('Theme is not set');
}
return this.brightness;
}

build(ctor: new (builder: BrightnessMessageBuilderFromValue<T>) => T): T {
if (!this.brightness) {
throw new Error('Brightness is not set');
}
return new ctor(this);
}
}
1 change: 1 addition & 0 deletions src/pax/core/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './ReadAndWriteMessageAbs';
export * from './HeatingStateMessage';
export * from './ColorThemeMessage';
export * from './BatteryPercentageMessage';
export * from './BrightnessMessage';
10 changes: 10 additions & 0 deletions src/state/paxState/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type PaxActions =
| { type: 'SET_HEATING_STATE'; payload: Pax.lib.HeatingStates }
| { type: 'SET_COLOR_THEME'; payload: Pax.lib.ColorTheme }
| { type: 'SET_BATTERY_PERCENTAGE'; payload: number }
| { type: 'SET_BRIGHTNESS'; payload: number }
| { type: 'RESET_PAX_STATE' };

export interface BuiltPaxActions {
Expand All @@ -14,6 +15,7 @@ export interface BuiltPaxActions {
setHeatingState: (heatingSate: Pax.lib.HeatingStates) => void;
setColorTheme: (theme: Pax.lib.ColorTheme) => void;
setBatteryPercentage: (percentage: number) => void;
setBrightness: (brightness: number) => void;
resetPaxState: () => void;
}

Expand Down Expand Up @@ -56,6 +58,13 @@ const setBatteryPercentage = (
dispatch({ type: 'SET_BATTERY_PERCENTAGE', payload: percentage });
};

const setBrightness = (
dispatch: React.Dispatch<PaxActions>,
brightness: number,
) => {
dispatch({ type: 'SET_BRIGHTNESS', payload: brightness });
};

export const buildActions = (
dispatch: React.Dispatch<PaxActions>,
): BuiltPaxActions => {
Expand All @@ -71,5 +80,6 @@ export const buildActions = (
setBatteryPercentage: (percentage: number) =>
setBatteryPercentage(dispatch, percentage),
resetPaxState: () => resetPaxState(dispatch),
setBrightness: (brightness: number) => setBrightness(dispatch, brightness),
};
};
4 changes: 4 additions & 0 deletions src/state/paxState/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const initialPaxState: PaxState = {
heaterSetPointTemperature: 0,
heatingSate: undefined,
colorTheme: undefined,
batteryPercentage: undefined,
brightness: undefined,
};

const reducer = (state: PaxState, action: PaxActions): PaxState => {
Expand All @@ -20,6 +22,8 @@ const reducer = (state: PaxState, action: PaxActions): PaxState => {
return { ...state, colorTheme: action.payload };
case 'SET_BATTERY_PERCENTAGE':
return { ...state, batteryPercentage: action.payload };
case 'SET_BRIGHTNESS':
return { ...state, brightness: action.payload };
case 'RESET_PAX_STATE':
return initialPaxState;
default:
Expand Down
1 change: 1 addition & 0 deletions src/state/paxState/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export interface PaxState {
heatingSate?: Pax.lib.HeatingStates;
colorTheme?: Pax.lib.ColorTheme;
batteryPercentage?: number;
brightness?: number;
}

0 comments on commit fc9c520

Please sign in to comment.