Skip to content

Commit 707dc88

Browse files
committed
Add EC memory map abstractions
1 parent df3a680 commit 707dc88

File tree

6 files changed

+191
-15
lines changed

6 files changed

+191
-15
lines changed

cros_ec_python/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .cros_ec import CrOS_EC, DeviceTypes
2-
from .commands import general, features, pwm, leds, thermal
2+
from .commands import memmap, general, features, pwm, leds, thermal
33
from .exceptions import ECError

cros_ec_python/commands/general.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ def get_version(ec: CrOS_EC, version: Literal[0, 1] = 0) -> dict[str, str | int]
4646
resp = ec.command(version, EC_CMD_GET_VERSION, 0, 32 + 32 + 32 + 4)
4747
unpacked = struct.unpack("<32s32s32sI", resp)
4848
return {
49-
"version_string_ro": unpacked[0].decode("utf-8").strip("\x00"),
50-
"version_string_rw": unpacked[1].decode("utf-8").strip("\x00"),
51-
"reserved": unpacked[2].decode("utf-8").strip("\x00"),
49+
"version_string_ro": unpacked[0].decode("utf-8").rstrip("\x00"),
50+
"version_string_rw": unpacked[1].decode("utf-8").rstrip("\x00"),
51+
"reserved": unpacked[2].decode("utf-8").rstrip("\x00"),
5252
"current_image": unpacked[3]
5353
}
5454
case 1:
5555
resp = ec.command(version, EC_CMD_GET_VERSION, 0, 32 + 32 + 32 + 4 + 32)
5656
unpacked = struct.unpack("<32s32s32sI32s", resp)
5757
return {
58-
"version_string_ro": unpacked[0].decode("utf-8").strip("\x00"),
59-
"version_string_rw": unpacked[1].decode("utf-8").strip("\x00"),
60-
"cros_fwid_ro": unpacked[2].decode("utf-8").strip("\x00"),
58+
"version_string_ro": unpacked[0].decode("utf-8").rstrip("\x00"),
59+
"version_string_rw": unpacked[1].decode("utf-8").rstrip("\x00"),
60+
"cros_fwid_ro": unpacked[2].decode("utf-8").rstrip("\x00"),
6161
"current_image": unpacked[3],
62-
"crod_fwid_rw": unpacked[4].decode("utf-8").strip("\x00")
62+
"crod_fwid_rw": unpacked[4].decode("utf-8").rstrip("\x00")
6363
}
6464
case _:
6565
raise NotImplementedError
@@ -78,7 +78,7 @@ def get_build_info(ec: CrOS_EC) -> str:
7878
@return: The build info as a string.
7979
"""
8080
resp = ec.command(0, EC_CMD_GET_BUILD_INFO, 0, 0xfc, warn=False)
81-
return resp.decode("utf-8").strip("\x00")
81+
return resp.decode("utf-8").rstrip("\x00")
8282

8383

8484
EC_CMD_GET_CHIP_INFO: Final = 0x0005
@@ -93,9 +93,9 @@ def get_chip_info(ec: CrOS_EC) -> dict[str, str]:
9393
resp = ec.command(0, EC_CMD_GET_CHIP_INFO, 0, 32 + 32 + 32)
9494
unpacked = struct.unpack("<32s32s32s", resp)
9595
return {
96-
"vendor": unpacked[0].decode("utf-8").strip("\x00"),
97-
"name": unpacked[1].decode("utf-8").strip("\x00"),
98-
"revision": unpacked[2].decode("utf-8").strip("\x00")
96+
"vendor": unpacked[0].decode("utf-8").rstrip("\x00"),
97+
"name": unpacked[1].decode("utf-8").rstrip("\x00"),
98+
"revision": unpacked[2].decode("utf-8").rstrip("\x00")
9999
}
100100

101101

cros_ec_python/commands/memmap.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import struct
2+
from ..cros_ec import CrOS_EC
3+
from ..constants.COMMON import *
4+
from ..constants.MEMMAP import *
5+
6+
7+
def get_temps(ec: CrOS_EC, adjust: int | float = -273) -> list[int | float]:
8+
"""
9+
Get the temperature of all temp sensors.
10+
@param ec: The CrOS_EC object.
11+
@param adjust: The adjustment to apply to the temperature. Default is -273 to convert from Kelvin to Celsius.
12+
@return: A list of temperatures.
13+
"""
14+
version = int(ec.memmap(EC_MEMMAP_THERMAL_VERSION, 1)[0])
15+
if not version:
16+
# No temp sensors supported
17+
return []
18+
19+
ret = []
20+
if version >= 1:
21+
resp = ec.memmap(EC_MEMMAP_TEMP_SENSOR, EC_TEMP_SENSOR_ENTRIES)
22+
temps = struct.unpack(f"<{EC_TEMP_SENSOR_ENTRIES}B", resp)
23+
ret += [temp + EC_TEMP_SENSOR_OFFSET + adjust for temp in temps if temp < 0xFC]
24+
25+
if version >= 2:
26+
resp = ec.memmap(EC_MEMMAP_TEMP_SENSOR_B, EC_TEMP_SENSOR_B_ENTRIES)
27+
temps = struct.unpack(f"<{EC_TEMP_SENSOR_B_ENTRIES}B", resp)
28+
ret += [temp + EC_TEMP_SENSOR_OFFSET + adjust for temp in temps if temp < 0xFC]
29+
30+
return ret
31+
32+
33+
def get_fans(ec: CrOS_EC) -> list[int | None]:
34+
"""
35+
Get the speed of all fans.
36+
@param ec: The CrOS_EC object.
37+
@return: A list of fan speeds. None if the fan has stalled.
38+
"""
39+
version = int(ec.memmap(EC_MEMMAP_THERMAL_VERSION, 1)[0])
40+
if not version:
41+
# No fans supported
42+
return []
43+
44+
resp = ec.memmap(EC_MEMMAP_FAN, EC_FAN_SPEED_ENTRIES * 2) # 2 bytes per fan
45+
fans = struct.unpack(f"<{EC_FAN_SPEED_ENTRIES}H", resp)
46+
return [None if fan is EC_FAN_SPEED_STALLED else fan for fan in fans if fan < EC_FAN_SPEED_NOT_PRESENT]
47+
48+
49+
def get_switches(ec: CrOS_EC) -> dict[str, bool]:
50+
"""
51+
Get the state of the switches.
52+
@param ec: The CrOS_EC object.
53+
@return: The state of the switches.
54+
"""
55+
version = int(ec.memmap(EC_MEMMAP_SWITCHES_VERSION, 1)[0])
56+
if not version:
57+
# No switches supported
58+
return {}
59+
60+
resp = ec.memmap(EC_MEMMAP_SWITCHES, 1)[0]
61+
return {
62+
"lid_open": bool(resp & EC_SWITCH_LID_OPEN),
63+
"power_button_pressed": bool(resp & EC_SWITCH_POWER_BUTTON_PRESSED),
64+
"write_protect_disabled": bool(resp & EC_SWITCH_WRITE_PROTECT_DISABLED),
65+
"dedicated_recovery": bool(resp & EC_SWITCH_DEDICATED_RECOVERY)
66+
}
67+
68+
69+
def get_battery_values(ec: CrOS_EC) -> dict[str, int | bool | str]:
70+
"""
71+
Get the values of the battery.
72+
@param ec: The CrOS_EC object.
73+
@return: The state of the battery.
74+
"""
75+
version = int(ec.memmap(EC_MEMMAP_BATTERY_VERSION, 1)[0])
76+
if not version:
77+
# No battery supported
78+
return {}
79+
80+
resp = ec.memmap(EC_MEMMAP_BATT_VOLT, EC_MEMMAP_ALS - EC_MEMMAP_BATT_VOLT)
81+
data = struct.unpack("<IIIBBBxIIII8s8s8s8s", resp)
82+
return {
83+
"volt": data[0],
84+
"rate": data[1],
85+
"capacity": data[2],
86+
"ac_present": bool(data[3] & EC_BATT_FLAG_AC_PRESENT),
87+
"batt_present": bool(data[3] & EC_BATT_FLAG_BATT_PRESENT),
88+
"discharging": bool(data[3] & EC_BATT_FLAG_DISCHARGING),
89+
"charging": bool(data[3] & EC_BATT_FLAG_CHARGING),
90+
"level_critical": bool(data[3] & EC_BATT_FLAG_LEVEL_CRITICAL),
91+
"invalid_data": bool(data[3] & EC_BATT_FLAG_INVALID_DATA),
92+
"count": data[4],
93+
"index": data[5],
94+
"design_capacity": data[6],
95+
"design_voltage": data[7],
96+
"last_full_charge_capacity": data[8],
97+
"cycle_count": data[9],
98+
"manufacturer": data[10].decode("utf-8").rstrip("\x00"),
99+
"model": data[11].decode("utf-8").rstrip("\x00"),
100+
"serial": data[12].decode("utf-8").rstrip("\x00"),
101+
"type": data[13].decode("utf-8").rstrip("\x00")
102+
}
103+
104+
105+
def get_als(ec: CrOS_EC) -> list[int | None]:
106+
"""
107+
Get the current value from all Ambient Light Sensors.
108+
@param ec: The CrOS_EC object.
109+
@return: A list of ALS values. May be 0 if the sensor is not present.
110+
"""
111+
resp = ec.memmap(EC_MEMMAP_ALS, EC_ALS_ENTRIES * 2) # 2 bytes per sensor
112+
als = struct.unpack(f"<{EC_ALS_ENTRIES}H", resp)
113+
return [val for val in als]
114+
115+
116+
def get_accel(ec: CrOS_EC) -> list[int | None]:
117+
"""
118+
Get the current value from all accelerometers.
119+
@param ec: The CrOS_EC object.
120+
@return: A list of accelerometer values. May be 0 if the sensor is not present.
121+
"""
122+
EC_ACC_ENTRIES: Final = 3
123+
resp = ec.memmap(EC_MEMMAP_ACC_DATA, EC_ACC_ENTRIES * 2) # 2 bytes per sensor
124+
accel = struct.unpack(f"<{EC_ACC_ENTRIES}H", resp)
125+
return [val for val in accel]

cros_ec_python/constants/MEMMAP.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
EC_MEMMAP_TEMP_SENSOR : Final = 0x00 # Temp sensors 0x00 - 0x0f
99
EC_MEMMAP_FAN : Final = 0x10 # Fan speeds 0x10 - 0x17
1010
EC_MEMMAP_TEMP_SENSOR_B : Final = 0x18 # More temp sensors 0x18 - 0x1f
11-
EC_MEMMAP_ID : Final = 0x20 # 0x20 : Final =: Final = 'E', 0x21 : Final =: Final = 'C'
11+
EC_MEMMAP_ID : Final = 0x20 # 0x20 == 'E', 0x21 == 'C'
1212
EC_MEMMAP_ID_VERSION : Final = 0x22 # Version of data in 0x20 - 0x2f
1313
EC_MEMMAP_THERMAL_VERSION : Final = 0x23 # Version of data in 0x00 - 0x1f
1414
EC_MEMMAP_BATTERY_VERSION : Final = 0x24 # Version of data in 0x40 - 0x7f
@@ -64,7 +64,7 @@
6464

6565
# Number of temp sensors at EC_MEMMAP_TEMP_SENSOR_B.
6666
#
67-
# Valid only if EC_MEMMAP_THERMAL_VERSION returns >: Final = 2.
67+
# Valid only if EC_MEMMAP_THERMAL_VERSION returns >= 2.
6868

6969
EC_TEMP_SENSOR_B_ENTRIES : Final = 8
7070

@@ -75,7 +75,7 @@
7575
EC_TEMP_SENSOR_NOT_CALIBRATED : Final = 0xfc
7676

7777
# The offset of temperature value stored in mapped memory. This allows
78-
# reporting a temperature range of 200K to 454K : Final = -73C to 181C.
78+
# reporting a temperature range of 200K to 454K = -73C to 181C.
7979

8080
EC_TEMP_SENSOR_OFFSET : Final = 200
8181

tests/memmap.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import unittest
2+
from cros_ec_python import CrOS_EC, memmap as ec_memmap
3+
4+
ec = CrOS_EC()
5+
6+
7+
class TestGetTemps(unittest.TestCase):
8+
def test(self):
9+
resp = ec_memmap.get_temps(ec)
10+
print(type(self).__name__, "-", "Resp:", resp)
11+
self.assertIsInstance(resp, list)
12+
13+
14+
class TestGetFans(unittest.TestCase):
15+
def test(self):
16+
resp = ec_memmap.get_fans(ec)
17+
print(type(self).__name__, "-", "Resp:", resp)
18+
self.assertIsInstance(resp, list)
19+
20+
21+
class TestGetSwitches(unittest.TestCase):
22+
def test(self):
23+
resp = ec_memmap.get_switches(ec)
24+
print(type(self).__name__, "-", "Resp:", resp)
25+
self.assertIsInstance(resp, dict)
26+
27+
28+
class TestGetBattery(unittest.TestCase):
29+
def test(self):
30+
resp = ec_memmap.get_battery_values(ec)
31+
print(type(self).__name__, "-", "Resp:", resp)
32+
self.assertIsInstance(resp, dict)
33+
34+
35+
class TestGetALS(unittest.TestCase):
36+
def test(self):
37+
resp = ec_memmap.get_als(ec)
38+
print(type(self).__name__, "-", "Resp:", resp)
39+
self.assertIsInstance(resp, list)
40+
41+
42+
class TestGetAccel(unittest.TestCase):
43+
def test(self):
44+
resp = ec_memmap.get_accel(ec)
45+
print(type(self).__name__, "-", "Resp:", resp)
46+
self.assertIsInstance(resp, list)
47+
48+
49+
if __name__ == '__main__':
50+
unittest.main()

tests/tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from memmap import *
23
from general import *
34
from features import *
45
# These have some user input, uncomment if you're human.

0 commit comments

Comments
 (0)