Open
Description
Ok so I have this annoying UPS that constantly gives me Zeros on nut and it was pissing me off that I ignored it for a long while.
I captured data I reversed the bullshit from UPSmart. I am sure some of you know what I am talking about.
Now I am no expert and nut kept giving me 0 "ZEROS"
000.0 000.0 000.0 000 00.0 0.00 00.0 00000000
on what ever driver I tried and protocol.
I used python this code
import usb.core
import usb.util
import time
VENDOR_ID = 0x0001 # Vendor ID of the device
PRODUCT_ID = 0x0000 # Product ID of the device
TIMEOUT = 60000 # Timeout in milliseconds
RETRY_LIMIT = 5
RETRY_DELAY = 2 # seconds
def log_debug(message):
"""Helper function to log debug messages with timestamps."""
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {message}")
def initialize_ups(dev):
log_debug("Initializing MEC0003 UPS...")
setup_commands = [
(0x80, 0x06, 0x0300, 0x0000, 255), # Get String Descriptor
(0x80, 0x06, 0x0303, 0x0409, 255) # Get Specific String Descriptor
]
for request in setup_commands:
try:
log_debug(f"Sending setup command: {request}")
response = dev.ctrl_transfer(*request, TIMEOUT)
log_debug(f"Response: {list(response)}")
except usb.core.USBError as e:
log_debug(f"[ERROR] Failed to initialize UPS with command {request}: {e}")
def find_ups():
log_debug("Searching for MEC0003 UPS device...")
dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)
if dev is None:
log_debug("[ERROR] MEC0003 UPS device not found.")
return None
log_debug("MEC0003 UPS device found! Resetting and configuring...")
dev.reset()
# Detach kernel driver if necessary
if dev.is_kernel_driver_active(0):
log_debug("Kernel driver is active. Detaching...")
dev.detach_kernel_driver(0)
dev.set_configuration()
cfg = dev.get_active_configuration()
intf = cfg[(0, 0)]
# Claim the interface for our use
usb.util.claim_interface(dev, 0)
initialize_ups(dev)
# Find the interrupt endpoints (for completeness)
ep_in = usb.util.find_descriptor(
intf,
custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN
)
ep_out = usb.util.find_descriptor(
intf,
custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT
)
if not ep_in or not ep_out:
log_debug("[ERROR] Could not find required interrupt endpoints.")
return None
log_debug(f"Interrupt IN Endpoint: {hex(ep_in.bEndpointAddress)}")
log_debug(f"Interrupt OUT Endpoint: {hex(ep_out.bEndpointAddress)}")
return dev, ep_in, ep_out
def check_device_connection(dev):
"""Check if the device is still connected and accessible."""
try:
dev.ctrl_transfer(0x80, 0x06, 0x0300, 0x0000, 255)
return True
except usb.core.USBError as e:
log_debug(f"[ERROR] Device not connected: {e}")
return False
def send_command(dev, cmd, name):
"""
Send a command using a control transfer.
For string descriptor queries, this is the standard method.
"""
# full_cmd: Use the command bytes as-is; for control transfers, we pass the parameters directly.
log_debug(f"\nSending {name} command: {[hex(x) for x in cmd]}")
for attempt in range(RETRY_LIMIT):
if not check_device_connection(dev):
log_debug("[ERROR] Device is not connected. Reinitializing...")
result = find_ups()
if result is None:
log_debug("[ERROR] Failed to reinitialize device.")
return None
else:
dev, _, _ = result
try:
# For control transfers, the parameters are:
# bmRequestType, bRequest, wValue, wIndex, wLength
# Our Megatec query uses:
# bmRequestType = 0x80, bRequest = 0x06, wValue = 0x0303, wIndex = 0x0409, wLength = 102
response = dev.ctrl_transfer(0x80, 0x06, 0x0303, 0x0409, 102, TIMEOUT)
log_debug(f"Raw response: {list(response)}")
return response
except usb.core.USBError as e:
if e.errno == 110:
log_debug(f"[WARNING] Timeout occurred on {name} (Attempt {attempt + 1}/{RETRY_LIMIT}). Retrying...")
time.sleep(RETRY_DELAY)
continue
else:
log_debug(f"[ERROR] Control transfer failed for {name}: {e}")
return None
log_debug(f"Max retries reached for {name}. Operation failed.")
return None
def parse_device_response(response_bytes):
"""
Parse the raw device response.
USB string descriptors:
- The first byte is bLength (e.g., 96)
- The second byte is bDescriptorType (0x03 for string)
- The rest is UTF-16LE encoded text.
"""
if len(response_bytes) < 2:
log_debug("[ERROR] Response too short to parse.")
return
# Remove the first two bytes
string_data = bytes(response_bytes[2:])
try:
decoded = string_data.decode('utf-16le', errors='ignore').strip('\x00')
log_debug(f"Decoded response: {decoded}")
except Exception as e:
log_debug(f"[ERROR] Failed to decode response: {e}")
return
# Remove any surrounding parentheses, if present.
cleaned = decoded.strip("()")
log_debug(f"Cleaned response: {cleaned}")
# Split the string into parts (assuming space-separated values)
parts = cleaned.split()
log_debug(f"Response parts: {parts}")
try:
# Based on expected format, parse the parts.
voltage = float(parts[0]) if parts[0] else 0.0
# parts[1] might be unused or another parameter; adjust as needed.
temperature = float(parts[4]) if len(parts) > 4 else 0.0
battery_status = float(parts[5]) if len(parts) > 5 else 0.0
load = float(parts[6]) if len(parts) > 6 else 0.0
log_debug(f"Parsed Data: Voltage: {voltage} V, Temperature: {temperature}°C, Battery: {battery_status}%, Load: {load}%")
except ValueError as e:
log_debug(f"[ERROR] Failed to parse numeric values: {e}")
def query_all_data(dev):
# The Megatec "All Data Query" is sent as a control transfer.
# The command parameters based on the raw capture are:
# bmRequestType = 0x80, bRequest = 0x06, wValue = 0x0303, wIndex = 0x0409, wLength = 102
response = send_command(dev, [0x80, 0x06, 0x03, 0x03, 0x04, 0x09, 0x66, 0x00], "All Data Query")
if response:
parse_device_response(response)
else:
log_debug("[ERROR] No valid response received for All Data Query.")
def main():
result = find_ups()
if result is None:
log_debug("Device initialization failed.")
return
dev, ep_in, ep_out = result
query_all_data(dev)
if __name__ == "__main__":
main()
which would give me
025-02-02 13:25:31 - Searching for MEC0003 UPS device...
2025-02-02 13:25:31 - MEC0003 UPS device found! Resetting and configuring...
2025-02-02 13:25:31 - Initializing MEC0003 UPS...
2025-02-02 13:25:31 - Sending setup command: (128, 6, 768, 0, 255)
2025-02-02 13:25:31 - Response: [4, 3, 9, 4]
2025-02-02 13:25:31 - Sending setup command: (128, 6, 771, 1033, 255)
2025-02-02 13:25:31 - Response: [96, 3, 40, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 32, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 46, 0, 48, 0, 48, 0, 32, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 13, 0]
2025-02-02 13:25:31 - Interrupt IN Endpoint: 0x81
2025-02-02 13:25:31 - Interrupt OUT Endpoint: 0x2
2025-02-02 13:25:31 -
Sending All Data Query command: ['0x80', '0x6', '0x3', '0x3', '0x4', '0x9', '0x66', '0x0']
2025-02-02 13:25:31 - Raw response: [96, 3, 40, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 32, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 46, 0, 48, 0, 48, 0, 32, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 48, 0, 13, 0]
2025-02-02 13:25:31 - Decoded response: (000.0 000.0 000.0 000 00.0 0.00 00.0 00000000
2025-02-02 13:25:31 - Cleaned response: 000.0 000.0 000.0 000 00.0 0.00 00.0 00000000
2025-02-02 13:25:31 - Response parts: ['000.0', '000.0', '000.0', '000', '00.0', '0.00', '00.0', '00000000']
2025-02-02 13:25:31 - Parsed Data: Voltage: 0.0 V, Temperature: 0.0°C, Battery: 0.0%, Load: 0.0%
i ran it again out of frustration
2025-02-02 13:25:38 - Searching for MEC0003 UPS device...
2025-02-02 13:25:38 - MEC0003 UPS device found! Resetting and configuring...
2025-02-02 13:25:38 - Initializing MEC0003 UPS...
2025-02-02 13:25:38 - Sending setup command: (128, 6, 768, 0, 255)
2025-02-02 13:25:38 - Response: [4, 3, 9, 4]
2025-02-02 13:25:38 - Sending setup command: (128, 6, 771, 1033, 255)
2025-02-02 13:25:38 - Response: [96, 3, 40, 0, 50, 0, 50, 0, 54, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 50, 0, 50, 0, 55, 0, 46, 0, 48, 0, 32, 0, 48, 0, 50, 0, 57, 0, 32, 0, 52, 0, 57, 0, 46, 0, 57, 0, 32, 0, 50, 0, 55, 0, 46, 0, 49, 0, 32, 0, 50, 0, 57, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 48, 0, 49, 0, 48, 0, 48, 0, 48, 0, 13, 0]
2025-02-02 13:25:38 - Interrupt IN Endpoint: 0x81
2025-02-02 13:25:38 - Interrupt OUT Endpoint: 0x2
2025-02-02 13:25:38 -
Sending All Data Query command: ['0x80', '0x6', '0x3', '0x3', '0x4', '0x9', '0x66', '0x0']
2025-02-02 13:25:38 - Raw response: [96, 3, 40, 0, 50, 0, 50, 0, 54, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 46, 0, 48, 0, 32, 0, 50, 0, 50, 0, 55, 0, 46, 0, 48, 0, 32, 0, 48, 0, 50, 0, 57, 0, 32, 0, 52, 0, 57, 0, 46, 0, 57, 0, 32, 0, 50, 0, 55, 0, 46, 0, 49, 0, 32, 0, 50, 0, 57, 0, 46, 0, 48, 0, 32, 0, 48, 0, 48, 0, 48, 0, 48, 0, 49, 0, 48, 0, 48, 0, 48, 0, 13, 0]
2025-02-02 13:25:38 - Decoded response: (226.0 000.0 227.0 029 49.9 27.1 29.0 00001000
2025-02-02 13:25:38 - Cleaned response: 226.0 000.0 227.0 029 49.9 27.1 29.0 00001000
2025-02-02 13:25:38 - Response parts: ['226.0', '000.0', '227.0', '029', '49.9', '27.1', '29.0', '00001000']
2025-02-02 13:25:38 - Parsed Data: Voltage: 226.0 V, Temperature: 49.9°C, Battery: 27.1%, Load: 29.0%
Finally some results. hopefully this code can help some people in the future.
I must mention those parsed data are named incorrectly
here is csv of some data
State | Command |
---|---|
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 227.0 034 50.1 27.1 29.0 00001000 |
Send | 80 06 0d 03 09 04 00 66 |
Receive | # . . 24.00 . |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 226.0 035 50.0 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 226.0 033 49.9 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 226.0 032 49.9 27.1 29.0 00001000 |
Send | 80 06 0c 03 09 04 00 66 |
Receive | # V3.8 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 225.0 033 49.9 27.1 29.0 00001000 |
Send | 80 06 0d 03 09 04 00 66 |
Receive | # . . 24.00 . |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 226.0 034 50.1 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 227.0 033 50.0 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 225.0 034 50.1 27.1 29.0 00001000 |
Send | 80 06 0c 03 09 04 00 66 |
Receive | # V3.8 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (224.0 000.0 225.0 034 50.3 27.1 29.0 00001000 |
Send | 80 06 0d 03 09 04 00 66 |
Receive | # . . 24.00 . |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 226.0 034 50.0 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 225.0 034 50.0 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 226.0 034 50.1 27.1 29.0 00001000 |
Send | 80 06 0c 03 09 04 00 66 |
Receive | # V3.8 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 226.0 034 50.1 27.1 29.0 00001000 |
Send | 80 06 0d 03 09 04 00 66 |
Receive | # . . 24.00 . |
Send | 80 06 03 03 09 04 00 66 |
Receive | (227.0 000.0 227.0 034 49.8 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 225.0 034 50.2 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 226.0 032 49.8 27.1 29.0 00001000 |
Send | 80 06 0c 03 09 04 00 66 |
Receive | # |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 226.0 031 49.8 27.1 29.0 00001000 |
Send | 80 06 0d 03 09 04 00 66 |
Receive | #000.0 0.0 00.00 00.0 |
Send | 80 06 03 03 09 04 00 66 |
Receive | � |
Send | 80 06 03 03 09 04 00 66 |
Receive | (000.0 000.0 000.0 000 00.0 0.00 00.0 00000000 |
Receive | # |
Send | 80 06 03 03 09 04 00 66 |
Send | 80 06 0c 03 09 04 00 66 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 225.0 029 50.0 27.1 29.0 00001000 |
Send | 80 06 0d 03 09 04 00 66 |
Receive | # . . 24.00 . |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 226.0 029 50.0 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (225.0 000.0 226.0 029 50.1 27.1 29.0 00001000 |
Send | 80 06 03 03 09 04 00 66 |
Receive | (226.0 000.0 226.0 029 50.1 27.1 29.0 00001000 |
Send | 80 06 0c 03 09 04 00 66 |
Receive | # |
Send | 80 06 03 03 09 04 00 66 |
Receive | (223.0 000.0 224.0 029 50.0 27.1 29.0 00001000 |
Send | 80 06 0d 03 09 04 00 66 |
Receive | #000.0 0.0 00.00 00.0 |
Send | 80 06 03 03 09 04 00 66 |
Receive | � |
Send | 80 06 03 03 09 04 00 66 |
Receive | (000.0 000.0 000.0 000 00.0 0.00 00.0 00000000 |
Receive | # |
Send | 80 06 03 03 09 04 00 66 |
I hope that some people with better knowledge pertaining to nut can implement something. I tried 2.7 and compiled from git and nothing seemed to work.
Metadata
Metadata
Assignees
Labels
Driver based on Megatec Q<number> such as new nutdrv_qx, or obsoleted blazer and some othersCore team members can not commit to these tasks, but community would benefit from their completionSubmitted vendor-provided or user-discovered protocol information, or similar data (measurements...)Issues reported against NUT release 2.8.2 (maybe vanilla or with minor packaging tweaks)