gobot.io/x/gobot/v2@v2.1.0/system/pwmpin_sysfs.go (about)

     1  package system
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"strconv"
     9  	"syscall"
    10  	"time"
    11  )
    12  
    13  const pwmDebug = false
    14  
    15  const (
    16  	pwmPinErrorPattern    = "%s() failed for id %s with %v"
    17  	pwmPinSetErrorPattern = "%s(%v) failed for id %s with %v"
    18  )
    19  
    20  // pwmPinSysFs represents a PWM pin
    21  type pwmPinSysFs struct {
    22  	path                       string
    23  	pin                        string
    24  	polarityNormalIdentifier   string
    25  	polarityInvertedIdentifier string
    26  
    27  	write func(fs filesystem, path string, data []byte) (i int, err error)
    28  	read  func(fs filesystem, path string) ([]byte, error)
    29  	fs    filesystem
    30  }
    31  
    32  // newPWMPinSysfs returns a new pwmPin, working with sysfs file access.
    33  func newPWMPinSysfs(fs filesystem, path string, pin int, polNormIdent string, polInvIdent string) *pwmPinSysFs {
    34  	p := &pwmPinSysFs{
    35  		path:                       path,
    36  		pin:                        strconv.Itoa(pin),
    37  		polarityNormalIdentifier:   polNormIdent,
    38  		polarityInvertedIdentifier: polInvIdent,
    39  
    40  		read:  readPwmFile,
    41  		write: writePwmFile,
    42  		fs:    fs,
    43  	}
    44  	return p
    45  }
    46  
    47  // Export exports the pin for use by the operating system by writing the pin to the "Export" path
    48  func (p *pwmPinSysFs) Export() error {
    49  	_, err := p.write(p.fs, p.pwmExportPath(), []byte(p.pin))
    50  	if err != nil {
    51  		// If EBUSY then the pin has already been exported
    52  		e, ok := err.(*os.PathError)
    53  		if !ok || e.Err != syscall.EBUSY {
    54  			return fmt.Errorf(pwmPinErrorPattern, "Export", p.pin, err)
    55  		}
    56  	}
    57  
    58  	// Pause to avoid race condition in case there is any udev rule
    59  	// that changes file permissions on newly exported PWMPin. This
    60  	// is a common circumstance when running as a non-root user.
    61  	time.Sleep(100 * time.Millisecond)
    62  
    63  	return nil
    64  }
    65  
    66  // Unexport releases the pin from the operating system by writing the pin to the "Unexport" path
    67  func (p *pwmPinSysFs) Unexport() error {
    68  	if _, err := p.write(p.fs, p.pwmUnexportPath(), []byte(p.pin)); err != nil {
    69  		return fmt.Errorf(pwmPinErrorPattern, "Unexport", p.pin, err)
    70  	}
    71  	return nil
    72  }
    73  
    74  // Enabled reads and returns the enabled state of the pin
    75  func (p *pwmPinSysFs) Enabled() (bool, error) {
    76  	buf, err := p.read(p.fs, p.pwmEnablePath())
    77  	if err != nil {
    78  		return false, fmt.Errorf(pwmPinErrorPattern, "Enabled", p.pin, err)
    79  	}
    80  	if len(buf) == 0 {
    81  		return false, nil
    82  	}
    83  
    84  	val, e := strconv.Atoi(string(bytes.TrimRight(buf, "\n")))
    85  	return val > 0, e
    86  }
    87  
    88  // SetEnabled writes enable(1) or disable(0) status. For most platforms this is only possible if period was
    89  // already set to > 0. Regardless of setting to enable or disable, a "write error: Invalid argument" will occur.
    90  func (p *pwmPinSysFs) SetEnabled(enable bool) error {
    91  	enableVal := 0
    92  	if enable {
    93  		enableVal = 1
    94  	}
    95  	if _, err := p.write(p.fs, p.pwmEnablePath(), []byte(fmt.Sprintf("%v", enableVal))); err != nil {
    96  		if pwmDebug {
    97  			p.printState()
    98  		}
    99  		return fmt.Errorf(pwmPinSetErrorPattern, "SetEnabled", enable, p.pin, err)
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // Polarity reads and returns false if the polarity is inverted, otherwise true
   106  func (p *pwmPinSysFs) Polarity() (bool, error) {
   107  	buf, err := p.read(p.fs, p.pwmPolarityPath())
   108  	if err != nil {
   109  		return true, fmt.Errorf(pwmPinErrorPattern, "Polarity", p.pin, err)
   110  	}
   111  	if len(buf) == 0 {
   112  		return true, nil
   113  	}
   114  
   115  	ps := string(bytes.TrimRight(buf, "\n"))
   116  	if ps == p.polarityNormalIdentifier {
   117  		return true, nil
   118  	}
   119  	if ps == p.polarityInvertedIdentifier {
   120  		return false, nil
   121  	}
   122  
   123  	return true, fmt.Errorf("unknown value (%s) in Polarity", ps)
   124  }
   125  
   126  // SetPolarity writes the polarity as normal if called with true and as inverted if called with false
   127  func (p *pwmPinSysFs) SetPolarity(normal bool) error {
   128  	enabled, _ := p.Enabled()
   129  	if enabled {
   130  		return fmt.Errorf("Cannot set PWM polarity when enabled")
   131  	}
   132  	value := p.polarityNormalIdentifier
   133  	if !normal {
   134  		value = p.polarityInvertedIdentifier
   135  	}
   136  	if _, err := p.write(p.fs, p.pwmPolarityPath(), []byte(value)); err != nil {
   137  		if pwmDebug {
   138  			p.printState()
   139  		}
   140  		return fmt.Errorf(pwmPinSetErrorPattern, "SetPolarity", value, p.pin, err)
   141  	}
   142  	return nil
   143  }
   144  
   145  // Period returns the current period
   146  func (p *pwmPinSysFs) Period() (uint32, error) {
   147  	buf, err := p.read(p.fs, p.pwmPeriodPath())
   148  	if err != nil {
   149  		return 0, fmt.Errorf(pwmPinErrorPattern, "Period", p.pin, err)
   150  	}
   151  	if len(buf) == 0 {
   152  		return 0, nil
   153  	}
   154  
   155  	v := bytes.TrimRight(buf, "\n")
   156  	val, e := strconv.Atoi(string(v))
   157  	return uint32(val), e
   158  }
   159  
   160  // SetPeriod writes the current period in nanoseconds
   161  func (p *pwmPinSysFs) SetPeriod(period uint32) error {
   162  	if _, err := p.write(p.fs, p.pwmPeriodPath(), []byte(fmt.Sprintf("%v", period))); err != nil {
   163  
   164  		if pwmDebug {
   165  			p.printState()
   166  		}
   167  		return fmt.Errorf(pwmPinSetErrorPattern, "SetPeriod", period, p.pin, err)
   168  	}
   169  	return nil
   170  }
   171  
   172  // DutyCycle reads and returns the duty cycle in nanoseconds
   173  func (p *pwmPinSysFs) DutyCycle() (uint32, error) {
   174  	buf, err := p.read(p.fs, p.pwmDutyCyclePath())
   175  	if err != nil {
   176  		return 0, fmt.Errorf(pwmPinErrorPattern, "DutyCycle", p.pin, err)
   177  	}
   178  
   179  	val, err := strconv.Atoi(string(bytes.TrimRight(buf, "\n")))
   180  	return uint32(val), err
   181  }
   182  
   183  // SetDutyCycle writes the duty cycle in nanoseconds
   184  func (p *pwmPinSysFs) SetDutyCycle(duty uint32) error {
   185  	if _, err := p.write(p.fs, p.pwmDutyCyclePath(), []byte(fmt.Sprintf("%v", duty))); err != nil {
   186  		if pwmDebug {
   187  			p.printState()
   188  		}
   189  		return fmt.Errorf(pwmPinSetErrorPattern, "SetDutyCycle", duty, p.pin, err)
   190  	}
   191  	return nil
   192  }
   193  
   194  // pwmExportPath returns export path
   195  func (p *pwmPinSysFs) pwmExportPath() string {
   196  	return path.Join(p.path, "export")
   197  }
   198  
   199  // pwmUnexportPath returns unexport path
   200  func (p *pwmPinSysFs) pwmUnexportPath() string {
   201  	return path.Join(p.path, "unexport")
   202  }
   203  
   204  // pwmDutyCyclePath returns duty_cycle path for specified pin
   205  func (p *pwmPinSysFs) pwmDutyCyclePath() string {
   206  	return path.Join(p.path, "pwm"+p.pin, "duty_cycle")
   207  }
   208  
   209  // pwmPeriodPath returns period path for specified pin
   210  func (p *pwmPinSysFs) pwmPeriodPath() string {
   211  	return path.Join(p.path, "pwm"+p.pin, "period")
   212  }
   213  
   214  // pwmEnablePath returns enable path for specified pin
   215  func (p *pwmPinSysFs) pwmEnablePath() string {
   216  	return path.Join(p.path, "pwm"+p.pin, "enable")
   217  }
   218  
   219  // pwmPolarityPath returns polarity path for specified pin
   220  func (p *pwmPinSysFs) pwmPolarityPath() string {
   221  	return path.Join(p.path, "pwm"+p.pin, "polarity")
   222  }
   223  
   224  func writePwmFile(fs filesystem, path string, data []byte) (int, error) {
   225  	file, err := fs.openFile(path, os.O_WRONLY, 0644)
   226  	defer file.Close() //nolint:staticcheck // for historical reasons
   227  	if err != nil {
   228  		return 0, err
   229  	}
   230  
   231  	return file.Write(data)
   232  }
   233  
   234  func readPwmFile(fs filesystem, path string) ([]byte, error) {
   235  	file, err := fs.openFile(path, os.O_RDONLY, 0644)
   236  	defer file.Close() //nolint:staticcheck // for historical reasons
   237  	if err != nil {
   238  		return make([]byte, 0), err
   239  	}
   240  
   241  	buf := make([]byte, 200)
   242  	var i int
   243  	i, err = file.Read(buf)
   244  	if i == 0 {
   245  		return []byte{}, err
   246  	}
   247  	return buf[:i], err
   248  }
   249  
   250  func (p *pwmPinSysFs) printState() {
   251  	enabled, _ := p.Enabled()
   252  	polarity, _ := p.Polarity()
   253  	period, _ := p.Period()
   254  	duty, _ := p.DutyCycle()
   255  
   256  	fmt.Println("Print state of all PWM variables...")
   257  	fmt.Printf("Enable: %v, ", enabled)
   258  	fmt.Printf("Polarity: %v, ", polarity)
   259  	fmt.Printf("Period: %v, ", period)
   260  	fmt.Printf("DutyCycle: %v, ", duty)
   261  	var powerPercent float64
   262  	if enabled {
   263  		if polarity {
   264  			powerPercent = float64(duty) / float64(period) * 100
   265  		} else {
   266  			powerPercent = float64(period) / float64(duty) * 100
   267  		}
   268  	}
   269  	fmt.Printf("Power: %.1f\n", powerPercent)
   270  }