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 |