From eff91448afa3c1b684b0bdae0efc566a2874a9e2 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Fri, 22 Nov 2024 16:40:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0.=20=D0=92=D1=81=D1=82=D1=80=D0=B0=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20Modbus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modbus/pymodbus/constants.gd | 205 ++++++++++++++++++ modbus/pymodbus/exceptions.gd | 87 ++++++++ modbus/pymodbus/pdu.gd | 187 ++++++++++++++++ .../server/reactive/default_config.json | 2 +- modbus/pymodbus/utilities.gd | 177 +++++++++++++++ modbus/pymodbus/version.gd | 44 ++++ project.godot | 10 +- 7 files changed, 704 insertions(+), 8 deletions(-) create mode 100644 modbus/pymodbus/constants.gd create mode 100644 modbus/pymodbus/exceptions.gd create mode 100644 modbus/pymodbus/pdu.gd create mode 100644 modbus/pymodbus/utilities.gd create mode 100644 modbus/pymodbus/version.gd diff --git a/modbus/pymodbus/constants.gd b/modbus/pymodbus/constants.gd new file mode 100644 index 0000000..11350d3 --- /dev/null +++ b/modbus/pymodbus/constants.gd @@ -0,0 +1,205 @@ +extends Node + +## Constants For Modbus Server/Client +## This is the single location for storing default +## values for the servers and clients. + +## A collection of modbus default values[br] +class Defaults: + ## [param Port] + ## The default modbus tcp server port (502)[br] + ## + ## [param TLSPort] + ## The default modbus tcp over tls server port (802)[br] + ## + ## [param Backoff] + ## The default exponential backoff delay (0.3 seconds)[br] + ## + ## [param Retries] + ## The default number of times a client should retry the given + ## request before failing (3)[br] + ## + ## [param RetryOnEmpty] + ## A flag indicating if a transaction should be retried in the + ## case that an empty response is received. This is useful for + ## slow clients that may need more time to process a request.[br] + ## + ## [param RetryOnInvalid] + ## A flag indicating if a transaction should be retried in the + ## case that an invalid response is received.[br] + ## + ## [param Timeout] + ## The default amount of time a client should wait for a request + ## to be processed (3 seconds)[br] + ## + ## [param Reconnects] + ## The default number of times a client should attempt to reconnect + ## before deciding the server is down (0)[br] + ## + ## [param TransactionId] + ## The starting transaction identifier number (0)[br] + ## + ## [param ProtocolId] + ## The modbus protocol id. Currently this is set to 0 in all + ## but proprietary implementations.[br] + ## + ## [param UnitId] + ## The modbus slave addrss. Currently this is set to 0x00 which + ## means this request should be broadcast to all the slave devices + ## (really means that all the devices should respons).[br] + ## + ## [param Baudrate] + ## The speed at which the data is transmitted over the serial line. + ## This defaults to 19200.[br] + ## + ## [param Parity] + ## The type of checksum to use to verify data integrity. This can be + ## on of the following:[br] + ## - (E)ven - 1 0 1 0 | P(0)[br] + ## - (O)dd - 1 0 1 0 | P(1)[br] + ## - (N)one - 1 0 1 0 | no parity[br] + ## This defaults to (N)one.[br] + ## + ## [param Bytesize] + ## The number of bits in a byte of serial data. This can be one of + ## 5, 6, 7, or 8. This defaults to 8.[br] + ## + ## [param Stopbits] + ## The number of bits sent after each character in a message to + ## indicate the end of the byte. This defaults to 1.[br] + ## + ## [param ZeroMode] + ## Indicates if the slave datastore should use indexing at 0 or 1. + ## More about this can be read in section 4.4 of the modbus specification.[br] + ## + ## [param IgnoreMissingSlaves] + ## In case a request is made to a missing slave, this defines if an error + ## should be returned or simply ignored. This is useful for the case of a + ## serial server emulater where a request to a non-existant slave on a bus + ## will never respond. The client in this case will simply timeout.[br] + ## + ## [param broadcast_enable] + ## When False unit_id 0 will be treated as any other unit_id. When True and + ## the unit_id is 0 the server will execute all requests on all server + ## contexts and not respond and the client will skip trying to receive a + ## response. Default value False does not conform to Modbus spec but maintains + ## legacy behavior for existing pymodbus users[br] + + const Port: = 502 + const TLSPort: = 802 + const Backoff: = 0.3 + const Retries: = 3 + const RetryOnEmpty: = false + const RetryOnInvalid: = false + const Timeout: = 3 + const Reconnects: = 0 + const TransactionId: = 0 + const ProtocolId: = 0 + const UnitId: = 0x00 + const Baudrate: = 19200 + const Parity: = 'N' + const Bytesize: = 8 + const Stopbits: = 1 + const ZeroMode: = false + const IgnoreMissingSlaves: = false + const ReadSize: = 1024 + const broadcast_enable: = false + + +## These represent various status codes in the modbus +## protocol.[br] +class ModbusStatus extends Object: + ## + ## [param Waiting] + ## This indicates that a modbus device is currently + ## waiting for a given request to finish some running task.[br] + ## + ## [param Ready] + ## This indicates that a modbus device is currently + ## free to perform the next request task.[br] + ## + ## [param On] + ## This indicates that the given modbus entity is on[br] + ## + ## [param Off] + ## This indicates that the given modbus entity is off[br] + ## + ## [param SlaveOn] + ## This indicates that the given modbus slave is running[br] + ## + ## [param SlaveOff] + ## This indicates that the given modbus slave is not running[br] + + const Waiting: = 0xffff + const Ready: = 0x0000 + const On: = 0xff00 + const Off: = 0x0000 + const SlaveOn: = 0xff + const SlaveOff: = 0x00 + + +class Endian: + ## An enumeration representing the various byte endianess. + ## [param Auto] + ## This indicates that the byte order is chosen by the + ## current native environment.[br] + ## [param Big] + ## This indicates that the bytes are in little endian format.[br] + ## [param Little] + ## This indicates that the bytes are in big endian format + ## [i]I am simply borrowing the format strings from the + ## python struct module for my convenience.[/i] + const Auto: = '@' + const Big: = '>' + const Little: = '<' + + +class ModbusPlusOperation: + ## Represents the type of modbus plus request + ## [param GetStatistics] + ## Operation requesting that the current modbus plus statistics + ## be returned in the response.[br] + ## [param ClearStatistics] + ## Operation requesting that the current modbus plus statistics + ## be cleared and not returned in the response.[br] + const GetStatistics: = 0x0003 + const ClearStatistics: = 0x0004 + + +class DeviceInformation: + ## Represents what type of device information to read + ## + ## [param Basic] + ## This is the basic (required) device information to be returned. + ## This includes VendorName, ProductCode, and MajorMinorRevision + ## code.[br] + ## + ## [param Regular] + ## In addition to basic data objects, the device provides additional + ## and optional identification and description data objects. All of + ## the objects of this category are defined in the standard but their + ## implementation is optional.[br] + ## + ## [param Extended + ## In addition to regular data objects, the device provides additional + ## and optional identification and description private data about the + ## physical device itself. All of these data are device dependent.[br] + ## + ## [param Specific] + ## Request to return a single data object.[br] + + const Basic: = 0x01 + const Regular: = 0x02 + const Extended: = 0x03 + const Specific: = 0x04 + + +class MoreData: + ## Represents the more follows condition + ## [param Nothing] + ## This indiates that no more objects are going to be returned.[br] + ## + ## [param KeepReading] + ## This indicates that there are more objects to be returned.[br] + const Nothing: = 0x00 + const KeepReading: = 0xFF diff --git a/modbus/pymodbus/exceptions.gd b/modbus/pymodbus/exceptions.gd new file mode 100644 index 0000000..455f5ed --- /dev/null +++ b/modbus/pymodbus/exceptions.gd @@ -0,0 +1,87 @@ +extends Node + +## Pymodbus Exceptions. Custom exceptions to be used in the Modbus code. + + +## Base modbus exception +class ModbusException: + var string: String = '' + ## Initialize the exception. + ## [param s] The message to append to the error + func _init(s: String): string = s + func _to_string(): return ': %s' % string + func isError(): return true + func raise(): push_error(string) + + +### Error resulting from data i/o +class ModbusIOException extends ModbusException: + var fcode + var message + ## Initialize the exception + ## [param s] The message to append to the error + func _init(s: String = '', function_code=null): + fcode = function_code + message = '[Input/Output] %s' % s + ModbusException.new(message).raise() + + +## Error resulting from invalid parameter +class ParameterException extends ModbusException: + ## Initialize the exception + ## [param s] The message to append to the error + func _init(s: String = ''): + var message = '[Invalid Parameter] %s' % s + ModbusException.new(message).raise() + + +## Error resulting from making a request to a slave +## that does not exist +class NoSuchSlaveException extends ModbusException: + ## Initialize the exception. + ## [param s] The message to append to the error + func _init(s: String = ''): + var message = '[No Such Slave] %s' % s + ModbusException.new(message).raise() + + +## Error resulting from not implemented function +class NotImplementedException extends ModbusException: + ## Initialize the exception + ## [param s]: The message to append to the error + func _init(s: String = ''): + var message = '[Not Implemented] %s' % s + ModbusException.new(message).raise() + +## Error resulting from a bad connection +class ConnectionException extends ModbusException: + ## Initialize the exception + ## [param s] The message to append to the error + func _init(s: String = ''): + var message: String = '[Connection] %s' % s + ModbusException.new(message).raise() + + +## Error resulting from invalid response received or decoded +class InvalidMessageReceivedException extends ModbusException: + ## Initialize the exception + ## [param s] The message to append to the error + func _init(s: String = ''): + var message: String = '[Invalid Message] %s' % s + ModbusException.new(message).raise() + + +## Error resulting from failing to register a custom message request/response +class MessageRegisterException extends ModbusException: + func _init(s: String = ''): + var message: String = '[Error registering message] %s' % s + ModbusException.new(message).raise() + + +## Error resulting from modbus response timeout +class TimeOutException extends ModbusException: + ## Initialize the exception + ## [param s] The message to append to the error + func _init(s: String = ''): + var message: String = '[Timeout] %s' % s + ModbusException.new(message).raise() diff --git a/modbus/pymodbus/pdu.gd b/modbus/pymodbus/pdu.gd new file mode 100644 index 0000000..5da034a --- /dev/null +++ b/modbus/pymodbus/pdu.gd @@ -0,0 +1,187 @@ +extends Node + +## Contains base classes for modbus request/response/error packets + +# В автозагрузке должно быть +# from pymodbus.exceptions import +# from pymodbus.constants import Defaults +# from pymodbus.utilities import rtuFrameSize + + +# Base PDU's +class ModbusPDU extends Object: + ## Base class for all Modbus messages + ## [param transaction_id] + ## This value is used to uniquely identify a request + ## response pair. It can be implemented as a simple counter + ## [param protocol_id] + ## This is a constant set at 0 to indicate Modbus. It is + ## put here for ease of expansion. + ## [param unit_id] + ## This is used to route the request to the correct child. In + ## the TCP modbus, it is used for routing (or not used at all. However, + ## for the serial versions, it is used to specify which child to perform + ## the requests against. The value 0x00 represents the broadcast address + ## (also 0xff). + ## [param check] + ## This is used for LRC/CRC in the serial modbus protocols + ## [param skip_encode] + ## This is used when the message payload has already been encoded. + ## Generally this will occur when the PayloadBuilder is being used + ## to create a complicated message. By setting this to True, the + ## request will pass the currently encoded message through instead + ## of encoding it again. + + var transaction_id + var protocol_id + var unit_id + var skip_encode + var check + var function_code = 0 + var exception_code = 0 + + ## Initializes the base data for a modbus request + func _init(kwargs: Dictionary): + transaction_id = kwargs.get('transaction', constants.Defaults.TransactionId) + protocol_id = kwargs.get('protocol', constants.Defaults.ProtocolId) + unit_id = kwargs.get('unit', constants.Defaults.UnitId) + skip_encode = kwargs.get('skip_encode', false) + check = 0x0000 + + + ## Encodes the message + ## [b]raises[/b] A not implemented exception + func encode(): + exceptions.NotImplementedException.new().raise() + + ## Decodes data part of the message. + ## [param data] is a string object + ## [b]raises[/b] A not implemented exception + func decode(data: PackedByteArray): + exceptions.NotImplementedException.new().raise() + + ## Calculates the size of a PDU. + ## [param buffer] A buffer containing the data that have been received. + ## [b]returns[/b] The number of bytes in the PDU. + func calculateRtuFrameSize(buffer: PackedByteArray): + var rc = self.get('_rtu_frame_size') + if rc != null: + return rc + rc = self.get('_rtu_byte_count_pos') + if rc != null: + return utilities.rtuFrameSize(buffer, rc) + else: + exceptions.NotImplementedException.new('Cannot determine RTU frame size for %s' % self).raise() + + +## Base class for a modbus request PDU +class ModbusRequest extends ModbusPDU: + + func _init(kwargs: Dictionary): + # Proxy to the lower level initializer + super(kwargs) + + ## Builds an error response based on the function[br] + ## [param exception] The exception to return[br] + ## [b]raises[/b] An exception response[br] + func doException(exception): + var exc = exceptions.ExceptionResponse(function_code, exception) + log.error(exc) + return exc + + +class ModbusResponse extends ModbusPDU: + ## Base class for a modbus response PDU + ## [param should_respond] + ## A flag that indicates if this response returns a result back + ## to the client issuing the request + ## [param _rtu_frame_size] + ## Indicates the size of the modbus rtu response used for + ## calculating how much to read. + var should_respond = true + + ## Proxy to the lower level initializer + func _init(kwargs: Dictionary): + super(kwargs) + + ## Checks if the error is a success or failure + func isError(): + return function_code > 0x80 + + + +## Exception PDU's. An enumeration of the valid modbus exceptions +class ModbusExceptions: + const IllegalFunction = 0x01 + const IllegalAddress = 0x02 + const IllegalValue = 0x03 + const SlaveFailure = 0x04 + const Acknowledge = 0x05 + const SlaveBusy = 0x06 + const MemoryParityError = 0x08 + const GatewayPathUnavailable = 0x0A + const GatewayNoResponse = 0x0B + + var _decoded: = {} + + func _init(): + for prop in get_property_list(): + _decoded[get(prop.name)] = prop.name + + ## Given an error code, translate it to a string error name.[br] + ## [param code] The code number to translate + func decode(code: int) -> String: return _decoded.get(code) + + +## Base class for a modbus exception PDU +class ExceptionResponse extends ModbusResponse: + var ExceptionOffset = 0x80 + var _rtu_frame_size = 5 + var original_code = function_code + + ## Initializes the modbus exception response + ## [param function_code] The function to build an exception response for + ## [param exception_code] The specific modbus exception to return + func _init(func_code, kwargs: Dictionary, except_code: int = 0): + super(kwargs) + function_code = func_code | ExceptionOffset + exception_code = except_code + original_code = function_code + + ## Encodes a modbus exception response + func encode(): return exception_code + + ## Decodes a modbus exception response[br] + ## [param data] The packet data to decode + func decode(data: PackedByteArray): + exception_code = data.decode_u8(0) + + ## Builds a representation of an exception response[/br] + ## [b]returns[/b] The string representation of an exception response + func _to_string() -> String: + var message = ModbusExceptions.new().decode(self.exception_code) + return 'Exception Response(%d, %d, %s)' % [function_code, original_code, message] + + +## Defines the Modbus slave exception type 'Illegal Function' +## This exception code is returned if the slave:: +## - does not implement the function code **or** +## - is not in a state that allows it to process the function +class IllegalFunctionRequest extends ModbusRequest: + var ErrorCode = 1 + + ## Initializes a IllegalFunctionRequest[br] + ## [param function_code] The function we are erroring on[br] + func _init(func_code, kwargs): + super(kwargs) + self.function_code = func_code + + ## This is here so this failure will run correctly[br] + ## [param data] Not used[br] + func decode(_data: PackedByteArray): pass + + ## Builds an illegal function request error response + ## [param context] The current context for the message + ## [b]returns[/b] The error response packet + func execute(context): + return ExceptionResponse.new(function_code, ErrorCode) diff --git a/modbus/pymodbus/server/reactive/default_config.json b/modbus/pymodbus/server/reactive/default_config.json index 6f07667..bf507f6 100644 --- a/modbus/pymodbus/server/reactive/default_config.json +++ b/modbus/pymodbus/server/reactive/default_config.json @@ -29,4 +29,4 @@ "handler": "ModbusDisonnectedRequestHandler", "ignore_missing_slaves": false } -} \ No newline at end of file +} diff --git a/modbus/pymodbus/utilities.gd b/modbus/pymodbus/utilities.gd new file mode 100644 index 0000000..bc0e47c --- /dev/null +++ b/modbus/pymodbus/utilities.gd @@ -0,0 +1,177 @@ +extends Node + +## Modbus Utilities. +## A collection of utilities for packing data, unpacking +## data computing checksums, and decode checksums. + + +## Modbus Client States +class ModbusTransactionState extends Object: + const IDLE: = 0 + const SENDING: = 1 + const WAITING_FOR_REPLY: = 2 + const WAITING_TURNAROUND_DELAY: = 3 + const PROCESSING_REPLY: = 4 + const PROCESSING_ERROR: = 5 + const TRANSACTION_COMPLETE: = 6 + const RETRYING: = 7 + const NO_RESPONSE_STATE: = 8 + + func state_to_string(state) -> String: + var states: = { + ModbusTransactionState.IDLE: 'IDLE', + ModbusTransactionState.SENDING: 'SENDING', + ModbusTransactionState.WAITING_FOR_REPLY: 'WAITING_FOR_REPLY', + ModbusTransactionState.WAITING_TURNAROUND_DELAY: 'WAITING_TURNAROUND_DELAY', + ModbusTransactionState.PROCESSING_REPLY: 'PROCESSING_REPLY', + ModbusTransactionState.PROCESSING_ERROR: 'PROCESSING_ERROR', + ModbusTransactionState.TRANSACTION_COMPLETE: 'TRANSACTION_COMPLETE', + ModbusTransactionState.RETRYING: 'RETRYING TRANSACTION' } + return states.get(state, null) + +var __crc16_table: = [] + +func _ready(): + __crc16_table = __generate_crc16_table() + + +## Creates a string out of an array of bits +## [code] +## var bits = [False, True, False, True] +## var result = pack_bitstring(bits) +## [/code] +func pack_bitstring(bits: Array): + var ret = PackedByteArray() + var i = 0 + var packed: int = 0 + for bit in bits: + if bit: + packed += 128 + i += 1 + if i == 8: + ret += packed + i = 0 + packed = 0 + else: + packed >>= 1 + if 0 < i < 8: + packed >>= (7 - i) + ret += packed + return ret + + +## Creates bit array out of a string +## param string: The modbus data packet to decode +## [code] +## var bytes = 'bytes to decode' +## var result = unpack_bitstring(bytes) +## [/code] +func unpack_bitstring(string: String): + var psa: = string.to_ascii_buffer() + var byte_count = psa.size() + var bits: = [] + for byte in byte_count: + var value = psa.decode_u8(byte) + for i in 8: + bits.append((value & (1 << i)) > 0) + return bits + + +## Returns byte string from a given string +## [param s] +func make_byte_string(s) -> PackedByteArray: + return PackedByteArray(s) + + +# Error Detection Functions +func __generate_crc16_table() -> Array: + ## Generates a crc16 lookup table + ## note:: This will only be generated once + var result: = [] + for byte in 256: + var crc = 0x0000 + for i in 8: + if (byte ^ crc) & 0x0001: + crc = (crc >> 1) ^ 0xa001 + else: + crc >>= 1 + byte >>= 1 + result.append(crc) + return result + + +## Computes a crc16 on the passed in string. For modbus, +## this is only used on the binary serial protocols (in this +## case RTU). The difference between modbus's crc16 and a normal crc16 +## is that modbus starts the crc value out at 0xffff. +## [param data] The data to create a crc16 of +## [b]returns[/b] The calculated CRC +func computeCRC(data: PackedByteArray) -> int: + var crc: int = 0xffff + for a in data: + var idx = __crc16_table[(crc ^ a) & 0xff] + crc = ((crc >> 8) & 0xff) ^ idx + return ((crc << 8) & 0xff00) | ((crc >> 8) & 0x00ff) + + +## Checks if the data matches the passed in CRC +## [param data] The data to create a crc16 of +## [param check] The CRC to validate +## [b]returns[/b] True if matched, False otherwise +func checkCRC(data, check: int) -> bool: + return computeCRC(data) == check + + +## Used to compute the longitudinal redundancy check +## against a string. This is only used on the serial ASCII +## modbus protocol. A full description of this implementation +## can be found in appendex B of the serial line modbus description. +## [param data] The data to apply a lrc to +## [b]return[/b] The calculated LRC +func computeLRC(data: PackedByteArray) -> int: + var lrc: int = 0 + for a in data: + lrc += a + lrc &= 0xff + lrc = (lrc ^ 0xff) + 1 + return lrc & 0xff + + +## Checks if the passed in data matches the LRC +## [param data] The data to calculate +## [param check] The LRC to validate +## [b]returns[/b] True if matched, False otherwise +func checkLRC(data: PackedByteArray, check: int) -> bool: + return computeLRC(data) == check + + +## Calculates the size of the frame based on the byte count. +## param data: The buffer containing the frame. +## param byte_count_pos: The index of the byte count in the buffer. +## returns: The size of the frame. +## +## The structure of frames with a byte count field is always the +## same: +## +## first, there are some header fields +## then the byte count field +## then as many data bytes as indicated by the byte count, +## finally the CRC (two bytes). +## +## To calculate the frame size, it is therefore sufficient to extract +## the contents of the byte count field, add the position of this +## field, and finally increment the sum by three (one byte for the +## byte count field, two for the CRC). +func rtuFrameSize(data: PackedByteArray, byte_count_pos: int) -> int: + return data.decode_u32(byte_count_pos) + byte_count_pos + 3 + + +## Returns hex representation of bytestring received +## [param packet] +## [b]return[/b] +func hexlify_packets(packet: PackedByteArray): + if not packet: return '' + var s = String() + for ch in packet: + s += '%02x' % ch + return s diff --git a/modbus/pymodbus/version.gd b/modbus/pymodbus/version.gd new file mode 100644 index 0000000..008111d --- /dev/null +++ b/modbus/pymodbus/version.gd @@ -0,0 +1,44 @@ +extends Node + +## Handle the version information here; you should only have to +## change the version tuple. Since we are using twisted's +## version class, we can also query the svn version as well using +## the local .entries file. + + +var version = Version.new('pymodbus', 2, 5, 3) + + +class Version extends Object: + var package + var major + var minor + var micro + var pre + var name + + ## [param package] Name of the package that this is a version of.[br] + ## [param major] The major version number.[br] + ## [param minor] The minor version number.[br] + ## [param micro] The micro version number.[br] + ## [param pre] The pre release tag.[br] + func _init(pkg, mjr, mnr, mcr, pr=null): + self.package = pkg + self.major = mjr + self.minor = mnr + self.micro = mcr + self.pre = pr + self.name = 'pymodbus' + + ## Return a string in canonical short version format + ## ...
+    func short():
+        if self.pre:
+            return '%d.%d.%d.%s' % [self.major, self.minor, self.micro, self.pre]
+        else:
+            return '%d.%d.%d' % [self.major, self.minor, self.micro]
+
+    ## Returns a string representation of the object
+    ## A string representation of this object
+    func _to_string() -> String:
+        return '[%s, version %s]' % [self.package, self.short()]
diff --git a/project.godot b/project.godot
index c25686b..68cf38a 100644
--- a/project.godot
+++ b/project.godot
@@ -61,6 +61,9 @@ mercator="*res://scripts/mercator.gd"
 tcp5p28="*res://scripts/tcp5p28.gd"
 sch3="*res://scripts/sch3.gd"
 spt25="*res://scripts/spt25.gd"
+exceptions="*res://modbus/pymodbus/exceptions.gd"
+constants="*res://modbus/pymodbus/constants.gd"
+utilities="*res://modbus/pymodbus/utilities.gd"
 
 [debug]
 
@@ -198,10 +201,3 @@ shader_compiler/shader_cache/strip_debug=true
 textures/default_filters/anisotropic_filtering_level=0
 anti_aliasing/screen_space_roughness_limiter/enabled=false
 quality/driver/fallback_to_gles2=true
-
-[shader_globals]
-
-tools={
-"type": "bool",
-"value": true
-}