> For the complete documentation index, see [llms.txt](https://docs.oneiot.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.oneiot.de/gateway/instrument-driver-1.md).

# Instrument Driver

## Basic Overview

The *Instrument Driver* is responsible to handle the communication to the devices that are attached to the **OneIoT Gateway**. There are two possible communication modes.&#x20;

1. Command Mode
2. Stream Mode

#### Command Mode

In *command mode* the **OneIoT Gateway** serves as a master devic&#x65;*.* It sends commands to the slave devices attached to the gateway, handles their responses and communicates with the **OneIoT Cloud**. The basic skeleton for a command based Instrument Driver looks like the following (Modbus example).&#x20;

```python
import struct
import uasyncio as asyncio
import logging
from serial_communication import Serial
from queue import MESSAGE_BUS, Event, EVENT_TO_CLOUD_FOR_MQTT, EVENT_ERROR
import message_util
from libs.modbus_facade import ModbusFacade

class CustomHandler:

    def __init__(self, serial:Serial):
        # Every 30sec
        self.fire_every = 30
        self.modbus = ModbusFacade(serial)

    async def loop(self):
        # Read every x seconds
        while True:
            await asyncio.sleep_ms(self.fire_every*1000)
                # SEND COMMANDS OR READ VALUES HERE
                # PROCESS RESPONSES FROM DEVICE
                # EXAMPLE:
                answer_double = 
                    await self.modbus.read_holding_registers(0x10, 0x01, 0x02, double_valued=True)[0]
                await self.send_double(1, answer_double)
            
    async def handle_incoming(self, message):
        # Messages from the cloud are send to the driver and can be handled here
        if message_util.get_state_content_for_payload(message) is not None:
            item_number, state_str = message_util.get_state_content_for_payload(message)
            item_number = int(item_number)
            # DO SOMETHING WITH THE ITEM AND STATE, FOR EXAMPLE, SEND COMMAND TO DEVICES
            # EXAMPLE:
            state = int(state_str)
            if not await self.modbus.write_single_coil(0x10, 0x01, state):
                logging.log("Writing register failed.")        
        else:
            logging.log(str("Cannot handle message %s." % str(message)))        

    async def send_double(self, item, value):
        value = str(value)
        await self.send_message(item, value, "DOUBLE")

    async def send_message(self, item, value_string, type="DOUBLE"):
        message = None
        if value_string:
            message =  "S:" + str(item) + ":20:" + type + ":" + value_string
        else:
            message =  "S:" + str(item) + ":50"
            logging.log("No answer received for command...")

        await self.finalize_serial_to_cloud(message)
        # YOU CAN ALSO SEND SOME LOGS TO THE CLOUD HERE

    '''
    This finalizes a serial message given the OneIoT protocol. Timestamp is added and message is checked.
    Then it is sent to the cloud.
    '''
    async def finalize_serial_to_cloud(self, message):
        message = message_util.add_timestamp(message)
        correct = message_util.check_serial_message(message)
        if not correct:
            await MESSAGE_BUS.send(Event(EVENT_ERROR, "Incorrect handled serial: " + message))
        else:
            # just forward to devicecloud via MQTT
            await MESSAGE_BUS.send(Event(EVENT_TO_CLOUD_FOR_MQTT, [message]))
```

#### Stream Mode

In Stream Mode, the device proactively sends messages to the OneIoT Gateway. For this, the OneIoT [Communication Protocol](/gateway/communication-protocol.md) can be used and no further driver is used. If your device sends serial messages in other formats, a driver needs to be used.&#x20;

```python
class CustomHandler:

    def __init__(self):

    async def handle_message(self, message):
        # PARSE MESSAGE; EXAMPLE:
        item, value = parse_message(message)
        # SEND TO CLOUD; EXAMPLE:
        await send_double(item, value)
        
        
    def parse_message(message):
        # Semikolon  separated data (example only)
        data = message.split(';')
        item = int(data[0])
        value = data[1]
        return item, value
        
    async def send_double(self, item, value):
        await self.send_message(item, value, "DOUBLE")

    async def send_message(self, item, value_string, type="DOUBLE"):
        message = None
        if value_string:
            message =  "S:" + str(item) + ":20:" + type + ":" + value_string
        else:
            message =  "S:" + str(item) + ":50"
            logging.log("No answer received for command...")

        await self.finalize_serial_to_cloud(message)
        # YOU CAN ALSO SEND SOME LOGS TO THE CLOUD HERE

    '''
    This finalizes a serial message given the OneIoT protocol. Timestamp is added and message is checked.
    Then it is sent to the cloud.
    '''
    async def finalize_serial_to_cloud(self, message):
        message = message_util.add_timestamp(message)
        correct = message_util.check_serial_message(message)
        if not correct:
            await MESSAGE_BUS.send(Event(EVENT_ERROR, "Incorrect handled serial: " + message))
        else:
            # just forward to devicecloud via MQTT
            await MESSAGE_BUS.send(Event(EVENT_TO_CLOUD_FOR_MQTT, [message]))
        
        
        

```

## API Specification

### Modbus Facade

TBD

### Message Util
