asmb6-api 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- asmb6_api-1.0.0/PKG-INFO +133 -0
- asmb6_api-1.0.0/asmb6/__init__.py +3 -0
- asmb6_api-1.0.0/asmb6/apmi_interface.py +161 -0
- asmb6_api-1.0.0/asmb6/parser.py +167 -0
- asmb6_api-1.0.0/asmb6_api.egg-info/PKG-INFO +133 -0
- asmb6_api-1.0.0/asmb6_api.egg-info/SOURCES.txt +9 -0
- asmb6_api-1.0.0/asmb6_api.egg-info/dependency_links.txt +1 -0
- asmb6_api-1.0.0/asmb6_api.egg-info/requires.txt +1 -0
- asmb6_api-1.0.0/asmb6_api.egg-info/top_level.txt +1 -0
- asmb6_api-1.0.0/setup.cfg +4 -0
- asmb6_api-1.0.0/setup.py +33 -0
asmb6_api-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asmb6-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python API for ASMB6-iKVM remote management.
|
|
5
|
+
Home-page: https://github.com/grifonice99/asmb6-api
|
|
6
|
+
Author: Grifonice99
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7,<4
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests<3,>=2.28
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# ASMB6 API
|
|
22
|
+
|
|
23
|
+
A Python interface for interacting with the ASMB6-iKVM remote management module.
|
|
24
|
+
|
|
25
|
+
This library provides programmatic access to authentication, session handling, hardware monitoring, and basic power management functions through the module’s HTTP management interface.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Overview
|
|
30
|
+
|
|
31
|
+
The ASMB6 API is designed as a lightweight Python wrapper around the module’s web-based management interface. It enables automation and integration into scripts or monitoring systems without relying on the WebUI.
|
|
32
|
+
|
|
33
|
+
The library provides:
|
|
34
|
+
|
|
35
|
+
* Session-based authentication
|
|
36
|
+
* Structured sensor objects
|
|
37
|
+
* Power and host state queries
|
|
38
|
+
* Programmatic power control operations
|
|
39
|
+
|
|
40
|
+
The goal is to expose a clean and predictable Python interface suitable for automation and monitoring tasks.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
### Authentication & Session Management
|
|
47
|
+
|
|
48
|
+
* Login and logout support
|
|
49
|
+
* Session validation
|
|
50
|
+
* Explicit return codes for API calls
|
|
51
|
+
|
|
52
|
+
### Hardware Monitoring
|
|
53
|
+
|
|
54
|
+
* Temperature sensors
|
|
55
|
+
* Voltage sensors
|
|
56
|
+
* Fan speed monitoring
|
|
57
|
+
* Power supply status
|
|
58
|
+
* Structured `Sensors` container with parsed values
|
|
59
|
+
* Access to raw sensor payload for debugging
|
|
60
|
+
|
|
61
|
+
### Host Control & Status
|
|
62
|
+
|
|
63
|
+
* Retrieve host power state
|
|
64
|
+
* Query power button status
|
|
65
|
+
* Power control operations (shutdown, power on, reset, cycle)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
Clone the repository:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/Grifonice99/asmb6-api
|
|
75
|
+
cd asmb6-api
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Install required dependency:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install requests
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Basic Usage
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from apmi import APMI
|
|
90
|
+
|
|
91
|
+
apmi = APMI("192.168.1.100")
|
|
92
|
+
|
|
93
|
+
# Authenticate
|
|
94
|
+
status = apmi.login("admin", "password")
|
|
95
|
+
if status != 0:
|
|
96
|
+
raise RuntimeError(f"Login failed with code {status}")
|
|
97
|
+
|
|
98
|
+
# Query host power state
|
|
99
|
+
powered_on = apmi.status()
|
|
100
|
+
print("Host is on" if powered_on else "Host is off")
|
|
101
|
+
|
|
102
|
+
# Retrieve sensors
|
|
103
|
+
sensors = apmi.sensors()
|
|
104
|
+
print(sensors.cpu1.reading)
|
|
105
|
+
|
|
106
|
+
# Logout
|
|
107
|
+
apmi.logout()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Design Notes
|
|
113
|
+
|
|
114
|
+
* Command execution methods return explicit status codes.
|
|
115
|
+
* Device state queries return domain-level values (e.g., `bool`).
|
|
116
|
+
* Sensor data is exposed through structured objects rather than raw dictionaries.
|
|
117
|
+
* Raw sensor payload data remains accessible for debugging or inspection purposes.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Disclaimer
|
|
122
|
+
|
|
123
|
+
This project was developed and tested on an ASUS Z9PE-D16 motherboard equipped with an ASMB6-iKVM module.
|
|
124
|
+
|
|
125
|
+
Compatibility with other motherboards, firmware versions, or ASMB6 variants is not guaranteed. Behavior may differ depending on firmware revisions or platform-specific implementations.
|
|
126
|
+
|
|
127
|
+
Users are encouraged to test carefully in their own environment before relying on this library in production systems.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
This project is licensed under the terms defined in the `LICENSE` file.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
from .parser import sanitize, serialize, parse_sensors, Sensors
|
|
5
|
+
|
|
6
|
+
start_ref = """<?xml version="1.0" encoding="UTF-8"?>
|
|
7
|
+
|
|
8
|
+
<jnlp spec="1.0+" codebase="""
|
|
9
|
+
end_ref = """ </application-desc>
|
|
10
|
+
</jnlp>
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class APMI:
|
|
15
|
+
def __init__(self, ip, http_port=80):
|
|
16
|
+
self.http_port = http_port
|
|
17
|
+
self.username = None
|
|
18
|
+
self.ip = ip
|
|
19
|
+
self.session = requests.Session()
|
|
20
|
+
self.base = f"http://{self.ip}:{self.http_port}"
|
|
21
|
+
|
|
22
|
+
def login(self, username, password) -> int:
|
|
23
|
+
"""
|
|
24
|
+
Important:
|
|
25
|
+
Always call logout() before terminating the program.
|
|
26
|
+
|
|
27
|
+
Failing to do so may leave an active session on the
|
|
28
|
+
ASMB6-iKVM, which can cause login issues during
|
|
29
|
+
development or repeated test runs.
|
|
30
|
+
"""
|
|
31
|
+
data = {
|
|
32
|
+
"WEBVAR_USERNAME": username,
|
|
33
|
+
"WEBVAR_PASSWORD": password,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
res = self.session.post(self.base + "/rpc/WEBSES/create.asp", data=data)
|
|
37
|
+
|
|
38
|
+
json_res = json.loads(serialize(sanitize(res.text)))["WEBVAR_JSONVAR_WEB_SESSION"]
|
|
39
|
+
status = json_res["HAPI_STATUS"]
|
|
40
|
+
if status != 0:
|
|
41
|
+
print(f"Invalid credential")
|
|
42
|
+
else:
|
|
43
|
+
session_cookie = json_res["WEBVAR_STRUCTNAME_WEB_SESSION"][0]["SESSION_COOKIE"]
|
|
44
|
+
|
|
45
|
+
self.session.cookies.set("Username", username, domain=self.ip, path="/")
|
|
46
|
+
self.session.cookies.set("Language", "EN", domain=self.ip, path="/")
|
|
47
|
+
self.session.cookies.set("SessionCookie", session_cookie, domain=self.ip, path="/")
|
|
48
|
+
self.username = username
|
|
49
|
+
|
|
50
|
+
return status
|
|
51
|
+
|
|
52
|
+
def logout(self)->int:
|
|
53
|
+
res = self.session.get(self.base + "/rpc/WEBSES/logout.asp")
|
|
54
|
+
self.session = requests.Session()
|
|
55
|
+
self.username = None
|
|
56
|
+
return json.loads(serialize(sanitize(res.text)))["WEBVAR_JSONVAR_WEB_SESSION_LOGOUT"]["HAPI_STATUS"]
|
|
57
|
+
|
|
58
|
+
def validate_session(self)->int:
|
|
59
|
+
if not self.username:
|
|
60
|
+
raise Exception("You need to be authenticated")
|
|
61
|
+
res = self.session.get(self.base + "/rpc/WEBSES/validate.asp").text
|
|
62
|
+
return json.loads(serialize(sanitize(res.text)))["WEBVAR_JSONVAR_WEB_SESSION_VALIDATE"]["HAPI_STATUS"]
|
|
63
|
+
|
|
64
|
+
def sensors(self) -> Sensors:
|
|
65
|
+
if not self.username:
|
|
66
|
+
raise Exception("You need to be authenticated")
|
|
67
|
+
res = self.session.get(self.base + "/rpc/getallsensors.asp")
|
|
68
|
+
raw = json.loads(serialize(sanitize(res.text)))
|
|
69
|
+
sensors_data = raw["WEBVAR_JSONVAR_HL_GETALLSENSORS"][
|
|
70
|
+
"WEBVAR_STRUCTNAME_HL_GETALLSENSORS"
|
|
71
|
+
]
|
|
72
|
+
sensors = parse_sensors(sensors_data)
|
|
73
|
+
return sensors
|
|
74
|
+
|
|
75
|
+
def status(self) -> int:
|
|
76
|
+
if not self.username:
|
|
77
|
+
raise Exception("You need to be authenticated")
|
|
78
|
+
res = self.session.get(self.base + "/rpc/hoststatus.asp")
|
|
79
|
+
raw = json.loads(serialize(sanitize(res.text)))
|
|
80
|
+
return raw["WEBVAR_JSONVAR_HL_SYSTEM_STATE"][
|
|
81
|
+
"WEBVAR_STRUCTNAME_HL_SYSTEM_STATE"
|
|
82
|
+
][0]["JF_STATE"]
|
|
83
|
+
|
|
84
|
+
def pwr_btn_status(self) -> int:
|
|
85
|
+
if not self.username:
|
|
86
|
+
raise Exception("You need to be authenticated")
|
|
87
|
+
res = self.session.get(self.base + "/rpc/PWRbutton.asp")
|
|
88
|
+
raw = json.loads(serialize(sanitize(res.text)))
|
|
89
|
+
return raw["WEBVAR_JSONVAR_HL_BUTTON_STATE"][
|
|
90
|
+
"WEBVAR_STRUCTNAME_HL_BUTTON_STATE"
|
|
91
|
+
][0]["PB_STATE"]
|
|
92
|
+
|
|
93
|
+
def turn_off_board(self, immediate=False) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Note:
|
|
96
|
+
immediate=True may not turn the board off if it was powered on
|
|
97
|
+
less than 15 seconds before this call.
|
|
98
|
+
"""
|
|
99
|
+
if not self.username:
|
|
100
|
+
raise Exception("You need to be authenticated")
|
|
101
|
+
if immediate:
|
|
102
|
+
res = self.session.post(
|
|
103
|
+
self.base + "/rpc/hostctl.asp", data={"WEBVAR_POWER_CMD": 0}
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
res = self.session.post(
|
|
107
|
+
self.base + "/rpc/hostctl.asp", data={"WEBVAR_POWER_CMD": 5}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def turn_on_board(self) -> None:
|
|
111
|
+
if not self.username:
|
|
112
|
+
raise Exception("You need to be authenticated")
|
|
113
|
+
res = self.session.post(
|
|
114
|
+
self.base + "/rpc/hostctl.asp", data={"WEBVAR_POWER_CMD": 1}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def power_cycle_board(self) -> None:
|
|
118
|
+
if not self.username:
|
|
119
|
+
raise Exception("You need to be authenticated")
|
|
120
|
+
res = self.session.post(
|
|
121
|
+
self.base + "/rpc/hostctl.asp", data={"WEBVAR_POWER_CMD": 2}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def reset_board(self) -> None:
|
|
125
|
+
if not self.username:
|
|
126
|
+
raise Exception("You need to be authenticated")
|
|
127
|
+
res = self.session.post(
|
|
128
|
+
self.base + "/rpc/hostctl.asp", data={"WEBVAR_POWER_CMD": 3}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def get_events(self) -> dict:
|
|
132
|
+
if not self.username:
|
|
133
|
+
raise Exception("You need to be authenticated")
|
|
134
|
+
res = self.session.get(self.base + "/rpc/getallselentries.asp")
|
|
135
|
+
result = json.loads(serialize(sanitize(res.text)))[
|
|
136
|
+
"WEBVAR_JSONVAR_HL_GETALLSELENTRIES"
|
|
137
|
+
]["WEBVAR_STRUCTNAME_HL_GETALLSELENTRIES"][:-1]
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
def get_jnlp(self) -> str:
|
|
141
|
+
if not self.username:
|
|
142
|
+
raise Exception("You need to be authenticated")
|
|
143
|
+
|
|
144
|
+
res = self.session.get(
|
|
145
|
+
self.base + f"/Java/jviewer.jnlp?EXTRNIP={self.base}&JNLPSTR=JViewer",
|
|
146
|
+
stream=True,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
data = b""
|
|
150
|
+
try:
|
|
151
|
+
for chunk in res.iter_content(1):
|
|
152
|
+
if chunk:
|
|
153
|
+
data += chunk
|
|
154
|
+
except requests.exceptions.RequestException as e:
|
|
155
|
+
# Strings parsed from the firmware's jviewer.jnlp template.
|
|
156
|
+
if data.startswith(start_ref.encode()) and data.endswith(end_ref.encode()):
|
|
157
|
+
print("Notice: The data appears to have downloaded correctly, but the firmware closed the connection before all content-length data was received.")
|
|
158
|
+
else:
|
|
159
|
+
raise e
|
|
160
|
+
|
|
161
|
+
return data.decode()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
# IPMI sensor type codes:
|
|
4
|
+
# 1 : Temperature
|
|
5
|
+
# 2 : Voltage
|
|
6
|
+
# 4 : Fan
|
|
7
|
+
# 5 : Physical Security
|
|
8
|
+
# 8 : PMB Power
|
|
9
|
+
# 12 : Memory ECC Sensor
|
|
10
|
+
# 13 : Drive Slot
|
|
11
|
+
# 35 : Watchdog
|
|
12
|
+
# 193 : OEM Memory ECC Sensor
|
|
13
|
+
# 220 : Node Manager capabilities
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class base:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.reading = None
|
|
20
|
+
self.unr = None
|
|
21
|
+
self.uc = None
|
|
22
|
+
self.unc = None
|
|
23
|
+
self.lnr = None
|
|
24
|
+
self.lc = None
|
|
25
|
+
self.lnc = None
|
|
26
|
+
self.state = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Temperature(base):
|
|
30
|
+
def __init__(self):
|
|
31
|
+
super().__init__()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Voltage(base):
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
super().__init__()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Fan(base):
|
|
41
|
+
def __init__(self):
|
|
42
|
+
super().__init__()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class StateOnly:
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.state = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PowerSupply(base):
|
|
51
|
+
def __init__(self):
|
|
52
|
+
super().__init__()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Fan(base):
|
|
56
|
+
def __init__(self):
|
|
57
|
+
super().__init__()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Sensors:
|
|
61
|
+
def __init__(self):
|
|
62
|
+
self.cpu1: Temperature = Temperature()
|
|
63
|
+
self.cpu2: Temperature = Temperature()
|
|
64
|
+
self.tr1: Temperature = Temperature()
|
|
65
|
+
self.tr2: Temperature = Temperature()
|
|
66
|
+
self.vcore1: Voltage = Voltage()
|
|
67
|
+
self.vcore2: Voltage = Voltage()
|
|
68
|
+
self.vtt_cpu: Voltage = Voltage()
|
|
69
|
+
self.vddq_ab_cpu1: Voltage = Voltage()
|
|
70
|
+
self.vddq_cd_cpu1: Voltage = Voltage()
|
|
71
|
+
self.vddq_ef_cpu2: Voltage = Voltage()
|
|
72
|
+
self.vddq_gh_cpu2: Voltage = Voltage()
|
|
73
|
+
self.v12: Voltage = Voltage()
|
|
74
|
+
self.v5: Voltage = Voltage()
|
|
75
|
+
self.vsb5: Voltage = Voltage()
|
|
76
|
+
self.v3_3: Voltage = Voltage()
|
|
77
|
+
self.vsb3_3: Voltage = Voltage()
|
|
78
|
+
self.vbat: Voltage = Voltage()
|
|
79
|
+
self.cpu_fan1: Fan = Fan()
|
|
80
|
+
self.cpu_fan2: Fan = Fan()
|
|
81
|
+
self.frnt_fan1: Fan = Fan()
|
|
82
|
+
self.frnt_fan2: Fan = Fan()
|
|
83
|
+
self.frnt_fan3: Fan = Fan()
|
|
84
|
+
self.frnt_fan4: Fan = Fan()
|
|
85
|
+
self.rear_fan1: Fan = Fan()
|
|
86
|
+
self.rear_fan2: Fan = Fan()
|
|
87
|
+
self.cpu1_ecc1: StateOnly = StateOnly()
|
|
88
|
+
self.cpu1_ecc2: StateOnly = StateOnly()
|
|
89
|
+
self.cpu2_ecc1: StateOnly = StateOnly()
|
|
90
|
+
self.cpu2_ecc2: StateOnly = StateOnly()
|
|
91
|
+
self.pmbpower: PowerSupply = PowerSupply()
|
|
92
|
+
self.chassisintrusion: StateOnly = StateOnly()
|
|
93
|
+
self.watchdog2: StateOnly = StateOnly()
|
|
94
|
+
self.nm_capabilities: StateOnly = StateOnly()
|
|
95
|
+
self.raw = {}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def serialize(raw):
|
|
99
|
+
raw = raw.replace("=", ":")
|
|
100
|
+
raw = raw.replace(";", "")
|
|
101
|
+
raw = re.sub(r"(\b[A-Za-z_][A-Za-z0-9_]*\b)\s*:", r'"\1":', raw)
|
|
102
|
+
raw = raw.replace("'", '"')
|
|
103
|
+
return raw
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def sanitize(text: str):
|
|
107
|
+
return (
|
|
108
|
+
"{"
|
|
109
|
+
+ "\n".join(filter(lambda x: not x.startswith("//"), text.split("\n")))
|
|
110
|
+
+ "}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def Parse_data(raw) -> base:
|
|
115
|
+
sensor: base = base()
|
|
116
|
+
sensor.lnc = raw["LowNCThresh"]
|
|
117
|
+
sensor.lc = raw["LowCTThresh"]
|
|
118
|
+
sensor.lnr = raw["LowNRThresh"]
|
|
119
|
+
sensor.unc = raw["HighNCThresh"]
|
|
120
|
+
sensor.uc = raw["HighCTThresh"]
|
|
121
|
+
sensor.unr = raw["HighNRThresh"]
|
|
122
|
+
sensor.reading = raw["SensorReading"]/1000
|
|
123
|
+
sensor.state = raw["SensorState"]
|
|
124
|
+
return sensor
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def parse_sensors(raw) -> Sensors:
|
|
128
|
+
SensorsDict = {}
|
|
129
|
+
for x in raw:
|
|
130
|
+
if "SensorNumber" in x:
|
|
131
|
+
SensorsDict[x["SensorNumber"]] = x
|
|
132
|
+
|
|
133
|
+
sensors = Sensors()
|
|
134
|
+
sensors.cpu1 = Parse_data(SensorsDict[49])
|
|
135
|
+
sensors.cpu2 = Parse_data(SensorsDict[50])
|
|
136
|
+
sensors.vcore1 = Parse_data(SensorsDict[52])
|
|
137
|
+
sensors.vcore2 = Parse_data(SensorsDict[53])
|
|
138
|
+
sensors.v3_3 = Parse_data(SensorsDict[54])
|
|
139
|
+
sensors.v5 = Parse_data(SensorsDict[55])
|
|
140
|
+
sensors.v12 = Parse_data(SensorsDict[56])
|
|
141
|
+
sensors.vsb5 = Parse_data(SensorsDict[59])
|
|
142
|
+
sensors.vbat = Parse_data(SensorsDict[60])
|
|
143
|
+
sensors.vtt_cpu = Parse_data(SensorsDict[61])
|
|
144
|
+
sensors.vsb3_3 = Parse_data(SensorsDict[64])
|
|
145
|
+
sensors.vddq_ab_cpu1 = Parse_data(SensorsDict[77])
|
|
146
|
+
sensors.vddq_cd_cpu1 = Parse_data(SensorsDict[78])
|
|
147
|
+
sensors.vddq_ef_cpu2 = Parse_data(SensorsDict[80])
|
|
148
|
+
sensors.vddq_gh_cpu2 = Parse_data(SensorsDict[81])
|
|
149
|
+
sensors.cpu_fan1 = Parse_data(SensorsDict[160])
|
|
150
|
+
sensors.cpu_fan2 = Parse_data(SensorsDict[161])
|
|
151
|
+
sensors.frnt_fan1 = Parse_data(SensorsDict[162])
|
|
152
|
+
sensors.frnt_fan2 = Parse_data(SensorsDict[163])
|
|
153
|
+
sensors.frnt_fan3 = Parse_data(SensorsDict[164])
|
|
154
|
+
sensors.frnt_fan4 = Parse_data(SensorsDict[165])
|
|
155
|
+
sensors.rear_fan1 = Parse_data(SensorsDict[166])
|
|
156
|
+
sensors.rear_fan2 = Parse_data(SensorsDict[167])
|
|
157
|
+
sensors.tr1 = Parse_data(SensorsDict[204])
|
|
158
|
+
sensors.tr2 = Parse_data(SensorsDict[205])
|
|
159
|
+
sensors.nm_capabilities.state = SensorsDict[26]["SensorState"]
|
|
160
|
+
sensors.chassisintrusion.state = SensorsDict[79]["SensorState"]
|
|
161
|
+
sensors.cpu1_ecc1.state = SensorsDict[209]["SensorState"]
|
|
162
|
+
sensors.cpu1_ecc2.state = SensorsDict[210]["SensorState"]
|
|
163
|
+
sensors.cpu2_ecc1.state = SensorsDict[211]["SensorState"]
|
|
164
|
+
sensors.cpu2_ecc2.state = SensorsDict[212]["SensorState"]
|
|
165
|
+
sensors.watchdog2.state = SensorsDict[255]["SensorState"]
|
|
166
|
+
sensors.raw=SensorsDict
|
|
167
|
+
return sensors
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asmb6-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python API for ASMB6-iKVM remote management.
|
|
5
|
+
Home-page: https://github.com/grifonice99/asmb6-api
|
|
6
|
+
Author: Grifonice99
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7,<4
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests<3,>=2.28
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# ASMB6 API
|
|
22
|
+
|
|
23
|
+
A Python interface for interacting with the ASMB6-iKVM remote management module.
|
|
24
|
+
|
|
25
|
+
This library provides programmatic access to authentication, session handling, hardware monitoring, and basic power management functions through the module’s HTTP management interface.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Overview
|
|
30
|
+
|
|
31
|
+
The ASMB6 API is designed as a lightweight Python wrapper around the module’s web-based management interface. It enables automation and integration into scripts or monitoring systems without relying on the WebUI.
|
|
32
|
+
|
|
33
|
+
The library provides:
|
|
34
|
+
|
|
35
|
+
* Session-based authentication
|
|
36
|
+
* Structured sensor objects
|
|
37
|
+
* Power and host state queries
|
|
38
|
+
* Programmatic power control operations
|
|
39
|
+
|
|
40
|
+
The goal is to expose a clean and predictable Python interface suitable for automation and monitoring tasks.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
### Authentication & Session Management
|
|
47
|
+
|
|
48
|
+
* Login and logout support
|
|
49
|
+
* Session validation
|
|
50
|
+
* Explicit return codes for API calls
|
|
51
|
+
|
|
52
|
+
### Hardware Monitoring
|
|
53
|
+
|
|
54
|
+
* Temperature sensors
|
|
55
|
+
* Voltage sensors
|
|
56
|
+
* Fan speed monitoring
|
|
57
|
+
* Power supply status
|
|
58
|
+
* Structured `Sensors` container with parsed values
|
|
59
|
+
* Access to raw sensor payload for debugging
|
|
60
|
+
|
|
61
|
+
### Host Control & Status
|
|
62
|
+
|
|
63
|
+
* Retrieve host power state
|
|
64
|
+
* Query power button status
|
|
65
|
+
* Power control operations (shutdown, power on, reset, cycle)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
Clone the repository:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/Grifonice99/asmb6-api
|
|
75
|
+
cd asmb6-api
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Install required dependency:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install requests
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Basic Usage
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from apmi import APMI
|
|
90
|
+
|
|
91
|
+
apmi = APMI("192.168.1.100")
|
|
92
|
+
|
|
93
|
+
# Authenticate
|
|
94
|
+
status = apmi.login("admin", "password")
|
|
95
|
+
if status != 0:
|
|
96
|
+
raise RuntimeError(f"Login failed with code {status}")
|
|
97
|
+
|
|
98
|
+
# Query host power state
|
|
99
|
+
powered_on = apmi.status()
|
|
100
|
+
print("Host is on" if powered_on else "Host is off")
|
|
101
|
+
|
|
102
|
+
# Retrieve sensors
|
|
103
|
+
sensors = apmi.sensors()
|
|
104
|
+
print(sensors.cpu1.reading)
|
|
105
|
+
|
|
106
|
+
# Logout
|
|
107
|
+
apmi.logout()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Design Notes
|
|
113
|
+
|
|
114
|
+
* Command execution methods return explicit status codes.
|
|
115
|
+
* Device state queries return domain-level values (e.g., `bool`).
|
|
116
|
+
* Sensor data is exposed through structured objects rather than raw dictionaries.
|
|
117
|
+
* Raw sensor payload data remains accessible for debugging or inspection purposes.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Disclaimer
|
|
122
|
+
|
|
123
|
+
This project was developed and tested on an ASUS Z9PE-D16 motherboard equipped with an ASMB6-iKVM module.
|
|
124
|
+
|
|
125
|
+
Compatibility with other motherboards, firmware versions, or ASMB6 variants is not guaranteed. Behavior may differ depending on firmware revisions or platform-specific implementations.
|
|
126
|
+
|
|
127
|
+
Users are encouraged to test carefully in their own environment before relying on this library in production systems.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
This project is licensed under the terms defined in the `LICENSE` file.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests<3,>=2.28
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
asmb6
|
asmb6_api-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
here = Path(__file__).parent.resolve()
|
|
6
|
+
|
|
7
|
+
init_text = (here / "asmb6" / "__init__.py").read_text(encoding="utf-8")
|
|
8
|
+
match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']',
|
|
9
|
+
init_text, re.M)
|
|
10
|
+
|
|
11
|
+
if not match:
|
|
12
|
+
raise RuntimeError("Version not found")
|
|
13
|
+
|
|
14
|
+
version = match.group(1)
|
|
15
|
+
|
|
16
|
+
long_description = (here / "readme.md").read_text(encoding="utf-8")
|
|
17
|
+
|
|
18
|
+
setup(
|
|
19
|
+
name="asmb6-api",
|
|
20
|
+
version=version,
|
|
21
|
+
description="Python API for ASMB6-iKVM remote management.",
|
|
22
|
+
long_description=long_description,
|
|
23
|
+
long_description_content_type="text/markdown",
|
|
24
|
+
url="https://github.com/grifonice99/asmb6-api",
|
|
25
|
+
author="Grifonice99",
|
|
26
|
+
python_requires=">=3.7,<4",
|
|
27
|
+
packages=find_packages(exclude=("tests", "docs")),
|
|
28
|
+
install_requires=["requests>=2.28,<3"],
|
|
29
|
+
classifiers=[
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
],
|
|
33
|
+
)
|