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 }