44import os
55import logging
66import itertools
7+ import sysfs
78import platform
89
910(major , minor , patch ) = platform .release ().split ("-" )[0 ].split ("." )
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
1621eQEP0 = 0
1722eQEP1 = 1
1823eQEP2 = 2
1924eQEP2b = 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
6978class 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