Added wifischedule 3507/head
authorNils Koenig <mail_openwrtdev@newk.it>
Sat, 12 Nov 2016 15:54:19 +0000 (16:54 +0100)
committerNils Koenig <mail_openwrtdev@newk.it>
Wed, 16 Nov 2016 01:25:35 +0000 (02:25 +0100)
Turns WiFi on and off according to a schedule

Signed-off-by: Nils Koenig <openwrt@newk.it>
net/wifischedule/LICENSE [new file with mode: 0644]
net/wifischedule/Makefile [new file with mode: 0644]
net/wifischedule/README.md [new file with mode: 0644]
net/wifischedule/net/etc/config/wifi_schedule [new file with mode: 0644]
net/wifischedule/net/usr/bin/wifi_schedule.sh [new file with mode: 0755]

diff --git a/net/wifischedule/LICENSE b/net/wifischedule/LICENSE
new file mode 100644 (file)
index 0000000..e313028
--- /dev/null
@@ -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 (file)
index 0000000..a9dd3f7
--- /dev/null
@@ -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))
diff --git a/net/wifischedule/README.md b/net/wifischedule/README.md
new file mode 100644 (file)
index 0000000..591abb1
--- /dev/null
@@ -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 (file)
index 0000000..946a1ff
--- /dev/null
@@ -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 (executable)
index 0000000..363f95d
--- /dev/null
@@ -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