forked from adafruit/Adafruit_CircuitPython_EMC2101
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemc2101_fanspeed.py
More file actions
212 lines (156 loc) · 6.59 KB
/
emc2101_fanspeed.py
File metadata and controls
212 lines (156 loc) · 6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# SPDX-FileCopyrightText: Copyright (c) 2020 Bryan Siepert for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2022 Ruth Ivimey-Cook
#
# SPDX-License-Identifier: MIT
"""
`adafruit_emc2101.emc2101_fanspeed`
================================================================================
Brushless fan controller: extended functionality
* Author(s): Bryan Siepert, Rylie Pavlik
Implementation Notes
--------------------
**Hardware:**
* `Adafruit EMC2101 Breakout <https://adafruit.com/product/4808>`_ (Product ID: 4808)
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://circuitpython.org/downloads
* Adafruit's Bus Device library:
https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
* Adafruit's Register library:
https://github.com/adafruit/Adafruit_CircuitPython_Register
"""
from adafruit_register.i2c_struct_array import StructArray
from adafruit_emc2101 import emc2101_regs
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_EMC2101.git"
class FanSpeedLUT:
"""A class used to provide a dict-like interface to the EMC2101's
Temperature to Fan speed Look Up Table (LUT).
Keys are integer temperatures, values are fan duty cycles between 0 and
100. A max of 8 values may be stored.
To remove a single stored point in the LUT, assign it as `None`.
Usage:
At 50 C the speed should be 62% of max duty cycle. (Updates the chip
immediately).
.. code-block:: python
emc2101.lut[50] = 62
Set two values up and write to the chip on exit from 'with' block.
.. code-block:: python
with emc2101.lut as lut:
lut[20] = 0
lut[40] = 10
Delete an unneeded temperature point: (Updates the chip immediately).
.. code-block:: python
emc2101.lut[20] = None
Read a dict of the currently set values:
.. code-block:: python
values = emc2101.lut.lookup_table
# returns:
# { 40: 10, 50: 62 }
Delete some LUT values, assign None:
.. code-block:: python
for temp in emc2101.lut.lookup_table:
emc2101.lut[temp] = None
Delete all LUT values at once:
.. code-block:: python
emc2101.lut.clear()
"""
# 8 (Temperature, Speed) pairs in increasing order
_fan_lut = StructArray(emc2101_regs.LUT_BASE, "<B", 16)
_defer_update = False
def __init__(self, fan_obj):
self._defer_update = False
self.emc_fan = fan_obj
self.lut_values = {}
self.i2c_device = fan_obj.i2c_device
def __enter__(self):
"""'with' wrapper: defer lut update until end of 'with' so
update_lut work can be done just once at the end of setting the LUT.
"""
# Use increment/decrement so nested with's are dealt with properly.
self._defer_update = True
return self
# 'with' wrapper
def __exit__(self, typ, val, tbk):
"""'with' wrapper: defer lut update until end of 'with' so
update_lut work can be done just once at the end of setting the LUT.
"""
self._defer_update = False
self._update_lut()
def __getitem__(self, index):
if not isinstance(index, int):
raise IndexError
if not index in self.lut_values:
raise IndexError
return self.lut_values[index]
def __setitem__(self, index, value):
if not isinstance(index, int):
raise IndexError
if value is None:
# Assign None to remove this entry
del self.lut_values[index]
elif value > 100.0 or value < 0:
# Range check
raise ValueError("LUT values must be a fan speed from 0-100%")
else:
self.lut_values[index] = value
if not self._defer_update:
self._update_lut()
def __repr__(self):
"""return the official string representation of the LUT"""
# pylint: disable=consider-using-f-string
return "FanSpeedLUT {:x}".format(id(self))
def __str__(self):
"""return the official string representation of the LUT"""
value_strs = []
lut_keys = tuple(sorted(self.lut_values.keys()))
for temp in lut_keys:
fan_drive = self.lut_values[temp]
# pylint: disable=consider-using-f-string
value_strs.append("%d deg C => %.1f%% duty cycle" % (temp, fan_drive))
return "\n".join(value_strs)
@property
def lookup_table(self):
"""Return a dictionary of LUT values."""
lut_keys = tuple(sorted(self.lut_values.keys()))
values = {}
for temp in lut_keys:
fan_drive = self.lut_values[temp]
values[temp] = fan_drive
return values
def __len__(self):
return len(self.lut_values)
# this function does a whole lot of work to organized the user-supplied lut dict into
# their correct spot within the lut table as pairs of set registers, sorted with the lowest
# temperature first
def _update_lut(self):
# Make sure we're not going to try to set more entries than we have slots
if len(self.lut_values) > 8:
raise ValueError("LUT can only contain a maximum of 8 items")
# Backup state
current_mode = self.emc_fan.lut_enabled
# Disable the lut to allow it to be updated
self.emc_fan.lut_enabled = False
# we want to assign the lowest temperature to the lowest LUT slot, so we sort the keys/temps
# get and sort the new lut keys so that we can assign them in order
for idx, current_temp in enumerate(sorted(self.lut_values.keys())):
# We don't want to make `_speed_to_lsb()` public, it is only needed here.
# pylint: disable=protected-access
current_speed = self.emc_fan._speed_to_lsb(self.lut_values[current_temp])
self._set_lut_entry(idx, current_temp, current_speed)
# Set the remaining LUT entries to the default (Temp/Speed = max value)
for idx in range(len(self.lut_values), 8):
self._set_lut_entry(
idx, emc2101_regs.MAX_LUT_TEMP, emc2101_regs.MAX_LUT_SPEED
)
self.emc_fan.lut_enabled = current_mode
def _set_lut_entry(self, idx, temp, speed):
"""Internal function: add a value to the local LUT as a byte array,
suitable for block transfer to the EMC I2C interface.
"""
self._fan_lut[idx * 2] = bytearray((temp,))
self._fan_lut[idx * 2 + 1] = bytearray((speed,))
def clear(self):
"""Clear all LUT entries."""
self.lut_values = {}
self._update_lut()