<commit_before>
"""
Rickie Kerndt <rkerndt@cs.uoregon.edu
bike_power_center.py
Provides services for monitoring and managing bicycle electrical systems
"""

from __future__ import print_function
import sys
import os
sys.path.append('/home/pi/lib/python')

import signal
import ina219
import power_meter as pm
import rgb
from threading import Event, Timer
from datetime import datetime
import smbus

class PowerCenter():
    """
    Methods for monitoring power generating, consumption, and modifying on bicycle electronics to control
    power consumption.
    """

    # battery states
    CHARGING    = 0
    FULL_CHARGE = 1
    ON_BATTERY  = 2
    LOW_BATTERY = 3
    LOW_BATTERY_VOLTS = 3.2
    LOW_BATTERY_HYSTERESIS = 0.02   # once low battery state is reached voltage must rise this additional
                                    # value to obtain on_battery state
    FULL_CHARGE_AMPS_POS = 0.003    # maximum batt amp draw on full charge with external power
    FULL_CHARGE_AMPS_NEG = 0.000    # maximum batt charge amp considered at full charge
    FULL_CHARGE_HYSTERESIS = 0.01

    LOG_FILE = "/var/log/power_center.log"
    DATA_FILE_PATH = "/var/log"
    DATA_FILE_PREFIX = "power_center"
    DATA_FILE_SUFFIX = ".csv"
    PID_FILE = "/var/run/power_center.pid"

    # power center states
    RUNNING = 0
    SHUTDOWN = 1
    TERMINATE = 2

    # timer intervals
    CHECK_BATTERY_INTERVAL = 1.0
    LOG_DATA_INTERVAL = 1.0
    POWER_MONITOR_INTERVAL = 0.08

    DEBUG = False

    # ina219 defaults
    INA219_I2C_BUS = 1
    BATT_I2C_ADDRESS = 0x40
    EXT_I2C_ADDRESS = 0x41
    INA219_POLL_SECS = 0.1
    INA219_SAMPLES = 128

    # LED BCM pin numbers
    RED_PIN = 21
    GREEN_PIN = 20
    BLUE_PIN = 16

    def __init__(self, i2c_bus=INA219_I2C_BUS, batt_addr=BATT_I2C_ADDRESS, ext_addr=EXT_I2C_ADDRESS):
        """
        Instantiates power center which for now just monitors battery and external power ina219 devices.
        """
        self._trigger = Event()
        self._state = None
        self.battery_power = pm.PowerMeter(pm.INA219_Monitor(PowerCenter.INA219_POLL_SECS, PowerCenter.BATT_I2C_ADDRESS,
                                                             PowerCenter.INA219_I2C_BUS, ina219.INA219_CALIB_32V_1A,
                                                             PowerCenter.INA219_SAMPLES),
                                           self._trigger)
        self.dyno_power = pm.PowerMeter(pm.INA219_Monitor(PowerCenter.INA219_POLL_SECS, PowerCenter.EXT_I2C_ADDRESS,
                                                          PowerCenter.INA219_I2C_BUS, ina219.INA219_CALIB_32V_2A,
                                                          PowerCenter.INA219_SAMPLES),
                                        self._trigger)
        self.log_fh = None
        self.data_fh = None
        self.battery_status = None
        self.battery_status_led = rgb.RGB_led(PowerCenter.RED_PIN, PowerCenter.GREEN_PIN, PowerCenter.BLUE_PIN)
        self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery)
        self._log_timer = None
        self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync)
        self._debug = PowerCenter.DEBUG

        signal.signal(signal.SIGTERM, self._sig_handler)
        signal.signal(signal.SIGINT, self._sig_handler)
        signal.signal(signal.SIGUSR1, self._sig_handler)
        signal.signal(signal.SIGHUP, self._sig_handler)

        try:
            fh = open(PowerCenter.PID_FILE, 'w')
            print('%d' % os.getpid(), file=fh)
            fh.close()
        except:
            print('Failed to open %s for pid file: %s' % (PowerCenter.PID_FILE, sys.exc_info()[0]), file=sys.stderr)

    def run(self):
        """
        Starts things up and launches timers
        """
        self._state = PowerCenter.RUNNING
        self.battery_power.open()
        self.dyno_power.open()
        self._power_monitor_timer.start()
        self._battery_timer.start()

        try:
            self.log_fh = open(PowerCenter.LOG_FILE, 'a')
            self._log_message("Power Center starting")
        except:
            print('Failed to open %s for logging: %s' % (PowerCenter.LOG_FILE, sys.exc_info()[0]), file=sys.stderr)
            self.log_fh = None

        while self._state != PowerCenter.TERMINATE:
            signal.pause()
            if self._debug:
                self._log_message('Caught Interrupt')

    def close(self):
        """
        Shuts things down and cancels timers. Set status flag to TERMINATE
        """
        # set state
        if self._debug:
            self._log_message('Setting state to SHUTDOWN')
        self._status = PowerCenter.SHUTDOWN

        # shutdown timers
        if self._power_monitor_timer:
            if self._debug:
                self._log_message('Canceling power monitor timer')
            self._power_monitor_timer.cancel()
        if self._log_timer:
            if self._debug:
                self._log_message('Canceling log timer')
            self._log_timer.cancel()
        if self._battery_timer:
            if self._debug:
                self._log_message('Canceling battery timer')
            self._battery_timer.cancel()

        # shutdown power monitors
        if self._debug:
            self._log_message('Stopping battery power meter')
        self.battery_power.stop()
        if self._debug:
            self._log_message('Stopping dyno power meter')
        self.dyno_power.stop()
        self._trigger.set()
        self.battery_power.close()
        if self._debug:
            self._log_message('Battery power meter closed')
        self.dyno_power.close()
        if self._debug:
            self._log_message('Dyno power meter closed')

        # shutdown LED(s)
        if self.battery_status_led:
            self.battery_status_led.close()

        # shutdown logging
        if self.log_fh:
            self._log_message('Shutting down Power Center')
            self.log_fh.flush()
            self.log_fh.close()
            self.log_fh = None

        # set state
        if self._debug:
            self._log_message('Setting state to TERMINATE')
        self._state = PowerCenter.TERMINATE

    def _power_monitor_sync(self):
        """
        Sets event controlling synchronization of power monitors
        """
        self._trigger.set()
        if self._state == PowerCenter.RUNNING:
            self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync)
            self._power_monitor_timer.start()

    def _check_battery(self):
        """
        Runs battery check and then relaunches timer
        """
        self._update_battery_status()
        if self._state == PowerCenter.RUNNING:
            self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery)
            self._battery_timer.start()

    def _update_battery_status(self):
        """
        Checks battery voltage and amperage updating LED indication if there is a change
        CHARGING := negative battery amperage
        FULL_CHARGE := positive battery amperage below FULL_CHARGE_AMPS
        ON_BATTERY := positive battery amperage
        LOW_BATTERY := battery voltage <= LOW_BATTERY_VOLTS
        """
        amperage = self.battery_power.amp
        voltage = self.battery_power.volt

        if voltage <= PowerCenter.LOW_BATTERY_VOLTS:
            if self.battery_status != PowerCenter.LOW_BATTERY:
                self._log_message("Low battery after %f wattseconds" % self.battery_power.watt_seconds)
                self.battery_status = PowerCenter.LOW_BATTERY
                self.battery_status_led.red()
        elif amperage < PowerCenter.FULL_CHARGE_AMPS_NEG:
            if ((self.battery_status != PowerCenter.CHARGING) and
                not ((self.battery_status == PowerCenter.FULL_CHARGE_AMPS_NEG) and
                    (amperage < PowerCenter.FULL_CHARGE_AMPS_NEG + PowerCenter.FULL_CHARGE_HYSTERESIS))):
                self._log_message("Battery is charging")
                self.battery_status = PowerCenter.CHARGING
                self.battery_status_led.green(True)
        elif amperage < PowerCenter.FULL_CHARGE_AMPS_POS:
            if self.battery_status != PowerCenter.FULL_CHARGE:
                # when transitioning from charging to full charge zero wattseconds
                # so we can improve tracking battery capacity
                if self.battery_status == PowerCenter.CHARGING:
                    self._log_message("Battery has reached full charge")
                    self.battery_power._watt_seconds = 0.0
                self.battery_status = PowerCenter.FULL_CHARGE
                self.battery_status_led.green()
        else:
            if ((self.battery_status != PowerCenter.ON_BATTERY) and
               not ((self.battery_status == PowerCenter.LOW_BATTERY) and
                    (voltage < PowerCenter.LOW_BATTERY_VOLTS + PowerCenter.LOW_BATTERY_HYSTERESIS))):
                self._log_message("On battery")
                self.battery_status = PowerCenter.ON_BATTERY
                self.battery_status_led.blue()

    def _output_log(self):
        """
        Updates logs and relaunches timer
        """
        self._log_power_data()
        if self._state == PowerCenter.RUNNING:
            self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log)
            self._log_timer.start()

    def _log_power_data(self):
        """
        writes the battery and dyno power values to the log file
        """
        if self.data_fh:
            battery_data = self.battery_power.get()
            battery_watt_seconds = self.battery_power.watt_seconds
            dyno_data = self.dyno_power.get()
            dyno_watt_seconds = self.dyno_power.watt_seconds
            message = '"%s",%s,%f,%s,%f' % (datetime.now(), battery_data.csv(), battery_watt_seconds, dyno_data.csv(), dyno_watt_seconds)
            print(message, file=self.data_fh)

    def _log_message(self, message):
        """
        writes message to log file inserting a timestamp
        :param message: string
        """
        battery_data = self.battery_power.get()
        battery_watt_seconds = self.battery_power.watt_seconds
        dyno_data = self.dyno_power.get()
        dyno_watt_seconds = self.dyno_power.watt_seconds
        power_data = '%s,%f,%s,%f' % (battery_data.csv(), battery_watt_seconds, dyno_data.csv(), dyno_watt_seconds)

        if self.log_fh:
            print('"%s", %s, %s' % (datetime.now(), message, power_data), file=self.log_fh)
            self.log_fh.flush()
        if self._debug:
            print('"%s", %s, %s' % (datetime.now(), message, power_data), file=sys.stderr)

    def _sig_handler(self, signum, frame):
        """
        Handles system signals. SIGINT, SIGTERM to gracefully terminate operation. SIGUSR1 will toggle
        data logging. SIGHUP restarts log file (send after rotating logs)
        :param signum:
        :param frame:
        """
        if signum in [signal.SIGTERM, signal.SIGINT]:
            if self._debug:
                self._log_message('Received signal %d' % signum)
            self.close()

        elif signum == signal.SIGUSR1:
            if self.data_fh:
                self._log_message('Data logging disabled')
                self.data_fh.flush()
                self.data_fh.close()
                self.data_fh = None
            else:
                mark = datetime.now()
                time_string = mark.strftime('%y%m%d_%H%M%S')
                data_file = PowerCenter.DATA_FILE_PATH + '/' + PowerCenter.DATA_FILE_PREFIX + time_string + PowerCenter.DATA_FILE_SUFFIX
                try:
                    self.data_fh = open(data_file, 'a')
                except:
                    print('Failed to open %s for logging: %s' % (data_file, sys.exc_info()[0]), file=sys.stderr)
                    self.data_fh = None
                    return
                self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log)
                self._log_timer.start()
                self._log_message('Data logging enabled')

        elif signum == signal.SIGHUP:
            if self.log_fh:
                self.log_fh.close()
            try:
                self.log_fh = open(PowerCenter.LOG_FILE, 'a')
                self._log_message("Log file restart")
            except:
                print('Failed to open %s for logging: %s' % (PowerCenter.LOG_FILE, sys.exc_info()[0]), file=sys.stderr)
                self.log_fh = None

def main():
    """
    Instantiates PowerCenter and waits for termination
    """
    if len(sys.argv) == 4:
        i2c_bus = int(sys.argv[1])
        batt_addr = int(sys.argv[2])
        ext_addr = int(sys.argv[3])
    else:
        i2c_bus = PowerCenter.INA219_I2C_BUS
        batt_addr = PowerCenter.BATT_I2C_ADDRESS
        ext_addr = PowerCenter.EXT_I2C_ADDRESS

    pc = PowerCenter(i2c_bus, batt_addr, ext_addr)
    pc.run()

if __name__ == "__main__":
    main()<commit_msg>handling null data<commit_after>
"""
Rickie Kerndt <rkerndt@cs.uoregon.edu
bike_power_center.py
Provides services for monitoring and managing bicycle electrical systems
"""

from __future__ import print_function
import sys
import os
sys.path.append('/home/pi/lib/python')

import signal
import ina219
import power_meter as pm
import rgb
from threading import Event, Timer
from datetime import datetime
import smbus

class PowerCenter():
    """
    Methods for monitoring power generating, consumption, and modifying on bicycle electronics to control
    power consumption.
    """

    # battery states
    CHARGING    = 0
    FULL_CHARGE = 1
    ON_BATTERY  = 2
    LOW_BATTERY = 3
    LOW_BATTERY_VOLTS = 3.2
    LOW_BATTERY_HYSTERESIS = 0.02   # once low battery state is reached voltage must rise this additional
                                    # value to obtain on_battery state
    FULL_CHARGE_AMPS_POS = 0.003    # maximum batt amp draw on full charge with external power
    FULL_CHARGE_AMPS_NEG = 0.000    # maximum batt charge amp considered at full charge
    FULL_CHARGE_HYSTERESIS = 0.01

    LOG_FILE = "/var/log/power_center.log"
    DATA_FILE_PATH = "/var/log"
    DATA_FILE_PREFIX = "power_center"
    DATA_FILE_SUFFIX = ".csv"
    PID_FILE = "/var/run/power_center.pid"

    # power center states
    RUNNING = 0
    SHUTDOWN = 1
    TERMINATE = 2

    # timer intervals
    CHECK_BATTERY_INTERVAL = 1.0
    LOG_DATA_INTERVAL = 1.0
    POWER_MONITOR_INTERVAL = 0.08

    DEBUG = False

    # ina219 defaults
    INA219_I2C_BUS = 1
    BATT_I2C_ADDRESS = 0x40
    EXT_I2C_ADDRESS = 0x41
    INA219_POLL_SECS = 0.1
    INA219_SAMPLES = 128

    # LED BCM pin numbers
    RED_PIN = 21
    GREEN_PIN = 20
    BLUE_PIN = 16

    def __init__(self, i2c_bus=INA219_I2C_BUS, batt_addr=BATT_I2C_ADDRESS, ext_addr=EXT_I2C_ADDRESS):
        """
        Instantiates power center which for now just monitors battery and external power ina219 devices.
        """
        self._trigger = Event()
        self._state = None
        self.battery_power = pm.PowerMeter(pm.INA219_Monitor(PowerCenter.INA219_POLL_SECS, PowerCenter.BATT_I2C_ADDRESS,
                                                             PowerCenter.INA219_I2C_BUS, ina219.INA219_CALIB_32V_1A,
                                                             PowerCenter.INA219_SAMPLES),
                                           self._trigger)
        self.dyno_power = pm.PowerMeter(pm.INA219_Monitor(PowerCenter.INA219_POLL_SECS, PowerCenter.EXT_I2C_ADDRESS,
                                                          PowerCenter.INA219_I2C_BUS, ina219.INA219_CALIB_32V_2A,
                                                          PowerCenter.INA219_SAMPLES),
                                        self._trigger)
        self.log_fh = None
        self.data_fh = None
        self.battery_status = None
        self.battery_status_led = rgb.RGB_led(PowerCenter.RED_PIN, PowerCenter.GREEN_PIN, PowerCenter.BLUE_PIN)
        self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery)
        self._log_timer = None
        self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync)
        self._debug = PowerCenter.DEBUG

        signal.signal(signal.SIGTERM, self._sig_handler)
        signal.signal(signal.SIGINT, self._sig_handler)
        signal.signal(signal.SIGUSR1, self._sig_handler)
        signal.signal(signal.SIGHUP, self._sig_handler)

        try:
            fh = open(PowerCenter.PID_FILE, 'w')
            print('%d' % os.getpid(), file=fh)
            fh.close()
        except:
            print('Failed to open %s for pid file: %s' % (PowerCenter.PID_FILE, sys.exc_info()[0]), file=sys.stderr)

    def run(self):
        """
        Starts things up and launches timers
        """
        self._state = PowerCenter.RUNNING
        self.battery_power.open()
        self.dyno_power.open()
        self._power_monitor_timer.start()
        self._battery_timer.start()

        try:
            self.log_fh = open(PowerCenter.LOG_FILE, 'a')
            self._log_message("Power Center starting")
        except:
            print('Failed to open %s for logging: %s' % (PowerCenter.LOG_FILE, sys.exc_info()[0]), file=sys.stderr)
            self.log_fh = None

        while self._state != PowerCenter.TERMINATE:
            signal.pause()
            if self._debug:
                self._log_message('Caught Interrupt')

    def close(self):
        """
        Shuts things down and cancels timers. Set status flag to TERMINATE
        """
        # set state
        if self._debug:
            self._log_message('Setting state to SHUTDOWN')
        self._status = PowerCenter.SHUTDOWN

        # shutdown timers
        if self._power_monitor_timer:
            if self._debug:
                self._log_message('Canceling power monitor timer')
            self._power_monitor_timer.cancel()
        if self._log_timer:
            if self._debug:
                self._log_message('Canceling log timer')
            self._log_timer.cancel()
        if self._battery_timer:
            if self._debug:
                self._log_message('Canceling battery timer')
            self._battery_timer.cancel()

        # shutdown power monitors
        if self._debug:
            self._log_message('Stopping battery power meter')
        self.battery_power.stop()
        if self._debug:
            self._log_message('Stopping dyno power meter')
        self.dyno_power.stop()
        self._trigger.set()
        self.battery_power.close()
        if self._debug:
            self._log_message('Battery power meter closed')
        self.dyno_power.close()
        if self._debug:
            self._log_message('Dyno power meter closed')

        # shutdown LED(s)
        if self.battery_status_led:
            self.battery_status_led.close()

        # shutdown logging
        if self.log_fh:
            self._log_message('Shutting down Power Center')
            self.log_fh.flush()
            self.log_fh.close()
            self.log_fh = None

        # set state
        if self._debug:
            self._log_message('Setting state to TERMINATE')
        self._state = PowerCenter.TERMINATE

    def _power_monitor_sync(self):
        """
        Sets event controlling synchronization of power monitors
        """
        self._trigger.set()
        if self._state == PowerCenter.RUNNING:
            self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync)
            self._power_monitor_timer.start()

    def _check_battery(self):
        """
        Runs battery check and then relaunches timer
        """
        self._update_battery_status()
        if self._state == PowerCenter.RUNNING:
            self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery)
            self._battery_timer.start()

    def _update_battery_status(self):
        """
        Checks battery voltage and amperage updating LED indication if there is a change
        CHARGING := negative battery amperage
        FULL_CHARGE := positive battery amperage below FULL_CHARGE_AMPS
        ON_BATTERY := positive battery amperage
        LOW_BATTERY := battery voltage <= LOW_BATTERY_VOLTS
        """
        amperage = self.battery_power.amp
        voltage = self.battery_power.volt

        if voltage <= PowerCenter.LOW_BATTERY_VOLTS:
            if self.battery_status != PowerCenter.LOW_BATTERY:
                self._log_message("Low battery after %f wattseconds" % self.battery_power.watt_seconds)
                self.battery_status = PowerCenter.LOW_BATTERY
                self.battery_status_led.red()
        elif amperage < PowerCenter.FULL_CHARGE_AMPS_NEG:
            if ((self.battery_status != PowerCenter.CHARGING) and
                not ((self.battery_status == PowerCenter.FULL_CHARGE_AMPS_NEG) and
                    (amperage < PowerCenter.FULL_CHARGE_AMPS_NEG + PowerCenter.FULL_CHARGE_HYSTERESIS))):
                self._log_message("Battery is charging")
                self.battery_status = PowerCenter.CHARGING
                self.battery_status_led.green(True)
        elif amperage < PowerCenter.FULL_CHARGE_AMPS_POS:
            if self.battery_status != PowerCenter.FULL_CHARGE:
                # when transitioning from charging to full charge zero wattseconds
                # so we can improve tracking battery capacity
                if self.battery_status == PowerCenter.CHARGING:
                    self._log_message("Battery has reached full charge")
                    self.battery_power._watt_seconds = 0.0
                self.battery_status = PowerCenter.FULL_CHARGE
                self.battery_status_led.green()
        else:
            if ((self.battery_status != PowerCenter.ON_BATTERY) and
               not ((self.battery_status == PowerCenter.LOW_BATTERY) and
                    (voltage < PowerCenter.LOW_BATTERY_VOLTS + PowerCenter.LOW_BATTERY_HYSTERESIS))):
                self._log_message("On battery")
                self.battery_status = PowerCenter.ON_BATTERY
                self.battery_status_led.blue()

    def _output_log(self):
        """
        Updates logs and relaunches timer
        """
        self._log_power_data()
        if self._state == PowerCenter.RUNNING:
            self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log)
            self._log_timer.start()

    def _format_power_data(self):
        """
        Provides a string format of power values for battery and dyno with values separated by commas.
        If power data isn't yet available, then returns commas representing missing data.
        :return: string
        """

        battery_data = self.battery_power.get()
        if battery_data:
            battery_csv = battery_data.csv()
        else:
            battery_csv = ",,,,"
        battery_watt_seconds = self.battery_power.watt_seconds
        dyno_data = self.dyno_power.get()
        if dyno_data:
            dyno_csv = dyno_data.csv()
        else:
            dyno_csv = ",,,,"
        dyno_watt_seconds = self.dyno_power.watt_seconds

        return '%s,%f,%s,%f' % (battery_csv, battery_watt_seconds, dyno_csv, dyno_watt_seconds)

    def _log_power_data(self):
        """
        writes the battery and dyno power values to the log file
        """
        if self.data_fh:
            message = '"%s",%s' % (datetime.now(), self._format_power_data())
            print(message, file=self.data_fh)

    def _log_message(self, message):
        """
        writes message to log file inserting a timestamp
        :param message: string
        """
        power_data = self._format_power_data()

        if self.log_fh:
            print('"%s", %s, %s' % (datetime.now(), message, power_data), file=self.log_fh)
            self.log_fh.flush()
        if self._debug:
            print('"%s", %s, %s' % (datetime.now(), message, power_data), file=sys.stderr)

    def _sig_handler(self, signum, frame):
        """
        Handles system signals. SIGINT, SIGTERM to gracefully terminate operation. SIGUSR1 will toggle
        data logging. SIGHUP restarts log file (send after rotating logs)
        :param signum:
        :param frame:
        """
        if signum in [signal.SIGTERM, signal.SIGINT]:
            if self._debug:
                self._log_message('Received signal %d' % signum)
            self.close()

        elif signum == signal.SIGUSR1:
            if self.data_fh:
                self._log_message('Data logging disabled')
                self.data_fh.flush()
                self.data_fh.close()
                self.data_fh = None
            else:
                mark = datetime.now()
                time_string = mark.strftime('%y%m%d_%H%M%S')
                data_file = PowerCenter.DATA_FILE_PATH + '/' + PowerCenter.DATA_FILE_PREFIX + time_string + PowerCenter.DATA_FILE_SUFFIX
                try:
                    self.data_fh = open(data_file, 'a')
                except:
                    print('Failed to open %s for logging: %s' % (data_file, sys.exc_info()[0]), file=sys.stderr)
                    self.data_fh = None
                    return
                self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log)
                self._log_timer.start()
                self._log_message('Data logging enabled')

        elif signum == signal.SIGHUP:
            if self.log_fh:
                self.log_fh.close()
            try:
                self.log_fh = open(PowerCenter.LOG_FILE, 'a')
                self._log_message("Log file restart")
            except:
                print('Failed to open %s for logging: %s' % (PowerCenter.LOG_FILE, sys.exc_info()[0]), file=sys.stderr)
                self.log_fh = None

def main():
    """
    Instantiates PowerCenter and waits for termination
    """
    if len(sys.argv) == 4:
        i2c_bus = int(sys.argv[1])
        batt_addr = int(sys.argv[2])
        ext_addr = int(sys.argv[3])
    else:
        i2c_bus = PowerCenter.INA219_I2C_BUS
        batt_addr = PowerCenter.BATT_I2C_ADDRESS
        ext_addr = PowerCenter.EXT_I2C_ADDRESS

    pc = PowerCenter(i2c_bus, batt_addr, ext_addr)
    pc.run()

if __name__ == "__main__":
    main()<|endoftext|>


<commit_before># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

try:
    from itertools import ifilter as filter_
except ImportError:
    filter_ = filter

from httplib import (
    responses, INTERNAL_SERVER_ERROR, NOT_FOUND,
    UNSUPPORTED_MEDIA_TYPE, METHOD_NOT_ALLOWED)

from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.utility import dumps

class Resource(_Resource):
    """
    basic subclass of :class:`._Resource` for passing requests to
    specific methods
    """
    TEMPLATE = NotImplemented
    CONTENT_TYPES = set(["text/html", "application/json"])

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    @property
    def methods(self):
        """A set containing all the methods this resource implements."""
        methods = set()
        for method in ("get", "post", "put", "delete", "head"):
            for attribute_name in (method, "render_%s" % method.upper()):
                attribute = getattr(self, attribute_name, None)
                if attribute is not None:
                    methods.add(method)
        return methods

    def content_types(self, request, default=None):
        """Returns the content type(s) present in the request"""
        headers = request.requestHeaders.getRawHeaders("content-type")
        if isinstance(default, STRING_TYPES):
            default = [default]
        elif default is None:
            default = []
        return set(headers) if headers is not None else set(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally
        """
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.content_types(request, default="text/html")

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            deferred = html_error.render(
                code=code, code_msg=responses[code], message=message)
            deferred.addCallback(request.write).addCallback(
                lambda _: request.finish())

        elif "application/json" in content_types:
            request.setResponseCode(code)
            request.write(dumps(error=message))
            request.finish()

        else:
            request.setResponseCode(INTERNAL_SERVER_ERROR)
            request.write(
                dumps(
                    error="Can only handle text/html or application/json here"))
            request.finish()

    def render(self, request):
        # I this is a leaf, meaning the end of the uri branch, then
        # make sure the uri ends with '/'.  Proper REST-like services
        # differentiate between /foo and /foo/ but twisted generally does
        # not care so we enforce it ourselves.
        if self.isLeaf and not request.uri.endswith("/"):
            self.error(request, NOT_FOUND, "%s does not exist" % request.uri)
            return NOT_DONE_YET

        # make sure that the requested content type is supported
        content_type = self.content_types(request, default="text/html")
        if not self.CONTENT_TYPES & content_type:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_type)
            return NOT_DONE_YET

        # Try to find the method this web request is making by first trying
        # our usual convention then the 'standard' convention.
        request_methods = (request.method.lower(), "render_%s" % request.method)
        for method_name in request_methods:
            if hasattr(self, method_name):
                return getattr(self, method_name)(request)

        # If we could not find function to call for the given method
        # produce a warning.
        else:
            supported_methods = self.methods
            if not supported_methods:
                message = "%r does not support any methods" % request.uri
            else:
                message = "%r only supports the %s method(s)" % (
                    request.uri,
                    ", ".join(map(str.upper, supported_methods)))

            self.error(request, METHOD_NOT_ALLOWED, message)
            return NOT_DONE_YET
<commit_msg>unpacking data pre method call, switching to kwargs<commit_after># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

from json import loads
from httplib import (
    responses, INTERNAL_SERVER_ERROR, NOT_FOUND, BAD_REQUEST,
    UNSUPPORTED_MEDIA_TYPE, METHOD_NOT_ALLOWED)

try:
    from itertools import ifilter as filter_
except ImportError:
    filter_ = filter

from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.utility import dumps

class Resource(_Resource):
    """
    basic subclass of :class:`._Resource` for passing requests to
    specific methods
    """
    TEMPLATE = NotImplemented
    CONTENT_TYPES = set(["text/html", "application/json"])
    LOAD_DATA_FOR_METHODS = set(["POST", "PUT"])

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    @property
    def methods(self):
        """A set containing all the methods this resource implements."""
        methods = set()
        for method in ("get", "post", "put", "delete", "head"):
            for attribute_name in (method, "render_%s" % method.upper()):
                attribute = getattr(self, attribute_name, None)
                if attribute is not None:
                    methods.add(method)
        return methods

    def content_types(self, request, default=None):
        """Returns the content type(s) present in the request"""
        headers = request.requestHeaders.getRawHeaders("content-type")
        if isinstance(default, STRING_TYPES):
            default = [default]
        elif default is None:
            default = []
        return set(headers) if headers is not None else set(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally
        """
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.content_types(request, default="text/html")

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            deferred = html_error.render(
                code=code, code_msg=responses[code], message=message)
            deferred.addCallback(request.write).addCallback(
                lambda _: request.finish())

        elif "application/json" in content_types:
            request.setResponseCode(code)
            request.write(dumps(error=message))
            request.finish()

        else:
            request.setResponseCode(INTERNAL_SERVER_ERROR)
            request.write(
                dumps(
                    error="Can only handle text/html or application/json here"))
            request.finish()

    def render(self, request):
        # I this is a leaf, meaning the end of the uri branch, then
        # make sure the uri ends with '/'.  Proper REST-like services
        # differentiate between /foo and /foo/ but twisted generally does
        # not care so we enforce it ourselves.
        if self.isLeaf and not request.uri.endswith("/"):
            self.error(request, NOT_FOUND, "%s does not exist" % request.uri)
            return NOT_DONE_YET

        # make sure that the requested content type is supported
        content_type = self.content_types(request, default="text/html")
        if not self.CONTENT_TYPES & content_type:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_type)
            return NOT_DONE_YET

        # Try to find the method this web request is making by first trying
        # our usual convention then the 'standard' convention.
        request_methods = (request.method.lower(), "render_%s" % request.method)
        for method_name in request_methods:
            if hasattr(self, method_name):
                kwargs = {"request": request}

                # unpack the incoming data for the request
                if "application/json" in content_type \
                        and request.method in self.LOAD_DATA_FOR_METHODS:
                    try:
                        data = loads(request.content.read())
                    except ValueError as e:
                        self.error(
                            request, BAD_REQUEST,
                            "Failed to decode json data: %r" % e)
                        return NOT_DONE_YET
                    else:
                        kwargs.update(data=data)

                return getattr(self, method_name)(**kwargs)

        # If we could not find function to call for the given method
        # produce a warning.
        else:
            supported_methods = self.methods
            if not supported_methods:
                message = "%r does not support any methods" % request.uri
            else:
                message = "%r only supports the %s method(s)" % (
                    request.uri,
                    ", ".join(map(str.upper, supported_methods)))

            self.error(request, METHOD_NOT_ALLOWED, message)
            return NOT_DONE_YET
<|endoftext|>


<commit_before>"""
Error handlers for errors originating from the Submission systems.
"""

from __future__ import division

__author__ = "Michiel van Setten"
__copyright__ = " "
__version__ = "0.9"
__maintainer__ = "Michiel van Setten"
__email__ = "mjvansetten@gmail.com"
__date__ = "Mar 24, 2014"

import os
import os.path
import abc
import re

from abc import ABCMeta

from custodian.custodian import Custodian, ErrorHandler

class SchedulerErrorHandler(ErrorHandler):
    """
    Custodian error handler for scheduler related errors
      scheduler takes the schduler, currently: slurm is supported
    """
    def __init__(self, scheduler, err_file='queue.err', out_file='queue.out', run_err_file='run.err', batch_err_file='batch.err'):
        self.scheduler = scheduler
        self.err_file = err_file
        self.out_file = out_file
        self.run_err_file = run_err_file
        self.batch_err_file = batch_err_file
        self.errors = []

    def check(self):
        parser = AbstractErrorParser.factory('slurm', err_file=self.err_file, out_file=self.out_file, run_err_file=self.run_err_file, batch_err_file=self.batch_err_file)
        parser.parse()
        self.errors = parser.errors
        if len(self.errors) == 0:
            return False
        else:
            return True

    def correct(self):
        pass

class AbstractError():
    """
    Error base class
    """
    __metaclass__ = ABCMeta

    def __init__(self, errmsg, meta_data):
        self.errmsg = errmsg
        self.meta_data = meta_data
        self._message = str

    def __str__(self):
        self._message = '%s  %s\n' \
                        '  error message : %s \n' \
                        '  meta data     : %s' % (self.name, self.__doc__, self.errmsg, str(self.meta_data))
        return self._message

    @property
    def name(self):
        return self.__class__.__name__

class SubmitError(AbstractError):
    """
    Errors occurring at submission. The limits on the cluster may have changed.
    """
    def __init__(self, errmsg, meta_data):
        super(SubmitError, self).__init__(errmsg, meta_data)

class FullQueueError(AbstractError):
    """
    Errors occurring at submission. To many jobs in the queue / total cpus / .. .
    """
    def __init__(self, errmsg, meta_data):
        super(FullQueueError, self).__init__(errmsg, meta_data)

class DiskError(AbstractError):
    """
    Errors involving problems writing to disk.
    """
    def __init__(self, errmsg, meta_data):
        super(DiskError, self).__init__(errmsg, meta_data)

class TimeCancelError(AbstractError):
    """
    Error due to exceeding the time limit for the job.
      .limit will return the limit that was broken, None if it could not be determined.
    """
    def __init__(self, errmsg, meta_data):
        super(TimeCancelError, self).__init__(errmsg, meta_data)
        self.limit = None
        if meta_data is not None and 'broken_limit' in meta_data.keys():
                self.limit = meta_data['broken_limit']

class MemoryCancelError(AbstractError):
    """
    Error due to exceeding the memory limit for the job.
      .limit will return the limit that was broken, None if it could not be determined.
    """
    def __init__(self, errmsg, meta_data):
        super(MemoryCancelError, self).__init__(errmsg, meta_data)
        self.limit = None
        if meta_data is not None and 'broken_limit' in meta_data.keys():
                self.limit = meta_data['broken_limit']

class NodeFailureError(AbstractError):
    """
    Error due the hardware failure of a specific node.
     .node will return the problematic node, None if it could not be determined.
    """
    def __init__(self, errmsg, meta_data):
        super(NodeFailureError, self).__init__(errmsg, meta_data)
        self.node = None
        if len(meta_data) > None and 'node' in meta_data.keys():
                self.node = meta_data['node']

class AbstractErrorParser():
    """
    Abstract class for parsing errors originating from the scheduler system and error that are not reported by the
    program itself, i.e. segmentation faults.

    A concrete implementation of this class for a specific sceduler needs a class attribute ERRORS for containing a
    dictionary specifying error:

    ERRORS = {ErrorClass: {'filespecifier' : {'string': "the string to be lookedfor",
                                              'metafilter': "string specifing the regular exprecion to obtian the meta data"}}

    """
    __metaclass__ = ABCMeta

    def __init__(self, err_file, out_file, run_err_file, batch_err_file):
        self.files = {'err': err_file, 'out': out_file, 'run_err': run_err_file, 'batch_err': batch_err_file}
        self.errors = []
        return

    def factory(scheduler, *kwargs):
        if scheduler == "slurm":
            return SlurmErrorParser(*kwargs)
        elif scheduler == "pbs":
            return PBSErrorParse(*kwargs)
        assert 0, "undefined scheduler " + scheduler
    factory = staticmethod(factory)

    @staticmethod
    def extract_metadata(message, metafilter):
        meta_dict = {}
        for key in metafilter.keys():
            match = re.match(metafilter[key][0], message)
            if match is not None:
                meta_dict.update({key: re.match(metafilter[key][0], message).group(metafilter[key][1])})
        return meta_dict

    def parse_single(self, errmsg):
        """
        Parse the provided files for the corresponding strings.
        """
        found = False
        message = None
        metadata = None
        for k in errmsg.keys():
            print 'parsing ', self.files[k], ' for ', errmsg[k]['string']
            try:
                with open(self.files[k], mode='r') as f:
                    lines = f.read().split('\n')
                for line in lines:
                    if errmsg[k]['string'] in line:
                        found = True
                        message = line.strip()
                        metadata = self.extract_metadata(message, errmsg[k]['metafilter'])
            except (IOError, OSError):
                print self.files[k], 'not found'

        return found, message, metadata

    def parse(self):
        """
        Parse for the occurce of all errors defined in ERRORS
        """
        for my_error in self.ERRORS:
            result = self.parse_single(self.ERRORS[my_error])
            if result[0]:
                self.errors.append(my_error(result[1], result[2]))

class SlurmErrorParser(AbstractErrorParser):
    """
    Implementation for the Slurm scheduler
    """
    def __init__(self, err_file, out_file, run_err_file, batch_err_file):
        super(SlurmErrorParser, self).__init__(err_file, out_file, run_err_file, batch_err_file)

    ERRORS = {SubmitError: {'batch_err': {'string': "sbatch: error: Batch job submission failed:",
                                          'metafilter': ""}},
              FullQueueError: {'batch_err': {'string': "sbatch: error: Batch job submission failed: Job violates accounting/QOS policy",
                                             'metafilter': ""}},
              MemoryCancelError: {'err': {'string': "Exceeded job memory limit",
                                          'metafilter': ""}},
              NodeFailureError: {'run_err': {'string': "can't open /dev/ipath, network down",
                                      'metafilter': {'node': [r"node(\d+)\.(\d+)can't open (\S*), network down \(err=26\)", 1]}}},
              AbstractError: {'out': {'string': "a string to be found",
                                      'metafilter': ""}},
              }

class PBSErrorParse(AbstractErrorParser):
    """
    Implementation for the PBS scheduler
    """

if __name__ == "__main__":
    my_parser = SlurmErrorParser(err_file='queue.err', out_file='queue.out', run_err_file='run.err', batch_err_file='batch.err')
    my_parser.parse()
    print 'parser.errors', my_parser.errors
    for error in my_parser.errors:
        print error
<commit_msg>scheduler errors<commit_after>"""
Error handlers for errors originating from the Submission systems.
"""

from __future__ import division

__author__ = "Michiel van Setten"
__copyright__ = " "
__version__ = "0.9"
__maintainer__ = "Michiel van Setten"
__email__ = "mjvansetten@gmail.com"
__date__ = "Mar 24, 2014"

import os
import os.path
import abc
import re

from abc import ABCMeta

from custodian.custodian import Custodian, ErrorHandler

class SchedulerErrorHandler(ErrorHandler):
    """
    Custodian error handler for scheduler related errors
      scheduler takes the schduler, currently: slurm is supported
    """
    def __init__(self, scheduler, err_file='queue.err', out_file='queue.out', run_err_file='run.err', batch_err_file='batch.err'):
        self.scheduler = scheduler
        self.err_file = err_file
        self.out_file = out_file
        self.run_err_file = run_err_file
        self.batch_err_file = batch_err_file
        self.errors = []

    def check(self):
        parser = AbstractErrorParser.factory('slurm', err_file=self.err_file, out_file=self.out_file, run_err_file=self.run_err_file, batch_err_file=self.batch_err_file)
        parser.parse()
        self.errors = parser.errors
        if len(self.errors) == 0:
            return False
        else:
            return True

    def correct(self):
        pass

class AbstractError():
    """
    Error base class
    """
    __metaclass__ = ABCMeta

    def __init__(self, errmsg, meta_data):
        self.errmsg = errmsg
        self.meta_data = meta_data
        self._message = str

    def __str__(self):
        self._message = '%s  %s\n' \
                        '  error message : %s \n' \
                        '  meta data     : %s' % (self.name, self.__doc__, self.errmsg, str(self.meta_data))
        return self._message

    @property
    def name(self):
        return self.__class__.__name__

class SubmitError(AbstractError):
    """
    Errors occurring at submission. The limits on the cluster may have changed.
    """
    def __init__(self, errmsg, meta_data):
        super(SubmitError, self).__init__(errmsg, meta_data)

class FullQueueError(AbstractError):
    """
    Errors occurring at submission. To many jobs in the queue / total cpus / .. .
    """
    def __init__(self, errmsg, meta_data):
        super(FullQueueError, self).__init__(errmsg, meta_data)

class DiskError(AbstractError):
    """
    Errors involving problems writing to disk.
    """
    def __init__(self, errmsg, meta_data):
        super(DiskError, self).__init__(errmsg, meta_data)

class TimeCancelError(AbstractError):
    """
    Error due to exceeding the time limit for the job.
      .limit will return the limit that was broken, None if it could not be determined.
    """
    def __init__(self, errmsg, meta_data):
        super(TimeCancelError, self).__init__(errmsg, meta_data)
        self.limit = None
        if meta_data is not None and 'broken_limit' in meta_data.keys():
                self.limit = meta_data['broken_limit']

class MemoryCancelError(AbstractError):
    """
    Error due to exceeding the memory limit for the job.
      .limit will return the limit that was broken, None if it could not be determined.
    """
    def __init__(self, errmsg, meta_data):
        super(MemoryCancelError, self).__init__(errmsg, meta_data)
        self.limit = None
        if meta_data is not None and 'broken_limit' in meta_data.keys():
                self.limit = meta_data['broken_limit']

class NodeFailureError(AbstractError):
    """
    Error due the hardware failure of a specific node.
     .node will return the problematic node, None if it could not be determined.
    """
    def __init__(self, errmsg, meta_data):
        super(NodeFailureError, self).__init__(errmsg, meta_data)
        self.node = None
        if len(meta_data) > None and 'node' in meta_data.keys():
                self.node = meta_data['node']

class AbstractErrorParser():
    """
    Abstract class for parsing errors originating from the scheduler system and error that are not reported by the
    program itself, i.e. segmentation faults.

    A concrete implementation of this class for a specific sceduler needs a class attribute ERRORS for containing a
    dictionary specifying error:

    ERRORS = {ErrorClass: {'filespecifier' : {'string': "the string to be lookedfor",
                                              'metafilter': "string specifing the regular exprecion to obtian the meta data"}}

    """
    __metaclass__ = ABCMeta

    def __init__(self, err_file, out_file, run_err_file, batch_err_file):
        self.files = {'err': err_file, 'out': out_file, 'run_err': run_err_file, 'batch_err': batch_err_file}
        self.errors = []
        return

    def factory(scheduler, *kwargs):
        if scheduler == "slurm":
            return SlurmErrorParser(*kwargs)
        elif scheduler == "pbs":
            return PBSErrorParse(*kwargs)
        assert 0, "undefined scheduler " + scheduler
    factory = staticmethod(factory)

    @staticmethod
    def extract_metadata(message, metafilter):
        meta_dict = {}
        for key in metafilter.keys():
            match = re.match(metafilter[key][0], message)
            if match is not None:
                meta_dict.update({key: re.match(metafilter[key][0], message).group(metafilter[key][1])})
        return meta_dict

    def parse_single(self, errmsg):
        """
        Parse the provided files for the corresponding strings.
        """
        found = False
        message = None
        metadata = None
        for k in errmsg.keys():
            print 'parsing ', self.files[k], ' for ', errmsg[k]['string']
            try:
                with open(self.files[k], mode='r') as f:
                    lines = f.read().split('\n')
                for line in lines:
                    if errmsg[k]['string'] in line:
                        found = True
                        message = line.strip()
                        metadata = self.extract_metadata(message, errmsg[k]['metafilter'])
            except (IOError, OSError):
                print self.files[k], 'not found'

        return found, message, metadata

    def parse(self):
        """
        Parse for the occurce of all errors defined in ERRORS
        """
        for my_error in self.ERRORS:
            result = self.parse_single(self.ERRORS[my_error])
            if result[0]:
                self.errors.append(my_error(result[1], result[2]))

class SlurmErrorParser(AbstractErrorParser):
    """
    Implementation for the Slurm scheduler
    """
    def __init__(self, err_file, out_file, run_err_file, batch_err_file):
        super(SlurmErrorParser, self).__init__(err_file, out_file, run_err_file, batch_err_file)

    ERRORS = {SubmitError: {'batch_err': {'string': "sbatch: error: Batch job submission failed:",
                                          'metafilter': ""}},
              FullQueueError: {'batch_err': {'string': "sbatch: error: Batch job submission failed: Job violates accounting/QOS policy",
                                             'metafilter': ""}},
              MemoryCancelError: {'err': {'string': "Exceeded job memory limit",
                                          'metafilter': ""}},
              NodeFailureError: {'run_err': {'string': "can't open /dev/ipath, network down",
                                      'metafilter': {'node': [r"node(\d+)\.(\d+)can't open (\S*), network down \(err=26\)", 1]}}},
              AbstractError: {'out': {'string': "a string to be found",
                                      'metafilter': ""}},
              }

class PBSErrorParse(AbstractErrorParser):
    """
    Implementation for the PBS scheduler
    """

if __name__ == "__main__":
    my_parser = SlurmErrorParser(err_file='queue.err', out_file='queue.out', run_err_file='run.err', batch_err_file='batch.err')
    #or use the factory function:
    #parser = AbstractErrorParser.factory('slurm', err_file=self.err_file, out_file=self.out_file, run_err_file=self.run_err_file, batch_err_file=self.batch_err_file)

    my_parser.parse()
    print 'parser.errors', my_parser.errors
    for error in my_parser.errors:
        print error
<|endoftext|>


<commit_before>
"""
Rickie Kerndt <rkerndt@cs.uoregon.edu
bike_power_center.py
Provides services for monitoring and managing bicycle electrical systems
"""

from __future__ import print_function
import sys
sys.path.append('/home/pi/lib/python')

import signal
import ina219
import power_meter as pm
import rgb
from threading import Event, Timer
from datetime import datetime

class PowerCenter():
    """
    Methods for monitoring power generating, consumption, and modifying on bicycle electronics to control
    power consumption.
    """

    CHARGING    = 0
    FULL_CHARGE = 1
    ON_BATTERY  = 2
    LOW_BATTERY = 3
    LOW_BATTERY_VOLTS = 3.2

    LOG_FILE = "/var/log/power_center.log"
    POWER_MONITOR_INTERVAL = 0.08
    RUNNING = 0
    SHUTDOWN = 1

    # timer intervals
    CHECK_BATTERY_INTERVAL = 1.0
    LOG_DATA_INTERVAL = 1.0

    def __init__(self):
        """
        Instantiates power center which for now just monitors battery and external power ina219 devices.
        """
        self._trigger = Event()
        self._close_event = Event()
        self._state = None
        self.battery_power = pm.PowerMeter(pm.INA219_Monitor(0.1, 0x40, 1, ina219.INA219_CALIB_32V_1A, 128),
                                           self._trigger)
        self.dyno_power = pm.PowerMeter(pm.INA219_Monitor(0.1, 0x41, 1, ina219.INA219_CALIB_32V_2A, 128),
                                        self._trigger)
        self.log_file = PowerCenter.LOG_FILE
        self.log_fh = None
        self.battery_status = None
        self.battery_status_led = rgb.RGB_led(21, 20, 16)
        self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery)
        self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log)
        self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync)

        signal.signal(signal.SIGTERM, self._sig_handler)
        signal.signal(signal.SIGINT, self._sig_handler)
        signal.signal(signal.SIGUSR1, self._sig_handler)

    def run(self):
        """
        Starts things up and launches timers
        """
        self._state = PowerCenter.RUNNING
        self.battery_power.open()
        self.dyno_power.open()
        self._power_monitor_timer.start()
        self._battery_timer.start()
        if self.log_fh:
            self._log_timer.start()

    def wait(self):
        """
        Provides an event that a calling process can wait on until termination of PowerCenter
        """
        self._close_event.wait()

    def close(self):
        """
        Shuts things down and cancels timers
        """
        self._status = PowerCenter.SHUTDOWN
        self._power_monitor_timer.cancel()
        self._log_timer.cancel()
        self._battery_timer.cancel()
        self.battery_power.close()
        self.dyno_power.close()
        self.battery_status.close()
        if self.log_fh:
            self._log_message('Shutting down Power Center')
            self.log_fh.flush()
            self.log_fh.close()

    def _power_monitor_sync(self):
        """
        Sets event controlling synchronization of power monitors
        """
        self._trigger.set()
        if self._state == PowerCenter.RUNNING:
            self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync).start()

    def _check_battery(self):
        """
        Runs battery check and then relaunches timer
        """
        self._update_battery_status()
        if self._state == PowerCenter.RUNNING:
            self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery).start()

    def _update_battery_status(self):
        """
        Checks battery voltage and amperage updating LED indication if there is a change
        CHARGING := negative battery amperage
        FULL_CHARGE := positive battery amperage with greater external amperage
        ON_BATTERY := positive battery amperage
        LOW_BATTERY := battery voltage <= LOW_BATTERY_VOLTS
        """
        amperage = self.battery_power.amp
        voltage = self.battery_power.volt
        ext_amperage = self.dyno_power.amp

        if voltage <= PowerCenter.LOW_BATTERY_VOLTS:
            if self.battery_status != PowerCenter.LOW_BATTERY:
                self.battery_status = PowerCenter.LOW_BATTERY
                self.battery_status_led.red()
        elif amperage < 0:
            if self.battery_status != PowerCenter.CHARGING:
                self.battery_status = PowerCenter.CHARGING
                self.battery_status_led.green(True)
        elif ext_amperage > amperage:
            if self.battery_status != PowerCenter.FULL_CHARGE:
                self.batter_status = PowerCenter.FULL_CHARGE
                self.battery_status_led.green()
        else:
            if self.battery_status != PowerCenter.ON_BATTERY:
                self.battery_status = PowerCenter.ON_BATTERY
                self.battery_status_led.blue()

    def _output_log(self):
        """
        Updates logs and relaunches timer
        """
        self._log_power_data()
        if self._state == PowerCenter.RUNNING:
            self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log).start()

    def _log_power_data(self):
        """
        writes the battery and dyno power values to the log file
        """
        if self.log_fh:
            battery_data = self.battery_power.get()
            dyno_data = self.dyno_power.get()
            self._log_message(battery_data.csv() + ',' + dyno_data.csv())

    def _log_message(self, message):
        """
        writes message to log file inserting a timestamp
        :param message: string
        """
        if self.log_fh:
            print('%s %s' % (datetime.now(), message), file=self.log_fh)

    def _sig_handler(self, signum, frame):
        """
        Handles system signals. SIGINT, SIGTERM to gracefully terminate operation. SIGUSR1 will toggle
        data logging.
        :param signum:
        :param frame:
        """
        if signum in [signal.SIGTERM, signal.SIGINT]:
            self.close()
            self._close_event.set()

        if signum == signal.SIGUSR1:
            if self.log_fh:
                self._log_message('Logging disabled')
                self.log_fh.flush()
                self.log_fh.close()
                self.log_fh = None
            else:
                try:
                    self.log_fh = open(self.log_file, 'a')
                except:
                    print('Failed to open %s for logging: %s' % (self.log_file, sys.exc_info()[0]), sys.stderr)
                    self.log_fh = None
                    return
                self._log_message('Logging enabled')

def main():
    """
    Instantiates PowerCenter and waits for termination
    """
    pc = PowerCenter()
    pc.run()
    pc.wait()

if __name__ == "__main__":
    main()<commit_msg>need the reference before calling start<commit_after>
"""
Rickie Kerndt <rkerndt@cs.uoregon.edu
bike_power_center.py
Provides services for monitoring and managing bicycle electrical systems
"""

from __future__ import print_function
import sys
sys.path.append('/home/pi/lib/python')

import signal
import ina219
import power_meter as pm
import rgb
from threading import Event, Timer
from datetime import datetime

class PowerCenter():
    """
    Methods for monitoring power generating, consumption, and modifying on bicycle electronics to control
    power consumption.
    """

    CHARGING    = 0
    FULL_CHARGE = 1
    ON_BATTERY  = 2
    LOW_BATTERY = 3
    LOW_BATTERY_VOLTS = 3.2

    LOG_FILE = "/var/log/power_center.log"
    POWER_MONITOR_INTERVAL = 0.08
    RUNNING = 0
    SHUTDOWN = 1

    # timer intervals
    CHECK_BATTERY_INTERVAL = 1.0
    LOG_DATA_INTERVAL = 1.0

    def __init__(self):
        """
        Instantiates power center which for now just monitors battery and external power ina219 devices.
        """
        self._trigger = Event()
        self._close_event = Event()
        self._state = None
        self.battery_power = pm.PowerMeter(pm.INA219_Monitor(0.1, 0x40, 1, ina219.INA219_CALIB_32V_1A, 128),
                                           self._trigger)
        self.dyno_power = pm.PowerMeter(pm.INA219_Monitor(0.1, 0x41, 1, ina219.INA219_CALIB_32V_2A, 128),
                                        self._trigger)
        self.log_file = PowerCenter.LOG_FILE
        self.log_fh = None
        self.battery_status = None
        self.battery_status_led = rgb.RGB_led(21, 20, 16)
        self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery)
        self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log)
        self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync)

        signal.signal(signal.SIGTERM, self._sig_handler)
        signal.signal(signal.SIGINT, self._sig_handler)
        signal.signal(signal.SIGUSR1, self._sig_handler)

    def run(self):
        """
        Starts things up and launches timers
        """
        self._state = PowerCenter.RUNNING
        self.battery_power.open()
        self.dyno_power.open()
        self._power_monitor_timer.start()
        self._battery_timer.start()
        if self.log_fh:
            self._log_timer.start()

    def wait(self):
        """
        Provides an event that a calling process can wait on until termination of PowerCenter
        """
        self._close_event.wait()

    def close(self):
        """
        Shuts things down and cancels timers
        """
        self._status = PowerCenter.SHUTDOWN
        self._power_monitor_timer.cancel()
        self._log_timer.cancel()
        self._battery_timer.cancel()
        self.battery_power.close()
        self.dyno_power.close()
        self.battery_status.close()
        if self.log_fh:
            self._log_message('Shutting down Power Center')
            self.log_fh.flush()
            self.log_fh.close()

    def _power_monitor_sync(self):
        """
        Sets event controlling synchronization of power monitors
        """
        self._trigger.set()
        if self._state == PowerCenter.RUNNING:
            self._power_monitor_timer = Timer(PowerCenter.POWER_MONITOR_INTERVAL, self._power_monitor_sync)
            self._power_monitor_timer.start()

    def _check_battery(self):
        """
        Runs battery check and then relaunches timer
        """
        self._update_battery_status()
        if self._state == PowerCenter.RUNNING:
            self._battery_timer = Timer(PowerCenter.CHECK_BATTERY_INTERVAL, self._check_battery)
            self._battery_timer.start()

    def _update_battery_status(self):
        """
        Checks battery voltage and amperage updating LED indication if there is a change
        CHARGING := negative battery amperage
        FULL_CHARGE := positive battery amperage with greater external amperage
        ON_BATTERY := positive battery amperage
        LOW_BATTERY := battery voltage <= LOW_BATTERY_VOLTS
        """
        amperage = self.battery_power.amp
        voltage = self.battery_power.volt
        ext_amperage = self.dyno_power.amp

        if voltage <= PowerCenter.LOW_BATTERY_VOLTS:
            if self.battery_status != PowerCenter.LOW_BATTERY:
                self.battery_status = PowerCenter.LOW_BATTERY
                self.battery_status_led.red()
        elif amperage < 0:
            if self.battery_status != PowerCenter.CHARGING:
                self.battery_status = PowerCenter.CHARGING
                self.battery_status_led.green(True)
        elif ext_amperage > amperage:
            if self.battery_status != PowerCenter.FULL_CHARGE:
                self.batter_status = PowerCenter.FULL_CHARGE
                self.battery_status_led.green()
        else:
            if self.battery_status != PowerCenter.ON_BATTERY:
                self.battery_status = PowerCenter.ON_BATTERY
                self.battery_status_led.blue()

    def _output_log(self):
        """
        Updates logs and relaunches timer
        """
        self._log_power_data()
        if self._state == PowerCenter.RUNNING:
            self._log_timer = Timer(PowerCenter.LOG_DATA_INTERVAL, self._output_log)
            self._log_timer.start()

    def _log_power_data(self):
        """
        writes the battery and dyno power values to the log file
        """
        if self.log_fh:
            battery_data = self.battery_power.get()
            dyno_data = self.dyno_power.get()
            self._log_message(battery_data.csv() + ',' + dyno_data.csv())

    def _log_message(self, message):
        """
        writes message to log file inserting a timestamp
        :param message: string
        """
        if self.log_fh:
            print('%s %s' % (datetime.now(), message), file=self.log_fh)

    def _sig_handler(self, signum, frame):
        """
        Handles system signals. SIGINT, SIGTERM to gracefully terminate operation. SIGUSR1 will toggle
        data logging.
        :param signum:
        :param frame:
        """
        if signum in [signal.SIGTERM, signal.SIGINT]:
            self.close()
            self._close_event.set()

        if signum == signal.SIGUSR1:
            if self.log_fh:
                self._log_message('Logging disabled')
                self.log_fh.flush()
                self.log_fh.close()
                self.log_fh = None
            else:
                try:
                    self.log_fh = open(self.log_file, 'a')
                except:
                    print('Failed to open %s for logging: %s' % (self.log_file, sys.exc_info()[0]), sys.stderr)
                    self.log_fh = None
                    return
                self._log_message('Logging enabled')

def main():
    """
    Instantiates PowerCenter and waits for termination
    """
    pc = PowerCenter()
    pc.run()
    pc.wait()

if __name__ == "__main__":
    main()<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import collections
import os
import StringIO

import jinja2
import webapp2
from PIL import Image

from google.appengine.api import users

import meme

TODO = None

JINJA_ENV = jinja2.Environment(
    loader=jinja2.FileSystemLoader(
        os.path.join(os.path.dirname(__file__), 'templates')),
    extensions=['jinja2.ext.autoescape'])

MEME_TEMPLATES = collections.OrderedDict([
    ('happyboy', 'happyboy.jpeg'),
    ('rapperboy', 'rapperboy.jpeg'),
    ('realappengine', 'realappengine.jpeg'),
    ('shrugging', 'shrugging.jpeg'),
    ('bathing', 'bathing.jpeg'),
])

DEFAULT_TEMPLATE = 'happyboy.jpeg'

IMAGE_DIR = 'images'

MEME_FONTS = collections.OrderedDict([
    ('mikachan', 'mikachan.ttf'),
    ('FreeSerif', 'FreeSerif.ttf'),
])

DEFAULT_FONT = 'FreeSerif.ttf'

FONT_DIR = 'fonts'

def get_login_logout_context(target_url):
    """Returns nickname, link url and link text for the login/logout html."""
    user = users.get_current_user()
    if user:
        nickname = user.nickname()
        link_text = 'Logout'
        link_url = users.create_logout_url(target_url)
    else:
        nickname = 'Anonymous user'
        link_text = 'Login'
        link_url = users.create_login_url(target_url)
    return nickname, link_url, link_text

class MainHandler(webapp2.RequestHandler):
    """A handler for showing an HTML form."""

    def get(self):
        """Render an HTML form for creating Memes."""
        template = JINJA_ENV.get_template('index.html')
        nickname, link_url, link_text = get_login_logout_context(
            self.request.uri)
        context = {
            'templates': MEME_TEMPLATES,
            'fonts': MEME_FONTS,
            'nickname': nickname,
            'link_text': link_text,
            'link_url': link_url
        }
        self.response.out.write(template.render(context))

class ImageHandler(webapp2.RequestHandler):
    """A handler for rendering and saving Memes."""

    def render_meme(self):
        """Renders an image with given HTTP parameters.

        Returns:
            A rendered Image object.
        """
        meme_template = self.request.get('meme_template')
        image = Image.open(os.path.join(IMAGE_DIR,
            MEME_TEMPLATES.get(meme_template, DEFAULT_TEMPLATE)))
        meme_font = self.request.get('meme_font')
        font_file = os.path.join(FONT_DIR,
                                 MEME_FONTS.get(meme_font, DEFAULT_FONT))
        upper_text = self.request.get('upper_text')
        upper_text_align = self.request.get('upper_text_align', meme.MIDDLE)
        middle_text = self.request.get('middle_text')
        middle_text_align = self.request.get('middle_text_align', meme.MIDDLE)
        lower_text = self.request.get('lower_text')
        lower_text_align = self.request.get('lower_text_align', meme.MIDDLE)
        if upper_text:
            meme.draw_text(image, meme.TOP, upper_text_align, upper_text,
                           font_file=font_file)
        if middle_text:
            meme.draw_text(image, meme.MIDDLE, middle_text_align, middle_text,
                           font_file=font_file)
        if lower_text:
            meme.draw_text(image, meme.BOTTOM, lower_text_align, lower_text,
                           font_file=font_file)
        return image

    def get(self):
        """A handler for rendering a preview image."""
        image = self.render_meme()
        self.response.headers['Content-Type'] = 'image/jpg'
        image.save(self.response.out, 'JPEG')

    def post(self):
        """A handler for save the meme."""
        image = self.render_meme()
        new_meme = meme.Meme()
        user = users.get_current_user()
        if user:
            new_meme.owner = user
        output = StringIO.StringIO()
        image.save(output, "JPEG")
        new_meme.image = output.getvalue()
        output.close()
        new_meme.put()
        self.redirect("/meme/{}".format(new_meme.key.id()), abort=True)

class MemeHandler(webapp2.RequestHandler):
    """A handler for rendering an HTML page of a particular meme."""

    def get(self, meme_id):
        """Renders an HTML page of a Meme with a given id.

        Raises a 404 error when entity not found.

        Args:
            meme_id: id of Meme entity extracted from the URL path.
        """
        target = meme.Meme.get_by_id(long(meme_id))
        if target is None:
            self.abort(404)
        nickname, link_url, link_text = get_login_logout_context(
            self.request.uri)
        template = JINJA_ENV.get_template('meme.html')
        context = {
            'nickname': nickname,
            'link_url': link_url,
            'link_text': link_text,
            'meme_owner': target.owner.nickname(),
            'meme_id': target.key.id(),
            'meme_created_date': target.created_at,
        }
        self.response.write(template.render(context))

class MemeImageHandler(webapp2.RequestHandler):
    """A handler for serving an image of a particular meme."""

    def get(self, meme_id):
        """Serves an image of a particular meme.

        Raises a 404 error when entity not found.

        Args:
            meme_id: id of Meme entity extracted from the URL path.
        """
        target = meme.Meme.get_by_id(long(meme_id))
        if target is None:
            self.abort(404)
        self.response.headers['Content-Type'] = 'image/jpeg'
        self.response.write(target.image)

APPLICATION = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/preview', ImageHandler),
    ('/create_meme', ImageHandler),
    ('/meme/(\d+)', MemeHandler),
    ('/image/(\d+)', MemeImageHandler),
], debug=True)
<commit_msg>Fixed a bug for anonymous memes.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import collections
import os
import StringIO

import jinja2
import webapp2
from PIL import Image

from google.appengine.api import users

import meme

TODO = None

JINJA_ENV = jinja2.Environment(
    loader=jinja2.FileSystemLoader(
        os.path.join(os.path.dirname(__file__), 'templates')),
    extensions=['jinja2.ext.autoescape'])

MEME_TEMPLATES = collections.OrderedDict([
    ('happyboy', 'happyboy.jpeg'),
    ('rapperboy', 'rapperboy.jpeg'),
    ('realappengine', 'realappengine.jpeg'),
    ('shrugging', 'shrugging.jpeg'),
    ('bathing', 'bathing.jpeg'),
])

DEFAULT_TEMPLATE = 'happyboy.jpeg'

IMAGE_DIR = 'images'

MEME_FONTS = collections.OrderedDict([
    ('mikachan', 'mikachan.ttf'),
    ('FreeSerif', 'FreeSerif.ttf'),
])

DEFAULT_FONT = 'FreeSerif.ttf'

FONT_DIR = 'fonts'

def get_login_logout_context(target_url):
    """Returns nickname, link url and link text for the login/logout html."""
    user = users.get_current_user()
    if user:
        nickname = user.nickname()
        link_text = 'Logout'
        link_url = users.create_logout_url(target_url)
    else:
        nickname = 'Anonymous user'
        link_text = 'Login'
        link_url = users.create_login_url(target_url)
    return nickname, link_url, link_text

class MainHandler(webapp2.RequestHandler):
    """A handler for showing an HTML form."""

    def get(self):
        """Render an HTML form for creating Memes."""
        template = JINJA_ENV.get_template('index.html')
        nickname, link_url, link_text = get_login_logout_context(
            self.request.uri)
        context = {
            'templates': MEME_TEMPLATES,
            'fonts': MEME_FONTS,
            'nickname': nickname,
            'link_text': link_text,
            'link_url': link_url
        }
        self.response.out.write(template.render(context))

class ImageHandler(webapp2.RequestHandler):
    """A handler for rendering and saving Memes."""

    def render_meme(self):
        """Renders an image with given HTTP parameters.

        Returns:
            A rendered Image object.
        """
        meme_template = self.request.get('meme_template')
        image = Image.open(os.path.join(IMAGE_DIR,
            MEME_TEMPLATES.get(meme_template, DEFAULT_TEMPLATE)))
        meme_font = self.request.get('meme_font')
        font_file = os.path.join(FONT_DIR,
                                 MEME_FONTS.get(meme_font, DEFAULT_FONT))
        upper_text = self.request.get('upper_text')
        upper_text_align = self.request.get('upper_text_align', meme.MIDDLE)
        middle_text = self.request.get('middle_text')
        middle_text_align = self.request.get('middle_text_align', meme.MIDDLE)
        lower_text = self.request.get('lower_text')
        lower_text_align = self.request.get('lower_text_align', meme.MIDDLE)
        if upper_text:
            meme.draw_text(image, meme.TOP, upper_text_align, upper_text,
                           font_file=font_file)
        if middle_text:
            meme.draw_text(image, meme.MIDDLE, middle_text_align, middle_text,
                           font_file=font_file)
        if lower_text:
            meme.draw_text(image, meme.BOTTOM, lower_text_align, lower_text,
                           font_file=font_file)
        return image

    def get(self):
        """A handler for rendering a preview image."""
        image = self.render_meme()
        self.response.headers['Content-Type'] = 'image/jpg'
        image.save(self.response.out, 'JPEG')

    def post(self):
        """A handler for save the meme."""
        image = self.render_meme()
        new_meme = meme.Meme()
        user = users.get_current_user()
        if user:
            new_meme.owner = user
        output = StringIO.StringIO()
        image.save(output, "JPEG")
        new_meme.image = output.getvalue()
        output.close()
        new_meme.put()
        self.redirect("/meme/{}".format(new_meme.key.id()), abort=True)

class MemeHandler(webapp2.RequestHandler):
    """A handler for rendering an HTML page of a particular meme."""

    def get(self, meme_id):
        """Renders an HTML page of a Meme with a given id.

        Raises a 404 error when entity not found.

        Args:
            meme_id: id of Meme entity extracted from the URL path.
        """
        target = meme.Meme.get_by_id(long(meme_id))
        if target is None:
            self.abort(404)
        nickname, link_url, link_text = get_login_logout_context(
            self.request.uri)
        template = JINJA_ENV.get_template('meme.html')
        if target.owner:
            owner_nickname = target.owner.nickname()
        else:
            owner_nickname = 'Anonymous user'
        context = {
            'nickname': nickname,
            'link_url': link_url,
            'link_text': link_text,
            'meme_owner': owner_nickname,
            'meme_id': target.key.id(),
            'meme_created_date': target.created_at,
        }
        self.response.write(template.render(context))

class MemeImageHandler(webapp2.RequestHandler):
    """A handler for serving an image of a particular meme."""

    def get(self, meme_id):
        """Serves an image of a particular meme.

        Raises a 404 error when entity not found.

        Args:
            meme_id: id of Meme entity extracted from the URL path.
        """
        target = meme.Meme.get_by_id(long(meme_id))
        if target is None:
            self.abort(404)
        self.response.headers['Content-Type'] = 'image/jpeg'
        self.response.write(target.image)

APPLICATION = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/preview', ImageHandler),
    ('/create_meme', ImageHandler),
    ('/meme/(\d+)', MemeHandler),
    ('/image/(\d+)', MemeImageHandler),
], debug=True)
<|endoftext|>


<commit_before>########
# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
#    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    * See the License for the specific language governing permissions and
#    * limitations under the License.

from cloudify_rest_client.responses import ListResponse

DEFAULT_TENANT_ROLE = 'user'

class Tenant(dict):

    def __init__(self, tenant):
        super(Tenant, self).__init__()
        self.update(tenant)

    @property
    def name(self):
        """
        :return: The name of the tenant.
        """
        return self.get('name')

    @property
    def users(self):
        """
        :return: The list of users connected to the tenant.
        """
        return self.get('users')

    @property
    def groups(self):
        """
        :return: The list of user groups connected the tenant.
        """
        return self.get('groups')

class TenantsClient(object):

    def __init__(self, api):
        self.api = api

    def list(self, _include=None, sort=None, is_descending=False, **kwargs):
        """
        Returns a list of currently stored tenants.

        :param _include: List of fields to include in response.
        :param sort: Key for sorting the list.
        :param is_descending: True for descending order, False for ascending.
        :param kwargs: Optional filter fields. For a list of available fields
               see the REST service's models.Tenants.fields
        :return: Tenants list.
        """
        params = kwargs
        if sort:
            params['_sort'] = '-' + sort if is_descending else sort

        response = self.api.get('/tenants',
                                _include=_include,
                                params=params)
        return ListResponse([Tenant(item) for item in response['items']],
                            response['metadata'])

    def create(self, tenant_name):
        response = self.api.post(
            '/tenants/{0}'.format(tenant_name),
            expected_status_code=201
        )

        return Tenant(response)

    def add_user(self, username, tenant_name, role=None):
        """Add user to a tenant.

        Optionally, set the role of the user in the context of the tenant in
        which it has been added.

        :param username: Name of the user to add to the tenant
        :param tenant_name: Name of the tenant to which the user is added
        :param role: Name of the role assigned to the user in the tenant

        """
        if role is None:
            role = DEFAULT_TENANT_ROLE

        data = {
            'username': username,
            'tenant_name': tenant_name,
            'role': role,
        }
        response = self.api.put('/tenants/users', data=data)
        return Tenant(response)

    def update_user(self, username, tenant_name, role):
        """Update user in a tenant.

        :param username: Name of the user to add to the tenant
        :type username: str
        :param tenant_name: Name of the tenant to which the user is added
        :type tenant_name: str
        :param role: Name of the role assigned to the user in the tenant
        :type role: str

        """
        data = {
            'username': username,
            'tenant_name': tenant_name,
            'role': role,
        }
        response = self.api.patch('/tenants/users', data=data)
        return Tenant(response)

    def remove_user(self, username, tenant_name):
        data = {'username': username, 'tenant_name': tenant_name}
        response = self.api.delete('/tenants/users', data=data)
        return Tenant(response)

    def add_group(self, group_name, tenant_name, role=None):
        """Add group to a tenant.

        Optionally, set the role that will be assigned to the users members of
        the group that is added to the tenant.

        :param group_name: Name of the group to add to the tenant
        :param tenant_name: Name of the tenant to which the group is added
        :param role: Name of the role assigned to the members of the group

        """
        if role is None:
            role = DEFAULT_TENANT_ROLE
        data = {
            'group_name': group_name,
            'tenant_name': tenant_name,
            'role': role,
        }
        response = self.api.put('/tenants/user-groups', data=data)
        return Tenant(response)

    def remove_group(self, group_name, tenant_name):
        data = {'group_name': group_name, 'tenant_name': tenant_name}
        response = self.api.delete('/tenants/user-groups', data=data)
        return Tenant(response)

    def get(self, tenant_name, **kwargs):
        response = self.api.get(
            '/tenants/{0}'.format(tenant_name),
            params=kwargs
        )
        return Tenant(response)

    def delete(self, tenant_name):
        response = self.api.delete('/tenants/{0}'.format(tenant_name))
        return Tenant(response)
<commit_msg>CFY-7373 Add TenantsClient.update_group method (#197)<commit_after>########
# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
#    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    * See the License for the specific language governing permissions and
#    * limitations under the License.

from cloudify_rest_client.responses import ListResponse

DEFAULT_TENANT_ROLE = 'user'

class Tenant(dict):

    def __init__(self, tenant):
        super(Tenant, self).__init__()
        self.update(tenant)

    @property
    def name(self):
        """
        :return: The name of the tenant.
        """
        return self.get('name')

    @property
    def users(self):
        """
        :return: The list of users connected to the tenant.
        """
        return self.get('users')

    @property
    def groups(self):
        """
        :return: The list of user groups connected the tenant.
        """
        return self.get('groups')

class TenantsClient(object):

    def __init__(self, api):
        self.api = api

    def list(self, _include=None, sort=None, is_descending=False, **kwargs):
        """
        Returns a list of currently stored tenants.

        :param _include: List of fields to include in response.
        :param sort: Key for sorting the list.
        :param is_descending: True for descending order, False for ascending.
        :param kwargs: Optional filter fields. For a list of available fields
               see the REST service's models.Tenants.fields
        :return: Tenants list.
        """
        params = kwargs
        if sort:
            params['_sort'] = '-' + sort if is_descending else sort

        response = self.api.get('/tenants',
                                _include=_include,
                                params=params)
        return ListResponse([Tenant(item) for item in response['items']],
                            response['metadata'])

    def create(self, tenant_name):
        response = self.api.post(
            '/tenants/{0}'.format(tenant_name),
            expected_status_code=201
        )

        return Tenant(response)

    def add_user(self, username, tenant_name, role=None):
        """Add user to a tenant.

        Optionally, set the role of the user in the context of the tenant in
        which it has been added.

        :param username: Name of the user to add to the tenant
        :param tenant_name: Name of the tenant to which the user is added
        :param role: Name of the role assigned to the user in the tenant

        """
        if role is None:
            role = DEFAULT_TENANT_ROLE

        data = {
            'username': username,
            'tenant_name': tenant_name,
            'role': role,
        }
        response = self.api.put('/tenants/users', data=data)
        return Tenant(response)

    def update_user(self, username, tenant_name, role):
        """Update user in a tenant.

        :param username: Name of the user to add to the tenant
        :type username: str
        :param tenant_name: Name of the tenant to which the user is added
        :type tenant_name: str
        :param role: Name of the role assigned to the user in the tenant
        :type role: str

        """
        data = {
            'username': username,
            'tenant_name': tenant_name,
            'role': role,
        }
        response = self.api.patch('/tenants/users', data=data)
        return Tenant(response)

    def remove_user(self, username, tenant_name):
        data = {'username': username, 'tenant_name': tenant_name}
        response = self.api.delete('/tenants/users', data=data)
        return Tenant(response)

    def add_group(self, group_name, tenant_name, role=None):
        """Add group to a tenant.

        Optionally, set the role that will be assigned to the users members of
        the group that is added to the tenant.

        :param group_name: Name of the group to add to the tenant
        :param tenant_name: Name of the tenant to which the group is added
        :param role: Name of the role assigned to the members of the group

        """
        if role is None:
            role = DEFAULT_TENANT_ROLE
        data = {
            'group_name': group_name,
            'tenant_name': tenant_name,
            'role': role,
        }
        response = self.api.put('/tenants/user-groups', data=data)
        return Tenant(response)

    def update_group(self, group_name, tenant_name, role):
        """Update group in a tenant.

        :param group_name: Name of the user to add to the tenant
        :type group_name: str
        :param tenant_name: Name of the tenant to which the user is added
        :type tenant_name: str
        :param role: Name of the role assigned to the user in the tenant
        :type role: str

        """
        data = {
            'group_name': group_name,
            'tenant_name': tenant_name,
            'role': role,
        }
        response = self.api.patch('/tenants/user-groups', data=data)
        return Tenant(response)

    def remove_group(self, group_name, tenant_name):
        data = {'group_name': group_name, 'tenant_name': tenant_name}
        response = self.api.delete('/tenants/user-groups', data=data)
        return Tenant(response)

    def get(self, tenant_name, **kwargs):
        response = self.api.get(
            '/tenants/{0}'.format(tenant_name),
            params=kwargs
        )
        return Tenant(response)

    def delete(self, tenant_name):
        response = self.api.delete('/tenants/{0}'.format(tenant_name))
        return Tenant(response)
<|endoftext|>