# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.oneiot.de/gateway/instrument-driver-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
