Commit 1bd935fb3a6e58bd1be7f5dfc2a6cf3314d5bea0

Authored by Peter M. Groen
Committed by GitHub
2 parents b8720844 aaf431bb

Merge pull request #1 from pgroen/feat/pgroen/modbus-tcp-implementation

Feat/pgroen/modbus tcp implementation
.gitignore 0 → 100644
  1 +/CMakeLists.txt.user
  2 +/.gitmodules
  3 +/build/
  4 +/submodules/
... ...
CMakeLists.txt 0 → 100644
  1 +cmake_minimum_required(VERSION 3.0)
  2 +project(modbus-cpp)
  3 +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/submodules/cmake)
  4 +
  5 +# ==============================================================================
  6 +# = Include build information
  7 +include(projectheader)
  8 +project_header(modbus-cpp)
  9 +
  10 +add_subdirectory(src)
  11 +
... ...
README.md
1 1 # modbus-cpp
2   -A TCP / RTU modbus library written in c++ as a header-only.
  2 +The goal was to write a TCP / RTU modbus library written in c++ as a header-only, but reality quickly followed suit.
  3 +To avoid large implementations in the header file and create a mess, I quickly decided to write it more conventional.
  4 +
  5 +The Modbus protocol description kan be found in the 'documentation' folder. [documentation/protocol.md]
  6 +The actual implementation is described in [documentation/implementation.d]
... ...
documentation/graphics/08_modbus_addressing_model.png 0 → 100644

59.8 KB

documentation/graphics/09_modbus_state_diagram.png 0 → 100644

104 KB

documentation/graphics/originals/modbus_drawings.odg 0 → 100644
No preview for this file type
documentation/implementation.md 0 → 100644
documentation/modbus-cpp.asta 0 → 100644
No preview for this file type
documentation/protocol.md 0 → 100644
  1 +# MODBUS Application Protocol SPecification V1.1b3
  2 +
  3 +## Introduction
  4 +
  5 +MODBUS is an application layer messaging protocol for client/server communication between
  6 +devices connected on different types of buses or networks.
  7 +
  8 +It is currently implemented using:
  9 +
  10 +* TCP/IP over Ethernet. See MODBUS Messaging Implementation Guide V1.0a.
  11 +* Asynchronous serial transmission over a variety of media (wire : EIA/TIA -232-E, EIA-422,
  12 +EIA/TIA-485-A; fiber, radio, etc.)
  13 +* MODBUS PLUS, a high speed token passing network.
  14 +
  15 +## Abbreviations
  16 +
  17 +|Abbreviation |Description |
  18 +|---|--------------------|
  19 +| ADU | Application Data Unit
  20 +| HDLC | High Level Data Link Control |
  21 +| HMI | Human Machine Interface |
  22 +| IETF | Internet Engineering Task Force |
  23 +| I/O | Input / Output |
  24 +| IP | Internet Protocol |
  25 +| MAC | Media Access Control |
  26 +| MB | MODBUS Protocol |
  27 +| MBAP | MODBUS Application Protocol |
  28 +| PDU | Protocol Data Unit |
  29 +| PLC | Programmable Logic Controller |
  30 +| TCP | Transmission Control Protocol |
  31 +
  32 +## Context
  33 +The MODBUS protocol allows an easy communication within all types of network architectures. Every type of devices (PLC, HMI, Control Panel, Driver, Motion control, I/O Device...) can use MODBUS protocol to initiate a remote operation.
  34 +The same communication can be done as well on serial line as on Ethernet TCP/IP networks. Gateways allow a communication between several types of buses or network using the MODBUS protocol.
  35 +
  36 +## General Description
  37 +
  38 +### Protocol description
  39 +The MODBUS protocol defines a simple protocol data unit (PDU) independent of the underlying communication layers. The mapping of MODBUS protocol on specific buses or network ccan introduce some additional fields on the Application Data Unit (ADU).
  40 +
  41 +```
  42 + ADU
  43 +<-------------------------------------------------------------------------------->
  44 ++--------------------+ +---------------+ +---------------------+ +-------------+
  45 +| Additional address | | Function code | | Data | | Error check |
  46 ++--------------------+ +---------------+ +---------------------+ +-------------+
  47 + <--------------------------------------->
  48 + PDU
  49 +General MODBUS frame
  50 +```
  51 +
  52 +The MODBUS application data unit is built by the client that initiates a MODBUS transaction. The function indicates to the server what kind of action to perform. The MODBUS application protocol establishes the format of a request initiated by a client.
  53 +The function code field of a MODBUS data unit is coded in one byte. Valid codes are in the range of 1..255 decimal ( where the range 128 - 255 is reserved and used for exception responses.) When a message is sent from a Client to a Server device the function code field tells the server what kind of action to perform. Function code "0" is not valid.
  54 +
  55 +Sub function codes are added to some function codes to define multiple actions.
  56 +The data field of messages sent from a client to server devices contains additional information that the server uses to take the action defined by the function code. This can include items like discrete and register addresses, the quantity of items to be handled, ad the count of actual data bytes in the field. The data field may be non-existent (of zero-length) in certain kinds of requests, in this case the server does not require any additional information. The function code alone specifies the action.
  57 +
  58 +If no error occurs related to the MODBUS function requested in a properly received MODBUS
  59 +ADU the data field of a response from a server to a client contains the data requested. If an
  60 +error related to the MODBUS function requested occurs, the field contains an exception code
  61 +that the server application can use to determine the next action to be taken.
  62 +For example a client can read the ON / OFF states of a group of discrete outputs or inputs or
  63 +it can read/write the data contents of a group of registers.
  64 +When the server responds to the client, it uses the function code field to indicate either a
  65 +normal (error-free) response or that some kind of error occurred (called an exception
  66 +response). For a normal response, the server simply echoes to the request the original
  67 +function code.
  68 +
  69 +For an exception response, the server returns a code that is equivalent to the original function
  70 +code from the request PDU with its most significant bit set to logic 1.
  71 +
  72 +The size of the MODBUS PDU is limited by the size constraint inherited from the first
  73 +MODBUS implementation on Serial Line network (max. RS485 ADU = 256 bytes).
  74 +
  75 +Therefore: <br>
  76 +MODBUS PDU for serial line communication = 256 - Server address (1 byte) - CRC (2
  77 +bytes) = 253 bytes.
  78 +
  79 +Consequently:<br>
  80 +RS232 / RS485 ADU = 253 bytes + Server address (1 byte) + CRC (2 bytes) = 256 bytes.
  81 +TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes.
  82 +The MODBUS protocol defines three PDUs. They are :<br>
  83 +
  84 +| Full name | PDU | Definition | Description |
  85 +|-----------|-----|------------|-------------|
  86 +| MODBUS Request PDU | mb_req_pdu | {function_code, request_data} | function_code = [1 byte] MODBUS function code<br> request_data = [n bytes] This field is function code dependent and usually contains information such as variable references, variable counts, data offsets, sub-function codes etc |
  87 +| MODBUS Response PDU | mb_rsp_pdu | {function_code, response_data}| function_code = [1 byte] MODBUS function code <br> response_data = [n bytes] This field is function code dependent and usually contains information such as variable references, variable counts, data offsets, sub-function codes, etc. |
  88 +| MODBUS Exception Response PDU | mb_excep_rsp_pdu | {exception-function_code, request_data} | exception-function_code = [1 byte] MODBUS function code + 0x80 <br> exception_code = [1 byte] MODBUS Exception Code Defined in table "MODBUS Exception Codes" (see section 7 ). |
  89 +
  90 +
  91 +### Data Encoding
  92 +
  93 +MODBUS uses a ‘big-Endian’ representation for addresses and data items. This means that when a numerical quantity larger than a single byte is transmitted, the most significant
  94 +byte is sent first. So for example :
  95 +
  96 +| Register size | value |
  97 +|---------------|--------|
  98 +| 16 - bits | 0x1234 |
  99 +
  100 +the first byte sent is 0x12 then 0x34. By bitshifting and adding, the original value can be compiled. ( 0x12 << 8u = 0x1200 + 0x34 = 0x1234 )
  101 +
  102 +## MODBUS Data Model
  103 +MODBUS bases its data model on a series of tables that have distinguishing characteristics. The four primary tables are:
  104 +
  105 +| Primary tables | Object type | Type of | Comments |
  106 +|----------------|-------------|---------|----------|
  107 +| Discretes Input | Single Bit | Read-Only | This type of data can be provided by an I/O system. |
  108 +| Coils | Single Bit | Read-Write | This type of data can be alterable by an application program. |
  109 +| Input Registers | 16-bit word | Read-Only | This type of data can be provided by an I/O system |
  110 +| Holding Registers | 16-bit word | Read-Write | This type of data can be alterable by an application program. |
  111 +
  112 +The distinctions between inputs and outputs, and between bit -addressable and word-
  113 +addressable data items, do not imply any application behavior. It is perfectly acceptable, and
  114 +very common, to regard all four tables as overlaying one another, if this is the most natural
  115 +interpretation on the target machine in question.
  116 +For each of the primary tables, the protocol allows individual selection of 65536 data items,
  117 +and the operations of read or write of those items are designed to span multiple consecutive
  118 +data items up to a data size limit which is dependent on the transaction function code.
  119 +It’s obvious that all the data handled via MODBUS (bits, registers) must be located in device
  120 +application memory. But physical address in memory should not be confused with data
  121 +reference. The only requirement is to link data reference with physical address.
  122 +MODBUS logical reference numbers, which are used in MODBUS funct ions, are unsigned
  123 +integer indices starting at zero.
  124 +
  125 +## MODBUS Adressing Model
  126 +The MODBUS application protocol defines precisely PDU addressing rules.
  127 +In a MODBUS PDU each data is addressed from 0 to 65535.
  128 +It also defines clearly a MODBUS data model composed of 4 blocks that comprises several
  129 +elements numbered from 1 to n.
  130 +In the MODBUS data Model each element within a data block is numbered from 1 to n.
  131 +Afterwards the MODBUS data model has to be bound to the device application ( IEC -61131
  132 +object, or other application model).
  133 +The pre-mapping between the MODBUS data model and the device application is totally
  134 +vendor device specific.
  135 +
  136 +![MODBUS Addressing Model](graphics/08_modbus_addressing_model.png)
  137 +
  138 +## Define MODBUS Transaction
  139 +The following state diagram describes the generic processing oif a MODBUS transaction in server side.
  140 +
  141 +![MODBUS Transaction State Diagram](graphics/09_modbus_state_diagram.png)
  142 +
  143 +Once the request has been processed by a server, a MODBUS response using the
  144 +adequate MODBUS server transaction is built.
  145 +Depending on the result of the processing two types of response are built :
  146 +
  147 + * A positive MODBUS response :
  148 + * the response function code = the request function code
  149 +
  150 + * A MODBUS Exception response ( see section 7 ):
  151 + * the objective is to provide to the client relevant information concerning the
  152 +error detected during the processing ;
  153 + * the exception function code = the request function code + 0x80 ;
  154 + * an exception code is provided to indicate the reason of the error.
  155 +
  156 +## Function Code Categories
  157 +
  158 +There are three categories of MODBUS Functions codes. They are :
  159 +
  160 +<strong>Public Function Codes</strong>
  161 +
  162 +* Are well defined function codes,
  163 +* guaranteed to be unique,
  164 +* validated by the MODBUS.org community,
  165 +* publicly documented
  166 +* have available conformance test,
  167 +* includes both defined public assigned function codes as well as unassigned function codes reserved for future use.
  168 +
  169 +<strong>User-Defined Function Codes</strong>
  170 +
  171 +* there are two ranges of user-defined function codes, i.e. 65 to 72 and from 100 to 110 decimal.
  172 +* user can select and implement a function code that is not supported by the specification.
  173 +* there is no guarantee that the use of the selected function code will be unique
  174 +* if the user wants to re-position the functionality as a public function code, he must
  175 +initiate an RFC to introduce the change into the public category and to have a new
  176 +public function code assigned.
  177 +* MODBUS Organization, Inc expressly reserves the right to develop the proposed RFC.
  178 +
  179 +<strong>Reserved Function Codes</strong>
  180 +
  181 +* Function Codes currently used by some companies for legacy products and that
  182 +are not available for public use.
  183 +
  184 +### Public Function Code Definition
  185 +
  186 +| Domain | #Bits | Type | Description | Function code | Sub code | (hex) |
  187 +|--------|-------|------|----------------------|---------------|----------|-------|
  188 +| Data Access | Bit Access | Physical Discrete Inputs | Read Discrete Inputs | 02 | | 0x02 |
  189 +| | | Internal Bits or Physical Coils| Read Coils | 01 | | 0x01 |
  190 +| | | | Write Single Coil | 05 | | 0x05 |
  191 +| | | | Write Multiple Coils | 15 | | 0x0F |
  192 +| | 16 bits access| Physical Input Registers | Read Input Register | 04 | | 0x04 |
  193 +| | | Internal Registers or Physical Ouput Registers| Read Holding Registers | 03 | | 0x03 |
  194 +| | | | Write Single Register | 06 | | 0x06 |
  195 +| | | | Write multiple Registers | 16 | | 0x10 |
  196 +| | | | Read/Write Multiple Registers | 23 | | 0x17 |
  197 +| | | | Mask Write Register | 22 | | 0x16 |
  198 +| | | | Read FIFO queue | 24 | | 0x18 |
  199 +| | | File Record Access | Read File record | 20 | | 0x14 |
  200 +| | | | Write File Record | 21 | | 0x15 |
  201 +| Diagnostics | | | Read Exception Status | 07 | | 0x07 |
  202 +| | | | Diagnostic | 08 | 00-18,20 | 0x08 |
  203 +| | | | Get Com event counter | 11 | | 0x0B |
  204 +| | | | Get Com Event Log | 12 | | 0x0C |
  205 +| | | | Report Server ID | 17 | | 0x11 |
  206 +| | | | Read device Identification | 43 | 14 | 0x2B |
  207 +| Other | | | Encapsulated Interface Transport | 43 | 13, 14 | 0x2B |
  208 +| | | | CANopen General Reference | 43 | 13 | 0x2B |
  209 +
  210 +## Function codes descriptions
  211 +
  212 +### 01 (0x01) Read Coils
  213 +This function code is used to read from 1 to 2000 contiguous status of coils in a remote
  214 +device. The Request PDU specifies the starting address, i.e. the address of the first coil
  215 +specified, and the number of coils. In the PDU Coils are addressed starting at zero. Therefore
  216 +coils numbered 1-16 are addressed as 0-15.<br>
  217 +The coils in the response message are packed as one coil per bit of the data field. Status is
  218 +indicated as 1= ON and 0= OFF. The LSB of the first data byte contains the output addressed
  219 +in the query. The other coils follow toward the high order end of this byte, and from low order
  220 +to high order in subsequent bytes.<br>
  221 +If the returned output quantity is not a multiple of eight, the remaining bits in the final data
  222 +byte will be padded with zeros (toward the high order end of the byte). The Byte Count field
  223 +specifies the quantity of complete bytes of data.
  224 +
  225 +#### Request
  226 +
  227 +| Description | #Bytes | Value |
  228 +|---|---|---|
  229 +| Function code | 1 Byte | <strong>0x01</strong> |
  230 +| Starting Address | 2 Bytes | 0x0000 to 0xFFFF |
  231 +| Quantity of coils | 2 Bytes | 1 to 2000 (0x7D0) |
  232 +
  233 +#### Response
  234 +
  235 +| Description | #Bytes | Value |
  236 +|---|---|---|
  237 +| Function code | 1 Byte | <strong>0x01</strong> |
  238 +| Byte count | 1 Byte | <strong>N*</strong> |
  239 +| Coil Status | <strong>n</strong> Byte | n = N or N + 1 |
  240 +
  241 +<strong>*N</strong> = Quantity of Outputs / 8, if the remainder is different of 0 => N = N+1
  242 +
  243 +#### Error
  244 +
  245 +| Description | #Bytes | Value |
  246 +|---|---|---|
  247 +| Function Code | 1 Byte | <strong>Function code + 0x80</strong> |
  248 +| Exception Code | 1 Byte | 01, 02, 03 or 04 |
  249 +
  250 +Here is an example of a request to read discrete output 20-38:
  251 +
  252 +|Request | ++ | Response | ++ |
  253 +|--------|----|----------|----|
  254 +| Field Name | (Hex) | Field Name | (Hex) |
  255 +| Function | 0x01 | Function | 0x01 |
  256 +| Starting Address Hi | 0x00 | Byte Count | 0x03 |
  257 +| Starting Address Lo | 0x13 | Outputs status 27-20 | 0xCD |
  258 +| Quantity of Outputs Hi | 0x00 | Outputs status 25-28 | 0x6B |
  259 +| Quantity of Outputs Lo | 0x13 | Outputs status 38-36 | 0x05 |
  260 +
  261 +The status of outputs 27–20 is shown as the byte value CD hex, or binary 1100 1101. Output 27 is the MSB of this byte, and output 20 is the LSB.
  262 +By convention, bits within a byte are shown with the MSB to the left, and the LSB to the right. Thus the outputs in the first byte are ‘27 through 20’, from left to right. The next byte has outputs ‘35 through 28’, left to right. As the bits are transmitted serially, they flow from LSB to MSB: 20 . . . 27, 28 . . . 35, and so on.<br>
  263 +In the last data byte, the status of outputs 38-36 is shown as the byte value 05 hex, or binary 0000 0101. Output 38 is in the sixth bit position from the left, and output 36 is the LSB of this byte. The five remaining high order bits are zero filled.
  264 +
  265 +### 02 (0x02) Read Discrete Inputs
  266 +This function code is used to read from 1 to 2000 contiguous status of discrete inputs in a remote device. The Request PDU specifies the starting address, i.e. the address of the first input specified, and the number of inputs. In the PDU Discrete Inputs a re addressed starting
  267 +at zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15.
  268 +The discrete inputs in the response message are packed as one input per bit of the data field.
  269 +Status is indicated as 1= ON; 0= OFF. The LSB of the first data byte contains the input
  270 +addressed in the query. The other inputs follow toward the high order end of this byte, and from low order to high order in subsequent bytes.
  271 +If the returned input quantity is not a multiple of eight, the remaining bits in the final d ata byte will be padded with zeros (toward the high order end of the byte). The Byte Count field specifies the quantity of complete bytes of data.
  272 +
  273 +#### Request
  274 +
  275 +| Description | #Bytes | Value |
  276 +|---|---|---|
  277 +| Function code | 1 Byte | <strong>0x02</strong> |
  278 +| Starting Address | 2 Bytes | 0x0000 to 0xFFFF |
  279 +| Quantity of Inputs | 2 Bytes | 1 to 2000 (0x7D0) |
  280 +
  281 +#### Response
  282 +
  283 +| Description | #Bytes | Value |
  284 +|---|---|---|
  285 +| Function code | 1 Byte | <strong>0x02</strong> |
  286 +| Byte count | 1 Byte | <strong>N*</strong> |
  287 +| Input Status | <strong>N*</strong> x 1 Byte | n = N or N + 1 |
  288 +
  289 +<strong>*N</strong> = Quantity of Outputs / 8, if the remainder is different of 0 => N = N+1
  290 +
  291 +#### Error
  292 +
  293 +| Description | #Bytes | Value |
  294 +|---|---|---|
  295 +| Function Code | 1 Byte | <strong>Function code + 0x82</strong> |
  296 +| Exception Code | 1 Byte | 01, 02, 03 or 04 |
  297 +
  298 +The status of discrete inputs 204–197 is shown as the byte value AC hex, or binary 1010 1100. Input 204 is the MSB of this byte, and input 197 is the LSB. The status of discrete inputs 218–213 is shown as the byte value 35 hex, or binary 0011 0101. Input 218 is in the third bit position from the left, and input 213 is the LSB.
  299 +
  300 +### 03 (0x03) Read Holding Registers
  301 +This function code is used to read the contents of a contiguous block of holding registers in a remote device. The Request PDU specifies the starting r egister address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore registers numbered 1-16 are addressed as 0-15. The register data in the response message are packed as two bytes per register, with the binary contents right justified within each byte. For each register, the first byte contains the high order bits and the second contains the low order bits.
  302 +
  303 +#### Request
  304 +
  305 +| Description | #Bytes | Value |
  306 +|---|---|---|
  307 +| Function code | 1 Byte | <strong>0x03</strong> |
  308 +| Starting Address | 2 Bytes | 0x0000 to 0xFFFF |
  309 +| Quantity of Registers | 2 Bytes | 1 to 125 (0x7D) |
  310 +
  311 +#### Response
  312 +
  313 +| Description | #Bytes | Value |
  314 +|---|---|---|
  315 +| Function code | 1 Byte | <strong>0x03</strong> |
  316 +| Byte count | 1 Byte | <strong>2 x N*</strong> |
  317 +| Register value | <strong>N*</strong> x 2 Bytes | |
  318 +
  319 +<strong>*N</strong> = Quantity of Registers
  320 +
  321 +#### Error
  322 +
  323 +| Description | #Bytes | Value |
  324 +|---|---|---|
  325 +| Function Code | 1 Byte | <strong>Function code + 0x83</strong> |
  326 +| Exception Code | 1 Byte | 01, 02, 03 or 04 |
  327 +
  328 +Here is an example of a request to read registers 108 - 110:
  329 +
  330 +|Request| | Response| |
  331 +|---|---|----|---|
  332 +| Field Name | (Hex) | Field Name | (Hex) |
  333 +| Function | 0x03 | Function | 0x03 |
  334 +| Starting Address Hi | 0x00 | Byte Count | 0x06 |
  335 +| Starting Address Lo | 0x6B | Register value Hi (108) | 0x02 |
  336 +| No. of Registers Hi | 0x00 | Register value Lo (108) | 0x2B |
  337 +| No. of Registers Lo | 0x03 | Register value Hi (109) | 0x00 |
  338 +| | | Register value Lo (109) | 0x00 |
  339 +| | | Register value Hi (110) | 0x00 |
  340 +| | | Register value Lo (110) | 0x64 |
  341 +
  342 +The contents of register 108 are shown as the two byte values of 02 2B hex, or 555 decimal. The contents of registers 109–110 are 00 00 and 00 64 hex, or 0 and 100 decimal, respectively.
  343 +
  344 +### 04 (0x04) Read Input Registers
  345 +This function code is used to read from 1 to 125 contiguous input registers in a remote device. The Request PDU specifies the starting register address and the number of registers. In the PDU Registers are addressed starting at zero. Therefore input registers n umbered 1-16 are addressed as 0-15.<br>
  346 +The register data in the response message are packed as two bytes per register, with the binary contents right justified within each byte. For each register, the first byte contains the high order bits and the second contains the low order bits.
  347 +
  348 +#### Request
  349 +
  350 +| Description | #Bytes | Value |
  351 +|---|---|---|
  352 +| Function code | 1 Byte | <strong>0x04</strong> |
  353 +| Starting Address | 2 Bytes | 0x0000 to 0xFFFF |
  354 +| Quantity of Input Registers | 2 Bytes | 1 to 125 (0x007D) |
  355 +
  356 +#### Response
  357 +
  358 +| Description | #Bytes | Value |
  359 +|---|---|---|
  360 +| Function code | 1 Byte | <strong>0x04</strong> |
  361 +| Byte count | 1 Byte | <strong>2 x N*</strong> |
  362 +| Register value | <strong>N*</strong> x 2 Bytes | |
  363 +
  364 +<strong>*N</strong> = Quantity of Input Registers
  365 +
  366 +#### Error
  367 +
  368 +| Description | #Bytes | Value |
  369 +|---|---|---|
  370 +| Function Code | 1 Byte | <strong>Function code + 0x84</strong> |
  371 +| Exception Code | 1 Byte | 01, 02, 03 or 04 |
  372 +
  373 +Here is an example of a request to read input register 9:
  374 +
  375 +|Request| | Response| |
  376 +|---|---|----|---|
  377 +| Field Name | (Hex) | Field Name | (Hex) |
  378 +| Function | 0x04 | Function | 0x04 |
  379 +| Starting Address Hi | 0x00 | Byte Count | 0x02 |
  380 +| Starting Address Lo | 0x08 | Input Reg. 9 Hi | 0x00 |
  381 +| Quantity of Input Reg. Hi | 0x00 | Input Reg. 9 Lo | 0x0A |
  382 +| QUantity of Input Reg. Lo | 0x01 | | |
  383 +
  384 +The contents of input register 9 are shown as the two byte values of 00 0A hex, or 10 decimal.
  385 +
  386 +### 05 (0x05) Write Single Coil
  387 +This function code is used to write a single output to either ON or OFF in a remote device. The requested ON/OFF state is specified by a constant in the request data field. A value of FF 00 hex requests the output to be ON. A value of 00 00 requests it to be OFF. All other values are illegal and will not affect the output. The Request PDU specifies the address of the coil to be forced. Coils are addressed starting at zero. Therefore coil numbered 1 is addressed as 0. The requested ON/OFF state is specified by a constant in the Coil Value field. A value of 0XFF00 requests the coil to be ON. A value of 0x0000 requests the coil to be off. All other values are illegal and will not affect the coil.
  388 +
  389 +The normal response is an echo of the request, returned after the coil state has been written.
  390 +
  391 +#### Request
  392 +
  393 +| Description | #Bytes | Value |
  394 +|---|---|---|
  395 +| Function code | 1 Byte | <strong>0x05</strong> |
  396 +| Output Address | 2 Bytes | 0x0000 to 0xFFFF |
  397 +| Output Value | 2 Bytes | 0x0000 or 0xFF00 |
  398 +
  399 +#### Response
  400 +
  401 +| Description | #Bytes | Value |
  402 +|---|---|---|
  403 +| Function code | 1 Byte | <strong>0x05</strong> |
  404 +| Output Address | 2 Bytes | 0x0000 to 0xFFFF |
  405 +| Output Value | 2 Bytes | 0x0000 or 0xFF00 |
  406 +
  407 +<strong>*N</strong> = Quantity of Input Registers
  408 +
  409 +#### Error
  410 +
  411 +| Description | #Bytes | Value |
  412 +|---|---|---|
  413 +| Error Code | 1 Byte | <strong>Function code + 0x85</strong> |
  414 +| Exception Code | 1 Byte | 01, 02, 03 or 04 |
  415 +
  416 +Here is an example of a request to read input register 9:
  417 +
  418 +|Request| | Response| |
  419 +|---|---|----|---|
  420 +| Field Name | (Hex) | Field Name | (Hex) |
  421 +| Function | 0x05 | Function | 0x05 |
  422 +| Output Address Hi | 0x00 | Output Address Hi | 0x00 |
  423 +| Output Address Lo | 0xAC | Output Address Lo | 0xAC |
  424 +| Output Value Hi | 0xFF | Output Value Hi | 0xFF |
  425 +| Output Value Lo | 0x00 | Output Value Lo | 0x00 |
  426 +
... ...
include/imodbus.h 0 → 100644
include/modbus.h deleted
1   -/*
2   - * Copyright (c)2022 Peter M. Groen
3   - *
4   - * This source code is licensed under the MIT license found in the
5   - * LICENSE file in hte root directory of this source tree
6   - */
7   -
8   -#pragma once
9   -
10   -#include <cstring>
11   -#include <stdint.h>
12   -#include <string>
13   -
14   -#ifdef ENABLE_MODBUS_LOGGING
15   -#include <cstdio>
16   -#define LOG(fmt, ...) printf("[ modbuspp ]" fmt, ##__VA_ARGS__)
17   -#else
18   -#define LOG(...) (void)0
19   -#endif
20   -
21   -#include <unistd.h>
22   -#include <sys/socket.h>
23   -#include <netinet/in.h>
24   -#include <arpa/inet.h>
25   -using X_SOCKET = int;
26   -
27   -#define X_ISVALIDSOCKET(s) ((s) >= 0)
28   -#define X_CLOSE_SOCKET(s) close(s)
29   -#define X_ISCONNECTSUCCEED(s) ((s) >= 0)
30   -
31   -using SOCKADDR = struct sockaddr;
32   -using SOCKADDR_IN = struct sockaddr_in;
33   -
34   -#define MAX_MSG_LENGTH 260
35   -
36   -///Function Code
37   -#define READ_COILS 0x01
38   -#define READ_INPUT_BITS 0x02
39   -#define READ_REGS 0x03
40   -#define READ_INPUT_REGS 0x04
41   -#define WRITE_COIL 0x05
42   -#define WRITE_REG 0x06
43   -#define WRITE_COILS 0x0F
44   -#define WRITE_REGS 0x10
45   -
46   -///Exception Codes
47   -
48   -#define EX_ILLEGAL_FUNCTION 0x01 // Function Code not Supported
49   -#define EX_ILLEGAL_ADDRESS 0x02 // Output Address not exists
50   -#define EX_ILLEGAL_VALUE 0x03 // Output Value not in Range
51   -#define EX_SERVER_FAILURE 0x04 // Slave Deive Fails to process request
52   -#define EX_ACKNOWLEDGE 0x05 // Service Need Long Time to Execute
53   -#define EX_SERVER_BUSY 0x06 // Server Was Unable to Accept MB Request PDU
54   -#define EX_NEGATIVE_ACK 0x07
55   -#define EX_MEM_PARITY_PROB 0x08
56   -#define EX_GATEWAY_PROBLEMP 0x0A // Gateway Path not Available
57   -#define EX_GATEWAY_PROBLEMF 0x0B // Target Device Failed to Response
58   -#define EX_BAD_DATA 0XFF // Bad Data lenght or Address
59   -
60   -#define BAD_CON -1
61   -
62   -/**
63   - * Modbus Operator Class
64   - * Providing networking support and mobus operation support.
65   - */
66   -class modbus
67   -{
68   -
69   -public:
70   - bool err{};
71   - int err_no{};
72   - std::string error_msg;
73   -
74   - modbus(std::string host, uint16_t port);
75   - ~modbus();
76   -
77   - bool modbus_connect();
78   - void modbus_close() const;
79   -
80   - bool is_connected() const { return _connected; }
81   -
82   - void modbus_set_slave_id(int id);
83   -
84   - int modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer);
85   - int modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer);
86   - int modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer);
87   - int modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer);
88   -
89   - int modbus_write_coil(uint16_t address, const bool &to_write);
90   - int modbus_write_register(uint16_t address, const uint16_t &value);
91   - int modbus_write_coils(uint16_t address, uint16_t amount, const bool *value);
92   - int modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value);
93   -
94   -private:
95   - bool _connected{};
96   - uint16_t PORT{};
97   - uint32_t _msg_id{};
98   - int _slaveid{};
99   - std::string HOST;
100   -
101   - X_SOCKET _socket{};
102   - SOCKADDR_IN _server{};
103   -
104   - void modbus_build_request(uint8_t *to_send, uint16_t address, int func) const;
105   -
106   - int modbus_read(uint16_t address, uint16_t amount, int func);
107   - int modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value);
108   -
109   - ssize_t modbus_send(uint8_t *to_send, size_t length);
110   - ssize_t modbus_receive(uint8_t *buffer) const;
111   -
112   - void modbuserror_handle(const uint8_t *msg, int func);
113   -
114   - void set_bad_con();
115   - void set_bad_input();
116   -};
117   -
118   -/**
119   - * Main Constructor of Modbus Connector Object
120   - * @param host IP Address of Host
121   - * @param port Port for the TCP Connection
122   - * @return A Modbus Connector Object
123   - */
124   -inline modbus::modbus(std::string host, uint16_t port = 502)
125   -{
126   - HOST = host;
127   - PORT = port;
128   - _slaveid = 1;
129   - _msg_id = 1;
130   - _connected = false;
131   - err = false;
132   - err_no = 0;
133   - error_msg = "";
134   -}
135   -
136   -/**
137   - * Destructor of Modbus Connector Object
138   - */
139   -inline modbus::~modbus(void) = default;
140   -
141   -/**
142   - * Modbus Slave ID Setter
143   - * @param id ID of the Modbus Server Slave
144   - */
145   -inline void modbus::modbus_set_slave_id(int id)
146   -{
147   - _slaveid = id;
148   -}
149   -
150   -/**
151   - * Build up a Modbus/TCP Connection
152   - * @return If A Connection Is Successfully Built
153   - */
154   -inline bool modbus::modbus_connect()
155   -{
156   - if (HOST.empty() || PORT == 0)
157   - {
158   - LOG("Missing Host and Port");
159   - return false;
160   - }
161   - else
162   - {
163   - LOG("Found Proper Host %s and Port %d", HOST.c_str(), PORT);
164   - }
165   -
166   - _socket = socket(AF_INET, SOCK_STREAM, 0);
167   - if (!X_ISVALIDSOCKET(_socket))
168   - {
169   - LOG("Error Opening Socket");
170   - return false;
171   - }
172   - else
173   - {
174   - LOG("Socket Opened Successfully");
175   - }
176   -
177   - struct timeval timeout
178   - {
179   - };
180   - timeout.tv_sec = 20; // after 20 seconds connect() will timeout
181   - timeout.tv_usec = 0;
182   -
183   - setsockopt(_socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(timeout));
184   - setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));
185   - _server.sin_family = AF_INET;
186   - _server.sin_addr.s_addr = inet_addr(HOST.c_str());
187   - _server.sin_port = htons(PORT);
188   -
189   - if (!X_ISCONNECTSUCCEED(connect(_socket, (SOCKADDR *)&_server, sizeof(_server))))
190   - {
191   - LOG("Connection Error");
192   - return false;
193   - }
194   -
195   - LOG("Connected");
196   - _connected = true;
197   - return true;
198   -}
199   -
200   -/**
201   - * Close the Modbus/TCP Connection
202   - */
203   -inline void modbus::modbus_close() const
204   -{
205   - X_CLOSE_SOCKET(_socket);
206   - LOG("Socket Closed");
207   -}
208   -
209   -/**
210   - * Modbus Request Builder
211   - * @param to_send Message Buffer to Be Sent
212   - * @param address Reference Address
213   - * @param func Modbus Functional Code
214   - */
215   -inline void modbus::modbus_build_request(uint8_t *to_send, uint16_t address, int func) const
216   -{
217   - to_send[0] = (uint8_t)(_msg_id >> 8u);
218   - to_send[1] = (uint8_t)(_msg_id & 0x00FFu);
219   - to_send[2] = 0;
220   - to_send[3] = 0;
221   - to_send[4] = 0;
222   - to_send[6] = (uint8_t)_slaveid;
223   - to_send[7] = (uint8_t)func;
224   - to_send[8] = (uint8_t)(address >> 8u);
225   - to_send[9] = (uint8_t)(address & 0x00FFu);
226   -}
227   -
228   -/**
229   - * Write Request Builder and Sender
230   - * @param address Reference Address
231   - * @param amount Amount of data to be Written
232   - * @param func Modbus Functional Code
233   - * @param value Data to Be Written
234   - */
235   -inline int modbus::modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value)
236   -{
237   - int status = 0;
238   - uint8_t *to_send;
239   - if (func == WRITE_COIL || func == WRITE_REG)
240   - {
241   - to_send = new uint8_t[12];
242   - modbus_build_request(to_send, address, func);
243   - to_send[5] = 6;
244   - to_send[10] = (uint8_t)(value[0] >> 8u);
245   - to_send[11] = (uint8_t)(value[0] & 0x00FFu);
246   - status = modbus_send(to_send, 12);
247   - }
248   - else if (func == WRITE_REGS)
249   - {
250   - to_send = new uint8_t[13 + 2 * amount];
251   - modbus_build_request(to_send, address, func);
252   - to_send[5] = (uint8_t)(7 + 2 * amount);
253   - to_send[10] = (uint8_t)(amount >> 8u);
254   - to_send[11] = (uint8_t)(amount & 0x00FFu);
255   - to_send[12] = (uint8_t)(2 * amount);
256   - for (int i = 0; i < amount; i++)
257   - {
258   - to_send[13 + 2 * i] = (uint8_t)(value[i] >> 8u);
259   - to_send[14 + 2 * i] = (uint8_t)(value[i] & 0x00FFu);
260   - }
261   - status = modbus_send(to_send, 13 + 2 * amount);
262   - }
263   - else if (func == WRITE_COILS)
264   - {
265   - to_send = new uint8_t[14 + (amount - 1) / 8];
266   - modbus_build_request(to_send, address, func);
267   - to_send[5] = (uint8_t)(7 + (amount + 7) / 8);
268   - to_send[10] = (uint8_t)(amount >> 8u);
269   - to_send[11] = (uint8_t)(amount & 0x00FFu);
270   - to_send[12] = (uint8_t)((amount + 7) / 8);
271   - for (int i = 0; i < (amount + 7) / 8; i++)
272   - to_send[13 + i] = 0; // init needed before summing!
273   - for (int i = 0; i < amount; i++)
274   - {
275   - to_send[13 + i / 8] += (uint8_t)(value[i] << (i % 8u));
276   - }
277   - status = modbus_send(to_send, 14 + (amount - 1) / 8);
278   - }
279   - delete[] to_send;
280   - return status;
281   -}
282   -
283   -/**
284   - * Read Request Builder and Sender
285   - * @param address Reference Address
286   - * @param amount Amount of Data to Read
287   - * @param func Modbus Functional Code
288   - */
289   -inline int modbus::modbus_read(uint16_t address, uint16_t amount, int func)
290   -{
291   - uint8_t to_send[12];
292   - modbus_build_request(to_send, address, func);
293   - to_send[5] = 6;
294   - to_send[10] = (uint8_t)(amount >> 8u);
295   - to_send[11] = (uint8_t)(amount & 0x00FFu);
296   - return modbus_send(to_send, 12);
297   -}
298   -
299   -/**
300   - * Read Holding Registers
301   - * MODBUS FUNCTION 0x03
302   - * @param address Reference Address
303   - * @param amount Amount of Registers to Read
304   - * @param buffer Buffer to Store Data Read from Registers
305   - */
306   -inline int modbus::modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
307   -{
308   - if (_connected)
309   - {
310   - modbus_read(address, amount, READ_REGS);
311   - uint8_t to_rec[MAX_MSG_LENGTH];
312   - ssize_t k = modbus_receive(to_rec);
313   - if (k == -1)
314   - {
315   - set_bad_con();
316   - return BAD_CON;
317   - }
318   - modbuserror_handle(to_rec, READ_REGS);
319   - if (err)
320   - return err_no;
321   - for (auto i = 0; i < amount; i++)
322   - {
323   - buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;
324   - buffer[i] += (uint16_t)to_rec[10u + 2u * i];
325   - }
326   - return 0;
327   - }
328   - else
329   - {
330   - set_bad_con();
331   - return BAD_CON;
332   - }
333   -}
334   -
335   -/**
336   - * Read Input Registers
337   - * MODBUS FUNCTION 0x04
338   - * @param address Reference Address
339   - * @param amount Amount of Registers to Read
340   - * @param buffer Buffer to Store Data Read from Registers
341   - */
342   -inline int modbus::modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
343   -{
344   - if (_connected)
345   - {
346   - modbus_read(address, amount, READ_INPUT_REGS);
347   - uint8_t to_rec[MAX_MSG_LENGTH];
348   - ssize_t k = modbus_receive(to_rec);
349   - if (k == -1)
350   - {
351   - set_bad_con();
352   - return BAD_CON;
353   - }
354   - modbuserror_handle(to_rec, READ_INPUT_REGS);
355   - if (err)
356   - return err_no;
357   - for (auto i = 0; i < amount; i++)
358   - {
359   - buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;
360   - buffer[i] += (uint16_t)to_rec[10u + 2u * i];
361   - }
362   - return 0;
363   - }
364   - else
365   - {
366   - set_bad_con();
367   - return BAD_CON;
368   - }
369   -}
370   -
371   -/**
372   - * Read Coils
373   - * MODBUS FUNCTION 0x01
374   - * @param address Reference Address
375   - * @param amount Amount of Coils to Read
376   - * @param buffer Buffer to Store Data Read from Coils
377   - */
378   -inline int modbus::modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer)
379   -{
380   - if (_connected)
381   - {
382   - if (amount > 2040)
383   - {
384   - set_bad_input();
385   - return EX_BAD_DATA;
386   - }
387   - modbus_read(address, amount, READ_COILS);
388   - uint8_t to_rec[MAX_MSG_LENGTH];
389   - ssize_t k = modbus_receive(to_rec);
390   - if (k == -1)
391   - {
392   - set_bad_con();
393   - return BAD_CON;
394   - }
395   - modbuserror_handle(to_rec, READ_COILS);
396   - if (err)
397   - return err_no;
398   - for (auto i = 0; i < amount; i++)
399   - {
400   - buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
401   - }
402   - return 0;
403   - }
404   - else
405   - {
406   - set_bad_con();
407   - return BAD_CON;
408   - }
409   -}
410   -
411   -/**
412   - * Read Input Bits(Discrete Data)
413   - * MODBUS FUNCITON 0x02
414   - * @param address Reference Address
415   - * @param amount Amount of Bits to Read
416   - * @param buffer Buffer to store Data Read from Input Bits
417   - */
418   -inline int modbus::modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer)
419   -{
420   - if (_connected)
421   - {
422   - if (amount > 2040)
423   - {
424   - set_bad_input();
425   - return EX_BAD_DATA;
426   - }
427   - modbus_read(address, amount, READ_INPUT_BITS);
428   - uint8_t to_rec[MAX_MSG_LENGTH];
429   - ssize_t k = modbus_receive(to_rec);
430   - if (k == -1)
431   - {
432   - set_bad_con();
433   - return BAD_CON;
434   - }
435   - if (err)
436   - return err_no;
437   - for (auto i = 0; i < amount; i++)
438   - {
439   - buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
440   - }
441   - modbuserror_handle(to_rec, READ_INPUT_BITS);
442   - return 0;
443   - }
444   - else
445   - {
446   - return BAD_CON;
447   - }
448   -}
449   -
450   -/**
451   - * Write Single Coils
452   - * MODBUS FUNCTION 0x05
453   - * @param address Reference Address
454   - * @param to_write Value to be Written to Coil
455   - */
456   -inline int modbus::modbus_write_coil(uint16_t address, const bool &to_write)
457   -{
458   - if (_connected)
459   - {
460   - int value = to_write * 0xFF00;
461   - modbus_write(address, 1, WRITE_COIL, (uint16_t *)&value);
462   - uint8_t to_rec[MAX_MSG_LENGTH];
463   - ssize_t k = modbus_receive(to_rec);
464   - if (k == -1)
465   - {
466   - set_bad_con();
467   - return BAD_CON;
468   - }
469   - modbuserror_handle(to_rec, WRITE_COIL);
470   - if (err)
471   - return err_no;
472   - return 0;
473   - }
474   - else
475   - {
476   - set_bad_con();
477   - return BAD_CON;
478   - }
479   -}
480   -
481   -/**
482   - * Write Single Register
483   - * FUCTION 0x06
484   - * @param address Reference Address
485   - * @param value Value to Be Written to Register
486   - */
487   -inline int modbus::modbus_write_register(uint16_t address, const uint16_t &value)
488   -{
489   - if (_connected)
490   - {
491   - modbus_write(address, 1, WRITE_REG, &value);
492   - uint8_t to_rec[MAX_MSG_LENGTH];
493   - ssize_t k = modbus_receive(to_rec);
494   - if (k == -1)
495   - {
496   - set_bad_con();
497   - return BAD_CON;
498   - }
499   - modbuserror_handle(to_rec, WRITE_COIL);
500   - if (err)
501   - return err_no;
502   - return 0;
503   - }
504   - else
505   - {
506   - set_bad_con();
507   - return BAD_CON;
508   - }
509   -}
510   -
511   -/**
512   - * Write Multiple Coils
513   - * MODBUS FUNCTION 0x0F
514   - * @param address Reference Address
515   - * @param amount Amount of Coils to Write
516   - * @param value Values to Be Written to Coils
517   - */
518   -inline int modbus::modbus_write_coils(uint16_t address, uint16_t amount, const bool *value)
519   -{
520   - if (_connected)
521   - {
522   - uint16_t *temp = new uint16_t[amount];
523   - for (int i = 0; i < amount; i++)
524   - {
525   - temp[i] = (uint16_t)value[i];
526   - }
527   - modbus_write(address, amount, WRITE_COILS, temp);
528   - delete[] temp;
529   - uint8_t to_rec[MAX_MSG_LENGTH];
530   - ssize_t k = modbus_receive(to_rec);
531   - if (k == -1)
532   - {
533   - set_bad_con();
534   - return BAD_CON;
535   - }
536   - modbuserror_handle(to_rec, WRITE_COILS);
537   - if (err)
538   - return err_no;
539   - return 0;
540   - }
541   - else
542   - {
543   - set_bad_con();
544   - return BAD_CON;
545   - }
546   -}
547   -
548   -/**
549   - * Write Multiple Registers
550   - * MODBUS FUNCION 0x10
551   - * @param address Reference Address
552   - * @param amount Amount of Value to Write
553   - * @param value Values to Be Written to the Registers
554   - */
555   -inline int modbus::modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value)
556   -{
557   - if (_connected)
558   - {
559   - modbus_write(address, amount, WRITE_REGS, value);
560   - uint8_t to_rec[MAX_MSG_LENGTH];
561   - ssize_t k = modbus_receive(to_rec);
562   - if (k == -1)
563   - {
564   - set_bad_con();
565   - return BAD_CON;
566   - }
567   - modbuserror_handle(to_rec, WRITE_REGS);
568   - if (err)
569   - return err_no;
570   - return 0;
571   - }
572   - else
573   - {
574   - set_bad_con();
575   - return BAD_CON;
576   - }
577   -}
578   -
579   -/**
580   - * Data Sender
581   - * @param to_send Request to Be Sent to Server
582   - * @param length Length of the Request
583   - * @return Size of the request
584   - */
585   -inline ssize_t modbus::modbus_send(uint8_t *to_send, size_t length)
586   -{
587   - _msg_id++;
588   - return send(_socket, (const char *)to_send, (size_t)length, 0);
589   -}
590   -
591   -/**
592   - * Data Receiver
593   - * @param buffer Buffer to Store the Data Retrieved
594   - * @return Size of Incoming Data
595   - */
596   -inline ssize_t modbus::modbus_receive(uint8_t *buffer) const
597   -{
598   - return recv(_socket, (char *)buffer, MAX_MSG_LENGTH, 0);
599   -}
600   -
601   -inline void modbus::set_bad_con()
602   -{
603   - err = true;
604   - error_msg = "BAD CONNECTION";
605   -}
606   -
607   -inline void modbus::set_bad_input()
608   -{
609   - err = true;
610   - error_msg = "BAD FUNCTION INPUT";
611   -}
612   -
613   -/**
614   - * Error Code Handler
615   - * @param msg Message Received from the Server
616   - * @param func Modbus Functional Code
617   - */
618   -inline void modbus::modbuserror_handle(const uint8_t *msg, int func)
619   -{
620   - err = false;
621   - error_msg = "NO ERR";
622   - if (msg[7] == func + 0x80)
623   - {
624   - err = true;
625   - switch (msg[8])
626   - {
627   - case EX_ILLEGAL_FUNCTION:
628   - error_msg = "1 Illegal Function";
629   - break;
630   - case EX_ILLEGAL_ADDRESS:
631   - error_msg = "2 Illegal Address";
632   - break;
633   - case EX_ILLEGAL_VALUE:
634   - error_msg = "3 Illegal Value";
635   - break;
636   - case EX_SERVER_FAILURE:
637   - error_msg = "4 Server Failure";
638   - break;
639   - case EX_ACKNOWLEDGE:
640   - error_msg = "5 Acknowledge";
641   - break;
642   - case EX_SERVER_BUSY:
643   - error_msg = "6 Server Busy";
644   - break;
645   - case EX_NEGATIVE_ACK:
646   - error_msg = "7 Negative Acknowledge";
647   - break;
648   - case EX_MEM_PARITY_PROB:
649   - error_msg = "8 Memory Parity Problem";
650   - break;
651   - case EX_GATEWAY_PROBLEMP:
652   - error_msg = "10 Gateway Path Unavailable";
653   - break;
654   - case EX_GATEWAY_PROBLEMF:
655   - error_msg = "11 Gateway Target Device Failed to Respond";
656   - break;
657   - default:
658   - error_msg = "UNK";
659   - break;
660   - }
661   - }
662   -}
663   -
scripts/setup_submodules 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# ===============================================
  4 +# == Setting some environment variables
  5 +# ===============================================
  6 +GIT_URL_OPEN="http://gitlab.osdev.nl/open_source"
  7 +GIT_URL_CLOSED="git@gitlab.osdev.nl:closed_source"
  8 +
  9 +FUNC_RESULT="-1"
  10 +
  11 +# Name : print_usage_exit()
  12 +# Description : Print the way this script is intended to be used and exit.
  13 +# Parameters : None.
  14 +# Returns : err_code 1 to the Operating System
  15 +# --------------------------------------------------------------------------------------
  16 +function print_usage_exit()
  17 +{
  18 + echo "Usage $0 -i|--install|-u|--update"
  19 + echo " -i or --install Install the submodules mentioned in the submodules.list"
  20 + echo " -u or --update Update the submodules mentioned in the submodules.list"
  21 + echo " "
  22 + exit 1
  23 +}
  24 +
  25 +# Name : check_top_or_sub
  26 +# Description : Determine if we're running in a "single" lib-build or part of a
  27 +# "meta"-repository ( submodule ).
  28 +# Parameters : None
  29 +# Returns : Updates the value FUNC_RESULT.
  30 +# -1 - We're neither a git-repo or submodule.
  31 +# 0 - We're a submodule
  32 +# 1 - We're a top-repo ( Single library )
  33 +# --------------------------------------------------------------------------------------
  34 +function check_top_or_sub()
  35 +{
  36 + # This function checks if we're the top-repository.
  37 + # In that case we need the submodules.. If we're already a submodule,
  38 + # we simply exit this script with a message
  39 + if [ -e ./.git ]; then
  40 + FUNC_RESULT="1"
  41 + return
  42 + elif [ -e ../.git ]; then
  43 + if [ -e ../.submodules ]; then
  44 + echo "Seems like we're already a submodule. Nothing to do here."
  45 + FUNC_RESULT="0"
  46 + return
  47 + fi
  48 + fi
  49 + FUNC_RESULT="-1"
  50 + return
  51 +}
  52 +
  53 +# Name : check_working_dir
  54 +# Description : If we're in the top of our repo, we can run this script further.
  55 +# Parameters : None.
  56 +# Returns : Updates the value FUNC_RESULT.
  57 +# -1 - Not used.
  58 +# 0 - We're not on the top-level
  59 +# 1 - We're at the top-level. Good to go.
  60 +# --------------------------------------------------------------------------------------
  61 +function check_working_dir()
  62 +{
  63 + FUNC_RESULT="-1"
  64 + # Check if we're in the top-level directory of our repository.
  65 + if [ -f ./scripts/submodules.list ]; then
  66 + # We're good to go
  67 + FUNC_RESULT="1"
  68 + return
  69 + fi
  70 + FUNC_RESULT="0"
  71 + return
  72 +}
  73 +
  74 +# Name : read_submodules
  75 +# Description : Read the list of submodules needed for this project
  76 +# Parameters : None
  77 +# Returns : Updates the value FUNC_RESULT
  78 +# 0 - Module list was not found
  79 +# 1 - Module list was found and read.
  80 +# --------------------------------------------------------------------------------------
  81 +function read_submodules()
  82 +{
  83 + FUNC_RESULT="-1"
  84 + if [ -e ./scripts/submodules.list ]; then
  85 + source ./scripts/submodules.list
  86 + FUNC_RESULT="1"
  87 + return
  88 + fi
  89 +
  90 + echo "Submodules list not found...."
  91 + FUNC_RESULT="0"
  92 + return
  93 +}
  94 +
  95 +# Name : add_submodules
  96 +# Description : Configure the repo to add the submodules.
  97 +# Parameters : None.
  98 +# Returns : None.
  99 +# --------------------------------------------------------------------------------------
  100 +function add_submodules()
  101 +{
  102 + echo -e "Adding SubModule(s)."
  103 + for SUB_MODULE in ${SUB_MODULES_OPEN}
  104 + do
  105 + git submodule add -f ${GIT_URL_OPEN}/${SUB_MODULE}.git submodules/${SUB_MODULE}
  106 + git config submodule.${SUB_MODULE}.url ${GIT_URL_OPEN}/${SUB_MODULE}.git
  107 + done
  108 +
  109 + for SUB_MODULE in ${SUB_MODULES_CLOSED}
  110 + do
  111 + echo {GIT_URL_CLOSED}/${SUB_MODULE}.git
  112 + git submodule add -f ${GIT_URL_CLOSED}/${SUB_MODULE}.git submodules/${SUB_MODULE}
  113 + git config submodule.${SUB_MODULE}.url ${GIT_URL_CLOSED}/${SUB_MODULE}.git
  114 + done
  115 +
  116 +}
  117 +
  118 +# Name : get_submodules
  119 +# Description : Actually get the submodules from gitlab and add them.
  120 +# Parameters : None
  121 +# Returns : None
  122 +# --------------------------------------------------------------------------------------
  123 +function get_submodules()
  124 +{
  125 + git submodule update --init --recursive
  126 +}
  127 +
  128 +# Name : update_submodules
  129 +# Description : Update the submodules already added.
  130 +# Parameters : None
  131 +# Returns : None
  132 +# --------------------------------------------------------------------------------------
  133 +function update_submodules()
  134 +{
  135 + git submodule update --recursive
  136 +}
  137 +
  138 +# =============================================================================
  139 +# == T H E M A I N E N T R Y O F T H I S S C R I P T ==
  140 +# =============================================================================
  141 +check_top_or_sub
  142 +if [ "${FUNC_RESULT}" == "0" ]; then
  143 + echo "Seems like we're a submodule already or not part of a repository."
  144 + exit 0
  145 +fi
  146 +
  147 +check_working_dir
  148 +if [ "${FUNC_RESULT}" == "0" ]; then
  149 + echo "Go to the top of this repository and type : scripts/setup_submodules [-i|--install]"
  150 + exit 0
  151 +fi
  152 +
  153 +read_submodules
  154 +
  155 +case "$1" in
  156 + -i*|--install*)
  157 + echo "Installing submodules for this repository ( ${PWD} )"
  158 + add_submodules
  159 + get_submodules
  160 + ;;
  161 + -u*|--update*)
  162 + echo "Update submodules : ${SUB_MODULES}"
  163 + update_submodules
  164 + ;;
  165 + *)
  166 + echo "No parameters found..."
  167 + print_usage_exit
  168 + ;;
  169 +esac
  170 +
... ...
scripts/submodules.list 0 → 100644
  1 +SUB_MODULES_OPEN="cmake"
  2 +
  3 +SUB_MODULES_CLOSED=""
... ...
src/3rdparty/MagicEnum.hpp 0 → 100644
  1 +// __ __ _ ______ _____
  2 +// | \/ | (_) | ____| / ____|_ _
  3 +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_
  4 +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _|
  5 +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_|
  6 +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____|
  7 +// __/ | https://github.com/Neargye/magic_enum
  8 +// |___/ version 0.7.3
  9 +//
  10 +// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
  11 +// SPDX-License-Identifier: MIT
  12 +// Copyright (c) 2019 - 2022 Daniil Goncharov <neargye@gmail.com>.
  13 +//
  14 +// Permission is hereby granted, free of charge, to any person obtaining a copy
  15 +// of this software and associated documentation files (the "Software"), to deal
  16 +// in the Software without restriction, including without limitation the rights
  17 +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  18 +// copies of the Software, and to permit persons to whom the Software is
  19 +// furnished to do so, subject to the following conditions:
  20 +//
  21 +// The above copyright notice and this permission notice shall be included in all
  22 +// copies or substantial portions of the Software.
  23 +//
  24 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  25 +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  26 +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  27 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  28 +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  29 +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  30 +// SOFTWARE.
  31 +
  32 +#ifndef NEARGYE_MAGIC_ENUM_HPP
  33 +#define NEARGYE_MAGIC_ENUM_HPP
  34 +
  35 +#define MAGIC_ENUM_VERSION_MAJOR 0
  36 +#define MAGIC_ENUM_VERSION_MINOR 7
  37 +#define MAGIC_ENUM_VERSION_PATCH 3
  38 +
  39 +#include <array>
  40 +#include <cassert>
  41 +#include <cstdint>
  42 +#include <cstddef>
  43 +#include <iosfwd>
  44 +#include <limits>
  45 +#include <type_traits>
  46 +#include <utility>
  47 +#include <variant>
  48 +
  49 +#if defined(MAGIC_ENUM_CONFIG_FILE)
  50 +#include MAGIC_ENUM_CONFIG_FILE
  51 +#endif
  52 +
  53 +#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL)
  54 +#include <optional>
  55 +#endif
  56 +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING)
  57 +#include <string>
  58 +#endif
  59 +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW)
  60 +#include <string_view>
  61 +#endif
  62 +
  63 +#if defined(__clang__)
  64 +# pragma clang diagnostic push
  65 +#elif defined(__GNUC__)
  66 +# pragma GCC diagnostic push
  67 +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'.
  68 +#elif defined(_MSC_VER)
  69 +# pragma warning(push)
  70 +# pragma warning(disable : 26495) // Variable 'static_string<N>::chars_' is uninitialized.
  71 +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value.
  72 +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call.
  73 +#endif
  74 +
  75 +// Checks magic_enum compiler compatibility.
  76 +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910
  77 +# undef MAGIC_ENUM_SUPPORTED
  78 +# define MAGIC_ENUM_SUPPORTED 1
  79 +#endif
  80 +
  81 +// Checks magic_enum compiler aliases compatibility.
  82 +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920
  83 +# undef MAGIC_ENUM_SUPPORTED_ALIASES
  84 +# define MAGIC_ENUM_SUPPORTED_ALIASES 1
  85 +#endif
  86 +
  87 +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128.
  88 +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN.
  89 +#if !defined(MAGIC_ENUM_RANGE_MIN)
  90 +# define MAGIC_ENUM_RANGE_MIN -128
  91 +#endif
  92 +
  93 +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 128.
  94 +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX.
  95 +#if !defined(MAGIC_ENUM_RANGE_MAX)
  96 +# define MAGIC_ENUM_RANGE_MAX 128
  97 +#endif
  98 +
  99 +namespace magic_enum {
  100 +
  101 +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL.
  102 +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL)
  103 +MAGIC_ENUM_USING_ALIAS_OPTIONAL
  104 +#else
  105 +using std::optional;
  106 +#endif
  107 +
  108 +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW.
  109 +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW)
  110 +MAGIC_ENUM_USING_ALIAS_STRING_VIEW
  111 +#else
  112 +using std::string_view;
  113 +#endif
  114 +
  115 +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING.
  116 +#if defined(MAGIC_ENUM_USING_ALIAS_STRING)
  117 +MAGIC_ENUM_USING_ALIAS_STRING
  118 +#else
  119 +using std::string;
  120 +#endif
  121 +
  122 +namespace customize {
  123 +
  124 +// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.
  125 +// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX.
  126 +// If need another range for specific enum type, add specialization enum_range for necessary enum type.
  127 +template <typename E>
  128 +struct enum_range {
  129 + static_assert(std::is_enum_v<E>, "magic_enum::customize::enum_range requires enum type.");
  130 + static constexpr int min = MAGIC_ENUM_RANGE_MIN;
  131 + static constexpr int max = MAGIC_ENUM_RANGE_MAX;
  132 + static_assert(max > min, "magic_enum::customize::enum_range requires max > min.");
  133 +};
  134 +
  135 +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN.");
  136 +static_assert((MAGIC_ENUM_RANGE_MAX - MAGIC_ENUM_RANGE_MIN) < (std::numeric_limits<std::uint16_t>::max)(), "MAGIC_ENUM_RANGE must be less than UINT16_MAX.");
  137 +
  138 +namespace detail {
  139 +enum class default_customize_tag {};
  140 +enum class invalid_customize_tag {};
  141 +} // namespace magic_enum::customize::detail
  142 +
  143 +using customize_t = std::variant<string_view, detail::default_customize_tag, detail::invalid_customize_tag>;
  144 +
  145 +// Default customize.
  146 +inline constexpr auto default_tag = detail::default_customize_tag{};
  147 +// Invalid customize.
  148 +inline constexpr auto invalid_tag = detail::invalid_customize_tag{};
  149 +
  150 +// If need custom names for enum, add specialization enum_name for necessary enum type.
  151 +template <typename E>
  152 +constexpr customize_t enum_name(E) noexcept {
  153 + return default_tag;
  154 +}
  155 +
  156 +// If need custom type name for enum, add specialization enum_type_name for necessary enum type.
  157 +template <typename E>
  158 +constexpr customize_t enum_type_name() noexcept {
  159 + return default_tag;
  160 +}
  161 +
  162 +} // namespace magic_enum::customize
  163 +
  164 +namespace detail {
  165 +
  166 +template <auto V, typename = std::enable_if_t<std::is_enum_v<std::decay_t<decltype(V)>>>>
  167 +using enum_constant = std::integral_constant<std::decay_t<decltype(V)>, V>;
  168 +
  169 +template <typename... T>
  170 +inline constexpr bool always_false_v = false;
  171 +
  172 +template <typename T>
  173 +struct supported
  174 +#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT)
  175 + : std::true_type {};
  176 +#else
  177 + : std::false_type {};
  178 +#endif
  179 +
  180 +template <typename T, typename = void>
  181 +struct has_is_flags : std::false_type {};
  182 +
  183 +template <typename T>
  184 +struct has_is_flags<T, std::void_t<decltype(customize::enum_range<T>::is_flags)>> : std::bool_constant<std::is_same_v<bool, std::decay_t<decltype(customize::enum_range<T>::is_flags)>>> {};
  185 +
  186 +template <typename T, typename = void>
  187 +struct range_min : std::integral_constant<int, MAGIC_ENUM_RANGE_MIN> {};
  188 +
  189 +template <typename T>
  190 +struct range_min<T, std::void_t<decltype(customize::enum_range<T>::min)>> : std::integral_constant<decltype(customize::enum_range<T>::min), customize::enum_range<T>::min> {};
  191 +
  192 +template <typename T, typename = void>
  193 +struct range_max : std::integral_constant<int, MAGIC_ENUM_RANGE_MAX> {};
  194 +
  195 +template <typename T>
  196 +struct range_max<T, std::void_t<decltype(customize::enum_range<T>::max)>> : std::integral_constant<decltype(customize::enum_range<T>::max), customize::enum_range<T>::max> {};
  197 +
  198 +template <std::size_t N>
  199 +class static_string {
  200 + public:
  201 + constexpr explicit static_string(string_view str) noexcept : static_string{str, std::make_index_sequence<N>{}} {
  202 + assert(str.size() == N);
  203 + }
  204 +
  205 + constexpr const char* data() const noexcept { return chars_; }
  206 +
  207 + constexpr std::size_t size() const noexcept { return N; }
  208 +
  209 + constexpr operator string_view() const noexcept { return {data(), size()}; }
  210 +
  211 + private:
  212 + template <std::size_t... I>
  213 + constexpr static_string(string_view str, std::index_sequence<I...>) noexcept : chars_{str[I]..., '\0'} {}
  214 +
  215 + char chars_[N + 1];
  216 +};
  217 +
  218 +template <>
  219 +class static_string<0> {
  220 + public:
  221 + constexpr explicit static_string() = default;
  222 +
  223 + constexpr explicit static_string(string_view) noexcept {}
  224 +
  225 + constexpr const char* data() const noexcept { return nullptr; }
  226 +
  227 + constexpr std::size_t size() const noexcept { return 0; }
  228 +
  229 + constexpr operator string_view() const noexcept { return {}; }
  230 +};
  231 +
  232 +constexpr string_view pretty_name(string_view name) noexcept {
  233 + for (std::size_t i = name.size(); i > 0; --i) {
  234 + if (!((name[i - 1] >= '0' && name[i - 1] <= '9') ||
  235 + (name[i - 1] >= 'a' && name[i - 1] <= 'z') ||
  236 + (name[i - 1] >= 'A' && name[i - 1] <= 'Z') ||
  237 +#if defined(MAGIC_ENUM_ENABLE_NONASCII)
  238 + (name[i - 1] & 0x80) ||
  239 +#endif
  240 + (name[i - 1] == '_'))) {
  241 + name.remove_prefix(i);
  242 + break;
  243 + }
  244 + }
  245 +
  246 + if (name.size() > 0 && ((name.front() >= 'a' && name.front() <= 'z') ||
  247 + (name.front() >= 'A' && name.front() <= 'Z') ||
  248 +#if defined(MAGIC_ENUM_ENABLE_NONASCII)
  249 + (name.front() & 0x80) ||
  250 +#endif
  251 + (name.front() == '_'))) {
  252 + return name;
  253 + }
  254 +
  255 + return {}; // Invalid name.
  256 +}
  257 +
  258 +class case_insensitive {
  259 + static constexpr char to_lower(char c) noexcept {
  260 + return (c >= 'A' && c <= 'Z') ? static_cast<char>(c + ('a' - 'A')) : c;
  261 + }
  262 +
  263 + public:
  264 + template <typename L, typename R>
  265 + constexpr auto operator()([[maybe_unused]] L lhs, [[maybe_unused]] R rhs) const noexcept -> std::enable_if_t<std::is_same_v<std::decay_t<L>, char> && std::is_same_v<std::decay_t<R>, char>, bool> {
  266 +#if defined(MAGIC_ENUM_ENABLE_NONASCII)
  267 + static_assert(always_false_v<L, R>, "magic_enum::case_insensitive not supported Non-ASCII feature.");
  268 + return false;
  269 +#else
  270 + return to_lower(lhs) == to_lower(rhs);
  271 +#endif
  272 + }
  273 +};
  274 +
  275 +constexpr std::size_t find(string_view str, char c) noexcept {
  276 +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__)
  277 +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc
  278 +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html
  279 + constexpr bool workaround = true;
  280 +#else
  281 + constexpr bool workaround = false;
  282 +#endif
  283 +
  284 + if constexpr (workaround) {
  285 + for (std::size_t i = 0; i < str.size(); ++i) {
  286 + if (str[i] == c) {
  287 + return i;
  288 + }
  289 + }
  290 +
  291 + return string_view::npos;
  292 + } else {
  293 + return str.find_first_of(c);
  294 + }
  295 +}
  296 +
  297 +template <typename T, std::size_t N, std::size_t... I>
  298 +constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N], std::index_sequence<I...>) noexcept {
  299 + return {{a[I]...}};
  300 +}
  301 +
  302 +template <typename BinaryPredicate>
  303 +constexpr bool is_default_predicate() noexcept {
  304 + return std::is_same_v<std::decay_t<BinaryPredicate>, std::equal_to<string_view::value_type>> ||
  305 + std::is_same_v<std::decay_t<BinaryPredicate>, std::equal_to<>>;
  306 +}
  307 +
  308 +template <typename BinaryPredicate>
  309 +constexpr bool is_nothrow_invocable() {
  310 + return is_default_predicate<BinaryPredicate>() ||
  311 + std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>;
  312 +}
  313 +
  314 +template <typename BinaryPredicate>
  315 +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable<BinaryPredicate>()) {
  316 +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__)
  317 + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html
  318 + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html
  319 + constexpr bool workaround = true;
  320 +#else
  321 + constexpr bool workaround = false;
  322 +#endif
  323 +
  324 + if constexpr (!is_default_predicate<BinaryPredicate>() || workaround) {
  325 + if (lhs.size() != rhs.size()) {
  326 + return false;
  327 + }
  328 +
  329 + const auto size = lhs.size();
  330 + for (std::size_t i = 0; i < size; ++i) {
  331 + if (!p(lhs[i], rhs[i])) {
  332 + return false;
  333 + }
  334 + }
  335 +
  336 + return true;
  337 + } else {
  338 + return lhs == rhs;
  339 + }
  340 +}
  341 +
  342 +template <typename L, typename R>
  343 +constexpr bool cmp_less(L lhs, R rhs) noexcept {
  344 + static_assert(std::is_integral_v<L> && std::is_integral_v<R>, "magic_enum::detail::cmp_less requires integral type.");
  345 +
  346 + if constexpr (std::is_signed_v<L> == std::is_signed_v<R>) {
  347 + // If same signedness (both signed or both unsigned).
  348 + return lhs < rhs;
  349 + } else if constexpr (std::is_same_v<L, bool>) { // bool special case
  350 + return static_cast<R>(lhs) < rhs;
  351 + } else if constexpr (std::is_same_v<R, bool>) { // bool special case
  352 + return lhs < static_cast<L>(rhs);
  353 + } else if constexpr (std::is_signed_v<R>) {
  354 + // If 'right' is negative, then result is 'false', otherwise cast & compare.
  355 + return rhs > 0 && lhs < static_cast<std::make_unsigned_t<R>>(rhs);
  356 + } else {
  357 + // If 'left' is negative, then result is 'true', otherwise cast & compare.
  358 + return lhs < 0 || static_cast<std::make_unsigned_t<L>>(lhs) < rhs;
  359 + }
  360 +}
  361 +
  362 +template <typename I>
  363 +constexpr I log2(I value) noexcept {
  364 + static_assert(std::is_integral_v<I>, "magic_enum::detail::log2 requires integral type.");
  365 +
  366 + if constexpr (std::is_same_v<I, bool>) { // bool special case
  367 + return assert(false), value;
  368 + } else {
  369 + auto ret = I{0};
  370 + for (; value > I{1}; value >>= I{1}, ++ret) {}
  371 +
  372 + return ret;
  373 + }
  374 +}
  375 +
  376 +template <typename T>
  377 +inline constexpr bool is_enum_v = std::is_enum_v<T> && std::is_same_v<T, std::decay_t<T>>;
  378 +
  379 +template <typename E>
  380 +constexpr auto n() noexcept {
  381 + static_assert(is_enum_v<E>, "magic_enum::detail::n requires enum type.");
  382 +
  383 + [[maybe_unused]] constexpr auto custom = customize::enum_type_name<E>();
  384 + static_assert(std::is_same_v<std::decay_t<decltype(custom)>, customize::customize_t>, "magic_enum::customize requires customize_t type.");
  385 + if constexpr (custom.index() == 0) {
  386 + constexpr auto name = std::get<string_view>(custom);
  387 + static_assert(!name.empty(), "magic_enum::customize requires not empty string.");
  388 + return static_string<name.size()>{name};
  389 + } else if constexpr (custom.index() == 1 && supported<E>::value) {
  390 +#if defined(__clang__) || defined(__GNUC__)
  391 + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2});
  392 +#elif defined(_MSC_VER)
  393 + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17});
  394 +#else
  395 + constexpr auto name = string_view{};
  396 +#endif
  397 + return static_string<name.size()>{name};
  398 + } else {
  399 + return static_string<0>{}; // Unsupported compiler or Invalid customize.
  400 + }
  401 +}
  402 +
  403 +template <typename E>
  404 +inline constexpr auto type_name_v = n<E>();
  405 +
  406 +template <typename E, E V>
  407 +constexpr auto n() noexcept {
  408 + static_assert(is_enum_v<E>, "magic_enum::detail::n requires enum type.");
  409 +
  410 + [[maybe_unused]] constexpr auto custom = customize::enum_name<E>(V);
  411 + static_assert(std::is_same_v<std::decay_t<decltype(custom)>, customize::customize_t>, "magic_enum::customize requires customize_t type.");
  412 + if constexpr (custom.index() == 0) {
  413 + constexpr auto name = std::get<string_view>(custom);
  414 + static_assert(!name.empty(), "magic_enum::customize requires not empty string.");
  415 + return static_string<name.size()>{name};
  416 + } else if constexpr (custom.index() == 1 && supported<E>::value) {
  417 +#if defined(__clang__) || defined(__GNUC__)
  418 + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2});
  419 +#elif defined(_MSC_VER)
  420 + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17});
  421 +#else
  422 + constexpr auto name = string_view{};
  423 +#endif
  424 + return static_string<name.size()>{name};
  425 + } else {
  426 + return static_string<0>{}; // Unsupported compiler or Invalid customize.
  427 + }
  428 +}
  429 +
  430 +template <typename E, E V>
  431 +inline constexpr auto enum_name_v = n<E, V>();
  432 +
  433 +template <typename E, auto V>
  434 +constexpr bool is_valid() noexcept {
  435 + static_assert(is_enum_v<E>, "magic_enum::detail::is_valid requires enum type.");
  436 +
  437 + return n<E, static_cast<E>(V)>().size() != 0;
  438 +}
  439 +
  440 +template <typename E, int O, bool IsFlags, typename U = std::underlying_type_t<E>>
  441 +constexpr E value(std::size_t i) noexcept {
  442 + static_assert(is_enum_v<E>, "magic_enum::detail::value requires enum type.");
  443 +
  444 + if constexpr (std::is_same_v<U, bool>) { // bool special case
  445 + static_assert(O == 0, "magic_enum::detail::value requires valid offset.");
  446 +
  447 + return static_cast<E>(i);
  448 + } else if constexpr (IsFlags) {
  449 + return static_cast<E>(U{1} << static_cast<U>(static_cast<int>(i) + O));
  450 + } else {
  451 + return static_cast<E>(static_cast<int>(i) + O);
  452 + }
  453 +}
  454 +
  455 +template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
  456 +constexpr int reflected_min() noexcept {
  457 + static_assert(is_enum_v<E>, "magic_enum::detail::reflected_min requires enum type.");
  458 +
  459 + if constexpr (IsFlags) {
  460 + return 0;
  461 + } else {
  462 + constexpr auto lhs = range_min<E>::value;
  463 + constexpr auto rhs = (std::numeric_limits<U>::min)();
  464 +
  465 + if constexpr (cmp_less(rhs, lhs)) {
  466 + return lhs;
  467 + } else {
  468 + return rhs;
  469 + }
  470 + }
  471 +}
  472 +
  473 +template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
  474 +constexpr int reflected_max() noexcept {
  475 + static_assert(is_enum_v<E>, "magic_enum::detail::reflected_max requires enum type.");
  476 +
  477 + if constexpr (IsFlags) {
  478 + return std::numeric_limits<U>::digits - 1;
  479 + } else {
  480 + constexpr auto lhs = range_max<E>::value;
  481 + constexpr auto rhs = (std::numeric_limits<U>::max)();
  482 +
  483 + if constexpr (cmp_less(lhs, rhs)) {
  484 + return lhs;
  485 + } else {
  486 + return rhs;
  487 + }
  488 + }
  489 +}
  490 +
  491 +template <typename E, bool IsFlags>
  492 +inline constexpr auto reflected_min_v = reflected_min<E, IsFlags>();
  493 +
  494 +template <typename E, bool IsFlags>
  495 +inline constexpr auto reflected_max_v = reflected_max<E, IsFlags>();
  496 +
  497 +template <std::size_t N>
  498 +constexpr std::size_t values_count(const bool (&valid)[N]) noexcept {
  499 + auto count = std::size_t{0};
  500 + for (std::size_t i = 0; i < N; ++i) {
  501 + if (valid[i]) {
  502 + ++count;
  503 + }
  504 + }
  505 +
  506 + return count;
  507 +}
  508 +
  509 +template <typename E, bool IsFlags, int Min, std::size_t... I>
  510 +constexpr auto values(std::index_sequence<I...>) noexcept {
  511 + static_assert(is_enum_v<E>, "magic_enum::detail::values requires enum type.");
  512 + constexpr bool valid[sizeof...(I)] = {is_valid<E, value<E, Min, IsFlags>(I)>()...};
  513 + constexpr std::size_t count = values_count(valid);
  514 +
  515 + if constexpr (count > 0) {
  516 + E values[count] = {};
  517 + for (std::size_t i = 0, v = 0; v < count; ++i) {
  518 + if (valid[i]) {
  519 + values[v++] = value<E, Min, IsFlags>(i);
  520 + }
  521 + }
  522 +
  523 + return to_array(values, std::make_index_sequence<count>{});
  524 + } else {
  525 + return std::array<E, 0>{};
  526 + }
  527 +}
  528 +
  529 +template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
  530 +constexpr auto values() noexcept {
  531 + static_assert(is_enum_v<E>, "magic_enum::detail::values requires enum type.");
  532 + constexpr auto min = reflected_min_v<E, IsFlags>;
  533 + constexpr auto max = reflected_max_v<E, IsFlags>;
  534 + constexpr auto range_size = max - min + 1;
  535 + static_assert(range_size > 0, "magic_enum::enum_range requires valid size.");
  536 + static_assert(range_size < (std::numeric_limits<std::uint16_t>::max)(), "magic_enum::enum_range requires valid size.");
  537 +
  538 + return values<E, IsFlags, reflected_min_v<E, IsFlags>>(std::make_index_sequence<range_size>{});
  539 +}
  540 +
  541 +template <typename E, typename U = std::underlying_type_t<E>>
  542 +constexpr bool is_flags_enum() noexcept {
  543 + static_assert(is_enum_v<E>, "magic_enum::detail::is_flags_enum requires enum type.");
  544 +
  545 + if constexpr (has_is_flags<E>::value) {
  546 + return customize::enum_range<E>::is_flags;
  547 + } else if constexpr (std::is_same_v<U, bool>) { // bool special case
  548 + return false;
  549 + } else {
  550 +#if defined(MAGIC_ENUM_NO_CHECK_FLAGS)
  551 + return false;
  552 +#else
  553 + constexpr auto flags_values = values<E, true>();
  554 + constexpr auto default_values = values<E, false>();
  555 + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) {
  556 + return false;
  557 + }
  558 + for (std::size_t i = 0; i < default_values.size(); ++i) {
  559 + const auto v = static_cast<U>(default_values[i]);
  560 + if (v != 0 && (v & (v - 1)) != 0) {
  561 + return false;
  562 + }
  563 + }
  564 + return flags_values.size() > 0;
  565 +#endif
  566 + }
  567 +}
  568 +
  569 +template <typename E>
  570 +inline constexpr bool is_flags_v = is_flags_enum<E>();
  571 +
  572 +template <typename E>
  573 +inline constexpr std::array values_v = values<E, is_flags_v<E>>();
  574 +
  575 +template <typename E, typename D = std::decay_t<E>>
  576 +using values_t = decltype((values_v<D>));
  577 +
  578 +template <typename E>
  579 +inline constexpr auto count_v = values_v<E>.size();
  580 +
  581 +template <typename E, typename U = std::underlying_type_t<E>>
  582 +inline constexpr auto min_v = (count_v<E> > 0) ? static_cast<U>(values_v<E>.front()) : U{0};
  583 +
  584 +template <typename E, typename U = std::underlying_type_t<E>>
  585 +inline constexpr auto max_v = (count_v<E> > 0) ? static_cast<U>(values_v<E>.back()) : U{0};
  586 +
  587 +template <typename E, std::size_t... I>
  588 +constexpr auto names(std::index_sequence<I...>) noexcept {
  589 + static_assert(is_enum_v<E>, "magic_enum::detail::names requires enum type.");
  590 +
  591 + return std::array<string_view, sizeof...(I)>{{enum_name_v<E, values_v<E>[I]>...}};
  592 +}
  593 +
  594 +template <typename E>
  595 +inline constexpr std::array names_v = names<E>(std::make_index_sequence<count_v<E>>{});
  596 +
  597 +template <typename E, typename D = std::decay_t<E>>
  598 +using names_t = decltype((names_v<D>));
  599 +
  600 +template <typename E, std::size_t... I>
  601 +constexpr auto entries(std::index_sequence<I...>) noexcept {
  602 + static_assert(is_enum_v<E>, "magic_enum::detail::entries requires enum type.");
  603 +
  604 + return std::array<std::pair<E, string_view>, sizeof...(I)>{{{values_v<E>[I], enum_name_v<E, values_v<E>[I]>}...}};
  605 +}
  606 +
  607 +template <typename E>
  608 +inline constexpr std::array entries_v = entries<E>(std::make_index_sequence<count_v<E>>{});
  609 +
  610 +template <typename E, typename D = std::decay_t<E>>
  611 +using entries_t = decltype((entries_v<D>));
  612 +
  613 +template <typename E, typename U = std::underlying_type_t<E>>
  614 +constexpr bool is_sparse() noexcept {
  615 + static_assert(is_enum_v<E>, "magic_enum::detail::is_sparse requires enum type.");
  616 +
  617 + if constexpr (count_v<E> == 0) {
  618 + return false;
  619 + } else if constexpr (std::is_same_v<U, bool>) { // bool special case
  620 + return false;
  621 + } else {
  622 + constexpr auto max = is_flags_v<E> ? log2(max_v<E>) : max_v<E>;
  623 + constexpr auto min = is_flags_v<E> ? log2(min_v<E>) : min_v<E>;
  624 + constexpr auto range_size = max - min + 1;
  625 +
  626 + return range_size != count_v<E>;
  627 + }
  628 +}
  629 +
  630 +template <typename E>
  631 +inline constexpr bool is_sparse_v = is_sparse<E>();
  632 +
  633 +template <typename E, typename U = std::underlying_type_t<E>>
  634 +constexpr U values_ors() noexcept {
  635 + static_assert(is_enum_v<E>, "magic_enum::detail::values_ors requires enum type.");
  636 +
  637 + auto ors = U{0};
  638 + for (std::size_t i = 0; i < count_v<E>; ++i) {
  639 + ors |= static_cast<U>(values_v<E>[i]);
  640 + }
  641 +
  642 + return ors;
  643 +}
  644 +
  645 +template <bool, typename R>
  646 +struct enable_if_enum {};
  647 +
  648 +template <typename R>
  649 +struct enable_if_enum<true, R> {
  650 + using type = R;
  651 + static_assert(supported<R>::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility).");
  652 +};
  653 +
  654 +template <typename T, typename R, typename BinaryPredicate = std::equal_to<>>
  655 +using enable_if_t = typename enable_if_enum<std::is_enum_v<std::decay_t<T>> && std::is_invocable_r_v<bool, BinaryPredicate, char, char>, R>::type;
  656 +
  657 +template <typename T, typename Enable = std::enable_if_t<std::is_enum_v<std::decay_t<T>>>>
  658 +using enum_concept = T;
  659 +
  660 +template <typename T, bool = std::is_enum_v<T>>
  661 +struct is_scoped_enum : std::false_type {};
  662 +
  663 +template <typename T>
  664 +struct is_scoped_enum<T, true> : std::bool_constant<!std::is_convertible_v<T, std::underlying_type_t<T>>> {};
  665 +
  666 +template <typename T, bool = std::is_enum_v<T>>
  667 +struct is_unscoped_enum : std::false_type {};
  668 +
  669 +template <typename T>
  670 +struct is_unscoped_enum<T, true> : std::bool_constant<std::is_convertible_v<T, std::underlying_type_t<T>>> {};
  671 +
  672 +template <typename T, bool = std::is_enum_v<std::decay_t<T>>>
  673 +struct underlying_type {};
  674 +
  675 +template <typename T>
  676 +struct underlying_type<T, true> : std::underlying_type<std::decay_t<T>> {};
  677 +
  678 +template <typename Value, typename = void>
  679 +struct constexpr_hash_t;
  680 +
  681 +template <typename Value>
  682 +struct constexpr_hash_t<Value, std::enable_if_t<is_enum_v<Value>>> {
  683 + constexpr auto operator()(Value value) const noexcept {
  684 + using U = typename underlying_type<Value>::type;
  685 + if constexpr (std::is_same_v<U, bool>) { // bool special case
  686 + return static_cast<std::size_t>(value);
  687 + } else {
  688 + return static_cast<U>(value);
  689 + }
  690 + }
  691 + using secondary_hash = constexpr_hash_t;
  692 +};
  693 +
  694 +template <typename Value>
  695 +struct constexpr_hash_t<Value, std::enable_if_t<std::is_same_v<Value, string_view>>> {
  696 + static constexpr std::uint32_t crc_table[256] {
  697 + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L,
  698 + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L,
  699 + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L,
  700 + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L,
  701 + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
  702 + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L,
  703 + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL,
  704 + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL,
  705 + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L,
  706 + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
  707 + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L,
  708 + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L,
  709 + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL,
  710 + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L,
  711 + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
  712 + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL,
  713 + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L,
  714 + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L,
  715 + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L,
  716 + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
  717 + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL,
  718 + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L,
  719 + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL,
  720 + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL,
  721 + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
  722 + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L,
  723 + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L,
  724 + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L,
  725 + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL,
  726 + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
  727 + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL,
  728 + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL
  729 + };
  730 + constexpr std::uint32_t operator()(string_view value) const noexcept {
  731 + auto crc = static_cast<std::uint32_t>(0xffffffffL);
  732 + for (const auto c : value) {
  733 + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast<std::uint32_t>(c)) & 0xff];
  734 + }
  735 + return crc ^ 0xffffffffL;
  736 + }
  737 +
  738 + struct secondary_hash {
  739 + constexpr std::uint32_t operator()(string_view value) const noexcept {
  740 + auto acc = static_cast<std::uint64_t>(2166136261ULL);
  741 + for (const auto c : value) {
  742 + acc = ((acc ^ static_cast<std::uint64_t>(c)) * static_cast<std::uint64_t>(16777619ULL)) & std::numeric_limits<std::uint32_t>::max();
  743 + }
  744 + return static_cast<std::uint32_t>(acc);
  745 + }
  746 + };
  747 +};
  748 +
  749 +template <typename Hash>
  750 +constexpr static Hash hash_v{};
  751 +
  752 +template <auto* GlobValues, typename Hash>
  753 +constexpr auto calculate_cases(std::size_t Page) noexcept {
  754 + constexpr std::array values = *GlobValues;
  755 + constexpr std::size_t size = values.size();
  756 +
  757 + using switch_t = std::invoke_result_t<Hash, typename decltype(values)::value_type>;
  758 + static_assert(std::is_integral_v<switch_t> && !std::is_same_v<switch_t, bool>);
  759 + const std::size_t values_to = (std::min)(static_cast<std::size_t>(256), size - Page);
  760 +
  761 + std::array<switch_t, 256> result{};
  762 + auto fill = result.begin();
  763 + for (auto first = values.begin() + Page, last = values.begin() + Page + values_to; first != last; ) {
  764 + *fill++ = hash_v<Hash>(*first++);
  765 + }
  766 +
  767 + // dead cases, try to avoid case collisions
  768 + for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits<switch_t>::max)(); *fill++ = ++last_value) {
  769 + }
  770 +
  771 + auto it = result.begin();
  772 + for (auto last_value = (std::numeric_limits<switch_t>::min)(); fill != result.end(); *fill++ = last_value++) {
  773 + while (last_value == *it) {
  774 + ++last_value, ++it;
  775 + }
  776 + }
  777 +
  778 + return result;
  779 +}
  780 +
  781 +template <typename R, typename F, typename... Args>
  782 +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v<R, F, Args...>) {
  783 + if constexpr (std::is_void_v<R>) {
  784 + std::forward<F>(f)(std::forward<Args>(args)...);
  785 + } else {
  786 + return static_cast<R>(std::forward<F>(f)(std::forward<Args>(args)...));
  787 + }
  788 +}
  789 +
  790 +enum class case_call_t {
  791 + index, value
  792 +};
  793 +
  794 +template <typename T = void>
  795 +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v<T>) { return T{}; };
  796 +
  797 +template <>
  798 +inline constexpr auto default_result_type_lambda<void> = []() noexcept {};
  799 +
  800 +template <auto* Arr, typename Hash>
  801 +constexpr bool no_duplicate() noexcept {
  802 + using value_t = std::decay_t<decltype((*Arr)[0])>;
  803 + using hash_value_t = std::invoke_result_t<Hash, value_t>;
  804 + std::array<hash_value_t, Arr->size()> hashes{};
  805 + std::size_t size = 0;
  806 + for (auto elem : *Arr) {
  807 + hashes[size] = hash_v<Hash>(elem);
  808 + for (auto i = size++; i > 0; --i) {
  809 + if (hashes[i] < hashes[i - 1]) {
  810 + auto tmp = hashes[i];
  811 + hashes[i] = hashes[i - 1];
  812 + hashes[i - 1] = tmp;
  813 + } else if (hashes[i] == hashes[i - 1]) {
  814 + return false;
  815 + } else {
  816 + break;
  817 + }
  818 + }
  819 + }
  820 + return true;
  821 +}
  822 +
  823 +#define MAGIC_ENUM_FOR_EACH_256(T) T(0)T(1)T(2)T(3)T(4)T(5)T(6)T(7)T(8)T(9)T(10)T(11)T(12)T(13)T(14)T(15)T(16)T(17)T(18)T(19)T(20)T(21)T(22)T(23)T(24)T(25)T(26)T(27)T(28)T(29)T(30)T(31) \
  824 + T(32)T(33)T(34)T(35)T(36)T(37)T(38)T(39)T(40)T(41)T(42)T(43)T(44)T(45)T(46)T(47)T(48)T(49)T(50)T(51)T(52)T(53)T(54)T(55)T(56)T(57)T(58)T(59)T(60)T(61)T(62)T(63) \
  825 + T(64)T(65)T(66)T(67)T(68)T(69)T(70)T(71)T(72)T(73)T(74)T(75)T(76)T(77)T(78)T(79)T(80)T(81)T(82)T(83)T(84)T(85)T(86)T(87)T(88)T(89)T(90)T(91)T(92)T(93)T(94)T(95) \
  826 + T(96)T(97)T(98)T(99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \
  827 + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \
  828 + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \
  829 + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \
  830 + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255)
  831 +
  832 +#define MAGIC_ENUM_CASE(val) \
  833 + case cases[val]: \
  834 + if constexpr ((val) + Page < size) { \
  835 + if (!pred(values[val + Page], searched)) { \
  836 + break; \
  837 + } \
  838 + if constexpr (CallValue == case_call_t::index) { \
  839 + if constexpr (std::is_invocable_r_v<result_t, Lambda, std::integral_constant<std::size_t, val + Page>>) { \
  840 + return detail::invoke_r<result_t>(std::forward<Lambda>(lambda), std::integral_constant<std::size_t, val + Page>{}); \
  841 + } else if constexpr (std::is_invocable_v<Lambda, std::integral_constant<std::size_t, val + Page>>) { \
  842 + assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \
  843 + } \
  844 + } else if constexpr (CallValue == case_call_t::value) { \
  845 + if constexpr (std::is_invocable_r_v<result_t, Lambda, enum_constant<values[val + Page]>>) { \
  846 + return detail::invoke_r<result_t>(std::forward<Lambda>(lambda), enum_constant<values[val + Page]>{}); \
  847 + } else if constexpr (std::is_invocable_r_v<result_t, Lambda, enum_constant<values[val + Page]>>) { \
  848 + assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \
  849 + } \
  850 + } \
  851 + break; \
  852 + } else [[fallthrough]];
  853 +
  854 +template <auto* GlobValues,
  855 + case_call_t CallValue,
  856 + std::size_t Page = 0,
  857 + typename Hash = constexpr_hash_t<typename std::decay_t<decltype(*GlobValues)>::value_type>,
  858 + typename Lambda, typename ResultGetterType = decltype(default_result_type_lambda<>),
  859 + typename BinaryPredicate = std::equal_to<>>
  860 +constexpr std::invoke_result_t<ResultGetterType> constexpr_switch(
  861 + Lambda&& lambda,
  862 + typename std::decay_t<decltype(*GlobValues)>::value_type searched,
  863 + ResultGetterType&& def = default_result_type_lambda<>,
  864 + BinaryPredicate&& pred = {}) {
  865 + using result_t = std::invoke_result_t<ResultGetterType>;
  866 + using hash_t = std::conditional_t<no_duplicate<GlobValues, Hash>(), Hash, typename Hash::secondary_hash>;
  867 + constexpr std::array values = *GlobValues;
  868 + constexpr std::size_t size = values.size();
  869 + constexpr std::array cases = calculate_cases<GlobValues, hash_t>(Page);
  870 +
  871 + switch (hash_v<hash_t>(searched)) {
  872 + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE)
  873 + default:
  874 + if constexpr (size > 256 + Page) {
  875 + return constexpr_switch<GlobValues, CallValue, Page + 256, Hash>(std::forward<Lambda>(lambda), searched, std::forward<ResultGetterType>(def));
  876 + }
  877 + break;
  878 + }
  879 + return def();
  880 +}
  881 +
  882 +#undef MAGIC_ENUM_FOR_EACH_256
  883 +#undef MAGIC_ENUM_CASE
  884 +
  885 +template <typename E, typename Lambda, std::size_t... I>
  886 +constexpr auto for_each(Lambda&& lambda, std::index_sequence<I...>) {
  887 + static_assert(is_enum_v<E>, "magic_enum::detail::for_each requires enum type.");
  888 + constexpr bool has_void_return = (std::is_void_v<std::invoke_result_t<Lambda, enum_constant<values_v<E>[I]>>> || ...);
  889 + constexpr bool all_same_return = (std::is_same_v<std::invoke_result_t<Lambda, enum_constant<values_v<E>[0]>>, std::invoke_result_t<Lambda, enum_constant<values_v<E>[I]>>> && ...);
  890 +
  891 + if constexpr (has_void_return) {
  892 + (lambda(enum_constant<values_v<E>[I]>{}), ...);
  893 + } else if constexpr (all_same_return) {
  894 + return std::array{lambda(enum_constant<values_v<E>[I]>{})...};
  895 + } else {
  896 + return std::tuple{lambda(enum_constant<values_v<E>[I]>{})...};
  897 + }
  898 +}
  899 +
  900 +template <typename E, typename Lambda, typename D = std::decay_t<E>>
  901 +using for_each_t = decltype(for_each<D>(std::declval<Lambda>(), std::make_index_sequence<count_v<D>>{}));
  902 +
  903 +} // namespace magic_enum::detail
  904 +
  905 +// Checks is magic_enum supported compiler.
  906 +inline constexpr bool is_magic_enum_supported = detail::supported<void>::value;
  907 +
  908 +template <typename T>
  909 +using Enum = detail::enum_concept<T>;
  910 +
  911 +// Checks whether T is an Unscoped enumeration type.
  912 +// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false.
  913 +template <typename T>
  914 +struct is_unscoped_enum : detail::is_unscoped_enum<T> {};
  915 +
  916 +template <typename T>
  917 +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum<T>::value;
  918 +
  919 +// Checks whether T is an Scoped enumeration type.
  920 +// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false.
  921 +template <typename T>
  922 +struct is_scoped_enum : detail::is_scoped_enum<T> {};
  923 +
  924 +template <typename T>
  925 +inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value;
  926 +
  927 +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T.
  928 +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed.
  929 +template <typename T>
  930 +struct underlying_type : detail::underlying_type<T> {};
  931 +
  932 +template <typename T>
  933 +using underlying_type_t = typename underlying_type<T>::type;
  934 +
  935 +template <auto V>
  936 +using enum_constant = detail::enum_constant<V>;
  937 +
  938 +// Returns type name of enum.
  939 +template <typename E>
  940 +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t<E, string_view> {
  941 + constexpr string_view name = detail::type_name_v<std::decay_t<E>>;
  942 + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name.");
  943 +
  944 + return name;
  945 +}
  946 +
  947 +// Returns number of enum values.
  948 +template <typename E>
  949 +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t<E, std::size_t> {
  950 + return detail::count_v<std::decay_t<E>>;
  951 +}
  952 +
  953 +// Returns enum value at specified index.
  954 +// No bounds checking is performed: the behavior is undefined if index >= number of enum values.
  955 +template <typename E>
  956 +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
  957 + using D = std::decay_t<E>;
  958 +
  959 + if constexpr (detail::is_sparse_v<D>) {
  960 + return assert((index < detail::count_v<D>)), detail::values_v<D>[index];
  961 + } else {
  962 + constexpr bool is_flag = detail::is_flags_v<D>;
  963 + constexpr auto min = is_flag ? detail::log2(detail::min_v<D>) : detail::min_v<D>;
  964 +
  965 + return assert((index < detail::count_v<D>)), detail::value<D, min, is_flag>(index);
  966 + }
  967 +}
  968 +
  969 +// Returns enum value at specified index.
  970 +template <typename E, std::size_t I>
  971 +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t<E, std::decay_t<E>> {
  972 + using D = std::decay_t<E>;
  973 + static_assert(I < detail::count_v<D>, "magic_enum::enum_value out of range.");
  974 +
  975 + return enum_value<D>(I);
  976 +}
  977 +
  978 +// Returns std::array with enum values, sorted by enum value.
  979 +template <typename E>
  980 +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t<E, detail::values_t<E>> {
  981 + return detail::values_v<std::decay_t<E>>;
  982 +}
  983 +
  984 +// Returns integer value from enum value.
  985 +template <typename E>
  986 +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t<E, underlying_type_t<E>> {
  987 + return static_cast<underlying_type_t<E>>(value);
  988 +}
  989 +
  990 +
  991 +// Returns underlying value from enum value.
  992 +template <typename E>
  993 +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t<E, underlying_type_t<E>> {
  994 + return static_cast<underlying_type_t<E>>(value);
  995 +}
  996 +
  997 +// Obtains index in enum values from enum value.
  998 +// Returns optional with index.
  999 +template <typename E>
  1000 +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t<E, optional<std::size_t>> {
  1001 + using D = std::decay_t<E>;
  1002 + using U = underlying_type_t<D>;
  1003 +
  1004 + if constexpr (detail::count_v<D> == 0) {
  1005 + return {}; // Empty enum.
  1006 + } else if constexpr (detail::is_sparse_v<D> || detail::is_flags_v<D>) {
  1007 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::index>(
  1008 + [](std::size_t i) { return optional<std::size_t>{i}; },
  1009 + value,
  1010 + detail::default_result_type_lambda<optional<std::size_t>>);
  1011 + } else {
  1012 + const auto v = static_cast<U>(value);
  1013 + if (v >= detail::min_v<D> && v <= detail::max_v<D>) {
  1014 + return static_cast<std::size_t>(v - detail::min_v<D>);
  1015 + }
  1016 + return {}; // Invalid value or out of range.
  1017 + }
  1018 +}
  1019 +
  1020 +// Returns name from static storage enum variable.
  1021 +// This version is much lighter on the compile times and is not restricted to the enum_range limitation.
  1022 +template <auto V>
  1023 +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t<decltype(V), string_view> {
  1024 + constexpr string_view name = detail::enum_name_v<std::decay_t<decltype(V)>, V>;
  1025 + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name.");
  1026 +
  1027 + return name;
  1028 +}
  1029 +
  1030 +// Returns name from enum value.
  1031 +// If enum value does not have name or value out of range, returns empty string.
  1032 +template <typename E>
  1033 +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t<E, string_view> {
  1034 + using D = std::decay_t<E>;
  1035 +
  1036 + if (const auto i = enum_index<D>(value)) {
  1037 + return detail::names_v<D>[*i];
  1038 + }
  1039 + return {};
  1040 +}
  1041 +
  1042 +// Returns name from enum-flags value.
  1043 +// If enum-flags value does not have name or value out of range, returns empty string.
  1044 +template <typename E>
  1045 +[[nodiscard]] auto enum_flags_name(E value) -> detail::enable_if_t<E, string> {
  1046 + using D = std::decay_t<E>;
  1047 + using U = underlying_type_t<D>;
  1048 +
  1049 + if constexpr (detail::is_flags_v<D>) {
  1050 + string name;
  1051 + auto check_value = U{0};
  1052 + for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
  1053 + if (const auto v = static_cast<U>(enum_value<D>(i)); (static_cast<U>(value) & v) != 0) {
  1054 + check_value |= v;
  1055 + const auto n = detail::names_v<D>[i];
  1056 + if (!name.empty()) {
  1057 + name.append(1, '|');
  1058 + }
  1059 + name.append(n.data(), n.size());
  1060 + }
  1061 + }
  1062 +
  1063 + if (check_value != 0 && check_value == static_cast<U>(value)) {
  1064 + return name;
  1065 + }
  1066 +
  1067 + return {}; // Invalid value or out of range.
  1068 + } else {
  1069 + return string{enum_name(value)};
  1070 + }
  1071 +}
  1072 +
  1073 +// Returns std::array with names, sorted by enum value.
  1074 +template <typename E>
  1075 +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t<E, detail::names_t<E>> {
  1076 + return detail::names_v<std::decay_t<E>>;
  1077 +}
  1078 +
  1079 +// Returns std::array with pairs (value, name), sorted by enum value.
  1080 +template <typename E>
  1081 +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t<E, detail::entries_t<E>> {
  1082 + return detail::entries_v<std::decay_t<E>>;
  1083 +}
  1084 +
  1085 +// Obtains enum value from integer value.
  1086 +// Returns optional with enum value.
  1087 +template <typename E>
  1088 +[[nodiscard]] constexpr auto enum_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_t<E, optional<std::decay_t<E>>> {
  1089 + using D = std::decay_t<E>;
  1090 + using U = underlying_type_t<D>;
  1091 +
  1092 + if constexpr (detail::count_v<D> == 0) {
  1093 + return {}; // Empty enum.
  1094 + } else if constexpr (detail::is_sparse_v<D>) {
  1095 + if constexpr (detail::is_flags_v<D>) {
  1096 + constexpr auto count = detail::count_v<D>;
  1097 + auto check_value = U{0};
  1098 + for (std::size_t i = 0; i < count; ++i) {
  1099 + if (const auto v = static_cast<U>(enum_value<D>(i)); (value & v) != 0) {
  1100 + check_value |= v;
  1101 + }
  1102 + }
  1103 +
  1104 + if (check_value != 0 && check_value == value) {
  1105 + return static_cast<D>(value);
  1106 + }
  1107 + return {}; // Invalid value or out of range.
  1108 + } else {
  1109 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::value>(
  1110 + [](D v) { return optional<D>{v}; },
  1111 + static_cast<D>(value),
  1112 + detail::default_result_type_lambda<optional<D>>);
  1113 + }
  1114 + } else {
  1115 + constexpr auto min = detail::min_v<D>;
  1116 + constexpr auto max = detail::is_flags_v<D> ? detail::values_ors<D>() : detail::max_v<D>;
  1117 +
  1118 + if (value >= min && value <= max) {
  1119 + return static_cast<D>(value);
  1120 + }
  1121 + return {}; // Invalid value or out of range.
  1122 + }
  1123 +}
  1124 +
  1125 +// Allows you to write magic_enum::enum_cast<foo>("bar", magic_enum::case_insensitive);
  1126 +inline constexpr auto case_insensitive = detail::case_insensitive{};
  1127 +
  1128 +// Obtains enum value from name.
  1129 +// Returns optional with enum value.
  1130 +template <typename E, typename BinaryPredicate = std::equal_to<>>
  1131 +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate&& p = {}) noexcept(detail::is_nothrow_invocable<BinaryPredicate>()) -> detail::enable_if_t<E, optional<std::decay_t<E>>, BinaryPredicate> {
  1132 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_cast requires bool(char, char) invocable predicate.");
  1133 + using D = std::decay_t<E>;
  1134 + using U = underlying_type_t<D>;
  1135 +
  1136 + if constexpr (detail::count_v<D> == 0) {
  1137 + return {}; // Empty enum.
  1138 + } else if constexpr (detail::is_flags_v<D>) {
  1139 + auto result = U{0};
  1140 + while (!value.empty()) {
  1141 + const auto d = detail::find(value, '|');
  1142 + const auto s = (d == string_view::npos) ? value : value.substr(0, d);
  1143 + auto f = U{0};
  1144 + for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
  1145 + if (detail::cmp_equal(s, detail::names_v<D>[i], p)) {
  1146 + f = static_cast<U>(enum_value<D>(i));
  1147 + result |= f;
  1148 + break;
  1149 + }
  1150 + }
  1151 + if (f == U{0}) {
  1152 + return {}; // Invalid value or out of range.
  1153 + }
  1154 + value.remove_prefix((d == string_view::npos) ? value.size() : d + 1);
  1155 + }
  1156 +
  1157 + if (result != U{0}) {
  1158 + return static_cast<D>(result);
  1159 + }
  1160 + return {}; // Invalid value or out of range.
  1161 + } else if constexpr (detail::count_v<D> > 0) {
  1162 + if constexpr (detail::is_default_predicate<BinaryPredicate>()) {
  1163 + return detail::constexpr_switch<&detail::names_v<D>, detail::case_call_t::index>(
  1164 + [](std::size_t i) { return optional<D>{detail::values_v<D>[i]}; },
  1165 + value,
  1166 + detail::default_result_type_lambda<optional<D>>,
  1167 + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); });
  1168 + } else {
  1169 + for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
  1170 + if (detail::cmp_equal(value, detail::names_v<D>[i], p)) {
  1171 + return enum_value<D>(i);
  1172 + }
  1173 + }
  1174 + return {}; // Invalid value or out of range.
  1175 + }
  1176 + }
  1177 +}
  1178 +
  1179 +// Obtains index in enum values from static storage enum variable.
  1180 +template <auto V>
  1181 +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t<decltype(V), std::size_t> {
  1182 + constexpr auto index = enum_index<std::decay_t<decltype(V)>>(V);
  1183 + static_assert(index, "magic_enum::enum_index enum value does not have a index.");
  1184 +
  1185 + return *index;
  1186 +}
  1187 +
  1188 +// Checks whether enum contains enumerator with such enum value.
  1189 +template <typename E>
  1190 +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t<E, bool> {
  1191 + using D = std::decay_t<E>;
  1192 + using U = underlying_type_t<D>;
  1193 +
  1194 + return static_cast<bool>(enum_cast<D>(static_cast<U>(value)));
  1195 +}
  1196 +
  1197 +// Checks whether enum contains enumerator with such integer value.
  1198 +template <typename E>
  1199 +[[nodiscard]] constexpr auto enum_contains(underlying_type_t<E> value) noexcept -> detail::enable_if_t<E, bool> {
  1200 + using D = std::decay_t<E>;
  1201 +
  1202 + return static_cast<bool>(enum_cast<D>(value));
  1203 +}
  1204 +
  1205 +// Checks whether enum contains enumerator with such name.
  1206 +template <typename E, typename BinaryPredicate = std::equal_to<>>
  1207 +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate&& p = {}) noexcept(detail::is_nothrow_invocable<BinaryPredicate>()) -> detail::enable_if_t<E, bool, BinaryPredicate> {
  1208 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_contains requires bool(char, char) invocable predicate.");
  1209 + using D = std::decay_t<E>;
  1210 +
  1211 + return static_cast<bool>(enum_cast<D>(value, std::forward<BinaryPredicate>(p)));
  1212 +}
  1213 +
  1214 +template <typename Result = void, typename E, typename Lambda>
  1215 +constexpr auto enum_switch(Lambda&& lambda, E value) -> detail::enable_if_t<E, Result> {
  1216 + using D = std::decay_t<E>;
  1217 +
  1218 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::value>(
  1219 + std::forward<Lambda>(lambda),
  1220 + value,
  1221 + detail::default_result_type_lambda<Result>);
  1222 +}
  1223 +
  1224 +template <typename Result, typename E, typename Lambda>
  1225 +constexpr auto enum_switch(Lambda&& lambda, E value, Result&& result) -> detail::enable_if_t<E, Result> {
  1226 + using D = std::decay_t<E>;
  1227 +
  1228 + return detail::constexpr_switch<&detail::values_v<D>, detail::case_call_t::value>(
  1229 + std::forward<Lambda>(lambda),
  1230 + value,
  1231 + [&result] { return std::forward<Result>(result); });
  1232 +}
  1233 +
  1234 +template <typename E, typename Result = void, typename BinaryPredicate = std::equal_to<>, typename Lambda>
  1235 +constexpr auto enum_switch(Lambda&& lambda, string_view name, BinaryPredicate&& p = {}) -> detail::enable_if_t<E, Result, BinaryPredicate> {
  1236 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_switch requires bool(char, char) invocable predicate.");
  1237 + using D = std::decay_t<E>;
  1238 +
  1239 + if (const auto v = enum_cast<D>(name, std::forward<BinaryPredicate>(p))) {
  1240 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v);
  1241 + }
  1242 + return detail::default_result_type_lambda<Result>();
  1243 +}
  1244 +
  1245 +template <typename E, typename Result, typename BinaryPredicate = std::equal_to<>, typename Lambda>
  1246 +constexpr auto enum_switch(Lambda&& lambda, string_view name, Result&& result, BinaryPredicate&& p = {}) -> detail::enable_if_t<E, Result, BinaryPredicate> {
  1247 + static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_switch requires bool(char, char) invocable predicate.");
  1248 + using D = std::decay_t<E>;
  1249 +
  1250 + if (const auto v = enum_cast<D>(name, std::forward<BinaryPredicate>(p))) {
  1251 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v, std::forward<Result>(result));
  1252 + }
  1253 + return std::forward<Result>(result);
  1254 +}
  1255 +
  1256 +template <typename E, typename Result = void, typename Lambda>
  1257 +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t<E> value) -> detail::enable_if_t<E, Result> {
  1258 + using D = std::decay_t<E>;
  1259 +
  1260 + if (const auto v = enum_cast<D>(value)) {
  1261 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v);
  1262 + }
  1263 + return detail::default_result_type_lambda<Result>();
  1264 +}
  1265 +
  1266 +template <typename E, typename Result, typename Lambda>
  1267 +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t<E> value, Result&& result) -> detail::enable_if_t<E, Result> {
  1268 + using D = std::decay_t<E>;
  1269 +
  1270 + if (const auto v = enum_cast<D>(value)) {
  1271 + return enum_switch<Result, D>(std::forward<Lambda>(lambda), *v, std::forward<Result>(result));
  1272 + }
  1273 + return std::forward<Result>(result);
  1274 +}
  1275 +
  1276 +template <typename E, typename Lambda>
  1277 +constexpr auto enum_for_each(Lambda&& lambda) -> detail::enable_if_t<E, detail::for_each_t<E, Lambda>> {
  1278 + using D = std::decay_t<E>;
  1279 +
  1280 + return detail::for_each<D>(std::forward<Lambda>(lambda), std::make_index_sequence<detail::count_v<D>>{});
  1281 +}
  1282 +
  1283 +namespace detail {
  1284 +
  1285 +template <typename E>
  1286 +constexpr optional<std::uintmax_t> fuse_one_enum(optional<std::uintmax_t> hash, E value) noexcept {
  1287 + if (hash) {
  1288 + if (const auto index = enum_index(value)) {
  1289 + return (*hash << log2(enum_count<E>() + 1)) | *index;
  1290 + }
  1291 + }
  1292 + return {};
  1293 +}
  1294 +
  1295 +template <typename E>
  1296 +constexpr optional<std::uintmax_t> fuse_enum(E value) noexcept {
  1297 + return fuse_one_enum(0, value);
  1298 +}
  1299 +
  1300 +template <typename E, typename... Es>
  1301 +constexpr optional<std::uintmax_t> fuse_enum(E head, Es... tail) noexcept {
  1302 + return fuse_one_enum(fuse_enum(tail...), head);
  1303 +}
  1304 +
  1305 +template <typename... Es>
  1306 +constexpr auto typesafe_fuse_enum(Es... values) noexcept {
  1307 + enum class enum_fuse_t : std::uintmax_t;
  1308 + const auto fuse = fuse_enum(values...);
  1309 + if (fuse) {
  1310 + return optional<enum_fuse_t>{static_cast<enum_fuse_t>(*fuse)};
  1311 + }
  1312 + return optional<enum_fuse_t>{};
  1313 +}
  1314 +
  1315 +} // namespace magic_enum::detail
  1316 +
  1317 +// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements.
  1318 +template <typename... Es>
  1319 +[[nodiscard]] constexpr auto enum_fuse(Es... values) noexcept {
  1320 + static_assert((std::is_enum_v<std::decay_t<Es>> && ...), "magic_enum::enum_fuse requires enum type.");
  1321 + static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 values.");
  1322 + static_assert((detail::log2(enum_count<Es>() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums");
  1323 +#if defined(MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE)
  1324 + const auto fuse = detail::fuse_enum<std::decay_t<Es>...>(values...);
  1325 +#else
  1326 + const auto fuse = detail::typesafe_fuse_enum<std::decay_t<Es>...>(values...);
  1327 +#endif
  1328 + return assert(fuse), fuse;
  1329 +}
  1330 +
  1331 +namespace ostream_operators {
  1332 +
  1333 +template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
  1334 +std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, E value) {
  1335 + using D = std::decay_t<E>;
  1336 + using U = underlying_type_t<D>;
  1337 +
  1338 + if constexpr (detail::supported<D>::value) {
  1339 + if (const auto name = enum_flags_name<D>(value); !name.empty()) {
  1340 + for (const auto c : name) {
  1341 + os.put(c);
  1342 + }
  1343 + return os;
  1344 + }
  1345 + }
  1346 + return (os << static_cast<U>(value));
  1347 +}
  1348 +
  1349 +template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
  1350 +std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, optional<E> value) {
  1351 + return value ? (os << *value) : os;
  1352 +}
  1353 +
  1354 +} // namespace magic_enum::ostream_operators
  1355 +
  1356 +namespace istream_operators {
  1357 +
  1358 +template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
  1359 +std::basic_istream<Char, Traits>& operator>>(std::basic_istream<Char, Traits>& is, E& value) {
  1360 + using D = std::decay_t<E>;
  1361 +
  1362 + std::basic_string<Char, Traits> s;
  1363 + is >> s;
  1364 + if (const auto v = enum_cast<D>(s)) {
  1365 + value = *v;
  1366 + } else {
  1367 + is.setstate(std::basic_ios<Char>::failbit);
  1368 + }
  1369 + return is;
  1370 +}
  1371 +
  1372 +} // namespace magic_enum::istream_operators
  1373 +
  1374 +namespace iostream_operators {
  1375 +
  1376 +using namespace ostream_operators;
  1377 +using namespace istream_operators;
  1378 +
  1379 +} // namespace magic_enum::iostream_operators
  1380 +
  1381 +namespace bitwise_operators {
  1382 +
  1383 +template <typename E, detail::enable_if_t<E, int> = 0>
  1384 +constexpr E operator~(E rhs) noexcept {
  1385 + return static_cast<E>(~static_cast<underlying_type_t<E>>(rhs));
  1386 +}
  1387 +
  1388 +template <typename E, detail::enable_if_t<E, int> = 0>
  1389 +constexpr E operator|(E lhs, E rhs) noexcept {
  1390 + return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) | static_cast<underlying_type_t<E>>(rhs));
  1391 +}
  1392 +
  1393 +template <typename E, detail::enable_if_t<E, int> = 0>
  1394 +constexpr E operator&(E lhs, E rhs) noexcept {
  1395 + return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) & static_cast<underlying_type_t<E>>(rhs));
  1396 +}
  1397 +
  1398 +template <typename E, detail::enable_if_t<E, int> = 0>
  1399 +constexpr E operator^(E lhs, E rhs) noexcept {
  1400 + return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) ^ static_cast<underlying_type_t<E>>(rhs));
  1401 +}
  1402 +
  1403 +template <typename E, detail::enable_if_t<E, int> = 0>
  1404 +constexpr E& operator|=(E& lhs, E rhs) noexcept {
  1405 + return lhs = (lhs | rhs);
  1406 +}
  1407 +
  1408 +template <typename E, detail::enable_if_t<E, int> = 0>
  1409 +constexpr E& operator&=(E& lhs, E rhs) noexcept {
  1410 + return lhs = (lhs & rhs);
  1411 +}
  1412 +
  1413 +template <typename E, detail::enable_if_t<E, int> = 0>
  1414 +constexpr E& operator^=(E& lhs, E rhs) noexcept {
  1415 + return lhs = (lhs ^ rhs);
  1416 +}
  1417 +
  1418 +} // namespace magic_enum::bitwise_operators
  1419 +
  1420 +} // namespace magic_enum
  1421 +
  1422 +#if defined(__clang__)
  1423 +# pragma clang diagnostic pop
  1424 +#elif defined(__GNUC__)
  1425 +# pragma GCC diagnostic pop
  1426 +#elif defined(_MSC_VER)
  1427 +# pragma warning(pop)
  1428 +#endif
  1429 +
  1430 +#endif // NEARGYE_MAGIC_ENUM_HPP
... ...
src/CMakeLists.txt 0 → 100644
  1 +cmake_minimum_required(VERSION 3.16)
  2 +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/submodules/cmake)
  3 +
  4 +include(projectheader)
  5 +project_header(modbus-cpp)
  6 +
  7 +include(compiler)
  8 +
  9 +include_directories(
  10 + ${CMAKE_SOURCE_DIR}/include
  11 +)
  12 +
  13 +set(SRC_LIST
  14 + ${CMAKE_CURRENT_SOURCE_DIR}/modbusbase.h
  15 + ${CMAKE_CURRENT_SOURCE_DIR}/modbusbase.cpp
  16 + ${CMAKE_CURRENT_SOURCE_DIR}/modbustcp.h
  17 + ${CMAKE_CURRENT_SOURCE_DIR}/modbustcp.cpp
  18 + ${CMAKE_CURRENT_SOURCE_DIR}/modbusrtu.h
  19 + ${CMAKE_CURRENT_SOURCE_DIR}/modbusrtu.cpp
  20 + ${CMAKE_CURRENT_SOURCE_DIR}/connectionconfig.h
  21 + ${CMAKE_CURRENT_SOURCE_DIR}/connectionconfig.cpp
  22 +)
  23 +
  24 +include(library)
  25 +add_libraries(PUBLIC
  26 +)
  27 +
  28 +include(installation)
  29 +install_component()
  30 +
... ...
src/connectionconfig.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +#include "connectionconfig.h"
  8 +
  9 +using namespace osdev::components::modbus;
  10 +
  11 +ConnectionConfig::ConnectionConfig()
  12 +{
  13 +
  14 +}
... ...
src/connectionconfig.h 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +#pragma once
  8 +
  9 +#include <string>
  10 +
  11 +namespace osdev {
  12 +namespace components {
  13 +namespace modbus {
  14 +
  15 +class ConnectionConfig
  16 +{
  17 +public:
  18 + enum class E_CONNECTIONTYPE
  19 + {
  20 + SERIAL,
  21 + TCP,
  22 + UNKNOWN
  23 + };
  24 +
  25 + enum class E_PARITY
  26 + {
  27 + ODD,
  28 + EVEN,
  29 + NONE
  30 + };
  31 +
  32 + enum class E_BAUDRATE
  33 + {
  34 + R1200 = 1200,
  35 + R2400 = 2400,
  36 + R4800 = 4800,
  37 + R9600 = 9600,
  38 + R19200 = 19200,
  39 + R38400 = 38400,
  40 + R57600 = 57600,
  41 + R76800 = 76800,
  42 + R115200 = 115200
  43 + };
  44 +
  45 + ConnectionConfig();
  46 +
  47 + // Getters and Setters
  48 + void setConnectionType( E_CONNECTIONTYPE type ) { m_conType = type; }
  49 + E_CONNECTIONTYPE getConnectionType() const { return m_conType; }
  50 +
  51 + void setBaudRate( E_BAUDRATE baud_rate ) { m_baudRate = baud_rate; }
  52 + E_BAUDRATE getBaudRate() const { return m_baudRate; }
  53 +
  54 + void setParity(E_PARITY parity) { m_parity = parity; }
  55 + E_PARITY getParity() const { return m_parity; }
  56 +
  57 + void setDataBits(int data_bits) { m_dataBits = data_bits; }
  58 + int getDataBits() const { return m_dataBits; }
  59 +
  60 + void setStopBits(int stop_bits) { m_stopBits = stop_bits; }
  61 + int getStopBits() const { return m_stopBits; }
  62 +
  63 + void setIpAddress(const std::string &ip_address) { m_ipAddress = ip_address; }
  64 + std::string getIpAddress() const { return m_ipAddress; }
  65 +
  66 + void setPortNumber(unsigned int port_number) { m_portNumber = port_number; }
  67 + unsigned int getPortNumber() const { return m_portNumber; }
  68 +
  69 + void setFrameTimeout(int frame_timeout) { m_frameTimeOut = frame_timeout; }
  70 + int getFrameTimeout() const { return m_frameTimeOut; }
  71 +
  72 +private:
  73 + E_CONNECTIONTYPE m_conType = E_CONNECTIONTYPE::UNKNOWN;
  74 + E_BAUDRATE m_baudRate = E_BAUDRATE::R9600;
  75 + E_PARITY m_parity = E_PARITY::NONE;
  76 + int m_dataBits = 8;
  77 + int m_stopBits = 1;
  78 + std::string m_ipAddress{};
  79 + unsigned int m_portNumber = 502;
  80 + int m_frameTimeOut = -1;
  81 +
  82 +};
  83 +
  84 +} // End namespace modbus
  85 +} // End namespace components
  86 +} // End namespace osdev
... ...
src/modbusbase.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#include "modbusbase.h"
  9 +
  10 +using namespace osdev::components::modbus;
  11 +
  12 +int ModbusBase::readCoils(uint16_t address, uint16_t amount, bool *buffer)
  13 +{
  14 + if (m_connected)
  15 + {
  16 + if (amount > 2040)
  17 + {
  18 + setBadInput();
  19 + return EX_BAD_DATA;
  20 + }
  21 +
  22 + modbusRead(address, amount, READ_COILS);
  23 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  24 + ssize_t result = modbusReceive(to_rec);
  25 + if (result == -1)
  26 + {
  27 + setBadConnection();
  28 + return BAD_CON;
  29 + }
  30 +
  31 + modbusErrorHandle(to_rec, READ_COILS);
  32 + if (m_error)
  33 + {
  34 + return m_error_number;
  35 + }
  36 +
  37 + for (auto i = 0; i < amount; i++)
  38 + {
  39 + buffer[i] = static_cast<bool>((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
  40 + }
  41 + return 0;
  42 + }
  43 + else
  44 + {
  45 + setBadConnection();
  46 + return BAD_CON;
  47 + }
  48 +}
  49 +
  50 +int ModbusBase::readInputBits(uint16_t address, uint16_t amount, bool *buffer)
  51 +{
  52 + if (m_connected)
  53 + {
  54 + if (amount > 2040)
  55 + {
  56 + setBadInput();
  57 + return EX_BAD_DATA;
  58 + }
  59 +
  60 + modbusRead(address, amount, READ_INPUT_BITS);
  61 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  62 + ssize_t result = modbusReceive(to_rec);
  63 + if (result == -1)
  64 + {
  65 + setBadConnection();
  66 + return BAD_CON;
  67 + }
  68 +
  69 + if (m_error)
  70 + {
  71 + return m_error_number;
  72 + }
  73 +
  74 + for (auto i = 0; i < amount; i++)
  75 + {
  76 + buffer[i] = static_cast<bool>((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
  77 + }
  78 + modbusErrorHandle(to_rec, READ_INPUT_BITS);
  79 + return 0;
  80 + }
  81 + else
  82 + {
  83 + return BAD_CON;
  84 + }
  85 +}
  86 +
  87 +int ModbusBase::readHoldingRegisters(uint16_t address, uint16_t amount, uint16_t *buffer)
  88 +{
  89 + if (m_connected)
  90 + {
  91 + modbusRead(address, amount, READ_REGS);
  92 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  93 + ssize_t result = modbusReceive(to_rec);
  94 + if (result == -1)
  95 + {
  96 + setBadConnection();
  97 + return BAD_CON;
  98 + }
  99 + modbusErrorHandle(to_rec, READ_REGS);
  100 + if (m_error)
  101 + {
  102 + return m_error_number;
  103 + }
  104 + for (auto i = 0; i < amount; i++)
  105 + {
  106 + buffer[i] = (static_cast<uint16_t>(to_rec[9u + 2u * i])) << 8u;
  107 + buffer[i] += static_cast<uint16_t>(to_rec[10u + 2u * i]);
  108 + }
  109 + return 0;
  110 + }
  111 + else
  112 + {
  113 + setBadConnection();
  114 + return BAD_CON;
  115 + }
  116 +}
  117 +
  118 +int ModbusBase::readInputRegisters(uint16_t address, uint16_t amount, uint16_t *buffer)
  119 +{
  120 + if (m_connected)
  121 + {
  122 + modbusRead(address, amount, READ_INPUT_REGS);
  123 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  124 + ssize_t result = modbusReceive(to_rec);
  125 + if (result == -1)
  126 + {
  127 + setBadConnection();
  128 + return BAD_CON;
  129 + }
  130 +
  131 + modbusErrorHandle(to_rec, READ_INPUT_REGS);
  132 + if (m_error)
  133 + {
  134 + return m_error_number;
  135 + }
  136 +
  137 + for (auto i = 0; i < amount; i++)
  138 + {
  139 + buffer[i] = (static_cast<uint16_t>(to_rec[9u + 2u * i])) << 8u;
  140 + buffer[i] = static_cast<uint16_t>(to_rec[10u + 2u * i]);
  141 + }
  142 + return 0;
  143 + }
  144 + else
  145 + {
  146 + setBadConnection();
  147 + return BAD_CON;
  148 + }
  149 +}
  150 +
  151 +int ModbusBase::writeCoil(uint16_t address, const bool &to_write)
  152 +{
  153 + if (m_connected)
  154 + {
  155 + int value = to_write * 0xFF00;
  156 + modbusWrite(address, 1, WRITE_COIL, reinterpret_cast<uint16_t *>(&value));
  157 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  158 + ssize_t result = modbusReceive(to_rec);
  159 + if (result == -1)
  160 + {
  161 + setBadConnection();
  162 + return BAD_CON;
  163 + }
  164 +
  165 + modbusErrorHandle(to_rec, WRITE_COIL);
  166 + if (m_error)
  167 + {
  168 + return m_error_number;
  169 + }
  170 + return 0;
  171 + }
  172 + else
  173 + {
  174 + setBadConnection();
  175 + return BAD_CON;
  176 + }
  177 +}
  178 +
  179 +int ModbusBase::writeRegister(uint16_t address, const uint16_t &value)
  180 +{
  181 + if (m_connected)
  182 + {
  183 + modbusWrite(address, 1, WRITE_REG, &value);
  184 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  185 + ssize_t result = modbusReceive(to_rec);
  186 + if (result == -1)
  187 + {
  188 + setBadConnection();
  189 + return BAD_CON;
  190 + }
  191 +
  192 + modbusErrorHandle(to_rec, WRITE_COIL);
  193 + if (m_error)
  194 + {
  195 + return m_error_number;
  196 + }
  197 + return 0;
  198 + }
  199 + else
  200 + {
  201 + setBadConnection();
  202 + return BAD_CON;
  203 + }
  204 +}
  205 +
  206 +int ModbusBase::writeCoils(uint16_t address, uint16_t amount, const bool *value)
  207 +{
  208 + if (m_connected)
  209 + {
  210 + uint16_t *temp = new uint16_t[amount];
  211 + for (int i = 0; i < amount; i++)
  212 + {
  213 + temp[i] = static_cast<uint16_t>(value[i]);
  214 + }
  215 +
  216 + modbusWrite(address, amount, WRITE_COILS, temp);
  217 + delete[] temp;
  218 +
  219 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  220 + ssize_t result = modbusReceive(to_rec);
  221 + if (result == -1)
  222 + {
  223 + setBadConnection();
  224 + return BAD_CON;
  225 + }
  226 +
  227 + modbusErrorHandle(to_rec, WRITE_COILS);
  228 + if (m_error)
  229 + {
  230 + return m_error_number;
  231 + }
  232 + return 0;
  233 + }
  234 + else
  235 + {
  236 + setBadConnection();
  237 + return BAD_CON;
  238 + }
  239 +}
  240 +
  241 +int ModbusBase::writeRegisters(uint16_t address, uint16_t amount, const uint16_t *value)
  242 +{
  243 + if (m_connected)
  244 + {
  245 + modbusWrite(address ,amount, WRITE_REGS, value);
  246 + uint8_t *to_rec = new uint8_t[m_max_message_length];
  247 + ssize_t result = modbusReceive(to_rec);
  248 + if (result == -1)
  249 + {
  250 + setBadConnection();
  251 + return BAD_CON;
  252 + }
  253 +
  254 + modbusErrorHandle(to_rec, WRITE_COILS);
  255 + if (m_error)
  256 + {
  257 + return m_error_number;
  258 + }
  259 + return 0;
  260 + }
  261 + else
  262 + {
  263 + setBadConnection();
  264 + return BAD_CON;
  265 + }
  266 +}
  267 +
  268 +void ModbusBase::buildRequest(uint8_t *to_send, uint16_t address, int function_code) const
  269 +{
  270 + to_send[0] = static_cast<uint8_t>(m_msg_id >> 8u);
  271 + to_send[1] = static_cast<uint8_t>(m_msg_id & 0x00FFu);
  272 + to_send[2] = 0;
  273 + to_send[3] = 0;
  274 + to_send[4] = 0;
  275 + to_send[6] = static_cast<uint8_t>(m_slaveId);
  276 + to_send[7] = static_cast<uint8_t>(function_code);
  277 + to_send[8] = static_cast<uint8_t>(address >> 8u);
  278 + to_send[9] = static_cast<uint8_t>(address & 0x00FFu);
  279 +}
  280 +
  281 +int ModbusBase::modbusRead(uint16_t address, uint16_t amount, int function_code)
  282 +{
  283 + // TODO: Building the frames should be dependant on the chosen transport layer.. ( 256 for RTU, 260 for TCP )
  284 + // Declare as pure virtual and implement in the transport-specific class?
  285 + // For now we focus on TCP as it is easier to implement.
  286 + uint8_t to_send[12];
  287 + buildRequest(to_send, address, function_code);
  288 + to_send[5] = 6;
  289 + to_send[10] = static_cast<uint8_t>(amount >> 8u);
  290 + to_send[11] = static_cast<uint8_t>(amount & 0x00FFu);
  291 + return modbusSend(to_send, 12);
  292 +}
  293 +
  294 +int ModbusBase::modbusWrite(uint16_t address, uint16_t amount, int function_code, const uint16_t *value)
  295 +{
  296 + // TODO: Building the frames should be dependant on the chosen transport layer.. ( 256 for RTU, 260 for TCP )
  297 + // Declare as pure virtual and implement in the transport-specific class?
  298 + // For now we focus on TCP as it is easier to implement.
  299 + int status = 0;
  300 + uint8_t *to_send;
  301 +
  302 + switch (function_code)
  303 + {
  304 + // Intentionally fall-through
  305 + case WRITE_COIL:
  306 + case WRITE_REG:
  307 + {
  308 + to_send = new uint8_t[12];
  309 + buildRequest(to_send, address, function_code);
  310 + to_send[5] = 6;
  311 + to_send[10] = static_cast<uint8_t>(value[0] >> 8u);
  312 + to_send[11] = static_cast<uint8_t>(value[0] & 0x00FFu);
  313 + status = modbusSend(to_send, 12);
  314 + break;
  315 + }
  316 + case WRITE_REGS:
  317 + {
  318 + to_send = new uint8_t[13 + 2 * amount];
  319 + buildRequest(to_send, address, function_code);
  320 + to_send[5] = static_cast<uint8_t>(7 + 2 * amount);
  321 + to_send[10] = static_cast<uint8_t>(amount >> 8u);
  322 + to_send[11] = static_cast<uint8_t>(amount & 0x00FFu);
  323 + to_send[12] = static_cast<uint8_t>(2 * amount);
  324 + for (int i = 0; i < amount; i++)
  325 + {
  326 + to_send[13 + 2 * i] = static_cast<uint8_t>(value[i] >> 8u);
  327 + to_send[14 + 2 * i] = static_cast<uint8_t>(value[i] & 0x00FFu);
  328 + }
  329 + status = modbusSend(to_send, 13 + 2 * amount);
  330 + break;
  331 + }
  332 + case WRITE_COILS:
  333 + {
  334 + to_send = new uint8_t[14 + ( amount - 1 ) / 8];
  335 + buildRequest(to_send, address, function_code);
  336 + to_send[5] = static_cast<uint8_t>(7 + ( amount + 7 ) / 8);
  337 + to_send[10] = static_cast<uint8_t>(amount >> 8u);
  338 + to_send[11] = static_cast<uint8_t>(amount & 0x00FFu);
  339 + to_send[12] = static_cast<uint8_t>((amount + 7) / 8);
  340 + for (int i = 0; i < (amount + 7) / 8; i++)
  341 + {
  342 + to_send[13 + i] = 0; // Init needed before summing.
  343 + }
  344 + for (int i = 0; i < amount; i++)
  345 + {
  346 + to_send[13 + i / 8] += static_cast<uint8_t>(value[i] << (i % 8u));
  347 + }
  348 + status = modbusSend(to_send, 13 + (amount - 1) / 8);
  349 + }
  350 + }
  351 + delete[] to_send;
  352 + return status;
  353 +}
  354 +
  355 +void ModbusBase::modbusErrorHandle(const uint8_t *msg, int function_code)
  356 +{
  357 + (void)msg;
  358 + (void)function_code;
  359 +}
  360 +
  361 +void ModbusBase::setBadConnection()
  362 +{
  363 +
  364 +}
  365 +
  366 +void ModbusBase::setBadInput()
  367 +{
  368 +
  369 +}
  370 +
... ...
src/modbusbase.h 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +#pragma once
  8 +
  9 +#include <cstring>
  10 +#include <string>
  11 +#include <stdint.h>
  12 +
  13 +// Create a simple logger for console output during debugging.
  14 +// TODO: Replace with a custom logger by using std::function
  15 +// to keep it consistent with TrueMQTT.
  16 +#ifdef ENABLE_MODBUS_LOGGING
  17 +#include <cstdio>
  18 +#define LOG(fmt, ...) printf("[ modbus-cpp ]" fmt, ##__VA_ARGS__)
  19 +#else
  20 +#define LOG(...) (void)0
  21 +#endif
  22 +
  23 +#define X_ISVALIDSOCKET(s) ((s) >= 0)
  24 +#define X_CLOSE_SOCKET(s) close(s)
  25 +#define X_ISCONNECTSUCCEED(s) ((s) >= 0)
  26 +
  27 +// Function Codes
  28 +#define READ_COILS 0x01
  29 +#define READ_INPUT_BITS 0x02
  30 +#define READ_REGS 0x03
  31 +#define READ_INPUT_REGS 0x04
  32 +#define WRITE_COIL 0x05
  33 +#define WRITE_REG 0x06
  34 +#define WRITE_COILS 0x0F
  35 +#define WRITE_REGS 0x10
  36 +
  37 +// Exception codes
  38 +#define EX_ILLEGAL_FUNCTION 0x01
  39 +#define EX_ILLEGAL_ADDRESS 0x02
  40 +#define EX_ILLEGAL_VALUE 0x03
  41 +#define EX_SERVER_FAILURE 0x04
  42 +#define EX_ACKNOWLEDGE 0x05
  43 +#define EX_SERVER_BUSY 0x06
  44 +#define EX_NEGATIVE_ACK 0x07
  45 +#define EX_MEM_PARITY_PROB 0x08
  46 +#define EX_GATEWAY_PROBLEMP 0x0A
  47 +#define EX_GATEWAY_PROBLEMF 0x0B
  48 +#define EX_BAD_DATA 0xFF
  49 +
  50 +#define BAD_CON -1
  51 +
  52 +namespace osdev {
  53 +namespace components {
  54 +namespace modbus {
  55 +
  56 +// Modbus base class. Providing all Modbus PDUs without transport implementation
  57 +class ModbusBase
  58 +{
  59 +public:
  60 + ModbusBase();
  61 + virtual ~ModbusBase();
  62 +
  63 + // Pure virtuals. Override when inherited.
  64 + virtual bool Connect() = 0;
  65 + virtual bool Close() = 0;
  66 +
  67 + /*!
  68 + * \brief modbusSend
  69 + * \param to_send
  70 + * \param length
  71 + * \return
  72 + */
  73 + virtual int modbusSend(uint8_t *to_send, size_t length) = 0;
  74 +
  75 + /*!
  76 + * \brief modbusReceive
  77 + * \param buffer
  78 + * \return
  79 + */
  80 + virtual int modbusReceive(uint8_t *buffer) = 0;
  81 +
  82 + // Modbus implementation(s)
  83 + /*!
  84 + * Read Coils
  85 + * MODBUS Function 0x01
  86 + * \param address - Reference Address
  87 + * \param amount - Amount of Coils to Read
  88 + * \param buffer - Buffer to Store Data Read from Coils
  89 + */
  90 + int readCoils(uint16_t address, uint16_t amount, bool *buffer); // Replace buffer with something sensible?
  91 +
  92 + /*!
  93 + * Read Input Bits
  94 + * MODBUS Function 0x02
  95 + * \param address - Refernce Address
  96 + * \param amount - Amount of BITS to Read
  97 + * \param buffer - Buffer to store Data Read from Input Bits
  98 + */
  99 + int readInputBits(uint16_t address, uint16_t amount, bool *buffer); // Replace buffer with something sensible?
  100 +
  101 + /*!
  102 + * Read Holding Registers
  103 + * MODBUS Function 0x03
  104 + * \param address - Reference Address
  105 + * \param amount - Amount of Registers to Read
  106 + * \param buffer - Buffer to Store Data Read from Registers
  107 + */
  108 + int readHoldingRegisters(uint16_t address, uint16_t amount, uint16_t *buffer);
  109 +
  110 + /*!
  111 + * Read Input Registers
  112 + * MODBUS Function 0x04
  113 + * \param address - Reference Address
  114 + * \param amount - Amount of registers to read
  115 + * \param buffer - Buffer to store Data Read from Registers
  116 + */
  117 + int readInputRegisters(uint16_t address, uint16_t amount, uint16_t *buffer);
  118 +
  119 + /*!
  120 + * Write Single Coils
  121 + * MODBUS Function 0x05
  122 + * \param address - Reference Address
  123 + * \param to_write - Value to be written to Coil
  124 + */
  125 + int writeCoil(uint16_t address, const bool &to_write);
  126 +
  127 + /*!
  128 + * Write Single Register
  129 + * MODBUS Function 0x06
  130 + * \param address - Reference Address
  131 + * \param value - Value to Be Written to Register
  132 + */
  133 + int writeRegister(uint16_t address, const uint16_t &value);
  134 +
  135 + /*!
  136 + * Write Multiple Coils
  137 + * MODBUS Function 0x0F
  138 + * \param address - Reference Address
  139 + * \param amount - Amount of coils to write
  140 + * \param value - Values to Be Written to Coils
  141 + */
  142 + int writeCoils(uint16_t address, uint16_t amount, const bool *value);
  143 +
  144 + /*!
  145 + * Write Multiple Registers
  146 + * MODBUS Function 0x10
  147 + * \param address - Reference Address
  148 + * \param amount - Amount of Value to Write
  149 + * \param value - Values to Be Written to the Registers
  150 + */
  151 + int writeRegisters(uint16_t address, uint16_t amount, const uint16_t *value);
  152 +
  153 + // Getters and Setters.
  154 + void setConnected(bool connected = false){ m_connected = connected;}
  155 + bool getConnected() const { return m_connected; }
  156 + void setMessageId( uint32_t message_id ) { m_msg_id = message_id; }
  157 + uint32_t getMessageId() const { return m_msg_id; }
  158 + void setSlaveId(int slave_id){ m_slaveId = slave_id; }
  159 + int getSlaveId() const { return m_slaveId; }
  160 + void setError(bool error, int error_number = 0, const std::string &error_message = std::string())
  161 + {
  162 + m_error = error;
  163 + m_error_number = error_number;
  164 + m_error_message = error_message;
  165 + }
  166 + bool getError() const { return m_error; }
  167 + int getErrorNumber() const { return m_error_number; }
  168 + std::string getErrorMessage() const { return m_error_message;}
  169 + void setMaxMessageLength(unsigned int max_message_length) { m_max_message_length = max_message_length; }
  170 + unsigned int getMaxMessageLength() const { return m_max_message_length; }
  171 +
  172 +private: // Methods
  173 + /*!
  174 + * Modbus Request Builder
  175 + * \param to_send - Message buffer to be send
  176 + * \param address - Reference Address
  177 + * \param function_code - Modbus Functional Code
  178 + */
  179 + void buildRequest(uint8_t *to_send, uint16_t address, int function_code) const;
  180 +
  181 + int modbusRead(uint16_t address, uint16_t amount, int function_code);
  182 +
  183 + /*!
  184 + * Write Request Builder and Sender
  185 + * \param address - Reference address
  186 + * \param amount - Amount of data to be written
  187 + * \param function_code - Modbus Functional Code
  188 + * \param value - Data to be written
  189 + *
  190 + * \return int -
  191 + */
  192 + int modbusWrite(uint16_t address, uint16_t amount, int function_code, const uint16_t *value);
  193 +
  194 + /*!
  195 + * \brief modbusErrorHandle
  196 + * \param msg
  197 + * \param function_code
  198 + */
  199 + void modbusErrorHandle(const uint8_t *msg, int function_code);
  200 +
  201 + /*!
  202 + * \brief setBadConnection
  203 + */
  204 + void setBadConnection();
  205 +
  206 + /*!
  207 + * \brief setBadInput
  208 + */
  209 + void setBadInput();
  210 +
  211 +private: // Members (Giggity!)
  212 + bool m_connected{};
  213 + uint32_t m_msg_id{};
  214 + int m_slaveId{};
  215 + bool m_error{};
  216 + int m_error_number{};
  217 + std::string m_error_message;
  218 + unsigned int m_max_message_length;
  219 +
  220 +};
  221 +
  222 +} // End namespace modbus
  223 +} // End namespace components
  224 +} // End namespace osdev
  225 +
... ...
src/modbusrtu.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
... ...
src/modbusrtu.h 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +
  8 +#include "modbusbase.h"
  9 +
  10 +namespace osdev {
  11 +namespace components {
  12 +namespace modbus {
  13 +
  14 +class ModbusRtu : public ModbusBase
  15 +{
  16 +
  17 +};
  18 +
  19 +} /* End namespace modbus */
  20 +} /* End namespace components */
  21 +} /* End namespace osdev */
... ...
src/modbustcp.cpp 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +#include "modbustcp.h"
  8 +
  9 +using namespace osdev::components::modbus;
  10 +
  11 +ModbusTcp::ModbusTcp(const ConnectionConfig &con_config)
  12 + : m_host( con_config.getIpAddress() )
  13 + , m_port( con_config.getPortNumber() )
  14 +{
  15 +
  16 +}
  17 +
  18 +bool ModbusTcp::Connect()
  19 +{
  20 + if (m_host.empty() || m_port == 0)
  21 + {
  22 + LOG("Missing Host and Port");
  23 + return false;
  24 + }
  25 + else
  26 + {
  27 + LOG("Found Proper Host %s and Port %d", m_host.c_str(), m_port);
  28 + }
  29 +
  30 + m_socket = socket(AF_INET, SOCK_STREAM, 0);
  31 + if (!X_ISVALIDSOCKET(m_socket))
  32 + {
  33 + LOG("Error Opening Socket");
  34 + return false;
  35 + }
  36 + else
  37 + {
  38 + LOG("Socket Opened Successfully");
  39 + }
  40 +
  41 + struct timeval timeout
  42 + {
  43 + };
  44 + timeout.tv_sec = 20; // after 20 seconds connect() will timeout
  45 + timeout.tv_usec = 0;
  46 +
  47 +
  48 + setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char *>(&timeout), sizeof(timeout));
  49 + setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char *>(&timeout), sizeof(timeout));
  50 + m_server.sin_family = AF_INET;
  51 + m_server.sin_addr.s_addr = inet_addr(m_host.c_str());
  52 + m_server.sin_port = htons(m_port);
  53 +
  54 + if (!X_ISCONNECTSUCCEED(connect(m_socket, reinterpret_cast<SOCKADDR *>(&m_server), sizeof(m_server))))
  55 + {
  56 + LOG("Connection Error");
  57 + return false;
  58 + }
  59 +
  60 + LOG("Connected");
  61 + ModbusBase::setConnected( true );
  62 + return true;
  63 +}
  64 +
  65 +bool ModbusTcp::Close()
  66 +{
  67 + X_CLOSE_SOCKET(m_socket);
  68 + LOG("Socket Closed");
  69 + ModbusBase::setConnected(false);
  70 + return ModbusBase::getConnected();
  71 +}
  72 +
  73 +int ModbusTcp::modbusReceive(uint8_t *buffer)
  74 +{
  75 + return recv(m_socket, reinterpret_cast<char *>(buffer), MAX_MSG_LENGTH, 0);
  76 +}
  77 +
  78 +int ModbusTcp::modbusSend(uint8_t *buffer, size_t length)
  79 +{
  80 + uint32_t msg_id = ModbusBase::getMessageId();
  81 + ModbusBase::setMessageId(msg_id++);
  82 + return send(m_socket, reinterpret_cast<const char *>(buffer), static_cast<size_t>(length), 0);
  83 +}
... ...
src/modbustcp.h 0 → 100644
  1 +/*
  2 + * Copyright (c) 2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in the root directory of this source tree.
  6 + */
  7 +#pragma once
  8 +
  9 +#include "modbusbase.h"
  10 +#include "connectionconfig.h"
  11 +
  12 +#include <unistd.h>
  13 +#include <sys/socket.h>
  14 +#include <netinet/in.h>
  15 +#include <arpa/inet.h>
  16 +
  17 +namespace osdev {
  18 +namespace components {
  19 +namespace modbus {
  20 +
  21 +using X_SOCKET = int;
  22 +using SOCKADDR = struct sockaddr;
  23 +using SOCKADDR_IN = struct sockaddr_in;
  24 +
  25 +#define MAX_MSG_LENGTH 260
  26 +
  27 +class ModbusTcp : public ModbusBase
  28 +{
  29 +public:
  30 + explicit ModbusTcp(const ConnectionConfig &con_config);
  31 + virtual ~ModbusTcp() {}
  32 +
  33 + virtual bool Connect() override;
  34 + virtual bool Close() override;
  35 +
  36 + /*!
  37 + * \brief modbusSend
  38 + * \param to_send
  39 + * \param length
  40 + * \return
  41 + */
  42 + virtual int modbusSend(uint8_t *to_send, size_t length) override;
  43 +
  44 + /*!
  45 + * \brief modbusReceive
  46 + * \param buffer
  47 + * \return
  48 + */
  49 + virtual int modbusReceive(uint8_t *buffer) override;
  50 +
  51 +private:
  52 + std::string m_host {};
  53 + uint16_t m_port {502};
  54 + X_SOCKET m_socket {-1};
  55 + SOCKADDR_IN m_server {};
  56 +
  57 +};
  58 +
  59 +} /* End namespace modbus */
  60 +} /* End namespace components */
  61 +} /* End namespace osdev */
... ...