npm package: @saveeye/saveeye-sdk-reactnative
Description: An SDK to integrate SaveEye devices, including device provisioning
Author: SaveEye ApS
Latest version: 1.0.6

# Saveeye SDK

The Saveeye SDK provides a set of methods to interact with Saveeye devices, including provisioning, connecting to WiFi, and retrieving device information. Below is a description of the different methods available in the SDK.

## Example app

An example app is available here: [SaveEye SDK Example App](https://github.com/saveeye/SDK-Example)

## Permissions

### iOS

- Since iOS 13, apps that want to access the SSID (Wi-Fi network name) are required to have location permission. Add the key **NSLocationWhenInUseUsageDescription** in Info.plist with an appropriate description.
- Since iOS 14, apps that communicate over the local network are required to have local network permission. Add the key **NSLocalNetworkUsageDescription** in Info.plist with an appropriate description.
- **NSBluetoothAlwaysUsageDescription** is needed to communicate with the SaveEye device over BLE

### Android

```XML
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />

  <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

  <!-- Request legacy Bluetooth permissions on older devices. -->
  <uses-permission
      android:name="android.permission.BLUETOOTH"
      android:maxSdkVersion="30" />
  <uses-permission
      android:name="android.permission.BLUETOOTH_ADMIN"
      android:maxSdkVersion="30" />
```

On newer android versions a prompt to the user is required for BLUETOOTH_SCAN, BLUETOOTH_CONNECT and ACCESS_FINE_LOCATION.
It can be done via https://github.com/zoontek/react-native-permissions using the following code:

```TypeScript
  async function requestBluetoothPermissions(setError: (msg: string) => void) {
    try {
      if (Platform.OS === "android") {
        // Android 12+ permissions
        const permissions = [
          PERMISSIONS.ANDROID.BLUETOOTH_SCAN,
          PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
          PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
        ];
        for (const perm of permissions) {
          const result = await request(perm);
          if (result !== RESULTS.GRANTED) {
            setError("Bluetooth and location permissions are required.");
            return false;
          }
        }
      }
      // iOS: Add iOS permissions if needed
      return true;
    } catch (e) {
      setError("Failed to request permissions.");
      return false;
    }
  }
```

# General

### getInstance

```typescript
getInstance();
```

Returns the singleton instance of the SaveeyeSdk class.

### initialize

```typescript
initialize(appSDKKey: string, onJWTRequest: () => Promise<string>, environment?: 'dev' | 'staging' | 'prod', locale?: string): void
```

Initializes the SDK with the provided appSDKKey (can be generated in the SaveEye manager portal) and a callback returning a fresh JWT.

**Parameters:**

- `appSDKKey`: The SDK key generated in the SaveEye manager portal
- `onJWTRequest`: A callback function that returns a fresh JWT token
- `environment`: (Optional) The environment to use. Defaults to `'prod'`.
- `locale`: (Optional) Locale for the app (e.g. `'en-US'`, `'da-DK'`). Used for localized error descriptions on your SaveEye devices. If the locale is not supported by the service, English is used as the default.

> **⚠️ Important:** The `environment` parameter is for internal SaveEye development use only. Partners should not use this parameter and should always use the production environment.

#### Example

```typescript
import SaveeyeSdk from '@saveeye/saveeye-sdk-reactnative';

async function getJWTToken(): Promise<string> {
  let token = await auth.currentUser?.getIdToken();
  return token ?? '';
}

// Standard initialization (production environment)
SaveeyeSdk.getInstance().initialize(APP_SDK_KEY, getJWTToken);

// With locale for localized device error descriptions (e.g. from expo-localization)
SaveeyeSdk.getInstance().initialize(APP_SDK_KEY, getJWTToken, 'prod', 'da-DK');

// Note: Partners should always use the standard initialization above.
// The environment parameter is for internal SaveEye development only.
```

### healthCheck

```typescript
healthCheck(): Promise<Boolean>
```

Checks if the SaveEye service is healthy and accessible. This method queries the GraphQL API to verify the service status.

**Returns:**

- A Promise that resolves to `true` if the service is healthy, or `false` if it's not.

**Example:**

```typescript
const isHealthy = await SaveeyeSdk.getInstance().healthCheck();
if (isHealthy) {
  console.log('SaveEye service is healthy');
} else {
  console.log('SaveEye service is not healthy');
}
```

### getSaveEyeDevicesNearby

```typescript
getSaveEyeDevicesNearby(): Promise<ESPDevice[]>
```

Scans for SaveEye devices nearby using Bluetooth Low Energy (BLE).

Returns a Promise that resolves to an array of `ESPDevice` objects representing the SaveEye devices found in the vicinity. This is typically used during the provisioning process to discover devices that are ready to be configured or paired.

**Example:**

```typescript
const devices = await SaveeyeSdk.getInstance().getSaveEyeDevicesNearby();
console.log('Nearby SaveEye devices:', devices);
```

# Provisioning

### getDeviceIdBySerialOrBLEName

```typescript
getDeviceIdBySerialOrBLEName(serialOrBLEName: string): Promise<string>
```

Resolves a device ID from a device's serial number or BLE name. This is useful if you have a serial or BLE name and need to obtain the device's unique ID before provisioning or other operations.

- `serialOrBLEName`: The serial number or BLE name of the device (e.g., `SAVEEYE_KV7G8JVT`).
- **Returns:** A promise that resolves to the device ID string.

**Example:**

```typescript
const deviceId =
  await SaveeyeSdk.getInstance().getDeviceIdBySerialOrBLEName(
    'SAVEEYE_KV7G8JVT'
  );
```

### getDeviceTypeById

```typescript
getDeviceTypeById(deviceId: string): Promise<DeviceTypeFragment>
```

Retrieves the device type and profile for a device by its ID. This uses public device metadata and does not require the device to be paired with the user. Useful during provisioning to determine the device profile (e.g. for `setEncryptionKey`) before the device is paired.

**Parameters:**

- `deviceId`: The unique identifier of the device.

**Returns:**

- A Promise that resolves to a `DeviceTypeFragment` containing:
  - `saveEyePlusDeviceTypeProfile`: The device profile (e.g. 4, 5, or 6). Used to determine which encryption key(s) to pass to `setEncryptionKey`.
  - `deviceType`: The device type enum (e.g. `EYE_Q100`, `EYE_Q300`, `PLUS`, `SPLITTER`).

**Example:**

```typescript
const deviceType = await SaveeyeSdk.getInstance().getDeviceTypeById(deviceId);
// Use deviceType.saveEyePlusDeviceTypeProfile to choose the right key(s) for setEncryptionKey
if (deviceType.saveEyePlusDeviceTypeProfile === 5) {
  await SaveeyeSdk.getInstance().setEncryptionKey(deviceId, 'your-mep-key-20-chars');
}
```

### provisionDevice

```typescript
provisionDevice(qrCodeOrId: string, externalId?: string): Promise<{ espDevice: ESPDevice; deviceId: string }>
```

Provisions a device using a QR code or device ID. The function will extract the device ID from the QR code string using the SDK's internal logic. If you have a serial or BLE name, use `getDeviceIdBySerialOrBLEName` first to resolve the device ID.

> **💡 Tip:** Before provisioning a device, you can check if it's already online using `isDeviceOnline()`. If the device is online, you can simply use `pairDevice()` instead of the full provisioning process, which is more efficient and doesn't require a device reset.

**Returns:**
A promise that resolves to an object containing:

- `espDevice`: The discovered and connected ESPDevice instance.
- `deviceId`: The resolved device ID used for provisioning.

Parameters:

- `qrCodeOrId`: The QR code string or device ID. If a QR code is provided, the device ID will be extracted automatically.
- `externalId?`: (Optional) External ID to pair the device with. If not provided, the device will be paired with the userId within the JWT.

Status updates follow this sequence during device provisioning:

```typescript
export enum ConnectionStages {
  NONE = 'None', // Initial state
  FETCHING_DEVICE_CONFIG = 'FetchingDeviceConfig', // Fetching device config from SaveEye server
  FETCHED_DEVICE_CONFIG = 'FetchedDeviceConfig', // Device config retrieved
  SEARCHING = 'Searching', // Searching for device via BLE
  CONNECTED = 'Connected', // Connected to device via BLE
  PAIRING = 'Pairing', // Pairing device with user
  PAIRED = 'Paired', // Device successfully paired
  ERROR = 'Error', // An error occurred during any stage
}
```

### getAvailableSSIDForDevice

```typescript
getAvailableSSIDForDevice(device: ESPDevice): Promise<ESPWifiList[]>
```

Retrieves the available WiFi networks for the specified device.

### connectToWiFi

```typescript
connectToWiFi(device: ESPDevice, deviceId: string, ssid: string, passphrase?: string): Promise<void>
```

Connects the specified device to the provided WiFi network. This method also updates the onboarding status to indicate that WiFi setup is complete.

**Parameters:**

- `device`: The ESPDevice instance to connect.
- `deviceId`: The unique identifier of the device (as returned from provisioning).
- `ssid`: The SSID (network name) to connect to.
- `passphrase?`: (Optional) The WiFi password. Leave empty for open networks.

**Returns:**

- A Promise that resolves when the device is successfully connected to the WiFi network.

**Example:**

```typescript
// Assume you have already provisioned the device and have both the ESPDevice instance and deviceId
const { espDevice, deviceId } =
  await SaveeyeSdk.getInstance().provisionDevice(qrOrSerial);

// Scan for available WiFi networks
const wifiList =
  await SaveeyeSdk.getInstance().getAvailableSSIDForDevice(espDevice);

// Connect to a selected WiFi network (with or without passphrase)
await SaveeyeSdk.getInstance().connectToWiFi(
  espDevice,
  deviceId,
  wifiList[0].ssid,
  'your_wifi_password'
);

console.log('Device connected to WiFi and onboarding status updated.');
```

### getOnboardingSession

```typescript
getOnboardingSession(deviceId: string): Promise<{
  status: PlusDeviceOnboardingSessionStatus;
  startedOn: Date;
  lastUpdatedOn: Date;
  completedOn: Date;
  errorCode: string;
}>
```

Retrieves the current onboarding session status for a specific device. This method is useful for checking the progress or result of the device onboarding process, including timestamps and any error codes.

**Parameters:**

- `deviceId`: The unique identifier of the device whose onboarding session you want to query.

**Returns:**

- A Promise that resolves to an object containing:
  - `status`: The current onboarding session status (enum value).
  - `startedOn`: The timestamp when onboarding started.
  - `lastUpdatedOn`: The timestamp of the last update to the onboarding session.
  - `completedOn`: The timestamp when onboarding was completed (if available).
  - `errorCode`: Any error code associated with the onboarding session (if any).

**Example:**

```typescript
const session = await SaveeyeSdk.getInstance().getOnboardingSession(deviceId);
console.log('Onboarding status:', session.status);
console.log('Started on:', session.startedOn);
console.log('Last updated on:', session.lastUpdatedOn);
console.log('Completed on:', session.completedOn);
console.log('Error code:', session.errorCode);
```

### PlusDeviceOnboardingSessionStatus (enum)

The `PlusDeviceOnboardingSessionStatus` enum describes the possible states of a device's onboarding session:

- `BLE_CONNECTED`: The device has been connected via Bluetooth Low Energy (BLE).
- `WI_FI_SETUP_DONE`: The device has successfully connected to WiFi.
- `FIRMWARE_UPDATE_IN_PROGRESS`: The device is currently updating its firmware.
- `FIRMWARE_UPDATE_DONE`: The device has completed a firmware update.
- `DONE`: The onboarding process is complete.
- `ERROR_MESSAGES`: An error occurred during onboarding.

You can use these values to interpret the `status` field returned by `getOnboardingSession()`.

### onDeviceStatusUpdate

```typescript
onDeviceStatusUpdate(callback: (status: string) => void)
```

Registers`a callback to receive device status updates when the device is provisioning using BLE.

Status updates follow this sequence during device provisioning:

```typescript
export enum ConnectionStages {
  NONE = 'None', // Initial state
  FETCHING_DEVICE_CONFIG = 'FetchingDeviceConfig', // Fetching device config from SaveEye server
  FETCHED_DEVICE_CONFIG = 'FetchedDeviceConfig', // Device config retrieved
  SEARCHING = 'Searching', // Searching for device via BLE
  CONNECTED = 'Connected', // Connected to device via BLE
  PAIRING = 'Pairing', // Pairing device with user
  PAIRED = 'Paired', // Device successfully paired
  ERROR = 'Error', // An error occurred during any stage
}
```

### stopDeviceStatusUpdates

```typescript
stopDeviceStatusUpdates();
```

Stops receiving device status updates.

### setEncryptionKey

**Encryption Key Requirements by Device Profile:**

| Device Profile | Key Parameter(s)       | Required Length(s) |
| :------------: | :--------------------- | :----------------- |
|       4        | `opticalKey`           | Any (string)       |
|       5        | `mepKey`               | 20 characters      |
|       6        | `gpk60Key`, `gpk61Key` | 32 characters each |

> **⚠️ Note:** Supplying a key with an incorrect length will result in an error. Only provide the key(s) relevant to the device's profile. Other parameters can be left as `undefined` or omitted.

```typescript
setEncryptionKey(
  deviceId: string,
  mepKey?: string,
  gpk60Key?: string,
  gpk61Key?: string,
  opticalKey?: string
): Promise<void>
```

Sets the encryption key(s) for a specific device. The type of key(s) required depends on the device's profile. You can determine the device profile from the `profile` field in the device's `deviceType` (see `getMyDevices` or `getDeviceById`), or use `getDeviceTypeById` to get the profile by device ID without requiring the device to be paired.

**Parameters:**

- `deviceId`: The ID of the device to update
- `mepKey?`: (Profile 5) The MEP encryption key
- `gpk60Key?`: (Profile 6) The GPK60 encryption key
- `gpk61Key?`: (Profile 6) The GPK61 encryption key
- `opticalKey?`: (Profile 4) The Optical encryption key

**Examples:**

```typescript
// For a profile 5 device (MEP)
await SaveeyeSdk.getInstance().setEncryptionKey(
  deviceId,
  '12345678901234567890'
);

// For a profile 6 device (GPK)
await SaveeyeSdk.getInstance().setEncryptionKey(
  deviceId,
  undefined,
  '12345678901234567890123456789012',
  'abcdefghijabcdefghijabcdefghijabcd'
);

// For a profile 4 device (Optical)
await SaveeyeSdk.getInstance().setEncryptionKey(
  deviceId,
  undefined,
  undefined,
  undefined,
  'optical-key-value'
);
```

### setEncryptionKeysFromInputs

```typescript
setEncryptionKeysFromInputs(
  deviceId: string,
  encryptionInputs: EncryptionInput[]
): Promise<void>
```

Sets encryption key(s) for a device using the same input structure returned by `getDeviceSettings`. Use this when you have already fetched device settings and want to submit user-entered values for the encryption fields (e.g. from a form). Each item in `encryptionInputs` must include the `id` from the device's `encryptionInputs`, with `value` set to the user's input.

**Parameters:**

- `deviceId`: The ID of the device to update.
- `encryptionInputs`: Array of objects with `id` and `value` (the key value to set). Match the structure from `getDeviceSettings().encryptionInputs`, filling in `value` with the user's input.

**Example:**

```typescript
const settings = await SaveeyeSdk.getInstance().getDeviceSettings(deviceId);
const encryptionInputs = (settings.encryptionInputs ?? []).map((input) => ({
  id: input.id,
  value: userEnteredValues[input.id] ?? input.value ?? '',
}));
await SaveeyeSdk.getInstance().setEncryptionKeysFromInputs(deviceId, encryptionInputs);
```

### pairDevice

```typescript
pairDevice(qrCodeOrId: string, externalId?: string): Promise<void>
```

Pair a device with an optional externalId. If no externalId is provided, the UserId from the JWT token will be used instead.
Also called automatically from provisionDevice, but this can be used to avoid having to reprovision a device.

**Parameters:**

- `qrCodeOrId`: The QR code string or device ID to pair
- `externalId?`: (Optional) External ID to pair the device with. If not provided, the device will be paired with the userId within the JWT

### pairRemoteDeviceWithBaseDevice

```typescript
pairRemoteDeviceWithBaseDevice(deviceId: string, baseDeviceId: string, externalId?: string): Promise<void>
```

Pairs a remote device with a base device and optionally with an external ID. This method creates a pairing between the remote device and the base device, and if an external ID is provided, it also pairs the remote device with that external ID. If no external ID is provided, the UserId from the JWT token will be used instead.

**Parameters:**

- `deviceId`: The ID of the remote device to pair
- `baseDeviceId`: The ID of the base device to pair the remote device with
- `externalId?`: (Optional) External ID to pair the device with. If not provided, the device will be paired with the userId within the JWT

**Example:**

```typescript
await SaveeyeSdk.getInstance().pairRemoteDeviceWithBaseDevice(
  'remote-device-id',
  'base-device-id',
  'optional-external-id'
);
```

### unpairDevice

```typescript
unpairDevice(deviceId: string, externalId?: string): Promise<void>
```

Unpairs a device from the current user. This removes the association between the device and the user, allowing the device to be paired with a different user.

**Parameters:**

- `deviceId`: The ID of the device to unpair
- `externalId?`: (Optional) External ID to unpair the device from. This is mandatory if the externalId is a listclaim (As provided when creating app SDK key in the SaveEye Manager).

# User

### getMyDevices

```typescript
getMyDevices(): Promise<MyDeviceFragment[]>
```

Retrieves the devices associated with the user. Use this to list all devices linked to the user's account. The previous `pairedDevices` / `getMyPairedDevices` API has been removed in favor of `getMyDevices`.

### getDeviceById

```typescript
getDeviceById(deviceId: string): Promise<MyDeviceFragment>
```

Retrieves a specific device by its ID. Note: This function will only return the device if the user's JWT token is paired with the device.

**Parameters:**

- `deviceId`: The ID of the device to retrieve

The `MyDeviceFragment` type contains the following fields:

```typescript
type MyDeviceFragment = {
  id: string; // Unique identifier of the device
  alias?: string | null; // User-defined name for the device
  errorCode?: string | null; // Latest error code from the device (null when there is no error)
  errorCodeDescriptionLocalized?: string | null; // Human-readable description of the error
  firmwareUpdateState: FirmwareUpdateState; // Current firmware update state (see FirmwareUpdateState enum)
  serial: string; // Device serial number
  allTimesHighConsumption: number; // Highest recorded consumption value
  allTimesHighProduction: number; // Highest recorded production value
  hasProduction: boolean; // Whether the device has measured production
  rmsCurrentMaxPerPhaseAmpere: number; // Maximum RMS current per phase in amperes
  deviceType: {
    name: string; // Name of the device type
    profile?: number | null; // Profile of the device type
  };
};
```

**Note:** `errorCodeDescriptionLocalized` is localized using the locale passed to `initialize()`. If no locale was provided or the locale is not supported, English is used. The value is in **markdown** format and can be rendered with a markdown-capable UI component.

### FirmwareUpdateState (enum)

The `firmwareUpdateState` field on `MyDeviceFragment` (returned by `getMyDevices` and `getDeviceById`) uses the `FirmwareUpdateState` enum:

- `IDLE`: The device is idle and not performing any firmware update.
- `UPDATING`: The device is currently applying a firmware update.
- `FAILED`: A firmware update failed; the device was unable to complete the update.

Use this to show update status in the UI (e.g. a progress or status indicator while `UPDATING`, or an error state when `FAILED`).

# Device data

## Historic

### getEnergyUsageHistory

```typescript
  getEnergyUsageHistory(deviceId: string, startUTC: Date, endUTC: Date, interval: IntervalType): Promise<EnergyUsageHistory>
```

Retrieves the energy usage history of a device within a specified time range and interval. The interval parameter determines the granularity of the data points (e.g. hourly, daily, monthly). Returns a Promise containing the energy usage data for the specified period.

**Parameters:**

- `deviceId`: The ID of the device to get history for
- `startUTC`: Start date/time in UTC
- `endUTC`: End date/time in UTC
- `interval`: The data point interval (IntervalType)

### getPowerUsageHistory

```typescript
getPowerUsageHistory(deviceId: string, startUTC: Date, endUTC: Date, interval: IntervalType): Promise<PowerUsageHistory>
```

Retrieves the power usage history of a device within a specified time range and interval. The interval parameter determines the granularity of the data points (e.g. hourly, daily, monthly). Returns a Promise containing the power usage data for the specified period.

**Parameters:**

- `deviceId`: The ID of the device to get history for
- `startUTC`: Start date/time in UTC
- `endUTC`: End date/time in UTC
- `interval`: The data point interval (IntervalType)

The `PowerUsageHistory` type contains the following fields:

```typescript
type PowerUsageHistory = {
  deviceId: string; // The unique identifier of the device
  intervalType: IntervalType; // The time interval used for aggregating power usage data
  powerUsageSummaries: Array<PowerUsageSummary>; // Collection of power usage summaries
};

type PowerUsageSummary = {
  aggregationPeriod: Date; // The timestamp representing the start of the aggregation period (UTC)
  averageConsumptionWatt: number; // The average power consumption (in watts) during the aggregation period
  averageProductionWatt: number; // The average power production (in watts) during the aggregation period
  maxConsumptionWatt: number; // The maximum power consumption (in watts) recorded during the aggregation period
  maxProductionWatt: number; // The maximum power production (in watts) recorded during the aggregation period
  minConsumptionWatt: number; // The minimum power consumption (in watts) recorded during the aggregation period
  minProductionWatt: number; // The minimum power production (in watts) recorded during the aggregation period
};
```

## Realtime

### subscribeToRealtimeData

```typescript
  async subscribeToRealtimeData(
    deviceId: string,
    callback: (data: RealtimeReading, error?: WebSocketErrorEvent) => void
  )
```

Subscribes to real-time data updates for a specific device. This method establishes a WebSocket connection to receive real-time data from the device and invokes the provided callback function whenever new data is received or when an error occurs.

**Multiple Device Support:**

- You can subscribe to multiple different devices simultaneously (one WebSocket per device)
- Each device maintains its own independent WebSocket connection
- Subscribing to different devices will create separate connections

**Multiple Callbacks Per Device:**

- You can register multiple callbacks for the same device
- If you subscribe to a device that is already connected, the callback will be added to the existing subscription
- A `CONCURRENT_SUBSCRIPTION` error will be returned to notify you of the duplicate subscription
- **Important:** Even though an error is returned, your callback will still receive data from the existing WebSocket connection
- All callbacks registered for a device will receive the same data when it arrives

**Parameters:**

- `deviceId`: The ID of the device to subscribe to
- `callback`: A function that receives real-time data or error events

**Error Handling:**

If you subscribe to a device that is already connected, you will receive a `WebSocketErrorEvent` with:

- `type: 'CONCURRENT_SUBSCRIPTION'`
- `message`: A description indicating the callback was added to an existing subscription

Despite the error, your callback will be registered and will receive data from the existing WebSocket connection.

**Example - Multiple Devices:**

```typescript
// Subscribe to device 1
sdk.subscribeToRealtimeData('device-1', (data, error) => {
  if (error) {
    console.log('Device 1 error:', error);
  } else {
    console.log('Device 1 data:', data);
  }
});

// Subscribe to device 2 (different device - creates new WebSocket)
sdk.subscribeToRealtimeData('device-2', (data, error) => {
  console.log('Device 2 data:', data);
});

// Subscribe to device 1 again (same device - error but callback added)
sdk.subscribeToRealtimeData('device-1', (data, error) => {
  // This callback will receive CONCURRENT_SUBSCRIPTION error initially
  // But will still receive data from the existing WebSocket connection
  if (error && error.type === 'CONCURRENT_SUBSCRIPTION') {
    console.log('Already subscribed, but will receive data');
  } else if (data) {
    console.log('Device 1 data (second callback):', data);
  }
});
```

### unsubscribeFromRealtimeData

```typescript
unsubscribeFromRealtimeData(deviceId?: string): Promise<void>
```

Unsubscribes from real-time data updates. This method closes the WebSocket connection(s) that were established for receiving real-time data.

**Parameters:**

- `deviceId?`: (Optional) The ID of the device to unsubscribe from. If not provided, unsubscribes from all devices.

**Behavior:**

- If `deviceId` is provided: Closes the WebSocket connection for that specific device and removes all callbacks for that device
- If `deviceId` is not provided: Closes all WebSocket connections for all devices and removes all callbacks

**Best Practices:**

Best implemented with a return function in eg. a useEffect, to make sure the websocket is closed when not in use.

#### React Native Example:

```typescript
useEffect(() => {
  SaveeyeSdk.getInstance().subscribeToRealtimeData(deviceId, (data, error) => {
    if (error) {
      if (error.type === 'CONCURRENT_SUBSCRIPTION') {
        // Already subscribed, but callback will still receive data
        console.log('Already subscribed to device');
      } else {
        console.error('WebSocket error:', error);
      }
    } else if (data) {
      console.log('Got realtime data:', data);
    }
  });

  return () => {
    // Unsubscribe from this specific device
    SaveeyeSdk.getInstance().unsubscribeFromRealtimeData(deviceId);
  };
}, [deviceId]);
```

#### Expo Router Example:

```typescript
useFocusEffect(
  useCallback(() => {
    SaveeyeSdk.getInstance().subscribeToRealtimeData(
      deviceId,
      (data, error) => {
        if (error) {
          if (error.type === 'CONCURRENT_SUBSCRIPTION') {
            // Already subscribed, but callback will still receive data
            console.log('Already subscribed to device');
          } else {
            console.error('WebSocket error:', error);
          }
        } else if (data) {
          console.log('Got realtime data:', data);
        }
      }
    );

    return () => {
      // Unsubscribe from this specific device
      SaveeyeSdk.getInstance().unsubscribeFromRealtimeData(deviceId);
    };
  }, [deviceId])
);
```

#### Multiple Devices Example:

```typescript
useEffect(() => {
  const sdk = SaveeyeSdk.getInstance();
  const deviceIds = ['device-1', 'device-2', 'device-3'];

  // Subscribe to multiple devices
  deviceIds.forEach((id) => {
    sdk.subscribeToRealtimeData(id, (data, error) => {
      if (error) {
        console.error(`Error for device ${id}:`, error);
      } else if (data) {
        console.log(`Data from device ${id}:`, data);
      }
    });
  });

  return () => {
    // Unsubscribe from all devices
    sdk.unsubscribeFromRealtimeData();
  };
}, []);
```

# Settings

### getDeviceSettings

```typescript
getDeviceSettings(deviceId: string): Promise<DeviceSettingsFragment>
```

Retrieves the settings of a specific device. `DeviceSettingsFragment` has the following shape:

```typescript
type DeviceSettingsFragment = {
  __typename?: 'Device';
  alias?: string | null;
  plusDevice?: {
    localMqttBroker?: string | null;
    localMqttEnabled?: boolean | null;
    localMqttPassword?: string | null;
    localMqttPort?: number | null;
    localMqttUser?: string | null;
    consumptionAlarmMaxWh?: number | null;
    blinksPerKwh?: number | null;
    errorCode?: string | null;
  } | null;
  encryptionInputs?: Array<{
    id: string;
    value?: string | null;
    placeholder: string;
  }> | null;
};
```

**Changes:** `consumptionAlarmMinWh` has been removed. MQTT and alarm fields now live under `plusDevice`. The new `encryptionInputs` array describes encryption-related fields (e.g. for device provisioning).

### setDeviceAlias

```typescript
setDeviceAlias(deviceId: string, name: string): Promise<void>
```

Sets the alias (name) for a specific device. This method updates the device's alias on the server.

### setLocalMQTTSettings

```typescript
setLocalMQTTSettings(
    deviceId: string,
    enabled: boolean,
    broker: string,
    port: number,
    username: string,
    password: string
): Promise<void>
```

Sets the local MQTT settings for a specific device. This method updates the local MQTT configuration on the server for the specified device.

### setDeviceAlarmThresholds

```typescript
setDeviceAlarmThresholds(
    deviceId: string,
    alarmMaxWh?: number,
    alarmMinWh?: number
  ): Promise<void>
```

Sets the alarm thresholds for a specific device. This method updates the maximum and minimum alarm thresholds for the device on the server. When the threshold is reached your backend can be notified.

**Parameters:**

- `deviceId`: The ID of the device to update
- `alarmMaxWh?`: (Optional) The maximum alarm threshold in watt-hours
- `alarmMinWh?`: (Optional) The minimum alarm threshold in watt-hours

### setRmsCurrentMaxPerPhase

```typescript
setRmsCurrentMaxPerPhase(deviceId: string, rmsCurrentMax: number): Promise<void>
```

Sets the maximum RMS current per phase for a specific device. This method updates the maximum allowed current (in amperes) that can flow through each phase of the meter. This can be retrieved again through the getMyDevices function.

**Parameters:**

- `deviceId`: The ID of the device to update
- `rmsCurrentMax`: The maximum RMS current in amperes per phase

### setBlinksPerKwh

```typescript
setBlinksPerKwh(deviceId: string, blinksPerKwh: number): Promise<void>
```

Sets the number of blinks per kilowatt-hour (kWh) for a specific device. This value is used for devices that measure energy consumption via an optical sensor (e.g., by counting LED blinks on a traditional electricity meter). Setting the correct value ensures accurate energy measurement and reporting.

**Parameters:**

- `deviceId`: The ID of the device to update.
- `blinksPerKwh`: The number of LED blinks that correspond to 1 kWh on the meter (typically found on the meter label, e.g., 1000 or 2000).

**Returns:**

- A Promise that resolves when the value has been set successfully.

**Example:**

```typescript
await SaveeyeSdk.getInstance().setBlinksPerKwh(deviceId, 1000);
```

This will set the device to interpret 1000 blinks as 1 kWh.

### isDeviceOnline

```typescript
isDeviceOnline(qrCodeOrId: string): Promise<boolean>
```

Checks if a specific device is currently online and connected to the SaveEye network. This method can be used to determine the connectivity status of a device before attempting to retrieve data or perform operations that require an active connection.

**Parameters:**

- `qrCodeOrId`: The QR code string or device ID to check the online status for.

**Returns:**

- A Promise that resolves to `true` if the device is online and connected, or `false` if the device is offline.

**Example:**

```typescript
const isOnline = await SaveeyeSdk.getInstance().isDeviceOnline(qrCodeOrId);
if (isOnline) {
  console.log('Device is online and ready for data retrieval');
} else {
  console.log('Device is offline or unreachable');
}
```

# Support

For support or questions, please contact [thj@saveeye.com](mailto:thj@saveeye.com).