#include #include #include // For std::hex, std::dec, std::fixed, std::setprecision #include // For baudrate mapping #include // For string manipulation #include // For read, write, close functions #include // File control definitions #include // POSIX terminal control definitions #include // For strerror // --------------------------- Modbus Utility Functions --------------------------- // Function to calculate Modbus RTU CRC16 uint16_t calculate_modbus_crc(const std::vector& data) { uint16_t crc = 0xFFFF; for (uint8_t byte : data) { crc ^= byte; for (int i = 0; i < 8; ++i) { if (crc & 0x0001) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return crc; } // Function to convert MSB, LSB to a signed 16-bit integer int16_t bytesToSignedInt16(uint8_t msb, uint8_t lsb) { uint16_t val = (static_cast(msb) << 8) | lsb; return static_cast(val); // C++ handles two's complement automatically for signed cast } // Function to convert MSB, LSB to an unsigned 16-bit integer uint16_t bytesToUnsignedInt16(uint8_t msb, uint8_t lsb) { return (static_cast(msb) << 8) | lsb; } // Baud rate mapping std::map baudrate_map = { {0, 1200}, {1, 2400}, {2, 4800}, {3, 9600}, {4, 19200}, {5, 38400}, {6, 57600} }; // --------------------------- Serial Port Communication Functions --------------------------- // Function to configure serial port int configure_serial_port(const std::string& port_path, speed_t baud_rate_const) { int serial_port = open(port_path.c_str(), O_RDWR | O_NOCTTY | O_SYNC); // O_NOCTTY: not controlling terminal, O_SYNC: write synchronously if (serial_port < 0) { std::cerr << "Error " << errno << " opening serial port " << port_path << ": " << strerror(errno) << std::endl; return -1; } struct termios tty; if (tcgetattr(serial_port, &tty) != 0) { std::cerr << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl; close(serial_port); return -1; } cfsetospeed(&tty, baud_rate_const); cfsetispeed(&tty, baud_rate_const); // 8N1 settings tty.c_cflag &= ~PARENB; // No parity tty.c_cflag &= ~CSTOPB; // 1 stop bit tty.c_cflag &= ~CSIZE; // Clear current character size mask tty.c_cflag |= CS8; // 8 data bits tty.c_cflag &= ~CRTSCTS; // No hardware flow control (RTS/CTS) tty.c_cflag |= CREAD | CLOCAL; // Enable reading, ignore modem control lines // Local flags tty.c_lflag &= ~ICANON; // Disable canonical mode (raw input) tty.c_lflag &= ~ECHO; // Disable echo tty.c_lflag &= ~ECHOE; // Disable erasure tty.c_lflag &= ~ECHONL; // Disable new-line echo tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP // Input flags tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off software flow control tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable special handling of bytes // Output flags tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (raw output) tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed // VMIN and VTIME ensure read() blocks until a certain number of bytes are received or a timeout occurs tty.c_cc[VMIN] = 0; // Minimum bytes to read (0 means `read` returns immediately with available bytes) tty.c_cc[VTIME] = 10; // Read timeout in 0.1s increments (1s here). Adjust as needed. if (tcsetattr(serial_port, TCSANOW, &tty) != 0) { std::cerr << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl; close(serial_port); return -1; } return serial_port; } // Function to send Modbus request and receive response std::vector send_modbus_request(int serial_fd, const std::vector& request, int expected_response_len) { if (serial_fd < 0) { std::cerr << "Serial port not open." << std::endl; return {}; } // Clear any pending data in the receive buffer tcflush(serial_fd, TCIFLUSH); // Send the request ssize_t bytes_written = write(serial_fd, request.data(), request.size()); if (bytes_written != static_cast(request.size())) { std::cerr << "Error writing to serial port: " << strerror(errno) << std::endl; return {}; } // std::cout << "Sent: "; // for (uint8_t byte : request) { // std::cout << std::setw(2) << std::setfill('0') << std::hex << static_cast(byte) << " "; // } // std::cout << std::dec << std::endl; // Read the response std::vector response_buffer(expected_response_len); ssize_t bytes_read = 0; size_t total_bytes_read = 0; while (total_bytes_read < expected_response_len) { bytes_read = read(serial_fd, response_buffer.data() + total_bytes_read, expected_response_len - total_bytes_read); if (bytes_read < 0) { if (errno == EINTR) continue; // Interrupted system call, try again std::cerr << "Error reading from serial port: " << strerror(errno) << std::endl; return {}; } else if (bytes_read == 0) { std::cerr << "Read timeout or no data received. Expected " << expected_response_len << " bytes, got " << total_bytes_read << std::endl; return {}; } total_bytes_read += bytes_read; } if (total_bytes_read != static_cast(expected_response_len)) { std::cerr << "Incomplete response received. Expected " << expected_response_len << " bytes, got " << total_bytes_read << std::endl; response_buffer.resize(total_bytes_read); // Trim to actual received size } return response_buffer; } // --------------------------- Modbus Response Parser (adapted from previous example) --------------------------- void process_modbus_response(const std::vector& response_bytes, const std::string& description, uint8_t slave_id_expected) { if (response_bytes.empty()) { std::cout << "No response received for " << description << std::endl; return; } std::cout << "\n--- Processing " << description << " ---" << std::endl; std::cout << "Raw response (bytes): "; for (uint8_t byte : response_bytes) { std::cout << std::setw(2) << std::setfill('0') << std::hex << static_cast(byte) << " "; } std::cout << std::dec << std::endl; if (response_bytes.size() < 5) { // Minimum 5 bytes: slaveID, FC, count, CRC (2 bytes) std::cerr << "Error: Response too short (" << response_bytes.size() << " bytes)." << std::endl; return; } uint8_t slave_id_received = response_bytes[0]; if (slave_id_received != slave_id_expected) { std::cerr << "Error: Slave ID mismatch. Expected 0x" << std::hex << static_cast(slave_id_expected) << ", Received 0x" << static_cast(slave_id_received) << std::dec << std::endl; return; } uint8_t function_code = response_bytes[1]; uint8_t byte_count = response_bytes[2]; // Modbus Exception Response check (Function code + 0x80) // E.g., if request FC was 0x03, exception FC would be 0x83 if ((function_code & 0x80) == 0x80) { // Check if MSB is set, indicating an exception uint8_t exception_code = response_bytes[3]; std::cerr << "Modbus Exception Received (Function Code 0x" << std::hex << static_cast(function_code) << ", Exception Code 0x" << static_cast(exception_code) << "): "; switch (exception_code) { case 0x01: std::cerr << "ILLEGAL FUNCTION"; break; case 0x02: std::cerr << "ILLEGAL DATA ADDRESS"; break; case 0x03: std::cerr << "ILLEGAL DATA VALUE"; break; case 0x04: std::cerr << "SLAVE DEVICE FAILURE"; break; default: std::cerr << "UNKNOWN EXCEPTION"; break; } std::cerr << std::dec << std::endl; return; } // CRC16 Check std::vector data_for_crc(response_bytes.begin(), response_bytes.end() - 2); uint16_t received_crc = bytesToUnsignedInt16(response_bytes[response_bytes.size() - 1], response_bytes[response_bytes.size() - 2]); // LSB first then MSB uint16_t calculated_crc = calculate_modbus_crc(data_for_crc); if (received_crc != calculated_crc) { std::cerr << "Warning: CRC mismatch! Expected 0x" << std::setw(4) << std::setfill('0') << std::hex << calculated_crc << ", received 0x" << std::setw(4) << std::setfill('0') << received_crc << std::dec << std::endl; // Depending on strictness, you might choose to return here // return; } else { std::cout << "CRC check: OK (received 0x" << std::setw(4) << std::setfill('0') << std::hex << received_crc << std::dec << ")" << std::endl; } // Data starts at index 3 (after slaveID, FC, byte_count) if (function_code == 0x03 || function_code == 0x04) { // Read Holding Registers / Read Input Registers if (byte_count != (response_bytes.size() - 5)) { std::cerr << "Warning: Byte count in response header (" << static_cast(byte_count) << ") does not match actual data length (" << (response_bytes.size() - 5) << ")." << std::endl; } // Data is always 16-bit register values in pairs of bytes for (int i = 0; i < byte_count; i += 2) { if (3 + i + 1 >= response_bytes.size() - 2) { // Ensure we don't read past valid data + CRC std::cerr << "Error: Incomplete data in response for " << description << std::endl; break; } uint8_t msb = response_bytes[3 + i]; uint8_t lsb = response_bytes[4 + i]; uint16_t raw_value_u16 = bytesToUnsignedInt16(msb, lsb); int16_t raw_value_s16 = bytesToSignedInt16(msb, lsb); // Auto handles two's complement if (description.find("Temperature") != std::string::npos && description.find("Alarm") == std::string::npos) { // Temperature conversion float temperature = static_cast(raw_value_s16) / 10.0f; std::cout << "Interpreted Temperature: " << std::fixed << std::setprecision(1) << temperature << " °C" << std::endl; } else if (description.find("Humidity") != std::string::npos && description.find("Alarm") == std::string::npos) { // Humidity conversion float humidity = static_cast(raw_value_u16) / 10.0f; // Note: Humidity is typically unsigned, so using raw_value_u16 std::cout << "Interpreted Humidity: " << std::fixed << std::setprecision(1) << humidity << " %RH" << std::endl; } else if (description.find("Alarm Status") != std::string::npos) { // Alarm status is typically in the LSB of the 16-bit word, as 0x00XX uint8_t alarm_code = lsb; // Or raw_value_u16 & 0xFF; std::string status_text; switch (alarm_code) { case 0x00: status_text = "No Alarm"; break; case 0x01: status_text = "Upper Limit Alarm"; break; case 0x02: status_text = "Lower Limit Alarm"; break; default: status_text = "Unknown Alarm Status"; break; } std::cout << "Interpreted Alarm Status (Code 0x" << std::hex << static_cast(alarm_code) << std::dec << "): " << status_text << std::endl; } else if (description.find("Address Register") != std::string::npos) { // Modbus address is a direct unsigned 16-bit value std::cout << "Interpreted Address Register Value: 0x" << std::hex << raw_value_u16 << std::dec << " (" << raw_value_u16 << ")" << std::endl; } else if (description.find("Baudrate Register") != std::string::npos) { // Baudrate is a code mapping std::cout << "Baudrate Register Code: " << raw_value_u16; if (baudrate_map.count(raw_value_u16)) { std::cout << " (Value: " << baudrate_map[raw_value_u16] << " bps)"; } else { std::cout << " (Warning: Unknown baudrate code)"; } std::cout << std::endl; } else { std::cout << "Raw Register Value (Hex): 0x" << std::hex << raw_value_u16 << std::dec << std::endl; std::cout << "Raw Register Value (Decimal): " << raw_value_u16 << std::endl; } } } else { std::cout << "Unsupported/Unknown Function Code: 0x" << std::hex << static_cast(function_code) << std::dec << std::endl; } } // --------------------------- Main Program --------------------------- int main() { std::cout << "--- JWST-20 Sensor Real-Time Test (Corrected Interpretation Logic) ---" << std::endl; const std::string serial_port_path = "/dev/ttyS7"; // <-- !!! Replace with your actual serial port !!! const speed_t modbus_baud_rate = B9600; // Modbus default baud rate (9600 bps) const uint8_t slave_id = 0x01; // Sensor's Modbus slave ID int serial_fd = configure_serial_port(serial_port_path, modbus_baud_rate); if (serial_fd < 0) { return 1; // Exit on serial port error } // --- Modbus Request Definitions (from your datasheet/original test program) --- // Address: 0x0000 (0), Quantity: 1 register (temperature) std::vector temp_request = { slave_id, 0x03, // Slave ID, Function Code (Read Holding Registers) 0x00, 0x00, // Starting Address High, Low (0x0000) 0x00, 0x01 // Quantity of Registers High, Low (1 register) }; uint16_t temp_crc = calculate_modbus_crc(temp_request); temp_request.push_back(temp_crc & 0xFF); // CRC Low byte temp_request.push_back((temp_crc >> 8) & 0xFF); // CRC High byte // Expected response length: SlaveID (1) + FC (1) + ByteCount (1) + Data (2*1) + CRC (2) = 7 bytes // Address: 0x0001 (1), Quantity: 1 register (humidity) std::vector humidity_request = { slave_id, 0x03, // Slave ID, Function Code (Read Holding Registers) 0x00, 0x01, // Starting Address High, Low (0x0001) 0x00, 0x01 // Quantity of Registers High, Low (1 register) }; uint16_t humidity_crc = calculate_modbus_crc(humidity_request); humidity_request.push_back(humidity_crc & 0xFF); humidity_request.push_back((humidity_crc >> 8) & 0xFF); // Address: 0x0000 (0), Quantity: 1 register (Temperature Alarm, Input Register) std::vector temp_alarm_request = { slave_id, 0x04, // Slave ID, Function Code (Read Input Registers) 0x00, 0x00, // Starting Address High, Low (0x0000) 0x00, 0x01 // Quantity of Registers High, Low (1 register) }; uint16_t temp_alarm_crc = calculate_modbus_crc(temp_alarm_request); temp_alarm_request.push_back(temp_alarm_crc & 0xFF); temp_alarm_request.push_back((temp_alarm_crc >> 8) & 0xFF); // Address: 0x0001 (1), Quantity: 1 register (Humidity Alarm, Input Register) std::vector humidity_alarm_request = { slave_id, 0x04, // Slave ID, Function Code (Read Input Registers) 0x00, 0x01, // Starting Address High, Low (0x0001) 0x00, 0x01 // Quantity of Registers High, Low (1 register) }; uint16_t humidity_alarm_crc = calculate_modbus_crc(humidity_alarm_request); humidity_alarm_request.push_back(humidity_alarm_crc & 0xFF); humidity_alarm_request.push_back((humidity_alarm_crc >> 8) & 0xFF); // Address: 0x1000 (4096), Quantity: 1 register (Modbus Address) std::vector modbus_addr_request = { slave_id, 0x03, 0x10, 0x00, // Starting Address (0x1000) 0x00, 0x01 }; uint16_t modbus_addr_crc = calculate_modbus_crc(modbus_addr_request); modbus_addr_request.push_back(modbus_addr_crc & 0xFF); modbus_addr_request.push_back((modbus_addr_crc >> 8) & 0xFF); // Address: 0x1001 (4097), Quantity: 1 register (Baudrate) std::vector baudrate_request = { slave_id, 0x03, 0x10, 0x01, // Starting Address (0x1001) 0x00, 0x01 }; uint16_t baudrate_crc = calculate_modbus_crc(baudrate_request); baudrate_request.push_back(baudrate_crc & 0xFF); baudrate_request.push_back((baudrate_crc >> 8) & 0xFF); // --- Main Loop: Read and Process Data --- std::cout << "\nStarting continuous Modbus polling. Press Ctrl+C to exit." << std::endl; while (true) { std::vector response; // 1. Read Temperature response = send_modbus_request(serial_fd, temp_request, 7); // Expected 7 bytes: 1+1+1+2*1+2 process_modbus_response(response, "Live Temperature", slave_id); // 2. Read Humidity response = send_modbus_request(serial_fd, humidity_request, 7); process_modbus_response(response, "Live Humidity", slave_id); // 3. Read Temperature Alarm Status (using Function Code 0x04 for Input Registers) response = send_modbus_request(serial_fd, temp_alarm_request, 7); process_modbus_response(response, "Live Temperature Alarm Status (Input Register)", slave_id); // 4. Read Humidity Alarm Status (using Function Code 0x04 for Input Registers) response = send_modbus_request(serial_fd, humidity_alarm_request, 7); process_modbus_response(response, "Live Humidity Alarm Status (Input Register)", slave_id); // 5. Read Modbus Address response = send_modbus_request(serial_fd, modbus_addr_request, 7); process_modbus_response(response, "Live Modbus Address Register", slave_id); // 6. Read Baudrate response = send_modbus_request(serial_fd, baudrate_request, 7); process_modbus_response(response, "Live Baudrate Register", slave_id); sleep(2); // Wait 2 seconds before polling again to avoid flooding the sensor } close(serial_fd); // Close the serial port when done std::cout << "\nProgram terminated." << std::endl; return 0; }