├── .gitignore ├── LICENSE ├── LUX_MAX44009 ├── lux_max44009.py └── readme.md ├── README.md ├── SHT20 ├── README.md └── sht20.py └── minimalmodbus ├── README.md └── minimalmodbus.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | .venv/ 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | # Vim temp file 93 | *.swp 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 OOPY开源团队 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LUX_MAX44009/lux_max44009.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | 4 | from machine import Pin, I2C 5 | 6 | 7 | MAX44009_ADDRESS = 74 8 | MAX44009_CONFIG = 0x00 9 | 10 | 11 | class MAX44009(object): 12 | 13 | """ MAX44009 光照传感器 """ 14 | 15 | def __init__(self, scl_pin=16, sda_pin=17, freq=400000): 16 | """ 初始化i2c及max44009 """ 17 | self._address = MAX44009_ADDRESS 18 | self.scl = Pin(scl_pin) 19 | self.sda = Pin(sda_pin) 20 | self.freq = freq 21 | self._max44009_config = bytearray() 22 | self._max44009_config.append(MAX44009_CONFIG) 23 | self._i2c = I2C(scl=self.scl, sda=self.sda, freq=self.freq) 24 | self._i2c.writeto(self._address, self._max44009_config) 25 | 26 | def _get_origin_data(self, accuracy=False): 27 | """ 获取原始数据 """ 28 | origin_data = bytearray() 29 | self._i2c.writeto(self._address, b'\x03') 30 | origin_data += self._i2c.readfrom(self._address, 1) 31 | if accuracy: 32 | self._i2c.writeto(self._address, b'\x04') 33 | origin_data += self._i2c.readfrom(self._address, 1) 34 | 35 | return origin_data 36 | 37 | def get_lux(self, accuracy=False): 38 | """ 转换为单位为lux的自然数 """ 39 | if not accuracy: 40 | origin_data = self._get_origin_data() 41 | exponent = (origin_data[0] & 0xf0) >> 4 42 | mantissa = origin_data[0] & 0x0f 43 | return 2**exponent * mantissa * 0.72 44 | else: 45 | origin_data = self._get_origin_data(accuracy) 46 | exponent = (origin_data[0] & 0xf0) >> 4 47 | mantissa = ((origin_data[0] & 0x0f) << 4) + (origin_data[1] & 0x0f) 48 | return 2**exponent * mantissa * 0.045 49 | -------------------------------------------------------------------------------- /LUX_MAX44009/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MAX44009 光照传感器 3 | tags: python,lib,sensor 4 | author: [Huifeng](https://github.com/sgrrzhf) 5 | --- 6 | 7 | # 类 MAX44009 8 | ## 初始化参数 9 | 10 | ``scl_pin`` I2C的scl引脚序号,默认为16 11 | 12 | ``sda_pin`` I2C的sda引脚序号,默认为17 13 | 14 | ``freq`` I2C的时钟频率,默认为100k 15 | 16 | ## 函数 17 | `` get_lux() `` 18 | ### 功能: 获取光照强度数据 19 | ### 参数: 20 | `accuracy` 设置读取的精度 21 | `accuracy = True`时,读取的数据为高精度,反之为低精度,默认为`False` 22 | ### 返回: 浮点数,光照强度 23 | 24 | 25 | ## 范例 26 | ```python 27 | >>> from lux_max44009 import MAX44009 28 | >>> 29 | >>> device = MAX44009() 30 | >>> lux = device.get_lux() 31 | >>> print("lux:",lux) 32 | lux: 63.36 33 | >>> accuracy_lux = device.get_lux(accuracy=True) 34 | >>> print("lux:",accuracy_lux) 35 | lux: 64.08 36 | >>> 37 | 38 | ``` 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | oopy_lib 2 | ========== 3 | #### oopy open source team driver library 4 | #### ``oopy`` 开源团队驱动库 5 | 6 | ### 文件形式 7 | ``` 8 | . 9 | ├── LICENSE 10 | └── README.md 11 | └── xxx 12 | └── xxx.py 13 | └── README.md 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /SHT20/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SHT20 3 | tags: python,lib,sensor 4 | author: [Huifeng](https://github.com/sgrrzhf) 5 | --- 6 | 7 | # 类SHT20 8 | ## 初始化参数 9 | 10 | ``scl_pin`` I2C的scl引脚序号,默认为16 11 | 12 | ``sda_pin`` I2C的sda引脚序号,默认为17 13 | 14 | ``clk_freq`` I2C的时钟频率,默认为100k 15 | 16 | ## 函数 17 | `` get_temperature()`` 18 | 功能:获取温度数据 19 | 返回:float型变量,温度 20 | 21 | ``get_relative_humidity()`` 22 | 功能:获取相对湿度 23 | 返回值:float型变量,相对湿度 24 | 25 | ## 范例 26 | ```python 27 | >>> from sht20 import SHT20 28 | >>> sht_sensor = SHT20() 29 | >>> T = sht_sensor.get_temperature() 30 | >>> RH = sht_sensor.get_relative_humidity() 31 | >>> print('temperature:', T) 32 | temperature: 26.423867 33 | >>> print('relative_humidity:', RH) 34 | relative_humidity: 45.738740 35 | ``` 36 | -------------------------------------------------------------------------------- /SHT20/sht20.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, I2C 2 | from struct import unpack as unp 3 | from time import sleep_ms 4 | 5 | # SHT20 default address 6 | SHT20_I2CADDR = 64 7 | 8 | # SHT20 Command 9 | TRI_T_MEASURE_NO_HOLD = b'\xf3' 10 | TRI_RH_MEASURE_NO_HOLD = b'\xf5' 11 | READ_USER_REG = b'\xe7' 12 | WRITE_USER_REG = b'\xe6' 13 | SOFT_RESET = b'\xfe' 14 | 15 | 16 | class SHT20(object): 17 | 18 | def __init__(self, scl_pin=16, sda_pin=17, clk_freq=100000): 19 | self._address = SHT20_I2CADDR 20 | 21 | pin_c = Pin(scl_pin) 22 | pin_d = Pin(sda_pin) 23 | self._bus = I2C(scl=pin_c, sda=pin_d, freq=clk_freq) 24 | 25 | def get_temperature(self): 26 | self._bus.writeto(self._address, TRI_T_MEASURE_NO_HOLD) 27 | sleep_ms(150) 28 | origin_data = self._bus.readfrom(self._address, 2) 29 | origin_value = unp('>h', origin_data)[0] 30 | value = -46.85 + 175.72 * (origin_value / 65536) 31 | return value 32 | 33 | def get_relative_humidity(self): 34 | self._bus.writeto(self._address, TRI_RH_MEASURE_NO_HOLD) 35 | sleep_ms(150) 36 | origin_data = self._bus.readfrom(self._address, 2) 37 | origin_value = unp('>H', origin_data)[0] 38 | value = -6 + 125 * (origin_value / 65536) 39 | return value 40 | -------------------------------------------------------------------------------- /minimalmodbus/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: minimalmodbus 3 | tags: micropython,lib 4 | author: [Jonas Berg](https://github.com/pyhys/minimalmodbus) 5 | author: [Huifeng](https://github.com/sgrrzhf) 6 | --- 7 | # 类Instrument 8 | 9 | ## 初始化参数 10 | 参数 | 说明 11 | --- | --- 12 | `port` | UART模块号 13 | `slaveaddress` | 从机地址,范围1~247 14 | `stopbits` | UART停止位,默认1 15 | `bytesizes` | UART数据位,默认8 16 | `parity` | UART校验位,默认None 17 | `baudrate` | UART波特率,默认9600 18 | `timeout` | UART读超时时间,默认1000 ms 19 | 20 | ## 函数 21 | * `write_register()`: 写单个寄存器 22 | * **参数** 23 | * `registeraddress`: 寄存器地址 24 | * `value`: 待写入的值 25 | * **返回值** 26 | * None 27 | 28 | --- 29 | * `read_registers()`: 读寄存器 30 | * **参数** 31 | * `registeraddress`: 起始地址 32 | * `numberOfRegisters`: 待读取的数量 33 | * `functioncode`: 功能码,可选3,4,默认3 34 | 35 | * **返回值** 36 | * 读取的一组寄存器值,list类型 37 | --- 38 | * `write_registers()`: 写多个寄存器 39 | * **参数** 40 | * `registeraddress`: 起始地址 41 | * `values`: 一组待写入的数据,list类型 42 | * **返回值** 43 | * None 44 | --- 45 | 46 | ## 范例 47 | ```bash 48 | >>> from minimalmodbus import Instrument 49 | >>> device = Instrument(3,2) 50 | >>> registers = device.read_registers(0,2,functioncode = 4) 51 | >>> temperature = registers[0]/10 52 | >>> r_humidity = registers[1]/10 53 | >>> print('\rtemperature: %0.1f, r_humidity: %0.1f%%'%(temperature,r_humidity)) 54 | temperature: 29.6, r_humidity: 47.9% 55 | 56 | >>> device.write_register(1,2468) 57 | >>> print(device.read_registers(1,1)) 58 | [2468] 59 | 60 | >>> value = [1,2,3,4,1122,3344] 61 | >>> device.write_registers(4,value) 62 | >>> print(device.read_registers(4,6)) 63 | [1, 2, 3, 4, 1122, 3344] 64 | ``` 65 | -------------------------------------------------------------------------------- /minimalmodbus/minimalmodbus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright 2015 Jonas Berg 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | #.. moduleauthor:: Jonas Berg 19 | #MinimalModbus: A Python driver for the Modbus RTU and Modbus ASCII protocols via serial port (via RS485 or RS232). 20 | 21 | # 修改内容: 精简部分内容并移植到micropython平台 22 | 23 | #__author__ = 'Jonas Berg' 24 | #__修改者__ = 'huifeng' 25 | #__email__ = 'pyhys@users.sourceforge.net' 26 | #__url__ = 'https://github.com/pyhys/minimalmodbus' 27 | #__license__ = 'Apache License, Version 2.0' 28 | 29 | #__version__ = '0.7' 30 | #__status__ = 'Beta' 31 | 32 | 33 | import os 34 | from pyb import UART 35 | import struct 36 | import sys 37 | import time 38 | 39 | # Allow long also in Python3 40 | # http://python3porting.com/noconv.html 41 | _NUMBER_OF_BYTES_PER_REGISTER = 2 42 | _SECONDS_TO_MILLISECONDS = 1 43 | 44 | # Several instrument instances can share the same serialport 45 | _LATEST_READ_TIMES = {} 46 | 47 | #################### 48 | ## Default values ## 49 | #################### 50 | 51 | BAUDRATE = 9600 52 | #"""Default value for the baudrate in Baud (int).""" 53 | 54 | PARITY = None 55 | #"""Default value for the parity. See the pySerial module for documentation. Defaults to serial.PARITY_NONE""" 56 | 57 | BYTESIZE = 8 58 | #"""Default value for the bytesize (int).""" 59 | 60 | STOPBITS = 1 61 | #"""Default value for the number of stopbits (int).""" 62 | 63 | TIMEOUT = 1000 64 | #"""Default value for the timeout value in seconds (float).""" 65 | 66 | 67 | ##################### 68 | ## Named constants ## 69 | ##################### 70 | 71 | MODE_RTU = 'rtu' 72 | 73 | ############################## 74 | ## Modbus instrument object ## 75 | ############################## 76 | 77 | 78 | class Instrument(): 79 | # """Instrument class for talking to instruments (slaves) via the Modbus RTU or ASCII protocols (via RS485 or RS232). 80 | 81 | # Args: 82 | # * port (str): The serial port name, for example ``/dev/ttyUSB0`` (Linux), ``/dev/tty.usbserial`` (OS X) or ``COM4`` (Windows). 83 | # * slaveaddress (int): Slave address in the range 1 to 247 (use decimal numbers, not hex). 84 | # * mode (str): Mode selection. Can be MODE_RTU or MODE_ASCII. 85 | 86 | # """ 87 | 88 | def __init__(self, port, slaveaddress, mode=MODE_RTU, **kwargs): 89 | self.port = port 90 | self.stopbits = kwargs.get('stopbits', STOPBITS) 91 | self.bytesize = kwargs.get('bytesize', BYTESIZE) 92 | self.parity = kwargs.get('parity', PARITY) 93 | self.baudrate = kwargs.get('baudrate', BAUDRATE) 94 | self.timeout = kwargs.get('timeout', TIMEOUT) 95 | 96 | self.serial = UART(self.port, self.baudrate) 97 | self.serial.init(self.baudrate, bits = self.bytesize, stop = self.stopbits, 98 | timeout = self.timeout, parity = self.parity) 99 | self.address = slaveaddress 100 | 101 | self.mode = mode 102 | 103 | self.debug = False 104 | 105 | self.precalculate_read_size = True 106 | 107 | self.handle_local_echo = False 108 | 109 | def __repr__(self): 110 | return "{}.{}".format( 111 | self.__module__, 112 | self.__class__.__name__, 113 | id(self), 114 | self.address, 115 | self.mode, 116 | self.precalculate_read_size, 117 | self.debug, 118 | self.serial, 119 | ) 120 | 121 | ###################################### 122 | ## Methods for talking to the slave ## 123 | ###################################### 124 | 125 | def write_register(self, registeraddress, value, signed=False): 126 | _checkBool(signed, description='signed') 127 | _checkNumerical(value, description='input value') 128 | 129 | self._genericCommand(6, registeraddress, value, signed=signed) 130 | 131 | 132 | def read_registers(self, registeraddress, numberOfRegisters, functioncode=3): 133 | _checkFunctioncode(functioncode, [3, 4]) 134 | _checkInt(numberOfRegisters, minvalue=1, description='number of registers') 135 | return self._genericCommand(functioncode, registeraddress, \ 136 | numberOfRegisters=numberOfRegisters, payloadformat='registers') 137 | 138 | 139 | def write_registers(self, registeraddress, values): 140 | if not isinstance(values, list): 141 | raise TypeError('The "values parameter" must be a list. Given: {0!r}'.format(values)) 142 | _checkInt(len(values), minvalue=1, description='length of input list') 143 | # Note: The content of the list is checked at content conversion. 144 | 145 | self._genericCommand(16, registeraddress, values, numberOfRegisters=len(values), payloadformat='registers') 146 | 147 | ##################### 148 | ## Generic command ## 149 | ##################### 150 | 151 | 152 | def _genericCommand(self, functioncode, registeraddress, value=None, \ 153 | numberOfRegisters=1, signed=False, payloadformat=None): 154 | NUMBER_OF_BITS = 1 155 | NUMBER_OF_BYTES_FOR_ONE_BIT = 1 156 | NUMBER_OF_BYTES_BEFORE_REGISTERDATA = 1 157 | ALL_ALLOWED_FUNCTIONCODES = list(range(1, 7)) + [15, 16] # To comply with both Python2 and Python3 158 | MAX_NUMBER_OF_REGISTERS = 255 159 | 160 | # Payload format constants, so datatypes can be told apart. 161 | # Note that bit datatype not is included, because it uses other functioncodes. 162 | PAYLOADFORMAT_REGISTER = 'register' 163 | PAYLOADFORMAT_REGISTERS = 'registers' 164 | 165 | ALL_PAYLOADFORMATS = [ PAYLOADFORMAT_REGISTER, PAYLOADFORMAT_REGISTERS] 166 | 167 | ## Check input values ## 168 | _checkFunctioncode(functioncode, ALL_ALLOWED_FUNCTIONCODES) # Note: The calling facade functions should validate this 169 | _checkRegisteraddress(registeraddress) 170 | _checkInt(numberOfRegisters, minvalue=1, maxvalue=MAX_NUMBER_OF_REGISTERS, description='number of registers') 171 | _checkBool(signed, description='signed') 172 | 173 | if payloadformat is not None: 174 | if payloadformat not in ALL_PAYLOADFORMATS: 175 | raise ValueError('Wrong payload format variable. Given: {0!r}'.format(payloadformat)) 176 | 177 | ## Check combinations of input parameters ## 178 | numberOfRegisterBytes = numberOfRegisters * _NUMBER_OF_BYTES_PER_REGISTER 179 | 180 | # Payload format 181 | if functioncode in [3, 4, 6, 16] and payloadformat is None: 182 | payloadformat = PAYLOADFORMAT_REGISTER 183 | 184 | if functioncode in [3, 4, 6, 16]: 185 | if payloadformat not in ALL_PAYLOADFORMATS: 186 | raise ValueError('The payload format is unknown. Given format: {0!r}, functioncode: {1!r}.'.\ 187 | format(payloadformat, functioncode)) 188 | else: 189 | if payloadformat is not None: 190 | raise ValueError('The payload format given is not allowed for this function code. ' + \ 191 | 'Given format: {0!r}, functioncode: {1!r}.'.format(payloadformat, functioncode)) 192 | 193 | # Signed 194 | if signed: 195 | if payloadformat not in [PAYLOADFORMAT_REGISTER, PAYLOADFORMAT_LONG]: 196 | raise ValueError('The "signed" parameter can not be used for this data format. ' + \ 197 | 'Given format: {0!r}.'.format(payloadformat)) 198 | 199 | # Number of registers 200 | if functioncode not in [3, 4, 16] and numberOfRegisters != 1: 201 | raise ValueError('The numberOfRegisters is not valid for this function code. ' + \ 202 | 'NumberOfRegisters: {0!r}, functioncode {1}.'.format(numberOfRegisters, functioncode)) 203 | 204 | if functioncode == 16 and payloadformat == PAYLOADFORMAT_REGISTER and numberOfRegisters != 1: 205 | raise ValueError('Wrong numberOfRegisters when writing to a ' + \ 206 | 'single register. Given {0!r}.'.format(numberOfRegisters)) 207 | # Note: For function code 16 there is checking also in the content conversion functions. 208 | 209 | # Value 210 | if functioncode in [5, 6, 15, 16] and value is None: 211 | raise ValueError('The input value is not valid for this function code. ' + \ 212 | 'Given {0!r} and {1}.'.format(value, functioncode)) 213 | 214 | if functioncode == 16 and payloadformat in [PAYLOADFORMAT_REGISTER]: 215 | _checkNumerical(value, description='input value') 216 | 217 | if functioncode == 6 and payloadformat == PAYLOADFORMAT_REGISTER: 218 | _checkNumerical(value, description='input value') 219 | 220 | # Value for string 221 | if functioncode == 16 and payloadformat == PAYLOADFORMAT_REGISTERS: 222 | if not isinstance(value, list): 223 | raise TypeError('The value parameter must be a list. Given {0!r}.'.format(value)) 224 | 225 | if len(value) != numberOfRegisters: 226 | raise ValueError('The list length does not match number of registers. ' + \ 227 | 'List: {0!r}, Number of registers: {1!r}.'.format(value, numberOfRegisters)) 228 | 229 | ## Build payload to slave ## 230 | if functioncode in [1, 2]: 231 | payloadToSlave = _numToTwoByteArray(registeraddress) + \ 232 | _numToTwoByteArray(NUMBER_OF_BITS) 233 | 234 | elif functioncode in [3, 4]: 235 | payloadToSlave = _numToTwoByteArray(registeraddress) + \ 236 | _numToTwoByteArray(numberOfRegisters) 237 | 238 | elif functioncode == 5: 239 | payloadToSlave = _numToTwoByteArray(registeraddress) + \ 240 | _createBitpattern(functioncode, value) 241 | 242 | elif functioncode == 6: 243 | payloadToSlave = _numToTwoByteArray(registeraddress) + \ 244 | _numToTwoByteArray(value, signed=signed) 245 | 246 | elif functioncode == 15: 247 | payloadToSlave = _numToTwoByteArray(registeraddress) + \ 248 | _numToTwoByteArray(NUMBER_OF_BITS) + \ 249 | _numToOneByteArray(NUMBER_OF_BYTES_FOR_ONE_BIT) + \ 250 | _createBitpattern(functioncode, value) 251 | 252 | elif functioncode == 16: 253 | if payloadformat == PAYLOADFORMAT_REGISTER: 254 | registerdata = _numToTwoByteArray(value, signed=signed) 255 | elif payloadformat == PAYLOADFORMAT_REGISTERS: 256 | registerdata = _valuelistToBytestring(value, numberOfRegisters) 257 | 258 | assert len(registerdata) == numberOfRegisterBytes 259 | payloadToSlave = _numToTwoByteArray(registeraddress) + \ 260 | _numToTwoByteArray(numberOfRegisters) + \ 261 | _numToOneByteArray(numberOfRegisterBytes) + \ 262 | registerdata 263 | 264 | ## Communicate ## 265 | payloadFromSlave = self._performCommand(functioncode, payloadToSlave) 266 | 267 | ## Check the contents in the response payload ## 268 | if functioncode in [1, 2, 3, 4]: 269 | _checkResponseByteCount(payloadFromSlave) # response byte count 270 | 271 | if functioncode in [5, 6, 15, 16]: 272 | _checkResponseRegisterAddress(payloadFromSlave, registeraddress) # response register address 273 | 274 | if functioncode == 5: 275 | _checkResponseWriteData(payloadFromSlave, _createBitpattern(functioncode, value)) # response write data 276 | 277 | if functioncode == 6: 278 | _checkResponseWriteData(payloadFromSlave, \ 279 | _numToTwoByteArray(value, signed=signed)) # response write data 280 | 281 | if functioncode == 15: 282 | _checkResponseNumberOfRegisters(payloadFromSlave, NUMBER_OF_BITS) # response number of bits 283 | 284 | if functioncode == 16: 285 | _checkResponseNumberOfRegisters(payloadFromSlave, numberOfRegisters) # response number of registers 286 | 287 | ## Calculate return value ## 288 | if functioncode in [1, 2]: 289 | registerdata = payloadFromSlave[NUMBER_OF_BYTES_BEFORE_REGISTERDATA:] 290 | if len(registerdata) != NUMBER_OF_BYTES_FOR_ONE_BIT: 291 | raise ValueError('The registerdata length does not match NUMBER_OF_BYTES_FOR_ONE_BIT. ' + \ 292 | 'Given {0}.'.format(len(registerdata))) 293 | 294 | return _bitResponseToValue(registerdata) 295 | 296 | if functioncode in [3, 4]: 297 | registerdata = payloadFromSlave[NUMBER_OF_BYTES_BEFORE_REGISTERDATA:] 298 | if len(registerdata) != numberOfRegisterBytes: 299 | raise ValueError('The registerdata length does not match number of register bytes. ' + \ 300 | 'Given {0!r} and {1!r}.'.format(len(registerdata), numberOfRegisterBytes)) 301 | 302 | elif payloadformat == PAYLOADFORMAT_REGISTERS: 303 | return _bytearrayToValuelist(registerdata, numberOfRegisters) 304 | 305 | elif payloadformat == PAYLOADFORMAT_REGISTER: 306 | return _twoByteStringToNum(registerdata, signed=signed) 307 | 308 | raise ValueError('Wrong payloadformat for return value generation. ' + \ 309 | 'Given {0}'.format(payloadformat)) 310 | 311 | ########################################## 312 | ## Communication implementation details ## 313 | ########################################## 314 | 315 | 316 | def _performCommand(self, functioncode, payloadToSlave): 317 | DEFAULT_NUMBER_OF_BYTES_TO_READ = 1000 318 | 319 | _checkFunctioncode(functioncode, None) 320 | _checkString(payloadToSlave, description='payload') 321 | 322 | # Build request 323 | request = _embedPayload(self.address, self.mode, functioncode, payloadToSlave) 324 | 325 | # Calculate number of bytes to read 326 | number_of_bytes_to_read = DEFAULT_NUMBER_OF_BYTES_TO_READ 327 | if self.precalculate_read_size: 328 | try: 329 | number_of_bytes_to_read = _predictResponseSize(self.mode, functioncode, payloadToSlave) 330 | except: 331 | if self.debug: 332 | template = 'MinimalModbus debug mode. Could not precalculate response size for Modbus {} mode. ' + \ 333 | 'Will read {} bytes. request: {!r}' 334 | _print_out(template.format(self.mode, number_of_bytes_to_read, request)) 335 | 336 | 337 | # Communicate 338 | response = self._communicate(request, number_of_bytes_to_read) 339 | 340 | # Extract payload 341 | payloadFromSlave = _extractPayload(response, self.address, self.mode, functioncode) 342 | return payloadFromSlave 343 | 344 | 345 | def _communicate(self, request, number_of_bytes_to_read): 346 | _checkString(request, minlength=1, description='request') 347 | _checkInt(number_of_bytes_to_read) 348 | 349 | if self.debug: 350 | _print_out('\nMinimalModbus debug mode. Writing to instrument (expecting {} bytes back): {!r} ({})'. \ 351 | format(number_of_bytes_to_read, request, _hexlify(request))) 352 | 353 | 354 | #self.serial.flushInput() TODO 355 | 356 | # Sleep to make sure 3.5 character times have passed 357 | minimum_silent_period = _calculate_minimum_silent_period(self.baudrate) 358 | time_since_read = time.ticks_ms() - _LATEST_READ_TIMES.get(self.port, 0) 359 | 360 | if time_since_read < minimum_silent_period: 361 | sleep_time = minimum_silent_period - time_since_read 362 | 363 | if self.debug: 364 | template = 'MinimalModbus debug mode. Sleeping for {:.1f} ms. ' + \ 365 | 'Minimum silent period: {:.1f} ms, time since read: {:.1f} ms.' 366 | text = template.format( 367 | sleep_time * _SECONDS_TO_MILLISECONDS, 368 | minimum_silent_period * _SECONDS_TO_MILLISECONDS, 369 | time_since_read * _SECONDS_TO_MILLISECONDS) 370 | _print_out(text) 371 | 372 | time.sleep_ms(sleep_time) 373 | 374 | elif self.debug: 375 | template = 'MinimalModbus debug mode. No sleep required before write. ' + \ 376 | 'Time since previous read: {:.1f} ms, minimum silent period: {:.2f} ms.' 377 | text = template.format( 378 | time_since_read * _SECONDS_TO_MILLISECONDS, 379 | minimum_silent_period * _SECONDS_TO_MILLISECONDS) 380 | _print_out(text) 381 | 382 | # Write request 383 | latest_write_time = time.ticks_ms() 384 | 385 | self.serial.write(request) 386 | 387 | # Read and discard local echo 388 | if self.handle_local_echo: 389 | localEchoToDiscard = self.serial.read(len(request)) 390 | if self.debug: 391 | template = 'MinimalModbus debug mode. Discarding this local echo: {!r} ({} bytes).' 392 | text = template.format(localEchoToDiscard, len(localEchoToDiscard)) 393 | _print_out(text) 394 | if localEchoToDiscard != request: 395 | template = 'Local echo handling is enabled, but the local echo does not match the sent request. ' + \ 396 | 'Request: {!r} ({} bytes), local echo: {!r} ({} bytes).' 397 | text = template.format(request, len(request), localEchoToDiscard, len(localEchoToDiscard)) 398 | raise IOError(text) 399 | 400 | # Read response 401 | answer = self.serial.read(number_of_bytes_to_read) 402 | _LATEST_READ_TIMES[self.port] = time.ticks_ms() 403 | 404 | if self.debug: 405 | template = 'MinimalModbus debug mode. Response from instrument: {!r} ({}) ({} bytes), ' + \ 406 | 'roundtrip time: {:.1f} ms. Timeout setting: {:.1f} ms.\n' 407 | text = template.format( 408 | answer, 409 | _hexlify(answer), 410 | len(answer), 411 | (_LATEST_READ_TIMES.get(self.port, 0) - latest_write_time) * _SECONDS_TO_MILLISECONDS, 412 | self.timeout * _SECONDS_TO_MILLISECONDS) 413 | _print_out(text) 414 | 415 | if len(answer) == 0: 416 | raise IOError('No communication with the instrument (no answer)') 417 | 418 | return answer 419 | 420 | #################### 421 | # Payload handling # 422 | #################### 423 | 424 | 425 | def _embedPayload(slaveaddress, mode, functioncode, payloaddata): 426 | _checkSlaveaddress(slaveaddress) 427 | _checkMode(mode) 428 | _checkFunctioncode(functioncode, None) 429 | _checkString(payloaddata, description='payload') 430 | 431 | firstPart = _numToOneByteArray(slaveaddress) + _numToOneByteArray(functioncode) + payloaddata 432 | 433 | request = firstPart + _calculateCrcString(firstPart) 434 | 435 | return request 436 | 437 | 438 | def _extractPayload(response, slaveaddress, mode, functioncode): 439 | BYTEPOSITION_FOR_SLAVEADDRESS = 0 # Relative to (stripped) response 440 | BYTEPOSITION_FOR_FUNCTIONCODE = 1 441 | 442 | NUMBER_OF_RESPONSE_STARTBYTES = 2 # Number of bytes before the response payload (in stripped response) 443 | NUMBER_OF_CRC_BYTES = 2 444 | BITNUMBER_FUNCTIONCODE_ERRORINDICATION = 7 445 | 446 | MINIMAL_RESPONSE_LENGTH_RTU = NUMBER_OF_RESPONSE_STARTBYTES + NUMBER_OF_CRC_BYTES 447 | 448 | # Argument validity testing 449 | # _checkString(response, description='response') 450 | _checkSlaveaddress(slaveaddress) 451 | _checkMode(mode) 452 | _checkFunctioncode(functioncode, None) 453 | 454 | plainresponse = response 455 | 456 | # Validate response length 457 | if len(response) < MINIMAL_RESPONSE_LENGTH_RTU: 458 | raise ValueError('Too short Modbus RTU response (minimum length {} bytes). Response: {!r}'.format( \ 459 | MINIMAL_RESPONSE_LENGTH_RTU, 460 | response)) 461 | 462 | # Validate response checksum 463 | calculateChecksum = _calculateCrcString 464 | numberOfChecksumBytes = NUMBER_OF_CRC_BYTES 465 | 466 | receivedChecksum = response[-numberOfChecksumBytes:] 467 | responseWithoutChecksum = response[0 : len(response) - numberOfChecksumBytes] 468 | 469 | calculatedChecksum = calculateChecksum(responseWithoutChecksum) 470 | 471 | if receivedChecksum != calculatedChecksum: 472 | template = 'Checksum error in {} mode: {!r} instead of {!r} . The response is: {!r} (plain response: {!r})' 473 | text = template.format( 474 | mode, 475 | receivedChecksum, 476 | calculatedChecksum, 477 | response, plainresponse) 478 | raise ValueError(text) 479 | 480 | # Check slave address 481 | # responseaddress = ord(response[BYTEPOSITION_FOR_SLAVEADDRESS]) 482 | 483 | responseaddress = response[BYTEPOSITION_FOR_SLAVEADDRESS] 484 | if responseaddress != slaveaddress: 485 | raise ValueError('Wrong return slave address: {} instead of {}. The response is: {!r}'.format( \ 486 | responseaddress, slaveaddress, response)) 487 | 488 | # Check function code 489 | # receivedFunctioncode = ord(response[BYTEPOSITION_FOR_FUNCTIONCODE]) 490 | 491 | receivedFunctioncode = response[BYTEPOSITION_FOR_FUNCTIONCODE] 492 | if receivedFunctioncode == _setBitOn(functioncode, BITNUMBER_FUNCTIONCODE_ERRORINDICATION): 493 | raise ValueError('The slave is indicating an error. The response is: {!r}'.format(response)) 494 | 495 | elif receivedFunctioncode != functioncode: 496 | raise ValueError('Wrong functioncode: {} instead of {}. The response is: {!r}'.format( \ 497 | receivedFunctioncode, functioncode, response)) 498 | 499 | # Read data payload 500 | firstDatabyteNumber = NUMBER_OF_RESPONSE_STARTBYTES 501 | 502 | lastDatabyteNumber = len(response) - NUMBER_OF_CRC_BYTES 503 | 504 | payload = response[firstDatabyteNumber:lastDatabyteNumber] 505 | return bytearray(payload) 506 | ############################################ 507 | ## Serial communication utility functions ## 508 | ############################################ 509 | 510 | 511 | def _predictResponseSize(mode, functioncode, payloadToSlave): 512 | MIN_PAYLOAD_LENGTH = 4 # For implemented functioncodes here 513 | 514 | NUMBER_OF_PAYLOAD_BYTES_IN_WRITE_CONFIRMATION = 4 515 | NUMBER_OF_PAYLOAD_BYTES_FOR_BYTECOUNTFIELD = 1 516 | 517 | RTU_TO_ASCII_PAYLOAD_FACTOR = 2 518 | 519 | NUMBER_OF_RTU_RESPONSE_STARTBYTES = 2 520 | NUMBER_OF_RTU_RESPONSE_ENDBYTES = 2 521 | 522 | # Argument validity testing 523 | _checkMode(mode) 524 | _checkFunctioncode(functioncode, None) 525 | _checkString(payloadToSlave, description='payload', minlength=MIN_PAYLOAD_LENGTH) 526 | 527 | # Calculate payload size 528 | if functioncode in [5, 6, 15, 16]: 529 | response_payload_size = NUMBER_OF_PAYLOAD_BYTES_IN_WRITE_CONFIRMATION 530 | 531 | elif functioncode in [1, 2, 3, 4]: 532 | given_size = _twoByteStringToNum(payloadToSlave[2:4]) 533 | if functioncode == 1 or functioncode == 2: 534 | # Algorithm from MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b 535 | number_of_inputs = given_size 536 | response_payload_size = NUMBER_OF_PAYLOAD_BYTES_FOR_BYTECOUNTFIELD + \ 537 | number_of_inputs // 8 + (1 if number_of_inputs % 8 else 0) 538 | 539 | elif functioncode == 3 or functioncode == 4: 540 | number_of_registers = given_size 541 | response_payload_size = NUMBER_OF_PAYLOAD_BYTES_FOR_BYTECOUNTFIELD + \ 542 | number_of_registers * _NUMBER_OF_BYTES_PER_REGISTER 543 | 544 | else: 545 | raise ValueError('Wrong functioncode: {}. The payload is: {!r}'.format( \ 546 | functioncode, payloadToSlave)) 547 | 548 | # Calculate number of bytes to read 549 | return NUMBER_OF_RTU_RESPONSE_STARTBYTES + \ 550 | response_payload_size + \ 551 | NUMBER_OF_RTU_RESPONSE_ENDBYTES 552 | 553 | 554 | def _calculate_minimum_silent_period(baudrate): 555 | _checkNumerical(baudrate, minvalue=1, description='baudrate') # Avoid division by zero 556 | 557 | BITTIMES_PER_CHARACTERTIME = 11 558 | MINIMUM_SILENT_CHARACTERTIMES = 3.5 559 | 560 | bittime = 1000 / float(baudrate) 561 | return bittime * BITTIMES_PER_CHARACTERTIME * MINIMUM_SILENT_CHARACTERTIMES 562 | 563 | ############################## 564 | # String and num conversions # 565 | ############################## 566 | 567 | def _numToOneByteArray(inputvalue): 568 | _checkInt(inputvalue, minvalue=0, maxvalue=0xFF) 569 | outstring = bytearray(1) 570 | outstring[0] = inputvalue 571 | return outstring 572 | 573 | 574 | def _numToTwoByteArray(value, LsbFirst=False, signed=False): 575 | _checkNumerical(value, description='inputvalue') 576 | _checkBool(LsbFirst, description='LsbFirst') 577 | _checkBool(signed, description='signed parameter') 578 | 579 | if LsbFirst: 580 | formatcode = '<' # Little-endian 581 | else: 582 | formatcode = '>' # Big-endian 583 | if signed: 584 | formatcode += 'h' # (Signed) short (2 bytes) 585 | else: 586 | formatcode += 'H' # Unsigned short (2 bytes) 587 | 588 | outstring = _pack(formatcode, value) 589 | assert len(outstring) == 2 590 | return outstring 591 | 592 | 593 | def _twoByteStringToNum(bytearray, signed=False): 594 | _checkString(bytearray, minlength=2, maxlength=2, description='bytearray') 595 | _checkBool(signed, description='signed parameter') 596 | 597 | formatcode = '>' # Big-endian 598 | if signed: 599 | formatcode += 'h' # (Signed) short (2 bytes) 600 | else: 601 | formatcode += 'H' # Unsigned short (2 bytes) 602 | 603 | fullregister = _unpack(formatcode, bytearray) 604 | 605 | return fullregister 606 | 607 | def _valuelistToBytestring(valuelist, numberOfRegisters): 608 | MINVALUE = 0 609 | MAXVALUE = 65535 610 | 611 | _checkInt(numberOfRegisters, minvalue=1, description='number of registers') 612 | 613 | if not isinstance(valuelist, list): 614 | raise TypeError('The valuelist parameter must be a list. Given {0!r}.'.format(valuelist)) 615 | 616 | for value in valuelist: 617 | _checkInt(value, minvalue=MINVALUE, maxvalue=MAXVALUE, description='elements in the input value list') 618 | 619 | _checkInt(len(valuelist), minvalue=numberOfRegisters, maxvalue=numberOfRegisters, \ 620 | description='length of the list') 621 | 622 | numberOfBytes = _NUMBER_OF_BYTES_PER_REGISTER * numberOfRegisters 623 | 624 | bytearray_temp = bytearray(0) 625 | for value in valuelist: 626 | bytearray_temp += _numToTwoByteArray(value, signed=False) 627 | 628 | assert len(bytearray_temp) == numberOfBytes 629 | return bytearray_temp 630 | 631 | 632 | def _bytearrayToValuelist(bytearray, numberOfRegisters): 633 | _checkInt(numberOfRegisters, minvalue=1, description='number of registers') 634 | numberOfBytes = _NUMBER_OF_BYTES_PER_REGISTER * numberOfRegisters 635 | _checkString(bytearray, 'byte string', minlength=numberOfBytes, maxlength=numberOfBytes) 636 | 637 | values = [] 638 | for i in range(numberOfRegisters): 639 | offset = _NUMBER_OF_BYTES_PER_REGISTER * i 640 | substring = bytearray[offset : offset + _NUMBER_OF_BYTES_PER_REGISTER] 641 | values.append(_twoByteStringToNum(substring)) 642 | 643 | return values 644 | 645 | 646 | def _pack(formatstring, value): 647 | try: 648 | result = struct.pack(formatstring, value) 649 | except: 650 | errortext = 'The value to send is probably out of range, as the num-to-bytearray conversion failed.' 651 | errortext += ' Value: {0!r} Struct format code is: {1}' 652 | raise ValueError(errortext.format(value, formatstring)) 653 | 654 | return bytearray(result) 655 | 656 | def _unpack(formatstring, packed): 657 | _checkString(packed, description='packed string', minlength=1) 658 | 659 | try: 660 | value = struct.unpack(formatstring, packed)[0] 661 | except: 662 | errortext = 'The received bytearray is probably wrong, as the bytearray-to-num conversion failed.' 663 | errortext += ' Bytestring: {0!r} Struct format code is: {1}' 664 | raise ValueError(errortext.format(packed, formatstring)) 665 | 666 | return value 667 | 668 | 669 | def _hexencode(bytearray, insert_spaces = False): 670 | separator = '' if not insert_spaces else ' ' 671 | 672 | # Use plain string formatting instead of binhex.hexlify, 673 | # in order to have it Python 2.x and 3.x compatible 674 | 675 | byte_representions = [] 676 | for c in bytearray: 677 | byte_representions.append( '{0:02X}'.format(c) ) 678 | return separator.join(byte_representions).strip() 679 | 680 | 681 | def _hexlify(bytearray): 682 | return _hexencode(bytearray, insert_spaces = True) 683 | 684 | 685 | def _bitResponseToValue(bytearray): 686 | _checkString(bytearray, description='bytearray', minlength=1, maxlength=1) 687 | 688 | RESPONSE_ON = '\x01' 689 | RESPONSE_OFF = '\x00' 690 | 691 | if bytearray == RESPONSE_ON: 692 | return 1 693 | elif bytearray == RESPONSE_OFF: 694 | return 0 695 | else: 696 | raise ValueError('Could not convert bit response to a value. Input: {0!r}'.format(bytearray)) 697 | 698 | 699 | def _createBitpattern(functioncode, value): 700 | _checkFunctioncode(functioncode, [5, 15]) 701 | _checkInt(value, minvalue=0, maxvalue=1, description='inputvalue') 702 | 703 | if functioncode == 5: 704 | if value == 0: 705 | return '\x00\x00' 706 | else: 707 | return '\xff\x00' 708 | 709 | elif functioncode == 15: 710 | if value == 0: 711 | return '\x00' 712 | else: 713 | return '\x01' # Is this correct?? 714 | 715 | 716 | #################### 717 | # Bit manipulation # 718 | #################### 719 | 720 | def _setBitOn(x, bitNum): 721 | _checkInt(x, minvalue=0, description='input value') 722 | _checkInt(bitNum, minvalue=0, description='bitnumber') 723 | 724 | return x | (1 << bitNum) 725 | 726 | ############################ 727 | # Error checking functions # 728 | ############################ 729 | 730 | _CRC16TABLE = ( 731 | 0, 49345, 49537, 320, 49921, 960, 640, 49729, 50689, 1728, 1920, 732 | 51009, 1280, 50625, 50305, 1088, 52225, 3264, 3456, 52545, 3840, 53185, 733 | 52865, 3648, 2560, 51905, 52097, 2880, 51457, 2496, 2176, 51265, 55297, 734 | 6336, 6528, 55617, 6912, 56257, 55937, 6720, 7680, 57025, 57217, 8000, 735 | 56577, 7616, 7296, 56385, 5120, 54465, 54657, 5440, 55041, 6080, 5760, 736 | 54849, 53761, 4800, 4992, 54081, 4352, 53697, 53377, 4160, 61441, 12480, 737 | 12672, 61761, 13056, 62401, 62081, 12864, 13824, 63169, 63361, 14144, 62721, 738 | 13760, 13440, 62529, 15360, 64705, 64897, 15680, 65281, 16320, 16000, 65089, 739 | 64001, 15040, 15232, 64321, 14592, 63937, 63617, 14400, 10240, 59585, 59777, 740 | 10560, 60161, 11200, 10880, 59969, 60929, 11968, 12160, 61249, 11520, 60865, 741 | 60545, 11328, 58369, 9408, 9600, 58689, 9984, 59329, 59009, 9792, 8704, 742 | 58049, 58241, 9024, 57601, 8640, 8320, 57409, 40961, 24768, 24960, 41281, 743 | 25344, 41921, 41601, 25152, 26112, 42689, 42881, 26432, 42241, 26048, 25728, 744 | 42049, 27648, 44225, 44417, 27968, 44801, 28608, 28288, 44609, 43521, 27328, 745 | 27520, 43841, 26880, 43457, 43137, 26688, 30720, 47297, 47489, 31040, 47873, 746 | 31680, 31360, 47681, 48641, 32448, 32640, 48961, 32000, 48577, 48257, 31808, 747 | 46081, 29888, 30080, 46401, 30464, 47041, 46721, 30272, 29184, 45761, 45953, 748 | 29504, 45313, 29120, 28800, 45121, 20480, 37057, 37249, 20800, 37633, 21440, 749 | 21120, 37441, 38401, 22208, 22400, 38721, 21760, 38337, 38017, 21568, 39937, 750 | 23744, 23936, 40257, 24320, 40897, 40577, 24128, 23040, 39617, 39809, 23360, 751 | 39169, 22976, 22656, 38977, 34817, 18624, 18816, 35137, 19200, 35777, 35457, 752 | 19008, 19968, 36545, 36737, 20288, 36097, 19904, 19584, 35905, 17408, 33985, 753 | 34177, 17728, 34561, 18368, 18048, 34369, 33281, 17088, 17280, 33601, 16640, 754 | 33217, 32897, 16448) 755 | 756 | 757 | def _calculateCrcString(inputstring): 758 | # Preload a 16-bit register with ones 759 | register = 0xFFFF 760 | 761 | try: 762 | _checkString(inputstring, description='input CRC string') 763 | for char in inputstring: 764 | register = (register >> 8) ^ _CRC16TABLE[(register ^ char) & 0xFF] 765 | except: 766 | for char in inputstring: 767 | register = (register >> 8) ^ _CRC16TABLE[(register ^ char) & 0xFF] 768 | 769 | return _numToTwoByteArray(register, LsbFirst=True) 770 | 771 | 772 | def _checkMode(mode): 773 | if not isinstance(mode, str): 774 | raise TypeError('The {0} should be a string. Given: {1!r}'.format("mode", mode)) 775 | 776 | if mode not in [MODE_RTU]: 777 | raise ValueError("Unreconized Modbus mode given. Must be 'rtu' or 'ascii' but {0!r} was given.".format(mode)) 778 | 779 | 780 | def _checkFunctioncode(functioncode, listOfAllowedValues=[]): 781 | FUNCTIONCODE_MIN = 1 782 | FUNCTIONCODE_MAX = 127 783 | 784 | _checkInt(functioncode, FUNCTIONCODE_MIN, FUNCTIONCODE_MAX, description='functioncode') 785 | 786 | if listOfAllowedValues is None: 787 | return 788 | 789 | if not isinstance(listOfAllowedValues, list): 790 | raise TypeError('The listOfAllowedValues should be a list. Given: {0!r}'.format(listOfAllowedValues)) 791 | 792 | for value in listOfAllowedValues: 793 | _checkInt(value, FUNCTIONCODE_MIN, FUNCTIONCODE_MAX, description='functioncode inside listOfAllowedValues') 794 | 795 | if functioncode not in listOfAllowedValues: 796 | raise ValueError('Wrong function code: {0}, allowed values are {1!r}'.format(functioncode, listOfAllowedValues)) 797 | 798 | 799 | def _checkSlaveaddress(slaveaddress): 800 | SLAVEADDRESS_MAX = 247 801 | SLAVEADDRESS_MIN = 0 802 | 803 | _checkInt(slaveaddress, SLAVEADDRESS_MIN, SLAVEADDRESS_MAX, description='slaveaddress') 804 | 805 | 806 | def _checkRegisteraddress(registeraddress): 807 | REGISTERADDRESS_MAX = 0xFFFF 808 | REGISTERADDRESS_MIN = 0 809 | 810 | _checkInt(registeraddress, REGISTERADDRESS_MIN, REGISTERADDRESS_MAX, description='registeraddress') 811 | 812 | 813 | def _checkResponseByteCount(payload): 814 | POSITION_FOR_GIVEN_NUMBER = 0 815 | NUMBER_OF_BYTES_TO_SKIP = 1 816 | 817 | _checkString(payload, minlength=1, description='payload') 818 | 819 | givenNumberOfDatabytes = payload[POSITION_FOR_GIVEN_NUMBER] 820 | countedNumberOfDatabytes = len(payload) - NUMBER_OF_BYTES_TO_SKIP 821 | 822 | if givenNumberOfDatabytes != countedNumberOfDatabytes: 823 | errortemplate = 'Wrong given number of bytes in the response: {0}, but counted is {1} as data payload length is {2}.' + \ 824 | ' The data payload is: {3!r}' 825 | errortext = errortemplate.format(givenNumberOfDatabytes, countedNumberOfDatabytes, len(payload), payload) 826 | raise ValueError(errortext) 827 | 828 | 829 | def _checkResponseRegisterAddress(payload, registeraddress): 830 | _checkString(payload, minlength=2, description='payload') 831 | _checkRegisteraddress(registeraddress) 832 | 833 | bytesForStartAddress = payload[0:2] 834 | receivedStartAddress = _twoByteStringToNum(bytesForStartAddress) 835 | 836 | if receivedStartAddress != registeraddress: 837 | raise ValueError('Wrong given write start adress: {0}, but commanded is {1}. The data payload is: {2!r}'.format( \ 838 | receivedStartAddress, registeraddress, payload)) 839 | 840 | 841 | def _checkResponseNumberOfRegisters(payload, numberOfRegisters): 842 | _checkString(payload, minlength=4, description='payload') 843 | _checkInt(numberOfRegisters, minvalue=1, maxvalue=0xFFFF, description='numberOfRegisters') 844 | 845 | bytesForNumberOfRegisters = payload[2:4] 846 | receivedNumberOfWrittenReisters = _twoByteStringToNum(bytesForNumberOfRegisters) 847 | 848 | if receivedNumberOfWrittenReisters != numberOfRegisters: 849 | raise ValueError('Wrong number of registers to write in the response: {0}, but commanded is {1}. The data payload is: {2!r}'.format( \ 850 | receivedNumberOfWrittenReisters, numberOfRegisters, payload)) 851 | 852 | 853 | def _checkResponseWriteData(payload, writedata): 854 | _checkString(payload, minlength=4, description='payload') 855 | _checkString(writedata, minlength=2, maxlength=2, description='writedata') 856 | 857 | receivedWritedata = payload[2:4] 858 | 859 | if receivedWritedata != writedata: 860 | raise ValueError('Wrong write data in the response: {0!r}, but commanded is {1!r}. The data payload is: {2!r}'.format( \ 861 | receivedWritedata, writedata, payload)) 862 | 863 | 864 | def _checkString(inputstring, description, minlength=0, maxlength=None): 865 | # Type checking 866 | if not isinstance(description, str): 867 | raise TypeError('The description should be a string. Given: {0!r}'.format(description)) 868 | 869 | if not isinstance(inputstring, bytearray): 870 | raise TypeError('The {0} should be a string. Given: {1!r}'.format(description, inputstring)) 871 | 872 | if not isinstance(maxlength, (int, type(None))): 873 | raise TypeError('The maxlength must be an integer or None. Given: {0!r}'.format(maxlength)) 874 | 875 | # Check values 876 | _checkInt(minlength, minvalue=0, maxvalue=None, description='minlength') 877 | 878 | if len(inputstring) < minlength: 879 | raise ValueError('The {0} is too short: {1}, but minimum value is {2}. Given: {3!r}'.format( \ 880 | description, len(inputstring), minlength, inputstring)) 881 | 882 | if not maxlength is None: 883 | if maxlength < 0: 884 | raise ValueError('The maxlength must be positive. Given: {0}'.format(maxlength)) 885 | 886 | if maxlength < minlength: 887 | raise ValueError('The maxlength must not be smaller than minlength. Given: {0} and {1}'.format( \ 888 | maxlength, minlength)) 889 | 890 | if len(inputstring) > maxlength: 891 | raise ValueError('The {0} is too long: {1}, but maximum value is {2}. Given: {3!r}'.format( \ 892 | description, len(inputstring), maxlength, inputstring)) 893 | 894 | 895 | def _checkInt(inputvalue, minvalue=None, maxvalue=None, description='inputvalue'): 896 | if not isinstance(description, str): 897 | raise TypeError('The description should be a string. Given: {0!r}'.format(description)) 898 | 899 | if not isinstance(inputvalue, (int, int)): 900 | raise TypeError('The {0} must be an integer. Given: {1!r}'.format(description, inputvalue)) 901 | 902 | if not isinstance(minvalue, (int, int, type(None))): 903 | raise TypeError('The minvalue must be an integer or None. Given: {0!r}'.format(minvalue)) 904 | 905 | if not isinstance(maxvalue, (int, int, type(None))): 906 | raise TypeError('The maxvalue must be an integer or None. Given: {0!r}'.format(maxvalue)) 907 | 908 | _checkNumerical(inputvalue, minvalue, maxvalue, description) 909 | 910 | 911 | def _checkNumerical(inputvalue, minvalue=None, maxvalue=None, description='inputvalue'): 912 | # Type checking 913 | if not isinstance(description, str): 914 | raise TypeError('The description should be a string. Given: {0!r}'.format(description)) 915 | 916 | if not isinstance(inputvalue, (int, int, float)): 917 | raise TypeError('The {0} must be numerical. Given: {1!r}'.format(description, inputvalue)) 918 | 919 | if not isinstance(minvalue, (int, float, int, type(None))): 920 | raise TypeError('The minvalue must be numeric or None. Given: {0!r}'.format(minvalue)) 921 | 922 | if not isinstance(maxvalue, (int, float, int, type(None))): 923 | raise TypeError('The maxvalue must be numeric or None. Given: {0!r}'.format(maxvalue)) 924 | 925 | # Consistency checking 926 | if (not minvalue is None) and (not maxvalue is None): 927 | if maxvalue < minvalue: 928 | raise ValueError('The maxvalue must not be smaller than minvalue. Given: {0} and {1}, respectively.'.format( \ 929 | maxvalue, minvalue)) 930 | 931 | # Value checking 932 | if not minvalue is None: 933 | if inputvalue < minvalue: 934 | raise ValueError('The {0} is too small: {1}, but minimum value is {2}.'.format( \ 935 | description, inputvalue, minvalue)) 936 | 937 | if not maxvalue is None: 938 | if inputvalue > maxvalue: 939 | raise ValueError('The {0} is too large: {1}, but maximum value is {2}.'.format( \ 940 | description, inputvalue, maxvalue)) 941 | 942 | 943 | def _checkBool(inputvalue, description='inputvalue'): 944 | if not isinstance(inputvalue, bool): 945 | raise TypeError('The {0} must be boolean. Given: {1!r}'.format(description, inputvalue)) 946 | 947 | ##################### 948 | # Development tools # 949 | ##################### 950 | 951 | 952 | def _print_out(inputstring): 953 | sys.stdout.write(inputstring + '\n') 954 | 955 | 956 | --------------------------------------------------------------------------------