├── .gitignore ├── LICENSE ├── Makefile ├── ReadMe.md ├── etc ├── fan-control.json └── init.d │ └── fan-control ├── package ├── DEBIAN │ ├── changelog │ ├── compat │ ├── conffiles │ ├── control │ ├── copyright │ ├── prerm │ └── rules └── make.sh ├── src ├── Makefile ├── fan-control.c └── lib │ ├── tiny-json.c │ └── tiny-json.h └── systemd └── fan-control.service.in /.gitignore: -------------------------------------------------------------------------------- 1 | .o 2 | *.deb 3 | systemd/fan-control.service 4 | src/fan-control 5 | src/*.o 6 | src/lib/*.o -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nick Peng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2022 Nick Peng 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | PKG_CONFIG := pkg-config 24 | DESTDIR := 25 | PREFIX := /usr 26 | SBINDIR := $(PREFIX)/sbin 27 | SYSCONFDIR := /etc 28 | RUNSTATEDIR := /var/run 29 | SYSTEMDSYSTEMUNITDIR := $(shell ${PKG_CONFIG} --variable=systemdsystemunitdir systemd) 30 | FAN_CONTROL_SYSTEMD = systemd/fan-control.service 31 | VER := "1.0.0" 32 | 33 | .PHONY: all clean install FAN_CONTROL_BIN package 34 | all: package 35 | 36 | FAN_CONTROL_BIN: $(FAN_CONTROL_SYSTEMD) 37 | $(MAKE) $(MFLAGS) -C src all 38 | 39 | $(FAN_CONTROL_SYSTEMD): systemd/fan-control.service.in 40 | cp $< $@ 41 | sed -i 's|@SBINDIR@|$(SBINDIR)|' $@ 42 | sed -i 's|@SYSCONFDIR@|$(SYSCONFDIR)|' $@ 43 | sed -i 's|@RUNSTATEDIR@|$(RUNSTATEDIR)|' $@ 44 | 45 | package: FAN_CONTROL_BIN 46 | cd package && ./make.sh -o $(shell pwd) --ver ${VER} 47 | 48 | clean: 49 | $(MAKE) $(MFLAGS) -C src clean 50 | $(RM) $(FAN_CONTROL_SYSTEMD) 51 | $(RM) fan-control*.deb 52 | 53 | install: FAN_CONTROL_BIN 54 | install -v -m 0755 -D -t $(DESTDIR)$(SYSCONFDIR)/init.d etc/init.d/fan-control 55 | install -v -m 0640 -D -t $(DESTDIR)$(SYSCONFDIR) etc/fan-control.json 56 | install -v -m 0755 -D -t $(DESTDIR)$(SBINDIR) src/fan-control 57 | install -v -m 0644 -D -t $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) systemd/fan-control.service 58 | 59 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | Fan-control 2 | ============== 3 | 4 | A tool to control fan speed by temperature automatically for ROCK5B. 5 | 6 | Features 7 | -------------- 8 | 1. Control fan speed by temperature. (above 45 degrees) 9 | 2. set fan speed manually 10 | 11 | Build & install 12 | ============== 13 | ```shell 14 | make package 15 | dpkg -i fan-control*.deb 16 | ``` 17 | 18 | Usage 19 | ============== 20 | ```shell 21 | systemctl enable fan-control 22 | systemctl start fan-control 23 | ``` 24 | 25 | Configuration 26 | ============== 27 | 28 | configuration file locations is `/etc/fan-control.json`, Configuration parameter description: 29 | 30 | |Configuration|Description| 31 | |--|--| 32 | |pwmchip|pwmchip id, 1 for auto scan| 33 | |gpio|gpio id, 0 is default gpio | 34 | |pwm-period|PWM period| 35 | |temp-map|temperature configuration table| 36 | |temp|temperature, in degrees Celsius| 37 | |duty|duty ratio| 38 | |duration|duration, in second| 39 | 40 | 41 | License 42 | =============== 43 | MIT License 44 | 45 | 46 | -------------------------------------------------------------------------------- /etc/fan-control.json: -------------------------------------------------------------------------------- 1 | { 2 | "pwmchip": -1, 3 | "gpio": 0, 4 | "pwm-period": 10000, 5 | "temp-map": [ 6 | { 7 | "temp": 40, 8 | "duty": 0, 9 | "duration": 20 10 | }, 11 | { 12 | "temp": 44, 13 | "duty": 55, 14 | "duration": 25 15 | }, 16 | { 17 | "temp": 49, 18 | "duty": 60, 19 | "duration": 35 20 | }, 21 | { 22 | "temp": 54, 23 | "duty": 70, 24 | "duration": 45 25 | }, 26 | { 27 | "temp": 59, 28 | "duty": 80, 29 | "duration": 60 30 | }, 31 | { 32 | "temp": 64, 33 | "duty": 90, 34 | "duration": 120 35 | }, 36 | { 37 | "temp": 67, 38 | "duty": 100, 39 | "duration": 180 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /etc/init.d/fan-control: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # MIT License 4 | 5 | # Copyright (c) 2022 Nick Peng 6 | 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | ### BEGIN INIT INFO 26 | # Provides: fan-control 27 | # Required-Start: $network 28 | # Required-Stop: $network 29 | # Default-Start: 2 3 4 5 30 | # Default-Stop: 31 | # Short-Description: Start fan-control service 32 | ### END INIT INFO 33 | 34 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 35 | 36 | FAN_CONTROL=/usr/sbin/fan-control 37 | PIDFILE=/run/fan-control.pid 38 | 39 | test -x $FAN_CONTROL || exit 5 40 | 41 | case $1 in 42 | start) 43 | $FAN_CONTROL -d -p $PIDFILE 44 | while true; do 45 | if [ -e "$PIDFILE" ]; then 46 | break; 47 | fi 48 | sleep .5 49 | done 50 | PID="$(cat $PIDFILE 2>/dev/null)" 51 | if [ -z "$PID" ]; then 52 | echo "start fan-control service failed." 53 | exit 1 54 | fi 55 | if [ ! -e "/proc/$PID" ]; then 56 | echo "start fan-control service failed." 57 | exit 1 58 | fi 59 | echo "start fan-control service success." 60 | ;; 61 | stop) 62 | if [ ! -f "$PIDFILE" ]; then 63 | echo "fan-control service is stopped." 64 | exit 0 65 | fi 66 | PID="$(cat $PIDFILE 2>/dev/null)" 67 | if [ ! -e "/proc/$PID" ] || [ -z "$PID" ]; then 68 | echo "fan-control service is stopped" 69 | exit 0 70 | fi 71 | 72 | kill -TERM "$PID" 73 | if [ $? -ne 0 ]; then 74 | echo "Stop fan-control service failed." 75 | exit 1; 76 | fi 77 | LOOP=1 78 | while true; do 79 | if [ ! -d "/proc/$PID" ]; then 80 | break; 81 | fi 82 | 83 | if [ $LOOP -gt 12 ]; then 84 | kill -9 "$PID" 85 | break; 86 | fi 87 | LOOP=$((LOOP+1)) 88 | sleep .5 89 | done 90 | echo "Stop fan-control service success." 91 | ;; 92 | restart) 93 | "$0" stop && "$0" start 94 | ;; 95 | status) 96 | PID="$(cat "$PIDFILE" 2>/dev/null)" 97 | if [ ! -e "/proc/$PID" ] || [ -z "$PID" ]; then 98 | echo "fan-control service is not running." 99 | exit 1 100 | fi 101 | echo "fan-control service is running." 102 | status=$? 103 | ;; 104 | *) 105 | echo "Usage: $0 {start|stop|restart|status}" 106 | exit 2 107 | ;; 108 | esac 109 | 110 | exit $status 111 | 112 | -------------------------------------------------------------------------------- /package/DEBIAN/changelog: -------------------------------------------------------------------------------- 1 | fan-control (1:1.0.0) stable; urgency=low 2 | 3 | * Initial build 4 | 5 | -- initial release. Mon, 9 jul 2018 21:20:28 +0800 6 | -------------------------------------------------------------------------------- /package/DEBIAN/compat: -------------------------------------------------------------------------------- 1 | 9 -------------------------------------------------------------------------------- /package/DEBIAN/conffiles: -------------------------------------------------------------------------------- 1 | /etc/fan-control.json 2 | -------------------------------------------------------------------------------- /package/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Source: fan-control-rock5b 2 | Maintainer: Nick Peng 3 | Build-Depends: debhelper (>= 8.0.0) 4 | Version: 5 | Section: utils 6 | Package: fan-control-rock5b 7 | Priority: extra 8 | Architecture: armhf 9 | Description: fan control for rock 5B 10 | -------------------------------------------------------------------------------- /package/DEBIAN/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: fan-control-rock5b 3 | Source: http://github.com/pymumu/fan-control-rock5b 4 | 5 | Files: * 6 | Copyright: 2022 Nick peng 7 | License: MIT 8 | -------------------------------------------------------------------------------- /package/DEBIAN/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | systemctl stop fan-control 4 | systemctl disable fan-control 5 | -------------------------------------------------------------------------------- /package/DEBIAN/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ --with systemd --builddirectory=./target/ 4 | 5 | clean: 6 | make -C ../src clean 7 | 8 | build: 9 | make -C ../src 10 | 11 | override_dh_systemd_enable: 12 | dh_systemd_enable --name=fan-control 13 | 14 | override_dh_installinit: 15 | dh_installinit --name=fan-control 16 | 17 | override_dh_installdeb: 18 | dh_installdeb 19 | cp ../systemd/fan-control.service ${CURDIR}/debian/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /package/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (C) 2022 Ruilin Peng (Nick) . 4 | # 5 | # fan-control is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # fan-control is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | CURR_DIR=$(cd $(dirname $0);pwd) 18 | VER="1.0.0" 19 | FAN_CONTROL_DIR=$CURR_DIR/.. 20 | FAN_CONTROL_BIN=$FAN_CONTROL_DIR/src/fan-control 21 | 22 | showhelp() 23 | { 24 | echo "Usage: make [OPTION]" 25 | echo "Options:" 26 | echo " -o output directory." 27 | echo " --arch archtecture." 28 | echo " --ver version." 29 | echo " -h show this message." 30 | } 31 | 32 | build() 33 | { 34 | ROOT=/tmp/fan-control-deiban 35 | rm -fr $ROOT 36 | mkdir -p $ROOT 37 | cd $ROOT/ 38 | 39 | cp $CURR_DIR/DEBIAN $ROOT/ -af 40 | CONTROL=$ROOT/DEBIAN/control 41 | mkdir $ROOT/usr/sbin -p 42 | mkdir $ROOT/lib/systemd/system -p 43 | mkdir $ROOT/etc/init.d -p 44 | 45 | pkgver=$(echo ${VER}| sed 's/^1\.//g') 46 | sed -i "s/Version:.*/Version: ${pkgver}/" $ROOT/DEBIAN/control 47 | sed -i "s/Architecture:.*/Architecture: $ARCH/" $ROOT/DEBIAN/control 48 | chmod 0755 $ROOT/DEBIAN/prerm 49 | 50 | cp $FAN_CONTROL_DIR/systemd/fan-control.service $ROOT/lib/systemd/system/ 51 | cp $FAN_CONTROL_DIR/etc/init.d/fan-control $ROOT/etc/init.d/ 52 | cp $FAN_CONTROL_DIR/etc/fan-control.json $ROOT/etc/ 53 | cp $FAN_CONTROL_DIR/src/fan-control $ROOT/usr/sbin 54 | if [ $? -ne 0 ]; then 55 | echo "copy fan-control file failed." 56 | return 1 57 | fi 58 | chmod +x $ROOT/usr/sbin/fan-control 59 | chmod +x $ROOT/etc/init.d/fan-control 60 | 61 | dpkg -b $ROOT $OUTPUTDIR/fan-control-rock5b.$VER.$FILEARCH.deb 62 | 63 | rm -fr $ROOT/ 64 | } 65 | 66 | main() 67 | { 68 | OPTS=`getopt -o o:h --long arch:,ver:,filearch: \ 69 | -n "" -- "$@"` 70 | 71 | if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi 72 | 73 | # Note the quotes around `$TEMP': they are essential! 74 | eval set -- "$OPTS" 75 | 76 | while true; do 77 | case "$1" in 78 | --arch) 79 | ARCH="$2" 80 | shift 2;; 81 | --filearch) 82 | FILEARCH="$2" 83 | shift 2;; 84 | --ver) 85 | VER="$2" 86 | shift 2;; 87 | -o ) 88 | OUTPUTDIR="$2" 89 | shift 2;; 90 | -h | --help ) 91 | showhelp 92 | return 0 93 | shift ;; 94 | -- ) shift; break ;; 95 | * ) break ;; 96 | esac 97 | done 98 | 99 | if [ -z "$ARCH" ]; then 100 | ARCH="`dpkg --print-architecture`" 101 | fi 102 | 103 | if [ -z "$FILEARCH" ]; then 104 | FILEARCH=$ARCH 105 | fi 106 | 107 | if [ -z "$OUTPUTDIR" ]; then 108 | OUTPUTDIR=$CURR_DIR; 109 | fi 110 | 111 | build 112 | } 113 | 114 | main $@ 115 | exit $? 116 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY:all 3 | CFLAGS= -O2 -Wall 4 | 5 | all: fan-control 6 | 7 | clean: 8 | $(RM) fan-control *.o lib/*.o 9 | 10 | fan-control: fan-control.o lib/tiny-json.o 11 | $(CC) $(CFLAGS) $^ -o $@ 12 | 13 | %.o : %.c 14 | $(CC) $(CFLAGS) -c $< -o $@ 15 | -------------------------------------------------------------------------------- /src/fan-control.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2022 Nick Peng 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "lib/tiny-json.h" 35 | 36 | #define TMP_BUFF_LEN_32 32 37 | #define MAX_CONF_FILE_SIZE 4096 38 | 39 | int pidfile_fd = 0; 40 | int pwmchip_id = -1; 41 | int pwmchip_gpio_id = 0; 42 | int pwm_period = 10000; 43 | int fan_mode = 0; 44 | 45 | #define DEFAULT_PID_PATH "/run/fan-control.pid" 46 | #define DEFAULT_CONF_PATH "/etc/fan-control.json" 47 | 48 | #define FAN_PWM_PATH "/sys/devices/platform/fd8b0010.pwm/pwm" 49 | #define TEMP_PATH "/sys/class/thermal/thermal_zone0/temp" 50 | 51 | struct temp_map_struct 52 | { 53 | int speed; 54 | int temp; 55 | int duty; 56 | int duration; 57 | }; 58 | 59 | struct temp_map_struct default_temp_map[] = { 60 | {0, 40, 0, 20}, 61 | {1, 44, 5500, 25}, 62 | {2, 49, 6000, 35}, 63 | {3, 54, 7000, 45}, 64 | {4, 59, 8000, 60}, 65 | {5, 64, 9000, 120}, 66 | {6, 67, 10000, 180}, 67 | }; 68 | 69 | int default_temp_map_size = sizeof(default_temp_map) / sizeof(struct temp_map_struct); 70 | struct temp_map_struct *temp_map = default_temp_map; 71 | int temp_map_size = sizeof(default_temp_map) / sizeof(struct temp_map_struct); 72 | 73 | int write_value(const char *file, const char *value) 74 | { 75 | int fd; 76 | fd = open(file, O_WRONLY); 77 | if (fd == -1) 78 | { 79 | return -1; 80 | } 81 | 82 | if (write(fd, value, strnlen(value, 1024)) == -1) 83 | { 84 | close(fd); 85 | return -1; 86 | } 87 | 88 | close(fd); 89 | return 0; 90 | } 91 | 92 | int write_pwmchip_value(int chipId, const char *key, const char *value) 93 | { 94 | char file[1024]; 95 | snprintf(file, 1024, "%s/pwmchip%d/%s", FAN_PWM_PATH, chipId, key); 96 | return write_value(file, value); 97 | } 98 | 99 | int write_pwmchip_pwm_value(int chipId, int pwm, const char *key, const char *value) 100 | { 101 | char file[1024]; 102 | snprintf(file, 1024, "%s/pwmchip%d/pwm%d/%s", FAN_PWM_PATH, chipId, pwm, key); 103 | return write_value(file, value); 104 | } 105 | 106 | int write_speed(int speed) 107 | { 108 | if (speed >= temp_map_size) 109 | { 110 | return -1; 111 | } 112 | 113 | char buffer[16]; 114 | snprintf(buffer, 15, "%d", temp_map[speed].duty); 115 | if (fan_mode == 0) 116 | { 117 | return write_pwmchip_pwm_value(pwmchip_id, pwmchip_gpio_id, "duty_cycle", buffer); 118 | } 119 | else if (fan_mode == 1) 120 | { 121 | return write_value("/sys/devices/platform/pwm-fan/hwmon/hwmon8/pwm1", buffer); 122 | } 123 | 124 | return -1; 125 | } 126 | 127 | int set_speed(int speed) 128 | { 129 | static int last_speed = -1; 130 | int ret = 0; 131 | if (speed < -1 || speed >= temp_map_size) 132 | { 133 | return 0; 134 | } 135 | 136 | if (last_speed == speed) 137 | { 138 | return 0; 139 | } 140 | 141 | if (last_speed <= 0 && speed > 0) 142 | { 143 | write_speed(temp_map_size - 1); 144 | usleep(100000); 145 | } 146 | 147 | ret = write_speed(speed); 148 | last_speed = speed; 149 | return ret; 150 | } 151 | 152 | int get_speed(int temperature) 153 | { 154 | int i = 0; 155 | int speed = 0; 156 | static int last_speed = -1; 157 | static int last_temperature = -1; 158 | static int count = 0; 159 | 160 | for (i = temp_map_size - 1; i >= 0; i--) 161 | { 162 | if (temperature > temp_map[i].temp) 163 | { 164 | speed = temp_map[i].speed; 165 | if (last_speed < speed) 166 | { 167 | count = temp_map[i].duration; 168 | } 169 | 170 | break; 171 | } 172 | } 173 | 174 | if (speed < last_speed) 175 | { 176 | count--; 177 | } 178 | else if (temperature > last_temperature) 179 | { 180 | count++; 181 | } 182 | 183 | if (count <= 0 || last_speed == -1 || last_speed < speed) 184 | { 185 | last_speed = speed; 186 | } 187 | 188 | last_temperature = temperature; 189 | return last_speed; 190 | } 191 | 192 | void show_help(void) 193 | { 194 | char *msg = "PI custom fan control service.\n" 195 | "Usage: fan-control [option]\n" 196 | "Options:\n" 197 | " -d start as a daemon service.\n" 198 | " -p specify a pid file path (default: /run/fan-control.pid)\n" 199 | " -s [0-6] set fan speed.\n" 200 | " -h show help message.\n" 201 | "\n"; 202 | printf("%s", msg); 203 | } 204 | 205 | int init_pwm_gpio_by_ids(int chipId, int pwmId) 206 | { 207 | int ret = 0; 208 | char max_speed[16]; 209 | 210 | snprintf(max_speed, 15, "%d", temp_map[temp_map_size - 1].duty); 211 | ret = write_pwmchip_value(chipId, "export", "0"); 212 | if (ret < 0 && errno != EBUSY) 213 | { 214 | printf("Failed to export GPIO, %s\n", strerror(errno)); 215 | return -1; 216 | } 217 | 218 | ret = write_pwmchip_pwm_value(chipId, pwmId, "duty_cycle", "0"); 219 | if (ret < 0 && errno != EINVAL) 220 | { 221 | printf("Failed to export GPIO, %s\n", strerror(errno)); 222 | goto do_unexport; 223 | } 224 | 225 | ret = write_pwmchip_pwm_value(chipId, pwmId, "period", max_speed); 226 | if (ret < 0) 227 | { 228 | printf("Failed to export GPIO, %s\n", strerror(errno)); 229 | goto do_unexport; 230 | } 231 | 232 | ret = write_pwmchip_pwm_value(chipId, pwmId, "polarity", "normal"); 233 | if (ret < 0) 234 | { 235 | printf("Failed to export GPIO, %s\n", strerror(errno)); 236 | goto do_unexport; 237 | } 238 | 239 | ret = write_pwmchip_pwm_value(chipId, pwmId, "enable", "1"); 240 | if (ret < 0) 241 | { 242 | printf("Failed to export GPIO, %s\n", strerror(errno)); 243 | goto do_unexport; 244 | } 245 | 246 | return 0; 247 | 248 | do_unexport: 249 | write_pwmchip_value(chipId, "unexport", "0"); 250 | return -1; 251 | } 252 | 253 | int init_pwm_GPIO() 254 | { 255 | if (pwmchip_id > 0) 256 | { 257 | if (init_pwm_gpio_by_ids(pwmchip_id, pwmchip_gpio_id) != 0) 258 | { 259 | printf("Failed to init pwmchip%d GPIO %d, %s\n", pwmchip_id, pwmchip_gpio_id, strerror(errno)); 260 | return -1; 261 | } 262 | } 263 | 264 | for (int i = 0; i < 6; i++) 265 | { 266 | if (init_pwm_gpio_by_ids(i, pwmchip_gpio_id) == 0) 267 | { 268 | pwmchip_id = i; 269 | printf("Found pwmchip%d\n", pwmchip_id); 270 | fan_mode = 0; 271 | return 0; 272 | } 273 | } 274 | 275 | printf("Failed to init GPIO\n"); 276 | return -1; 277 | } 278 | 279 | int init_thermal() 280 | { 281 | int ret = write_value("/sys/class/thermal/thermal_zone0/policy", "user_space"); 282 | if (ret < 0) 283 | { 284 | printf("Failed to set thermal policy, %s\n", strerror(errno)); 285 | return -1; 286 | } 287 | 288 | ret = write_value("/sys/class/thermal/thermal_zone0/mode", "disabled"); 289 | if (ret < 0) 290 | { 291 | printf("Failed to set thermal mode, %s\n", strerror(errno)); 292 | return -1; 293 | } 294 | 295 | fan_mode = 1; 296 | 297 | return 0; 298 | } 299 | 300 | void update_temp_map() 301 | { 302 | for (int i = 0; i < temp_map_size; i++) 303 | { 304 | temp_map[i].duty = temp_map[i].duty * 100 / pwm_period * 255 / 100; 305 | } 306 | } 307 | 308 | int create_pid_file(const char *pid_file) 309 | { 310 | int fd = 0; 311 | int flags = 0; 312 | char buff[TMP_BUFF_LEN_32]; 313 | 314 | /* create pid file, and lock this file */ 315 | fd = open(pid_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 316 | if (fd == -1) 317 | { 318 | fprintf(stderr, "create pid file failed, %s\n", strerror(errno)); 319 | return -1; 320 | } 321 | 322 | flags = fcntl(fd, F_GETFD); 323 | if (flags < 0) 324 | { 325 | fprintf(stderr, "Could not get flags for PID file %s\n", pid_file); 326 | goto errout; 327 | } 328 | 329 | flags |= FD_CLOEXEC; 330 | if (fcntl(fd, F_SETFD, flags) == -1) 331 | { 332 | fprintf(stderr, "Could not set flags for PID file %s\n", pid_file); 333 | goto errout; 334 | } 335 | 336 | if (lockf(fd, F_TLOCK, 0) < 0) 337 | { 338 | fprintf(stderr, "Server is already running.\n"); 339 | goto errout; 340 | } 341 | 342 | snprintf(buff, TMP_BUFF_LEN_32, "%d\n", getpid()); 343 | 344 | if (write(fd, buff, strnlen(buff, TMP_BUFF_LEN_32)) < 0) 345 | { 346 | fprintf(stderr, "write pid to file failed, %s.\n", strerror(errno)); 347 | goto errout; 348 | } 349 | 350 | if (pidfile_fd > 0) 351 | { 352 | close(pidfile_fd); 353 | } 354 | 355 | pidfile_fd = fd; 356 | 357 | return 0; 358 | errout: 359 | if (fd > 0) 360 | { 361 | close(fd); 362 | } 363 | return -1; 364 | } 365 | 366 | int parser_conf_json(const char *data) 367 | { 368 | char str[MAX_CONF_FILE_SIZE]; 369 | struct temp_map_struct *temp_map_buff = NULL; 370 | enum 371 | { 372 | MAX_FIELDS = 1024 373 | }; 374 | json_t pool[MAX_FIELDS]; 375 | 376 | strncpy(str, data, MAX_CONF_FILE_SIZE - 1); 377 | json_t const *parent = json_create(str, pool, MAX_FIELDS); 378 | if (parent == NULL) 379 | { 380 | printf("Failed to parse json file.\n"); 381 | goto errout; 382 | } 383 | 384 | json_t const *pwmchipfield = json_getProperty(parent, "pwmchip"); 385 | if (pwmchipfield != NULL) 386 | { 387 | if (json_getType(pwmchipfield) != JSON_INTEGER) 388 | { 389 | printf("Invalid pwmchip field.\n"); 390 | goto errout; 391 | } 392 | 393 | pwmchip_id = json_getInteger(pwmchipfield); 394 | } 395 | 396 | json_t const *gpiofield = json_getProperty(parent, "gpio"); 397 | if (gpiofield != NULL) 398 | { 399 | if (json_getType(gpiofield) != JSON_INTEGER) 400 | { 401 | printf("Invalid gpio field.\n"); 402 | goto errout; 403 | } 404 | 405 | pwmchip_gpio_id = json_getInteger(gpiofield); 406 | } 407 | 408 | json_t const *periodfield = json_getProperty(parent, "pwm-period"); 409 | if (periodfield != NULL) 410 | { 411 | if (json_getType(periodfield) != JSON_INTEGER) 412 | { 413 | printf("Invalid period field.\n"); 414 | goto errout; 415 | } 416 | 417 | pwm_period = json_getInteger(periodfield); 418 | } 419 | 420 | json_t const *temp_map_array = json_getProperty(parent, "temp-map"); 421 | if (temp_map_array != NULL) 422 | { 423 | if (json_getType(temp_map_array) != JSON_ARRAY) 424 | { 425 | printf("Invalid temp-map field.\n"); 426 | goto errout; 427 | } 428 | 429 | int temp_obj_size = 0; 430 | json_t const *temp_obj; 431 | for (temp_obj = json_getChild(temp_map_array); temp_obj != 0; temp_obj = json_getSibling(temp_obj)) 432 | { 433 | temp_obj_size++; 434 | } 435 | 436 | if (temp_obj_size > 0) 437 | { 438 | temp_map_buff = (struct temp_map_struct *)malloc(sizeof(struct temp_map_struct) * temp_obj_size); 439 | if (temp_map_buff == NULL) 440 | { 441 | printf("Failed to malloc temp_map_buff.\n"); 442 | goto errout; 443 | } 444 | memset(temp_map_buff, 0, sizeof(struct temp_map_struct) * temp_obj_size); 445 | 446 | int id = 0; 447 | for (temp_obj = json_getChild(temp_map_array); temp_obj != 0; temp_obj = json_getSibling(temp_obj)) 448 | { 449 | if (JSON_OBJ != json_getType(temp_obj)) 450 | { 451 | continue; 452 | } 453 | 454 | json_t const *json_temp = json_getProperty(temp_obj, "temp"); 455 | json_t const *json_duty = json_getProperty(temp_obj, "duty"); 456 | json_t const *json_duration = json_getProperty(temp_obj, "duration"); 457 | 458 | if (json_getType(json_temp) != JSON_INTEGER) 459 | { 460 | printf("Invalid temp field.\n"); 461 | goto errout; 462 | } 463 | 464 | if (json_getType(json_duty) != JSON_INTEGER) 465 | { 466 | printf("Invalid duty field.\n"); 467 | goto errout; 468 | } 469 | 470 | if (json_getType(json_duration) != JSON_INTEGER) 471 | { 472 | printf("Invalid duration field.\n"); 473 | goto errout; 474 | } 475 | 476 | int temp = json_getInteger(json_temp); 477 | int duty = json_getInteger(json_duty); 478 | int duration = json_getInteger(json_duration); 479 | 480 | temp_map_buff[id].speed = id; 481 | temp_map_buff[id].temp = temp; 482 | temp_map_buff[id].duty = duty * pwm_period / 100; 483 | temp_map_buff[id].duration = duration; 484 | id++; 485 | } 486 | 487 | temp_map_size = temp_obj_size; 488 | temp_map = temp_map_buff; 489 | } 490 | } 491 | 492 | return 0; 493 | 494 | errout: 495 | if (temp_map_buff != NULL) 496 | { 497 | free(temp_map_buff); 498 | } 499 | return -1; 500 | } 501 | 502 | int load_conf(const char *conf_file) 503 | { 504 | FILE *fp = NULL; 505 | char buff[MAX_CONF_FILE_SIZE]; 506 | 507 | fp = fopen(conf_file, "r"); 508 | if (fp == NULL) 509 | { 510 | printf("Failed to open config file, %s\n", strerror(errno)); 511 | return -1; 512 | } 513 | 514 | memset(buff, 0, MAX_CONF_FILE_SIZE); 515 | int len = fread(buff, 1, MAX_CONF_FILE_SIZE, fp); 516 | if (len <= 0) 517 | { 518 | printf("Failed to read config file, %s\n", strerror(errno)); 519 | goto errout; 520 | } 521 | 522 | if (parser_conf_json(buff) < 0) 523 | { 524 | printf("Failed to parser config file.\n"); 525 | goto errout; 526 | } 527 | 528 | fclose(fp); 529 | return 0; 530 | 531 | errout: 532 | if (fp != NULL) 533 | { 534 | fclose(fp); 535 | } 536 | 537 | return -1; 538 | } 539 | 540 | void display_config() 541 | { 542 | if (fan_mode == 0) 543 | { 544 | printf("pwmchip: %d\n", pwmchip_id); 545 | printf("gpio: %d\n", pwmchip_gpio_id); 546 | printf("pwm-period: %d\n", pwm_period); 547 | } 548 | printf("temp-map:\n"); 549 | 550 | for (int i = 0; i < temp_map_size; i++) 551 | { 552 | printf(" speed: %d, temp: %d, duty: %d, duration: %d\n", temp_map[i].speed, temp_map[i].temp, temp_map[i].duty, temp_map[i].duration); 553 | } 554 | } 555 | 556 | int main(int argc, char *argv[]) 557 | { 558 | int fd_temperature = -1; 559 | char buff[32]; 560 | char pid_file[1024]; 561 | char conf_file[1024] = {0}; 562 | int temperatrue = 0; 563 | int speed_set = -1; 564 | int is_daemon = 0; 565 | 566 | int opt; 567 | 568 | while ((opt = getopt(argc, argv, "s:p:c:dh")) != -1) 569 | { 570 | switch (opt) 571 | { 572 | case 's': 573 | speed_set = atoi(optarg); 574 | break; 575 | case 'p': 576 | strncpy(pid_file, optarg, sizeof(pid_file) - 1); 577 | break; 578 | case 'c': 579 | strncpy(conf_file, optarg, sizeof(conf_file) - 1); 580 | break; 581 | case 'd': 582 | is_daemon = 1; 583 | break; 584 | case 'h': 585 | show_help(); 586 | return 1; 587 | break; 588 | default: 589 | show_help(); 590 | return 1; 591 | } 592 | } 593 | 594 | if (conf_file[0] == 0) 595 | { 596 | strncpy(conf_file, DEFAULT_CONF_PATH, sizeof(conf_file) - 1); 597 | } 598 | 599 | if (load_conf(conf_file) != 0) 600 | { 601 | fprintf(stderr, "load config file failed.\n"); 602 | return 1; 603 | } 604 | 605 | if (is_daemon) 606 | { 607 | if (daemon(0, 0) != 0) 608 | { 609 | printf("run daemon failed.\n"); 610 | return 1; 611 | } 612 | 613 | if (pid_file[0] == '\0') 614 | { 615 | strncpy(pid_file, DEFAULT_PID_PATH, sizeof(pid_file) - 1); 616 | } 617 | 618 | if (create_pid_file(pid_file)) 619 | { 620 | return 1; 621 | } 622 | } 623 | 624 | if (init_pwm_GPIO()) 625 | { 626 | if (init_thermal()) 627 | { 628 | printf("Failed to init thermal.\n"); 629 | return 1; 630 | } 631 | 632 | update_temp_map(); 633 | } 634 | 635 | display_config(); 636 | 637 | if (speed_set != -1) 638 | { 639 | printf("Set speed to %d.\n", speed_set); 640 | if (speed_set < 0 || speed_set >= temp_map_size) 641 | { 642 | fprintf(stderr, "speed is invalid.\n"); 643 | return 1; 644 | } 645 | 646 | if (set_speed(speed_set) != 0) 647 | { 648 | printf("Set speed to %d failed.\n", speed_set); 649 | return 1; 650 | } 651 | 652 | return 0; 653 | } 654 | 655 | fd_temperature = open(TEMP_PATH, O_RDONLY); 656 | if (fd_temperature < 0) 657 | { 658 | printf("Failed to open temperature file, %s\n", strerror(errno)); 659 | return -1; 660 | } 661 | 662 | while (1) 663 | { 664 | sleep(1); 665 | 666 | lseek(fd_temperature, 0, SEEK_SET); 667 | if (read(fd_temperature, &buff, 32) <= 0) 668 | { 669 | perror("read"); 670 | goto errout; 671 | } 672 | 673 | temperatrue = atoi(buff); 674 | speed_set = get_speed(temperatrue / 1000); 675 | set_speed(speed_set); 676 | 677 | if (!is_daemon) 678 | { 679 | printf("speed:%d temperatrue:%d\n", speed_set, temperatrue); 680 | } 681 | } 682 | close(fd_temperature); 683 | return 0; 684 | 685 | errout: 686 | if (fd_temperature > 0) 687 | { 688 | close(fd_temperature); 689 | fd_temperature = -1; 690 | } 691 | 692 | return 1; 693 | } 694 | -------------------------------------------------------------------------------- /src/lib/tiny-json.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | 5 | 6 | Licensed under the MIT License . 7 | SPDX-License-Identifier: MIT 8 | Copyright (c) 2016-2018 Rafa Garcia . 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | #include 31 | #include 32 | #include "tiny-json.h" 33 | 34 | /** Structure to handle a heap of JSON properties. */ 35 | typedef struct jsonStaticPool_s { 36 | json_t* mem; /**< Pointer to array of json properties. */ 37 | unsigned int qty; /**< Length of the array of json properties. */ 38 | unsigned int nextFree; /**< The index of the next free json property. */ 39 | jsonPool_t pool; 40 | } jsonStaticPool_t; 41 | 42 | /* Search a property by its name in a JSON object. */ 43 | json_t const* json_getProperty( json_t const* obj, char const* property ) { 44 | json_t const* sibling; 45 | for( sibling = obj->u.c.child; sibling; sibling = sibling->sibling ) 46 | if ( sibling->name && !strcmp( sibling->name, property ) ) 47 | return sibling; 48 | return 0; 49 | } 50 | 51 | /* Search a property by its name in a JSON object and return its value. */ 52 | char const* json_getPropertyValue( json_t const* obj, char const* property ) { 53 | json_t const* field = json_getProperty( obj, property ); 54 | if ( !field ) return 0; 55 | jsonType_t type = json_getType( field ); 56 | if ( JSON_ARRAY >= type ) return 0; 57 | return json_getValue( field ); 58 | } 59 | 60 | /* Internal prototypes: */ 61 | static char* goBlank( char* str ); 62 | static char* goNum( char* str ); 63 | static json_t* poolInit( jsonPool_t* pool ); 64 | static json_t* poolAlloc( jsonPool_t* pool ); 65 | static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool ); 66 | static char* setToNull( char* ch ); 67 | static bool isEndOfPrimitive( char ch ); 68 | 69 | /* Parse a string to get a json. */ 70 | json_t const* json_createWithPool( char *str, jsonPool_t *pool ) { 71 | char* ptr = goBlank( str ); 72 | if ( !ptr || (*ptr != '{' && *ptr != '[') ) return 0; 73 | json_t* obj = pool->init( pool ); 74 | obj->name = 0; 75 | obj->sibling = 0; 76 | obj->u.c.child = 0; 77 | ptr = objValue( ptr, obj, pool ); 78 | if ( !ptr ) return 0; 79 | return obj; 80 | } 81 | 82 | /* Parse a string to get a json. */ 83 | json_t const* json_create( char* str, json_t mem[], unsigned int qty ) { 84 | jsonStaticPool_t spool; 85 | spool.mem = mem; 86 | spool.qty = qty; 87 | spool.pool.init = poolInit; 88 | spool.pool.alloc = poolAlloc; 89 | return json_createWithPool( str, &spool.pool ); 90 | } 91 | 92 | /** Get a special character with its escape character. Examples: 93 | * 'b' -> '\\b', 'n' -> '\\n', 't' -> '\\t' 94 | * @param ch The escape character. 95 | * @retval The character code. */ 96 | static char getEscape( char ch ) { 97 | static struct { char ch; char code; } const pair[] = { 98 | { '\"', '\"' }, { '\\', '\\' }, 99 | { '/', '/' }, { 'b', '\b' }, 100 | { 'f', '\f' }, { 'n', '\n' }, 101 | { 'r', '\r' }, { 't', '\t' }, 102 | }; 103 | unsigned int i; 104 | for( i = 0; i < sizeof pair / sizeof *pair; ++i ) 105 | if ( pair[i].ch == ch ) 106 | return pair[i].code; 107 | return '\0'; 108 | } 109 | 110 | /** Parse 4 characters. 111 | * @param str Pointer to first digit. 112 | * @retval '?' If the four characters are hexadecimal digits. 113 | * @retval '\0' In other cases. */ 114 | static unsigned char getCharFromUnicode( unsigned char const* str ) { 115 | unsigned int i; 116 | for( i = 0; i < 4; ++i ) 117 | if ( !isxdigit( str[i] ) ) 118 | return '\0'; 119 | return '?'; 120 | } 121 | 122 | /** Parse a string and replace the scape characters by their meaning characters. 123 | * This parser stops when finds the character '\"'. Then replaces '\"' by '\0'. 124 | * @param str Pointer to first character. 125 | * @retval Pointer to first non white space after the string. If success. 126 | * @retval Null pointer if any error occur. */ 127 | static char* parseString( char* str ) { 128 | unsigned char* head = (unsigned char*)str; 129 | unsigned char* tail = (unsigned char*)str; 130 | for( ; *head; ++head, ++tail ) { 131 | if ( *head == '\"' ) { 132 | *tail = '\0'; 133 | return (char*)++head; 134 | } 135 | if ( *head == '\\' ) { 136 | if ( *++head == 'u' ) { 137 | char const ch = getCharFromUnicode( ++head ); 138 | if ( ch == '\0' ) return 0; 139 | *tail = ch; 140 | head += 3; 141 | } 142 | else { 143 | char const esc = getEscape( *head ); 144 | if ( esc == '\0' ) return 0; 145 | *tail = esc; 146 | } 147 | } 148 | else *tail = *head; 149 | } 150 | return 0; 151 | } 152 | 153 | /** Parse a string to get the name of a property. 154 | * @param ptr Pointer to first character. 155 | * @param property The property to assign the name. 156 | * @retval Pointer to first of property value. If success. 157 | * @retval Null pointer if any error occur. */ 158 | static char* propertyName( char* ptr, json_t* property ) { 159 | property->name = ++ptr; 160 | ptr = parseString( ptr ); 161 | if ( !ptr ) return 0; 162 | ptr = goBlank( ptr ); 163 | if ( !ptr ) return 0; 164 | if ( *ptr++ != ':' ) return 0; 165 | return goBlank( ptr ); 166 | } 167 | 168 | /** Parse a string to get the value of a property when its type is JSON_TEXT. 169 | * @param ptr Pointer to first character ('\"'). 170 | * @param property The property to assign the name. 171 | * @retval Pointer to first non white space after the string. If success. 172 | * @retval Null pointer if any error occur. */ 173 | static char* textValue( char* ptr, json_t* property ) { 174 | ++property->u.value; 175 | ptr = parseString( ++ptr ); 176 | if ( !ptr ) return 0; 177 | property->type = JSON_TEXT; 178 | return ptr; 179 | } 180 | 181 | /** Compare two strings until get the null character in the second one. 182 | * @param ptr sub string 183 | * @param str main string 184 | * @retval Pointer to next character. 185 | * @retval Null pointer if any error occur. */ 186 | static char* checkStr( char* ptr, char const* str ) { 187 | while( *str ) 188 | if ( *ptr++ != *str++ ) 189 | return 0; 190 | return ptr; 191 | } 192 | 193 | /** Parser a string to get a primitive value. 194 | * If the first character after the value is different of '}' or ']' is set to '\0'. 195 | * @param ptr Pointer to first character. 196 | * @param property Property handler to set the value and the type, (true, false or null). 197 | * @param value String with the primitive literal. 198 | * @param type The code of the type. ( JSON_BOOLEAN or JSON_NULL ) 199 | * @retval Pointer to first non white space after the string. If success. 200 | * @retval Null pointer if any error occur. */ 201 | static char* primitiveValue( char* ptr, json_t* property, char const* value, jsonType_t type ) { 202 | ptr = checkStr( ptr, value ); 203 | if ( !ptr || !isEndOfPrimitive( *ptr ) ) return 0; 204 | ptr = setToNull( ptr ); 205 | property->type = type; 206 | return ptr; 207 | } 208 | 209 | /** Parser a string to get a true value. 210 | * If the first character after the value is different of '}' or ']' is set to '\0'. 211 | * @param ptr Pointer to first character. 212 | * @param property Property handler to set the value and the type, (true, false or null). 213 | * @retval Pointer to first non white space after the string. If success. 214 | * @retval Null pointer if any error occur. */ 215 | static char* trueValue( char* ptr, json_t* property ) { 216 | return primitiveValue( ptr, property, "true", JSON_BOOLEAN ); 217 | } 218 | 219 | /** Parser a string to get a false value. 220 | * If the first character after the value is different of '}' or ']' is set to '\0'. 221 | * @param ptr Pointer to first character. 222 | * @param property Property handler to set the value and the type, (true, false or null). 223 | * @retval Pointer to first non white space after the string. If success. 224 | * @retval Null pointer if any error occur. */ 225 | static char* falseValue( char* ptr, json_t* property ) { 226 | return primitiveValue( ptr, property, "false", JSON_BOOLEAN ); 227 | } 228 | 229 | /** Parser a string to get a null value. 230 | * If the first character after the value is different of '}' or ']' is set to '\0'. 231 | * @param ptr Pointer to first character. 232 | * @param property Property handler to set the value and the type, (true, false or null). 233 | * @retval Pointer to first non white space after the string. If success. 234 | * @retval Null pointer if any error occur. */ 235 | static char* nullValue( char* ptr, json_t* property ) { 236 | return primitiveValue( ptr, property, "null", JSON_NULL ); 237 | } 238 | 239 | /** Analyze the exponential part of a real number. 240 | * @param ptr Pointer to first character. 241 | * @retval Pointer to first non numerical after the string. If success. 242 | * @retval Null pointer if any error occur. */ 243 | static char* expValue( char* ptr ) { 244 | if ( *ptr == '-' || *ptr == '+' ) ++ptr; 245 | if ( !isdigit( (int)(*ptr) ) ) return 0; 246 | ptr = goNum( ++ptr ); 247 | return ptr; 248 | } 249 | 250 | /** Analyze the decimal part of a real number. 251 | * @param ptr Pointer to first character. 252 | * @retval Pointer to first non numerical after the string. If success. 253 | * @retval Null pointer if any error occur. */ 254 | static char* fraqValue( char* ptr ) { 255 | if ( !isdigit( (int)(*ptr) ) ) return 0; 256 | ptr = goNum( ++ptr ); 257 | if ( !ptr ) return 0; 258 | return ptr; 259 | } 260 | 261 | /** Parser a string to get a numerical value. 262 | * If the first character after the value is different of '}' or ']' is set to '\0'. 263 | * @param ptr Pointer to first character. 264 | * @param property Property handler to set the value and the type: JSON_REAL or JSON_INTEGER. 265 | * @retval Pointer to first non white space after the string. If success. 266 | * @retval Null pointer if any error occur. */ 267 | static char* numValue( char* ptr, json_t* property ) { 268 | if ( *ptr == '-' ) ++ptr; 269 | if ( !isdigit( (int)(*ptr) ) ) return 0; 270 | if ( *ptr != '0' ) { 271 | ptr = goNum( ptr ); 272 | if ( !ptr ) return 0; 273 | } 274 | else if ( isdigit( (int)(*++ptr) ) ) return 0; 275 | property->type = JSON_INTEGER; 276 | if ( *ptr == '.' ) { 277 | ptr = fraqValue( ++ptr ); 278 | if ( !ptr ) return 0; 279 | property->type = JSON_REAL; 280 | } 281 | if ( *ptr == 'e' || *ptr == 'E' ) { 282 | ptr = expValue( ++ptr ); 283 | if ( !ptr ) return 0; 284 | property->type = JSON_REAL; 285 | } 286 | if ( !isEndOfPrimitive( *ptr ) ) return 0; 287 | if ( JSON_INTEGER == property->type ) { 288 | char const* value = property->u.value; 289 | bool const negative = *value == '-'; 290 | static char const min[] = "-9223372036854775808"; 291 | static char const max[] = "9223372036854775807"; 292 | unsigned int const maxdigits = ( negative? sizeof min: sizeof max ) - 1; 293 | unsigned int const len = ( unsigned int const ) ( ptr - value ); 294 | if ( len > maxdigits ) return 0; 295 | if ( len == maxdigits ) { 296 | char const tmp = *ptr; 297 | *ptr = '\0'; 298 | char const* const threshold = negative ? min: max; 299 | if ( 0 > strcmp( threshold, value ) ) return 0; 300 | *ptr = tmp; 301 | } 302 | } 303 | ptr = setToNull( ptr ); 304 | return ptr; 305 | } 306 | 307 | /** Add a property to a JSON object or array. 308 | * @param obj The handler of the JSON object or array. 309 | * @param property The handler of the property to be added. */ 310 | static void add( json_t* obj, json_t* property ) { 311 | property->sibling = 0; 312 | if ( !obj->u.c.child ){ 313 | obj->u.c.child = property; 314 | obj->u.c.last_child = property; 315 | } else { 316 | obj->u.c.last_child->sibling = property; 317 | obj->u.c.last_child = property; 318 | } 319 | } 320 | 321 | /** Parser a string to get a json object value. 322 | * @param ptr Pointer to first character. 323 | * @param obj The handler of the JSON root object or array. 324 | * @param pool The handler of a json pool for creating json instances. 325 | * @retval Pointer to first character after the value. If success. 326 | * @retval Null pointer if any error occur. */ 327 | static char* objValue( char* ptr, json_t* obj, jsonPool_t* pool ) { 328 | obj->type = *ptr == '{' ? JSON_OBJ : JSON_ARRAY; 329 | obj->u.c.child = 0; 330 | obj->sibling = 0; 331 | ptr++; 332 | for(;;) { 333 | ptr = goBlank( ptr ); 334 | if ( !ptr ) return 0; 335 | if ( *ptr == ',' ) { 336 | ++ptr; 337 | continue; 338 | } 339 | char const endchar = ( obj->type == JSON_OBJ )? '}': ']'; 340 | if ( *ptr == endchar ) { 341 | *ptr = '\0'; 342 | json_t* parentObj = obj->sibling; 343 | if ( !parentObj ) return ++ptr; 344 | obj->sibling = 0; 345 | obj = parentObj; 346 | ++ptr; 347 | continue; 348 | } 349 | json_t* property = pool->alloc( pool ); 350 | if ( !property ) return 0; 351 | if( obj->type != JSON_ARRAY ) { 352 | if ( *ptr != '\"' ) return 0; 353 | ptr = propertyName( ptr, property ); 354 | if ( !ptr ) return 0; 355 | } 356 | else property->name = 0; 357 | add( obj, property ); 358 | property->u.value = ptr; 359 | switch( *ptr ) { 360 | case '{': 361 | property->type = JSON_OBJ; 362 | property->u.c.child = 0; 363 | property->sibling = obj; 364 | obj = property; 365 | ++ptr; 366 | break; 367 | case '[': 368 | property->type = JSON_ARRAY; 369 | property->u.c.child = 0; 370 | property->sibling = obj; 371 | obj = property; 372 | ++ptr; 373 | break; 374 | case '\"': ptr = textValue( ptr, property ); break; 375 | case 't': ptr = trueValue( ptr, property ); break; 376 | case 'f': ptr = falseValue( ptr, property ); break; 377 | case 'n': ptr = nullValue( ptr, property ); break; 378 | default: ptr = numValue( ptr, property ); break; 379 | } 380 | if ( !ptr ) return 0; 381 | } 382 | } 383 | 384 | /** Initialize a json pool. 385 | * @param pool The handler of the pool. 386 | * @return a instance of a json. */ 387 | static json_t* poolInit( jsonPool_t* pool ) { 388 | jsonStaticPool_t *spool = json_containerOf( pool, jsonStaticPool_t, pool ); 389 | spool->nextFree = 1; 390 | return spool->mem; 391 | } 392 | 393 | /** Create an instance of a json from a pool. 394 | * @param pool The handler of the pool. 395 | * @retval The handler of the new instance if success. 396 | * @retval Null pointer if the pool was empty. */ 397 | static json_t* poolAlloc( jsonPool_t* pool ) { 398 | jsonStaticPool_t *spool = json_containerOf( pool, jsonStaticPool_t, pool ); 399 | if ( spool->nextFree >= spool->qty ) return 0; 400 | return spool->mem + spool->nextFree++; 401 | } 402 | 403 | /** Checks whether an character belongs to set. 404 | * @param ch Character value to be checked. 405 | * @param set Set of characters. It is just a null-terminated string. 406 | * @return true or false there is membership or not. */ 407 | static bool isOneOfThem( char ch, char const* set ) { 408 | while( *set != '\0' ) 409 | if ( ch == *set++ ) 410 | return true; 411 | return false; 412 | } 413 | 414 | /** Increases a pointer while it points to a character that belongs to a set. 415 | * @param str The initial pointer value. 416 | * @param set Set of characters. It is just a null-terminated string. 417 | * @return The final pointer value or null pointer if the null character was found. */ 418 | static char* goWhile( char* str, char const* set ) { 419 | for(; *str != '\0'; ++str ) { 420 | if ( !isOneOfThem( *str, set ) ) 421 | return str; 422 | } 423 | return 0; 424 | } 425 | 426 | /** Set of characters that defines a blank. */ 427 | static char const* const blank = " \n\r\t\f"; 428 | 429 | /** Increases a pointer while it points to a white space character. 430 | * @param str The initial pointer value. 431 | * @return The final pointer value or null pointer if the null character was found. */ 432 | static char* goBlank( char* str ) { 433 | return goWhile( str, blank ); 434 | } 435 | 436 | /** Increases a pointer while it points to a decimal digit character. 437 | * @param str The initial pointer value. 438 | * @return The final pointer value or null pointer if the null character was found. */ 439 | static char* goNum( char* str ) { 440 | for( ; *str != '\0'; ++str ) { 441 | if ( !isdigit( (int)(*str) ) ) 442 | return str; 443 | } 444 | return 0; 445 | } 446 | 447 | /** Set of characters that defines the end of an array or a JSON object. */ 448 | static char const* const endofblock = "}]"; 449 | 450 | /** Set a char to '\0' and increase its pointer if the char is different to '}' or ']'. 451 | * @param ch Pointer to character. 452 | * @return Final value pointer. */ 453 | static char* setToNull( char* ch ) { 454 | if ( !isOneOfThem( *ch, endofblock ) ) *ch++ = '\0'; 455 | return ch; 456 | } 457 | 458 | /** Indicate if a character is the end of a primitive value. */ 459 | static bool isEndOfPrimitive( char ch ) { 460 | return ch == ',' || isOneOfThem( ch, blank ) || isOneOfThem( ch, endofblock ); 461 | } 462 | -------------------------------------------------------------------------------- /src/lib/tiny-json.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | 5 | 6 | Licensed under the MIT License . 7 | SPDX-License-Identifier: MIT 8 | Copyright (c) 2016-2018 Rafa Garcia . 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | #ifndef _TINY_JSON_H_ 31 | #define _TINY_JSON_H_ 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #define json_containerOf( ptr, type, member ) \ 43 | ((type*)( (char*)ptr - offsetof( type, member ) )) 44 | 45 | /** @defgroup tinyJson Tiny JSON parser. 46 | * @{ */ 47 | 48 | /** Enumeration of codes of supported JSON properties types. */ 49 | typedef enum { 50 | JSON_OBJ, JSON_ARRAY, JSON_TEXT, JSON_BOOLEAN, 51 | JSON_INTEGER, JSON_REAL, JSON_NULL 52 | } jsonType_t; 53 | 54 | /** Structure to handle JSON properties. */ 55 | typedef struct json_s { 56 | struct json_s* sibling; 57 | char const* name; 58 | union { 59 | char const* value; 60 | struct { 61 | struct json_s* child; 62 | struct json_s* last_child; 63 | } c; 64 | } u; 65 | jsonType_t type; 66 | } json_t; 67 | 68 | /** Parse a string to get a json. 69 | * @param str String pointer with a JSON object. It will be modified. 70 | * @param mem Array of json properties to allocate. 71 | * @param qty Number of elements of mem. 72 | * @retval Null pointer if any was wrong in the parse process. 73 | * @retval If the parser process was successfully a valid handler of a json. 74 | * This property is always unnamed and its type is JSON_OBJ. */ 75 | json_t const* json_create( char* str, json_t mem[], unsigned int qty ); 76 | 77 | /** Get the name of a json property. 78 | * @param json A valid handler of a json property. 79 | * @retval Pointer to null-terminated if property has name. 80 | * @retval Null pointer if the property is unnamed. */ 81 | static inline char const* json_getName( json_t const* json ) { 82 | return json->name; 83 | } 84 | 85 | /** Get the value of a json property. 86 | * The type of property cannot be JSON_OBJ or JSON_ARRAY. 87 | * @param property A valid handler of a json property. 88 | * @return Pointer to null-terminated string with the value. */ 89 | static inline char const* json_getValue( json_t const* property ) { 90 | return property->u.value; 91 | } 92 | 93 | /** Get the type of a json property. 94 | * @param json A valid handler of a json property. 95 | * @return The code of type.*/ 96 | static inline jsonType_t json_getType( json_t const* json ) { 97 | return json->type; 98 | } 99 | 100 | /** Get the next sibling of a JSON property that is within a JSON object or array. 101 | * @param json A valid handler of a json property. 102 | * @retval The handler of the next sibling if found. 103 | * @retval Null pointer if the json property is the last one. */ 104 | static inline json_t const* json_getSibling( json_t const* json ) { 105 | return json->sibling; 106 | } 107 | 108 | /** Search a property by its name in a JSON object. 109 | * @param obj A valid handler of a json object. Its type must be JSON_OBJ. 110 | * @param property The name of property to get. 111 | * @retval The handler of the json property if found. 112 | * @retval Null pointer if not found. */ 113 | json_t const* json_getProperty( json_t const* obj, char const* property ); 114 | 115 | 116 | /** Search a property by its name in a JSON object and return its value. 117 | * @param obj A valid handler of a json object. Its type must be JSON_OBJ. 118 | * @param property The name of property to get. 119 | * @retval If found a pointer to null-terminated string with the value. 120 | * @retval Null pointer if not found or it is an array or an object. */ 121 | char const* json_getPropertyValue( json_t const* obj, char const* property ); 122 | 123 | /** Get the first property of a JSON object or array. 124 | * @param json A valid handler of a json property. 125 | * Its type must be JSON_OBJ or JSON_ARRAY. 126 | * @retval The handler of the first property if there is. 127 | * @retval Null pointer if the json object has not properties. */ 128 | static inline json_t const* json_getChild( json_t const* json ) { 129 | return json->u.c.child; 130 | } 131 | 132 | /** Get the value of a json boolean property. 133 | * @param property A valid handler of a json object. Its type must be JSON_BOOLEAN. 134 | * @return The value stdbool. */ 135 | static inline bool json_getBoolean( json_t const* property ) { 136 | return *property->u.value == 't'; 137 | } 138 | 139 | /** Get the value of a json integer property. 140 | * @param property A valid handler of a json object. Its type must be JSON_INTEGER. 141 | * @return The value stdint. */ 142 | static inline int64_t json_getInteger( json_t const* property ) { 143 | return strtoll( property->u.value,(char**)NULL, 10); 144 | } 145 | 146 | /** Get the value of a json real property. 147 | * @param property A valid handler of a json object. Its type must be JSON_REAL. 148 | * @return The value. */ 149 | static inline double json_getReal( json_t const* property ) { 150 | return strtod( property->u.value,(char**)NULL ); 151 | } 152 | 153 | 154 | 155 | /** Structure to handle a heap of JSON properties. */ 156 | typedef struct jsonPool_s jsonPool_t; 157 | struct jsonPool_s { 158 | json_t* (*init)( jsonPool_t* pool ); 159 | json_t* (*alloc)( jsonPool_t* pool ); 160 | }; 161 | 162 | /** Parse a string to get a json. 163 | * @param str String pointer with a JSON object. It will be modified. 164 | * @param pool Custom json pool pointer. 165 | * @retval Null pointer if any was wrong in the parse process. 166 | * @retval If the parser process was successfully a valid handler of a json. 167 | * This property is always unnamed and its type is JSON_OBJ. */ 168 | json_t const* json_createWithPool( char* str, jsonPool_t* pool ); 169 | 170 | /** @ } */ 171 | 172 | #ifdef __cplusplus 173 | } 174 | #endif 175 | 176 | #endif /* _TINY_JSON_H_ */ 177 | -------------------------------------------------------------------------------- /systemd/fan-control.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=fan control for rock5b 3 | After=network.target 4 | StartLimitBurst=0 5 | StartLimitIntervalSec=60 6 | 7 | [Service] 8 | Type=forking 9 | PIDFile=@RUNSTATEDIR@/fan-control.pid 10 | ExecStart=@SBINDIR@/fan-control -d -p @RUNSTATEDIR@/fan-control.pid 11 | Restart=always 12 | RestartSec=2 13 | TimeoutStopSec=15 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | Alias=fan-control.service 18 | --------------------------------------------------------------------------------