# Mutiple files

## Overview

Supporting multiple files in UbiFunctions enhances flexibility and scalability for developers. When creating serverless functions, the ability to structure code into multiple files improves maintainability, facilitates code reuse, and aligns with industry best practices for software development.

UbiFunctions allows you to use multiple files such as:

* **Source files:** These files contain code that can be executed.
* **Config files:** These are typically `.json`, `.conf`, `.toml`, or similar files that do not contain executable code but rather provide the data and parameters needed for your logic to run with a particular behaviour.
* **Data files:** These are files that store information in various formats, such as `.csv`, `.txt`, or `.json`. They can contain structured, semi-structured, or unstructured data used as input for processing or analysis.

## Creating a file

To create a file within your UbiFunction's root directory, click the **+** icon in the files pane and then select **New file.** Write a name. After doing so, a new file will be added to your UbiFunction:

| <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FwnEJMlxxHmiNbY7aC2Gv%2Fimage.png?alt=media&#x26;token=6bc9732a-8357-43ac-b3f5-f375c5722b3b" alt="" data-size="original"> | <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FNbob4zygxUCMThrXnI1j%2Fimage.png?alt=media&#x26;token=c587cdff-d577-4c33-af5b-5ed05211c3f9" alt="" data-size="original"> |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

## Creating a folder

Folders help you organize and separate each module more effectively. To create a folder, click the **+** icon in the files pane and then select **New folder.**  Write a name. After doing so, a new folder will be added to your UbiFunction:

| <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FLlXctUMHAvbL1DAHHEit%2Fimage.png?alt=media&#x26;token=6d142d6b-2c0b-4517-9ad6-c17b37ab4893" alt="" data-size="original"> | <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FsJLH4qjD8puxSI8Iy9d8%2Fimage.png?alt=media&#x26;token=bf549b14-aad4-4487-829e-f2732250400e" alt="" data-size="original"> |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Creating files or folders within a folder

To create a file or folder, hover over an existing folder, click the three-dot menu, and select **New File** or **New Folder**.&#x20;

| <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FLIyymL16uKQ73EV3c7SN%2Fimage.png?alt=media&#x26;token=ec3e4173-a21a-4a0c-9c1f-1c4309a7cfb4" alt="" data-size="original"> | <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FehhYyNfCyyumSNP8mL2l%2Fimage.png?alt=media&#x26;token=f325e045-8ce6-4bfc-b606-b8631d4f6060" alt="" data-size="original"> |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

## **File Structure and importing**

When you add files or folders to a UbiFunction, they behave like a directory on your local machine, with the root directory as the working directory. For example, adding `decoder.py|js` alongside the main script allows you to import it as:

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const decoder = require('./decoder');
```

{% endtab %}

{% tab title="Python" %}

```python
import decoder
```

{% endtab %}
{% endtabs %}

Similarly, if you create a folder `utils` and add a file `parser.py` or `parser.js` inside it, the structure behaves like a subdirectory on your computer. To import from it, you would use:

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const parser = require('./utils/parser');
```

{% endtab %}

{% tab title="Python" %}

```python
from utils import parser
```

{% endtab %}
{% endtabs %}

This means that when working in UbiFunctions, you can think of the file structure as if you are managing a local project folder.&#x20;

{% hint style="info" %}
UbiFunctions use a protected file system where you can create files and folders via the GUI, but scripts run in a strictly read-only environment where they cannot write to files or create new files and folders.
{% endhint %}

## Data and config files

UbiFunctions allow you to include data and configuration files (e.g., `.json`, `.conf`, `.txt`, `.csv`) to provide inputs or settings for your functions. To read a file, you would handle it just as you would in a typical script on your local machine:

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const fs = require('fs');

// Read a JSON file
// Adjust the file's path accordingly
const settings = JSON.parse(fs.readFileSync('settings.json', 'utf-8'));
```

{% endtab %}

{% tab title="Python" %}

```python
import json

# Read a JSON file
# Adjust the file's path accordingly
with open('settings.json', 'r') as file:
    settings = json.load(file)
print(settings)
```

{% endtab %}
{% endtabs %}

## Example

Suppose that you have a fleet of several different devices on an LNS constantly sending data. On each uplink, you require to decode that data and then forward it to the corresponding device in Ubidots.

For this, you can create an UbiFunction where you'll have separate scripts for each device's type decoder and properly use them from the main function to decode the data.

For the example, proceed to create:

* A folder called *modules.* Within this folder, you'll create the following files:&#x20;
  * A Python script called *s2100.py.* This is the decoder for the s2100 device.
  * A Python script called *s210x.py.* This is the decoder for the s210x device.
  * A Python script called *utils.py.* This one will contain all utils scripts.
* A configuration file containing the configuration for each device type. This one is called *device\_config.json.*

It should look like this:\ <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FCqRttZqk92KAgp4qxuUW%2Fimage.png?alt=media&#x26;token=8825d2e9-139d-4481-8ce7-7155341bc06e" alt="" data-size="original">

The following are the contents of each script:

**s2100.py script**

```python
def s2100_decode(data: int) -> dict:
    """Decode data for s2100 device."""
    temperature = (data & 0x3) >> 0
    humidity = (data & (0x3 << 2)) >> 2
    battery = (data & (0x3 << 4)) >> 4

    return {
        "temperature": temperature,
        "humidity": humidity,
        "battery": battery
    }
```

#### s210x.py script <a href="#h_bc270579ec" id="h_bc270579ec"></a>

```python
def s210x_decode(data: int) -> dict:
    """Decode data for s210x device."""
    soil_electrical_conductivity = (data & 0x3) >> 0
    battery = (data & (0x3 << 2)) >> 2

    return {
        "soil_electrical_conductivity": soil_electrical_conductivity,
        "battery": battery
    }
```

#### utils.py script <a href="#h_e0291278e3" id="h_e0291278e3"></a>

```python
def parse_data(data_str: str) -> int:
    """Convert a binary string to an integer."""
    return int(data_str, 2)

```

\
**device\_config.json**

```python
{
    "s2100" : {"color" : "#fefefe"},
    "s210x" : {"color" : "#ffffff"}
}
```

#### main.py <a href="#h_b306192085" id="h_b306192085"></a>

```python
import requests
import time
import json
import os
from modules.utils import parse_data
from modules.s2100 import s2100_decode
from modules.s210x import s210x_decode

decoders = {
    "s210x" : s2100_decode,
    "s2100" : s210x_decode
}

def main(args):

    """Decode payload based on device type."""
    device_type = args.get("device")
    data_str = args.get("data")
    
    if not device_type or device_type not in decoders:
        print(f"Device type: {device_type} not supported...")
        return {"status" : "error"}

    # Convert binary string to integer
    data = parse_data(data_str)
    decoded_data = decoders[device_type](data)

    # Open and read the JSON file
    with open("device_config.json", 'r') as file:
        config = json.load(file)

    print(config[device_type])
    print(decoded_data)
    return {"status" : "success" , "data" : decoded_data}
```

If you execute this UbiFunction with the following test payload:

```python
{"device": "s2100", "data": "0b01011010"}
```

it should output something like this:

```python
{'color': '#fefefe'}
{'soil_electrical_conductivity': 2, 'battery': 2}
```
