Monday, February 17, 2020

(#35) What's the Fun in That (Part Three) 

The Software   


Part one of the series "What's the Fun in That" constructed an elaborate electrical timer from a Raspberry Pi and an 8 Channel Relay board.

Here I'll quickly cover the software side.  There are a gazillion projects out there that will show you how to connect Relay Boards to the GPIO pins on a Raspberry Pi.  And, once connected, energzing the relays is simply a matter of driving the GPIO pins Low or High.

But here are a few code snippets that might help you along the way. We're doing this in Python. And we're using the RPi.GPIO library:


Import RPi.GPIO

    
import logging
import RPi.GPIO as GPIO
import time
           
class ChannelManager(object):
                             
    # ---------------------------------------------------------
    def __init__(self):
        self.socket_pin_assignments = {
            1: 2,
            2: 3,
            3: 4,
            4: 22,
            5: 10,
            6: 9,
            7: 27,
            8: 17}

   
 The "socket_pin_assignments" dictionary maps a socket number to it's connected GPIO pin number. The pin number depends on how we've wired up the relay to the Raspberry Pi.  If you choose different pins, or a smaller relay board then your values would be different.

Before going much farther, we set the library to use BCM style pin numbering and we tell the library we're going to be using the GPIO pins as outputs:


GPIO.setup()

    
try:
    GPIO.setmode(GPIO.BCM)
    for key, value in self.socket_pin_assignments.items():
        logging.info("Setting socket {} pin {} to 

                      GPIO Output".format(key, value))
        GPIO.setup(value, GPIO.OUT)
except Exception:
    logging.exception('Exception in setting up GPIO ports')

    


The Clapper!

When an "On" message comes in, this function is called:
    
def socket_on(self, socket_number, duration=900):
    if (socket_number <= 0 or socket_number > self.max_socket_num):
        logging.error('Invalid socket number {} passed to the on

                      command'.format(socket_number))
        return
            

    pin_number = self.socket_pin_assignments[socket_number]
    try:
        GPIO.output(pin_number, GPIO.LOW)
        self.socket_states[socket_number] = "on"
        self.socket_on_time[socket_number] = time.time()
        self.socket_on_max_duration[socket_number] = duration
    except Exception:
        logging.exception('Exception in setting socket {} 

                           pin {} to GPIO.LOW'
                          .format(socket_number, pin_number))
          


When an "Off" message arrives:

    
def socket_off(self, socket_number):
    if (socket_number <= 0 or socket_number > self.max_socket_num):
        logging.error('Invalid socket number {} passed to the off

                      command'.format(socket_number))
        return
            

    pin_number = self.socket_pin_assignments[socket_number]
    try:
        GPIO.output(pin_number, GPIO.HIGH)
        self.socket_states[socket_number] = "off"
        self.socket_on_time[socket_number] = 0.0
        self.socket_on_max_duration[socket_number] = 0
    except Exception:
        logging.exception('Exception in setting socket {} 

                           pin {} to GPIO.HIGH'
                          .format(socket_number, pin_number))
          

That's about all there is for 'the meat' of this software. In my code I also setup MQTT to send and receive messages (using paho-mqtt).

I use ZeroConf to locate the MQTT Broker dynamically (using zeroconf).




Meltdown Prevention

And I have a function that wakes up every five seconds to check if a socket has been left on for too long.  [ When sending an 'on' command, you also specify the maximum duration you want it left on. ]

            
def check_for_duration_exceeded(self):
  logging.debug('Checking for time exceeded on all on sockets')
  time_now = time.time()

  for socket_num, pin_number in 

            self.socket_pin_assignments.items():
  if self.socket_states[socket_num] == 'on':
     max_seconds_on = self.socket_on_max_duration[socket_num]
     total_seconds_on = time_now - self.socket_on_time[socket_num]
     if (total_seconds_on > max_seconds_on):
        logging.warning('Maximum On Time reached for 

                         socket {}. Sending off command'
                         .format(socket_num))
        self.socket_off(socket_num)

        

The MQTT Messaging

If nothing else, I'm committed to being Event Driven and Message Based. So at the core of the functionality are a handful of JSON formatted messages sent over MQTT.


Clap On!

To turn a socket on, send this message to the MQTT Broker:

   {
     "topic":"RELAY/RV8.1/CMD",
  
"datetime":"2020-02-13T13:19:16-07:00",
  
"id":"RV8.1",
  
"channel":3,
  
"command":"on",
  
"duration":3480
   }

Where Channel is the Socket Number [ 1..8 ], the command is "on" and the duration is the maximum number of seconds you want the socket left on.

Clap Off!

The off command is almost identical, with the duration parameter ignored.
   {
     "topic":"RELAY/RV8.1/CMD",
  
"datetime":"2020-02-13T13:21:21-07:00",
  
"id":"RV8.1",
  
"channel":7,
  
"command":"off",
  
"duration":0
   }

There are two more commands "allon" and "alloff" which affect all of the sockets with a single command.

The TPS Report

Lastly, the system publishes a status message, every minute with the current status of all sockets:

  {
     "topic":"RELAY/RV8.1/STATUS",
   "datetime":"2020-02-13T13:23:00-07:00",
  
"id":"RV8.1",
  
"states":[
         {
         "duration":0,
         "state":"off",
         "channel":1
         },
 
     {
         "duration":0,
         "state":"off",
         "channel":2
         },
     
{
         "duration":3480,
         "state":"on",
         "channel":3
         },
<etc...>
      {
         "duration":0,
         "state":"off",
         "channel":8
      }
    ]
  }





No comments :

Post a Comment