Skip to content

Commit d7cf2f1

Browse files
Merge pull request #231 from OpenDTU-App/219-implement-dtu-settings
2 parents e18d39c + afbdd10 commit d7cf2f1

File tree

16 files changed

+714
-41
lines changed

16 files changed

+714
-41
lines changed

.idea/i18nally.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/ApiHandler.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createContext, useContext, useEffect, useMemo } from 'react';
44
import {
55
clearOpenDtuState,
66
setDeviceState,
7+
setDtuSettings,
78
setEventLog,
89
setGridProfile,
910
setInverterDevice,
@@ -186,6 +187,10 @@ export const ApiProvider: FC<PropsWithChildren> = ({ children }) => {
186187
dispatch(setNTPSettings({ data, index }));
187188
});
188189

190+
api.registerOnDtuSettingsHandler((data, index) => {
191+
dispatch(setDtuSettings({ data, index }));
192+
});
193+
189194
log.debug('Connecting API Handler');
190195

191196
api.connect();

src/api/opendtuapi.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { EventLogData } from '@/types/opendtu/eventlog';
1010
import type { GridProfileData } from '@/types/opendtu/gridprofile';
1111
import type { InverterDeviceData } from '@/types/opendtu/inverterDevice';
1212
import type {
13+
DtuSettings,
1314
NetworkSettings,
1415
NTPSettings,
1516
NTPTime,
@@ -118,6 +119,9 @@ class OpenDtuApi {
118119
private onNtpSettingsHandler:
119120
| ((data: NTPSettings, index: Index) => void)
120121
| null = null;
122+
private onDtuSettingsHandler:
123+
| ((data: DtuSettings, index: Index) => void)
124+
| null = null;
121125

122126
private ws: WebSocket | null = null;
123127
// communication
@@ -351,6 +355,18 @@ class OpenDtuApi {
351355
this.onNtpSettingsHandler = null;
352356
}
353357

358+
public registerOnDtuSettingsHandler(
359+
handler: (data: DtuSettings, index: Index) => void,
360+
): void {
361+
log.debug('OpenDtuApi.registerOnDtuSettingsHandler()');
362+
this.onDtuSettingsHandler = handler;
363+
}
364+
365+
public unregisterOnDtuSettingsHandler(): void {
366+
log.debug('OpenDtuApi.unregisterOnDtuSettingsHandler()');
367+
this.onDtuSettingsHandler = null;
368+
}
369+
354370
public async getSystemStatusFromUrl(
355371
url: URL,
356372
): Promise<GetSystemStatusReturn> {
@@ -1401,6 +1417,64 @@ class OpenDtuApi {
14011417
return res.status === 200 && parsed.type === 'success';
14021418
}
14031419

1420+
public async getDtuConfig(): Promise<DtuSettings | null> {
1421+
if (!this.baseUrl) {
1422+
log.error('getDtuConfig', 'no base url');
1423+
return null;
1424+
}
1425+
1426+
const res = await this.makeAuthenticatedRequest('/api/dtu/config', 'GET');
1427+
1428+
if (!res) {
1429+
log.error('getDtuConfig', 'no response');
1430+
return null;
1431+
}
1432+
1433+
if (res.status === 200) {
1434+
const json = await res.json();
1435+
1436+
if (this.onDtuSettingsHandler && this.index !== null) {
1437+
this.onDtuSettingsHandler(json, this.index);
1438+
}
1439+
1440+
log.debug('getDtuConfig', 'success');
1441+
1442+
return json;
1443+
}
1444+
1445+
log.error('getDtuConfig', 'invalid status', res.status);
1446+
1447+
return null;
1448+
}
1449+
1450+
public async setDtuConfig(config: DtuSettings): Promise<boolean | null> {
1451+
if (!this.baseUrl) {
1452+
log.error('setDtuConfig', 'no base url');
1453+
return null;
1454+
}
1455+
1456+
const formData = new FormData();
1457+
formData.append('data', JSON.stringify(config));
1458+
1459+
const res = await this.makeAuthenticatedRequest('/api/dtu/config', 'POST', {
1460+
body: formData,
1461+
});
1462+
1463+
if (!res) {
1464+
log.error('setDtuConfig', 'no response');
1465+
return null;
1466+
}
1467+
1468+
const parsed = await res.json();
1469+
1470+
log.debug('setDtuConfig', 'success', {
1471+
status: res.status,
1472+
parsed,
1473+
});
1474+
1475+
return res.status === 200 && parsed.type === 'success';
1476+
}
1477+
14041478
public async makeAuthenticatedRequest(
14051479
route: string,
14061480
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'PATCH',

src/components/modals/ChangeBooleanValueModal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { View } from 'react-native';
1010

1111
import { spacing } from '@/constants';
1212

13-
export interface ChangeValueModalProps {
13+
export interface ChangeBooleanValueModalProps {
1414
isOpen?: boolean;
1515
onClose?: () => void;
1616
defaultValue?: boolean;
@@ -21,7 +21,7 @@ export interface ChangeValueModalProps {
2121
inputProps?: Omit<SwitchProps, 'value' | 'onValueChange'>;
2222
}
2323

24-
const ChangeTextValueModal: FC<ChangeValueModalProps> = ({
24+
const ChangeBooleanValueModal: FC<ChangeBooleanValueModalProps> = ({
2525
isOpen,
2626
title,
2727
description,
@@ -152,4 +152,4 @@ const ChangeTextValueModal: FC<ChangeValueModalProps> = ({
152152
);
153153
};
154154

155-
export default ChangeTextValueModal;
155+
export default ChangeBooleanValueModal;

src/components/modals/ChangeEnumValueModal.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,14 @@ const ChangeEnumValueModal: FC<ChangeEnumValueModalProps> = ({
124124
<Text variant="bodyMedium">{description}</Text>
125125
</View>
126126
<ScrollView style={{ maxHeight: 350, width: '100%' }}>
127-
<Box mv={4}>
127+
<Box
128+
mv={4}
129+
style={{
130+
backgroundColor: theme.colors.surfaceVariant,
131+
borderRadius: theme.roundness * 6,
132+
paddingVertical: spacing,
133+
}}
134+
>
128135
<RadioButton.Group onValueChange={setValue} value={value}>
129136
{possibleValues.map(({ label, value }) => (
130137
<RadioButton.Item
@@ -133,7 +140,10 @@ const ChangeEnumValueModal: FC<ChangeEnumValueModalProps> = ({
133140
label={label}
134141
labelVariant="bodyMedium"
135142
style={{
136-
backgroundColor: theme.colors.surface,
143+
borderRadius: theme.roundness * 6,
144+
}}
145+
labelStyle={{
146+
borderRadius: theme.roundness * 6,
137147
}}
138148
/>
139149
))}

src/components/modals/ChangeTextValueModal.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { FC } from 'react';
2-
import { useEffect, useRef, useState } from 'react';
2+
import React, { useEffect, useRef, useState } from 'react';
33
import { useTranslation } from 'react-i18next';
44
import type { BottomDrawerMethods } from 'react-native-animated-bottom-drawer';
55
import BottomDrawer from 'react-native-animated-bottom-drawer';
@@ -20,7 +20,7 @@ import { spacing } from '@/constants';
2020

2121
const log = rootLogging.extend('ChangeTextValueModal');
2222

23-
export interface ChangeValueModalProps {
23+
export interface ChangeTextValueModalProps {
2424
isOpen?: boolean;
2525
onClose?: () => void;
2626
defaultValue?: string;
@@ -30,9 +30,10 @@ export interface ChangeValueModalProps {
3030
extraHeight?: number;
3131
inputProps?: Omit<TextInputProps, 'value' | 'onChangeText'>;
3232
validate?: (value: string) => boolean;
33+
allowedRegex?: RegExp;
3334
}
3435

35-
const ChangeTextValueModal: FC<ChangeValueModalProps> = ({
36+
const ChangeTextValueModal: FC<ChangeTextValueModalProps> = ({
3637
isOpen,
3738
title,
3839
description,
@@ -42,6 +43,7 @@ const ChangeTextValueModal: FC<ChangeValueModalProps> = ({
4243
inputProps,
4344
validate,
4445
extraHeight,
46+
allowedRegex,
4547
}) => {
4648
const theme = useTheme();
4749
const drawerRef = useRef<BottomDrawerMethods>(null);
@@ -149,13 +151,17 @@ const ChangeTextValueModal: FC<ChangeValueModalProps> = ({
149151
</View>
150152
<View style={{ marginTop: 40 }}>
151153
<TextInput
154+
{...inputProps}
152155
defaultValue={value}
153156
onChangeText={value => {
157+
if (allowedRegex && !allowedRegex.test(value)) {
158+
return;
159+
}
160+
154161
setWasModified(true);
155162
setError(null);
156163
setValue(value);
157164
}}
158-
{...inputProps}
159165
error={!!error}
160166
/>
161167
<HelperText type="error" visible={!!error}>

src/components/settings/NTPCurrentTimeComponents.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { List } from 'react-native-paper';
66
import useMemoWithInterval from '@/hooks/useMemoWithInterval';
77

88
export interface NTPCurrentTimeComponentsProps {
9-
initalCurrentOpendtuTime?: Date;
9+
initialCurrentOpenDtuTime?: Date;
1010
}
1111

1212
const NTPCurrentTimeComponents: FC<NTPCurrentTimeComponentsProps> = ({
13-
initalCurrentOpendtuTime,
13+
initialCurrentOpenDtuTime,
1414
}) => {
1515
const { t } = useTranslation();
1616

@@ -20,39 +20,42 @@ const NTPCurrentTimeComponents: FC<NTPCurrentTimeComponentsProps> = ({
2020
1000,
2121
);
2222

23-
const [currentOpendtuTime, setCurrentOpendtuTime] = useState<
23+
const [currentOpenDtuTime, setCurrentOpenDtuTime] = useState<
2424
Date | undefined
25-
>(initalCurrentOpendtuTime);
25+
>(initialCurrentOpenDtuTime);
2626

2727
useEffect(() => {
28-
if (!initalCurrentOpendtuTime) {
28+
if (!initialCurrentOpenDtuTime) {
2929
return;
3030
}
3131

32-
setCurrentOpendtuTime(initalCurrentOpendtuTime);
33-
}, [initalCurrentOpendtuTime]);
32+
setCurrentOpenDtuTime(initialCurrentOpenDtuTime);
33+
}, [initialCurrentOpenDtuTime]);
3434

3535
useEffect(() => {
3636
const interval = setInterval(() => {
37-
if (currentOpendtuTime) {
38-
const newDate = new Date(currentOpendtuTime.getTime() + 1000);
37+
if (currentOpenDtuTime) {
38+
const newDate = new Date(currentOpenDtuTime.getTime() + 1000);
3939

40-
setCurrentOpendtuTime(newDate);
40+
setCurrentOpenDtuTime(newDate);
4141
}
4242
}, 1000);
4343

4444
return () => {
4545
clearInterval(interval);
4646
};
47-
}, [currentOpendtuTime]);
47+
}, [currentOpenDtuTime]);
4848

4949
return (
50-
<List.Section title="Manual Time Synchronization">
50+
<List.Section title={t('settings.ntpSettings.manualTimeSync')}>
5151
<List.Item
52-
title="Current OpenDTU Time"
53-
description={currentOpendtuTime?.toString() || t('unknown')}
52+
title={t('settings.ntpSettings.currentOpenDtuTime')}
53+
description={currentOpenDtuTime?.toString() || t('unknown')}
54+
/>
55+
<List.Item
56+
title={t('settings.ntpSettings.currentPhoneTime')}
57+
description={currentLocalTime}
5458
/>
55-
<List.Item title="Current Local Time" description={currentLocalTime} />
5659
</List.Section>
5760
);
5861
};

src/slices/opendtu.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
OpenDTUReduxState,
66
OpenDTUSettings,
77
SetDeviceStateAction,
8+
SetDtuSettingsAction,
89
SetEventLogAction,
910
SetGridProfileAction,
1011
SetInverterDeviceAction,
@@ -362,6 +363,21 @@ const opendtuSlice = createSlice({
362363
.settings as OpenDTUSettings
363364
).ntp = action.payload.data;
364365
},
366+
setDtuSettings: (state, action: SetDtuSettingsAction) => {
367+
if (!state.dtuStates[action.payload.index]) {
368+
state.dtuStates[action.payload.index] = {};
369+
}
370+
371+
if (!state.dtuStates[action.payload.index]?.settings) {
372+
(state.dtuStates[action.payload.index] as OpenDTUDeviceState).settings =
373+
{};
374+
}
375+
376+
(
377+
(state.dtuStates[action.payload.index] as OpenDTUDeviceState)
378+
.settings as OpenDTUSettings
379+
).dtu = action.payload.data;
380+
},
365381
},
366382
});
367383

@@ -388,6 +404,7 @@ export const {
388404
setLimitStatus,
389405
setNetworkSettings,
390406
setNTPSettings,
407+
setDtuSettings,
391408
} = opendtuSlice.actions;
392409

393410
export const { reducer: OpenDTUReducer } = opendtuSlice;

0 commit comments

Comments
 (0)