skip to Main Content

OS = Ubuntu Server

I have no idea for the best way to do this and would appreciate any pointers.

I’m using an SBC (orange pi 5b) and PWM support is broken using wiringpi.
I can execute the following commands when logged in as root to create a 50Hz square wave on pin 2

echo 0 > /sys/class/pwm/pwmchip5/export
echo 20000000 > /sys/class/pwm/pwmchip5/pwm0/period
echo 1000000 > /sys/class/pwm/pwmchip5/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip5/pwm0/enable

I cannot execute the same commands in a user account using sudo or change permissions for the pwmchip5 directory

What I would like to achieve is to create a daemon/service/api/rpc (preferably in Python but I have a little skill in c) that runs as root (using crontab on boot or as a service) and that any non-root user can send it commands like

pwmservice init pwmchip5
pwmservice period pwmchip5 20000000 
pwmservice duty_cycle pwmchip5 1000000 
pwmservice enable pwmchip5

without having to be root or using sudo. Note that these last 4 commands would get the service to execute the bash commands above as root

is this possible and could someone point me in the right direction. Also, would something like this work or is there a better way.

https://betterprogramming.pub/a-simple-way-to-make-rpcs-with-python-52ad8e9286c1

cheers

2

Answers


  1. Chosen as BEST ANSWER

    This worked

    install the python rpc library

    pip install rpyc
    

    log in as root and create the following server.py file

    import rpyc
    from rpyc.utils.server import ThreadedServer
    from subprocess import  PIPE, run
    
    @rpyc.service
    class TestService(rpyc.Service):
        PINS = {2:"pwmchip5"}
    
        def bash(self, cmdstr:str):
            '''Run shell command
    
            Args:
                cmdstr (str): shell command string
            '''
            try:
                cmd = run(
                    cmdstr, stdout=PIPE, encoding="ascii", shell=True
                )
            except Exception as e:
                print(cmdstr)
                print(e)
    
        @rpyc.exposed
        def pwm(self, pin: int, period: int, duty_cycle: int) -> str:
            '''Set PWM 
    
            Args:
                pin (int): wiringpi pin number
                period (int): set period
                duty_cycle (int): set duty cycle
    
            Returns:
                str: _description_
            '''
            assert isinstance(pin, int) "Pin must be an int"
            assert isinstance(period, int) "Period must be an int"
            assert isinstance(duty_cycle, int) "Duty Cycle must be an int"
    
            chip = self.PINS[pin]
            c1 = f"echo 0 > /sys/class/pwm/{chip}/export"
            c2 = f"echo {period} > /sys/class/pwm/{chip}/pwm0/period"
            c3 = f"echo {duty_cycle} > /sys/class/pwm/{chip}/pwm0/duty_cycle"
            c4 = f"echo 1 > /sys/class/pwm/{chip}/pwm0/enable"
    
            for cmdstr in [c1,c2,c3,c4]:
                self.bash(cmdstr)
    
    
            return f"Success: {pin=} {period=} {duty_cycle=} {chip=}"
        
        @rpyc.exposed
        def disable(self) -> str:
            self.bash("echo 0 > /sys/class/pwm/{chip}/pwm0/enable")
    
            return 'Success'
        
    
    print('starting PWM server')
    server = ThreadedServer(TestService, port=18811)
    server.start()
    

     

    This can be automatically started at boot and as root using crontab

    Then use the following snippet as the regular non root user

    import rpyc
    
    connection = rpyc.connect("localhost", 18811)
    print(connection.root.pwm(2, 20000000, 5000000))
    

     

    This creates a 50Hz where the pwm arguments are (pin, period, duty_cycle)


  2. I think this is more of a Linux permissions question than a programming question, but nevertheless:

    You could use ls -l to determine the group-owner of the files in /sys/class/pwm/..., for example, this might be group gpio. Then add all users who need to execute those commands to that group with adduser username gpio. Maybe you need some udev rules to set up the group ownership first.


    Or you could write a small C program for this, compile it, and then put the compiled binary in /usr/local/bin/pwmcontrol with SETGID to the gpio group, or whichever group owns the control files 1. Then it will always run with the rights of that group. Note that SETGID (and SETUID) only works with a compiled program, not with a script.

    gcc -Wall -Werror -pedantic pwmcontrol.c -o pwmcontrol
    sudo cp pwmcontrol /usr/local/bin/
    sudo chgrp gpio /usr/local/bin/pwmcontrol
    sudo chmod g+s /usr/local/bin/pwmcontrol
    

    Here’s some C code to get you started, including error handling.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int pwm_enable() {
        /// TODO implement
        return 0; // success
    }
    
    int pwm_disable() {
        /// TODO implement
        return 0; // success
    }
    
    
    int write_file(const char *path, const char *data) {
        FILE *f = fopen(path, "w");
        if (!f) return 1;
        size_t length = strlen(data);
        size_t written = fwrite(data, 1, length, f);
        fclose(f);
        return (length != written) ? 1 : 0; // 0 == success, 1 == fail
    }
    
    int pwm_export() {
        /// TODO implement
        return 0; // success
    }
    
    void err_exit(const char *message, int exitcode) {
        perror(message);
        pwm_disable(); // for safety reasons, turn off PWM if something went wrong
        exit(exitcode);
    }
    
    int pwm_set(const char *period, const char *duty_cycle) {
        if (0 != write_file("/sys/class/pwm/pwmchip5/pwm0/period", period)) {
            return 1;
        }
        if (0 != write_file("/sys/class/pwm/pwmchip5/pwm0/duty_cycle", duty_cycle)) {
            return 1;
        }
        return 0; // success
    }
    
    int main(int argc, char **argv) {
        // Determine what to do according to the first argument
        if (argc == 4 && strcmp(argv[1], "set") == 0) {
            if (0 != pwm_export()) {
                err_exit("Error exporting PWM chip", 1);
            }
            if (0 != pwm_set(argv[2], argv[2])) {
                err_exit("Error setting PWM period and duty cycle", 1);
            }
            if (0 != pwm_enable()) {
                err_exit("Error enabling PWM", 1);
            }
            exit (0); // 0 == success
        }
        else if (argc == 2 && strcmp(argv[1], "start") == 0) {
            // TODO
            exit (0); // 0 == success
        }
        else if (argc == 2 && strcmp(argv[1], "stop") == 0) {
            // TODO
            exit (0); // 0 == success
        }
    
        printf("Usage:n");
        printf("    %s set PERIOD DUTYCYCLEn", argv[0]);
        printf("    %s startn", argv[0]);
        printf("    %s stopn", argv[0]);
        exit(1);
    }
    

    1 Of course, you could also use SETUID root (chown root + chmod u+s), but running something as root is dangerous. Especially if it is a C program that you wrote yourself without any idea of how C security exploits work.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search