Monday, July 14, 2014

The engine in an nut shell: the main logic

The main logic of the engine has already been implemented and tested. What is now missing is the management of a soil moisture sensor and the management of the weather forecast.
Implementing the first feature is not so hard, once we have the value of the humidity of the soil we just need to add an IF statement to enable/disable the irrigation valves.
Implementing the second feature (weather forecast) is a bit more tricky: we need to collect the values of pressure and humidity and use them to understand if it's going to rain.

The code is ready to manage both the features in a specific function named 'evaluateTurningOnOutput' that now always returns TRUE. So when we will develop those functionalities, we will not change the engine code a part from this function.

Now let's start with the code explanation of wnw_engine.py that is a bit more complex than the sketch, so we will not follow the script line by line.


First of all the imports. We need to include our two new classes (the database and the bridge):

import sys
import sys
import os
import time
import logging
from time import sleep
import subprocess

#Import class to manage the database
import wnw_database as wnwDB

#Import class to manage the bridge
#(communication with the ATmega via mailbox)
import wnw_bridge as wnwBridge

Then I declare few global variables to manage the DB and bridge connections and two main entities: _STAY_IN_THE_LOOP_ a boolean to indicate if we need to leave the engine because of a problem and _WATERING_PLAN_ an array to store the plan of irrigations:

#
#Global variables
#
#The connection to the database
theDB = None
#The connection to the bridge
theBridge = None
#A boolean to break the main loop in case of error
_STAY_IN_THE_LOOP_ = False
#The number of supported outputs
_OUTPUTS_NUMBER_ = 0
#The irrigation plan
_WATERING_PLAN_ = None

Finally we declare a set of constant, including the ones used to say to the engine that we don't support weather forecast and soil moisture sensor (WEATHER_FORECAST = False and SOIL_MOISTURE_SENSOR = False):

#
#Constant definitions
#
#Log file name, absolute path
LOG_FILENAME = '/mnt/sda1/wnw/log/engine.log'
#Soil maisture sensor enabling
SOIL_MOISTURE_SENSOR = False
#Soil moisture threshold use 
#to decide if the soil is dry or wet
SOIL_MOISTURE_THRESHOLD = 1000
#Weather forecast feature enabling
WEATHER_FORECAST = False


Then we have the definition of all the used functions that we can jump and analize later on. The process followed by the script is the following:
  1. Connect to the database and the bridge
  2. Run a startup procedure to initialize anything
  3. Start a main loop:
    1. Store the values of the sensors into the database
    2. For each output:
      1. Get the current status
      2. Evaluate the expected status
      3. If expected status is OFF (and the current status is ON) => turn off the output
      4. If expected status is ON and the soil moisture is low and the weather is high (and the current status is OFF) => turn on the output
      5. If expected status is ON and the specific irrigation has been forced by the user (and the current status is OFF) => turn on the output
      6. In any other case, just turn off the output
    3. Collect all the desiderata output and send a unique request to the lower layer (the sketch) via bridge
    4. Wait for a response from the lower layer and in case it's egual to the request, store the output status into the DB
    5. Sleep 10 seconds and then restart the main loop
  4. In case of any error, close the connection to the database and the bridge and exit

Following the source code:

1 => Connect to the database and the bridge

##################
# MAIN 
##################

try:
 logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,format='%(asctime)s %(levelname)s %(message)s', datefmt='%m/%d/%Y %H:%M:%S')
 logging.info('\n\n')
 logging.info('    <-- data-blogger-escaped---="" data-blogger-escaped-engine="" data-blogger-escaped-started="" data-blogger-escaped-wnw="">')
 
 #
 # Connect to the database
 # Connect to the bridge
 #
 theDB = wnwDB.WnWDatabaseConnection()
 logging.info('Connecting to the DB...')
 if theDB.init():
  logging.info('DB connection established')
 else:
  logging.error('DB connection not established: %s' % theDB.getErrorMessage())
  
 theBridge = wnwBridge.WnWBridge()
 logging.info('Connecting to the Bridge...')
 if theBridge.init():
  logging.info('Bridge connection established')
 else:
  logging.error('Bridge connection not established: %s' % theBridge.getErrorMessage())

2 => Run a startup procedure to initialize anything

 #
 # Startup process
 #
 logging.info('Calling the startup procedure...')
 _STAY_IN_THE_LOOP_ = startup()

3 => Start a main loop:

 #
 # Main loop
 #
 if _STAY_IN_THE_LOOP_:
  logging.info('Running the main loop...')
 else:
  logging.warning('Nothing to do. Exiting...')
  
 while _STAY_IN_THE_LOOP_:

3 => 1 => Store the values of the sensors into the database

  # Get time to calculate loop duration
  _loopStartTime = int(time.time() * 1000)
  
  # Store output status
  storeSensorsValues(theBridge.getValue('temperature'),theBridge.getValue('humidity'),theBridge.getValue('pressure'),theBridge.getValue('soilMoisture'),theBridge.getValue('luminosity'),)

3 => 2 => For each output

  for _output in _outputRange:
   logging.debug('Evaluating output %i' % _output)
   # Get the current status of the actuator

3 => 2 => 1 => Get the current status and 2 => Evaluate the expected status

   # Get the current status of the actuator
   _currentStatus = getCurrentStatus(_output)
      
   # Check the expected status of this actuator
   _expectedStatus = getExpectedStatus(_output, _nowInSeconds)

3 => 2 => 3 => If expected status is OFF

          if _expectedStatus == 0:
    if _currentStatus != 0:
     logging.debug('Turning OFF output %i ' % _output)
     storeAction(_output, wnwDB.ACTION_TURNOFF_AS_PER_WATERING_PLAN)
    _request += '0'

3 => 2 => 4 => If expected status is ON and the soil moisture is low and the weather is high

   elif _expectedStatus == 1:
    if evaluateTurningOnOutput(_output) == True:
     if _currentStatus != 1:
      logging.debug('Turning ON output %i ' % _output)
      storeAction(_output, wnwDB.ACTION_TURNON_AS_PER_WATERING_PLAN)
     _request += '1'
    else:
     if _currentStatus != 0:
      logging.debug('Turning OFF output %i ' % _output)
      storeAction(_output, wnwDB.ACTION_TURNOFF_AFTER_EVALUATION)
     _request += '0'

3 => 2 => 5 => If expected status is ON and the specific irrigation has been forced by the user

   elif _expectedStatus == 2:
    if _currentStatus != 1:
     logging.debug('Turning ON output %i [FORCED]' % _output)
     storeAction(_output, wnwDB.ACTION_TURNON_FORCED)
    _request += '1'

3 => 2 => 6 => In any other case, just turn off the output

    
   else:
    logging.warning('Unsupported expected status for output %i' % _output);
    logging.warning('The output will be turned off!')
    storeAction(_output, wnwDB.ACTION_TURNOFF_DEFAULT)
    _request += '0'

3 => 3 => Collect all the desiderata output and send a unique request

  # Sending the request to change the output    
  logging.debug('Sending output request %s' % _request)
  _retValue = theBridge.putValue('outputRequest', _request)
  if _retValue != _request:
   logging.error('putValue not working as expected')

3 => 4 => Wait for a response from the lower layer

  _returnValue = waitForOutputsResponse(_OUTPUTS_NUMBER_)
  if _returnValue == None:
   logging.error('Output response not present')
  else:
   logging.debug('outputResponse = %s' % _returnValue)

   if _returnValue == _request:
    logging.debug('Output change request correctly processed')
   else:
    logging.error("The output change request has not been correctly processed (request '%s', response '%s'" % (_request,_returnValue))
  
   # Store output status
   storeOutputStatus(_returnValue)

3 => 5 => Sleep 10 seconds

  # Get time to calculate loop duration
  logging.debug('Sleep...')
  _loopStopTime = int(time.time() * 1000)
  _durationInMillis = _loopStopTime - _loopStartTime
  _to10Seconds = (10000.0 - float(_durationInMillis)) / 1000.0
  if _to10Seconds > 0.0:
   sleep(_to10Seconds)

4 => In case of any error, close the connection to the database and the bridge and exit

except Exception as e:

 logging.error("Exception %s:" % e.args[0])
 sys.exit(1)

finally:

 if theDB:
  logging.info('Closing DB connection...')
  theDB.close()
 if theBridge:
  logging.info('Closing BRIDGE connection...')
  theBridge.close()

No comments:

Post a Comment