Script to invalidate all switch(es) plannings

Python and python framework

Moderator: leecollings

Post Reply
lost
Posts: 662
Joined: Thursday 10 November 2016 9:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Script to invalidate all switch(es) plannings

Post by lost »

Hello,

There's IMO a missing feature in Domoticz: Ability to invalidate plannings by device. Combined with the fact that 1 planning in a list = 1 timer idx, there may be lot's to change.

I use this kind of stuff to swith off heaters & invalidate plannings when I use my chimney: In fact, heaters will regulate and heat less when another source of heat is on but I wanted them not to witch on at all as i's really a waste of eletric power.

So this python script (in domoticz/scripts, named scheduleMod.py) that lists all schedules from domoticz, filter those that must be invalidated by device name (wildcards accepted, so if you have a naming prefix convention like heatersXXX, you can invalidate all schedules for them using heater* as a name), and change planning active settings:

Code: Select all

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Schedule activation modification for device (with wildcards support)
# Date for state change, if an 'hour' parameter is given, is stored in
# a user var than can be checked by a lua time script that'll reverse current state...
#
# Changelog : 17/12/2016, YL, 1st version.

import getopt
import logging
import json
import sys
import requests
import datetime
from   fnmatch  import fnmatch

#####################
# EDITABLE SETTINGS #
#####################

logLevel='INFO' # DEBUG / INFO

# Domoticz json API url
dmtJurl         = 'http://127.0.0.1:8080/json.htm?'

# Command parameters in json format (only change if API change!)
dmtJsonGetSched = {"type":"schedules"}
dmtJsonActTimer = {"type":"command", "param":"enabledisabletimer", "idx":"999"}
dmtJsonUpdVar   = {"type":"command", "param":"updateuservariable",
                   "vname":"reverseSchedule", "vtype":2, "vvalue":0}
                   
# Dmoticz time format on LUA side:
dmtLuaTimeFmt   = "%Y-%m-%d %H:%M:%S"
                   
#####################
def usage():
    """
    Display usage
    """
		
    sys.stderr.write( "Usage: ScheduleMod.py [-h] [-s<on/off>] [-t<timeInMn>] -n<device>\n")
    sys.stderr.write( "       If no 's', current state is reversed.\n")
    sys.stderr.write( "       If    't', a time for state reversal (mn) is stored for domoticz.\n")

#####################
def dmtJsonApi(url, jsonApiCmd, logger):
    """
    Send Domoticz json command
    """
		
    try:
        # Connect to Domoticz via JSON API and send data
        dmtRget=requests.get(url, params=jsonApiCmd)
    except requests.exceptions.RequestException as dmtErr:
        logger.log(logging.ERROR, "Unable to connect with URL=%s \nGet requests error %s" % (dmtRget.url, dmtErr))
    finally:
        logger.log(logging.DEBUG, "Sent data: [%s]" % (dmtRget.url))
		
	return dmtRget.json()

#####################
def main(argv):
    """
    Main
    """
    
    logging.basicConfig()
    logger = logging.getLogger()
    handler = logging.StreamHandler(sys.stdout)

    # Checks the parameters
    try:
        opts, args = getopt.getopt(argv, "h:s:n:t:",["help","state","name","time"])
    except getopt.GetoptError:
        usage()
        sys.exit(2)
    
    # Defaults    
    SchedActivate = 'reverse'
    devName       = 'nil'
    reverseTimeMn = 0
    
    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-s", "--state" ):
            SchedActivate=('disabletimer','enabletimer')[a == 'on']   
        if o in ("-n", "--name" ):
            devName=a
        if o in ("-t", "--time" ):
            reverseTimeMn=int(a)	
    
    # Configure the logger
    handler.setFormatter(
        logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
    logger.setLevel(logLevel)
	
    logger.log(logging.INFO, "Device(s)=%s, update schedules to '%s', reverse after %dmn." %(devName, SchedActivate, reverseTimeMn))
    
    # Get all schedules
    schedules = dmtJsonApi(dmtJurl, dmtJsonGetSched, logger)
    #logger.log(logging.DEBUG, "Schedules: [%s]" % (schedules))

    # Modify active setting for dev name...
    schedNb=len(schedules['result'])
    logger.log(logging.DEBUG, "Found %d schedules, extract those for %s" % (schedNb, devName))
    
    # Change schedule(s) for device(s) matching name (wildcard support): 
    for sched in schedules['result']:
        if (fnmatch(sched['DevName'], devName)):
            logger.log(logging.DEBUG, 'Found : %s , state=%s, idx=%d',
                       sched['DevName'],
                       sched['Active'],
                       sched['TimerID'])
            
            curState = ('disabletimer','enabletimer')[sched['Active'] == 'true']
            revState = ('enabletimer','disabletimer')[sched['Active'] == 'true']
            
            if (curState != SchedActivate):
                # Update domoticz request parameters
                dmtJsonActTimer['param'] = (SchedActivate, revState)[SchedActivate == 'reverse']
                dmtJsonActTimer['idx']   = sched['TimerID']
                
                # Send request...
                dmtJsonApi(dmtJurl, dmtJsonActTimer, logger)
    
    # Set user var to current date + time given for reversal (in mn), if any...
    if (reverseTimeMn != 0):
        revDate = datetime.datetime.now() + datetime.timedelta(minutes=reverseTimeMn)
        revDate = devName + ';' + revDate.strftime(dmtLuaTimeFmt)
        logger.log(logging.DEBUG, "Reverse time stored for Domoticz : %s", revDate)
        dmtJsonUpdVar['vvalue'] = revDate
        dmtJsonApi(dmtJurl, dmtJsonUpdVar, logger)
    else:
        dmtJsonUpdVar['vvalue'] = 'nil'
        dmtJsonApi(dmtJurl, dmtJsonUpdVar, logger)
    
    # Happy ending!
    logger.log(logging.INFO, "DONE !")
    
if __name__ == "__main__":
    main(sys.argv[1:])
As you may see, there's also the update of a user variable string to initiate as 'nil': reverseSchedule
It will store data in the form "device name";"date in same format as LUA".

Reason: Be able to reverse planning activations automagically, from a lua time script, when stored date is reached. This reverse date is set according to a -tTimeInMn parameter, if one is given.

This is done by this script (in domoticz/scripts/lua):

Code: Select all

-- Do stop action for reverseSchedule posted user variable (device ; date_to_reverse_schedule)

commandArray = {}

dev_date = uservariables['reverseSchedule']
dev, sT2 = dev_date:match("([^,]+);([^,]+)")

if (dev_date ~= 'nil') then
  year    = string.sub(sT2, 1, 4)
  month   = string.sub(sT2, 6, 7)
  day     = string.sub(sT2, 9, 10)
  hour    = string.sub(sT2, 12, 13)
  minutes = string.sub(sT2, 15, 16)
  seconds = string.sub(sT2, 18, 19)
  
  -- print("sT2:"..sT2)
  -- print("y:"..year.." m:"..month.." d:"..day.." h:"..hour.." m:"..minutes.." s:"..seconds)
  
  t2 = os.time{year=year, month=month, day=day, hour=hour, min=minutes, sec=seconds}
  t1 = os.time()
  
  -- print("t1:"..t1)
  -- print("t2:"..t2)
  
  diffTime = os.difftime(t2, t1)
  if (diffTime <= 0) then
    print("Restart schedule(s) for "..dev)
	commandArray['Heaters Temp Stop'] = 'Off'
    commandArray['Variable:reverseSchedule'] = 'nil'
	--os.execute("/home/pi/domoticz/scripts/scheduleMod.py -son -n"..dev)
  end
end

return commandArray
This makes use of a selector virtual switch (Heaters Temp Stop) that controls everything, giving in my case options to stop heaters for 3/6/9/12h, with schedules back on automatically. This is the one updated hereupper (line for direct update via previous script commented because I wanted current state coherent with switch position)

As it's impossible to give a python in a script:// from a switch action, there's a batch script to encapsulate python script (heatersStopTime.sh):

Code: Select all

#!/bin/bash

if [ ${1} -ne 0 ];
then
  /home/pi/domoticz/scripts/scheduleMod.py -nHeaters* -soff -t${1}
  setLevel=5
else
  /home/pi/domoticz/scripts/scheduleMod.py -nHeaters* -son
  setLevel=15
fi

curl "http://127.0.0.1:8080/json.htm?type=command&param=switchlight&idx=20&switchcmd=Set%20Level&level=${setLevel}"
curl "http://127.0.0.1:8080/json.htm?type=command&param=switchlight&idx=5&switchcmd=Set%20Level&level=${setLevel}"
curl "http://127.0.0.1:8080/json.htm?type=command&param=switchlight&idx=7&switchcmd=Set%20Level&level=${setLevel}"
curl "http://127.0.0.1:8080/json.htm?type=command&param=switchlight&idx=70&switchcmd=Set%20Level&level=${setLevel}"
curl "http://127.0.0.1:8080/json.htm?type=command&param=switchlight&idx=11&switchcmd=Set%20Level&level=${setLevel}"
curl "http://127.0.0.1:8080/json.htm?type=command&param=switchlight&idx=24&switchcmd=Set%20Level&level=${setLevel}"
All devices with name prefix Heater will be concerned.

Last 'curl' lines are heater control specific (qubino modules seen as dimmers that gives up to 6 orders to heater). Don't forget to update /home/pi pathes in all scripts if your user is not pi (as usual on raspberry pi), as well as idx/curl lines if you have exactly the same use-case as mine.

After this just calling this script in the selector switch actions like this (for 3h00=180mn; use 0 for off action):
script:///home/pi/domoticz/scripts/heatersStopTime.sh 180

The huge limitation of this first version is only one "device;date for reverse" is stored by python script and can be used by lua side: So it's only one group of devices at a time that can have schedules invalidated and valid again after a time automagically. Feel free to update this, at the moment I have no need for this + I'm an early beginner in python and lua + not so good at bash! Indeed, having the possibility to do everything in python would have made it easier as well! There's the possibility to compile domoticz with python support but I did not compiled last stable myself.

So one use using -t parameter at a time.

Well, that's all folks. This may be useful to others even if solving this schedules group activations hassle directly from domoticz core would be nice!
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest