test_hardware.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. """
  2. Integration tests for hardware communication.
  3. These tests require real hardware to be connected and are skipped by default.
  4. Run with: pytest tests/integration/ --run-hardware
  5. All tests in this file are marked with @pytest.mark.hardware and will
  6. be automatically skipped in CI environments (when CI=true).
  7. """
  8. import pytest
  9. import time
  10. @pytest.mark.hardware
  11. class TestSerialConnection:
  12. """Tests for real serial connection to sand table hardware."""
  13. def test_serial_port_opens(self, serial_connection):
  14. """Test that we can open a serial connection to the hardware."""
  15. assert serial_connection.is_open
  16. assert serial_connection.baudrate == 115200
  17. def test_grbl_status_query(self, serial_connection):
  18. """Test querying GRBL status with '?' command.
  19. GRBL should respond with a status string like:
  20. <Idle|MPos:0.000,0.000,0.000|Bf:15,128>
  21. or
  22. <Idle|WPos:0.000,0.000,0.000|Bf:15,128>
  23. """
  24. # Clear any stale data
  25. serial_connection.reset_input_buffer()
  26. # Send status query
  27. serial_connection.write(b'?')
  28. serial_connection.flush()
  29. # Wait for response
  30. time.sleep(0.1)
  31. response = serial_connection.readline().decode().strip()
  32. # GRBL status starts with '<' and contains position info
  33. assert response.startswith('<'), f"Expected GRBL status, got: {response}"
  34. assert 'Pos:' in response, f"Expected position data in: {response}"
  35. def test_grbl_settings_query(self, serial_connection):
  36. """Test querying GRBL settings with '$$' command.
  37. GRBL should respond with settings like:
  38. $0=10
  39. $1=25
  40. ...
  41. ok
  42. """
  43. # Clear any stale data
  44. serial_connection.reset_input_buffer()
  45. # Send settings query
  46. serial_connection.write(b'$$\n')
  47. serial_connection.flush()
  48. # Collect all response lines
  49. responses = []
  50. timeout = time.time() + 2 # 2 second timeout
  51. while time.time() < timeout:
  52. if serial_connection.in_waiting:
  53. line = serial_connection.readline().decode().strip()
  54. responses.append(line)
  55. if line == 'ok':
  56. break
  57. time.sleep(0.01)
  58. # Should have received settings
  59. assert len(responses) > 1, "Expected GRBL settings response"
  60. assert responses[-1] == 'ok', f"Expected 'ok' at end, got: {responses[-1]}"
  61. # At least some settings should start with '$'
  62. settings = [r for r in responses if r.startswith('$')]
  63. assert len(settings) > 0, "Expected at least one setting line"
  64. @pytest.mark.hardware
  65. class TestConnectionManager:
  66. """Integration tests for the connection_manager module with real hardware."""
  67. def test_list_serial_ports_finds_hardware(self, available_serial_ports, run_hardware):
  68. """Test that list_serial_ports finds the connected hardware."""
  69. if not run_hardware:
  70. pytest.skip("Hardware tests disabled")
  71. from modules.connection import connection_manager
  72. ports = connection_manager.list_serial_ports()
  73. # Should find at least one port
  74. assert len(ports) > 0, "Expected to find at least one serial port"
  75. # Should match what we found independently
  76. for port in available_serial_ports:
  77. if 'usb' in port.lower() or 'tty' in port.lower():
  78. assert port in ports or any(port in p for p in ports)
  79. def test_serial_connection_class(self, hardware_port, run_hardware):
  80. """Test SerialConnection class with real hardware.
  81. This tests the actual SerialConnection wrapper from connection_manager.
  82. """
  83. if not run_hardware:
  84. pytest.skip("Hardware tests disabled")
  85. from modules.connection.connection_manager import SerialConnection
  86. conn = SerialConnection(hardware_port)
  87. try:
  88. assert conn.is_connected()
  89. # Send status query
  90. conn.send('?')
  91. time.sleep(0.1)
  92. response = conn.readline()
  93. assert '<' in response, f"Expected GRBL status, got: {response}"
  94. finally:
  95. conn.close()
  96. @pytest.mark.hardware
  97. @pytest.mark.slow
  98. class TestHardwareOperations:
  99. """Slow integration tests that perform actual hardware operations.
  100. These tests take longer and may move the hardware.
  101. Use with caution!
  102. """
  103. def test_soft_reset(self, serial_connection):
  104. """Test GRBL soft reset (Ctrl+X).
  105. This sends a soft reset command and verifies GRBL responds
  106. with its startup message.
  107. """
  108. # Send soft reset (Ctrl+X = 0x18)
  109. serial_connection.write(b'\x18')
  110. serial_connection.flush()
  111. # Wait for reset and startup message
  112. time.sleep(1)
  113. # Collect responses
  114. responses = []
  115. timeout = time.time() + 3
  116. while time.time() < timeout:
  117. if serial_connection.in_waiting:
  118. line = serial_connection.readline().decode().strip()
  119. if line:
  120. responses.append(line)
  121. time.sleep(0.01)
  122. # GRBL should output startup message containing "Grbl"
  123. all_responses = ' '.join(responses)
  124. assert 'Grbl' in all_responses or 'grbl' in all_responses.lower(), \
  125. f"Expected GRBL startup message, got: {responses}"