forked from tiiuae/ci-test-automation
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add relay_boot_test.robot and needed keywords to use relay for boot device by cutting down power. Add KMTronic relay board control and status python scripts and README. Add also Robot framework library for KMTronic control. Signed-off-by: Ville-Pekka Juntunen <[email protected]>
- Loading branch information
Showing
9 changed files
with
441 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<!-- | ||
Copyright 2022-2025 TII (SSRC) and the Ghaf contributors | ||
SPDX-License-Identifier: CC-BY-SA-4.0 | ||
--> | ||
|
||
# KMTronic relay board | ||
|
||
USB controlled relay board to control test devices power suply. | ||
|
||
### Relay control script | ||
|
||
To control 4 channel KMtronic relay board use kmtronic_4ch_control.py. Board serial port is needed to given as argument and ON/OFF argument. | ||
Relay number argument is needed if want to control ONLY that specified relay. Otherwise ALL relays is set ON/OFF. | ||
|
||
Examples: | ||
|
||
$ python kmtronic_4ch_control.py /dev/ttyUSB0 OFF | ||
All relays set to OFF. | ||
|
||
$ python kmtronic_4ch_control.py /dev/ttyUSB0 ON 2 | ||
Relay 2 set to ON. | ||
|
||
### Relay status script | ||
|
||
To get 4 channel KMtronic relay board status use kmtronic_4ch_status.py. Board serial port is needed to given as argument. | ||
Status request return status of all relays. | ||
|
||
Example: | ||
|
||
$ python kmtronic_4ch_status.py /dev/ttyUSB0 | ||
Relay 1: OFF | ||
Relay 2: ON | ||
Relay 3: OFF | ||
Relay 4: OFF |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# SPDX-FileCopyrightText: 2022-2025 Technology Innovation Institute (TII) | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import serial | ||
import sys | ||
import time | ||
|
||
def set_relay_state(port, relay_number=None, state=None): | ||
""" | ||
Sets the state of one or more relays on the KMTronic 4 channel device. | ||
:param port: Serial port (e.g., /dev/ttyUSB0) | ||
:param relay_number: Relay number (1-4), or None to control all relays | ||
:param state: Desired state ("ON" or "OFF"), or None to turn all off | ||
""" | ||
try: | ||
# Configure the serial port | ||
ser = serial.Serial( | ||
port=port, | ||
baudrate=9600, | ||
bytesize=serial.EIGHTBITS, | ||
parity=serial.PARITY_NONE, | ||
stopbits=serial.STOPBITS_ONE, | ||
timeout=1 | ||
) | ||
|
||
if relay_number is None: | ||
# Control all relays one by one | ||
for relay in range(1, 5): # Relay numbers 1 to 4 | ||
command = bytearray.fromhex('FF') + bytearray([relay]) + (b'\x01' if state == "ON" else b'\x00') | ||
ser.write(command) | ||
time.sleep(0.1) # Small delay between commands | ||
print(f"All relays set to {state}.") | ||
else: | ||
if relay_number < 1 or relay_number > 4: | ||
raise ValueError("Relay number must be between 1 and 4.") | ||
# KMTronic protocol uses 0-indexed relay numbers | ||
command = bytearray.fromhex('FF') + bytearray([relay_number]) + (b'\x01' if state == "ON" else b'\x00') | ||
ser.write(command) | ||
print(f"Relay {relay_number} set to {state}.") | ||
|
||
ser.close() | ||
except serial.SerialException as e: | ||
print(f"Serial port error: {e}") | ||
except Exception as e: | ||
print(f"An error occurred: {e}") | ||
|
||
if __name__ == "__main__": | ||
# Check for sufficient arguments | ||
if len(sys.argv) < 3: | ||
print("Usage: python kmtronic_control.py <serial_port> <state> [relay_number]") | ||
print("Example 1: python kmtronic_control.py /dev/ttyUSB0 ON 1 # Turn relay 1 ON") | ||
print("Example 2: python kmtronic_control.py /dev/ttyUSB0 OFF # Turn all relays OFF") | ||
sys.exit(1) | ||
|
||
# Parse arguments | ||
port = sys.argv[1] | ||
state = sys.argv[2].upper() | ||
|
||
if state not in ["ON", "OFF"]: | ||
print("Error: State must be 'ON' or 'OFF'.") | ||
sys.exit(1) | ||
|
||
if len(sys.argv) > 3: | ||
# Control a specific relay | ||
relay_number = int(sys.argv[3]) | ||
set_relay_state(port, relay_number=relay_number, state=state) | ||
else: | ||
# Control all relays | ||
set_relay_state(port, state=state) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# SPDX-FileCopyrightText: 2022-2025 Technology Innovation Institute (TII) | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import serial | ||
import time | ||
import sys | ||
|
||
def read_relay_status(port): | ||
""" | ||
Reads the status of KMTronic 4 channel relays via the specified serial port. | ||
:param port: Serial port (e.g., /dev/ttyUSB0) | ||
""" | ||
try: | ||
# Configure the serial port | ||
ser = serial.Serial( | ||
port=port, | ||
baudrate=9600, | ||
bytesize=serial.EIGHTBITS, | ||
parity=serial.PARITY_NONE, | ||
stopbits=serial.STOPBITS_ONE, | ||
timeout=1 | ||
) | ||
|
||
# Send the status command | ||
ser.write(bytearray.fromhex('FF0900')) | ||
time.sleep(0.1) # Wait for the response | ||
|
||
# Read the response | ||
response = ser.read(4) # Read 4 bytes, one byte per relay | ||
ser.close() | ||
|
||
if len(response) == 4: | ||
# Parse and display the relay statuses | ||
relay_status = [ | ||
f"Relay {i+1}: {'ON' if byte == 1 else 'OFF'}" | ||
for i, byte in enumerate(response) | ||
] | ||
print("\n".join(relay_status)) | ||
else: | ||
print("Error: Unexpected response length.") | ||
except serial.SerialException as e: | ||
print(f"Serial port error: {e}") | ||
except Exception as e: | ||
print(f"An error occurred: {e}") | ||
|
||
if __name__ == "__main__": | ||
# Check for the port argument | ||
if len(sys.argv) != 2: | ||
print("Usage: python kmtronic_status.py <serial_port>") | ||
print("Example: python kmtronic_status.py /dev/ttyUSB0") | ||
else: | ||
port = sys.argv[1] | ||
read_relay_status(port) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# SPDX-FileCopyrightText: 2022-2025 Technology Innovation Institute (TII) | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import serial | ||
import time | ||
from robot.libraries.BuiltIn import BuiltIn | ||
|
||
class KMTronicLibrary: | ||
""" | ||
Robot Framework Library for controlling KMTronic USB relays. | ||
""" | ||
|
||
def __init__(self, port): | ||
"""Initialize the serial connection""" | ||
self.ser = serial.Serial( | ||
port=port, # Port to which the device is connected | ||
baudrate=9600, # Baud rate | ||
bytesize=serial.EIGHTBITS, # Number of data bits | ||
parity=serial.PARITY_NONE, # No parity | ||
stopbits=serial.STOPBITS_ONE, # One stop bit | ||
timeout=1 # Timeout for reading data | ||
) | ||
if self.ser.is_open: | ||
print(f"Connection established successfully on port {port}.") | ||
else: | ||
raise RuntimeError(f"Failed to open connection on port {port}") | ||
|
||
def close_relay_board_connection(self): | ||
""" | ||
Close the serial connection. | ||
""" | ||
if self.ser and self.ser.is_open: | ||
self.ser.close() | ||
self.ser = None | ||
print("Connection closed.") | ||
else: | ||
print("Connection is already closed.") | ||
|
||
def _check_connection(self): | ||
""" | ||
Internal method to check if the serial connection is open. | ||
Raises an exception if the connection is not open. | ||
""" | ||
if self.ser is None: | ||
raise RuntimeError("Serial connection is not initialized. Call 'open_connection' first.") | ||
if not self.ser.is_open: | ||
raise RuntimeError("Serial connection is not open. Call 'open_connection' first.") | ||
print("Serial connection is open and ready.") | ||
|
||
def set_relay_state(self, relay_number, state): | ||
""" | ||
Set the state of a specific relay. | ||
:param relay_number: Relay number (1-4). | ||
:param state: Desired state ("ON" or "OFF"). | ||
""" | ||
self._check_connection() | ||
|
||
# Ensure relay_number is an integer | ||
try: | ||
relay_number = int(relay_number) | ||
except ValueError: | ||
raise ValueError(f"Invalid relay number: {relay_number}. Must be an integer.") | ||
|
||
if relay_number < 1 or relay_number > 4: | ||
raise ValueError("Relay number must be between 1 and 4.") | ||
|
||
if state not in ["ON", "OFF"]: | ||
raise ValueError("State must be 'ON' or 'OFF'.") | ||
|
||
# Send the command to the specific relay | ||
command = bytearray.fromhex('FF') + bytearray([relay_number]) + (b'\x01' if state == "ON" else b'\x00') | ||
self.ser.write(command) | ||
time.sleep(0.1) # Wait for the command to process | ||
print(f"Relay {relay_number} set to {state}.") | ||
BuiltIn().log_to_console(f"Relay {relay_number} set to {state}.") | ||
|
||
def get_relay_state(self, relay_number): | ||
""" | ||
Get the state of a specific relay. | ||
:param relay_number: Relay number (1-4). | ||
:return: "ON" if the relay is ON, otherwise "OFF". | ||
""" | ||
self._check_connection() | ||
|
||
# Ensure relay_number is an integer | ||
try: | ||
relay_number = int(relay_number) | ||
except ValueError: | ||
raise ValueError(f"Invalid relay number: {relay_number}. Must be an integer.") | ||
|
||
if relay_number < 1 or relay_number > 4: | ||
raise ValueError("Relay number must be between 1 and 4.") | ||
|
||
# Send status command | ||
self.ser.write(bytearray.fromhex('FF0900')) | ||
time.sleep(0.1) # Wait for response | ||
|
||
# Read response (4 bytes, one byte per relay) | ||
response = self.ser.read(4) | ||
|
||
if len(response) != 4: | ||
raise RuntimeError("Invalid response length received.") | ||
|
||
# Decode the state of the specified relay | ||
relay_state = response[relay_number - 1] | ||
return "ON" if relay_state == 1 else "OFF" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.