Skip to content

Commit 73fb73a

Browse files
authored
Merge pull request #205 from dplanella/encoder-sysfs
Encoder: add support for reading/writing sysfs attributes
2 parents 0e593a1 + b23a861 commit 73fb73a

File tree

3 files changed

+337
-140
lines changed

3 files changed

+337
-140
lines changed

Adafruit_BBIO/Encoder.py

+166-113
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import logging
66
import itertools
7+
import sysfs
78
import platform
89

910
(major, minor, patch) = platform.release().split("-")[0].split(".")
@@ -13,11 +14,16 @@
1314
'Please upgrade your kernel to use this module.\n'
1415
'Your Linux kernel version is {}.'.format(platform.release()))
1516

17+
18+
# eQEP module channel identifiers
19+
# eQEP 2 and 2b are the same channel, exposed on two different sets of pins,
20+
# which are mutually exclusive
1621
eQEP0 = 0
1722
eQEP1 = 1
1823
eQEP2 = 2
1924
eQEP2b = 3
2025

26+
# Definitions to initialize the eQEP modules
2127
_OCP_PATH = "/sys/devices/platform/ocp"
2228
_eQEP_DEFS = [
2329
{'channel': 'eQEP0', 'pin_A': 'P9_92', 'pin_B': 'P9_27',
@@ -58,21 +64,43 @@ def __init__(self, channel, pin_A, pin_B, sys_path):
5864
rotary encoder
5965
sys_path (str): sys filesystem path to access the attributes
6066
of this eQEP module
67+
node (str): sys filesystem device node that contains the
68+
readable or writable attributes to control the QEP channel
6169
6270
'''
6371
self.channel = channel
6472
self.pin_A = pin_A
6573
self.pin_B = pin_B
6674
self.sys_path = sys_path
75+
self.node = sysfs.Node(sys_path)
6776

6877

6978
class RotaryEncoder(object):
79+
'''
80+
Rotary encoder class abstraction to control a given QEP channel.
81+
82+
Constructor:
83+
eqep_num: QEP object that determines which channel to control
84+
85+
Properties:
86+
position: current position of the encoder
87+
frequency: frequency at which the encoder reports new positions
88+
enabled: (read only) true if the module is enabled, false otherwise
89+
mode: current mode of the encoder (absolute: 0, relative: 1)
90+
91+
Methods:
92+
enable: enable the QEP channel
93+
disable: disable the QEP channel
94+
setAbsolute: shortcut for setting the mode to absolute
95+
setRelative: shortcut for setting the mode to relative
96+
zero: shortcut for setting the position to 0
97+
'''
7098

7199
def _run_cmd(self, cmd):
72100
'''Runs a command. If not successful (i.e. error code different than
73101
zero), print the stderr output as a warning.
74-
'''
75102
103+
'''
76104
try:
77105
output = check_output(cmd, stderr=STDOUT)
78106
self._logger.info(
@@ -83,164 +111,189 @@ def _run_cmd(self, cmd):
83111
"_run_cmd(): cmd='{}' return code={} output={}".format(
84112
" ".join(cmd), e.returncode, e.output))
85113

86-
def config_pin(self, pin):
87-
'''
88-
config_pin()
89-
Config pin for QEP
90-
'''
114+
def _config_pin(self, pin):
115+
'''Configures a pin in QEP mode using the `config-pin` binary'''
91116

92117
self._run_cmd(["config-pin", pin, "qep"])
93118

94-
def cat_file(self, path):
95-
'''
96-
cat_file()
97-
Print contents of file
98-
'''
119+
def __init__(self, eqep_num):
120+
'''Creates an instance of the class RotaryEncoder.
99121
100-
self._run_cmd(["cat", path])
122+
Arguments:
123+
eqep_num: determines which eQEP pins are set up.
124+
Allowed values: EQEP0, EQEP1, EQEP2 or EQEP2b,
125+
based on which pins the physical rotary encoder
126+
is connected to.
101127
102-
def __init__(self, eqep_num):
103-
'''
104-
RotaryEncoder(eqep_num)
105-
Creates an instance of the class RotaryEncoder.
106-
eqep_num determines which eQEP pins are set up.
107-
eqep_num can be: EQEP0, EQEP1, EQEP2 or EQEP2b based on which pins \
108-
the rotary encoder is connected to.
109128
'''
129+
# nanoseconds factor to convert period to frequency and back
130+
self._NS_FACTOR = 1000000000
110131

132+
# Set up logging at the module level
111133
self._logger = logging.getLogger(__name__)
112134
self._logger.addHandler(logging.NullHandler())
113135

114-
# Configure eqep module
136+
# Initialize the eQEP channel structures
115137
self._eqep = eQEP.fromdict(_eQEP_DEFS[eqep_num])
116138
self._logger.info(
117139
"Configuring: {}, pin A: {}, pin B: {}, sys path: {}".format(
118140
self._eqep.channel, self._eqep.pin_A, self._eqep.pin_B,
119141
self._eqep.sys_path))
120142

121-
self.config_pin(self._eqep.pin_A)
122-
self.config_pin(self._eqep.pin_B)
143+
# Configure the pins for the given channel
144+
self._config_pin(self._eqep.pin_A)
145+
self._config_pin(self._eqep.pin_B)
123146

124-
self.base_dir = self._eqep.sys_path
125147
self._logger.debug(
126-
"RotaryEncoder(): self.base_dir: {0}".format(self.base_dir))
148+
"RotaryEncoder(): sys node: {0}".format(self._eqep.sys_path))
127149

150+
# Enable the channel upon initialization
128151
self.enable()
129152

130-
def enable(self):
153+
@property
154+
def enabled(self):
155+
'''Returns the enabled status of the module:
156+
157+
true: module is enabled
158+
false: module is disabled
131159
'''
132-
enable()
133-
Turns the eQEP hardware ON
160+
isEnabled = bool(int(self._eqep.node.enabled))
161+
162+
return isEnabled
163+
164+
def _setEnable(self, enabled):
165+
'''Turns the eQEP hardware ON or OFF
166+
167+
value (int): 1 represents enabled, 0 is disabled
168+
134169
'''
135-
enable_file = "%s/enabled" % self.base_dir
136-
self._logger.debug("enable(): enable_file: {0}".format(enable_file))
137-
self._logger.warning(
138-
"enable(): TODO: not implemented, write 1 to {}".format(enable_file))
139-
# return sysfs.kernelFileIO(enable_file, '1')
170+
enabled = int(enabled)
171+
if enabled < 0 or enabled > 1:
172+
raise ValueError(
173+
'The "enabled" attribute can only be set to 0 or 1. '
174+
'You attempted to set it to {}.'.format(enabled))
175+
176+
self._eqep.node.enabled = str(enabled)
177+
self._logger.info("Channel: {}, enabled: {}".format(
178+
self._eqep.channel, self._eqep.node.enabled))
179+
180+
def enable(self):
181+
'''Turns the eQEP hardware ON'''
182+
183+
self._setEnable(1)
140184

141185
def disable(self):
186+
'''Turns the eQEP hardware OFF'''
187+
188+
self._setEnable(0)
189+
190+
@property
191+
def mode(self):
192+
'''Returns the mode the eQEP hardware is in (absolute or relative).
193+
142194
'''
143-
disable()
144-
Turns the eQEP hardware OFF
195+
mode = int(self._eqep.node.mode)
196+
197+
if mode == 0:
198+
mode_name = "absolute"
199+
elif mode == 1:
200+
mode_name = "relative"
201+
else:
202+
mode_name = "invalid"
203+
204+
self._logger.debug("getMode(): Channel {}, mode: {} ({})".format(
205+
self._eqep.channel, mode, mode_name))
206+
207+
return mode
208+
209+
@mode.setter
210+
def mode(self, mode):
211+
'''Sets the eQEP mode as absolute (0) or relative (1).
212+
See the setAbsolute() and setRelative() methods for
213+
more information.
214+
145215
'''
146-
enable_file = "%s/enabled" % self.base_dir
147-
self._logger.debug("disable(): enable_file: {0}".format(enable_file))
148-
self._logger.warning(
149-
"disable(): TODO: not implemented, write 0 to {}".format(
150-
enable_file))
151-
# return sysfs.kernelFileIO(enable_file, '0')
216+
mode = int(mode)
217+
if mode < 0 or mode > 1:
218+
raise ValueError(
219+
'The "mode" attribute can only be set to 0 or 1. '
220+
'You attempted to set it to {}.'.format(mode))
221+
222+
self._eqep.node.mode = str(mode)
223+
self._logger.debug("Mode set to: {}".format(
224+
self._eqep.node.mode))
152225

153226
def setAbsolute(self):
154-
'''
155-
setAbsolute()
156-
Set mode as Absolute
227+
'''Sets the eQEP mode as Absolute:
157228
The position starts at zero and is incremented or
158229
decremented by the encoder's movement
230+
159231
'''
160-
mode_file = "%s/mode" % self.base_dir
161-
self._logger.debug("setAbsolute(): mode_file: {0}".format(mode_file))
162-
self._logger.warning(
163-
"setAbsolute(): TODO: not implemented, write 0 to {}".format(
164-
mode_file))
165-
# return sysfs.kernelFileIO(mode_file, '0')
232+
self.mode = 0
166233

167234
def setRelative(self):
168-
'''
169-
setRelative()
170-
Set mode as Relative
235+
'''Sets the eQEP mode as Relative:
171236
The position is reset when the unit timer overflows.
172-
'''
173-
mode_file = "%s/mode" % self.base_dir
174-
self._logger.debug("setRelative(): mode_file: {0}".format(mode_file))
175-
self._logger.warning(
176-
"setRelative(): TODO: not implemented, write 1 to {}".format(
177-
mode_file))
178-
# return sysfs.kernelFileIO(mode_file, '1')
179-
180-
def getMode(self):
181-
'''
182-
getMode()
183-
Returns the mode the eQEP hardware is in.
184-
'''
185-
mode_file = "%s/mode" % self.base_dir
186-
self._logger.debug("getMode(): mode_file: {0}".format(mode_file))
187-
self._logger.warning("getMode(): TODO: read mode_file")
188-
# return sysfs.kernelFileIO(mode_file)
189237
190-
def getPosition(self):
191238
'''
192-
getPosition()
193-
Get the current position of the encoder.
239+
self.mode = 1
240+
241+
@property
242+
def position(self):
243+
'''Returns the current position of the encoder.
194244
In absolute mode, this attribute represents the current position
195245
of the encoder.
196246
In relative mode, this attribute represents the position of the
197247
encoder at the last unit timer overflow.
248+
198249
'''
199-
self._logger.debug("Channel: {}".format(self._eqep.channel))
200-
position_file = "%s/position" % self.base_dir
201-
self._logger.debug(
202-
"getPosition(): position_file: {0}".format(position_file))
203-
position_handle = open(position_file, 'r')
204-
self._logger.debug(
205-
"getPosition(): position_handle: {0}".format(position_handle))
206-
position = position_handle.read()
207-
self._logger.debug("getPosition(): position: {0}".format(position))
208-
# return sysfs.kernelFileIO(position_file)
250+
position = self._eqep.node.position
209251

210-
return position
252+
self._logger.debug("Get position: Channel {}, position: {}".format(
253+
self._eqep.channel, position))
254+
255+
return int(position)
256+
257+
@position.setter
258+
def position(self, position):
259+
'''Sets the current position to a new value'''
260+
261+
position = int(position)
262+
self._eqep.node.position = str(position)
263+
264+
self._logger.debug("Set position: Channel {}, position: {}".format(
265+
self._eqep.channel, position))
266+
267+
268+
@property
269+
def frequency(self):
270+
'''Sets the frequency in Hz at which the driver reports
271+
new positions.
211272
212-
def setFrequency(self, freq):
213-
'''
214-
setFrequency(freq)
215-
Set the frequency in Hz at which the driver reports new positions.
216273
'''
217-
period_file = "%s/period" % self.base_dir
218-
self._logger.debug(
219-
"setFrequency(): period_file: {0}".format(period_file))
220-
self._logger.debug("setFrequency(): freq: {0}".format(freq))
274+
frequency = self._eqep.node.period / self._NS_FACTOR
275+
221276
self._logger.debug(
222-
"setFrequency(): 1000000000/freq: {0}".format(1000000000/freq))
223-
self._logger.debug("setFrequency(): str(1000000000/freq)): {0}".format(
224-
str(1000000000/freq)))
225-
self._logger.warning(
226-
"setFrequency(): TODO: not implemented, set {} to {}".format(
227-
period_file, str(1000000000/freq)))
228-
# return sysfs.kernelFileIO(period_file, str(1000000000/freq))
229-
230-
def setPosition(self, val):
231-
'''
232-
setPosition(value)
233-
Give a new value to the current position
277+
"Set frequency(): Channel {}, frequency: {} Hz, "
278+
"period: {} ns".format(
279+
self._eqep.channel, frequency, period))
280+
281+
return frequency
282+
283+
@frequency.setter
284+
def frequency(self, frequency):
285+
'''Sets the frequency in Hz at which the driver reports
286+
new positions.
287+
234288
'''
235-
position_file = "%s/position" % self.base_dir
236-
self._logger.warning(
237-
"setPosition(): TODO: not implemented, write position to {}".format(
238-
position_file))
239-
# return sysfs.kernelFileIO(position_file, str(val))
289+
period = self._NS_FACTOR / frequency # Period in nanoseconds
290+
self._eqep.node.period = str(period)
291+
self._logger.debug(
292+
"Set frequency(): Channel {}, frequency: {} Hz, "
293+
"period: {} ns".format(
294+
self._eqep.channel, frequency, period))
240295

241296
def zero(self):
242-
'''
243-
zero()s
244-
Set the current position to 0
245-
'''
246-
return self.setPosition(0)
297+
'''Resets the current position to 0'''
298+
299+
self.position = 0

0 commit comments

Comments
 (0)