Commit 1bd935fb3a6e58bd1be7f5dfc2a6cf3314d5bea0
Committed by
GitHub
Merge pull request #1 from pgroen/feat/pgroen/modbus-tcp-implementation
Feat/pgroen/modbus tcp implementation
Showing
23 changed files
with
2945 additions
and
664 deletions
.gitignore
0 → 100644
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 | + | |
| 137 | + | |
| 138 | +## Define MODBUS Transaction | |
| 139 | +The following state diagram describes the generic processing oif a MODBUS transaction in server side. | |
| 140 | + | |
| 141 | + | |
| 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
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
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 */ | ... | ... |