<< return to Vizycam.com

User Tools

Site Tools


wiki:test

utilities

pinouts

ssh

birdfeeder

stage

hello

vizypowerboard API documentation

Module vizypowerboard

This module is used with the Vizy Power Board, which is a printed circuit board that plugs into the Raspberry Pi I/O connector and is included with the Vizy camera. More information about Vizy can be found here.

Expand source code
# Copyright (C) 2021 Charmed Labs LLC
#
# begin license header
#
# This file is part of Vizy Software. 
#
# This source code is provided under the terms of the
# GNU General Public License v2 (http://www.gnu.org/licenses/gpl-2.0.html).
# Those wishing to use this source code, software and/or
# technologies under different licensing terms should contact us at
# [email protected]. 
#
# end license header
"""
This module is used with the Vizy Power Board, which is a printed circuit 
board that plugs into the Raspberry Pi I/O connector and is included
with the Vizy camera.  More information 
about Vizy can be found [here](https://vizycamcam.com).
"""    
import smbus
import RPi.GPIO as GPIO
import time
import datetime
import os

FILE_VERSION = 1
COMPAT_HW_VERSION = [3, 0]

ERROR_GENERIC = 255
ERROR_BUSY = 254
ERROR_READ_ONLY = 253
ERROR_NOT_SUPPORTED = 252

EXEC_OFFSET = 0xc0
EXEC_RW = 0x80
EXEC_WRITE = 0x80
EXEC_NVCONFIG = 1
EXEC_AD = 2
EXEC_DA = 3
EXEC_RTC = 16
EXEC_RTC_CALIBRATE = 17

IO_MODE_INPUT = 0
"""Used with `VizyPowerBoard.io_set_mode()`."""
IO_MODE_OUTPUT = 0x80 
"""Used with `VizyPowerBoard.io_set_mode()`."""
IO_MODE_HIGH_CURRENT = IO_MODE_OUTPUT | 0x40
"""Used with `VizyPowerBoard.io_set_mode()`."""

DIPSWITCH_EXT_BUTTON = 0x01
"""Used with `VizyPowerBoard.dip_switches()`."""
DIPSWITCH_MUTE_BUZZER = 0x02
"""Used with `VizyPowerBoard.dip_switches()`."""
DIPSWITCH_NO_BG_LED = 0x04
"""Used with `VizyPowerBoard.dip_switches()`."""
DIPSWITCH_POWER_DEFAULT_OFF = 0x00
"""Used with `VizyPowerBoard.dip_switches()`."""
DIPSWITCH_POWER_DEFAULT_ON = 0x40
"""Used with `VizyPowerBoard.dip_switches()`."""
DIPSWITCH_POWER_SWITCH = 0x80
"""Used with `VizyPowerBoard.dip_switches()`."""
DIPSWITCH_POWER_PLUG = 0xc0
"""Used with `VizyPowerBoard.dip_switches()`."""

POWER_ON_SOURCE_ALARM = 0x01
"""Used with `VizyPowerBoard.power_on_source()`."""
POWER_ON_SOURCE_POWER_BUTTON = 0x02
"""Used with `VizyPowerBoard.power_on_source()`."""
POWER_ON_SOURCE_12V = 0x03
"""Used with `VizyPowerBoard.power_on_source()`."""
POWER_ON_SOURCE_5V = 0x04
"""Used with `VizyPowerBoard.power_on_source()`."""

CHANNEL_VIN = 4
"""Used with `VizyPowerBoard.measure()`."""
CHANNEL_5V = 5
"""Used with `VizyPowerBoard.measure()`."""

class VizyPowerBoard:
    """
    This class may be
    instantiated by more than one process.  The vizy-powerd service
    instantiates this class and uses it to monitor and control things such 
    as power-off requests, CPU temperature, fan speed, etc.  
    User programs can also instantiate this class and
    use its methods simultaneously.
    """    
    def __init__(self, addr=0x14, bus=1):
        """
        Args:
          addr (integer, optional, default=0x14): I2C address of the board
          bus (integer, optional, default=1): the I2C bus number 

        """
        self.bus = smbus.SMBus(bus)
        self.addr = addr
        hwv = self.hw_version()
        if hwv!=COMPAT_HW_VERSION:
            raise RuntimeError("The hardware version of your Vizy Power Board (" + str(hwv[0])+'.'+str(hwv[1]) + ") is incompatible with this software file (" + str(COMPAT_HW_VERSION[0])+'.'+str(COMPAT_HW_VERSION[1]) + ").")

    @staticmethod
    def _bcd2decimal(bcd):
        tens = (bcd&0xf0)>>4
        ones = bcd&0x0f
        return tens*10 + ones

    @staticmethod
    def _decimal2bcd(dec):
        tens = int(dec/10)
        ones = dec%10
        return (tens<<4) | ones

    @staticmethod
    def _u_int8(i):
        i = round(i)
        if i>0xff:
            return 0xff
        if i<0:
            return 0
        return i

    @staticmethod
    def _int8(i):
        i = round(i)
        if i>0x7f:
            return 0x7f
        if i<-0x80:
            return 0x80
        if i<0:
            return 0x100+i
        return i

    @staticmethod
    def _uint16(i):
        i = round(i)
        if i>0xffff:
            return 0xffff
        if i<0:
            return 0
        return i

    def _status(self):
        return self.bus.read_i2c_block_data(self.addr, 0, 1)[0]

    def _wait_until_not_busy(self):
        while self._status()==ERROR_BUSY:
            time.sleep(0.001)

    def hw_version(self):
        """
        Returns the major and minor versions of the PCB as a 2-item list.
        """ 
        return self.bus.read_i2c_block_data(self.addr, 1, 2)

    def fw_version(self):
        """
        Returns the major, minor and build versions of the firmware as 
        a 3-item list.
        """ 
        return self.bus.read_i2c_block_data(self.addr, 3, 3)

    def resource_url(self):
        """
        Returns the url of a JSON file that contains information about 
        resources, such as the location of the latest version of this code, 
        latest firmware, etc.
        """
        chars = self.bus.read_i2c_block_data(self.addr, 6, 32)
        s = ''
        for c in chars:
            if c==0: # read up to the null character
                break
            s += chr(c)
        return s

    def uuid(self):
        """
        Returns a 16-byte unique ID that can be used an a unique
        ID for your Vizy camera.  This unique ID is stored on the Vizy Power
        Board and remains constant regardless of firmware upgrades, etc.
        """
        return self.bus.read_i2c_block_data(self.addr, 22, 16)

    def power_off_requested(self):
        """
        Returns `True` if Vizy's button is held down for more than 5 seconds
        indicating that the user wishes to initiate safe shutdown and power
        off.  Returns `False` otherwise.

        This is used by the vizy-powerd service. 
        """
        button = self.bus.read_i2c_block_data(self.addr, 38, 1)
        if button[0]==0x0f:
            return True
        else:
            return False

    def power_off(self, t=5000):
        """
        Powers Vizy off. The `t` argument specifies how long 
        to wait before turning off (specified in milliseconds).  The 
        vizy-powerd service calls this upon shutdown.
        """
        self.bus.write_i2c_block_data(self.addr, 38, [0x1f, int(t/100)])

    def power_on_alarm_date(self, datetime_=None):
        """
        If you wish to power off your Vizy and have it "wake up" at a
        specified time and date, call this method with the desired
        datetime object and initiate a shutdown. (e.g. `sudo shutdown now`).


        The code below tells Vizy to turn on on December 2, 2022, 11:18am.

            import vizypowerboard as vpb
            from datetime import datetime
            v = vpb.VizyPowerBoard()
            d=datetime(year=2022, month=12, day=2, hour=11, minute=18, second=0)
            v.power_on_alarm_date(d)

        Args:
          datetime_ (datetime, optional, default=None): `datetime` object that
            specifies the date/time to "wake up" (turn on).

        Returns:
          Calling without a datetime object returns a `datetime` object 
          reflecting the active alarm time.  If there is no active alarm,
          `None` is returned.
           
        Notes:
          * Once setting the alarm date, Vizy will retain it even if Vizy loses
          power for extended periods of time.
          * If the alarm expires while Vizy is on, Vizy will emit a buzzer tone
          and remain on.
          * If the alarm expires while Vizy is off (but plugged into and
          receiving power), Vizy will turn on.
          * If the alarm expires while Vizy is unplugged from (or not receiving)
          power, Vizy will turn on as soon as it receives power.  
        """
        if datetime_ is None:
            t = self.bus.read_i2c_block_data(self.addr, 41, 6)
            if t[5]==0:
                return None
            return datetime.datetime(year=self._bcd2decimal(t[5])+2016, month=self._bcd2decimal(t[4]), day=self._bcd2decimal(t[3]), hour=self._bcd2decimal(t[2]), minute=self._bcd2decimal(t[1]), second=self._bcd2decimal(t[0]))
        t = [self._decimal2bcd(datetime_.second), self._decimal2bcd(datetime_.minute), self._decimal2bcd(datetime_.hour), self._decimal2bcd(datetime_.day), self._decimal2bcd(datetime_.month), self._decimal2bcd(datetime_.year-2016)]
        self.bus.write_i2c_block_data(self.addr, 41, t)
    

    def power_on_alarm_seconds(self, seconds=None):
        """
        Allows you to specify a power on alarm in seconds in the future. 
        For example, if you wish for Vizy to turn back on in 5 minutes, you
        would call `power_on_alarm_seconds(300)` and then initiate a shutdown.
        See `VizyPowerBoard.power_on_alarm_date()` for more information about the power on 
        alarm.

        Args:
          seconds (integer, optional, default=None): Number of seconds in the
            future you wish Vizy to turn on in.

        Returns:
          Calling this method without arguments returns the number of seconds
          until the alarm expires.  If no alarm is pending, `None` is returned.
        """
        if seconds is None:
            pod = self.power_on_alarm_date()
            if pod is None:
                return None
            diff = pod - self.rtc()
            return diff.days*86400+diff.seconds 
        # Add seconds to current time and set power on alarm    
        self.power_on_alarm_date(self.rtc()+datetime.timedelta(seconds=seconds))

    def power_on_source(self):
        """
        Returns the source of what turned on Vizy for the current power cycle.
        It is one of either:

        * POWER_ON_SOURCE_ALARM, indicates that Vizy was powered on
        by the power on alarm expiring.  See power_on_alarm_date() and
        power_on_alarm_seconds().
        * POWER_ON_SOURCE_POWER_BUTTON, indicates that Vizy was powered 
        on by someone pressing the button.
        * POWER_ON_SOURCE_12V = indicates that Vizy was powered on
        by power being applied to 12V power input.  This only applies if the
        dip switch power mode allows powering on by plugging in power via the
        12V power input.
        * POWER_ON_SOURCE_5V, indicates that Vizy was powered on by applying
        power to the Raspberry Pi's USB-C power input.
        """
        source = self.bus.read_i2c_block_data(self.addr, 40, 1)[0]
        return source

    def button(self):
        """
        Returns `True` if the button is being pressed currently, `False` otherwise.
        """
        button = self.bus.read_i2c_block_data(self.addr, 47, 1)
        if button[0]&0x02:
            return True
        else:
            return False

    def button_pressed(self):
        """
        Returns `True` if the button was pressed within the last 5 seconds,
        `False` otherwise.  This is useful if the polling is intermittant or
        slow, as button presses are not missed (as long as you check at least
        every 5 seconds!)
        """
        button = self.bus.read_i2c_block_data(self.addr, 47, 1)
        if button[0]&0x01:
            # Reset bit
            self.bus.write_i2c_block_data(self.addr, 47, [0])
            return True
        else:
            return False

    def vcc12(self, state=None):
        """
        If `state` is `True`, the 12V output on Vizy's I/O connector (pin 2) will be enabled and output 12V.  If `state` is `False`, the 12V output
        will be disabled.  Calling without arguments returns its current state.
        """ 
        config = self.bus.read_i2c_block_data(self.addr, 48, 1)
        if state is None:
            return True if config&0x01 else False
        if state:
            config[0] |= 0x01
        else:
            config[0] &= ~0x01

        self.bus.write_i2c_block_data(self.addr, 48, config)

    def vcc5(self, state=None):
        """
        If `state` is `True`, the 5V output on Vizy's I/O connector (pin 3) will
        be enabled and output 5V.  If `state` is `False`, the 5V output will be
        disabled.  Calling without arguments returns its current state.
        """ 
        if state is None:
            return True if config&0x02 else False
        if state:
            config[0] |= 0x02
        else:
            config[0] &= ~0x02

        self.bus.write_i2c_block_data(self.addr, 48, config)

    def led(self, r=0, g=0, b=0, flashes=0, repeat=False, atten=255, on=100, off=100, pause=200):
        """
        Controls the RGB LED in one of several modes:

        * **Continuous**: just setting `r`, `g`, and `b` will set set the LED color
        and turn it on continuously.  `r`, `g`, and `b` values range between 0 
        and  255.

                led(255, 0, 0)   # turn on LED, red color
                led(255, 255, 0) # turn on LED, yellow color
                led(0, 0, 255)   # turn on LED, blue color
                led(0, 0, 0)     # turn off LED

        * **Flashes**: setting the `flashes` value to a non-zero value will
        cause the LED to flash the indicated number of times.  You can also
        specify the `on` and `off` arguments to indicate the amount of time 
        the LED is on and off for each flash (specified in milliseconds).

                led(0, 0, 255, 3)  # flash blue 3 times (then stop)
                led(0, 0, 255, 3, on=500, off=500)  # flash blue 3 times, much more slowly

        * **Repeated flashes**: setting the `repeat` argument to `True` 
        will cause the indicated flash pattern to repeat forever.  You can
        modify the pause time between flash sequences by setting `pause`
        (milliseconds).

                led(0, 0, 255, 3, True, pause=500)  # flash blue 3 times, pause, then repeat
                led(0, 0, 255, repeat=True, on=500, off=500)`  # flash blue forever

        * **Flashing with attenuation**: you can also set the `atten` 
        argument to make the LED to turn on and off slowly, like an
        incandescent light.  The value is the rate of change, so lower values
        cause the LED color to change more slowly. 

                led(0, 0, 255, repeat=True, atten=10, on=500, off=500) # flash blue forever, but turn on and turn off very slowly
        """
        on = self._u_int8(on/10)
        off = self._u_int8(off/10)
        pause = self._u_int8(pause/10)
        if flashes==0:
            mode = 0
        if repeat:
            mode = 0x02
        else:
            mode = 0x01
        self.bus.write_i2c_block_data(self.addr, 49, [mode, self._u_int8(r), self._u_int8(g), self._u_int8(b),
            on, off, self._u_int8(flashes), pause, self._u_int8(atten)])

    def led_unicorn(self, speed=10):
        """
        This causes the LED to change color in succession: red, orange, yellow, 
        green, cyan, blue, violet and then repeat again.  The `speed` argument 
        ranges between 0 and 10.  For example, a `speed` of 0 causes the color 
        to change once every couple of seconds.  A `speed` of 10 causes the color to change about 6 times per second.
        """
        if speed>10:
            speed = 10
        elif speed<0:
            speed = 0

        on = self._u_int8(10 + (10-speed)*140/10)
        atten = self._u_int8(3 + speed*47/10)    
        self.bus.write_i2c_block_data(self.addr, 49, [0x08, 0, 0, 0, on, 0, 0, 0, atten])

    def led_background(self, r=-1, g=-1, b=-1):
        """
        The "background" LED color is the color of the LED when the LED is 
        turned "off".  It is used by system programs such as vizy-powerd to
        indicate Vizy's system state such as, booting (yellow), finished
        booting (green), running server (blue), etc.  Note, the background
        color does not influence the LED colors set by calls to led().

        Calling led_background() without arguments returns the current 
        background color r, g, and b values in a list. 

            led_background(48, 48, 0)  # set background color to yellow
            led(0, 255, 0)  # turn on LED, green (as expected)
            led(0, 0, 0)  # turn LED off, and restore background color (yellow as set previously)    
        """
        if r==-1:
            return self.bus.read_i2c_block_data(self.addr, 58, 3)
        self.bus.write_i2c_block_data(self.addr, 58, [self._u_int8(r), self._u_int8(g), self._u_int8(b)])
 

    def buzzer(self, freq, on=250, off=250, count=1, shift=0):
        """
        Emit tones through the buzzer.  The `freq` argument sets the frequency 
        of the tone in Hz and the `on` argument sets the length of the tone in
        milliseconds.  

        If you wish to emit more than 1 tone, you can set the
        `count` argument to the desired number.  

        The `off` argument sets 
        the pause between tones in milliseconds.  The `shift` argument is a
        value ranging between -128 and 127 that causes the tone's frequency to
        raise if `shift` is greater than 0, or descend if `shift` is less 
        than 0.

            buzzer(2000, 500) # emit a 2000Hz tone for 500ms
            buzzer(1000, count=3) # emit a 1000Hz tone 3 times   
            buzzer(1000, 500, 100, 3) # emit a longer 1000Hz tone 3 times
            buzzer(500, 250, 0, 10, 50) # emit 10 warbling tones like a siren
        """
        freq = self._uint16(freq)
        f0 = freq&0xff
        f1 = (freq>>8)&0xff
        self.bus.write_i2c_block_data(self.addr, 61, [0, f0, f1, self._u_int8(on/10), self._u_int8(off/10), 
            self._u_int8(count), self._int8(shift)])


    def io_set_mode(self, bit, mode=None):
        """
        Sets or gets the io mode of the given bit.  The `bit` argument ranges
        between  0 and 3 and corresponds to pins 4 through 7 on Vizy's IO
        connector. Calling this method with no mode argument returns the mode
        of the given bit, otherwise, the `mode` argument can be one of 
        the following:

        * IO_MODE_INPUT, sets the bit to high impedance input mode with 
        a weak pull-up resistor to 3.3V.  The input voltage can range between
        0 and Vin where Vin is the supply voltage.  Voltages lower than 1V 
        read as logic 0 via `VizyPowerBoard.io_bits()`.  Voltages higher 
        than 1V are read as logic 1.
        * IO_MODE_OUTPUT, sets the bit to output mode.  If the bit is set to
        logic 0 via `VizyPowerBoard.io_bits()`, the output voltage is 0V.  
        If the bit is set to logic 1, the output voltage is 3.3V.  In this mode, each bit can source and sink 5mA.  
        * IO_MODE_HIGH_CURRENT, sets the bit to a special high current mode
        that allows the bit to sink as much as 1000mA, when the bit 
        set to logic 0 via `VizyPowerBoard.io_bits()`.  Otherwise, this mode
        behaves exactly as IO_MODE_OUTPUT.    
        """
        if mode is None:
            return self.bus.read_i2c_block_data(self.addr, 68+bit, 1)[0]
        if bit==2:
            # set GPIO14 (UART TXD) as input so it doesn't conflict
            GPIO.setwarnings(False)
            GPIO.setmode(GPIO.BOARD)
            GPIO.setup(8, GPIO.IN)
        self.bus.write_i2c_block_data(self.addr, 68+bit, [self._u_int8(mode)])

    def io_bits(self, bits=None):
        """
        Sets or gets the logic state of the IO bits 0 through 3, corresponding
        to pins 4 through 7 on Vizy's IO connector.  The `bits` argument ranges
        between 0 and 15 as it is a binary representation of the logic state
        of the 4 IO bits.  If the `bits` argument isn't specified, the logic
        state of the 4 bits are returned.

            io_bits(1)   # set IO bit 0 to logic 1 and bits 1, 2, 3 to logic 0
            io_bits(10)  # set IO bits 1 and 3 to logic 1 and bits 0 and 2 to logic 0
            bits = io_bits()  # get logic state of IO bits
            io_bits(io_bits()|1)  # set IO bit 0 to logic 1, leave bits 1, 2, 3 unchanged. 
        """
        if bits is None:
            return self.bus.read_i2c_block_data(self.addr, 72, 1)
        self.bus.write_i2c_block_data(self.addr, 72, [bits])


    def ir_filter(self, state=None, duration=None):
        """
        Actuates the electro-mechanical IR-cut filter on Vizy's camera.  Vizy
        uses a CMOS sensor which is very sensitive to IR light.  IR light can 
        adversely affect color fidelity during the daytime so an IR-cut filter
        is used to block the IR light (`state`=True).  During nighttime IR light
        is typically used as a discreet method of illumination and the IR-cut
        filter is removed (`state`=False).  If the `state` argument is `True`,
        the filter is actuated in place (and will stay there) until another
        call is made with the state argument set to `False` (in which case the
        IR-cut filter will be removed).  

        The `duration` argument is optional and
        controls how long (in milliseconds) the actuation coil receives power.

        Calling this method without arguments returns the state of IR-cut
        filter.
        """
        if state is None:
            return True if self.bus.read_i2c_block_data(self.addr, 73, 1)[0] else False
        data = [1] if state else [0]
        if duration is not None:
            data.append(int(duration/10))
        self.bus.write_i2c_block_data(self.addr, 73, data)

    def fan(self, speed=None):
        """
        Set or get the fan speed.  The `speed` argument can range between 0 
        and 12 where 0 is off and 12 is maximum speed.  The fan speed 
        is typically regulated automatically by vizy-powerd.

        Calling this method without arguments returns the current fan speed.  
        """
        if speed is None:
            return self.bus.read_i2c_block_data(self.addr, 75, 1)[0]
        self.bus.write_i2c_block_data(self.addr, 75, [self._u_int8(speed)])
       

    def rtc(self, datetime_=None):
        """
        Set or get the real-time clock time/date.  The Vizy power board has a 
        battery-backed real-time clock that keeps track of time/date, power 
        alarms, etc. even while Vizy is receiving no power.  Passing in a
        datetime object sets the time/date.  

        Calling this method with no
        arguments returns a datetime object representing the current 
        date/time. 

        For example, the code below sets the date to December 2, 2020, 11:18am:

            from datetime import datetime
            import vizypowerboard as vpb
            v = vpb.VizyPowerBoard()
            t = datetime(year=2020, month=12, day=2, hour=11, minute=18, second=0)
            v.rtc(t)
        """
        if datetime_ is None:
            # Initiate RTC retrieval.
            self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC])
            # Wait until it's ready.
            self._wait_until_not_busy()

            t = self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 8)
            return datetime.datetime(year=self._bcd2decimal(t[7])+2016, month=self._bcd2decimal(t[6]), day=self._bcd2decimal(t[4]), hour=self._bcd2decimal(t[3]), minute=self._bcd2decimal(t[2]), second=self._bcd2decimal(t[1]))
 
        t = [EXEC_RTC|EXEC_WRITE, 0, self._decimal2bcd(datetime_.second), self._decimal2bcd(datetime_.minute), self._decimal2bcd(datetime_.hour), self._decimal2bcd(datetime_.day), 0, self._decimal2bcd(datetime_.month), self._decimal2bcd(datetime_.year-2016)]
        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, t)
        self._wait_until_not_busy()

    def dip_switches(self, val=None):
        """
        Set or get the (virtual) DIP switch state.  The DIP switches are a set 
        of "switches" that allow you to control Vizy's power-on or power-off
        behavior.  Once they are set, they will retain the setting even if 
        power is removed. 
        The switches are a set of values that can be ORed together:

        * DIPSWITCH_EXT_BUTTON, used to set external/remote power button, 
        e.g. with outdoor enclosure.  Default disabled.
        * DIPSWITCH_MUTE_BUZZER, used to mute the buzzer.  Default disabled.
        * DIPSWITCH_NO_BG_LED, used to disable the background LED, which is 
        normally set to yellow upon power up.  Default disabled.
        * DIPSWITCH_POWER_DEFAULT_OFF, if this power mode is set and you 
        plug in power via the 12V power input, Vizy will remain off by default
        until you press the button to power Vizy on.  And if power is
        interrupted while Vizy is on, *Vizy will turn off*.  If power is
        interrupted while Vizy is off, Vizy will remain off.  This is the
        default power mode.
        * DIPSWITCH_POWER_DEFAULT_ON, if this power mode is set and you 
        plug in power via the 12V power input, Vizy will turn on by default
        without pressing the button.  And if power is interrupted while Vizy
        is on, Vizy will reset, but remain on.  If power is interrupted while
        Vizy is off, *Vizy will turn on*.  Default disabled.
        * DIPSWITCH_POWER_SWITCH, if this power mode is set and you plug in
        power via the 12V power input, Vizy will remain off (as in
        DIPSWITCH_POWER_DEFAULT_OFF mode), unless power was 
        removed while Vizy was on.  In this case Vizy will turn on when you
        (re)apply power.  If power is interrupted while Vizy is off, Vizy 
        will remain off.  This behavior is similar to the behavior of a real
        power switch in that it retains the power "state" (on or off) and acts
        accordingly.  Default disabled.
        * DIPSWITCH_POWER_PLUG, if this power mode is set Vizy will remain
        powered on as long as it receives power through the 12V power plug, 
        and you will not be able to turn off Vizy via button or software as
        long as it's plugged in and receiving power.  Default disabled.
        
        For example: 

            dip_switches(DIPSWITCH_EXT_BUTTON | DIPSWITCH_POWER_SWITCH) # set external power button and power switch mode
        """
        if val is None:
            self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_NVCONFIG])
            # Wait until it's ready.
            self._wait_until_not_busy()
            return self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 1)[0]

        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_NVCONFIG|EXEC_WRITE, self._u_int8(val)])
        self._wait_until_not_busy()

    def rtc_adjust(self, val=None):
        """
        Set or get the real-time clock adjustment.  Vizy's real-time clock 
        crystal has an accuracy of 20ppm, which means that it can lose or gain 
        up to 20 seconds for every 1 million elapsed seconds.  Normally, this
        isn't an issue, but if Vizy spends a lengthy period of time (months)
        without Internet access, it could lose or gain minutes, which 
        depending on the application could be significant. The adjustment
        value can offset this inaccuracy.  The `val` argument can range 
        between -128 and 127 and has a multiplier of 2.170 ppm.  

        For example, 
        if the RTC is gaining 10 seconds every 1 million seconds, you would 
        call `rtc_adjust(-5)`.  If the RTC is losing 10 seconds every million
        seconds you would call `rtc_adjust(5)`.
        
        The adjustment value is retained by the real-time clock even when 
        Vizy's power is removed. 
        """
        if val is None:
            self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC_CALIBRATE])
            # Wait until it's ready.
            self._wait_until_not_busy()
            return self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 1)[0]

        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC_CALIBRATE|EXEC_WRITE, self._int8(val)])
        self._wait_until_not_busy()


    def measure(self, channel):
        """
        Get the voltage values of various channels.  The returned value is 
        the voltage measured (in Volts) of the given channel.  The `channel`
        argument can be one of the following:

        * CHANNEL_VIN, this channel measures the voltage present at the 12V
        power input.
        * CHANNEL_5V, this channel measures the voltage present at the 5V
        voltage rail provided to the Raspberry Pi.
        """  
        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_AD, self._u_int8(channel)])
        # Wait until it's ready.
        self._wait_until_not_busy()
        val = self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+2, 2)
        return (val[1]*0x100 + val[0])/1000


    def rtc_set_system_datetime(self, datetime_=None):
        """
        A convenience method that sets the system time/date based on the 
        real-time time/date.  This is called by vizy-powerd upon power-up. 
        """
        if os.geteuid()!=0:
            raise PermissionError("You need root permission to set the time/date.")
        if datetime_ is None:
            datetime_ = self.rtc()
        s = datetime_.isoformat()
        os.system(f"sudo date -s {s}")

Global variables

var CHANNEL_5V
var CHANNEL_VIN
var DIPSWITCH_EXT_BUTTON
var DIPSWITCH_MUTE_BUZZER
var DIPSWITCH_NO_BG_LED
var DIPSWITCH_POWER_DEFAULT_OFF
var DIPSWITCH_POWER_DEFAULT_ON
var DIPSWITCH_POWER_PLUG
var DIPSWITCH_POWER_SWITCH
var IO_MODE_HIGH_CURRENT
var IO_MODE_INPUT
var IO_MODE_OUTPUT
var POWER_ON_SOURCE_12V
var POWER_ON_SOURCE_5V
var POWER_ON_SOURCE_ALARM
var POWER_ON_SOURCE_POWER_BUTTON

Classes

class VizyPowerBoard (addr=20, bus=1)

This class may be instantiated by more than one process. The vizy-powerd service instantiates this class and uses it to monitor and control things such as power-off requests, CPU temperature, fan speed, etc.
User programs can also instantiate this class and use its methods simultaneously.

Args

addr : integer, optional, default=0x14
I2C address of the board
bus : integer, optional, default=1
the I2C bus number
Expand source code
class VizyPowerBoard:
    """
    This class may be
    instantiated by more than one process.  The vizy-powerd service
    instantiates this class and uses it to monitor and control things such 
    as power-off requests, CPU temperature, fan speed, etc.  
    User programs can also instantiate this class and
    use its methods simultaneously.
    """    
    def __init__(self, addr=0x14, bus=1):
        """
        Args:
          addr (integer, optional, default=0x14): I2C address of the board
          bus (integer, optional, default=1): the I2C bus number 

        """
        self.bus = smbus.SMBus(bus)
        self.addr = addr
        hwv = self.hw_version()
        if hwv!=COMPAT_HW_VERSION:
            raise RuntimeError("The hardware version of your Vizy Power Board (" + str(hwv[0])+'.'+str(hwv[1]) + ") is incompatible with this software file (" + str(COMPAT_HW_VERSION[0])+'.'+str(COMPAT_HW_VERSION[1]) + ").")

    @staticmethod
    def _bcd2decimal(bcd):
        tens = (bcd&0xf0)>>4
        ones = bcd&0x0f
        return tens*10 + ones

    @staticmethod
    def _decimal2bcd(dec):
        tens = int(dec/10)
        ones = dec%10
        return (tens<<4) | ones

    @staticmethod
    def _u_int8(i):
        i = round(i)
        if i>0xff:
            return 0xff
        if i<0:
            return 0
        return i

    @staticmethod
    def _int8(i):
        i = round(i)
        if i>0x7f:
            return 0x7f
        if i<-0x80:
            return 0x80
        if i<0:
            return 0x100+i
        return i

    @staticmethod
    def _uint16(i):
        i = round(i)
        if i>0xffff:
            return 0xffff
        if i<0:
            return 0
        return i

    def _status(self):
        return self.bus.read_i2c_block_data(self.addr, 0, 1)[0]

    def _wait_until_not_busy(self):
        while self._status()==ERROR_BUSY:
            time.sleep(0.001)

    def hw_version(self):
        """
        Returns the major and minor versions of the PCB as a 2-item list.
        """ 
        return self.bus.read_i2c_block_data(self.addr, 1, 2)

    def fw_version(self):
        """
        Returns the major, minor and build versions of the firmware as 
        a 3-item list.
        """ 
        return self.bus.read_i2c_block_data(self.addr, 3, 3)

    def resource_url(self):
        """
        Returns the url of a JSON file that contains information about 
        resources, such as the location of the latest version of this code, 
        latest firmware, etc.
        """
        chars = self.bus.read_i2c_block_data(self.addr, 6, 32)
        s = ''
        for c in chars:
            if c==0: # read up to the null character
                break
            s += chr(c)
        return s

    def uuid(self):
        """
        Returns a 16-byte unique ID that can be used an a unique
        ID for your Vizy camera.  This unique ID is stored on the Vizy Power
        Board and remains constant regardless of firmware upgrades, etc.
        """
        return self.bus.read_i2c_block_data(self.addr, 22, 16)

    def power_off_requested(self):
        """
        Returns `True` if Vizy's button is held down for more than 5 seconds
        indicating that the user wishes to initiate safe shutdown and power
        off.  Returns `False` otherwise.

        This is used by the vizy-powerd service. 
        """
        button = self.bus.read_i2c_block_data(self.addr, 38, 1)
        if button[0]==0x0f:
            return True
        else:
            return False

    def power_off(self, t=5000):
        """
        Powers Vizy off. The `t` argument specifies how long 
        to wait before turning off (specified in milliseconds).  The 
        vizy-powerd service calls this upon shutdown.
        """
        self.bus.write_i2c_block_data(self.addr, 38, [0x1f, int(t/100)])

    def power_on_alarm_date(self, datetime_=None):
        """
        If you wish to power off your Vizy and have it "wake up" at a
        specified time and date, call this method with the desired
        datetime object and initiate a shutdown. (e.g. `sudo shutdown now`).


        The code below tells Vizy to turn on on December 2, 2022, 11:18am.

            import vizypowerboard as vpb
            from datetime import datetime
            v = vpb.VizyPowerBoard()
            d=datetime(year=2022, month=12, day=2, hour=11, minute=18, second=0)
            v.power_on_alarm_date(d)

        Args:
          datetime_ (datetime, optional, default=None): `datetime` object that
            specifies the date/time to "wake up" (turn on).

        Returns:
          Calling without a datetime object returns a `datetime` object 
          reflecting the active alarm time.  If there is no active alarm,
          `None` is returned.
           
        Notes:
          * Once setting the alarm date, Vizy will retain it even if Vizy loses
          power for extended periods of time.
          * If the alarm expires while Vizy is on, Vizy will emit a buzzer tone
          and remain on.
          * If the alarm expires while Vizy is off (but plugged into and
          receiving power), Vizy will turn on.
          * If the alarm expires while Vizy is unplugged from (or not receiving)
          power, Vizy will turn on as soon as it receives power.  
        """
        if datetime_ is None:
            t = self.bus.read_i2c_block_data(self.addr, 41, 6)
            if t[5]==0:
                return None
            return datetime.datetime(year=self._bcd2decimal(t[5])+2016, month=self._bcd2decimal(t[4]), day=self._bcd2decimal(t[3]), hour=self._bcd2decimal(t[2]), minute=self._bcd2decimal(t[1]), second=self._bcd2decimal(t[0]))
        t = [self._decimal2bcd(datetime_.second), self._decimal2bcd(datetime_.minute), self._decimal2bcd(datetime_.hour), self._decimal2bcd(datetime_.day), self._decimal2bcd(datetime_.month), self._decimal2bcd(datetime_.year-2016)]
        self.bus.write_i2c_block_data(self.addr, 41, t)
    

    def power_on_alarm_seconds(self, seconds=None):
        """
        Allows you to specify a power on alarm in seconds in the future. 
        For example, if you wish for Vizy to turn back on in 5 minutes, you
        would call `power_on_alarm_seconds(300)` and then initiate a shutdown.
        See `VizyPowerBoard.power_on_alarm_date()` for more information about the power on 
        alarm.

        Args:
          seconds (integer, optional, default=None): Number of seconds in the
            future you wish Vizy to turn on in.

        Returns:
          Calling this method without arguments returns the number of seconds
          until the alarm expires.  If no alarm is pending, `None` is returned.
        """
        if seconds is None:
            pod = self.power_on_alarm_date()
            if pod is None:
                return None
            diff = pod - self.rtc()
            return diff.days*86400+diff.seconds 
        # Add seconds to current time and set power on alarm    
        self.power_on_alarm_date(self.rtc()+datetime.timedelta(seconds=seconds))

    def power_on_source(self):
        """
        Returns the source of what turned on Vizy for the current power cycle.
        It is one of either:

        * POWER_ON_SOURCE_ALARM, indicates that Vizy was powered on
        by the power on alarm expiring.  See power_on_alarm_date() and
        power_on_alarm_seconds().
        * POWER_ON_SOURCE_POWER_BUTTON, indicates that Vizy was powered 
        on by someone pressing the button.
        * POWER_ON_SOURCE_12V = indicates that Vizy was powered on
        by power being applied to 12V power input.  This only applies if the
        dip switch power mode allows powering on by plugging in power via the
        12V power input.
        * POWER_ON_SOURCE_5V, indicates that Vizy was powered on by applying
        power to the Raspberry Pi's USB-C power input.
        """
        source = self.bus.read_i2c_block_data(self.addr, 40, 1)[0]
        return source

    def button(self):
        """
        Returns `True` if the button is being pressed currently, `False` otherwise.
        """
        button = self.bus.read_i2c_block_data(self.addr, 47, 1)
        if button[0]&0x02:
            return True
        else:
            return False

    def button_pressed(self):
        """
        Returns `True` if the button was pressed within the last 5 seconds,
        `False` otherwise.  This is useful if the polling is intermittant or
        slow, as button presses are not missed (as long as you check at least
        every 5 seconds!)
        """
        button = self.bus.read_i2c_block_data(self.addr, 47, 1)
        if button[0]&0x01:
            # Reset bit
            self.bus.write_i2c_block_data(self.addr, 47, [0])
            return True
        else:
            return False

    def vcc12(self, state=None):
        """
        If `state` is `True`, the 12V output on Vizy's I/O connector (pin 2) will be enabled and output 12V.  If `state` is `False`, the 12V output
        will be disabled.  Calling without arguments returns its current state.
        """ 
        config = self.bus.read_i2c_block_data(self.addr, 48, 1)
        if state is None:
            return True if config&0x01 else False
        if state:
            config[0] |= 0x01
        else:
            config[0] &= ~0x01

        self.bus.write_i2c_block_data(self.addr, 48, config)

    def vcc5(self, state=None):
        """
        If `state` is `True`, the 5V output on Vizy's I/O connector (pin 3) will
        be enabled and output 5V.  If `state` is `False`, the 5V output will be
        disabled.  Calling without arguments returns its current state.
        """ 
        if state is None:
            return True if config&0x02 else False
        if state:
            config[0] |= 0x02
        else:
            config[0] &= ~0x02

        self.bus.write_i2c_block_data(self.addr, 48, config)

    def led(self, r=0, g=0, b=0, flashes=0, repeat=False, atten=255, on=100, off=100, pause=200):
        """
        Controls the RGB LED in one of several modes:

        * **Continuous**: just setting `r`, `g`, and `b` will set set the LED color
        and turn it on continuously.  `r`, `g`, and `b` values range between 0 
        and  255.

                led(255, 0, 0)   # turn on LED, red color
                led(255, 255, 0) # turn on LED, yellow color
                led(0, 0, 255)   # turn on LED, blue color
                led(0, 0, 0)     # turn off LED

        * **Flashes**: setting the `flashes` value to a non-zero value will
        cause the LED to flash the indicated number of times.  You can also
        specify the `on` and `off` arguments to indicate the amount of time 
        the LED is on and off for each flash (specified in milliseconds).

                led(0, 0, 255, 3)  # flash blue 3 times (then stop)
                led(0, 0, 255, 3, on=500, off=500)  # flash blue 3 times, much more slowly

        * **Repeated flashes**: setting the `repeat` argument to `True` 
        will cause the indicated flash pattern to repeat forever.  You can
        modify the pause time between flash sequences by setting `pause`
        (milliseconds).

                led(0, 0, 255, 3, True, pause=500)  # flash blue 3 times, pause, then repeat
                led(0, 0, 255, repeat=True, on=500, off=500)`  # flash blue forever

        * **Flashing with attenuation**: you can also set the `atten` 
        argument to make the LED to turn on and off slowly, like an
        incandescent light.  The value is the rate of change, so lower values
        cause the LED color to change more slowly. 

                led(0, 0, 255, repeat=True, atten=10, on=500, off=500) # flash blue forever, but turn on and turn off very slowly
        """
        on = self._u_int8(on/10)
        off = self._u_int8(off/10)
        pause = self._u_int8(pause/10)
        if flashes==0:
            mode = 0
        if repeat:
            mode = 0x02
        else:
            mode = 0x01
        self.bus.write_i2c_block_data(self.addr, 49, [mode, self._u_int8(r), self._u_int8(g), self._u_int8(b),
            on, off, self._u_int8(flashes), pause, self._u_int8(atten)])

    def led_unicorn(self, speed=10):
        """
        This causes the LED to change color in succession: red, orange, yellow, 
        green, cyan, blue, violet and then repeat again.  The `speed` argument 
        ranges between 0 and 10.  For example, a `speed` of 0 causes the color 
        to change once every couple of seconds.  A `speed` of 10 causes the color to change about 6 times per second.
        """
        if speed>10:
            speed = 10
        elif speed<0:
            speed = 0

        on = self._u_int8(10 + (10-speed)*140/10)
        atten = self._u_int8(3 + speed*47/10)    
        self.bus.write_i2c_block_data(self.addr, 49, [0x08, 0, 0, 0, on, 0, 0, 0, atten])

    def led_background(self, r=-1, g=-1, b=-1):
        """
        The "background" LED color is the color of the LED when the LED is 
        turned "off".  It is used by system programs such as vizy-powerd to
        indicate Vizy's system state such as, booting (yellow), finished
        booting (green), running server (blue), etc.  Note, the background
        color does not influence the LED colors set by calls to led().

        Calling led_background() without arguments returns the current 
        background color r, g, and b values in a list. 

            led_background(48, 48, 0)  # set background color to yellow
            led(0, 255, 0)  # turn on LED, green (as expected)
            led(0, 0, 0)  # turn LED off, and restore background color (yellow as set previously)    
        """
        if r==-1:
            return self.bus.read_i2c_block_data(self.addr, 58, 3)
        self.bus.write_i2c_block_data(self.addr, 58, [self._u_int8(r), self._u_int8(g), self._u_int8(b)])
 

    def buzzer(self, freq, on=250, off=250, count=1, shift=0):
        """
        Emit tones through the buzzer.  The `freq` argument sets the frequency 
        of the tone in Hz and the `on` argument sets the length of the tone in
        milliseconds.  

        If you wish to emit more than 1 tone, you can set the
        `count` argument to the desired number.  

        The `off` argument sets 
        the pause between tones in milliseconds.  The `shift` argument is a
        value ranging between -128 and 127 that causes the tone's frequency to
        raise if `shift` is greater than 0, or descend if `shift` is less 
        than 0.

            buzzer(2000, 500) # emit a 2000Hz tone for 500ms
            buzzer(1000, count=3) # emit a 1000Hz tone 3 times   
            buzzer(1000, 500, 100, 3) # emit a longer 1000Hz tone 3 times
            buzzer(500, 250, 0, 10, 50) # emit 10 warbling tones like a siren
        """
        freq = self._uint16(freq)
        f0 = freq&0xff
        f1 = (freq>>8)&0xff
        self.bus.write_i2c_block_data(self.addr, 61, [0, f0, f1, self._u_int8(on/10), self._u_int8(off/10), 
            self._u_int8(count), self._int8(shift)])


    def io_set_mode(self, bit, mode=None):
        """
        Sets or gets the io mode of the given bit.  The `bit` argument ranges
        between  0 and 3 and corresponds to pins 4 through 7 on Vizy's IO
        connector. Calling this method with no mode argument returns the mode
        of the given bit, otherwise, the `mode` argument can be one of 
        the following:

        * IO_MODE_INPUT, sets the bit to high impedance input mode with 
        a weak pull-up resistor to 3.3V.  The input voltage can range between
        0 and Vin where Vin is the supply voltage.  Voltages lower than 1V 
        read as logic 0 via `VizyPowerBoard.io_bits()`.  Voltages higher 
        than 1V are read as logic 1.
        * IO_MODE_OUTPUT, sets the bit to output mode.  If the bit is set to
        logic 0 via `VizyPowerBoard.io_bits()`, the output voltage is 0V.  
        If the bit is set to logic 1, the output voltage is 3.3V.  In this mode, each bit can source and sink 5mA.  
        * IO_MODE_HIGH_CURRENT, sets the bit to a special high current mode
        that allows the bit to sink as much as 1000mA, when the bit 
        set to logic 0 via `VizyPowerBoard.io_bits()`.  Otherwise, this mode
        behaves exactly as IO_MODE_OUTPUT.    
        """
        if mode is None:
            return self.bus.read_i2c_block_data(self.addr, 68+bit, 1)[0]
        if bit==2:
            # set GPIO14 (UART TXD) as input so it doesn't conflict
            GPIO.setwarnings(False)
            GPIO.setmode(GPIO.BOARD)
            GPIO.setup(8, GPIO.IN)
        self.bus.write_i2c_block_data(self.addr, 68+bit, [self._u_int8(mode)])

    def io_bits(self, bits=None):
        """
        Sets or gets the logic state of the IO bits 0 through 3, corresponding
        to pins 4 through 7 on Vizy's IO connector.  The `bits` argument ranges
        between 0 and 15 as it is a binary representation of the logic state
        of the 4 IO bits.  If the `bits` argument isn't specified, the logic
        state of the 4 bits are returned.

            io_bits(1)   # set IO bit 0 to logic 1 and bits 1, 2, 3 to logic 0
            io_bits(10)  # set IO bits 1 and 3 to logic 1 and bits 0 and 2 to logic 0
            bits = io_bits()  # get logic state of IO bits
            io_bits(io_bits()|1)  # set IO bit 0 to logic 1, leave bits 1, 2, 3 unchanged. 
        """
        if bits is None:
            return self.bus.read_i2c_block_data(self.addr, 72, 1)
        self.bus.write_i2c_block_data(self.addr, 72, [bits])


    def ir_filter(self, state=None, duration=None):
        """
        Actuates the electro-mechanical IR-cut filter on Vizy's camera.  Vizy
        uses a CMOS sensor which is very sensitive to IR light.  IR light can 
        adversely affect color fidelity during the daytime so an IR-cut filter
        is used to block the IR light (`state`=True).  During nighttime IR light
        is typically used as a discreet method of illumination and the IR-cut
        filter is removed (`state`=False).  If the `state` argument is `True`,
        the filter is actuated in place (and will stay there) until another
        call is made with the state argument set to `False` (in which case the
        IR-cut filter will be removed).  

        The `duration` argument is optional and
        controls how long (in milliseconds) the actuation coil receives power.

        Calling this method without arguments returns the state of IR-cut
        filter.
        """
        if state is None:
            return True if self.bus.read_i2c_block_data(self.addr, 73, 1)[0] else False
        data = [1] if state else [0]
        if duration is not None:
            data.append(int(duration/10))
        self.bus.write_i2c_block_data(self.addr, 73, data)

    def fan(self, speed=None):
        """
        Set or get the fan speed.  The `speed` argument can range between 0 
        and 12 where 0 is off and 12 is maximum speed.  The fan speed 
        is typically regulated automatically by vizy-powerd.

        Calling this method without arguments returns the current fan speed.  
        """
        if speed is None:
            return self.bus.read_i2c_block_data(self.addr, 75, 1)[0]
        self.bus.write_i2c_block_data(self.addr, 75, [self._u_int8(speed)])
       

    def rtc(self, datetime_=None):
        """
        Set or get the real-time clock time/date.  The Vizy power board has a 
        battery-backed real-time clock that keeps track of time/date, power 
        alarms, etc. even while Vizy is receiving no power.  Passing in a
        datetime object sets the time/date.  

        Calling this method with no
        arguments returns a datetime object representing the current 
        date/time. 

        For example, the code below sets the date to December 2, 2020, 11:18am:

            from datetime import datetime
            import vizypowerboard as vpb
            v = vpb.VizyPowerBoard()
            t = datetime(year=2020, month=12, day=2, hour=11, minute=18, second=0)
            v.rtc(t)
        """
        if datetime_ is None:
            # Initiate RTC retrieval.
            self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC])
            # Wait until it's ready.
            self._wait_until_not_busy()

            t = self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 8)
            return datetime.datetime(year=self._bcd2decimal(t[7])+2016, month=self._bcd2decimal(t[6]), day=self._bcd2decimal(t[4]), hour=self._bcd2decimal(t[3]), minute=self._bcd2decimal(t[2]), second=self._bcd2decimal(t[1]))
 
        t = [EXEC_RTC|EXEC_WRITE, 0, self._decimal2bcd(datetime_.second), self._decimal2bcd(datetime_.minute), self._decimal2bcd(datetime_.hour), self._decimal2bcd(datetime_.day), 0, self._decimal2bcd(datetime_.month), self._decimal2bcd(datetime_.year-2016)]
        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, t)
        self._wait_until_not_busy()

    def dip_switches(self, val=None):
        """
        Set or get the (virtual) DIP switch state.  The DIP switches are a set 
        of "switches" that allow you to control Vizy's power-on or power-off
        behavior.  Once they are set, they will retain the setting even if 
        power is removed. 
        The switches are a set of values that can be ORed together:

        * DIPSWITCH_EXT_BUTTON, used to set external/remote power button, 
        e.g. with outdoor enclosure.  Default disabled.
        * DIPSWITCH_MUTE_BUZZER, used to mute the buzzer.  Default disabled.
        * DIPSWITCH_NO_BG_LED, used to disable the background LED, which is 
        normally set to yellow upon power up.  Default disabled.
        * DIPSWITCH_POWER_DEFAULT_OFF, if this power mode is set and you 
        plug in power via the 12V power input, Vizy will remain off by default
        until you press the button to power Vizy on.  And if power is
        interrupted while Vizy is on, *Vizy will turn off*.  If power is
        interrupted while Vizy is off, Vizy will remain off.  This is the
        default power mode.
        * DIPSWITCH_POWER_DEFAULT_ON, if this power mode is set and you 
        plug in power via the 12V power input, Vizy will turn on by default
        without pressing the button.  And if power is interrupted while Vizy
        is on, Vizy will reset, but remain on.  If power is interrupted while
        Vizy is off, *Vizy will turn on*.  Default disabled.
        * DIPSWITCH_POWER_SWITCH, if this power mode is set and you plug in
        power via the 12V power input, Vizy will remain off (as in
        DIPSWITCH_POWER_DEFAULT_OFF mode), unless power was 
        removed while Vizy was on.  In this case Vizy will turn on when you
        (re)apply power.  If power is interrupted while Vizy is off, Vizy 
        will remain off.  This behavior is similar to the behavior of a real
        power switch in that it retains the power "state" (on or off) and acts
        accordingly.  Default disabled.
        * DIPSWITCH_POWER_PLUG, if this power mode is set Vizy will remain
        powered on as long as it receives power through the 12V power plug, 
        and you will not be able to turn off Vizy via button or software as
        long as it's plugged in and receiving power.  Default disabled.
        
        For example: 

            dip_switches(DIPSWITCH_EXT_BUTTON | DIPSWITCH_POWER_SWITCH) # set external power button and power switch mode
        """
        if val is None:
            self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_NVCONFIG])
            # Wait until it's ready.
            self._wait_until_not_busy()
            return self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 1)[0]

        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_NVCONFIG|EXEC_WRITE, self._u_int8(val)])
        self._wait_until_not_busy()

    def rtc_adjust(self, val=None):
        """
        Set or get the real-time clock adjustment.  Vizy's real-time clock 
        crystal has an accuracy of 20ppm, which means that it can lose or gain 
        up to 20 seconds for every 1 million elapsed seconds.  Normally, this
        isn't an issue, but if Vizy spends a lengthy period of time (months)
        without Internet access, it could lose or gain minutes, which 
        depending on the application could be significant. The adjustment
        value can offset this inaccuracy.  The `val` argument can range 
        between -128 and 127 and has a multiplier of 2.170 ppm.  

        For example, 
        if the RTC is gaining 10 seconds every 1 million seconds, you would 
        call `rtc_adjust(-5)`.  If the RTC is losing 10 seconds every million
        seconds you would call `rtc_adjust(5)`.
        
        The adjustment value is retained by the real-time clock even when 
        Vizy's power is removed. 
        """
        if val is None:
            self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC_CALIBRATE])
            # Wait until it's ready.
            self._wait_until_not_busy()
            return self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 1)[0]

        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC_CALIBRATE|EXEC_WRITE, self._int8(val)])
        self._wait_until_not_busy()


    def measure(self, channel):
        """
        Get the voltage values of various channels.  The returned value is 
        the voltage measured (in Volts) of the given channel.  The `channel`
        argument can be one of the following:

        * CHANNEL_VIN, this channel measures the voltage present at the 12V
        power input.
        * CHANNEL_5V, this channel measures the voltage present at the 5V
        voltage rail provided to the Raspberry Pi.
        """  
        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_AD, self._u_int8(channel)])
        # Wait until it's ready.
        self._wait_until_not_busy()
        val = self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+2, 2)
        return (val[1]*0x100 + val[0])/1000


    def rtc_set_system_datetime(self, datetime_=None):
        """
        A convenience method that sets the system time/date based on the 
        real-time time/date.  This is called by vizy-powerd upon power-up. 
        """
        if os.geteuid()!=0:
            raise PermissionError("You need root permission to set the time/date.")
        if datetime_ is None:
            datetime_ = self.rtc()
        s = datetime_.isoformat()
        os.system(f"sudo date -s {s}")

Methods

def button(self)

Returns True if the button is being pressed currently, False otherwise.

Expand source code
def button(self):
    """
    Returns `True` if the button is being pressed currently, `False` otherwise.
    """
    button = self.bus.read_i2c_block_data(self.addr, 47, 1)
    if button[0]&0x02:
        return True
    else:
        return False
def button_pressed(self)

Returns True if the button was pressed within the last 5 seconds, False otherwise. This is useful if the polling is intermittant or slow, as button presses are not missed (as long as you check at least every 5 seconds!)

Expand source code
def button_pressed(self):
    """
    Returns `True` if the button was pressed within the last 5 seconds,
    `False` otherwise.  This is useful if the polling is intermittant or
    slow, as button presses are not missed (as long as you check at least
    every 5 seconds!)
    """
    button = self.bus.read_i2c_block_data(self.addr, 47, 1)
    if button[0]&0x01:
        # Reset bit
        self.bus.write_i2c_block_data(self.addr, 47, [0])
        return True
    else:
        return False
def buzzer(self, freq, on=250, off=250, count=1, shift=0)

Emit tones through the buzzer. The freq argument sets the frequency of the tone in Hz and the on argument sets the length of the tone in milliseconds.

If you wish to emit more than 1 tone, you can set the count argument to the desired number.

The off argument sets the pause between tones in milliseconds. The shift argument is a value ranging between -128 and 127 that causes the tone's frequency to raise if shift is greater than 0, or descend if shift is less than 0.

buzzer(2000, 500) # emit a 2000Hz tone for 500ms
buzzer(1000, count=3) # emit a 1000Hz tone 3 times   
buzzer(1000, 500, 100, 3) # emit a longer 1000Hz tone 3 times
buzzer(500, 250, 0, 10, 50) # emit 10 warbling tones like a siren
Expand source code
def buzzer(self, freq, on=250, off=250, count=1, shift=0):
    """
    Emit tones through the buzzer.  The `freq` argument sets the frequency 
    of the tone in Hz and the `on` argument sets the length of the tone in
    milliseconds.  

    If you wish to emit more than 1 tone, you can set the
    `count` argument to the desired number.  

    The `off` argument sets 
    the pause between tones in milliseconds.  The `shift` argument is a
    value ranging between -128 and 127 that causes the tone's frequency to
    raise if `shift` is greater than 0, or descend if `shift` is less 
    than 0.

        buzzer(2000, 500) # emit a 2000Hz tone for 500ms
        buzzer(1000, count=3) # emit a 1000Hz tone 3 times   
        buzzer(1000, 500, 100, 3) # emit a longer 1000Hz tone 3 times
        buzzer(500, 250, 0, 10, 50) # emit 10 warbling tones like a siren
    """
    freq = self._uint16(freq)
    f0 = freq&0xff
    f1 = (freq>>8)&0xff
    self.bus.write_i2c_block_data(self.addr, 61, [0, f0, f1, self._u_int8(on/10), self._u_int8(off/10), 
        self._u_int8(count), self._int8(shift)])
def dip_switches(self, val=None)

Set or get the (virtual) DIP switch state. The DIP switches are a set of "switches" that allow you to control Vizy's power-on or power-off behavior. Once they are set, they will retain the setting even if power is removed. The switches are a set of values that can be ORed together:

  • DIPSWITCH_EXT_BUTTON, used to set external/remote power button, e.g. with outdoor enclosure. Default disabled.
  • DIPSWITCH_MUTE_BUZZER, used to mute the buzzer. Default disabled.
  • DIPSWITCH_NO_BG_LED, used to disable the background LED, which is normally set to yellow upon power up. Default disabled.
  • DIPSWITCH_POWER_DEFAULT_OFF, if this power mode is set and you plug in power via the 12V power input, Vizy will remain off by default until you press the button to power Vizy on. And if power is interrupted while Vizy is on, Vizy will turn off. If power is interrupted while Vizy is off, Vizy will remain off. This is the default power mode.
  • DIPSWITCH_POWER_DEFAULT_ON, if this power mode is set and you plug in power via the 12V power input, Vizy will turn on by default without pressing the button. And if power is interrupted while Vizy is on, Vizy will reset, but remain on. If power is interrupted while Vizy is off, Vizy will turn on. Default disabled.
  • DIPSWITCH_POWER_SWITCH, if this power mode is set and you plug in power via the 12V power input, Vizy will remain off (as in DIPSWITCH_POWER_DEFAULT_OFF mode), unless power was removed while Vizy was on. In this case Vizy will turn on when you (re)apply power. If power is interrupted while Vizy is off, Vizy will remain off. This behavior is similar to the behavior of a real power switch in that it retains the power "state" (on or off) and acts accordingly. Default disabled.
  • DIPSWITCH_POWER_PLUG, if this power mode is set Vizy will remain powered on as long as it receives power through the 12V power plug, and you will not be able to turn off Vizy via button or software as long as it's plugged in and receiving power. Default disabled.

For example:

dip_switches(DIPSWITCH_EXT_BUTTON | DIPSWITCH_POWER_SWITCH) # set external power button and power switch mode
Expand source code
def dip_switches(self, val=None):
    """
    Set or get the (virtual) DIP switch state.  The DIP switches are a set 
    of "switches" that allow you to control Vizy's power-on or power-off
    behavior.  Once they are set, they will retain the setting even if 
    power is removed. 
    The switches are a set of values that can be ORed together:

    * DIPSWITCH_EXT_BUTTON, used to set external/remote power button, 
    e.g. with outdoor enclosure.  Default disabled.
    * DIPSWITCH_MUTE_BUZZER, used to mute the buzzer.  Default disabled.
    * DIPSWITCH_NO_BG_LED, used to disable the background LED, which is 
    normally set to yellow upon power up.  Default disabled.
    * DIPSWITCH_POWER_DEFAULT_OFF, if this power mode is set and you 
    plug in power via the 12V power input, Vizy will remain off by default
    until you press the button to power Vizy on.  And if power is
    interrupted while Vizy is on, *Vizy will turn off*.  If power is
    interrupted while Vizy is off, Vizy will remain off.  This is the
    default power mode.
    * DIPSWITCH_POWER_DEFAULT_ON, if this power mode is set and you 
    plug in power via the 12V power input, Vizy will turn on by default
    without pressing the button.  And if power is interrupted while Vizy
    is on, Vizy will reset, but remain on.  If power is interrupted while
    Vizy is off, *Vizy will turn on*.  Default disabled.
    * DIPSWITCH_POWER_SWITCH, if this power mode is set and you plug in
    power via the 12V power input, Vizy will remain off (as in
    DIPSWITCH_POWER_DEFAULT_OFF mode), unless power was 
    removed while Vizy was on.  In this case Vizy will turn on when you
    (re)apply power.  If power is interrupted while Vizy is off, Vizy 
    will remain off.  This behavior is similar to the behavior of a real
    power switch in that it retains the power "state" (on or off) and acts
    accordingly.  Default disabled.
    * DIPSWITCH_POWER_PLUG, if this power mode is set Vizy will remain
    powered on as long as it receives power through the 12V power plug, 
    and you will not be able to turn off Vizy via button or software as
    long as it's plugged in and receiving power.  Default disabled.
    
    For example: 

        dip_switches(DIPSWITCH_EXT_BUTTON | DIPSWITCH_POWER_SWITCH) # set external power button and power switch mode
    """
    if val is None:
        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_NVCONFIG])
        # Wait until it's ready.
        self._wait_until_not_busy()
        return self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 1)[0]

    self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_NVCONFIG|EXEC_WRITE, self._u_int8(val)])
    self._wait_until_not_busy()
def fan(self, speed=None)

Set or get the fan speed. The speed argument can range between 0 and 12 where 0 is off and 12 is maximum speed. The fan speed is typically regulated automatically by vizy-powerd.

Calling this method without arguments returns the current fan speed.

Expand source code
def fan(self, speed=None):
    """
    Set or get the fan speed.  The `speed` argument can range between 0 
    and 12 where 0 is off and 12 is maximum speed.  The fan speed 
    is typically regulated automatically by vizy-powerd.

    Calling this method without arguments returns the current fan speed.  
    """
    if speed is None:
        return self.bus.read_i2c_block_data(self.addr, 75, 1)[0]
    self.bus.write_i2c_block_data(self.addr, 75, [self._u_int8(speed)])
def fw_version(self)

Returns the major, minor and build versions of the firmware as a 3-item list.

Expand source code
def fw_version(self):
    """
    Returns the major, minor and build versions of the firmware as 
    a 3-item list.
    """ 
    return self.bus.read_i2c_block_data(self.addr, 3, 3)
def hw_version(self)

Returns the major and minor versions of the PCB as a 2-item list.

Expand source code
def hw_version(self):
    """
    Returns the major and minor versions of the PCB as a 2-item list.
    """ 
    return self.bus.read_i2c_block_data(self.addr, 1, 2)
def io_bits(self, bits=None)

Sets or gets the logic state of the IO bits 0 through 3, corresponding to pins 4 through 7 on Vizy's IO connector. The bits argument ranges between 0 and 15 as it is a binary representation of the logic state of the 4 IO bits. If the bits argument isn't specified, the logic state of the 4 bits are returned.

io_bits(1)   # set IO bit 0 to logic 1 and bits 1, 2, 3 to logic 0
io_bits(10)  # set IO bits 1 and 3 to logic 1 and bits 0 and 2 to logic 0
bits = io_bits()  # get logic state of IO bits
io_bits(io_bits()|1)  # set IO bit 0 to logic 1, leave bits 1, 2, 3 unchanged.
Expand source code
def io_bits(self, bits=None):
    """
    Sets or gets the logic state of the IO bits 0 through 3, corresponding
    to pins 4 through 7 on Vizy's IO connector.  The `bits` argument ranges
    between 0 and 15 as it is a binary representation of the logic state
    of the 4 IO bits.  If the `bits` argument isn't specified, the logic
    state of the 4 bits are returned.

        io_bits(1)   # set IO bit 0 to logic 1 and bits 1, 2, 3 to logic 0
        io_bits(10)  # set IO bits 1 and 3 to logic 1 and bits 0 and 2 to logic 0
        bits = io_bits()  # get logic state of IO bits
        io_bits(io_bits()|1)  # set IO bit 0 to logic 1, leave bits 1, 2, 3 unchanged. 
    """
    if bits is None:
        return self.bus.read_i2c_block_data(self.addr, 72, 1)
    self.bus.write_i2c_block_data(self.addr, 72, [bits])
def io_set_mode(self, bit, mode=None)

Sets or gets the io mode of the given bit. The bit argument ranges between 0 and 3 and corresponds to pins 4 through 7 on Vizy's IO connector. Calling this method with no mode argument returns the mode of the given bit, otherwise, the mode argument can be one of the following:

  • IO_MODE_INPUT, sets the bit to high impedance input mode with a weak pull-up resistor to 3.3V. The input voltage can range between 0 and Vin where Vin is the supply voltage. Voltages lower than 1V read as logic 0 via VizyPowerBoard.io_bits(). Voltages higher than 1V are read as logic 1.
  • IO_MODE_OUTPUT, sets the bit to output mode. If the bit is set to logic 0 via VizyPowerBoard.io_bits(), the output voltage is 0V.
    If the bit is set to logic 1, the output voltage is 3.3V. In this mode, each bit can source and sink 5mA.
  • IO_MODE_HIGH_CURRENT, sets the bit to a special high current mode that allows the bit to sink as much as 1000mA, when the bit set to logic 0 via VizyPowerBoard.io_bits(). Otherwise, this mode behaves exactly as IO_MODE_OUTPUT.
Expand source code
def io_set_mode(self, bit, mode=None):
    """
    Sets or gets the io mode of the given bit.  The `bit` argument ranges
    between  0 and 3 and corresponds to pins 4 through 7 on Vizy's IO
    connector. Calling this method with no mode argument returns the mode
    of the given bit, otherwise, the `mode` argument can be one of 
    the following:

    * IO_MODE_INPUT, sets the bit to high impedance input mode with 
    a weak pull-up resistor to 3.3V.  The input voltage can range between
    0 and Vin where Vin is the supply voltage.  Voltages lower than 1V 
    read as logic 0 via `VizyPowerBoard.io_bits()`.  Voltages higher 
    than 1V are read as logic 1.
    * IO_MODE_OUTPUT, sets the bit to output mode.  If the bit is set to
    logic 0 via `VizyPowerBoard.io_bits()`, the output voltage is 0V.  
    If the bit is set to logic 1, the output voltage is 3.3V.  In this mode, each bit can source and sink 5mA.  
    * IO_MODE_HIGH_CURRENT, sets the bit to a special high current mode
    that allows the bit to sink as much as 1000mA, when the bit 
    set to logic 0 via `VizyPowerBoard.io_bits()`.  Otherwise, this mode
    behaves exactly as IO_MODE_OUTPUT.    
    """
    if mode is None:
        return self.bus.read_i2c_block_data(self.addr, 68+bit, 1)[0]
    if bit==2:
        # set GPIO14 (UART TXD) as input so it doesn't conflict
        GPIO.setwarnings(False)
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(8, GPIO.IN)
    self.bus.write_i2c_block_data(self.addr, 68+bit, [self._u_int8(mode)])
def ir_filter(self, state=None, duration=None)

Actuates the electro-mechanical IR-cut filter on Vizy's camera. Vizy uses a CMOS sensor which is very sensitive to IR light. IR light can adversely affect color fidelity during the daytime so an IR-cut filter is used to block the IR light (state=True). During nighttime IR light is typically used as a discreet method of illumination and the IR-cut filter is removed (state=False). If the state argument is True, the filter is actuated in place (and will stay there) until another call is made with the state argument set to False (in which case the IR-cut filter will be removed).

The duration argument is optional and controls how long (in milliseconds) the actuation coil receives power.

Calling this method without arguments returns the state of IR-cut filter.

Expand source code
def ir_filter(self, state=None, duration=None):
    """
    Actuates the electro-mechanical IR-cut filter on Vizy's camera.  Vizy
    uses a CMOS sensor which is very sensitive to IR light.  IR light can 
    adversely affect color fidelity during the daytime so an IR-cut filter
    is used to block the IR light (`state`=True).  During nighttime IR light
    is typically used as a discreet method of illumination and the IR-cut
    filter is removed (`state`=False).  If the `state` argument is `True`,
    the filter is actuated in place (and will stay there) until another
    call is made with the state argument set to `False` (in which case the
    IR-cut filter will be removed).  

    The `duration` argument is optional and
    controls how long (in milliseconds) the actuation coil receives power.

    Calling this method without arguments returns the state of IR-cut
    filter.
    """
    if state is None:
        return True if self.bus.read_i2c_block_data(self.addr, 73, 1)[0] else False
    data = [1] if state else [0]
    if duration is not None:
        data.append(int(duration/10))
    self.bus.write_i2c_block_data(self.addr, 73, data)
def led(self, r=0, g=0, b=0, flashes=0, repeat=False, atten=255, on=100, off=100, pause=200)

Controls the RGB LED in one of several modes:

  • Continuous: just setting r, g, and b will set set the LED color and turn it on continuously. r, g, and b values range between 0 and 255.

    led(255, 0, 0)   # turn on LED, red color
    led(255, 255, 0) # turn on LED, yellow color
    led(0, 0, 255)   # turn on LED, blue color
    led(0, 0, 0)     # turn off LED
    
  • Flashes: setting the flashes value to a non-zero value will cause the LED to flash the indicated number of times. You can also specify the on and off arguments to indicate the amount of time the LED is on and off for each flash (specified in milliseconds).

    led(0, 0, 255, 3)  # flash blue 3 times (then stop)
    led(0, 0, 255, 3, on=500, off=500)  # flash blue 3 times, much more slowly
    
  • Repeated flashes: setting the repeat argument to True will cause the indicated flash pattern to repeat forever. You can modify the pause time between flash sequences by setting pause (milliseconds).

    led(0, 0, 255, 3, True, pause=500)  # flash blue 3 times, pause, then repeat
    led(0, 0, 255, repeat=True, on=500, off=500)`  # flash blue forever
    
  • Flashing with attenuation: you can also set the atten argument to make the LED to turn on and off slowly, like an incandescent light. The value is the rate of change, so lower values cause the LED color to change more slowly.

    led(0, 0, 255, repeat=True, atten=10, on=500, off=500) # flash blue forever, but turn on and turn off very slowly
    
Expand source code
def led(self, r=0, g=0, b=0, flashes=0, repeat=False, atten=255, on=100, off=100, pause=200):
    """
    Controls the RGB LED in one of several modes:

    * **Continuous**: just setting `r`, `g`, and `b` will set set the LED color
    and turn it on continuously.  `r`, `g`, and `b` values range between 0 
    and  255.

            led(255, 0, 0)   # turn on LED, red color
            led(255, 255, 0) # turn on LED, yellow color
            led(0, 0, 255)   # turn on LED, blue color
            led(0, 0, 0)     # turn off LED

    * **Flashes**: setting the `flashes` value to a non-zero value will
    cause the LED to flash the indicated number of times.  You can also
    specify the `on` and `off` arguments to indicate the amount of time 
    the LED is on and off for each flash (specified in milliseconds).

            led(0, 0, 255, 3)  # flash blue 3 times (then stop)
            led(0, 0, 255, 3, on=500, off=500)  # flash blue 3 times, much more slowly

    * **Repeated flashes**: setting the `repeat` argument to `True` 
    will cause the indicated flash pattern to repeat forever.  You can
    modify the pause time between flash sequences by setting `pause`
    (milliseconds).

            led(0, 0, 255, 3, True, pause=500)  # flash blue 3 times, pause, then repeat
            led(0, 0, 255, repeat=True, on=500, off=500)`  # flash blue forever

    * **Flashing with attenuation**: you can also set the `atten` 
    argument to make the LED to turn on and off slowly, like an
    incandescent light.  The value is the rate of change, so lower values
    cause the LED color to change more slowly. 

            led(0, 0, 255, repeat=True, atten=10, on=500, off=500) # flash blue forever, but turn on and turn off very slowly
    """
    on = self._u_int8(on/10)
    off = self._u_int8(off/10)
    pause = self._u_int8(pause/10)
    if flashes==0:
        mode = 0
    if repeat:
        mode = 0x02
    else:
        mode = 0x01
    self.bus.write_i2c_block_data(self.addr, 49, [mode, self._u_int8(r), self._u_int8(g), self._u_int8(b),
        on, off, self._u_int8(flashes), pause, self._u_int8(atten)])
def led_background(self, r=-1, g=-1, b=-1)

The "background" LED color is the color of the LED when the LED is turned "off". It is used by system programs such as vizy-powerd to indicate Vizy's system state such as, booting (yellow), finished booting (green), running server (blue), etc. Note, the background color does not influence the LED colors set by calls to led().

Calling led_background() without arguments returns the current background color r, g, and b values in a list.

led_background(48, 48, 0)  # set background color to yellow
led(0, 255, 0)  # turn on LED, green (as expected)
led(0, 0, 0)  # turn LED off, and restore background color (yellow as set previously)
Expand source code
def led_background(self, r=-1, g=-1, b=-1):
    """
    The "background" LED color is the color of the LED when the LED is 
    turned "off".  It is used by system programs such as vizy-powerd to
    indicate Vizy's system state such as, booting (yellow), finished
    booting (green), running server (blue), etc.  Note, the background
    color does not influence the LED colors set by calls to led().

    Calling led_background() without arguments returns the current 
    background color r, g, and b values in a list. 

        led_background(48, 48, 0)  # set background color to yellow
        led(0, 255, 0)  # turn on LED, green (as expected)
        led(0, 0, 0)  # turn LED off, and restore background color (yellow as set previously)    
    """
    if r==-1:
        return self.bus.read_i2c_block_data(self.addr, 58, 3)
    self.bus.write_i2c_block_data(self.addr, 58, [self._u_int8(r), self._u_int8(g), self._u_int8(b)])
def led_unicorn(self, speed=10)

This causes the LED to change color in succession: red, orange, yellow, green, cyan, blue, violet and then repeat again. The speed argument ranges between 0 and 10. For example, a speed of 0 causes the color to change once every couple of seconds. A speed of 10 causes the color to change about 6 times per second.

Expand source code
def led_unicorn(self, speed=10):
    """
    This causes the LED to change color in succession: red, orange, yellow, 
    green, cyan, blue, violet and then repeat again.  The `speed` argument 
    ranges between 0 and 10.  For example, a `speed` of 0 causes the color 
    to change once every couple of seconds.  A `speed` of 10 causes the color to change about 6 times per second.
    """
    if speed>10:
        speed = 10
    elif speed<0:
        speed = 0

    on = self._u_int8(10 + (10-speed)*140/10)
    atten = self._u_int8(3 + speed*47/10)    
    self.bus.write_i2c_block_data(self.addr, 49, [0x08, 0, 0, 0, on, 0, 0, 0, atten])
def measure(self, channel)

Get the voltage values of various channels. The returned value is the voltage measured (in Volts) of the given channel. The channel argument can be one of the following:

  • CHANNEL_VIN, this channel measures the voltage present at the 12V power input.
  • CHANNEL_5V, this channel measures the voltage present at the 5V voltage rail provided to the Raspberry Pi.
Expand source code
def measure(self, channel):
    """
    Get the voltage values of various channels.  The returned value is 
    the voltage measured (in Volts) of the given channel.  The `channel`
    argument can be one of the following:

    * CHANNEL_VIN, this channel measures the voltage present at the 12V
    power input.
    * CHANNEL_5V, this channel measures the voltage present at the 5V
    voltage rail provided to the Raspberry Pi.
    """  
    self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_AD, self._u_int8(channel)])
    # Wait until it's ready.
    self._wait_until_not_busy()
    val = self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+2, 2)
    return (val[1]*0x100 + val[0])/1000
def power_off(self, t=5000)

Powers Vizy off. The t argument specifies how long to wait before turning off (specified in milliseconds). The vizy-powerd service calls this upon shutdown.

Expand source code
def power_off(self, t=5000):
    """
    Powers Vizy off. The `t` argument specifies how long 
    to wait before turning off (specified in milliseconds).  The 
    vizy-powerd service calls this upon shutdown.
    """
    self.bus.write_i2c_block_data(self.addr, 38, [0x1f, int(t/100)])
def power_off_requested(self)

Returns True if Vizy's button is held down for more than 5 seconds indicating that the user wishes to initiate safe shutdown and power off. Returns False otherwise.

This is used by the vizy-powerd service.

Expand source code
def power_off_requested(self):
    """
    Returns `True` if Vizy's button is held down for more than 5 seconds
    indicating that the user wishes to initiate safe shutdown and power
    off.  Returns `False` otherwise.

    This is used by the vizy-powerd service. 
    """
    button = self.bus.read_i2c_block_data(self.addr, 38, 1)
    if button[0]==0x0f:
        return True
    else:
        return False
def power_on_alarm_date(self, datetime_=None)

If you wish to power off your Vizy and have it "wake up" at a specified time and date, call this method with the desired datetime object and initiate a shutdown. (e.g. sudo shutdown now).

The code below tells Vizy to turn on on December 2, 2022, 11:18am.

import vizypowerboard as vpb
from datetime import datetime
v = vpb.VizyPowerBoard()
d=datetime(year=2022, month=12, day=2, hour=11, minute=18, second=0)
v.power_on_alarm_date(d)

Args

datetime_ : datetime, optional, default=None
datetime object that specifies the date/time to "wake up" (turn on).

Returns

Calling without a datetime object returns a datetime object reflecting the active alarm time. If there is no active alarm, None is returned.

Notes

  • Once setting the alarm date, Vizy will retain it even if Vizy loses power for extended periods of time.
  • If the alarm expires while Vizy is on, Vizy will emit a buzzer tone and remain on.
  • If the alarm expires while Vizy is off (but plugged into and receiving power), Vizy will turn on.
  • If the alarm expires while Vizy is unplugged from (or not receiving) power, Vizy will turn on as soon as it receives power.
Expand source code
def power_on_alarm_date(self, datetime_=None):
    """
    If you wish to power off your Vizy and have it "wake up" at a
    specified time and date, call this method with the desired
    datetime object and initiate a shutdown. (e.g. `sudo shutdown now`).


    The code below tells Vizy to turn on on December 2, 2022, 11:18am.

        import vizypowerboard as vpb
        from datetime import datetime
        v = vpb.VizyPowerBoard()
        d=datetime(year=2022, month=12, day=2, hour=11, minute=18, second=0)
        v.power_on_alarm_date(d)

    Args:
      datetime_ (datetime, optional, default=None): `datetime` object that
        specifies the date/time to "wake up" (turn on).

    Returns:
      Calling without a datetime object returns a `datetime` object 
      reflecting the active alarm time.  If there is no active alarm,
      `None` is returned.
       
    Notes:
      * Once setting the alarm date, Vizy will retain it even if Vizy loses
      power for extended periods of time.
      * If the alarm expires while Vizy is on, Vizy will emit a buzzer tone
      and remain on.
      * If the alarm expires while Vizy is off (but plugged into and
      receiving power), Vizy will turn on.
      * If the alarm expires while Vizy is unplugged from (or not receiving)
      power, Vizy will turn on as soon as it receives power.  
    """
    if datetime_ is None:
        t = self.bus.read_i2c_block_data(self.addr, 41, 6)
        if t[5]==0:
            return None
        return datetime.datetime(year=self._bcd2decimal(t[5])+2016, month=self._bcd2decimal(t[4]), day=self._bcd2decimal(t[3]), hour=self._bcd2decimal(t[2]), minute=self._bcd2decimal(t[1]), second=self._bcd2decimal(t[0]))
    t = [self._decimal2bcd(datetime_.second), self._decimal2bcd(datetime_.minute), self._decimal2bcd(datetime_.hour), self._decimal2bcd(datetime_.day), self._decimal2bcd(datetime_.month), self._decimal2bcd(datetime_.year-2016)]
    self.bus.write_i2c_block_data(self.addr, 41, t)
def power_on_alarm_seconds(self, seconds=None)

Allows you to specify a power on alarm in seconds in the future. For example, if you wish for Vizy to turn back on in 5 minutes, you would call power_on_alarm_seconds(300) and then initiate a shutdown. See VizyPowerBoard.power_on_alarm_date() for more information about the power on alarm.

Args

seconds : integer, optional, default=None
Number of seconds in the future you wish Vizy to turn on in.

Returns

Calling this method without arguments returns the number of seconds until the alarm expires. If no alarm is pending, None is returned.

Expand source code
def power_on_alarm_seconds(self, seconds=None):
    """
    Allows you to specify a power on alarm in seconds in the future. 
    For example, if you wish for Vizy to turn back on in 5 minutes, you
    would call `power_on_alarm_seconds(300)` and then initiate a shutdown.
    See `VizyPowerBoard.power_on_alarm_date()` for more information about the power on 
    alarm.

    Args:
      seconds (integer, optional, default=None): Number of seconds in the
        future you wish Vizy to turn on in.

    Returns:
      Calling this method without arguments returns the number of seconds
      until the alarm expires.  If no alarm is pending, `None` is returned.
    """
    if seconds is None:
        pod = self.power_on_alarm_date()
        if pod is None:
            return None
        diff = pod - self.rtc()
        return diff.days*86400+diff.seconds 
    # Add seconds to current time and set power on alarm    
    self.power_on_alarm_date(self.rtc()+datetime.timedelta(seconds=seconds))
def power_on_source(self)

Returns the source of what turned on Vizy for the current power cycle. It is one of either:

  • POWER_ON_SOURCE_ALARM, indicates that Vizy was powered on by the power on alarm expiring. See power_on_alarm_date() and power_on_alarm_seconds().
  • POWER_ON_SOURCE_POWER_BUTTON, indicates that Vizy was powered on by someone pressing the button.
  • POWER_ON_SOURCE_12V = indicates that Vizy was powered on by power being applied to 12V power input. This only applies if the dip switch power mode allows powering on by plugging in power via the 12V power input.
  • POWER_ON_SOURCE_5V, indicates that Vizy was powered on by applying power to the Raspberry Pi's USB-C power input.
Expand source code
def power_on_source(self):
    """
    Returns the source of what turned on Vizy for the current power cycle.
    It is one of either:

    * POWER_ON_SOURCE_ALARM, indicates that Vizy was powered on
    by the power on alarm expiring.  See power_on_alarm_date() and
    power_on_alarm_seconds().
    * POWER_ON_SOURCE_POWER_BUTTON, indicates that Vizy was powered 
    on by someone pressing the button.
    * POWER_ON_SOURCE_12V = indicates that Vizy was powered on
    by power being applied to 12V power input.  This only applies if the
    dip switch power mode allows powering on by plugging in power via the
    12V power input.
    * POWER_ON_SOURCE_5V, indicates that Vizy was powered on by applying
    power to the Raspberry Pi's USB-C power input.
    """
    source = self.bus.read_i2c_block_data(self.addr, 40, 1)[0]
    return source
def resource_url(self)

Returns the url of a JSON file that contains information about resources, such as the location of the latest version of this code, latest firmware, etc.

Expand source code
def resource_url(self):
    """
    Returns the url of a JSON file that contains information about 
    resources, such as the location of the latest version of this code, 
    latest firmware, etc.
    """
    chars = self.bus.read_i2c_block_data(self.addr, 6, 32)
    s = ''
    for c in chars:
        if c==0: # read up to the null character
            break
        s += chr(c)
    return s
def rtc(self, datetime_=None)

Set or get the real-time clock time/date. The Vizy power board has a battery-backed real-time clock that keeps track of time/date, power alarms, etc. even while Vizy is receiving no power. Passing in a datetime object sets the time/date.

Calling this method with no arguments returns a datetime object representing the current date/time.

For example, the code below sets the date to December 2, 2020, 11:18am:

from datetime import datetime
import vizypowerboard as vpb
v = vpb.VizyPowerBoard()
t = datetime(year=2020, month=12, day=2, hour=11, minute=18, second=0)
v.rtc(t)
Expand source code
def rtc(self, datetime_=None):
    """
    Set or get the real-time clock time/date.  The Vizy power board has a 
    battery-backed real-time clock that keeps track of time/date, power 
    alarms, etc. even while Vizy is receiving no power.  Passing in a
    datetime object sets the time/date.  

    Calling this method with no
    arguments returns a datetime object representing the current 
    date/time. 

    For example, the code below sets the date to December 2, 2020, 11:18am:

        from datetime import datetime
        import vizypowerboard as vpb
        v = vpb.VizyPowerBoard()
        t = datetime(year=2020, month=12, day=2, hour=11, minute=18, second=0)
        v.rtc(t)
    """
    if datetime_ is None:
        # Initiate RTC retrieval.
        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC])
        # Wait until it's ready.
        self._wait_until_not_busy()

        t = self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 8)
        return datetime.datetime(year=self._bcd2decimal(t[7])+2016, month=self._bcd2decimal(t[6]), day=self._bcd2decimal(t[4]), hour=self._bcd2decimal(t[3]), minute=self._bcd2decimal(t[2]), second=self._bcd2decimal(t[1]))

    t = [EXEC_RTC|EXEC_WRITE, 0, self._decimal2bcd(datetime_.second), self._decimal2bcd(datetime_.minute), self._decimal2bcd(datetime_.hour), self._decimal2bcd(datetime_.day), 0, self._decimal2bcd(datetime_.month), self._decimal2bcd(datetime_.year-2016)]
    self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, t)
    self._wait_until_not_busy()
def rtc_adjust(self, val=None)

Set or get the real-time clock adjustment. Vizy's real-time clock crystal has an accuracy of 20ppm, which means that it can lose or gain up to 20 seconds for every 1 million elapsed seconds. Normally, this isn't an issue, but if Vizy spends a lengthy period of time (months) without Internet access, it could lose or gain minutes, which depending on the application could be significant. The adjustment value can offset this inaccuracy. The val argument can range between -128 and 127 and has a multiplier of 2.170 ppm.

For example, if the RTC is gaining 10 seconds every 1 million seconds, you would call rtc_adjust(-5). If the RTC is losing 10 seconds every million seconds you would call rtc_adjust(5).

The adjustment value is retained by the real-time clock even when Vizy's power is removed.

Expand source code
def rtc_adjust(self, val=None):
    """
    Set or get the real-time clock adjustment.  Vizy's real-time clock 
    crystal has an accuracy of 20ppm, which means that it can lose or gain 
    up to 20 seconds for every 1 million elapsed seconds.  Normally, this
    isn't an issue, but if Vizy spends a lengthy period of time (months)
    without Internet access, it could lose or gain minutes, which 
    depending on the application could be significant. The adjustment
    value can offset this inaccuracy.  The `val` argument can range 
    between -128 and 127 and has a multiplier of 2.170 ppm.  

    For example, 
    if the RTC is gaining 10 seconds every 1 million seconds, you would 
    call `rtc_adjust(-5)`.  If the RTC is losing 10 seconds every million
    seconds you would call `rtc_adjust(5)`.
    
    The adjustment value is retained by the real-time clock even when 
    Vizy's power is removed. 
    """
    if val is None:
        self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC_CALIBRATE])
        # Wait until it's ready.
        self._wait_until_not_busy()
        return self.bus.read_i2c_block_data(self.addr, EXEC_OFFSET+1, 1)[0]

    self.bus.write_i2c_block_data(self.addr, EXEC_OFFSET, [EXEC_RTC_CALIBRATE|EXEC_WRITE, self._int8(val)])
    self._wait_until_not_busy()
def rtc_set_system_datetime(self, datetime_=None)

A convenience method that sets the system time/date based on the real-time time/date. This is called by vizy-powerd upon power-up.

Expand source code
def rtc_set_system_datetime(self, datetime_=None):
    """
    A convenience method that sets the system time/date based on the 
    real-time time/date.  This is called by vizy-powerd upon power-up. 
    """
    if os.geteuid()!=0:
        raise PermissionError("You need root permission to set the time/date.")
    if datetime_ is None:
        datetime_ = self.rtc()
    s = datetime_.isoformat()
    os.system(f"sudo date -s {s}")
def uuid(self)

Returns a 16-byte unique ID that can be used an a unique ID for your Vizy camera. This unique ID is stored on the Vizy Power Board and remains constant regardless of firmware upgrades, etc.

Expand source code
def uuid(self):
    """
    Returns a 16-byte unique ID that can be used an a unique
    ID for your Vizy camera.  This unique ID is stored on the Vizy Power
    Board and remains constant regardless of firmware upgrades, etc.
    """
    return self.bus.read_i2c_block_data(self.addr, 22, 16)
def vcc12(self, state=None)

If state is True, the 12V output on Vizy's I/O connector (pin 2) will be enabled and output 12V. If state is False, the 12V output will be disabled. Calling without arguments returns its current state.

Expand source code
def vcc12(self, state=None):
    """
    If `state` is `True`, the 12V output on Vizy's I/O connector (pin 2) will be enabled and output 12V.  If `state` is `False`, the 12V output
    will be disabled.  Calling without arguments returns its current state.
    """ 
    config = self.bus.read_i2c_block_data(self.addr, 48, 1)
    if state is None:
        return True if config&0x01 else False
    if state:
        config[0] |= 0x01
    else:
        config[0] &= ~0x01

    self.bus.write_i2c_block_data(self.addr, 48, config)
def vcc5(self, state=None)

If state is True, the 5V output on Vizy's I/O connector (pin 3) will be enabled and output 5V. If state is False, the 5V output will be disabled. Calling without arguments returns its current state.

Expand source code
def vcc5(self, state=None):
    """
    If `state` is `True`, the 5V output on Vizy's I/O connector (pin 3) will
    be enabled and output 5V.  If `state` is `False`, the 5V output will be
    disabled.  Calling without arguments returns its current state.
    """ 
    if state is None:
        return True if config&0x02 else False
    if state:
        config[0] |= 0x02
    else:
        config[0] &= ~0x02

    self.bus.write_i2c_block_data(self.addr, 48, config)
wiki/test.txt · Last modified: 2021/10/16 18:30 by vizycam