--- /dev/null
+# -*- coding: utf-8 -*-
+
+__all__ = ('DatatypesProtocol')
+
+
+import random
+import struct
+
+from typing import Mapping
+
+from .transport import ClientInterface
+
+
+class DatatypesProtocol():
+
+ def __init__(self, client: ClientInterface, coils, registers) -> None:
+ self.__client = client
+ self.__coils = coils
+ self.__registers = registers
+
+ async def read_coil(self, coil: int) -> bool:
+ assert coil in self.__coils, 'unknown coil'
+
+ res = await self.__client.read_coils(coil - 1, 1)
+ return res[0]
+
+ async def write_coil(self, coil: int, value: bool) -> None:
+ assert coil in self.__coils, 'unknown coil'
+
+ await self.__client.write_single_coil(coil - 1, value)
+
+ async def read_register(self, register: int) -> float:
+ assert register in self.__registers, 'unknown register'
+
+ rw = self.__registers[register]['rw']
+ assert rw is 'r' or rw is 'rw', 'register is not readable'
+
+ rType = self.__registers[register]['type']
+ rConf = self.__registers[register]['conf']
+ if rType == 'uint16':
+ return await self.__read_uint16_register(register, **rConf)
+ elif rType == 'uint32':
+ return await self.__read_uint32_register(register, **rConf)
+ elif rType == 'int32':
+ return await self.__read_int32_register(register, **rConf)
+ elif rType == 'enum':
+ return await self.__read_enum_register(register, **rConf)
+ else:
+ assert False, 'unknown register type'
+
+
+ async def write_register(self, register: int, value: int) -> None:
+ assert register in self.__registers, 'unknown register'
+
+ rw = self.__registers[register]['rw']
+ assert rw is 'w' or rw is 'rw', 'register is not writable'
+
+ rType = self.__registers[register]['type']
+ rConf = self.__registers[register]['conf']
+ if rType == 'uint16':
+ await self.__write_uint16_register(register, value, **rConf)
+ elif rType == 'uint32':
+ await self.__write_uint32_register(register, value, **rConf)
+ elif rType == 'int32':
+ await self.__write_int32_register(register, value, **rConf)
+ elif rType == 'enum':
+ await self.__write_enum_register(register, value, ** rConf)
+ else:
+ assert False, 'unknown register type'
+
+ async def __read_uint16_register(self, register: int, min: int, max: int, unit: str, scale: int)-> float:
+ res = await self.__client.read_holding_registers(register-1, 1)
+ res = res[0] / (1/scale)
+
+ assert res >= min and res <= max
+ return res
+
+ async def __write_uint16_register(self, register: int, value: float, min: int, max: int, unit: str, scale: int) -> None:
+ assert value >= min and value <= max
+
+ await self.__client.write_multiple_registers(register-1, [int(value * (1/scale))])
+
+ async def __read_uint32_register(self, register: int, min: int, max: int, unit: str, scale: int) -> float:
+ res = await self.__client.read_holding_registers(register-1, 2)
+ res = struct.pack('>HH', res[0], res[1])
+ res = struct.unpack('>L', res)
+ res = res[0] / (1/scale)
+
+ assert res >= min and res <= max
+ return res
+
+ async def __write_uint32_register(self, register: int, value: float, min: int, max: int, unit: str, scale: int) -> None:
+ assert value >= min and value <= max
+
+ req = int(value * (1/scale))
+ req = struct.pack('>L', req)
+ req = struct.unpack('>HH', req)
+
+ await self.__client.write_multiple_registers(register-1, [x for x in req])
+
+ async def __read_int32_register(self, register: int, min: int, max: int, unit: str, scale: int) -> float:
+ res = await self.__client.read_holding_registers(register-1, 2)
+
+ res = struct.pack('>HH', res[0], res[1])
+ res = struct.unpack('>l', res)
+ res = res[0] / (1/scale)
+
+ assert res >= min and res <= max
+ return res
+
+ async def __write_int32_register(self, register: int, value: float, min: int, max: int, unit: str, scale: int) -> None:
+ assert value >= min and value <= max
+
+ req = int(value * (1/scale))
+ req = struct.pack('>l', req)
+ req = struct.unpack('>HH', req)
+
+ await self.__client.write_multiple_registers(register-1, [x for x in req])
+
+ async def __read_enum_register(self, register: int, values: Mapping[int, str]) -> int:
+ res = await self.__client.read_holding_registers(register - 1, 1)
+ res = res[0]
+
+ assert res in values
+ return res
+
+ async def __write_enum_register(self, register: int, value: int, values: Mapping[int, str]) -> None:
+ assert value in values
+
+ await self.__client.write_multiple_registers(register-1, [value])
+
+ async def loopback_test(self, testvalue: int = None) -> None:
+ if testvalue is not None:
+ assert testvalue >= 0 and testvalue < 2 ^ 16
+ else:
+ testvalue = random.randrange(2 ^ 16)
+
+ await self.__client.loopback_test(testvalue)
--- /dev/null
+# -*- coding: utf-8 -*-
+
+__all__ = ('SJP1Fu')
+
+
+from . import DatatypesProtocol, ClientInterface
+
+
+class SJP1Fu():
+
+ def __init__(self, client: ClientInterface) -> None:
+ self.__protocol = DatatypesProtocol(client, SJP1FU_COILS, SJP1FU_REGISTERS)
+
+ async def set_frequency(self, frequency: int) -> None:
+ # Page 532 / 14-41
+ await self.__protocol.write_register(10502, frequency)
+
+ async def get_frequency(self) -> int:
+ # Page 532 / 14-41
+ return int(await self.__protocol.read_register(10502))
+
+ async def start_inverter(self) -> None:
+ # Page 518 / 14-27
+ await self.__protocol.write_coil(1, True)
+
+ async def stop_inverter(self) -> None:
+ # Page 518 / 14-27
+ await self.__protocol.write_coil(1, False)
+
+ @property
+ async def inverter_active(self) -> bool:
+ return await self.__protocol.read_coil(1)
+
+
+SJP1FU_COILS = {
+ 1: {
+ 'name': 'Operation Command',
+ 'rw': 'rw',
+ 'values': [ 'Stop', 'Run' ]
+ }
+}
+
+
+SJP1FU_REGISTERS = {
+ 10001: {
+ 'code': 'dA-01',
+ 'name': 'Output frequency monitor',
+ 'rw': 'r',
+ 'type': 'uint16',
+ 'conf': {
+ 'min': 0,
+ 'max': 590,
+ 'unit': 'Hz',
+ 'scale': 0.01,
+ },
+ },
+
+ 10003: {
+ 'code': 'dA-03',
+ 'name': 'Operation direction monitor',
+ 'rw': 'r',
+ 'type': 'enum',
+ 'conf': {
+ 'values': {
+ 0: 'Stop',
+ 1: 'Zero-speed out',
+ 2: 'Forward run',
+ 3: 'Reverse run',
+ }
+ }
+ },
+
+ 10502: {
+ 'name': 'Set Frequency',
+ 'rw': 'rw',
+ 'type': 'int32',
+ 'conf': {
+ 'min': -590,
+ 'max': 590,
+ 'unit': 'Hz',
+ 'scale': 0.01,
+ },
+ },
+
+ 11010: {
+ 'code': 'FA-10',
+ 'name': 'Acceleration time (monitor + setting)',
+ 'rw': 'rw',
+ 'type': 'uint32',
+ 'conf': {
+ 'min': 0,
+ 'max': 3600,
+ 'unit': 's',
+ 'scale': 0.01,
+ },
+ },
+ 11012: {
+ 'code': 'FA-12',
+ 'name': 'Deceleration time (monitor + setting)',
+ 'rw': 'rw',
+ 'type': 'uint32',
+ 'conf': {
+ 'min': 0,
+ 'max': 3600,
+ 'unit': 's',
+ 'scale': 0.01,
+ },
+ },
+
+ 12501: {
+ 'code': 'AF101',
+ 'name': 'First DC braking selection',
+ 'rw': 'rw',
+ 'type': 'enum',
+ 'conf': {
+ 'values': {
+# 0: 'Stop',
+# 1: 'Zero-speed out',
+# 2: 'Forward run',
+# 3: 'Reverse run',
+ }
+ }
+ },
+}
--- /dev/null
+# -*- coding: utf-8 -*-
+
+__all__ = ('ClientInterface', 'SerialPort', 'SerialClient')
+
+
+import os
+import abc
+import fcntl
+import struct
+import asyncio
+
+from typing import List
+
+from umodbus.client.serial import rtu
+from umodbus.functions import expected_response_pdu_size_from_request_pdu
+from umodbus.client.serial.redundancy_check import add_crc
+
+
+class ClientInterface():
+
+ @abc.abstractmethod
+ async def read_coils(self, starting_address: int, quantity: int) -> List[bool]:
+ ...
+
+ @abc.abstractmethod
+ async def read_discrete_inputs(self, starting_address: int, quantity: int) -> List[bool]:
+ ...
+
+ @abc.abstractmethod
+ async def read_holding_registers(self, starting_address: int, quantity: int) -> List[int]:
+ ...
+
+ @abc.abstractmethod
+ async def read_input_registers(self, starting_address: int, quantity: int) -> List[int]:
+ ...
+
+ @abc.abstractmethod
+ async def write_single_coil(self, address: int, value: bool) -> None:
+ ...
+
+ @abc.abstractmethod
+ async def write_single_register(self, address: int, value: int) -> None:
+ ...
+
+ @abc.abstractmethod
+ async def write_multiple_coils(self, starting_address: int, values: List[bool]) -> None:
+ ...
+
+ @abc.abstractmethod
+ async def write_multiple_registers(self, starting_address: int, values: List[int]) -> None:
+ ...
+
+ @abc.abstractmethod
+ async def loopback_test(self, value: int) -> int:
+ ...
+
+
+class SerialPort():
+
+ def __init__(self, filename: str) -> None:
+ self.__file = os.open(filename, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
+
+ self.__read_lock = asyncio.Lock()
+ self.__write_lock = asyncio.Lock()
+
+ fcntl.fcntl(self.__file, fcntl.F_SETFL, os.O_NONBLOCK)
+
+ async def __read(self, size: int = 1) -> bytes:
+ assert size > 0
+
+ loop = asyncio.get_event_loop()
+ future = loop.create_future()
+
+ loop.add_reader(self.__file, lambda: future.set_result(os.read(self.__file, size)))
+ future.add_done_callback(lambda _f: loop.remove_reader(self.__file))
+
+ return await future
+
+ async def read(self, size: int = 1) -> bytes:
+ assert size > 0
+
+ async with self.__read_lock:
+ buffer = b""
+ while size > 0:
+ data = await self.__read(size)
+ if data is b"":
+ return buffer
+
+ buffer += data
+ size -= len(data)
+
+ return buffer
+
+ async def __write(self, data: bytes) -> int:
+ assert len(data) > 0
+
+ loop = asyncio.get_event_loop()
+ future = loop.create_future()
+
+ loop.add_writer(self.__file, lambda: future.set_result(os.write(self.__file, data)))
+ future.add_done_callback(lambda _f: loop.remove_writer(self.__file))
+
+ return await future
+
+ async def write(self, data: bytes) -> int:
+ assert len(data) > 0
+
+ async with self.__write_lock:
+ size = 0
+ while len(data) > 0:
+ written = await self.__write(data)
+ if written is 0:
+ return size
+
+ data = data[written:]
+ size += written
+
+ return size
+
+
+class SerialClient(ClientInterface):
+
+ def __init__(self, port: SerialPort, slave_id: int) -> None:
+ self.__port = port
+ self.__slave_id = slave_id
+
+ async def read_coils(self, starting_address: int, quantity: int) -> List[bool]:
+ response = await self.__send_message(rtu.read_coils(self.__slave_id, starting_address, quantity))
+ response = [bool(x) for x in response]
+
+ return response
+
+ async def read_discrete_inputs(self, starting_address: int, quantity: int) -> List[bool]:
+ response = await self.__send_message(rtu.read_discrete_inputs(self.__slave_id, starting_address, quantity))
+ response = [bool(x) for x in response]
+
+ return response
+
+ async def read_holding_registers(self, starting_address: int, quantity: int) -> List[int]:
+ return await self.__send_message(rtu.read_holding_registers(self.__slave_id, starting_address, quantity))
+
+ async def read_input_registers(self, starting_address: int, quantity: int) -> List[int]:
+ return await self.__send_message(rtu.read_input_registers(self.__slave_id, starting_address, quantity))
+
+ async def write_single_coil(self, address: int, value: bool) -> None:
+ await self.__send_message(rtu.write_single_coil(self.__slave_id, address, int(value)))
+
+ async def write_single_register(self, address: int, value: int) -> None:
+ await self.__send_message(rtu.write_single_register(self.__slave_id, address, value))
+
+ async def write_multiple_coils(self, starting_address: int, values: List[bool]) -> None:
+ await self.__send_message(rtu.write_multiple_coils(self.__slave_id, starting_address, [int(x) for x in values]))
+
+ async def write_multiple_registers(self, starting_address: int, values: List[int]) -> None:
+ await self.__send_message(rtu.write_multiple_registers(self.__slave_id, starting_address, values))
+
+ async def loopback_test(self, value: int) -> int:
+ assert 0 <= value < 2 ^ 16
+
+ req = struct.pack('>BBHH', 1, 8, 0, value)
+ req = add_crc(req)
+
+ await self.__port.write(req)
+
+ res = await self.__port.read(len(req))
+ assert req == res
+
+ return value
+
+ async def __send_message(self, message: bytes) -> List[int]:
+ await self.__port.write(message)
+
+ # Check exception ADU (which is shorter than all other responses) first.
+ exception_adu_size = 5
+ response_error_adu = await self.__port.read(exception_adu_size)
+ rtu.raise_for_exception_adu(response_error_adu)
+
+ expected_response_size = \
+ expected_response_pdu_size_from_request_pdu(message[1:-2]) + 3
+ response_remainder = await self.__port.read(expected_response_size - exception_adu_size)
+
+ if len(response_remainder) < expected_response_size - exception_adu_size:
+ raise ValueError
+
+ result = rtu.parse_response_adu(response_error_adu + response_remainder, message)
+
+ if not isinstance(result, list):
+ return [result]
+
+ return result