From ad34a2b53c659f310e71ad2002dc8e08972c7867 Mon Sep 17 00:00:00 2001 From: Nils Koenig Date: Sat, 12 Nov 2016 16:54:19 +0100 Subject: [PATCH] Added wifischedule Turns WiFi on and off according to a schedule Signed-off-by: Nils Koenig --- net/wifischedule/LICENSE | 11 + net/wifischedule/Makefile | 51 +++ net/wifischedule/README.md | 86 +++++ net/wifischedule/net/etc/config/wifi_schedule | 19 ++ net/wifischedule/net/usr/bin/wifi_schedule.sh | 321 ++++++++++++++++++ 5 files changed, 488 insertions(+) create mode 100644 net/wifischedule/LICENSE create mode 100644 net/wifischedule/Makefile create mode 100644 net/wifischedule/README.md create mode 100644 net/wifischedule/net/etc/config/wifi_schedule create mode 100755 net/wifischedule/net/usr/bin/wifi_schedule.sh diff --git a/net/wifischedule/LICENSE b/net/wifischedule/LICENSE new file mode 100644 index 000000000..e313028d3 --- /dev/null +++ b/net/wifischedule/LICENSE @@ -0,0 +1,11 @@ +Copyright (c) 2016, prpl Foundation + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without +fee is hereby granted, provided that the above copyright notice and this permission notice appear +in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/net/wifischedule/Makefile b/net/wifischedule/Makefile new file mode 100644 index 000000000..a9dd3f7fc --- /dev/null +++ b/net/wifischedule/Makefile @@ -0,0 +1,51 @@ +# Copyright (c) 2016, prpl Foundation +# +# Permission to use, copy, modify, and/or distribute this software for any purpose with or without +# fee is hereby granted, provided that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +# FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# Author: Nils Koenig + +include $(TOPDIR)/rules.mk + +PKG_NAME:=wifischedule +PKG_VERSION:=1 +PKG_RELEASE:=1 +PKG_LICENSE:=PRPL + +PKG_MAINTAINER:=Nils Koenig + +include $(INCLUDE_DIR)/package.mk + +define Package/wifischedule + SUBMENU:=wireless + TITLE:=Turns WiFi on and off according to a schedule + SECTION:=net + CATEGORY:=Network +endef + +define Package/wifischedule/description +Turns WiFi on and off according to a schedule defined in UCI. +endef + +define Package/wifischedule/conffiles +/etc/config/wifi_schedule +endef + +define Build/Compile +endef + +define Package/wifischedule/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./net/usr/bin/wifi_schedule.sh $(1)/usr/bin/wifi_schedule.sh + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DATA) ./net/etc/config/wifi_schedule $(1)/etc/config/wifi_schedule +endef + +$(eval $(call BuildPackage,wifischedule)) diff --git a/net/wifischedule/README.md b/net/wifischedule/README.md new file mode 100644 index 000000000..591abb104 --- /dev/null +++ b/net/wifischedule/README.md @@ -0,0 +1,86 @@ +# wifischedule +Turns WiFi on and off according to a schedule on an openwrt router + +## Components +* wifischedule: Shell script that creates cron jobs based on configuration provided in UCI and does all the other logic of enabling and disabling wifi with the use of `/sbin/wifi` and `/usr/bin/iwinfo`. Can be used standalone. +* luci-app-wifischedule: LUCI frontend for creating the UCI configuration and triggering the actions. Depends on wifischedule. + + +## Use cases +You can create user-defined events when to enable or disable WiFi. +There are various use cases why you would like to do so: + +1. Reduce power consumption and therefore reduce CO2 emissions. +2. Reduce emitted electromagnatic radiation. +3. Force busincess hours when WiFi is available. + +Regarding 1: Please note, that you need to unload the wireless driver modules in order to get the most effect of saving power. +In my test scenario only disabling WiFi saves about ~0.4 Watt, unloading the modules removes another ~0.4 Watt. + +Regarding 2: Think of a wireless accesspoint e.g. in your bedrom, kids room where you want to remove the ammount of radiation emitted. + +Regarding 3: E.g. in a company, why would wireless need to be enabled weekends if no one is there working? +Or think of an accesspoint in your kids room when you want the youngsters to sleep after 10 pm instead of facebooking... + +## Configuration +You can create an arbitrary number of schedule events. Please note that there is on sanity check done wheather the start / stop times overlap or make sense. +If start and stop time are equal, this leads to disabling the WiFi at the given time. + +Logging if enabled is done to the file `/var/log/wifi_schedule.log` and can be reviewed through the "View Logfile" tab. +The cron jobs created can be reviewed through the "View Cron Jobs" tab. + +Please note that the "Unload Modules" function is currently considered as experimental. You can manually add / remove modules in the text field. +The button "Determine Modules Automatically" tries to make a best guess determining regarding the driver module and its dependencies. +When un-/loading the modules, there is a certain number of retries (`module_load`) performed. + +The option "Force disabling wifi even if stations associated" does what it says - when activated it simply shuts down WiFi. +When unchecked, its checked every `recheck_interval` minutes if there are still stations associated. Once the stations disconnect, WiFi is disabled. + +Please note, that the parameters `module_load` and `recheck_interval` are only accessible through uci. + +## UCI Configuration `wifi_schedule` +UCI configuration file: `/etc/config/wifi_schedule`: + +``` +config global + option logging '0' + option enabled '0' + option recheck_interval '10' + option modules_retries '10' + +config entry 'Businesshours' + option enabled '0' + option daysofweek 'Monday Tuesday Wednesday Thursday Friday' + option starttime '06:00' + option stoptime '22:00' + option forcewifidown '0' + +config entry 'Weekend' + option enabled '0' + option daysofweek 'Saturday Sunday' + option starttime '00:00' + option stoptime '00:00' + option forcewifidown '1' +``` + +## Script: `wifi_schedule.sh` +This is the script that does the work. Make your changes to the UCI config file: `/etc/config/wifi_schedule` + +Then call the script as follows in order to get the necessary cron jobs created: + +`wifi_schedule.sh cron` + +All commands: + +``` +wifi_schedule.sh cron|start|stop|forcestop|recheck|getmodules|savemodules|help + + cron: Create cronjob entries. + start: Start wifi. + stop: Stop wifi gracefully, i.e. check if there are stations associated and if so keep retrying. + forcestop: Stop wifi immediately. + recheck: Recheck if wifi can be disabled now. + getmodules: Returns a list of modules used by the wireless driver(s) + savemodules: Saves a list of automatic determined modules to UCI + help: This description. +``` diff --git a/net/wifischedule/net/etc/config/wifi_schedule b/net/wifischedule/net/etc/config/wifi_schedule new file mode 100644 index 000000000..946a1fff4 --- /dev/null +++ b/net/wifischedule/net/etc/config/wifi_schedule @@ -0,0 +1,19 @@ +config global + option logging '0' + option enabled '0' + option recheck_interval '10' + option modules_retries '10' + +config entry 'Businesshours' + option enabled '0' + option daysofweek 'Monday Tuesday Wednesday Thursday Friday' + option starttime '06:00' + option stoptime '22:00' + option forcewifidown '0' + +config entry 'Weekend' + option enabled '0' + option daysofweek 'Saturday Sunday' + option starttime '00:00' + option stoptime '00:00' + option forcewifidown '1' diff --git a/net/wifischedule/net/usr/bin/wifi_schedule.sh b/net/wifischedule/net/usr/bin/wifi_schedule.sh new file mode 100755 index 000000000..363f95dd6 --- /dev/null +++ b/net/wifischedule/net/usr/bin/wifi_schedule.sh @@ -0,0 +1,321 @@ +#!/bin/sh + +# Copyright (c) 2016, prpl Foundation +# +# Permission to use, copy, modify, and/or distribute this software for any purpose with or without +# fee is hereby granted, provided that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +# FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# Author: Nils Koenig + +SCRIPT=$0 +LOCKFILE=/tmp/wifi_schedule.lock +LOGFILE=/tmp/log/wifi_schedule.log +LOGGING=0 #default is off +PACKAGE=wifi_schedule +GLOBAL=${PACKAGE}.@global[0] + +_log() +{ + if [ ${LOGGING} -eq 1 ]; then + local ts=$(date) + echo "$ts $@" >> ${LOGFILE} + fi +} + +_exit() +{ + local rc=$1 + lock -u ${LOCKFILE} + exit ${rc} +} + +_cron_restart() +{ + /etc/init.d/cron restart > /dev/null +} + +_add_cron_script() +{ + (crontab -l ; echo "$1") | sort | uniq | crontab - + _cron_restart +} + +_rm_cron_script() +{ + crontab -l | grep -v "$1" | sort | uniq | crontab - + _cron_restart +} + +_get_uci_value_raw() +{ + local value + value=$(uci get $1 2> /dev/null) + local rc=$? + echo ${value} + return ${rc} +} + +_get_uci_value() +{ + local value + value=$(_get_uci_value_raw $1) + local rc=$? + if [ ${rc} -ne 0 ]; then + _log "Could not determine UCI value $1" + return 1 + fi + echo ${value} +} + +_format_dow_list() +{ + local dow=$1 + local flist="" + local day + for day in ${dow} + do + if [ ! -z ${flist} ]; then + flist="${flist}," + fi + flist="${flist}${day:0:3}" + done + echo ${flist} +} + + +_enable_wifi_schedule() +{ + local entry=$1 + local starttime + local stoptime + starttime=$(_get_uci_value ${PACKAGE}.${entry}.starttime) || _exit 1 + stoptime=$(_get_uci_value ${PACKAGE}.${entry}.stoptime) || _exit 1 + + local dow + dow=$(_get_uci_value_raw ${PACKAGE}.${entry}.daysofweek) || _exit 1 + + local fdow=$(_format_dow_list "$dow") + local forcewifidown + forcewifidown=$(_get_uci_value ${PACKAGE}.${entry}.forcewifidown) + local stopmode="stop" + if [ $forcewifidown -eq 1 ]; then + stopmode="forcestop" + fi + + + local stop_cron_entry="$(echo ${stoptime} | awk -F':' '{print $2, $1}') * * ${fdow} ${SCRIPT} ${stopmode}" # ${entry}" + _add_cron_script "${stop_cron_entry}" + + if [[ $starttime != $stoptime ]] + then + local start_cron_entry="$(echo ${starttime} | awk -F':' '{print $2, $1}') * * ${fdow} ${SCRIPT} start" # ${entry}" + _add_cron_script "${start_cron_entry}" + fi + + return 0 +} + +_get_wireless_interfaces() +{ + local n=$(cat /proc/net/wireless | wc -l) + cat /proc/net/wireless | tail -n $(($n - 2))|awk -F':' '{print $1}'| sed 's/ //' +} + + +get_module_list() +{ + local mod_list + local _if + for _if in $(_get_wireless_interfaces) + do + local mod=$(basename $(readlink -f /sys/class/net/${_if}/device/driver)) + local mod_dep=$(modinfo ${mod} | awk '{if ($1 ~ /depends/) print $2}') + mod_list=$(echo -e "${mod_list}\n${mod},${mod_dep}" | sort | uniq) + done + echo $mod_list | tr ',' ' ' +} + +save_module_list_uci() +{ + local list=$(get_module_list) + uci set ${GLOBAL}.modules="${list}" + uci commit ${PACKAGE} +} + +_unload_modules() +{ + local list=$(_get_uci_value ${GLOBAL}.modules) + local retries + retries=$(_get_uci_value ${GLOBAL}.modules_retries) || _exit 1 + _log "unload_modules ${list} (retries: ${retries})" + local i=0 + while [[ ${i} -lt ${retries} && "${list}" != "" ]] + do + i=$(($i+1)) + local mod + local first=0 + for mod in ${list} + do + if [ $first -eq 0 ]; then + list="" + first=1 + fi + rmmod ${mod} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + list="$list $mod" + fi + done + done +} + + +_load_modules() +{ + local list=$(_get_uci_value ${GLOBAL}.modules) + local retries + retries=$(_get_uci_value ${GLOBAL}.modules_retries) || _exit 1 + _log "load_modules ${list} (retries: ${retries})" + local i=0 + while [[ ${i} -lt ${retries} && "${list}" != "" ]] + do + i=$(($i+1)) + local mod + local first=0 + for mod in ${list} + do + if [ $first -eq 0 ]; then + list="" + first=1 + fi + modprobe ${mod} > /dev/null 2>&1 + rc=$? + if [ $rc -ne 255 ]; then + list="$list $mod" + fi + done + done +} + +_create_cron_entries() +{ + local entries=$(uci show ${PACKAGE} 2> /dev/null | awk -F'.' '{print $2}' | grep -v '=' | grep -v '@global\[0\]' | uniq | sort) + local _entry + for entry in ${entries} + do + local status + status=$(_get_uci_value ${PACKAGE}.${entry}.enabled) || _exit 1 + if [ ${status} -eq 1 ] + then + _enable_wifi_schedule ${entry} + fi + done +} + +check_cron_status() +{ + local global_enabled + global_enabled=$(_get_uci_value ${GLOBAL}.enabled) || _exit 1 + _rm_cron_script "${SCRIPT}" + if [ ${global_enabled} -eq 1 ]; then + _create_cron_entries + fi +} + +disable_wifi() +{ + _rm_cron_script "${SCRIPT} recheck" + /sbin/wifi down + local unload_modules + unload_modules=$(_get_uci_value_raw ${GLOBAL}.unload_modules) || _exit 1 + if [[ "${unload_modules}" == "1" ]]; then + _unload_modules + fi +} + +soft_disable_wifi() +{ + local _disable_wifi=1 + local iwinfo=/usr/bin/iwinfo + if [ ! -e ${iwinfo} ]; then + _log "${iwinfo} not available, skipping" + return 1 + fi + + # check if no stations are associated + local _if + for _if in $(_get_wireless_interfaces) + do + output=$(${iwinfo} ${_if} assoclist) + if [[ "$output" != "No station connected" ]] + then + _disable_wifi=0 + local stations=$(echo ${output}| grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}' | tr '\n' ' ') + _log "Station(s) ${stations}associated on ${_if}" + fi + done + + if [ ${_disable_wifi} -eq 1 ]; then + _log "No stations associated, disable wifi." + disable_wifi + else + _log "Could not disable wifi due to associated stations, retrying..." + local recheck_interval=$(_get_uci_value ${GLOBAL}.recheck_interval) + _add_cron_script "*/${recheck_interval} * * * * ${SCRIPT} recheck" + fi +} + +enable_wifi() +{ + _rm_cron_script "${SCRIPT} recheck" + local unload_modules + unload_modules=$(_get_uci_value_raw ${GLOBAL}.unload_modules) || _exit 1 + if [[ "${unload_modules}" == "1" ]]; then + _load_modules + fi + /sbin/wifi +} + +usage() +{ + echo "" + echo "$0 cron|start|stop|forcestop|recheck|getmodules|savemodules|help" + echo "" + echo " UCI Config File: /etc/config/${PACKAGE}" + echo "" + echo " cron: Create cronjob entries." + echo " start: Start wifi." + echo " stop: Stop wifi gracefully, i.e. check if there are stations associated and if so keep retrying." + echo " forcestop: Stop wifi immediately." + echo " recheck: Recheck if wifi can be disabled now." + echo " getmodules: Returns a list of modules used by the wireless driver(s)" + echo " savemodules: Saves a list of automatic determined modules to UCI" + echo " help: This description." + echo "" +} + +############################################################################### +# MAIN +############################################################################### +LOGGING=$(_get_uci_value ${GLOBAL}.logging) || _exit 1 +_log ${SCRIPT} $1 $2 +lock ${LOCKFILE} + +case "$1" in + cron) check_cron_status ;; + start) enable_wifi ;; + forcestop) disable_wifi ;; + stop) soft_disable_wifi ;; + recheck) soft_disable_wifi ;; + getmodules) get_module_list ;; + savemodules) save_module_list_uci ;; + help|--help|-h|*) usage ;; +esac + +_exit 0