add tsi charts
This commit is contained in:
Родитель
c80a8ae472
Коммит
0c28374226
|
@ -279,6 +279,8 @@ PODS:
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-torch (1.2.0):
|
- react-native-torch (1.2.0):
|
||||||
- React
|
- React
|
||||||
|
- react-native-webview (11.3.2):
|
||||||
|
- React-Core
|
||||||
- React-perflogger (0.64.0)
|
- React-perflogger (0.64.0)
|
||||||
- React-RCTActionSheet (0.64.0):
|
- React-RCTActionSheet (0.64.0):
|
||||||
- React-Core/RCTActionSheetHeaders (= 0.64.0)
|
- React-Core/RCTActionSheetHeaders (= 0.64.0)
|
||||||
|
@ -445,6 +447,7 @@ DEPENDENCIES:
|
||||||
- react-native-maps (from `../node_modules/react-native-maps`)
|
- react-native-maps (from `../node_modules/react-native-maps`)
|
||||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||||
- react-native-torch (from `../node_modules/react-native-torch`)
|
- react-native-torch (from `../node_modules/react-native-torch`)
|
||||||
|
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||||
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
|
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||||
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
|
||||||
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
|
||||||
|
@ -536,6 +539,8 @@ EXTERNAL SOURCES:
|
||||||
:path: "../node_modules/react-native-safe-area-context"
|
:path: "../node_modules/react-native-safe-area-context"
|
||||||
react-native-torch:
|
react-native-torch:
|
||||||
:path: "../node_modules/react-native-torch"
|
:path: "../node_modules/react-native-torch"
|
||||||
|
react-native-webview:
|
||||||
|
:path: "../node_modules/react-native-webview"
|
||||||
React-perflogger:
|
React-perflogger:
|
||||||
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
|
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
|
||||||
React-RCTActionSheet:
|
React-RCTActionSheet:
|
||||||
|
@ -622,6 +627,7 @@ SPEC CHECKSUMS:
|
||||||
react-native-maps: f4b89da81626ad7f151a8bfcb79733295d31ce5c
|
react-native-maps: f4b89da81626ad7f151a8bfcb79733295d31ce5c
|
||||||
react-native-safe-area-context: e471852c5ed67eea4b10c5d9d43c1cebae3b231d
|
react-native-safe-area-context: e471852c5ed67eea4b10c5d9d43c1cebae3b231d
|
||||||
react-native-torch: 86f052de32cc110922292770cdb389f00b5d7320
|
react-native-torch: 86f052de32cc110922292770cdb389f00b5d7320
|
||||||
|
react-native-webview: aea3233f26253f5c360164ee87d01ef9f7b9a27f
|
||||||
React-perflogger: 9c547d8f06b9bf00cb447f2b75e8d7f19b7e02af
|
React-perflogger: 9c547d8f06b9bf00cb447f2b75e8d7f19b7e02af
|
||||||
React-RCTActionSheet: 3080b6e12e0e1a5b313c8c0050699b5c794a1b11
|
React-RCTActionSheet: 3080b6e12e0e1a5b313c8c0050699b5c794a1b11
|
||||||
React-RCTAnimation: 3f96f21a497ae7dabf4d2f150ee43f906aaf516f
|
React-RCTAnimation: 3f96f21a497ae7dabf4d2f150ee43f906aaf516f
|
||||||
|
|
|
@ -25,6 +25,13 @@
|
||||||
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
|
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
|
||||||
remoteInfo = my_first_pnp_device;
|
remoteInfo = my_first_pnp_device;
|
||||||
};
|
};
|
||||||
|
4ABDA6BE260283330068B64E /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 4A45C58725FFDC06005A8543 /* RCTTorch.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = DA5891D81BA9A9FC002B4DB2;
|
||||||
|
remoteInfo = RCTTorch;
|
||||||
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
@ -110,6 +117,14 @@
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4ABDA6BB260283330068B64E /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4ABDA6BF260283330068B64E /* libRCTTorch.a */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
6678D5C939B9D8E03D5C4EBA /* Pods */ = {
|
6678D5C939B9D8E03D5C4EBA /* Pods */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -229,6 +244,12 @@
|
||||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
projectReferences = (
|
||||||
|
{
|
||||||
|
ProductGroup = 4ABDA6BB260283330068B64E /* Products */;
|
||||||
|
ProjectRef = 4A45C58725FFDC06005A8543 /* RCTTorch.xcodeproj */;
|
||||||
|
},
|
||||||
|
);
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
13B07F861A680F5B00A75B9A /* my_first_pnp_device */,
|
13B07F861A680F5B00A75B9A /* my_first_pnp_device */,
|
||||||
|
@ -237,6 +258,16 @@
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXReferenceProxy section */
|
||||||
|
4ABDA6BF260283330068B64E /* libRCTTorch.a */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = archive.ar;
|
||||||
|
path = libRCTTorch.a;
|
||||||
|
remoteRef = 4ABDA6BE260283330068B64E /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
|
/* End PBXReferenceProxy section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
00E356EC1AD99517003FC87E /* Resources */ = {
|
00E356EC1AD99517003FC87E /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
|
|
|
@ -7653,9 +7653,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-native-azure-iotcentral-client": {
|
"react-native-azure-iotcentral-client": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.8-b.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-azure-iotcentral-client/-/react-native-azure-iotcentral-client-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-azure-iotcentral-client/-/react-native-azure-iotcentral-client-1.1.8-b.0.tgz",
|
||||||
"integrity": "sha512-YOWAPS0j1/nFHFWskTKKZqcjeLq1De9TsEWohaQ6Ds9DrnG1U172ocqjaCKXpDYpGKWwY7mgvfQ77Dd+6NxgDA==",
|
"integrity": "sha512-4NAZwzxH59eFnwK/knVrydLzNvgXXuvP/YMuVChdth5v3zqJ30aK6G4qaVxGX+Ihp1e5+kcokyGDreGfoHH3Fg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"crypto-js": "^3.3.0",
|
"crypto-js": "^3.3.0",
|
||||||
"react-native-get-random-values": "^1.4.0",
|
"react-native-get-random-values": "^1.4.0",
|
||||||
|
@ -7995,6 +7995,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-webview": {
|
||||||
|
"version": "11.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-11.3.2.tgz",
|
||||||
|
"integrity": "sha512-j+0eUKYY3MCO7DRhZaIPY6+0q+Yo1Iyhz5f7cde+i5vR71CcJ/60DhZPw5SSqXZnZiVX0Myz1D46u8dtfRmQFg==",
|
||||||
|
"requires": {
|
||||||
|
"escape-string-regexp": "2.0.0",
|
||||||
|
"invariant": "2.2.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"escape-string-regexp": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-refresh": {
|
"react-refresh": {
|
||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-native": "0.64.0",
|
"react-native": "0.64.0",
|
||||||
"react-native-animatable": "^1.3.3",
|
"react-native-animatable": "^1.3.3",
|
||||||
"react-native-azure-iotcentral-client": "^1.1.7",
|
"react-native-azure-iotcentral-client": "^1.1.8-b.0",
|
||||||
"react-native-camera": "^3.43.0",
|
"react-native-camera": "^3.43.0",
|
||||||
"react-native-charts-wrapper": "^0.5.7",
|
"react-native-charts-wrapper": "^0.5.7",
|
||||||
"react-native-circular-progress": "^1.3.7",
|
"react-native-circular-progress": "^1.3.7",
|
||||||
|
@ -44,7 +44,8 @@
|
||||||
"react-native-sensors": "^7.2.0",
|
"react-native-sensors": "^7.2.0",
|
||||||
"react-native-svg": "^12.1.0",
|
"react-native-svg": "^12.1.0",
|
||||||
"react-native-torch": "^1.2.0",
|
"react-native-torch": "^1.2.0",
|
||||||
"react-native-vector-icons": "^8.1.0"
|
"react-native-vector-icons": "^8.1.0",
|
||||||
|
"react-native-webview": "^11.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.9",
|
"@babel/core": "^7.12.9",
|
||||||
|
|
233
src/App.tsx
233
src/App.tsx
|
@ -5,7 +5,7 @@ import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import {View, Platform, Alert} from 'react-native';
|
import { View, Platform, Alert } from 'react-native';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
import {
|
import {
|
||||||
NavigationContainer,
|
NavigationContainer,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import {
|
import {
|
||||||
Screens,
|
Screens,
|
||||||
NavigationScreens,
|
NavigationScreens,
|
||||||
|
@ -26,8 +26,10 @@ import {
|
||||||
ENABLE_DISABLE_COMMAND,
|
ENABLE_DISABLE_COMMAND,
|
||||||
SET_FREQUENCY_COMMAND,
|
SET_FREQUENCY_COMMAND,
|
||||||
ItemProps,
|
ItemProps,
|
||||||
} from './types';
|
LIGHT_TOGGLE_COMMAND,
|
||||||
import {SafeAreaProvider} from 'react-native-safe-area-context';
|
ChartType
|
||||||
|
} from 'types';
|
||||||
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import {
|
import {
|
||||||
LogsProvider,
|
LogsProvider,
|
||||||
StorageProvider,
|
StorageProvider,
|
||||||
|
@ -37,11 +39,10 @@ import {
|
||||||
ThemeMode,
|
ThemeMode,
|
||||||
} from 'contexts';
|
} from 'contexts';
|
||||||
import LogoIcon from './assets/IotcLogo.svg';
|
import LogoIcon from './assets/IotcLogo.svg';
|
||||||
import {Icon} from 'react-native-elements';
|
import { Icon } from 'react-native-elements';
|
||||||
import {createStackNavigator, HeaderTitle} from '@react-navigation/stack';
|
import { createStackNavigator, HeaderTitle } from '@react-navigation/stack';
|
||||||
import {camelToName, Text} from './components/typography';
|
import { camelToName, Text } from './components/typography';
|
||||||
import Insight, {ChartType} from './Insight';
|
import { Welcome } from './Welcome';
|
||||||
import {Welcome} from './Welcome';
|
|
||||||
import Logs from './Logs';
|
import Logs from './Logs';
|
||||||
import {
|
import {
|
||||||
IIcon,
|
IIcon,
|
||||||
|
@ -52,9 +53,9 @@ import {
|
||||||
useSimulation,
|
useSimulation,
|
||||||
} from 'hooks';
|
} from 'hooks';
|
||||||
import FileUpload from './FileUpload';
|
import FileUpload from './FileUpload';
|
||||||
import {Registration} from './Registration';
|
import { Registration } from './Registration';
|
||||||
import CardView from './CardView';
|
import CardView from './CardView';
|
||||||
import {Loader} from './components/loader';
|
import { Loader } from './components/loader';
|
||||||
import {
|
import {
|
||||||
IIoTCCommand,
|
IIoTCCommand,
|
||||||
IIoTCCommandResponse,
|
IIoTCCommandResponse,
|
||||||
|
@ -62,10 +63,13 @@ import {
|
||||||
IoTCClient,
|
IoTCClient,
|
||||||
IOTC_EVENTS,
|
IOTC_EVENTS,
|
||||||
} from 'react-native-azure-iotcentral-client';
|
} from 'react-native-azure-iotcentral-client';
|
||||||
import {AVAILABLE_SENSORS} from 'sensors';
|
import { AVAILABLE_SENSORS } from 'sensors';
|
||||||
|
import Torch from 'react-native-torch';
|
||||||
|
import Chart from 'Chart';
|
||||||
|
|
||||||
const Tab = createBottomTabNavigator<NavigationScreens>();
|
const Tab = createBottomTabNavigator<NavigationScreens>();
|
||||||
const Stack = createStackNavigator();
|
const Stack = createStackNavigator();
|
||||||
|
const TITLE = 'My First PnP Device';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [initialized, setInitialized] = useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
|
@ -88,7 +92,7 @@ export default function App() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navigation = React.memo(() => {
|
const Navigation = React.memo(() => {
|
||||||
const {mode} = useContext(ThemeContext);
|
const { mode } = useContext(ThemeContext);
|
||||||
return (
|
return (
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
theme={mode === ThemeMode.DARK ? DarkTheme : DefaultTheme}>
|
theme={mode === ThemeMode.DARK ? DarkTheme : DefaultTheme}>
|
||||||
|
@ -96,14 +100,14 @@ const Navigation = React.memo(() => {
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="root"
|
name="root"
|
||||||
options={({navigation}: {navigation: NavigationProperty}) => ({
|
options={({ navigation }: { navigation: NavigationProperty }) => ({
|
||||||
headerTitle: () => null,
|
headerTitle: () => null,
|
||||||
headerLeft: () => <Logo />,
|
headerLeft: () => <Logo />,
|
||||||
headerRight: () => <Profile navigate={navigation.navigate} />,
|
headerRight: () => <Profile navigate={navigation.navigate} />,
|
||||||
})}
|
})}
|
||||||
component={Root}
|
component={Root}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
{/* <Stack.Screen
|
||||||
name="Insight"
|
name="Insight"
|
||||||
component={Insight}
|
component={Insight}
|
||||||
options={({route}) => {
|
options={({route}) => {
|
||||||
|
@ -119,10 +123,27 @@ const Navigation = React.memo(() => {
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}}
|
}}
|
||||||
|
/> */}
|
||||||
|
<Stack.Screen
|
||||||
|
name="Insight"
|
||||||
|
component={Chart}
|
||||||
|
options={({ route }) => {
|
||||||
|
let data = {};
|
||||||
|
if (route.params) {
|
||||||
|
const params = route.params as NavigationParams;
|
||||||
|
if (params.title) {
|
||||||
|
data = { ...data, headerTitle: params.title };
|
||||||
|
}
|
||||||
|
if (params.backTitle) {
|
||||||
|
data = { ...data, headerBackTitle: params.backTitle };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="Settings"
|
name="Settings"
|
||||||
options={({navigation}: {navigation: NavigationProperty}) => ({
|
options={({ navigation }: { navigation: NavigationProperty }) => ({
|
||||||
stackAnimation: 'flip',
|
stackAnimation: 'flip',
|
||||||
headerTitle: Platform.select({
|
headerTitle: Platform.select({
|
||||||
ios: undefined,
|
ios: undefined,
|
||||||
|
@ -156,7 +177,7 @@ const Root = React.memo(() => {
|
||||||
await client.sendProperty({
|
await client.sendProperty({
|
||||||
[PROPERTY]: {
|
[PROPERTY]: {
|
||||||
__t: 'c',
|
__t: 'c',
|
||||||
...properties.reduce((obj, p) => ({...obj, [p.id]: p.value}), {}),
|
...properties.reduce((obj, p) => ({ ...obj, [p.id]: p.value }), {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -170,7 +191,7 @@ const Root = React.memo(() => {
|
||||||
|
|
||||||
// connect client if credentials are retrieved
|
// connect client if credentials are retrieved
|
||||||
|
|
||||||
const iconsRef = useRef<{[x in ScreenNames]: IIcon}>({
|
const iconsRef = useRef<{ [x in ScreenNames]: IIcon }>({
|
||||||
[Screens.TELEMETRY_SCREEN]: Platform.select({
|
[Screens.TELEMETRY_SCREEN]: Platform.select({
|
||||||
ios: {
|
ios: {
|
||||||
name: 'stats-chart-outline',
|
name: 'stats-chart-outline',
|
||||||
|
@ -225,8 +246,8 @@ const Root = React.memo(() => {
|
||||||
async (componentName: string, id: string, value: any) => {
|
async (componentName: string, id: string, value: any) => {
|
||||||
if (iotcentralClient && iotcentralClient.isConnected()) {
|
if (iotcentralClient && iotcentralClient.isConnected()) {
|
||||||
await iotcentralClient.sendTelemetry(
|
await iotcentralClient.sendTelemetry(
|
||||||
{[id]: value},
|
{ [id]: value },
|
||||||
{'$.sub': componentName},
|
{ '$.sub': componentName },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -236,18 +257,48 @@ const Root = React.memo(() => {
|
||||||
const onCommandUpdate = useCallback(async (command: IIoTCCommand) => {
|
const onCommandUpdate = useCallback(async (command: IIoTCCommand) => {
|
||||||
let data: any;
|
let data: any;
|
||||||
data = JSON.parse(command.requestPayload);
|
data = JSON.parse(command.requestPayload);
|
||||||
|
|
||||||
|
if (command.name === LIGHT_TOGGLE_COMMAND) {
|
||||||
|
const fn = async () => {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
console.log(`accendo per ${data.duration}`);
|
||||||
|
Torch.switchState(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(`spengo per ${data.duration}`);
|
||||||
|
Torch.switchState(false);
|
||||||
|
resolve();
|
||||||
|
}, data.duration * 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (data.pulses && data.pulses > 1) {
|
||||||
|
let count = 0;
|
||||||
|
const intv: number = setInterval(async () => {
|
||||||
|
if (count === data.pulses) {
|
||||||
|
clearInterval(intv);
|
||||||
|
await command.reply(IIoTCCommandResponse.SUCCESS, 'Executed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fn();
|
||||||
|
count++;
|
||||||
|
}, data.duration * 1000 + data.delay); // repeat light on with 1s delay from each other
|
||||||
|
} else {
|
||||||
|
await fn();
|
||||||
|
await command.reply(IIoTCCommandResponse.SUCCESS, 'Executed');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data.sensor) {
|
if (data.sensor) {
|
||||||
if (command.name === ENABLE_DISABLE_COMMAND) {
|
const sensor = sensorRef.current.find(s => s.id === data.sensor);
|
||||||
const sensor = sensorRef.current.find(s => s.id === data.sensor);
|
if (sensor) {
|
||||||
if (sensor) {
|
switch (command.name) {
|
||||||
sensor.enable(data.enable ? data.enable : false);
|
case ENABLE_DISABLE_COMMAND:
|
||||||
await command.reply(IIoTCCommandResponse.SUCCESS, 'Enable');
|
sensor.enable(data.enable ? data.enable : false);
|
||||||
}
|
await command.reply(IIoTCCommandResponse.SUCCESS, 'Enable');
|
||||||
} else if (command.name === SET_FREQUENCY_COMMAND) {
|
break;
|
||||||
const sensor = sensorRef.current.find(s => s.id === data.sensor);
|
case SET_FREQUENCY_COMMAND:
|
||||||
if (sensor) {
|
sensor.sendInterval(data.interval ? data.interval * 1000 : 5000);
|
||||||
sensor.sendInterval(data.interval ? data.interval * 1000 : 5000);
|
await command.reply(IIoTCCommandResponse.SUCCESS, 'Frequency');
|
||||||
await command.reply(IIoTCCommandResponse.SUCCESS, 'Frequency');
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,7 +306,7 @@ const Root = React.memo(() => {
|
||||||
|
|
||||||
const onPropUpdate = useCallback(
|
const onPropUpdate = useCallback(
|
||||||
async (prop: IIoTCProperty) => {
|
async (prop: IIoTCProperty) => {
|
||||||
let {name, value} = prop;
|
let { name, value } = prop;
|
||||||
if (value.__t === 'c') {
|
if (value.__t === 'c') {
|
||||||
// inside a component: TODO: change sdk
|
// inside a component: TODO: change sdk
|
||||||
name = Object.keys(value).filter(v => v !== '__t')[0];
|
name = Object.keys(value).filter(v => v !== '__t')[0];
|
||||||
|
@ -335,12 +386,12 @@ const Root = React.memo(() => {
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
key="tab"
|
key="tab"
|
||||||
tabBarOptions={Platform.select({
|
tabBarOptions={Platform.select({
|
||||||
android: {safeAreaInsets: {bottom: 0}},
|
android: { safeAreaInsets: { bottom: 0 } },
|
||||||
})}>
|
})}>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name={Screens.TELEMETRY_SCREEN}
|
name={Screens.TELEMETRY_SCREEN}
|
||||||
options={{
|
options={{
|
||||||
tabBarIcon: ({color, size}) => (
|
tabBarIcon: ({ color, size }) => (
|
||||||
<TabBarIcon icon={icons.Telemetry} color={color} size={size} />
|
<TabBarIcon icon={icons.Telemetry} color={color} size={size} />
|
||||||
),
|
),
|
||||||
}}>
|
}}>
|
||||||
|
@ -358,48 +409,48 @@ const Root = React.memo(() => {
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name={Screens.PROPERTIES_SCREEN}
|
name={Screens.PROPERTIES_SCREEN}
|
||||||
options={{
|
options={{
|
||||||
tabBarIcon: ({color, size}) => (
|
tabBarIcon: ({ color, size }) => (
|
||||||
<TabBarIcon icon={icons.Properties} color={color} size={size} />
|
<TabBarIcon icon={icons.Properties} color={color} size={size} />
|
||||||
),
|
),
|
||||||
}}>
|
}}>
|
||||||
{propertiesLoading
|
{propertiesLoading
|
||||||
? () => (
|
? () => (
|
||||||
<Loader
|
<Loader
|
||||||
message={'Waiting for properties...'}
|
message={'Waiting for properties...'}
|
||||||
visible={true}
|
visible={true}
|
||||||
style={{flex: 1, justifyContent: 'center'}}
|
style={{ flex: 1, justifyContent: 'center' }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: () => (
|
: () => (
|
||||||
<CardView
|
<CardView
|
||||||
items={properties}
|
items={properties}
|
||||||
componentName="Property"
|
componentName="Property"
|
||||||
onEdit={async (item, value) => {
|
onEdit={async (item, value) => {
|
||||||
try {
|
try {
|
||||||
await iotcentralClient?.sendProperty({
|
await iotcentralClient?.sendProperty({
|
||||||
[PROPERTY]: {__t: 'c', [item.id]: value},
|
[PROPERTY]: { __t: 'c', [item.id]: value },
|
||||||
});
|
});
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Property',
|
'Property',
|
||||||
`Property ${item.name} successfully sent to IoT Central`,
|
`Property ${item.name} successfully sent to IoT Central`,
|
||||||
[{text: 'OK'}],
|
[{ text: 'OK' }],
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Property',
|
'Property',
|
||||||
`Property ${item.name} not sent to IoT Central`,
|
`Property ${item.name} not sent to IoT Central`,
|
||||||
[{text: 'OK'}],
|
[{ text: 'OK' }],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tab.Screen>
|
</Tab.Screen>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name={Screens.FILE_UPLOAD_SCREEN}
|
name={Screens.FILE_UPLOAD_SCREEN}
|
||||||
component={FileUpload}
|
component={FileUpload}
|
||||||
options={{
|
options={{
|
||||||
tabBarIcon: ({color, size}) => (
|
tabBarIcon: ({ color, size }) => (
|
||||||
<TabBarIcon
|
<TabBarIcon
|
||||||
icon={icons['Image Upload']}
|
icon={icons['Image Upload']}
|
||||||
color={color}
|
color={color}
|
||||||
|
@ -412,7 +463,7 @@ const Root = React.memo(() => {
|
||||||
name={Screens.LOGS_SCREEN}
|
name={Screens.LOGS_SCREEN}
|
||||||
component={Logs}
|
component={Logs}
|
||||||
options={{
|
options={{
|
||||||
tabBarIcon: ({color, size}) => (
|
tabBarIcon: ({ color, size }) => (
|
||||||
<TabBarIcon icon={icons.Logs} color={color} size={size} />
|
<TabBarIcon icon={icons.Logs} color={color} size={size} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
@ -432,24 +483,24 @@ const getCardView = (items: ItemProps[], name: string, detail: boolean) => ({
|
||||||
onItemPress={
|
onItemPress={
|
||||||
detail
|
detail
|
||||||
? item => {
|
? item => {
|
||||||
navigation.navigate('Insight', {
|
navigation.navigate('Insight', {
|
||||||
chartType:
|
chartType:
|
||||||
item.id === AVAILABLE_SENSORS.GEOLOCATION
|
item.id === AVAILABLE_SENSORS.GEOLOCATION
|
||||||
? ChartType.MAP
|
? ChartType.MAP
|
||||||
: ChartType.DEFAULT,
|
: ChartType.DEFAULT,
|
||||||
currentValue: item.value,
|
currentValue: item.value,
|
||||||
telemetryId: item.id,
|
telemetryId: item.id,
|
||||||
title: camelToName(item.id),
|
title: camelToName(item.id),
|
||||||
backTitle: 'Telemetry',
|
backTitle: 'Telemetry',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Logo = React.memo(() => {
|
const Logo = React.memo(() => {
|
||||||
const {colors} = useTheme();
|
const { colors } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
@ -467,25 +518,25 @@ const Logo = React.memo(() => {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: 0.1,
|
letterSpacing: 0.1,
|
||||||
}}>
|
}}>
|
||||||
Azure IoT Central
|
{TITLE}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const Profile = React.memo((props: {navigate: any}) => {
|
const Profile = React.memo((props: { navigate: any }) => {
|
||||||
const {colors} = useTheme();
|
const { colors } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View style={{marginHorizontal: 10}}>
|
<View style={{ marginHorizontal: 10 }}>
|
||||||
<Icon
|
<Icon
|
||||||
style={{marginEnd: 20}}
|
style={{ marginEnd: 20 }}
|
||||||
name={
|
name={
|
||||||
Platform.select({
|
Platform.select({
|
||||||
ios: 'settings-outline',
|
ios: 'settings-outline',
|
||||||
android: 'settings',
|
android: 'settings',
|
||||||
}) as string
|
}) as string
|
||||||
}
|
}
|
||||||
type={Platform.select({ios: 'ionicon', android: 'material'})}
|
type={Platform.select({ ios: 'ionicon', android: 'material' })}
|
||||||
color={colors.text}
|
color={colors.text}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
props.navigate('Settings');
|
props.navigate('Settings');
|
||||||
|
@ -495,21 +546,21 @@ const Profile = React.memo((props: {navigate: any}) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const BackButton = React.memo((props: {goBack: any; title: string}) => {
|
const BackButton = React.memo((props: { goBack: any; title: string }) => {
|
||||||
const {colors} = useTheme();
|
const { colors } = useTheme();
|
||||||
const {goBack, title} = props;
|
const { goBack, title } = props;
|
||||||
return (
|
return (
|
||||||
<View style={{flexDirection: 'row', marginLeft: 10, alignItems: 'center'}}>
|
<View style={{ flexDirection: 'row', marginLeft: 10, alignItems: 'center' }}>
|
||||||
<Icon name="close" color={colors.text} onPress={goBack} />
|
<Icon name="close" color={colors.text} onPress={goBack} />
|
||||||
{Platform.OS === 'android' && (
|
{Platform.OS === 'android' && (
|
||||||
<HeaderTitle style={{marginLeft: 20}}>{title}</HeaderTitle>
|
<HeaderTitle style={{ marginLeft: 20 }}>{title}</HeaderTitle>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const TabBarIcon = React.memo<{icon: IIcon; color: string; size: number}>(
|
const TabBarIcon = React.memo<{ icon: IIcon; color: string; size: number }>(
|
||||||
({icon, color, size}) => {
|
({ icon, color, size }) => {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
name={icon ? icon.name : 'home'}
|
name={icon ? icon.name : 'home'}
|
||||||
|
|
|
@ -0,0 +1,396 @@
|
||||||
|
import { RouteProp, useTheme } from '@react-navigation/native';
|
||||||
|
import React from 'react';
|
||||||
|
import { WebView } from 'react-native-webview';
|
||||||
|
import { NavigationParams, DATA_AVAILABLE_EVENT, LineChartOptions, ChartType } from 'types';
|
||||||
|
import { SensorMap } from 'sensors';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import {
|
||||||
|
Name,
|
||||||
|
Text,
|
||||||
|
getRandomColor,
|
||||||
|
LightenDarkenColor,
|
||||||
|
} from 'components/typography';
|
||||||
|
import { AnimatedCircularProgress } from 'react-native-circular-progress';
|
||||||
|
import Map from 'components/map';
|
||||||
|
import { Loader } from 'components/loader';
|
||||||
|
import { useScreenDimensions } from 'hooks/layout';
|
||||||
|
|
||||||
|
type SeriesData = {
|
||||||
|
[date: string]: {
|
||||||
|
[field: string]: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ChartData = {
|
||||||
|
[groupId: string]: {
|
||||||
|
[telemetryId: string]: SeriesData;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TelemetryData = {
|
||||||
|
[telemetryId: string]: {
|
||||||
|
values: {
|
||||||
|
timestamp: string;
|
||||||
|
value: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TelemetryMetaData = {
|
||||||
|
[telemetryId: string]: {
|
||||||
|
displayName: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Chart = React.memo<{
|
||||||
|
route: RouteProp<
|
||||||
|
Record<
|
||||||
|
string,
|
||||||
|
NavigationParams & {
|
||||||
|
chartType: ChartType;
|
||||||
|
telemetryId: string;
|
||||||
|
currentValue: any;
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
'Insight'
|
||||||
|
>;
|
||||||
|
}>(({ route }) => {
|
||||||
|
const { colors, dark } = useTheme();
|
||||||
|
const { screen } = useScreenDimensions();
|
||||||
|
const { chartType, telemetryId, currentValue } = route.params;
|
||||||
|
const [data, setData] = React.useState<TelemetryData>({});
|
||||||
|
const [metadata, setMetadata] = React.useState<TelemetryMetaData>({});
|
||||||
|
const chartRef = React.useRef<WebView>(null);
|
||||||
|
const mapStyle = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
flex: 3,
|
||||||
|
margin: 20,
|
||||||
|
borderRadius: 20,
|
||||||
|
...(!dark
|
||||||
|
? {
|
||||||
|
shadowColor: "'rgba(0, 0, 0, 0.14)'",
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 3,
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.8,
|
||||||
|
shadowRadius: 3.84,
|
||||||
|
elevation: 5,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}),
|
||||||
|
[dark],
|
||||||
|
);
|
||||||
|
const chartOptions: LineChartOptions = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
noAnimate: true,
|
||||||
|
brushHandlesVisible: false,
|
||||||
|
snapBrush: false,
|
||||||
|
interpolationFunction: 'curveMonotoneX',
|
||||||
|
theme: dark ? 'dark' : 'light',
|
||||||
|
offset: 'Local',
|
||||||
|
legend: 'compact',
|
||||||
|
includeDots: true,
|
||||||
|
hideChartControlPanel: true,
|
||||||
|
}),
|
||||||
|
[dark],
|
||||||
|
);
|
||||||
|
|
||||||
|
const chartDataOptions = React.useMemo(
|
||||||
|
() =>
|
||||||
|
Object.keys(metadata).map(telemetryId => ({
|
||||||
|
alias: metadata[telemetryId].displayName,
|
||||||
|
color: metadata[telemetryId].color,
|
||||||
|
})),
|
||||||
|
[metadata],
|
||||||
|
);
|
||||||
|
|
||||||
|
const html = React.useMemo(
|
||||||
|
() => `<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://unpkg.com/tsiclient@latest/tsiclient.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/tsiclient@latest/tsiclient.css">
|
||||||
|
</link>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
background-color:${colors.background};
|
||||||
|
}
|
||||||
|
.tsi-seriesName {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.tsi-seriesName span {
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
.tsi-lineChartSVG {
|
||||||
|
height: 110% !important;
|
||||||
|
}
|
||||||
|
.tsi-legend.compact {
|
||||||
|
margin-bottom:60px;
|
||||||
|
}
|
||||||
|
.tsi-legend.compact .tsi-seriesLabel .tsi-splitByContainer .tsi-splitByLabel {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 4px;
|
||||||
|
padding: 0 4px 1px 4px;
|
||||||
|
margin-top: 1px;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
.tsi-legend.compact .tsi-seriesLabel .tsi-splitByContainer .tsi-splitByLabel .tsi-seriesName {
|
||||||
|
max-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tsi-legend.compact .tsi-seriesLabel .tsi-splitByContainer .tsi-splitByLabel .tsi-colorKey {
|
||||||
|
top: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tsi-lineChart .tsi-lineChartSVG text.standardYAxisText {
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tsi-lineChart .tsi-lineChartSVG text {
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
tsiClient = new TsiClient();
|
||||||
|
data = [];
|
||||||
|
|
||||||
|
lineChart = new tsiClient.ux.LineChart(document.getElementById('chart1'));
|
||||||
|
lineChart.render(data, ${JSON.stringify(
|
||||||
|
chartOptions,
|
||||||
|
)}, ${JSON.stringify(chartDataOptions)});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<body>
|
||||||
|
<div id="chart1" style="width: 100%; height: 1000px; margin-top: 40px;"></div>
|
||||||
|
</body>
|
||||||
|
</head>
|
||||||
|
</html>`,
|
||||||
|
[colors, chartOptions, chartDataOptions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateData = React.useCallback(
|
||||||
|
(id: string, value: any) => {
|
||||||
|
if (id !== telemetryId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof value !== 'number') {
|
||||||
|
// data is composite
|
||||||
|
setData(current => {
|
||||||
|
Object.keys(value).forEach(fieldId => {
|
||||||
|
if (!metadata[fieldId]) {
|
||||||
|
setMetadata(currentMetadata => ({
|
||||||
|
...currentMetadata,
|
||||||
|
[fieldId]: {
|
||||||
|
displayName: `${id}/${fieldId}`,
|
||||||
|
color: getRandomColor(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
current = {
|
||||||
|
...current,
|
||||||
|
[fieldId]: {
|
||||||
|
...current[fieldId],
|
||||||
|
values: [
|
||||||
|
...(current[fieldId]?.values ?? []),
|
||||||
|
{ timestamp: new Date().toISOString(), value: value[fieldId] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return current;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!metadata[id]) {
|
||||||
|
setMetadata(currentMetadata => ({
|
||||||
|
...currentMetadata,
|
||||||
|
[id]: {
|
||||||
|
displayName: id,
|
||||||
|
color: getRandomColor(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
setData(current => ({
|
||||||
|
...current,
|
||||||
|
[id]: {
|
||||||
|
...current[id],
|
||||||
|
values: [
|
||||||
|
...(current[id]?.values ?? []),
|
||||||
|
{ timestamp: new Date().toISOString(), value },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[telemetryId, metadata],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getChartCompatibleData = React.useCallback(
|
||||||
|
(telemetryData: TelemetryData) =>
|
||||||
|
Object.keys(telemetryData).reduce<ChartData[]>(
|
||||||
|
(data, telemetryId) => [
|
||||||
|
...data,
|
||||||
|
{
|
||||||
|
[telemetryId]: {
|
||||||
|
[metadata[telemetryId].displayName]: telemetryData[
|
||||||
|
telemetryId
|
||||||
|
].values.reduce<SeriesData>(
|
||||||
|
(values, value) => ({
|
||||||
|
...values,
|
||||||
|
[value.timestamp]: {
|
||||||
|
[telemetryId]: value.value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
[metadata],
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const sensor = SensorMap[telemetryId]; // || HealthMap[telemetryId];
|
||||||
|
sensor?.addListener(DATA_AVAILABLE_EVENT, updateData);
|
||||||
|
// init chart with current value
|
||||||
|
if (currentValue !== undefined) {
|
||||||
|
updateData(telemetryId, currentValue);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
sensor?.removeListener(DATA_AVAILABLE_EVENT, updateData);
|
||||||
|
};
|
||||||
|
}, [telemetryId, currentValue, updateData]);
|
||||||
|
|
||||||
|
// Init chart or update it with new data
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (Object.keys(data).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const run = `
|
||||||
|
if(!tsiClient){
|
||||||
|
tsiClient = new TsiClient();
|
||||||
|
}
|
||||||
|
if(!lineChart){
|
||||||
|
lineChart = new tsiClient.ux.LineChart(document.getElementById('chart1'));
|
||||||
|
}
|
||||||
|
data=${JSON.stringify(getChartCompatibleData(data))};
|
||||||
|
lineChart.render(data, ${JSON.stringify(
|
||||||
|
chartOptions,
|
||||||
|
)}, ${JSON.stringify(chartDataOptions)});
|
||||||
|
true;
|
||||||
|
`;
|
||||||
|
chartRef.current?.injectJavaScript(run);
|
||||||
|
}, [data, chartOptions, chartDataOptions, getChartCompatibleData]);
|
||||||
|
|
||||||
|
if (chartType === ChartType.MAP) {
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Map style={mapStyle} location={currentValue} />
|
||||||
|
<View style={style.summary}>
|
||||||
|
<Text>
|
||||||
|
<Name>Latitude:</Name> {currentValue.lat}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<Name>Longitude:</Name> {currentValue.lon}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Object.keys(data).length === 0 ? (
|
||||||
|
<Loader message="" visible={true} />
|
||||||
|
) : (
|
||||||
|
<View style={style.container}>
|
||||||
|
<View style={style.chart}>
|
||||||
|
<WebView
|
||||||
|
originWhitelist={['*']}
|
||||||
|
containerStyle={{ flex: 2, justifyContent: 'flex-start' }}
|
||||||
|
ref={chartRef}
|
||||||
|
source={{
|
||||||
|
html,
|
||||||
|
}}
|
||||||
|
startInLoadingState={true}
|
||||||
|
// use theme background color when chart is loading
|
||||||
|
// style allows to cover all webview space as per issue:
|
||||||
|
// https://github.com/react-native-webview/react-native-webview/issues/1031
|
||||||
|
renderLoading={() => <View style={{
|
||||||
|
position: 'absolute',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: colors.background
|
||||||
|
}} />}
|
||||||
|
/>
|
||||||
|
<View style={style.summary}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
marginTop: 40,
|
||||||
|
}}>
|
||||||
|
{Object.keys(data).map((telemetryId, i) => {
|
||||||
|
const telemetry = data[telemetryId];
|
||||||
|
if (!telemetry) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const avg =
|
||||||
|
telemetry.values.map(v => v.value).reduce((a, b) => a + b) /
|
||||||
|
telemetry.values.length;
|
||||||
|
const fill = avg > 1 || avg < -1 ? avg : Math.abs(avg * 1000);
|
||||||
|
return (
|
||||||
|
<AnimatedCircularProgress
|
||||||
|
key={`circle - ${i}`}
|
||||||
|
size={screen.width / 5}
|
||||||
|
width={5}
|
||||||
|
fill={fill}
|
||||||
|
tintColor={metadata[telemetryId].color}
|
||||||
|
backgroundColor={LightenDarkenColor(
|
||||||
|
metadata[telemetryId].color,
|
||||||
|
90,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
rotation={360}>
|
||||||
|
{() => {
|
||||||
|
const strVal = `${avg}`;
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{strVal.length > 6
|
||||||
|
? `${strVal.substring(0, 6)}...`
|
||||||
|
: strVal}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</AnimatedCircularProgress>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
flex: 2,
|
||||||
|
marginTop: 30,
|
||||||
|
marginHorizontal: 10,
|
||||||
|
},
|
||||||
|
summary: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Chart;
|
286
src/Insight.tsx
286
src/Insight.tsx
|
@ -1,286 +0,0 @@
|
||||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
||||||
import {View, StyleSheet, processColor} from 'react-native';
|
|
||||||
import {LineChart} from 'react-native-charts-wrapper';
|
|
||||||
import {
|
|
||||||
getRandomColor,
|
|
||||||
Text,
|
|
||||||
LightenDarkenColor,
|
|
||||||
Name,
|
|
||||||
} from './components/typography';
|
|
||||||
import {
|
|
||||||
ExtendedLineData,
|
|
||||||
ItemData,
|
|
||||||
CustomLineDatasetConfig,
|
|
||||||
NavigationParams,
|
|
||||||
DATA_AVAILABLE_EVENT,
|
|
||||||
} from './types';
|
|
||||||
import {useTheme, RouteProp} from '@react-navigation/native';
|
|
||||||
import {AnimatedCircularProgress} from 'react-native-circular-progress';
|
|
||||||
import {useScreenDimensions} from './hooks/layout';
|
|
||||||
import {Loader} from './components/loader';
|
|
||||||
import Map from './components/map';
|
|
||||||
import {SensorMap} from './sensors';
|
|
||||||
|
|
||||||
export enum ChartType {
|
|
||||||
DEFAULT,
|
|
||||||
MAP,
|
|
||||||
}
|
|
||||||
|
|
||||||
const Insight = React.memo<{
|
|
||||||
route: RouteProp<
|
|
||||||
Record<
|
|
||||||
string,
|
|
||||||
NavigationParams & {
|
|
||||||
chartType: ChartType;
|
|
||||||
telemetryId: string;
|
|
||||||
currentValue: any;
|
|
||||||
}
|
|
||||||
>,
|
|
||||||
'Insight'
|
|
||||||
>;
|
|
||||||
}>(({route}) => {
|
|
||||||
const {screen} = useScreenDimensions();
|
|
||||||
const {colors, dark} = useTheme();
|
|
||||||
const [data, setData] = useState<ExtendedLineData>({
|
|
||||||
dataSets: [],
|
|
||||||
});
|
|
||||||
const {current: start} = useRef(Date.now());
|
|
||||||
const timestamp = Date.now() - start;
|
|
||||||
const {chartType, telemetryId, currentValue} = route.params;
|
|
||||||
|
|
||||||
const mapStyle = useMemo(
|
|
||||||
() => ({
|
|
||||||
flex: 3,
|
|
||||||
margin: 20,
|
|
||||||
borderRadius: 20,
|
|
||||||
...(!dark
|
|
||||||
? {
|
|
||||||
shadowColor: "'rgba(0, 0, 0, 0.14)'",
|
|
||||||
shadowOffset: {
|
|
||||||
width: 0,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
shadowOpacity: 0.8,
|
|
||||||
shadowRadius: 3.84,
|
|
||||||
elevation: 5,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
}),
|
|
||||||
[dark],
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param itemdata Current sample for the item
|
|
||||||
* @param startTime Start time of the sampling. Must be the same value used as "since" param in the chart
|
|
||||||
* @param setData Dispatch to update dataset with current sample
|
|
||||||
*/
|
|
||||||
const updateData = useCallback(
|
|
||||||
(id: string, value: any) => {
|
|
||||||
const item = {id, value};
|
|
||||||
if (id !== telemetryId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let itemToProcess: ItemData[] = [item];
|
|
||||||
if (typeof item.value !== 'string' && typeof item.value !== 'number') {
|
|
||||||
// data is composite
|
|
||||||
itemToProcess = Object.keys(item.value).map(i => ({
|
|
||||||
id: `${item.id}.${i}`,
|
|
||||||
value: item.value[i],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
itemToProcess.forEach(itemdata => {
|
|
||||||
setData(currentDataSet => {
|
|
||||||
const currentItemData = currentDataSet.dataSets.find(
|
|
||||||
d => d.itemId === itemdata.id,
|
|
||||||
);
|
|
||||||
// Current sample time (x-axis) is the difference between current timestamp e the start time of sampling
|
|
||||||
const newSample = {x: Date.now() - start, y: itemdata.value};
|
|
||||||
|
|
||||||
if (!currentItemData) {
|
|
||||||
// current item is not in the dataset yet
|
|
||||||
const rgbcolor = getRandomColor();
|
|
||||||
return {
|
|
||||||
...currentDataSet,
|
|
||||||
dataSets: [
|
|
||||||
...currentDataSet.dataSets,
|
|
||||||
...[
|
|
||||||
{
|
|
||||||
itemId: itemdata.id,
|
|
||||||
values: [newSample],
|
|
||||||
label: itemdata.id,
|
|
||||||
config: {
|
|
||||||
color: processColor(rgbcolor),
|
|
||||||
valueTextColor: processColor(rgbcolor),
|
|
||||||
rgbcolor,
|
|
||||||
} as CustomLineDatasetConfig,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
currentItemData.values?.[currentItemData.values?.length] ===
|
|
||||||
itemdata.value
|
|
||||||
) {
|
|
||||||
return {...currentDataSet};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...currentDataSet,
|
|
||||||
dataSets: currentDataSet.dataSets.map(({...currentItem}) => {
|
|
||||||
if (currentItem.itemId === itemdata.id && currentItem.values) {
|
|
||||||
currentItem.values = [...currentItem.values, ...[newSample]];
|
|
||||||
}
|
|
||||||
return currentItem;
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[start, telemetryId],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const sensor = SensorMap[telemetryId]; // || HealthMap[telemetryId];
|
|
||||||
sensor?.addListener(DATA_AVAILABLE_EVENT, updateData);
|
|
||||||
// init chart with current value
|
|
||||||
if (currentValue !== undefined) {
|
|
||||||
updateData(telemetryId, currentValue);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
sensor?.removeListener(DATA_AVAILABLE_EVENT, updateData);
|
|
||||||
};
|
|
||||||
}, [telemetryId, currentValue, updateData]);
|
|
||||||
|
|
||||||
if (chartType === ChartType.MAP) {
|
|
||||||
return (
|
|
||||||
<View style={style.container}>
|
|
||||||
<Map style={mapStyle} location={currentValue} />
|
|
||||||
<View style={style.summary}>
|
|
||||||
<Text>
|
|
||||||
<Name>Latitude:</Name> {currentValue.lat}
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
<Name>Longitude:</Name> {currentValue.lon}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{data.dataSets.length === 0 ? (
|
|
||||||
<Loader message="" visible={true} />
|
|
||||||
) : (
|
|
||||||
<View style={style.container}>
|
|
||||||
<View style={style.chart}>
|
|
||||||
<LineChart
|
|
||||||
style={style.chartBox}
|
|
||||||
chartDescription={{text: ''}}
|
|
||||||
touchEnabled={true}
|
|
||||||
dragEnabled={true}
|
|
||||||
scaleEnabled={true}
|
|
||||||
pinchZoom={true}
|
|
||||||
extraOffsets={{bottom: 20}}
|
|
||||||
legend={{
|
|
||||||
wordWrapEnabled: true,
|
|
||||||
textColor: processColor(colors.text),
|
|
||||||
textSize: 16,
|
|
||||||
}}
|
|
||||||
xAxis={{
|
|
||||||
position: 'BOTTOM',
|
|
||||||
axisMaximum: timestamp + 500,
|
|
||||||
axisMinimum: timestamp - 10000,
|
|
||||||
valueFormatter: 'date',
|
|
||||||
since: start,
|
|
||||||
valueFormatterPattern: 'HH:mm:ss',
|
|
||||||
timeUnit: 'MILLISECONDS',
|
|
||||||
drawAxisLines: false,
|
|
||||||
drawGridLines: false,
|
|
||||||
textColor: processColor(colors.text),
|
|
||||||
}}
|
|
||||||
yAxis={{
|
|
||||||
right: {
|
|
||||||
drawAxisLines: false,
|
|
||||||
textColor: processColor(colors.text),
|
|
||||||
},
|
|
||||||
left: {
|
|
||||||
drawAxisLines: false,
|
|
||||||
textColor: processColor(colors.text),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
data={data}
|
|
||||||
/>
|
|
||||||
<View style={style.summary}>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-around',
|
|
||||||
marginTop: 40,
|
|
||||||
}}>
|
|
||||||
{data.dataSets.map((d, i) => {
|
|
||||||
if (!d.values) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const val = (d.values[d.values.length - 1] as {
|
|
||||||
x: any;
|
|
||||||
y: any;
|
|
||||||
}).y;
|
|
||||||
const fill = val > 1 || val < -1 ? val : Math.abs(val * 1000);
|
|
||||||
return (
|
|
||||||
<AnimatedCircularProgress
|
|
||||||
key={`circle-${i}`}
|
|
||||||
size={screen.width / 5}
|
|
||||||
width={5}
|
|
||||||
fill={fill}
|
|
||||||
tintColor={
|
|
||||||
d.config ? d.config.rgbcolor : getRandomColor()
|
|
||||||
}
|
|
||||||
backgroundColor={
|
|
||||||
d.config
|
|
||||||
? LightenDarkenColor(d.config.rgbcolor, 90, true)
|
|
||||||
: getRandomColor()
|
|
||||||
}
|
|
||||||
rotation={360}>
|
|
||||||
{() => {
|
|
||||||
const strVal = `${val}`;
|
|
||||||
return (
|
|
||||||
<Text>
|
|
||||||
{strVal.length > 6
|
|
||||||
? `${strVal.substring(0, 6)}...`
|
|
||||||
: strVal}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</AnimatedCircularProgress>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const style = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
chart: {
|
|
||||||
flex: 1,
|
|
||||||
marginTop: 30,
|
|
||||||
marginHorizontal: 10,
|
|
||||||
},
|
|
||||||
chartBox: {
|
|
||||||
flex: 2,
|
|
||||||
},
|
|
||||||
summary: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 20,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Insight;
|
|
114
src/Settings.tsx
114
src/Settings.tsx
|
@ -1,16 +1,16 @@
|
||||||
import { useContext, useState } from 'react';
|
import {useContext, useState} from 'react';
|
||||||
import { ThemeContext } from './contexts/theme';
|
import {ThemeContext} from './contexts/theme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Switch, ScrollView, Platform, Alert } from 'react-native';
|
import {View, Switch, ScrollView, Platform, Alert} from 'react-native';
|
||||||
import { useTheme, useNavigation } from '@react-navigation/native';
|
import {useTheme, useNavigation} from '@react-navigation/native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||||
import { Icon, ListItem } from 'react-native-elements';
|
import {Icon, ListItem} from 'react-native-elements';
|
||||||
import { Registration } from './Registration';
|
import {Registration} from './Registration';
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
import {createStackNavigator} from '@react-navigation/stack';
|
||||||
import { useIoTCentralClient, useSimulation } from './hooks/iotc';
|
import {useIoTCentralClient, useSimulation} from './hooks/iotc';
|
||||||
import { defaults } from './contexts/defaults';
|
import {defaults} from './contexts/defaults';
|
||||||
import { StorageContext } from './contexts/storage';
|
import {StorageContext} from './contexts/storage';
|
||||||
import { LogsContext } from './contexts/logs';
|
import {LogsContext} from './contexts/logs';
|
||||||
|
|
||||||
const Stack = createStackNavigator();
|
const Stack = createStackNavigator();
|
||||||
|
|
||||||
|
@ -25,19 +25,19 @@ type ProfileItem = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const { toggle } = useContext(ThemeContext);
|
const {toggle} = useContext(ThemeContext);
|
||||||
const { clear } = useContext(StorageContext);
|
const {clear} = useContext(StorageContext);
|
||||||
const { clear: clearLogs } = useContext(LogsContext);
|
const {clear: clearLogs} = useContext(LogsContext);
|
||||||
const [client, clearClient] = useIoTCentralClient();
|
const [client, clearClient] = useIoTCentralClient();
|
||||||
const [centralSimulated, simulate] = useSimulation();
|
const [centralSimulated, simulate] = useSimulation();
|
||||||
const { colors, dark } = useTheme();
|
const {colors, dark} = useTheme();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const updateUIItems = (title: string, val: any) => {
|
const updateUIItems = (title: string, val: any) => {
|
||||||
setItems(current =>
|
setItems(current =>
|
||||||
current.map(i => {
|
current.map(i => {
|
||||||
if (i.title === title) {
|
if (i.title === title) {
|
||||||
i = { ...i, value: val };
|
i = {...i, value: val};
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}),
|
}),
|
||||||
|
@ -51,7 +51,7 @@ export default function Settings() {
|
||||||
action: {
|
action: {
|
||||||
type: 'expand',
|
type: 'expand',
|
||||||
fn: navigation => {
|
fn: navigation => {
|
||||||
navigation.navigate('Registration', { previousScreen: 'root' });
|
navigation.navigate('Registration', {previousScreen: 'root'});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -83,26 +83,26 @@ export default function Settings() {
|
||||||
},
|
},
|
||||||
...(defaults.dev
|
...(defaults.dev
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: 'Simulation Mode',
|
title: 'Simulation Mode',
|
||||||
icon: dark ? 'sync-outline' : 'sync',
|
icon: dark ? 'sync-outline' : 'sync',
|
||||||
action: {
|
action: {
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
fn: async val => {
|
fn: async val => {
|
||||||
updateUIItems('Simulation Mode', val);
|
updateUIItems('Simulation Mode', val);
|
||||||
await simulate(val);
|
await simulate(val);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
value: centralSimulated,
|
||||||
value: centralSimulated,
|
} as ProfileItem,
|
||||||
} as ProfileItem
|
]
|
||||||
]
|
|
||||||
: []),
|
: []),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, marginTop: insets.top, marginBottom: insets.bottom }}>
|
<View style={{flex: 1, marginTop: insets.top, marginBottom: insets.bottom}}>
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={({ route }) => ({
|
screenOptions={({route}) => ({
|
||||||
headerShown: false, // TODO: fix header
|
headerShown: false, // TODO: fix header
|
||||||
})}>
|
})}>
|
||||||
<Stack.Screen name="setting_root">
|
<Stack.Screen name="setting_root">
|
||||||
|
@ -114,34 +114,40 @@ export default function Settings() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const RightElement = React.memo<{ item: ProfileItem; colors: any, dark: boolean }>(
|
const RightElement = React.memo<{
|
||||||
({ item, colors, dark }) => {
|
item: ProfileItem;
|
||||||
if (item.action && item.action.type === 'switch') {
|
colors: any;
|
||||||
return (
|
dark: boolean;
|
||||||
<Switch
|
}>(({item, colors, dark}) => {
|
||||||
value={item.value}
|
if (item.action && item.action.type === 'switch') {
|
||||||
onValueChange={item.action.fn}
|
return (
|
||||||
{...(Platform.OS === 'android' && {
|
<Switch
|
||||||
thumbColor: item.value ? colors.primary : (dark ? colors.text : colors.background),
|
value={item.value}
|
||||||
trackColor: { true: colors.border, false: colors.border }
|
onValueChange={item.action.fn}
|
||||||
})}
|
{...(Platform.OS === 'android' && {
|
||||||
/>
|
thumbColor: item.value
|
||||||
);
|
? colors.primary
|
||||||
}
|
: dark
|
||||||
return null;
|
? colors.text
|
||||||
},
|
: colors.background,
|
||||||
);
|
trackColor: {true: colors.border, false: colors.border},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
const Root = React.memo<{ items: ProfileItem[]; colors: any; dark: boolean }>(
|
const Root = React.memo<{items: ProfileItem[]; colors: any; dark: boolean}>(
|
||||||
({ items, colors, dark }) => {
|
({items, colors, dark}) => {
|
||||||
const nav = useNavigation<any>();
|
const nav = useNavigation<any>();
|
||||||
return (
|
return (
|
||||||
<ScrollView style={{ flex: 1 }}>
|
<ScrollView style={{flex: 1}}>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={`setting-${index}`}
|
key={`setting-${index}`}
|
||||||
bottomDivider
|
bottomDivider
|
||||||
containerStyle={{ backgroundColor: colors.card }}
|
containerStyle={{backgroundColor: colors.card}}
|
||||||
onPress={
|
onPress={
|
||||||
item.action && item.action.type !== 'switch'
|
item.action && item.action.type !== 'switch'
|
||||||
? item.action.fn.bind(null, nav)
|
? item.action.fn.bind(null, nav)
|
||||||
|
@ -149,7 +155,7 @@ const Root = React.memo<{ items: ProfileItem[]; colors: any; dark: boolean }>(
|
||||||
}>
|
}>
|
||||||
<Icon name={item.icon} type="ionicon" color={colors.text} />
|
<Icon name={item.icon} type="ionicon" color={colors.text} />
|
||||||
<ListItem.Content>
|
<ListItem.Content>
|
||||||
<ListItem.Title style={{ color: colors.text }}>
|
<ListItem.Title style={{color: colors.text}}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</ListItem.Title>
|
</ListItem.Title>
|
||||||
</ListItem.Content>
|
</ListItem.Content>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import {useNavigation} from '@react-navigation/native';
|
||||||
import { defaults } from 'contexts/defaults';
|
import {defaults} from 'contexts/defaults';
|
||||||
import {
|
import {
|
||||||
Properties as PropertiesData,
|
Properties as PropertiesData,
|
||||||
getDeviceInfo,
|
getDeviceInfo,
|
||||||
|
@ -13,8 +13,8 @@ import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Platform } from 'react-native';
|
import {Platform} from 'react-native';
|
||||||
import { AVAILABLE_SENSORS, SensorMap } from 'sensors';
|
import {AVAILABLE_SENSORS, SensorMap} from 'sensors';
|
||||||
import {
|
import {
|
||||||
DATA_AVAILABLE_EVENT,
|
DATA_AVAILABLE_EVENT,
|
||||||
ItemProps,
|
ItemProps,
|
||||||
|
@ -22,7 +22,7 @@ import {
|
||||||
SENSOR_UNAVAILABLE_EVENT,
|
SENSOR_UNAVAILABLE_EVENT,
|
||||||
TimedLog,
|
TimedLog,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { LogsContext } from '../contexts/logs';
|
import {LogsContext} from '../contexts/logs';
|
||||||
|
|
||||||
export type IIcon = {
|
export type IIcon = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -33,12 +33,12 @@ export function useScreenIcon(icon: IIcon): void {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigation.setParams({ icon });
|
navigation.setParams({icon});
|
||||||
}, [navigation, icon]);
|
}, [navigation, icon]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLogger(): [TimedLog, (logItem: LogItem) => void] {
|
export function useLogger(): [TimedLog, (logItem: LogItem) => void] {
|
||||||
const { logs, append } = useContext(LogsContext);
|
const {logs, append} = useContext(LogsContext);
|
||||||
return [logs, append];
|
return [logs, append];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,5 +412,5 @@ export function useProperties() {
|
||||||
loadDeviceInfo();
|
loadDeviceInfo();
|
||||||
}, [loadDeviceInfo]);
|
}, [loadDeviceInfo]);
|
||||||
|
|
||||||
return { loading, properties, updateProperty };
|
return {loading, properties, updateProperty};
|
||||||
}
|
}
|
||||||
|
|
46
src/types.ts
46
src/types.ts
|
@ -1,11 +1,11 @@
|
||||||
import {StackNavigationProp} from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import {GestureResponderEvent} from 'react-native';
|
import { GestureResponderEvent } from 'react-native';
|
||||||
import {
|
import {
|
||||||
LineData,
|
LineData,
|
||||||
LineValue,
|
LineValue,
|
||||||
LineDatasetConfig,
|
LineDatasetConfig,
|
||||||
} from 'react-native-charts-wrapper';
|
} from 'react-native-charts-wrapper';
|
||||||
import {IconProps} from 'react-native-elements';
|
import { IconProps } from 'react-native-elements';
|
||||||
|
|
||||||
export const Screens = {
|
export const Screens = {
|
||||||
TELEMETRY_SCREEN: 'Telemetry',
|
TELEMETRY_SCREEN: 'Telemetry',
|
||||||
|
@ -55,14 +55,14 @@ export type NavigationProperty = StackNavigationProp<
|
||||||
*/
|
*/
|
||||||
export type StateUpdater<T> = React.Dispatch<React.SetStateAction<T>>;
|
export type StateUpdater<T> = React.Dispatch<React.SetStateAction<T>>;
|
||||||
|
|
||||||
export type LogItem = {eventName: string; eventData: string};
|
export type LogItem = { eventName: string; eventData: string };
|
||||||
export type TimedLog = {timestamp: number | string; logItem: LogItem}[];
|
export type TimedLog = { timestamp: number | string; logItem: LogItem }[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chart typings
|
* Chart typings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type CustomLineDatasetConfig = LineDatasetConfig & {rgbcolor: string};
|
export type CustomLineDatasetConfig = LineDatasetConfig & { rgbcolor: string };
|
||||||
export interface ExtendedLineData extends LineData {
|
export interface ExtendedLineData extends LineData {
|
||||||
dataSets: {
|
dataSets: {
|
||||||
itemId: string;
|
itemId: string;
|
||||||
|
@ -98,6 +98,39 @@ export type GeoCoordinates = {
|
||||||
lonD?: number;
|
lonD?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LineChartOptions = {
|
||||||
|
brushContextMenuActions?: any[];
|
||||||
|
grid?: boolean;
|
||||||
|
includeDots?: boolean;
|
||||||
|
includeEnvelope?: boolean;
|
||||||
|
brushHandlesVisible?: boolean;
|
||||||
|
hideChartControlPanel?: boolean;
|
||||||
|
snapBrush?: boolean;
|
||||||
|
interpolationFunction?: '' | 'curveLinear' | 'curveMonotoneX';
|
||||||
|
legend?: 'shown' | 'compact' | 'hidden';
|
||||||
|
noAnimate?: boolean;
|
||||||
|
offset?: any;
|
||||||
|
spMeasures?: string[];
|
||||||
|
isTemporal?: boolean;
|
||||||
|
spAxisLabels?: string[];
|
||||||
|
stacked?: boolean;
|
||||||
|
theme?: 'dark' | 'light';
|
||||||
|
timestamp?: string;
|
||||||
|
tooltip?: boolean;
|
||||||
|
yAxisState?: 'stacked' | 'shared' | 'overlap';
|
||||||
|
yExtent?: [number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChartDataOptions = {
|
||||||
|
color: string;
|
||||||
|
alias: string;
|
||||||
|
dataType?: 'numeric' | 'categorical' | 'events';
|
||||||
|
};
|
||||||
|
export enum ChartType {
|
||||||
|
DEFAULT,
|
||||||
|
MAP,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Health typings
|
* Health typings
|
||||||
*/
|
*/
|
||||||
|
@ -135,6 +168,7 @@ export const LOG_DATA = 'LOG_DATA';
|
||||||
*/
|
*/
|
||||||
export const ENABLE_DISABLE_COMMAND = 'enableSensors';
|
export const ENABLE_DISABLE_COMMAND = 'enableSensors';
|
||||||
export const SET_FREQUENCY_COMMAND = 'changeInterval';
|
export const SET_FREQUENCY_COMMAND = 'changeInterval';
|
||||||
|
export const LIGHT_TOGGLE_COMMAND = 'lightOn';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IOTC COMPONENT NAME
|
* IOTC COMPONENT NAME
|
||||||
|
|
Загрузка…
Ссылка в новой задаче