diff --git a/ios/WildlifeWatcher.xcodeproj/project.pbxproj b/ios/WildlifeWatcher.xcodeproj/project.pbxproj
index 893fbb3..c777153 100644
--- a/ios/WildlifeWatcher.xcodeproj/project.pbxproj
+++ b/ios/WildlifeWatcher.xcodeproj/project.pbxproj
@@ -313,8 +313,13 @@
PRODUCT_BUNDLE_IDENTIFIER = com.wildlife.wildlifewatcher;
PRODUCT_NAME = WildlifeWatcher;
PROVISIONING_PROFILE_SPECIFIER = "";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -347,7 +352,12 @@
PRODUCT_NAME = WildlifeWatcher;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Wildlife Watcher App Store";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
diff --git a/ios/WildlifeWatcher/Info.plist b/ios/WildlifeWatcher/Info.plist
index 5b51e3f..2c4e5b3 100644
--- a/ios/WildlifeWatcher/Info.plist
+++ b/ios/WildlifeWatcher/Info.plist
@@ -65,11 +65,17 @@
armv7
+ UIStatusBarStyle
+
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortrait
UIViewControllerBasedStatusBarAppearance
diff --git a/src/ble/types.ts b/src/ble/types.ts
index f24ff08..11571df 100644
--- a/src/ble/types.ts
+++ b/src/ble/types.ts
@@ -68,7 +68,6 @@ export const COMMANDS: {
[CommandNames.ID]: {
name: CommandNames.ID,
readCommand: "id",
- readRegex: /\bDevEui:\s*([0-9A-Fa-f:]+)\b/,
},
[CommandNames.VERSION]: {
name: CommandNames.VERSION,
@@ -77,7 +76,7 @@ export const COMMANDS: {
[CommandNames.BATTERY]: {
name: CommandNames.BATTERY,
readCommand: "battery",
- readRegex: /\bBattery\s=\s([0-9.]+V)\b/,
+ readRegex: /\bBattery\s=\s(100|\d{1,3})%/,
},
[CommandNames.SENSOR]: {
name: CommandNames.SENSOR,
@@ -146,3 +145,61 @@ export const COMMANDS: {
readRegex: /(Device will enter DFU mode after disconnecting.)\s*/,
},
}
+
+type CharacteristicProperty =
+ | "Read"
+ | "Write"
+ | "WriteWithoutResponse"
+ | "Notify"
+
+type CharacteristicProperties = {
+ [key in CharacteristicProperty]?: CharacteristicProperty
+}
+
+type Descriptor = {
+ value: any
+ uuid: string
+}
+
+type Characteristic = {
+ properties: CharacteristicProperties
+ characteristic: string
+ service: string
+ descriptors?: Descriptor[]
+}
+
+type Service = {
+ uuid: string
+}
+
+type ManufacturerRawData = {
+ bytes: number[]
+ data: string
+ CDVType: string
+}
+
+type RawData = {
+ bytes: number[]
+ data: string
+ CDVType: string
+}
+
+type Advertising = {
+ manufacturerData: any
+ txPowerLevel: number
+ isConnectable: boolean
+ serviceData: any
+ localName: string
+ serviceUUIDs: string[]
+ manufacturerRawData: ManufacturerRawData
+ rawData: RawData
+}
+
+export type Services = {
+ characteristics: Characteristic[]
+ services: Service[]
+ advertising: Advertising
+ name: string
+ rssi: number
+ id: string
+}
diff --git a/src/components/AppDrawer.tsx b/src/components/AppDrawer.tsx
index 2b07230..fff6468 100644
--- a/src/components/AppDrawer.tsx
+++ b/src/components/AppDrawer.tsx
@@ -24,7 +24,7 @@ export const useAppDrawer = () => useContext(DrawerContext)
export const AppDrawer = ({ children }: PropsWithChildren) => {
const [isOpen, setIsOpen] = useState(false)
- const { padding, spacing } = useExtendedTheme()
+ const { appPadding, spacing } = useExtendedTheme()
const { setIsLoggedIn, isLoggedIn } = useAuth()
const { top } = useSafeAreaInsets()
@@ -43,7 +43,10 @@ export const AppDrawer = ({ children }: PropsWithChildren) => {
renderDrawerContent={() => {
return (
I'm empty at the moment.
diff --git a/src/components/CustomKeyboardAvoidingView.tsx b/src/components/CustomKeyboardAvoidingView.tsx
index 2c8e438..a9794ea 100644
--- a/src/components/CustomKeyboardAvoidingView.tsx
+++ b/src/components/CustomKeyboardAvoidingView.tsx
@@ -9,6 +9,7 @@ import {
} from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"
+import { useExtendedTheme } from "../theme"
type Props = {
style?: StyleProp
@@ -22,10 +23,11 @@ export const CustomKeyboardAvoidingView = ({
}: PropsWithChildren) => {
const insets = useSafeAreaInsets()
const [bottomPadding, setBottomPadding] = useState(insets.bottom)
+ const { spacing } = useExtendedTheme()
useEffect(() => {
- setBottomPadding(insets.bottom)
- }, [insets.bottom, insets.top])
+ setBottomPadding(insets.bottom + spacing)
+ }, [insets.bottom, insets.top, spacing])
return (
) => {
- const { padding } = useExtendedTheme()
+ const { appPadding } = useExtendedTheme()
return (
-
-
+
+
{children}
diff --git a/src/components/ui/WWScrollView.tsx b/src/components/ui/WWScrollView.tsx
new file mode 100644
index 0000000..6eb67d1
--- /dev/null
+++ b/src/components/ui/WWScrollView.tsx
@@ -0,0 +1,31 @@
+import { forwardRef } from "react"
+import {
+ ScrollViewProps,
+ StyleProp,
+ StyleSheet,
+ View,
+ ViewStyle,
+} from "react-native"
+import { ScrollView } from "react-native-gesture-handler"
+
+type Props = ScrollViewProps & {
+ containerStyle?: StyleProp
+}
+
+export const WWScrollView = forwardRef(
+ ({ children, containerStyle, ...props }, ref) => {
+ return (
+
+ true} style={containerStyle}>
+ {children}
+
+
+ )
+ },
+)
+
+const styles = StyleSheet.create({
+ view: {
+ flex: 1,
+ },
+})
diff --git a/src/hooks/useBle.ts b/src/hooks/useBle.ts
index e8ba71c..2c07f3e 100644
--- a/src/hooks/useBle.ts
+++ b/src/hooks/useBle.ts
@@ -1,15 +1,16 @@
-import React, { useCallback, useEffect, useRef } from "react"
+import React, { useCallback, useEffect, useRef, useState } from "react"
import { Platform } from "react-native"
import BleManager from "react-native-ble-manager"
import { Peripheral } from "react-native-ble-manager"
+import { BLE_SERVICE_UUID } from "../utils/constants"
import {
- BLE_CHARACTERISTIC_READ_UUID,
- BLE_SERVICE_UUID,
-} from "../utils/constants"
-import { invokeWithTimeout, sleep } from "../utils/helpers"
+ extractServiceAndCharacteristic,
+ invokeWithTimeout,
+ sleep,
+} from "../utils/helpers"
import { guard, log, logError } from "../utils/logger"
import { useAppDispatch, useAppSelector } from "../redux"
import {
@@ -30,6 +31,7 @@ import {
CommandConstructOptions,
CommandControlTypes,
CommandNames,
+ Services,
} from "../ble/types"
import { constructCommandString } from "../ble/parser"
@@ -73,6 +75,8 @@ const PING_REQUEST: string[] = []
export const useBle = (): ReturnType => {
const { initialized } = useAppSelector((state) => state.bleLibrary)
+ const [isBleConnecting, setIsBleConnecting] = useState(false)
+
const devices = useAppSelector((state) => state.devices)
const scanning = useAppSelector((state) => state.scanning)
@@ -232,9 +236,7 @@ export const useBle = (): ReturnType => {
)
const isDeviceReconnecting = useRef<{ [x: string]: boolean }>({})
- const isBleConnecting = Object.values(isDeviceReconnecting.current).find(
- (isConnected) => isConnected,
- )
+
const connectDevice = useCallback(
async (peripheral: ExtendedPeripheral, timeout?: number) => {
if (!initialized || peripheral.loading) return peripheral
@@ -261,6 +263,8 @@ export const useBle = (): ReturnType => {
isDeviceReconnecting.current[peripheral.id] = true
+ setIsBleConnecting(true)
+
const newPeripheral = { ...peripheral }
dispatch(deviceLoading({ id: newPeripheral.id, loading: true }))
@@ -286,18 +290,22 @@ export const useBle = (): ReturnType => {
log(`Device ${deviceIdentification} connected`)
- await invokeWithTimeout(
+ const services = (await invokeWithTimeout(
() => BleManager.retrieveServices(newPeripheral.id),
"BleManager.retrieveServices",
timeout,
- )
+ )) as Services
log(`Device ${deviceIdentification} services retireved`)
+ const extractedServices = extractServiceAndCharacteristic(services)
+
+ newPeripheral.services = extractedServices
+
await BleManager.startNotification(
newPeripheral.id,
- BLE_SERVICE_UUID,
- BLE_CHARACTERISTIC_READ_UUID,
+ extractedServices.serviceCharacteristic,
+ extractedServices.readCharacteristic,
)
log(`Device ${deviceIdentification} notifications started`)
@@ -313,7 +321,6 @@ export const useBle = (): ReturnType => {
await ping()
newPeripheral.connected = true
-
newPeripheral.intervals = {
ping: setInterval(async () => await ping(), 20000),
}
@@ -329,6 +336,7 @@ export const useBle = (): ReturnType => {
dispatch(deviceLoading({ id: newPeripheral.id, loading: false }))
if (isDeviceReconnecting.current[peripheral.id]) {
+ setIsBleConnecting(false)
isDeviceReconnecting.current[peripheral.id] = false
}
diff --git a/src/hooks/useBleListeners.tsx b/src/hooks/useBleListeners.tsx
index 44f0371..6640921 100644
--- a/src/hooks/useBleListeners.tsx
+++ b/src/hooks/useBleListeners.tsx
@@ -186,7 +186,6 @@ export const useBleListeners = () => {
console.debug(JSON.stringify(text))
const currentConfiguration = configRef.current[peripheral] || {}
- // const currentPeripheral = devicesRef.current[peripheral]
const currentLog = allLogs.current[peripheral] || ""
if (allLogs.current[peripheral]) {
diff --git a/src/hooks/useCommand.tsx b/src/hooks/useCommand.tsx
index 9e2e549..46568f6 100644
--- a/src/hooks/useCommand.tsx
+++ b/src/hooks/useCommand.tsx
@@ -26,7 +26,9 @@ const TIMEOUT = 1000 * 10
export const useCommand = ({ deviceId, command }: Props) => {
const requestRef = useRef()
const timeoutRef = useRef()
+
const [goal, setGoal] = useState()
+ const [commandLoading, setCommandLoading] = useState(true)
const { write } = useBleActions()
const devices = useAppSelector((state) => state.devices)
@@ -71,6 +73,7 @@ export const useCommand = ({ deviceId, command }: Props) => {
const set = useCallback(
(data?: string) => {
clearTimers()
+ setCommandLoading(true)
sendCommand(CommandControlTypes.WRITE, data)
@@ -86,6 +89,8 @@ export const useCommand = ({ deviceId, command }: Props) => {
clearInterval(requestRef.current)
}
+ setCommandLoading(false)
+
dispatch(
deviceConfigChanged({
id: deviceId,
@@ -122,6 +127,8 @@ export const useCommand = ({ deviceId, command }: Props) => {
// Means its a set only command in reality
if (!command.readCommand) return
+ setCommandLoading(true)
+
sendCommand(CommandControlTypes.READ)
requestRef.current = setInterval(
@@ -133,6 +140,8 @@ export const useCommand = ({ deviceId, command }: Props) => {
clearInterval(requestRef.current)
}
+ setCommandLoading(false)
+
dispatch(
deviceConfigChanged({
id: deviceId,
@@ -170,7 +179,7 @@ export const useCommand = ({ deviceId, command }: Props) => {
})
) {
clearTimers()
-
+ setCommandLoading(false)
/**
* If the hook already detects the correct configuration
* when it first renders, the get() method isn't even
@@ -209,6 +218,7 @@ export const useCommand = ({ deviceId, command }: Props) => {
return {
set,
get,
+ commandLoading,
}
}
@@ -221,6 +231,8 @@ const isCommandCompleted = ({
}) => {
if (!config) return false
+ if (config.loading) return false
+
if (goal) {
return config.value === goal
}
diff --git a/src/navigation/screens/TerminalScreen.tsx b/src/navigation/screens/TerminalScreen.tsx
index 815bfd7..8dab494 100644
--- a/src/navigation/screens/TerminalScreen.tsx
+++ b/src/navigation/screens/TerminalScreen.tsx
@@ -7,7 +7,6 @@ import { useEffect } from "react"
import {
NativeScrollEvent,
NativeSyntheticEvent,
- ScrollView,
StyleSheet,
View,
} from "react-native"
@@ -19,13 +18,18 @@ import { useCommand } from "../../hooks/useCommand"
import { COMMANDS } from "../../ble/types"
import { useSelectDevice } from "../../hooks/useSelectDevice"
import {
- ActivityIndicator,
Button,
+ Divider,
IconButton,
+ Switch,
TextInput,
- useTheme,
} from "react-native-paper"
import { WWText } from "../../components/ui/WWText"
+import { useExtendedTheme } from "../../theme"
+import { WWTextInput } from "../../components/ui/WWTextInput"
+import { WWScreenView } from "../../components/ui/WWScreenView"
+import { WWScrollView } from "../../components/ui/WWScrollView"
+import { AppLoading } from "./AppLoading"
type Props = {
embed?: boolean
@@ -33,7 +37,6 @@ type Props = {
export const Terminal = ({ embed }: Props) => {
const scrollViewRef = React.useRef()
- const { colors } = useTheme()
const {
params: { deviceId },
} = useRoute>()
@@ -46,13 +49,31 @@ export const Terminal = ({ embed }: Props) => {
const logs = deviceLogs[deviceId]
const configuration = useAppSelector((state) => state.configuration)
const config = configuration[deviceId]
-
- useCommand({ deviceId, command: COMMANDS.BATTERY })
- useCommand({ deviceId, command: COMMANDS.VERSION })
- const { set: setHb } = useCommand({ deviceId, command: COMMANDS.HEARTBEAT })
- const { set: setAppEui } = useCommand({ deviceId, command: COMMANDS.APPEUI })
- const { set: setDevEui } = useCommand({ deviceId, command: COMMANDS.DEVEUI })
- const { set: setSensor } = useCommand({ deviceId, command: COMMANDS.SENSOR })
+ const { spacing, colors, appPadding } = useExtendedTheme()
+ const { get: getBattery, commandLoading: batteryLoading } = useCommand({
+ deviceId,
+ command: COMMANDS.BATTERY,
+ })
+ const { get: getVersion, commandLoading: versionLoading } = useCommand({
+ deviceId,
+ command: COMMANDS.VERSION,
+ })
+ const { set: setHb, commandLoading: hbLoading } = useCommand({
+ deviceId,
+ command: COMMANDS.HEARTBEAT,
+ })
+ const { set: setAE, commandLoading: aeLoading } = useCommand({
+ deviceId,
+ command: COMMANDS.APPEUI,
+ })
+ const { set: setDE, commandLoading: deLoading } = useCommand({
+ deviceId,
+ command: COMMANDS.DEVEUI,
+ })
+ const { set: setSensor, commandLoading: sensorLoading } = useCommand({
+ deviceId,
+ command: COMMANDS.SENSOR,
+ })
const { set: reset } = useCommand({ deviceId, command: COMMANDS.RESET })
const { set: erase } = useCommand({ deviceId, command: COMMANDS.ERASE })
const { set: triggerDfu } = useCommand({
@@ -62,6 +83,40 @@ export const Terminal = ({ embed }: Props) => {
const [autoscroll, setAutoscroll] = useState(true)
+ const [heartbeat, setHeartbeat] = useState()
+ const [appEui, setAppEui] = useState("")
+ const [devEui, setDevEui] = useState("")
+ const [localSensor, setLocalSensor] = useState()
+
+ useEffect(() => {
+ setLocalSensor(config.SENSOR?.value === "enable")
+ }, [config.SENSOR?.value])
+
+ const triggerSensor = (value: boolean) => {
+ if (!sensorLoading) {
+ setSensor(value ? "enable" : "disable")
+ setLocalSensor(value)
+ }
+ }
+
+ const triggerHeartbeat = () => {
+ if (heartbeat && !hbLoading) {
+ setHb(`${heartbeat}s`)
+ }
+ }
+
+ const triggerAppEui = () => {
+ if (appEui && appEui.length > 0 && !aeLoading) {
+ setAE(appEui)
+ }
+ }
+
+ const triggerDevEui = () => {
+ if (devEui && devEui.length > 0 && !deLoading) {
+ setDE(devEui)
+ }
+ }
+
const writeText = useCallback(async () => {
await write(device, [text])
setText("")
@@ -124,179 +179,285 @@ export const Terminal = ({ embed }: Props) => {
}
}
- const { HEARTBEAT, APPEUI, SENSOR, LORAWAN } = config
+ const { HEARTBEAT, APPEUI, SENSOR, LORAWAN, DEVEUI, BATTERY, VERSION, ID } =
+ config
if (
!HEARTBEAT?.loaded ||
!APPEUI?.loaded ||
!SENSOR?.loaded ||
- !LORAWAN?.loaded
+ !LORAWAN?.loaded ||
+ !DEVEUI?.loaded ||
+ !BATTERY?.loaded ||
+ !VERSION?.loaded ||
+ !ID?.loaded
) {
- return
+ return
}
- const hb = HEARTBEAT.value
- const eui = APPEUI.value
- const sensor = SENSOR.value
- const lorawan = LORAWAN.value
-
return (
-
-
-
- {logs}
-
-
- {!autoscroll && (
- {
- toggleAutoscroll(true)
- scrollViewRef.current &&
- scrollViewRef.current.scrollToEnd({ animated: true })
- }}
- />
- )}
-
-
- setText(value)}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {logs.replaceAll("\r", "")}
+
+
+ {!autoscroll && (
+ {
+ toggleAutoscroll(true)
+ scrollViewRef.current &&
+ scrollViewRef.current.scrollToEnd({ animated: true })
+ }}
+ />
+ )}
-
-
-
-
-
- {config.HEARTBEAT && config.HEARTBEAT.loaded && (
- Current heartbeat: {hb}
- )}
-
+
+ setText(value)}
+ right={
+
+ }
+ />
-
-
-
+
+
+
+
+
+ ID and Version
+
+
+
+ {ID.loaded && (
+
+ ID: {ID.value}
+
+ )}
+
+
+ {VERSION.loaded && (
+
+ Version:{" "}
+ {versionLoading ? (
+ "Loading..."
+ ) : (
+ {VERSION.value}
+ )}
+
+ )}
+
+
+
+
+
-
- {config.APPEUI && config.APPEUI.loaded && (
- Current APPEUI: {eui}
- )}
+
+ Battery
+
+
+
+ {BATTERY.loaded && (
+
+ Current battery level:{" "}
+ {batteryLoading ? (
+ "Loading..."
+ ) : (
+ {BATTERY.value}%
+ )}
+
+ )}
+
+
+
+
+
-
-
-
-
- Should set APPEUI to AAA4567890123. (doesn't work)
-
+
+ Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+ Heartbeat
+
+
+
+ {HEARTBEAT.loaded && (
+
+ Current heartbeat:{" "}
+ {hbLoading ? (
+ "Loading..."
+ ) : (
+ {HEARTBEAT.value}
+ )}
+
+ )}
+
+
+
+ setHeartbeat(value)}
+ style={{ marginRight: spacing }}
+ placeholder={HEARTBEAT.value}
+ />
+
+
-
- {config.DEVEUI && config.DEVEUI.loaded && (
- Current DEVEUI: {eui}
- )}
+
+ App EUI
+
+
+
+ {APPEUI.loaded && (
+
+ Current App EUI:{" "}
+ {aeLoading ? (
+ "Loading..."
+ ) : (
+ {APPEUI.value}
+ )}
+
+ )}
+
+
+
+ setAppEui(value)}
+ style={{ marginRight: spacing }}
+ placeholder={APPEUI.value}
+ />
+
+
-
-
-
-
- Should set DEVEUI to BBB4567890123. (doesn't work)
-
+
+ Dev EUI
+
+
+
+ {DEVEUI.loaded && (
+
+ Current Dev EUI:{" "}
+ {deLoading ? (
+ "Loading..."
+ ) : (
+ {DEVEUI.value}
+ )}
+
+ )}
+
+
+
+ setDevEui(value)}
+ style={{ marginRight: spacing }}
+ placeholder={DEVEUI.value}
+ />
+
+
-
-
-
-
+
+ Sensor messages
+
+
+
+
+
+
+ Sensor messages are{" "}
+ {SENSOR.value === "enable" ? (
+ enabled
+ ) : (
+ disabled
+ )}
+ .
+
+
-
-
-
-
- Sensor is{" "}
- {sensor === "enable" ? (
- enabled
- ) : (
- disabled
- )}
-
-
- Lorawan status: {lorawan}
-
+
+ Lorawan status
+
+
+
+ Lorawan is currently{" "}
+
+ {LORAWAN.value?.toLowerCase()}
+
+ .
+
+
-
-
-
+
+
+
)
}
const styles = StyleSheet.create({
- scrollContainer: { flex: 1, margin: 10 },
+ scrollContainer: { flex: 1 },
scroll: { flex: 1 },
view: { height: 200 },
fab: {
@@ -305,10 +466,8 @@ const styles = StyleSheet.create({
right: 20,
},
logs: {
- fontSize: 8,
margin: 10,
marginBottom: 20,
- paddingStart: 10,
},
input: {
flexDirection: "row",
@@ -320,12 +479,20 @@ const styles = StyleSheet.create({
flexDirection: "row",
flexWrap: "wrap",
alignItems: "center",
- padding: 5,
},
button: {
margin: 5,
},
bold: {
- fontWeight: "900",
+ fontWeight: "400",
+ },
+ heartbeat: {
+ flex: 1,
+ flexDirection: "row",
+ alignItems: "center",
+ flexWrap: "wrap",
+ },
+ idversion: {
+ width: "100%",
},
})
diff --git a/src/redux/slices/devicesSlice.ts b/src/redux/slices/devicesSlice.ts
index 5dbf287..3229fb2 100644
--- a/src/redux/slices/devicesSlice.ts
+++ b/src/redux/slices/devicesSlice.ts
@@ -11,6 +11,11 @@ export interface ExtendedPeripheral extends Peripheral {
signalLost?: boolean
device: DeviceMetadata
loading: boolean
+ services?: {
+ serviceCharacteristic: string
+ readCharacteristic: string
+ writeCharacteristic: string
+ }
intervals: {
[x: string]: NodeJS.Timeout | undefined | null
}
diff --git a/src/theme.ts b/src/theme.ts
index 694b2e8..2bee743 100644
--- a/src/theme.ts
+++ b/src/theme.ts
@@ -25,7 +25,7 @@ const extendThemes = (theme: MD3Theme) => {
secondary: "#fed54e",
tertiary: "#ffffff",
},
- padding: 20,
+ appPadding: 20,
roundness: 10,
spacing: 10,
}
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index 7d4fb4e..9f80d66 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -1,10 +1,15 @@
import dayjs from "dayjs"
import { ExtendedPeripheral } from "../redux/slices/devicesSlice"
import { log } from "./logger"
-import { BLE_CHARACTERISTIC_WRITE_UUID, BLE_SERVICE_UUID } from "./constants"
+import {
+ BLE_CHARACTERISTIC_READ_UUID,
+ BLE_CHARACTERISTIC_WRITE_UUID,
+ BLE_SERVICE_UUID,
+} from "./constants"
import BleManager from "react-native-ble-manager"
import { Buffer } from "buffer"
import { readlineParserEmitter } from "../hooks/useBleListeners"
+import { Services } from "../ble/types"
export const clearAllDeviceIntervals = (
device: ExtendedPeripheral | undefined | null,
@@ -73,8 +78,9 @@ export const writeToDevice: WriteFunction = async (peripheral, data) => {
await BleManager.writeWithoutResponse(
peripheral.id,
- BLE_SERVICE_UUID,
- BLE_CHARACTERISTIC_WRITE_UUID,
+ peripheral.services?.serviceCharacteristic || BLE_SERVICE_UUID,
+ peripheral.services?.writeCharacteristic ||
+ BLE_CHARACTERISTIC_WRITE_UUID,
byteArray,
)
@@ -102,3 +108,61 @@ export const writeToDevice: WriteFunction = async (peripheral, data) => {
}
}
}
+
+const UUID_LENGTH = 36
+
+export const extractServiceAndCharacteristic = (services?: Services) => {
+ log("Extracting services and characteristics.")
+ if (!services) {
+ log("Service object not found, using default.")
+ return {
+ writeCharacteristic: BLE_CHARACTERISTIC_WRITE_UUID,
+ readCharacteristic: BLE_CHARACTERISTIC_READ_UUID,
+ serviceCharacteristic: BLE_SERVICE_UUID,
+ }
+ }
+
+ try {
+ const allServices = services.services.filter(
+ (s) => s.uuid.length === UUID_LENGTH,
+ )
+
+ if (allServices.length !== 1) {
+ throw new Error("Error: More then one service found.")
+ }
+
+ const service = allServices[0]
+
+ const write = services.characteristics.find((c) => {
+ if (c.service === service.uuid && c.properties.WriteWithoutResponse) {
+ return true
+ }
+ })
+
+ const read = services.characteristics.find((c) => {
+ if (c.service === service.uuid && c.properties.Notify) {
+ return true
+ }
+ })
+
+ if (!write || !read) {
+ throw new Error(
+ `Error: No combination found for this service: ${service.uuid}`,
+ )
+ }
+
+ return {
+ serviceCharacteristic: service.uuid,
+ readCharacteristic: read.characteristic,
+ writeCharacteristic: write.characteristic,
+ }
+ } catch (e: any) {
+ log(e.message)
+ log("Extracting services and characteristics failed, using default.")
+ return {
+ writeCharacteristic: BLE_CHARACTERISTIC_WRITE_UUID,
+ readCharacteristic: BLE_CHARACTERISTIC_READ_UUID,
+ serviceCharacteristic: BLE_SERVICE_UUID,
+ }
+ }
+}