Turns WiFi on and off according to a schedule Signed-off-by: Nils Koenig <openwrt@newk.it>lilik-openwrt-22.03
@ -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. |
@ -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 <openwrt@newk.it> | |||
include $(TOPDIR)/rules.mk | |||
PKG_NAME:=wifischedule | |||
PKG_VERSION:=1 | |||
PKG_RELEASE:=1 | |||
PKG_LICENSE:=PRPL | |||
PKG_MAINTAINER:=Nils Koenig <openwrt@newk.it> | |||
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)) |
@ -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. | |||
``` |
@ -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' |
@ -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 <openwrt@newk.it> | |||
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 |