// Modbus RTU server add-on (v2 polling)

/*
{
  "id": "modbus_rtu_v2",
  "name": "Modbus RTU Server",
  "description": "Blocks for a Modbus RTU server. Includes initialization, polling, and register handling. Ideal for creating a sensor or display device connected to a Modbus RTU bus.",
  "version": "1.0.0",
  "author": "Milan Spacek + Codex",
  "tags": ["Modbus", "RTU", "Server"],
  "screenshots": ["modbus_rtu_server.png"],
  "changelog": "v1.0.2"
}
*/

// MicroPython library embedded as a JS string
var modbus_rtu_v2_lib = `# modbus_rtu_v2.py
# Minimal Modbus RTU server (polling) for MicroPython (ESP32/RP2040)

try:
    import ustruct as struct
except ImportError:
    import struct

try:
    import uarray as _uarray
    array = _uarray.array
except ImportError:
    from array import array

try:
    from machine import UART
except Exception:
    UART = None

try:
    import ubinascii as binascii
except ImportError:
    import binascii

import time

FC_READ_HOLDING = 3
FC_WRITE_SINGLE = 6
FC_WRITE_MULTIPLE = 16
FC_READWRITE = 23

EX_ILLEGAL_FUNCTION = 1
EX_ILLEGAL_DATA_ADDRESS = 2
EX_ILLEGAL_DATA_VALUE = 3


def _crc16(data):
    crc = 0xFFFF
    for b in data:
        crc ^= b
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc & 0xFFFF


def _add_crc(payload):
    crc = _crc16(payload)
    return payload + bytes([crc & 0xFF, (crc >> 8) & 0xFF])


class ModbusRTUServer:
    def __init__(
        self,
        unit_id=1,
        max_regs=200,
        base_reg=0,
        idle_timeout_ms=5000,
        frame_gap_ms=5,
        rx_buf_size=256,
        debug=False,
    ):
        self.unit_id = int(unit_id) & 0xFF
        self.base_reg = int(base_reg)
        self._regs = array("H", [0] * int(max_regs))
        self._uart = None
        self._uart_cfg = None
        self._stop = False
        self.debug = bool(debug)
        self._rx_buf_size = int(rx_buf_size) if int(rx_buf_size) > 0 else 256
        self._rx_buf = bytearray(self._rx_buf_size)
        self._rx_len = 0
        self._rx_last_ms = time.ticks_ms()
        self._idle_timeout_ms = int(idle_timeout_ms)
        self._frame_gap_ms = int(frame_gap_ms)
        self._clear_rx()

    def _hex(self, data):
        try:
            return binascii.hexlify(data).decode()
        except Exception:
            try:
                return str(binascii.hexlify(data))
            except Exception:
                return str(data)

    def _log(self, msg):
        if self.debug:
            try:
                print("[MBRTU]", msg)
            except Exception:
                pass

    def _clear_rx(self):
        self._rx_len = 0
        self._rx_last_ms = time.ticks_ms()
        for i in range(self._rx_buf_size):
            self._rx_buf[i] = 0

    def init_uart(self, uart_id=1, baud=9600, tx=None, rx=None, bits=8, parity=None, stop=1, timeout=50, timeout_char=2):
        # store config and init UART
        self._uart_cfg = (
            int(uart_id), int(baud), tx, rx, int(bits), parity, int(stop), int(timeout), int(timeout_char)
        )
        self._log("UART cfg: id=%s baud=%s tx=%s rx=%s bits=%s parity=%s stop=%s timeout=%s timeout_char=%s" %
                  (uart_id, baud, tx, rx, bits, parity, stop, timeout, timeout_char))
        return self._setup_uart()

    def _setup_uart(self):
        if UART is None:
            print("ModbusRTU: UART is not available.")
            return None
        if self._uart_cfg is None:
            return None

        uart_id, baud, tx, rx, bits, parity, stop, timeout, timeout_char = self._uart_cfg

        # best-effort deinit of same UART id
        try:
            UART(int(uart_id)).deinit()
        except Exception:
            pass

        # deinit our instance if any
        if self._uart is not None:
            try:
                self._uart.deinit()
            except Exception:
                pass
            self._uart = None

        try:
            if tx is None or rx is None:
                self._uart = UART(int(uart_id), baudrate=int(baud), bits=int(bits), parity=parity, stop=int(stop), timeout=int(timeout), timeout_char=int(timeout_char))
            else:
                self._uart = UART(int(uart_id), baudrate=int(baud), tx=int(tx), rx=int(rx), bits=int(bits), parity=parity, stop=int(stop), timeout=int(timeout), timeout_char=int(timeout_char))
        except Exception as e:
            print("ModbusRTU: UART setup error", e)
            self._uart = None
            return None

        self._clear_rx()
        self._log("UART init OK")
        return self._uart

    def start(self):
        # reinit UART on start and clear buffers
        self._stop = False
        if self._uart_cfg is None:
            print("ModbusRTU: UART is not configured. Call init_uart() first.")
            return
        self._setup_uart()
        self._log("Server start")

    def stop(self):
        self._stop = True
        if self._uart:
            try:
                self._uart.deinit()
            except Exception:
                pass
            self._uart = None
        self._log("Server stop")

    # data helpers
    def set_reg(self, addr, value):
        idx = self._reg_index(addr)
        if idx is None:
            raise ValueError("register out of range")
        self._regs[idx] = int(value) & 0xFFFF

    def set_regs(self, addr, values):
        idx = self._reg_index(addr)
        if idx is None:
            raise ValueError("register out of range")
        values = list(values)
        if idx + len(values) > len(self._regs):
            raise ValueError("register range out of bounds")
        for i, v in enumerate(values):
            self._regs[idx + i] = int(v) & 0xFFFF

    def get_reg(self, addr, default=None):
        idx = self._reg_index(addr)
        if idx is None:
            return default
        return int(self._regs[idx])

    def set_reg_s16(self, addr, value):
        v = int(value)
        if v < -32768:
            v = -32768
        elif v > 32767:
            v = 32767
        self.set_reg(addr, v & 0xFFFF)

    def get_reg_s16(self, addr, default=0):
        v = self.get_reg(addr, None)
        if v is None:
            return default
        if v & 0x8000:
            v -= 0x10000
        return v

    def set_reg_f32(self, addr, value):
        try:
            f = float(value)
        except Exception:
            f = 0.0
        b = struct.pack(">f", f)
        w0 = (b[0] << 8) | b[1]
        w1 = (b[2] << 8) | b[3]
        self.set_reg(addr, w0)
        self.set_reg(addr + 1, w1)

    def get_reg_f32(self, addr, default=0.0):
        w0 = self.get_reg(addr, None)
        w1 = self.get_reg(addr + 1, None)
        if w0 is None or w1 is None:
            return default
        b = bytes([
            (w0 >> 8) & 0xFF, w0 & 0xFF,
            (w1 >> 8) & 0xFF, w1 & 0xFF
        ])
        try:
            return struct.unpack(">f", b)[0]
        except Exception:
            return default

    # polling handler
    def poll(self, max_loops=10):
        if self._stop or self._uart is None:
            return
        loops = 0
        while loops < int(max_loops):
            loops += 1
            now = time.ticks_ms()
            data = None
            try:
                n = self._uart.any()
                if n:
                    data = self._uart.read(n)
                else:
                    data = None
            except Exception:
                data = None

            if data:
                if self.debug:
                    self._log("RX %d bytes: %s" % (len(data), self._hex(data)))
                if self._append_rx(data):
                    self._rx_last_ms = now
                self._process_rx_buffer()
                continue

            if self._rx_len > 0:
                if time.ticks_diff(now, self._rx_last_ms) >= self._frame_gap_ms:
                    # parse whatever we have without waiting; handles back-to-back frames
                    self._process_rx_buffer(force=True)
                elif time.ticks_diff(now, self._rx_last_ms) >= self._idle_timeout_ms:
                    if self.debug:
                        self._log("RX idle timeout, clearing buffer")
                    self._rx_len = 0
            break

    def _reg_index(self, addr):
        idx = int(addr) - self.base_reg
        if idx < 0 or idx >= len(self._regs):
            return None
        return idx

    def _append_rx(self, data):
        n = len(data)
        if n <= 0:
            return False
        if self._rx_len + n > self._rx_buf_size:
            self._rx_len = 0
            return False
        self._rx_buf[self._rx_len : self._rx_len + n] = data
        self._rx_len += n
        return True

    def _expected_len(self, buf, n):
        if n < 2:
            return 0
        fc = buf[1]
        if fc in (FC_READ_HOLDING, FC_WRITE_SINGLE):
            return 8 if n >= 2 else 0
        if fc == FC_WRITE_MULTIPLE:
            if n < 7:
                return 0
            byte_count = buf[6]
            return 9 + byte_count
        if fc == FC_READWRITE:
            if n < 11:
                return 0
            byte_count = buf[10]
            return 13 + byte_count
        return -1

    def _process_rx_buffer(self, force=False):
        # Consume as many complete frames as possible, resyncing on CRC errors.
        while self._rx_len >= 4:
            exp_len = self._expected_len(self._rx_buf, self._rx_len)
            if exp_len == 0:
                break
            if exp_len < 0:
                # unknown function code, drop one byte and resync
                self._rx_buf[: self._rx_len - 1] = self._rx_buf[1 : self._rx_len]
                self._rx_len -= 1
                continue
            if exp_len > self._rx_len:
                if force:
                    # incomplete frame, drop buffer
                    if self.debug:
                        self._log("RX incomplete frame (%d/%d), clearing" % (self._rx_len, exp_len))
                    self._rx_len = 0
                break

            frame = bytes(self._rx_buf[: exp_len])
            crc_rx = frame[-2] | (frame[-1] << 8)
            crc_calc = _crc16(frame[:-2])
            if crc_rx != crc_calc:
                if self.debug:
                    self._log("CRC mismatch: rx=%04x calc=%04x" % (crc_rx, crc_calc))
                # drop one byte and resync
                self._rx_buf[: self._rx_len - 1] = self._rx_buf[1 : self._rx_len]
                self._rx_len -= 1
                continue

            if self.debug:
                self._log("FRAME %d bytes: %s" % (len(frame), self._hex(frame)))
            resp = self._process_frame(frame)
            if resp:
                if self.debug:
                    self._log("TX %d bytes: %s" % (len(resp), self._hex(resp)))
                try:
                    self._uart.write(resp)
                except Exception:
                    pass

            # consume frame
            remain = self._rx_len - exp_len
            if remain > 0:
                self._rx_buf[:remain] = self._rx_buf[exp_len : self._rx_len]
            self._rx_len = remain

    def _process_frame(self, frame):
        if frame is None or len(frame) < 4:
            if self.debug and frame is not None:
                self._log("Frame too short: %s" % self._hex(frame))
            return None

        addr = frame[0]
        if addr != self.unit_id and addr != 0:
            if self.debug:
                self._log("Addr mismatch: got %d, expected %d" % (addr, self.unit_id))
            return None

        crc_rx = frame[-2] | (frame[-1] << 8)
        crc_calc = _crc16(frame[:-2])
        if crc_rx != crc_calc:
            if self.debug:
                self._log("CRC mismatch: rx=%04x calc=%04x" % (crc_rx, crc_calc))
            return None

        pdu = frame[1:-2]
        if not pdu:
            if self.debug:
                self._log("Empty PDU")
            return None
        fc = pdu[0]

        broadcast = (addr == 0)
        if self.debug:
            self._log("FC=%d broadcast=%s" % (fc, "yes" if broadcast else "no"))

        if fc == FC_READ_HOLDING:
            if broadcast:
                return None
            if len(pdu) < 5:
                if self.debug:
                    self._log("FC3 invalid length")
                return self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            start, qty = struct.unpack(">HH", pdu[1:5])
            if qty < 1 or qty > 125:
                if self.debug:
                    self._log("FC3 invalid qty=%d" % qty)
                return self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            idx = self._reg_index(start)
            if idx is None or idx + qty > len(self._regs):
                if self.debug:
                    self._log("FC3 addr out of range start=%d qty=%d" % (start, qty))
                return self._exception(addr, fc, EX_ILLEGAL_DATA_ADDRESS)
            resp = bytearray(3 + qty * 2)
            resp[0] = addr
            resp[1] = fc
            resp[2] = qty * 2
            off = 3
            for i in range(qty):
                struct.pack_into(">H", resp, off, self._regs[idx + i])
                off += 2
            return _add_crc(resp)

        if fc == FC_WRITE_SINGLE:
            if len(pdu) < 5:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            raddr, value = struct.unpack(">HH", pdu[1:5])
            idx = self._reg_index(raddr)
            if idx is None:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_ADDRESS)
            self._regs[idx] = value & 0xFFFF
            if broadcast:
                return None
            resp = bytearray(6)
            resp[0] = addr
            resp[1] = fc
            struct.pack_into(">HH", resp, 2, raddr, value)
            return _add_crc(resp)

        if fc == FC_WRITE_MULTIPLE:
            if len(pdu) < 6:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            start, qty = struct.unpack(">HH", pdu[1:5])
            byte_count = pdu[5]
            if qty < 1 or qty > 123:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            if byte_count != qty * 2:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            idx = self._reg_index(start)
            if idx is None or idx + qty > len(self._regs):
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_ADDRESS)
            expected_len = 6 + byte_count
            if len(pdu) < expected_len:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            data = pdu[6 : 6 + byte_count]
            for i in range(qty):
                value = struct.unpack(">H", data[i * 2 : i * 2 + 2])[0]
                self._regs[idx + i] = value
            if broadcast:
                return None
            resp = bytearray(6)
            resp[0] = addr
            resp[1] = fc
            struct.pack_into(">HH", resp, 2, start, qty)
            return _add_crc(resp)

        if fc == FC_READWRITE:
            if len(pdu) < 10:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            r_start, r_qty, w_start, w_qty = struct.unpack(">HHHH", pdu[1:9])
            w_byte_count = pdu[9]
            if r_qty < 1 or r_qty > 125:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            if w_qty < 1 or w_qty > 121:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            if w_byte_count != w_qty * 2:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            w_idx = self._reg_index(w_start)
            r_idx = self._reg_index(r_start)
            if w_idx is None or w_idx + w_qty > len(self._regs):
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_ADDRESS)
            if r_idx is None or r_idx + r_qty > len(self._regs):
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_ADDRESS)
            expected_len = 10 + w_byte_count
            if len(pdu) < expected_len:
                return None if broadcast else self._exception(addr, fc, EX_ILLEGAL_DATA_VALUE)
            data = pdu[10 : 10 + w_byte_count]
            for i in range(w_qty):
                value = struct.unpack(">H", data[i * 2 : i * 2 + 2])[0]
                self._regs[w_idx + i] = value
            if broadcast:
                return None
            resp = bytearray(3 + r_qty * 2)
            resp[0] = addr
            resp[1] = fc
            resp[2] = r_qty * 2
            off = 3
            for i in range(r_qty):
                struct.pack_into(">H", resp, off, self._regs[r_idx + i])
                off += 2
            return _add_crc(resp)

        if broadcast:
            return None
        return self._exception(addr, fc, EX_ILLEGAL_FUNCTION)

    def _exception(self, addr, fc, code):
        resp = bytes([addr, fc | 0x80, code])
        return _add_crc(resp)
`;

// Button for uploading the library to the processor
// Register the button on the current workspace

demoWorkspace.registerButtonCallback("upload_lib_file_modbus_rtu_v2", async function(button) {
  if (!isEditorConnected()) {
    Swal.fire("Error", "Device is not connected!", "error");
    return;
  }

  try {
    mp().mute_terminal = true;
    term.writeln("Uploading file: modbus_rtu_v2.py");
    await delay(50);

    await mp().sendFile("lib/modbus_rtu_v2.py", modbus_rtu_v2_lib, false);
    await delay(250);
    await mp().sendData('\r\n');
    await delay(250);
    mp().mute_terminal = false;
    await mp().sendData('\r\n');

    Swal.fire("Done", "File was uploaded to the ESP!", "success");
  } catch (e) {
    Swal.fire("Error", e.message, "error");
  }
});

// Library import for generated code
var modbus_rtu_v2_import = `
try:
    import modbus_rtu_v2
except:
    print(terminal_color('Library modbus_rtu_v2.py was not found on the device.',col=91))
    print(terminal_color(' Please upload the library to the device with the button',col=91))
    print(terminal_color('        Upload library to processor',col=91))
`;

// -----------------------
// Python code generator
// -----------------------

Blockly.Python['modbus_rtu_v2_init'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;

  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var unit = Blockly.Python.valueToCode(block, 'UNIT', Blockly.Python.ORDER_ATOMIC) || '1';
  var maxRegs = Blockly.Python.valueToCode(block, 'MAXREG', Blockly.Python.ORDER_ATOMIC) || '64';
  var baseReg = Blockly.Python.valueToCode(block, 'BASE', Blockly.Python.ORDER_ATOMIC) || '0';
  var idleMs = Blockly.Python.valueToCode(block, 'IDLE', Blockly.Python.ORDER_ATOMIC) || '5000';
  var gapMs = Blockly.Python.valueToCode(block, 'GAP', Blockly.Python.ORDER_ATOMIC) || '5';

  var uartId = Blockly.Python.valueToCode(block, 'UART', Blockly.Python.ORDER_ATOMIC) || '1';
  var baud = Blockly.Python.valueToCode(block, 'BAUD', Blockly.Python.ORDER_ATOMIC) || '9600';
  var tx = Blockly.Python.valueToCode(block, 'TX', Blockly.Python.ORDER_ATOMIC) || '17';
  var rx = Blockly.Python.valueToCode(block, 'RX', Blockly.Python.ORDER_ATOMIC) || '16';
  var bits = block.getFieldValue('BITS') || '8';
  var parity = block.getFieldValue('PARITY') || 'None';
  var stop = block.getFieldValue('STOP') || '1';
  var timeout = Blockly.Python.valueToCode(block, 'TIMEOUT', Blockly.Python.ORDER_ATOMIC) || '2';
  var timeoutChar = Blockly.Python.valueToCode(block, 'TIMEOUT_CHAR', Blockly.Python.ORDER_ATOMIC) || '1';
  var debug = (block.getFieldValue('DEBUG') == 'TRUE') ? 'True' : 'False';

  var code = '';
  code += serverVar + ' = modbus_rtu_v2.ModbusRTUServer(unit_id=' + unit + ', max_regs=' + maxRegs + ', base_reg=' + baseReg + ', idle_timeout_ms=' + idleMs + ', frame_gap_ms=' + gapMs + ', debug=' + debug + ')\n';
  code += serverVar + '.init_uart(' + uartId + ', ' + baud + ', ' + tx + ', ' + rx + ', ' + bits + ', ' + parity + ', ' + stop + ', ' + timeout + ', ' + timeoutChar + ')\n';
  code += serverVar + '.start()\n';
  return code;
};

Blockly.Python['modbus_rtu_v2_poll'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var loops = Blockly.Python.valueToCode(block, 'LOOPS', Blockly.Python.ORDER_ATOMIC) || '10';
  return serverVar + '.poll(' + loops + ')\n';
};

Blockly.Python['modbus_rtu_v2_set_reg'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var addr = Blockly.Python.valueToCode(block, 'ADDR', Blockly.Python.ORDER_ATOMIC) || '0';
  var val = Blockly.Python.valueToCode(block, 'VAL', Blockly.Python.ORDER_ATOMIC) || '0';
  return serverVar + '.set_reg(' + addr + ', ' + val + ')\n';
};

Blockly.Python['modbus_rtu_v2_set_reg_s16'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var addr = Blockly.Python.valueToCode(block, 'ADDR', Blockly.Python.ORDER_ATOMIC) || '0';
  var val = Blockly.Python.valueToCode(block, 'VAL', Blockly.Python.ORDER_ATOMIC) || '0';
  return serverVar + '.set_reg_s16(' + addr + ', ' + val + ')\n';
};

Blockly.Python['modbus_rtu_v2_set_reg_f32'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var addr = Blockly.Python.valueToCode(block, 'ADDR', Blockly.Python.ORDER_ATOMIC) || '0';
  var val = Blockly.Python.valueToCode(block, 'VAL', Blockly.Python.ORDER_ATOMIC) || '0.0';
  return serverVar + '.set_reg_f32(' + addr + ', ' + val + ')\n';
};

Blockly.Python['modbus_rtu_v2_set_regs'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var addr = Blockly.Python.valueToCode(block, 'ADDR', Blockly.Python.ORDER_ATOMIC) || '0';
  var vals = Blockly.Python.valueToCode(block, 'LIST', Blockly.Python.ORDER_ATOMIC) || '[]';
  return serverVar + '.set_regs(' + addr + ', ' + vals + ')\n';
};

Blockly.Python['modbus_rtu_v2_get_reg'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var addr = Blockly.Python.valueToCode(block, 'ADDR', Blockly.Python.ORDER_ATOMIC) || '0';
  var defv = Blockly.Python.valueToCode(block, 'DEF', Blockly.Python.ORDER_ATOMIC) || '0';
  var code = serverVar + '.get_reg(' + addr + ', ' + defv + ')';
  return [code, Blockly.Python.ORDER_ATOMIC];
};

Blockly.Python['modbus_rtu_v2_get_reg_s16'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var addr = Blockly.Python.valueToCode(block, 'ADDR', Blockly.Python.ORDER_ATOMIC) || '0';
  var defv = Blockly.Python.valueToCode(block, 'DEF', Blockly.Python.ORDER_ATOMIC) || '0';
  var code = serverVar + '.get_reg_s16(' + addr + ', ' + defv + ')';
  return [code, Blockly.Python.ORDER_ATOMIC];
};

Blockly.Python['modbus_rtu_v2_get_reg_f32'] = function(block) {
  Blockly.Python.definitions_['import_modbus_rtu_v2'] = modbus_rtu_v2_import;
  var serverVar = Blockly.Python.nameDB_.getName(block.getFieldValue('SERVER'), Blockly.Variables.NAME_TYPE);
  var addr = Blockly.Python.valueToCode(block, 'ADDR', Blockly.Python.ORDER_ATOMIC) || '0';
  var defv = Blockly.Python.valueToCode(block, 'DEF', Blockly.Python.ORDER_ATOMIC) || '0.0';
  var code = serverVar + '.get_reg_f32(' + addr + ', ' + defv + ')';
  return [code, Blockly.Python.ORDER_ATOMIC];
};

// -----------------------
// Block definitions
// -----------------------

Blockly.Blocks['modbus_rtu_v2_init'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('initialize Modbus RTU server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER');
    this.appendValueInput('UNIT').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('modbus address');
    this.appendValueInput('BASE').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('first register');
    this.appendValueInput('MAXREG').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('register count');
    this.appendValueInput('UART').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('uart');
    this.appendValueInput('BAUD').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('baud rate');
    this.appendDummyInput()
        .appendField('bits')
        .appendField(new Blockly.FieldDropdown([['8','8'],['7','7']]), 'BITS')
        .appendField('parity')
        .appendField(new Blockly.FieldDropdown([['none','None'],['even','0'],['odd','1']]), 'PARITY')
        .appendField('stop bit')
        .appendField(new Blockly.FieldDropdown([['1','1'],['2','2']]), 'STOP')
        .appendField('debug')
        .appendField(new Blockly.FieldCheckbox('FALSE'), 'DEBUG');
    this.appendValueInput('TX').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('TX Pin');
    this.appendValueInput('RX').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('RX Pin');
    this.appendValueInput('GAP').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('line switch delay [ms]');
    this.appendValueInput('IDLE').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('frame timeout [ms]');
    this.appendValueInput('TIMEOUT').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('uart timeout [ms]');
    this.appendValueInput('TIMEOUT_CHAR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('character timeout [ms]');
    
    this.setInputsInline(false);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Full server and UART initialization, then start.');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_poll'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('handle Modbus (poll)');
    this.appendValueInput('LOOPS')
        .setCheck('Number')
        .setAlign(Blockly.ALIGN_RIGHT)
        .appendField('max processed frames');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Processes Modbus RTU requests from UART (without a thread).');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_set_reg'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('set register');
    this.appendValueInput('ADDR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('address');
    this.appendValueInput('VAL').setCheck(null).setAlign(Blockly.ALIGN_RIGHT).appendField('value [0 to 65535]');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Sets the register value (0-65535).');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_set_reg_s16'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('set s16');
    this.appendValueInput('ADDR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('address');
    this.appendValueInput('VAL').setCheck(null).setAlign(Blockly.ALIGN_RIGHT).appendField('value [-32767 to 32767]');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Sets a signed value -32768..32767.');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_set_reg_f32'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('set float');
    this.appendValueInput('ADDR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('address');
    this.appendValueInput('VAL').setCheck(null).setAlign(Blockly.ALIGN_RIGHT).appendField('value [decimal number]');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Sets Float32 into 2 registers (addr, addr+1).');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_set_regs'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('set registers');
    this.appendValueInput('ADDR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('address');
    this.appendValueInput('LIST').setCheck(null).setAlign(Blockly.ALIGN_RIGHT).appendField('values [0 to 65535]');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Sets multiple registers from a list.');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_get_reg'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('read register [0 to 65535]');
    this.appendValueInput('ADDR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('address');
    this.appendValueInput('DEF').setCheck(null).setAlign(Blockly.ALIGN_RIGHT).appendField('default');
    this.setOutput(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Returns the register value.');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_get_reg_s16'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('read [-32767 to 32767]');
    this.appendValueInput('ADDR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('address');
    this.appendValueInput('DEF').setCheck(null).setAlign(Blockly.ALIGN_RIGHT).appendField('default');
    this.setOutput(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Returns a signed value -32768..32767.');
    this.setHelpUrl('');
  }
};

Blockly.Blocks['modbus_rtu_v2_get_reg_f32'] = {
  init: function() {
    this.appendDummyInput()
        .appendField('server')
        .appendField(new Blockly.FieldVariable('mb_rtu'), 'SERVER')
        .appendField('read float');
    this.appendValueInput('ADDR').setCheck('Number').setAlign(Blockly.ALIGN_RIGHT).appendField('address');
    this.appendValueInput('DEF').setCheck(null).setAlign(Blockly.ALIGN_RIGHT).appendField('default');
    this.setOutput(true, null);
    this.setColour('#7a4a7c');
    this.setTooltip('Reads Float32 from 2 registers (addr, addr+1).');
    this.setHelpUrl('');
  }
};

<!toolbox!>

<category name="Modbus RTU v2" colour="#7a4a7c">
  <label text="Before first use, upload the library to the processor."></label>
  <button text="Upload library to processor" callbackKey="upload_lib_file_modbus_rtu_v2"></button>

  <block type="modbus_rtu_v2_init">
    <field name="SERVER">mb_rtu</field>
    <value name="UNIT"><shadow type="math_number"><field name="NUM">1</field></shadow></value>
    <value name="MAXREG"><shadow type="math_number"><field name="NUM">64</field></shadow></value>
    <value name="BASE"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
    <value name="IDLE"><shadow type="math_number"><field name="NUM">25</field></shadow></value>
    <value name="GAP"><shadow type="math_number"><field name="NUM">5</field></shadow></value>
    <value name="UART"><shadow type="math_number"><field name="NUM">1</field></shadow></value>
    <value name="BAUD"><shadow type="math_number"><field name="NUM">9600</field></shadow></value>
    <value name="TX"><shadow type="math_number"><field name="NUM">17</field></shadow></value>
    <value name="RX"><shadow type="math_number"><field name="NUM">16</field></shadow></value>
    <value name="TIMEOUT"><shadow type="math_number"><field name="NUM">4</field></shadow></value>
    <value name="TIMEOUT_CHAR"><shadow type="math_number"><field name="NUM">2</field></shadow></value>
    <field name="BITS">8</field>
    <field name="PARITY">None</field>
    <field name="STOP">1</field>
    <field name="DEBUG">FALSE</field>
  </block>

  <block type="modbus_rtu_v2_poll">
    <field name="SERVER">mb_rtu</field>
    <value name="LOOPS"><shadow type="math_number"><field name="NUM">10</field></shadow></value>
  </block>

  <label text="Register operations"></label>
  <block type="modbus_rtu_v2_set_reg">
    <field name="SERVER">mb_rtu</field>
    <value name="ADDR"><shadow type="math_number"><field name="NUM">8</field></shadow></value>
    <value name="VAL"><shadow type="math_number"><field name="NUM">123</field></shadow></value>
  </block>
  <block type="modbus_rtu_v2_set_reg_s16">
    <field name="SERVER">mb_rtu</field>
    <value name="ADDR"><shadow type="math_number"><field name="NUM">8</field></shadow></value>
    <value name="VAL"><shadow type="math_number"><field name="NUM">-123</field></shadow></value>
  </block>
  <block type="modbus_rtu_v2_set_reg_f32">
    <field name="SERVER">mb_rtu</field>
    <value name="ADDR"><shadow type="math_number"><field name="NUM">12</field></shadow></value>
    <value name="VAL"><shadow type="math_number"><field name="NUM">1.25</field></shadow></value>
  </block>
  	<block type="modbus_rtu_v2_set_regs">
		<field name="SERVER">mb_rtu</field>
		<value name="ADDR">
			<shadow type="math_number">
				<field name="NUM">20</field>
			</shadow>
		</value>
		<value name="LIST">
			<shadow type="lists_create_with">
				<mutation items="3"></mutation>
				<value name="ADD0">
					<shadow type="math_number">
						<field name="NUM">1</field>
					</shadow>
				</value>
				<value name="ADD1">
					<shadow type="math_number">
						<field name="NUM">2</field>
					</shadow>
				</value>
				<value name="ADD2">
					<shadow type="math_number">
						<field name="NUM">3</field>
					</shadow>
				</value>
			</shadow>
			<block type="lists_create_with">
				<mutation items="3"></mutation>
				<value name="ADD0">
					<block type="math_number">
						<field name="NUM">123</field>
					</block>
				</value>
				<value name="ADD1">
					<block type="math_number">
						<field name="NUM">456</field>
					</block>
				</value>
				<value name="ADD2">
					<block type="math_number">
						<field name="NUM">789</field>
					</block>
				</value>
			</block>
		</value>
	</block>
  <block type="modbus_rtu_v2_get_reg">
    <field name="SERVER">mb_rtu</field>
    <value name="ADDR"><shadow type="math_number"><field name="NUM">9</field></shadow></value>
    <value name="DEF"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
  </block>
  <block type="modbus_rtu_v2_get_reg_s16">
    <field name="SERVER">mb_rtu</field>
    <value name="ADDR"><shadow type="math_number"><field name="NUM">9</field></shadow></value>
    <value name="DEF"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
  </block>
  <block type="modbus_rtu_v2_get_reg_f32">
    <field name="SERVER">mb_rtu</field>
    <value name="ADDR"><shadow type="math_number"><field name="NUM">12</field></shadow></value>
    <value name="DEF"><shadow type="math_number"><field name="NUM">0.0</field></shadow></value>
  </block>
</category>
