Skip to content

Commit

Permalink
Add relay_boot_test.robot
Browse files Browse the repository at this point in the history
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
vjuntunen committed Jan 22, 2025
1 parent b7b4977 commit a2033d3
Show file tree
Hide file tree
Showing 9 changed files with 441 additions and 0 deletions.
34 changes: 34 additions & 0 deletions KMTronic/README.md
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
70 changes: 70 additions & 0 deletions KMTronic/kmtronic_4ch_control.py
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)
54 changes: 54 additions & 0 deletions KMTronic/kmtronic_4ch_status.py
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)
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ the Python dependencies are available, run `nix develop`.

For more information, see [drcontrol-README.md](./drcontrol/drcontrol-README.md).

## kmtronic_4ch_control.py and kmtronic_4ch_status.py

For more information, see [README.md](./KMTronic/README.md).

## Git commit hook

When contributing to this repo you should take the git commit hook into use.
Expand Down
2 changes: 2 additions & 0 deletions Robot-Framework/config/variables.robot
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Set Variables

${config}= Read Config
Set Global Variable ${SERIAL_PORT} ${config['addresses']['${DEVICE}']['serial_port']}
Set Global Variable ${RELAY_SERIAL_PORT} ${config['addresses']['relay_serial_port']}
Set Global Variable ${RELAY_NUMBER} ${config['addresses']['${DEVICE}']['relay_number']}
Set Global Variable ${DEVICE_IP_ADDRESS} ${config['addresses']['${DEVICE}']['device_ip_address']}
Set Global Variable ${SOCKET_IP_ADDRESS} ${config['addresses']['${DEVICE}']['socket_ip_address']}
Set Global Variable ${PLUG_TYPE} ${config['addresses']['${DEVICE}']['plug_type']}
Expand Down
108 changes: 108 additions & 0 deletions Robot-Framework/lib/KMTronicLibrary.py
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"
10 changes: 10 additions & 0 deletions Robot-Framework/resources/device_control.resource
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ Reboot Device
Log To Console Turning device on...
Turn Plug On

Reboot Device Via Relay
[Arguments] ${delay}=5
[Documentation] Turn off power of device, wait for given amount of seconds and turn on the power
# Open Relay Serial Port ${RELAY_SERIAL_PORT}
Log To Console ${\n}Turning device off...
Turn Relay Off ${RELAY_NUMBER}
Sleep ${delay}
Log To Console Turning device on...
Turn Relay On ${RELAY_NUMBER}

Reboot LenovoX1
[Arguments] ${delay}=20
[Documentation] Turn off the laptop by pressing power button for 10 sec turn on by short pressing power button
Expand Down
36 changes: 36 additions & 0 deletions Robot-Framework/resources/serial_keywords.resource
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Library BuiltIn
Library String
Library AdvancedLogging
Library ../lib/output_parser.py
Library ../lib/KMTronicLibrary.py ${RELAY_SERIAL_PORT}

*** Keywords ***
Open Serial Port
Expand All @@ -19,6 +20,41 @@ Open Serial Port
... stopbits=1
... timeout=${timeout}

Open Relay Serial Port
[Arguments] ${serialr_port} ${timeout}=1.0 ${baudrate}=9600
Add Port ${serialr_port}
... baudrate=${baudrate}
... bytesize=8
... parity=N
... stopbits=1
... timeout=${timeout}

Turn Relay Off
[Documentation] Turn given ${relay_number} relay off.
[Arguments] ${relay_number}
Set Relay State ${relay_number} OFF
Sleep 1s
${state}= Get Relay State ${relay_number}
${status} = Run Keyword And Return Status Should Be Equal As Strings ${state} OFF
IF ${status}
Log To Console Relay number:${relay_number} is turned OFF!
ELSE
FAIL Relay number:${relay_number} failed to turn OFF!
END

Turn Relay On
[Documentation] Turn given ${relay_number} relay on.
[Arguments] ${relay_number}
Set Relay State ${relay_number} ON
Sleep 1s
${state}= Get Relay State ${relay_number}
${status} = Run Keyword And Return Status Should Be Equal As Strings ${state} ON
IF ${status}
Log To Console Relay number:${relay_number} is turned ON!
ELSE
FAIL Relay number:${relay_number} failed to turn ON!
END

Check Serial Connection
[Documentation] Check if device is available by serial
Log To Console Trying to connect via serial...
Expand Down
Loading

0 comments on commit a2033d3

Please sign in to comment.