from selenium.webdriver.common.by import By
from base_page_object import BasePage
from nose.tools import assert_equal, assert_true
from conf import new_request_conf as request
import datetime

class PortfolioPage(BasePage):
    locator_dictionary = {
        "portfolio_header": (By.XPATH, "//div[@class='title']/span[.='Project REC portfolio']"),
        "portfolio_mass_header": (By.XPATH, "//div[@class='title']/span[.='Project MASS portfolio']"),
        "last_project_title": (By.XPATH, "//span[.='Recrutment_archive']"),
        "filter_button": (By.XPATH, "//a[@class='ant-dropdown-trigger']"),
        "settings_button": (By.XPATH, "//i[@class='anticon anticon-settings']"),
        "request_title": (By.XPATH, "//span[.='Recrutment_reject']"),
        "mass_request_title": (By.XPATH, "//span[.='Mass_request']"),
        "mass_inwork_title": (By.XPATH, "//span[.='Mass_inwork']"),
        "mass_frozen_title": (By.XPATH, "//span[.='Mass_freeze']"),
        "mass_complete_title": (By.XPATH, "//span[.='Mass_complete']"),
        "mass_cancelled_title": (By.XPATH, "//span[.='Mass_annulate']"),
        "mass_archived_title": (By.XPATH, "//span[.='Mass_archive']"),
        "inwork_title": (By.XPATH, "//span[.='Recrutment1']"),
        "frozen_title": (By.XPATH, "//span[.='Recrutment_freeze']"),
        "complete_title": (By.XPATH, "//span[.='Recrutment_complete']"),
        "cancelled_title": (By.XPATH, "//span[.='Recrutment_annulate']"),
        "archived_title": (By.XPATH, "//span[.='Recrutment_archive']"),
        "go_to_project_button": (By.XPATH, "//a[@class='sidebar-link']/i[@class='anticon anticon-briefcase']"),
        "user_field": (By.XPATH, "//div[@class='antd-select-container user-filter']//input"),
        "second_resp_project": (By.XPATH, "//span[.='Position2']"),
        "second_resp_mass_project": (By.XPATH, "//span[.='Mass_Second_User']")
        }

    def __init__(self, context):
        BasePage.__init__(
            self,
            context.browser)

    def validate_project_list(self, type):
        if type == "request":
            assert_true(self.request_title)
        elif type == "inwork":
            assert_true(self.inwork_title)
            assert_true(self.find_element(By.XPATH, "//span[.='10']"))
            assert_true(self.find_element(By.XPATH, "//span[.='25.04.2025']"))
        elif type == "frozen":
            assert_true(self.frozen_title)
            assert_true(self.find_element(By.XPATH, "//span[.='10']"))
            assert_true(self.find_element(By.XPATH, "//span[.='22.02.2022']"))
        elif type == "complete":
            assert_true(self.complete_title)
            assert_true(self.find_element(By.XPATH, "//span[.='2']"))
            assert_true(self.find_element(By.XPATH, "//span[.='22.02.2022']"))
        elif type == "cancelled":
            assert_true(self.cancelled_title)
            assert_true(self.find_element(By.XPATH, "//span[.='10']"))
            assert_true(self.find_element(By.XPATH, "//span[.='22.02.2022']"))
        elif type == "archived":
            assert_true(self.archived_title)
            assert_true(self.find_element(By.XPATH, "//span[.='1']"))
            assert_true(self.find_element(By.XPATH, "//span[.='22.02.2022']"))
        elif type == "mass request":
            assert_true(self.mass_request_title)
            assert_true(self.find_element(By.XPATH, "//span[.='10']"))
        elif type == "mass inwork":
            assert_true(self.mass_inwork_title)
            assert_true(self.find_element(By.XPATH, "//span[.='25']"))
            assert_true(self.find_element(By.XPATH, "//span[.='22.02.2022']"))
        elif type == "mass frozen":
            assert_true(self.mass_frozen_title)
            assert_true(self.find_element(By.XPATH, "//span[.='35']"))
        elif type == "mass complete":
            assert_true(self.mass_complete_title)
            assert_true(self.find_element(By.XPATH, "//span[.='1']"))
        elif type == "mass cancelled":
            assert_true(self.mass_cancelled_title)
            assert_true(self.find_element(By.XPATH, "//span[.='65']"))
        elif type == "mass archived":
            assert_true(self.mass_archived_title)
            assert_true(self.find_element(By.XPATH, "//span[.='45']"))
            assert_true(self.find_element(By.XPATH, "//span[.='25.10.2025']"))

    class FilterPage(BasePage):
        locator_dictionary = {
            "filter_header": (By.XPATH, "//span[.=Filter]"),
            "all_field": (By.XPATH, "//a[.='All']"),
            "request_field": (By.XPATH, "//a[.='Request']"),
            "inprogress_field": (By.XPATH, "//a[.='In progress']"),
            "frozen_field": (By.XPATH, "//a[.='Frozen']"),
            "completed_field": (By.XPATH, "//a[.='Completed']"),
            "cancelled_field": (By.XPATH, "//a[.='Canceled']"),
            "archive_field": (By.XPATH, "//a[.='Archive']"),
        }

        def __init__(self, context):
            BasePage.__init__(
                self,
                context.browser)

    class NotWorksheetPage(BasePage):
        locator_dictionary = {
            "not_worksheet_header": (By.XPATH, "//span[.='Project SearchWorksheet']"),
            "ok_button": (By.XPATH, "//button[@aria-label='Close']")
        }

        def __init__(self, context):
            BasePage.__init__(
                self,
                context.browser)

    class PortfolioSettingsPage(BasePage):
        locator_dictionary = {
            "settings_header": (By.XPATH, "//span[.='Projects Portfolio REC settings']"),
            "ok_button": (By.XPATH, "//button[@aria-label='Close']")
        }

        def __init__(self, context):
            BasePage.__init__(
                self,
                context.browser)


'''
Author   : Pranav Gaikwad
Unity Id : 200203543
'''

import sys
import argparse

from sncaeshelper import AesHelper

def parse_snc_args():
    """
    parses command line arguments
    """
    # custom usage message
    usage_msg = 'snc.py [-h] [-l] --key KEY server port'

    # parses CLI arguments
    argparser = argparse.ArgumentParser(usage=usage_msg)
    # positional arguments
    # Refer https://docs.python.org/2/library/argparse.html#nargs
    argparser.add_argument("connection", nargs='*',
                           help="Connection string in format [server] [port]")
    # other args
    argparser.add_argument(
        "-l", help="listen mode / start as client",
        action="store_true", default=False)
    argparser.add_argument("--key", help="encryption key", required=True)

    args = argparser.parse_args()

    # 'connection' argument is a list of all
    # arguments remained after parsing --key & -l
    # it could be server & port values; or just a
    # port value when '-l' is set
    connection_string_list = args.connection

    # checks if '-l' argument is set.
    # if yes, program will run in 'listen' mode
    listen = args.l

    host = ''
    port = ''
    key = args.key

    if len(key) < AesHelper.LENGTH_IDEAL_KEY:
        sys.stderr.write('Key should be at least %s characters long\n' %
                         str(AesHelper.LENGTH_IDEAL_KEY))
        sys.exit(1)

    # if 'listen' mode is set, connection_string_list only has PORT
    # in it. If not, connection_string_list needs to be parsed
    # as SERVER & port. this is due to limitation of 'argparser'
    # module which does not support conditional positional variables
    if listen:
        try:
            port = int(connection_string_list[0])
        except IndexError:
            sys.stderr.write('Please specify a port\n')
            sys.exit(1)
    else:
        try:
            host = connection_string_list[0]
            port = int(connection_string_list[1])
        except IndexError:
            sys.stderr.write(
                'Please specify connection string in [server] [port] format\n')
            sys.exit(1)
        except ValueError:
            sys.stderr.write('Incorrect host or port\n')
            sys.exit(1)

    return host, port, listen, key


import os
import torch
from torchmd.systems import System
from moleculekit.molecule import Molecule
from torchmd.forcefields.forcefield import ForceField
from torchmd.parameters import Parameters
from torchmd.forces import Forces
from torchmd.integrator import Integrator
from torchmd.wrapper import Wrapper
import numpy as np
from tqdm import tqdm
import argparse
import math
from torchmd.integrator import maxwell_boltzmann
from torchmd.utils import save_argparse, LogWriter, LoadFromFile
from argparse import Namespace
import yaml
import parmed
from ffevaluation.test_ffevaluate import openmm_energy, keepForces
from ffevaluation.ffevaluate import FFEvaluate

args = Namespace(
    **yaml.load(open("./tests/water/water_conf.yaml", "r"), Loader=yaml.FullLoader)
)
precisionmap = {"single": torch.float, "double": torch.double}
precision = precisionmap[args.precision]
replicas = 1
torch.manual_seed(args.seed)
torch.cuda.manual_seed_all(args.seed)
device = torch.device(args.device)

mol = Molecule(args.structure)
frame = 0
mol.dropFrames(keep=frame)
mol.box[:] = np.array(
    [[mol.crystalinfo["a"], mol.crystalinfo["b"], mol.crystalinfo["c"]]]
).T

if args.coordinates is not None:
    mol.read(args.coordinates)

atom_types = mol.atomtype if mol.atomtype[0] else mol.name  # TODO: Fix this crap
print(atom_types)
atom_pos = torch.tensor(mol.coords).permute(2, 0, 1).type(precision)

box = np.swapaxes(mol.box, 1, 0).astype(np.float64)

box_full = torch.zeros((replicas, 3, 3)).type(precision)
for r in range(box.shape[0]):
    box_full[r][torch.eye(3).bool()] = torch.tensor(box[r]).type(precision)

natoms = len(atom_types)
bonds = mol.bonds.astype(int).copy()
angles = mol.angles.astype(int).copy()

print("Force terms: ", args.forceterms)
ff = ForceField.create(mol, args.forcefield)
parameters = Parameters(ff, mol)

# forcefield = ForceField(args.forcefield, precision=precision)
# parameters = forcefield.create(atom_types, bonds=bonds, angles=angles)

atom_vel = maxwell_boltzmann(parameters.masses, args.temperature, replicas)
atom_forces = torch.zeros(replicas, natoms, 3).to(device).type(precision)

forceterms = ["LJ", "Bonds", "Angles", "Electrostatics"]
ommforceterms = ["lennardjones", "bond", "angle", "electrostatic"]

mol_org = mol.copy()

datastore = {}
for forceterm, ommforceterm in zip(forceterms, ommforceterms):
    forceterm = [
        forceterm,
    ]
    ommforceterm = [
        ommforceterm,
    ]
    mol = mol_org.copy()
    print("Force terms: ", forceterm)

    system = System(atom_pos, atom_vel, box_full, atom_forces, precision, device)
    forces = Forces(
        parameters,
        forceterm,
        external=None,
        cutoff=7.3,
        rfa=True,
    )
    Epot = forces.compute(system.pos, system.box, system.forces, returnDetails=True)[0]
    myforces = system.forces.cpu().numpy()[0]

    prm = parmed.charmm.CharmmParameterSet("./tests/water/parameters.prm")
    struct = parmed.charmm.CharmmPsfFile("./tests/water/structure.psf")
    keepForces(prm, struct, mol, forces=ommforceterm)
    omm_energies, omm_forces = openmm_energy(
        prm, struct, mol.coords, box=mol.box, cutoff=7.3
    )

    ffev = FFEvaluate(mol, prm, cutoff=7.3, rfa=True)
    energy, forces_ffev, _ = ffev.calculate(mol.coords, mol.box)
    energy = ffev.calculateEnergies(mol.coords, mol.box)
    forces_ffev = forces_ffev.squeeze()

    def compareForces(forces1, forces2):
        return np.max(np.abs(forces1 - forces2).flatten())

    datastore[forceterm[0]] = {
        "omm": {"energy": omm_energies["total"], "forces": omm_forces},
        "torchmd": {"energy": np.sum([x for _, x in Epot.items()]), "forces": myforces},
        "ffeval": {"energy": energy["total"], "forces": forces_ffev},
    }

for forceterm in datastore:
    print(forceterm)
    ommdata = datastore[forceterm]["omm"]
    torchmddata = datastore[forceterm]["torchmd"]
    ffevaldata = datastore[forceterm]["ffeval"]
    print(f"     Energy diff:", ommdata["energy"] - torchmddata["energy"])
    print(f"     Force diff:", compareForces(ommdata["forces"], torchmddata["forces"]))
    print(f"     FFEVAL: Energy diff:", ommdata["energy"] - ffevaldata["energy"])
    print(
        f"     FFEVAL: Force diff:",
        compareForces(ommdata["forces"], ffevaldata["forces"]),
    )

# print(energy)
# print(Epot)

from torchmd.mycalc import MyCalc
from ase.md.langevin import Langevin
from ase import Atoms
from ase import units

ccc = MyCalc(forces)
atoms = Atoms(
    mol.element.tolist(),
    mol.coords.squeeze(),
    masses=mol.masses.tolist(),
    charges=mol.charge.tolist(),
    pbc=True,
    cell=mol.box.flatten(),
    calculator=ccc,
)

dyn = Langevin(atoms, 2 * units.fs, units.kB * 300, 0.002)
# dyn.run(1000)

def printenergy(a):
    """Function to print the potential, kinetic and total energy"""
    epot = a.get_potential_energy() / len(a)
    ekin = a.get_kinetic_energy() / len(a)
    print(
        "Energy per atom: Epot = %.3feV  Ekin = %.3feV (T=%3.0fK)  "
        "Etot = %.3feV" % (epot, ekin, ekin / (1.5 * units.kB), epot + ekin)
    )

# Now run the dynamics
printenergy(atoms)
for i in range(20):
    dyn.run(10)
    printenergy(atoms)


# -*- coding: utf-8 -*-

"""
File for dealing with the configuration of Pi2O.py.
"""

import os
import logging
import threading
from configparser import SafeConfigParser, NoSectionError

from zone import NullRainSensor, SoftRainSensor, SprinklerZone

__version__ = '0.6'
__all__ = ['CONFIG_FILE', 'LockingConfigParser', 'load_config', 'init_zones',
           'save_config']

# Logger instance
confLogger = logging.getLogger('__main__')

# Maximum number of zones to configure
MAX_ZONES = 6

# Zone -> GPIO pin mapping
ZONE_PIN_MAPPING = {1: 17,
                    2: 27,
                    3: 22,
                    4: 10,
                    5:  9,
                    6: 11}

# Files
## Base path for the various files needed/generated by Pi2O.py
_BASE_PATH = os.path.dirname(os.path.abspath(__file__))

## Configuration
CONFIG_FILE = os.path.join(_BASE_PATH, 'Pi2O.config')

class LockingConfigParser(SafeConfigParser):
    """
    Sub-class of ConfigParser.SafeConfigParser that wraps the get, set, and
    write methods with a semaphore to ensure that only one get/set/read/write
    happens at a time.  The sub-class also adds a 'dict' property that makes
    it easier to tie the configuration into webforms.
    """

    _lock = threading.Semaphore()

    def get(self, *args, **kwds):
        """
        Locked get() method.
        """

        #self._lock.acquire()
        value = SafeConfigParser.get(self, *args, **kwds)
        #self._lock.release()
        return value

    def getint(self, *args, **kwds):
        """
        Locked getint() method.
        """

        value = SafeConfigParser.getint(self, *args, **kwds)
        return int(value)

    def getfloat(self, *args, **kwds):
        """
        Locked getfloat() method.
        """

        value = SafeConfigParser.getfloat(self, *args, **kwds)
        return float(value)

    def set(self, *args, **kwds):
        """
        Locked set() method.
        """

        #self._lock.acquire()
        SafeConfigParser.set(self, *args, **kwds)
        #self._lock.release()

    def read(self, *args, **kwds):
        """
        Locked read() method.
        """

        SafeConfigParser.read(self, *args, **kwds)

    def write(self, *args, **kwds):
        """
        Locked write() method.
        """

        SafeConfigParser.write(self, *args, **kwds)

    @property
    def dict(self):
        """
        Return the configuration as a dictionary with keys structured as
        section-option.
        """

        configDict = {}
        for section in self.sections():
            for keyword,value in self.items(section):
                configDict['%s-%s' % (section.lower(), keyword.replace('_', '-'))] = value

        # Done
        return configDict

    @dict.setter
    def dict(self, configDict):
        """
        Given a dictionary returned by the dict attribute, update the
        configuration as needed.
        """

        # Loop over the pairs in the dictionary
        assert(isinstance(configDict, dict))
        for key,value in configDict.items():
            try:
                section, keyword = key.split('-', 1)
                keyword = keyword.replace('-', '_')
                section = section.capitalize()
                if section == 'Rainsensor':
                    section = 'RainSensor'
                self.set(section, keyword, value)
            except Exception as e:
                print(str(e))
                pass

def load_config(filename):
    """
    Read in the configuration file and return a LockingConfigParser instance.
    """

    # Initial configuration file
    config = LockingConfigParser()

    ## Dummy information about the four zones:
    ##  1) name - zone nickname
    ##  2) pin - RPi GPIO pin
    ##  3) enabled - whether or not the zone is active
    for zone in range(1, MAX_ZONES+1):
        config.add_section(f"Zone{zone}")
        for keyword in ('name', 'enabled'):
            config.set(f"Zone{zone}", keyword, '')
            if keyword == 'enabled':
                config.set(f"Zone{zone}", keyword, 'off')

    ## Dummy rain sensor information
    ##  1) type - off, software, or hardware
    ##  2) precip - precipitation cutoff for the software rain sensor
    config.add_section('RainSensor')
    config.set('RainSensor', 'type', 'off')
    config.set('RainSensor', 'precip', '')

    ## Dummy schedule information - one for each month
    ##  1) start - start time as HH:MM, 24-hour format
    ##  2) duration - duration in minutes
    ##  3) interval - run interval in days
    ##  4) enabled - whether or not the schedule is active
    ##  5) wxadjust - whether or not weather adjustments should be applied
    for month in range(1, 13):
        config.add_section(f"Schedule{month}")
        for keyword in ('start', 'duration', 'interval', 'enabled', 'wxadjust'):
            if keyword == 'duration':
                for zone in range(1, MAX_ZONES+1):
                    config.set(f"Schedule{month}", f"{keyword}{zone}", '')
            else:
                config.set(f"Schedule{month}", keyword, '')
            if keyword in ('enabled', 'wxadjust'):
                config.set(f"Schedule{month}", keyword, 'off')

    ## Dummy weather station information
    ##  1) pws - PWS ID to use for weather info
    ##  2) max_adjust - maximum weather adjustment percentage
    ##  3) enabled - whether or not use to WUnderground
    config.add_section('Weather')
    for keyword in ('pws', 'max_adjust', 'enabled'):
        config.set('Weather', keyword, '')
        if keyword == 'enabled':
            config.set('Weather', keyword, 'off')
        if keyword == 'max_adjust':
            config.set('Weather', keyword, '200')

    # Try to read in the actual configuration file
    try:
        config.read(filename)
        confLogger.info('Loaded configuration from \'%s\'', os.path.basename(filename))

    except:
        pass

    # Done
    return config

def init_zones(config):
    """
    Given a LockingConfigParser configuration instance, create a list of
    SprinklerZone instances to control the various zones.
    """

    # Initialize the rain sensor
    if config.get('RainSensor', 'type') == 'off':
        rainSensor = NullRainSensor()
    else:
        rainSensor = SoftRainSensor( config.getfloat('RainSensor', 'precip'), config )

    # Create the list of SprinklerZone instances
    zones = []
    zone = 1
    while True:
        try:
            ## Is the zone enabled?
            zoneEnabled = config.get(f"Zone{zone}", 'enabled')
            if zoneEnabled == 'on':
                ### If so, use the real GPIO pin
                zonePin = ZONE_PIN_MAPPING[zone]
                ### If not, use a dummy pin
            else:
                zonePin = -1

            ## Create the SprinklerZone instance
            zones.append( SprinklerZone(zonePin, rainSensor=rainSensor) )

            ## Update the counter
            zone += 1

        except NoSectionError:
            break

    # Done
    return zones

def save_config(filename, config):
    """
    Given a filename and a LockingConfigParser, write the configuration to
    disk.
    """

    with open(filename, 'w') as fh:
        config.write(fh)

    confLogger.info('Saved configuration to \'%s\'', os.path.basename(filename))


import pandas as pd
import numpy as np
import re
import argparse
import os
from os import fdopen, remove
from tempfile import mkstemp
from shutil import move

import seaborn as sns
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt

def plot_confusionmatrix(y_true, y_pred, filename, labels, figsize=(10, 10)):
    """
    Generate matrix plot of confusion matrix with pretty annotations.
    The plot image is saved to disk.
    Adapted from https://gist.github.com/hitvoice/36cf44689065ca9b927431546381a3f7
    args:
      y_true:    true label of the data, with shape (nsamples,)
      y_pred:    prediction of the data, with shape (nsamples,)
      filename:  filename of figure file to save
      labels:    string array, name the order of class labels in the confusion matrix.
                 use `clf.classes_` if using scikit-learn models.
                 with shape (nclass,).
      figsize:   the size of the figure plotted.
    """

    cm = confusion_matrix(y_true, y_pred, labels=labels)
    cm_sum = np.sum(cm, axis=1, keepdims=True)
    cm_perc = cm / cm_sum.astype(float) * 100

    annot = np.empty_like(cm).astype(str)
    nrows, ncols = cm.shape
    for i in range(nrows):
        for j in range(ncols):
            c = cm[i, j]
            p = cm_perc[i, j]
            if i == j:
                s = cm_sum[i]
                annot[i, j] = "%.1f%%\n%d/%d" % (p, c, s)
            elif c == 0:
                annot[i, j] = ""
            else:
                annot[i, j] = "%.1f%%\n%d" % (p, c)
    cm = pd.DataFrame(cm, index=labels, columns=labels)
    cm.index.name = "Actual"
    cm.columns.name = "Predicted"
    fig, ax = plt.subplots(figsize=figsize)
    sns.heatmap(cm, annot=annot, fmt="", ax=ax)
    plt.savefig(filename, bbox_inches="tight")

def harmonic_score(scores):
    """
    Calculate the harmonic mean of a list of scores
    Args:
        scores (list): list of scores

    Returns:
        harmonic mean of scores
    """
    return len(scores) / sum((c + 1e-6) ** -1 for c in scores)

def gen_score_overview(preds_filename):
    """
    Generate a dataframe with several performance measures
    Args:
        preds_filename: name of file where predictions are saved

    Returns:
        score_overview (pd.DataFrame): dataframe with several performance measures
        df_pred (pd.DataFrame): dataframe with the predictions and true labels
    """
    preds_file = open(preds_filename)
    lines = preds_file.readlines()[1:]
    pred_info = []
    for l in lines:
        pred_info.extend([l.rstrip().split(" ")])
    df_pred = pd.DataFrame(pred_info, columns=["OBJECTID", "label", "pred"])
    df_pred.label = df_pred.label.astype(int)
    df_pred.pred = df_pred.pred.astype(int)

    preds = np.array(df_pred.pred)
    labels = np.array(df_pred.label)

    report = classification_report(labels, preds, digits=3, output_dict=True)
    score_overview = pd.DataFrame(report).transpose()

    score_overview = score_overview.append(pd.Series(name="harmonized avg"))
    score_overview.loc["harmonized avg", ["precision", "recall", "f1-score"]] = [
        harmonic_score(r)
        for i, r in score_overview.loc[
            ["0", "1", "2", "3"], ["precision", "recall", "f1-score"]
        ].T.iterrows()
    ]

    # create report only for damage categories (represented by 1,2,3)
    dam_report = classification_report(
        labels, preds, labels=[1, 2, 3], output_dict=True
    )
    dam_report = pd.DataFrame(dam_report).transpose()

    score_overview = score_overview.append(pd.Series(name="damage macro avg"))
    score_overview = score_overview.append(pd.Series(name="damage weighted avg"))
    score_overview = score_overview.append(pd.Series(name="damage harmonized avg"))

    score_overview.loc[
        "damage macro avg", ["precision", "recall", "f1-score", "support"]
    ] = (
        dam_report.loc[["macro avg"], ["precision", "recall", "f1-score", "support"]]
        .values.flatten()
        .tolist()
    )

    score_overview.loc[
        "damage weighted avg", ["precision", "recall", "f1-score", "support"]
    ] = (
        dam_report.loc[["weighted avg"], ["precision", "recall", "f1-score", "support"]]
        .values.flatten()
        .tolist()
    )

    score_overview.loc["damage harmonized avg", ["precision", "recall", "f1-score"]] = [
        harmonic_score(r)
        for i, r in score_overview.loc[
            ["1", "2", "3"], ["precision", "recall", "f1-score"]
        ].T.iterrows()
    ]

    damage_mapping = {
        "0": "No damage",
        "1": "Minor damage",
        "2": "Major damage",
        "3": "Destroyed",
    }
    score_overview.rename(index=damage_mapping, inplace=True)
    return score_overview, df_pred

def create_overviewdict(df_overview):
    scores_params = [
        "harmonized_f1",
        "macro recall",
        "harmonized_recall_damage",
        "weighted_recall_damage",
        "macro_recall_damage",
        "support damage",
        "support all",
        "percentage damage",
    ]

    scores_dict = dict.fromkeys(scores_params)

    # save overview params
    scores_dict["harmonized_f1"] = df_overview.loc["harmonized avg", "f1-score"]
    scores_dict["macro recall"] = df_overview.loc["macro avg", "recall"]
    scores_dict["harmonized_recall_damage"] = df_overview.loc[
        "damage harmonized avg", "recall"
    ]
    scores_dict["weighted_recall_damage"] = df_overview.loc[
        "damage weighted avg", "recall"
    ]
    scores_dict["macro_recall_damage"] = df_overview.loc["damage macro avg", "recall"]
    scores_dict["support damage"] = int(df_overview.loc["damage macro avg", "support"])
    scores_dict["support all"] = int(df_overview.loc["macro avg", "support"])
    scores_dict["percentage damage"] = round(
        scores_dict["support damage"] / scores_dict["support all"] * 100, 1
    )
    return scores_dict

def save_overviewfile(
    overview_dict, run_name, output_path, filename="allruns_scores.txt"
):
    """
    Save overview_dict to a file with all other runs.
    Args:
        overview_dict: Keys and values that should be saved
        run_name: name of experiment
        output_path: path to output file
        filename: name of output file
    """
    # save parameters to overview file
    # replace old values if line with same run_name already exists
    overview_path = os.path.join(output_path, filename)
    fh, abs_path = mkstemp()
    replicate = False
    scores_dict_rounded = {
        k: round(v, 3) if v is not None else "" for k, v in overview_dict.items()
    }
    with fdopen(fh, "w+") as new_file:
        new_file.write(
            "run_name,{}\n".format(
                ",".join(str(item) for item in list(overview_dict.keys()))
            )
        )
        if os.path.isfile(overview_path):
            with open(overview_path) as old_file:
                next(old_file)
                for line in old_file:
                    if re.search(r"^{},".format(run_name), line):
                        replicate = True
                        new_file.write(
                            "{},{}\n".format(
                                run_name,
                                ",".join(
                                    str(item)
                                    for item in list(scores_dict_rounded.values())
                                ),
                            )
                        )
                    else:
                        new_file.write(line)
        if not replicate:
            new_file.write(
                "{},{}\n".format(
                    run_name,
                    ",".join(str(item) for item in list(scores_dict_rounded.values())),
                )
            )
    if os.path.isfile(overview_path):
        remove(overview_path)
    move(abs_path, overview_path)

def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )

    parser.add_argument(
        "--run-name", type=str, required=True, help="name to identify execution",
    )

    parser.add_argument(
        "--run-folder",
        metavar="Folder name of outputs of run",
        help="Full path to the directory with txt file with labels and predictions",
    )

    parser.add_argument(
        "--checkpoint-folder",
        type=str,
        default=os.path.join(".", "runs"),
        help="runs path",
    )

    args = parser.parse_args()
    if not args.run_folder:
        args.run_folder = os.path.join(
            args.checkpoint_folder,
            "{}-input_size_32-learning_rate_0.001-batch_size_32".format(args.run_name),
        )

    # define all file names and paths
    test_file_name = "{}-split_test-epoch_001-model_siamese-predictions.txt".format(
        args.run_name
    )
    preds_model = "{}/predictions/{}".format(args.run_folder, test_file_name)
    preds_random = "{}-split_test-epoch_001-model_random-predictions.txt".format(
        args.run_name
    )
    preds_average = "{}-split_test-epoch_001-model_average-predictions.txt".format(
        args.run_name
    )
    output_path = "./performance/"
    score_overviews_path = os.path.join(output_path, "score_overviews/")
    confusion_matrices_path = os.path.join(output_path, "confusion_matrices/")

    for p in [output_path, score_overviews_path, confusion_matrices_path]:
        if not os.path.exists(p):
            os.makedirs(p)

    for preds_filename, preds_type in zip(
        [preds_model, preds_random, preds_average], ["model", "random", "average"]
    ):
        # check if file for preds type exists
        if os.path.exists(preds_filename):
            # generate overview with performance measures
            score_overview, df_pred = gen_score_overview(preds_filename)
            score_overview.to_csv(
                "{}{}_overview_{}.csv".format(
                    score_overviews_path, args.run_name, preds_type
                )
            )

            if preds_type == "model":
                scores_dict = create_overviewdict(score_overview)
                save_overviewfile(
                    scores_dict,
                    args.run_name,
                    output_path,
                    filename="allruns_scores.txt",
                )
                # generate and save confusion matrix
                plot_confusionmatrix(
                    df_pred.label,
                    df_pred.pred,
                    "{}{}_confusion".format(confusion_matrices_path, args.run_name),
                    [0, 1, 2, 3],
                    figsize=(9, 12),
                )

        else:
            print("No predictions for prediction type {}".format(preds_type))

if __name__ == "__main__":
    main()


#!/usr/bin/env python
#-*- coding:utf-8 -*-
"""
This module provides the plots
"""
__author__    = "I-Huan CHIU"
__email__     = "ichiu@chem.sci.osaka-u.ac.jp"
__created__   = "2019-11-08"
__copyright__ = "Copyright 2019 I-Huan CHIU"
__license__   = "GPL http://www.gnu.org/licenses/gpl.html"

# modules
import sys,os,random,math,ROOT
from ROOT import TFile, TTree, gROOT, gStyle, TCut, gPad, gDirectory
ROOT.gROOT.SetBatch(1)
import argparse
sys.path.append('/Users/chiu.i-huan/Desktop/new_scientific/imageAna/macro/utils/')
#gROOT.ProcessLine("gErrorIgnoreLevel = kPrint, kInfo, kWarning, kError, kBreak, kSysError, kFatal;")
ROOT.gErrorIgnoreLevel = ROOT.kWarning

from logger import log, supports_color
from helpers import GetTChain, createRatioCanvas
from color import SetMyPalette
from slice3D import MakeSlicePlots, makeTH2D, mergeTH2D
from filtbp import Filter, SimpleBackProjection
import enums

__location__ = os.path.realpath(
        os.path.join(os.getcwd(), os.path.dirname(__file__)))
ROOT.gROOT.LoadMacro( __location__+'/AtlasStyle/AtlasStyle.C')
#ROOT.SetAtlasStyle()

#from scipy.spatial.transform import Rotation as R
import numpy as np

def run3Dimage(args):
    h3d_t = ROOT.TH3D("solid_t_{}".format(args.cut),"solid_t_{}".format(args.cut),32,-16,16,32,-16,16,32,-16,16)
    h3d_t.SetXTitle("x")
    h3d_t.SetYTitle("y")
    h3d_t.SetZTitle("z")

    if args.input3Dhist is None:
       log().info("Preparing 2D image...")
       treesum = GetTChain(args.inputFolder,"tree")
       _ihlist = makeTH2D(treesum,args.dtype)
       ihlist  = mergeTH2D(_ihlist)

       log().info("Processing Back Projection...")
#       SBP = SimpleBackProjection(h2list=ihlist)
#       h3d=SBP.h3d
       FBP = Filter(h2list=ihlist)
       h3d=FBP.filtH3

    else:
       r3dfile  =  ROOT.TFile(args.input3Dhist)
       h3d = r3dfile.Get("solid")

    log().info("Making 3D plots")
    cv  = createRatioCanvas("cv", 1600, 1600)
    _h3d_t = h3d.Clone()
    if (_h3d_t.GetNbinsX() >= 128) : _h3d_t.Rebin3D(2,2,2)
    for _ix in range(1,_h3d_t.GetXaxis().GetNbins()+1):
       for _iy in range(1,_h3d_t.GetYaxis().GetNbins()+1):
          for _iz in range(1,_h3d_t.GetZaxis().GetNbins()+1):
             _bin = _h3d_t.GetBin(_ix,_iy,_iz)
             _x,_y,_z=_h3d_t.GetXaxis().GetBinCenter(_ix),_h3d_t.GetYaxis().GetBinCenter(_iy),_h3d_t.GetZaxis().GetBinCenter(_iz)
             if(_h3d_t.GetBinContent(_bin) > args.cut): h3d_t.Fill(_x,_y,_z,_h3d_t.GetBinContent(_bin))
    SetMyPalette("RB",0.5)
    h3d_t.Draw("BOX2Z")
    if args.output is not None: _outfig = "/Users/chiu.i-huan/Desktop/new_scientific/imageAna/run/figs/hist_3D_image."+args.dtype+"."+args.output+"_cut{}.ROOT.pdf".format(args.cut)
    else: _outfig = "/Users/chiu.i-huan/Desktop/new_scientific/imageAna/run/figs/hist_3D_image."+args.dtype+"_cut{}.ROOT.pdf".format(args.cut)
    cv.Print(_outfig)

    # === make slices along xyz-axis & projection ===
    if args.input3Dhist is None:

       log().info("Making 2D Slices")
       SetMyPalette("Bird",1)
       _MS = MakeSlicePlots(_hist3=h3d)
       h2_list_x, h2_list_y, h2_list_z = _MS.GetSlices("x"), _MS.GetSlices("y"), _MS.GetSlices("z")

       log().info("Storing all 2D & 3D plots")
       _out = "/Users/chiu.i-huan/Desktop/new_scientific/imageAna/run/figs/repro_3Dimage"+"."+args.dtype
       if args.output is not None: outname = _out + "_" +args.output + ".root"
       else: outname = _out+".root"
       log().info("Output : %s, in: /Users/chiu.i-huan/Desktop/new_scientific/imageAna/run/figs"%(outname))
       f = ROOT.TFile( outname, 'recreate' )
       f.cd()

       for _h2 in ihlist:
          _h2.Write()
       for _ih2 in range(len(FBP.filth2)):
          FBP.filth2[_ih2].SetTitle("Filt, angle : %.1f%s"%(360./len(FBP.filth2)*_ih2,enums.DEG))
          FBP.filth2[_ih2].Write()
       h3d_t.Write()
       h3d.Write()

       RebinSize = 2
       if len(ihlist) == 16:
          SetMyPalette("Bird",1)
          cv2  = createRatioCanvas("cv2", 3600, 3600)
          cv2.Divide(4,4)
          for _ih in range(len(ihlist)):
             cv2.cd(_ih+1).SetRightMargin(0.18)
             ihlist[_ih].Rebin2D(RebinSize,RebinSize)
             ihlist[_ih].SetStats(0)
             ihlist[_ih].SetXTitle("x")
             ihlist[_ih].SetYTitle("y")
             ihlist[_ih].SetTitle("angle : %.1f%s"%(360./len(ihlist)*_ih,enums.DEG))
             ihlist[_ih].Draw("colz")
          _out2dfig = _outfig.replace("hist_3D_image", "hist_2D_image")
          cv2.Print(_out2dfig)
       else:
          log.info("Cannot make 2D images, check angle range !")
       cv2.Write()
       f.Write()

    # check mk2dplots_forSipaper.py in script
#    if args.seperation:
#       RebinSize = 2
#       ROOT.gStyle.SetPalette(53)
#       for _ih in range(len(ihlist)):
#          cv3  = createRatioCanvas("cv3_{}".format(_ih), 1400, 1200)
#          cv3.SetRightMargin(0.18)
#          ihlist[_ih].Rebin2D(RebinSize,RebinSize)
#          ihlist[_ih].SetStats(0)
#          ihlist[_ih].SetTitle(";X [mm];Y [mm]")
#          ihlist[_ih].GetXaxis().CenterTitle(); ihlist[_ih].GetYaxis().CenterTitle();
#          ihlist[_ih].Draw("colz")
#          _outsepfig = _outfig.replace("hist_3D_image", "hist_2D_image_{}".format(_ih))
#          cv3.Print(_outsepfig)

    else:
       _out = "/Users/chiu.i-huan/Desktop/new_scientific/imageAna/run/figs/repro_3Dimage"+"."+args.dtype
       if args.output is not None: outname = _out + "_" +args.output + ".root"
       else: outname = _out+".root"
       f = ROOT.TFile( outname, 'update' )
       f.cd()
       h3d_t.Write()

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument("-i", "--inputFolder", type=str, default="/Users/chiu.i-huan/Desktop/new_scientific/imageAna/run/root/CdTe_root/", help="Input Ntuple Name")
    parser.add_argument("-o", "--output", type=str, default=None, help="Output file")
    parser.add_argument("-p", "--input3Dhist", type=str, default=None, help="Input 3D file")
    parser.add_argument("-d", "--dtype", dest="dtype", type=str, default = "CdTe_30MeV", help="Si_ or CdTe_ + 30MeV or 35MeV or none" )
    parser.add_argument("-c", "--cut", type=int, default = 100, help="count cut for 3D image" )
    parser.add_argument("-s", "--seperation", type=bool, default = False, help="separated 2D image" )
    args = parser.parse_args()

    run3Dimage( args )


import cv2
import numpy as np
cv2.namedWindow("Window")
def empty(x):
    pass
cap=cv2.VideoCapture(0)

success,frame1=cap.read()
#frame1=cv2.resize(frame1,(0,0),None,0.2,0.2)
success,frame2=cap.read()
#frame2=cv2.resize(frame2,(0,0),None,0.2,0.2)
cv2.createTrackbar("Threshold1","Window",20,255,empty)
cv2.createTrackbar("Threshold2","Window",255,255,empty)
while True:

    diff=cv2.absdiff(frame2,frame1)
    diffgray=cv2.cvtColor(diff,cv2.COLOR_BGR2GRAY)
    diffblur=cv2.GaussianBlur(diffgray,(5,5),0)
    thresh1=cv2.getTrackbarPos("Threshold1","Window")
    thresh2=cv2.getTrackbarPos("Threshold2","Window")
    _,thr=cv2.threshold(diffblur,thresh1,thresh2,cv2.THRESH_BINARY)

    kernel=np.ones((5,5))
    imgdilate=cv2.dilate(thr,kernel,iterations=3)
    imgerode=cv2.erode(imgdilate,kernel,iterations=1)
    #cv2.imshow("imgdil",imgdilate)
    #cv2.imshow("imgerr",imgerode)
    contours,_=cv2.findContours(imgerode,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    for cnt in contours:
        area=cv2.contourArea(cnt)
        if area>2000:
            #cv2.drawContours(frame1,cnt,-1,(0,255,0),3)
            peri=cv2.arcLength(cnt,True)
            approx=cv2.approxPolyDP(cnt,0.02*peri,True)
            x,y,w,h=cv2.boundingRect(approx)
            cv2.rectangle(frame1,(x,y),(x+w,y+h),(0,255,0),3)
            cv2.putText(frame1,"Movement",(20,40),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)

    cv2.imshow("track",frame1)

    frame1=frame2
    success,frame2=cap.read()
    #frame2=cv2.resize(frame2,(0,0),None,0.2,0.2)
    if cv2.waitKey(1) & 0xFF==ord('q'):
        break
cv2.destroyAllWindows()
cap.release()


#! usr/bin/python
#coding=utf-8

# importing the basic library
from __future__ import print_function
from utils import *
ctx = check_ctx()

# importing MxNet >= 1.0
import mxnet as mx
from mxnet import ndarray as nd
from mxnet import autograd, gluon

import random

mx.random.seed(1)
random.seed(1)

def test_ctx():
    try:
        ctx = mx.gpu()
        _ = nd.zeros((1,), ctx=ctx)
    except:
        ctx = mx.cpu()
    print('CPU or GPU? : ', ctx)

def lr_decay(lr, epoch, lr_decay):
    # 学习率自我衰减。
    if epoch > 4:
#         lr *= 0.1
        lr /= (1+lr_decay * epoch)
    return lr

# Mini-batch stochastic gradient descent.
def sgd(params, lr, batch_size):
    for param in params:
        param[:] = param - lr * param.grad / batch_size
    return params

# Adam.
def adam(params, vs, sqrs, lr, batch_size, t):
    beta1 = 0.9
    beta2 = 0.999
    eps_stable = 1e-8
#     print(params)
    for param, v, sqr in zip(params, vs, sqrs):
#         print(param)
        g = params[param].grad / batch_size

        v[:] = beta1 * v + (1. - beta1) * g
        sqr[:] = beta2 * sqr + (1. - beta2) * nd.square(g)

        v_bias_corr = v / (1. - beta1 ** t)
        sqr_bias_corr = sqr / (1. - beta2 ** t)

        div = lr * v_bias_corr / (nd.sqrt(sqr_bias_corr) + eps_stable)
        params[param][:] = params[param] - div

    return params, vs, sqrs

if __name__ == '__main__':
    print('CPU or GPU? : ', ctx)


import time
from typing import Optional
from CSP.Problem import Problem
from CSP.Variable import Variable

class Solver:

    def __init__(self, problem: Problem, use_mrv=False, use_lcv=False, use_forward_check=False):
        self.problem = problem
        self.use_lcv = use_lcv
        self.use_mrv = use_mrv
        self.use_forward_check = use_forward_check

    def is_finished(self) -> bool:
        return all([x.is_satisfied() for x in self.problem.constraints]) and len(
            self.problem.get_unassigned_variables()) == 0

    def solve(self):
        self.problem.calculate_neighbors()
        start = time.time()
        for var in self.problem.variables:
            if not self.forward_check(var):
                print("Problem Unsolvable")
                return
        result = self.backtracking()
        end = time.time()
        time_elapsed = (end - start) * 1000
        if result:
            print(f'Solved after {time_elapsed} ms')
        else:
            print(f'Failed to solve after {time_elapsed} ms')

    def backtracking(self):
        if self.is_finished():
            return True
        var = self.select_unassigned_variable()
        if var is None:
            return False
        for value in self.order_domain_values(var):
            var.value = value
            if self.use_forward_check:
                self.forward_check(var)
            if self.is_consistent(var):
                if self.backtracking():
                    return True
            var.value = None
        return False

    def forward_check(self, var):
        for neighbor in var.neighbors:
            if not neighbor.has_value:
                for value in neighbor.domain:
                    neighbor.value = value
                    consistent = self.is_consistent(neighbor)
                    neighbor.value = None
                    if not consistent:
                        neighbor.domain.remove(value)
                if not neighbor.domain:
                    return False
        return True

    def select_unassigned_variable(self) -> Optional[Variable]:
        if self.use_mrv:
            return self.mrv()
        unassigned_variables = self.problem.get_unassigned_variables()
        return unassigned_variables[0] if unassigned_variables else None

    def order_domain_values(self, var: Variable):
        if self.use_lcv:
            return self.lcv(var)
        return var.domain

    def mrv(self) -> Optional[Variable]:
        unassigned_variables = self.problem.get_unassigned_variables()
        if not unassigned_variables:
            return None
        return min(unassigned_variables, key=lambda var: len(var.domain))

    def is_consistent(self, var: Variable):
        for neighbor in var.neighbors:
            if neighbor.has_value and neighbor.value == var.value:
                return False
        return True

    def lcv(self, var: Variable):
        value_count = {}
        for value in var.domain:
            count = 0
            for neighbor in var.neighbors:
                if not neighbor.has_value and value in neighbor.domain:
                    count += 1
            value_count[value] = count
        sorted_values = sorted(value_count, key=value_count.get)
        return sorted_values


#!/bin/python
import time
import boto3
import csv
import numpy as np
from pms3003 import PMSensor
import RPi.GPIO as GPIO
import dht11

# set up project path for csv generation
path = '/project/path/'

# set sensor environment - indoor/outdoor (0/1)
environment = 1

# set up gpio serial port
# /dev/ttyAMA0 -> Bluetooth (or GPIO when Bluetooth module turned off)
# /dev/ttyS0 -> GPIO serial port (also referenced as '/dev/serial0')
device = '/dev/serial0'

# set up dynamodb table name
dynamodb_table = 'pms3003'

# get dht11 data
# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read dht11 data using pin 4
instance = dht11.DHT11(pin = 4)		#verify if pin is correct

# measure hum and temp (n-1)-times
n = 10
data_dht = []

# loop over
for i in range(1,int(n/2)):
        while True:
                result = instance.read()
                if result.is_valid():
                        data_dht = np.append([data_dht], [result.temperature, result.humidity])
                        break
        time.sleep(2)

# call a PMSensor class
pm = PMSensor(device, environment)

# get PM1, PM2.5, PM10 values
data = pm.read_pm()

# reject outliers
data = data[np.all(np.abs((data - np.mean(data, axis=0))) <= 2 * np.std(data, axis=0), axis=1)]
data = data[np.array(data[:,2], dtype=float)/np.array(data[:,1], dtype=float) < 2]
data = data[np.array(data[:,2], dtype=float) > np.array(data[:,1], dtype=float)]

# get the pm average as an int
pm1, pm25, pm10 = np.mean(data, axis=0, dtype=int)

# continue measuring hum and temp
for i in range(1,int(n/2)):
        while True:
                result = instance.read()
                if result.is_valid():
                        data_dht = np.append([data_dht], [result.temperature, result.humidity])
                        break
        time.sleep(2)

# GPIO cleanup
GPIO.cleanup()

# reshape and reject outliers
data_dht = data_dht.reshape((n-2,2))
data_dht = data_dht[np.all(np.abs((data_dht - np.mean(data_dht, axis=0))) <= 2 * np.std(data_dht, axis=0), axis=1)]

# get the dht average as an int
temp, hum = np.mean(data_dht, axis=0, dtype=int)

# get current timestamp
pm_date = (time.strftime('%Y-%m-%d ') + time.strftime('%H:%M:%S'))

# write to csv
with open(path + 'pm-archive.csv','a+') as f:
 writer = csv.writer(f)
 writer.writerow([pm_date,pm1,pm25,pm10,temp,hum])

# write to dynamodb table
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(dynamodb_table)

try:
        # insert the data
        table.put_item(
                Item={
                'device' : 'pms3003',
                'dt' : pm_date,
                'pm1' : str(pm1),
                'pm25' : str(pm25),
                'pm10' : str(pm10),
                'temp' : str(temp),
                'hum' : str(hum)
                }
        )

except Exception:
        # write to csv in case of error
        with open(path + 'pm-not-loaded.csv','a+') as fn:
         writer = csv.writer(fn)
         writer.writerow([pm_date,pm1,pm25,pm10,temp,hum])

	# SNS notification about an unsuccessful load
        #sns = boto3.resource('sns')
        #topic = sns.Topic('arn::pastehere::')
        #response = topic.publish(
        #       Message='Problem loading data on: ' + pm_date,
        #       Subject='PMS3003 Unsuccessful Data Load',
        #       MessageStructure='string'
        #)


from datasette.app import Datasette
import pytest

@pytest.mark.asyncio
async def test_plugin_is_installed(tmpdir):
    templates = tmpdir / "templates"
    templates.mkdir()
    pages = tmpdir / "templates" / "pages"
    pages.mkdir()
    (pages / "demo.html").write_text(
        "request.args = {{ request.args._data|safe }}", "utf-8"
    )
    datasette = Datasette([], memory=True, template_dir=str(templates))
    response = await datasette.client.get("/demo?foo=bar")
    assert response.status_code == 200
    assert response.text == "request.args = {'foo': ['bar']}"


from __future__ import annotations
import os
import sys
import torch
import logging
import numpy as np
from typing import Any, Optional, Union, Callable
from dataclasses import dataclass, field, asdict
from datasets import Dataset
from transformers import (
    Wav2Vec2Processor,
    HfArgumentParser,
    TrainingArguments as HFTrainingArguments,
    set_seed,
    Trainer,
    AutoConfig,
    AutoModelForCTC,
    EarlyStoppingCallback
)
from transformers.trainer_utils import get_last_checkpoint, is_main_process
from transformers.trainer_pt_utils import get_parameter_names
from huggingsound.metrics import cer, wer

logger = logging.getLogger(__name__)

@dataclass
class ModelArguments:
    """
    Model arguments

    Parameters:
    ----------
    freeze_feature_extractor: Optional[bool] = True
        Whether to freeze the feature extractor layers of the model.

    attention_dropout: Optional[float] = 0.1
        The dropout ratio for the attention probabilities.

    activation_dropout: Optional[float] = 0.1
        The dropout ratio for activations inside the fully connected layer.

    hidden_dropout: Optional[float] = 0.1
        The dropout probabilitiy for all fully connected layers in the embeddings, encoder, and pooler.

    feat_proj_dropout: Optional[float] = 0.1
        The dropout probabilitiy for all 1D convolutional layers in feature extractor.

    final_dropout: Optional[float] = 0.1
        The dropout probabilitiy for CTC head.

    mask_time_prob: Optional[float] = 0.05
        Propability of each feature vector along the time axis to be chosen as the start of the vector
        span to be masked. Approximately ``mask_time_prob * sequence_length // mask_time_length`` feature
        vectors will be masked along the time axis. This is only relevant if ``apply_spec_augment is True``.

    mask_time_length: Optional[int] = 10
        Length of vector span to mask along the time axis.

    mask_feature_prob: Optional[float] = 0.0
        Propability of each feature vector along the feature axis to be chosen as the start of the vector span to
        be masked. Approximately ``mask_time_prob * hidden_size // mask_time_length`` feature vectors will be
        masked along the time axis. This is only relevant if ``apply_spec_augment is True``."

    mask_feature_length: Optional[int] = 10
        Length of vector span to mask along the feature axis.

    layerdrop: Optional[float] = 0.0
        The probability to randomly drops layers at training time. For reference see https://arxiv.org/abs/1909.11556

    apply_spec_augment: Optional[bool] = True
        Whether to apply *SpecAugment* data augmentation to the outputs of the feature extractor. For reference see <https://arxiv.org/abs/1904.08779>"

    ctc_loss_reduction: Optional[str] = "sum"
        Specifies the reduction to apply to the output of ``torch.nn.CTCLoss``. Can be 'sum' or 'mean'.

    ctc_zero_infinity: Optional[bool] = False
        Whether to zero infinite losses and the associated gradients of ``torch.nn.CTCLoss``. Infinite losses
        mainly occur when the inputs are too short to be aligned to the targets
    """

    freeze_feature_extractor: bool = field(default=True)
    attention_dropout: float = field(default=0.1)
    activation_dropout: float = field(default=0.1)
    hidden_dropout: float = field(default=0.1)
    feat_proj_dropout: float = field(default=0.1)
    final_dropout: float = field(default=0.1)
    mask_time_prob: float = field(default=0.05)
    mask_time_length: int = field(default=10)
    mask_feature_prob: float = field(default=0.0)
    mask_feature_length: int = field(default=10)
    layerdrop: float = field(default=0.0)
    apply_spec_augment: bool = field(default=True)
    ctc_loss_reduction: str = field(default="sum")
    ctc_zero_infinity: bool = field(default=False)

@dataclass
class TrainingArguments:
    """
    Training arguments

    Parameters:
    ----------
    overwrite_output_dir: Optional[bool] = False
        If True, it will overwrite the content of the output directory.
        If False, it will continue training if output directory points to a checkpoint directory.

    ignore_pretrained_weights: Optional[bool] = False
        If True, it will initializar all the model's weight randonly, i.e., it it will not use the pre-trained model weights during training.

    dataloader_num_workers: Optional[int] = 0
        Number of subprocesses to use for data loading (PyTorch only). 0 means that the data will be loaded in the main process.

    learning_rate: Optional[float] = 3e-4
        The learning rate for AdamW optimizer.

    min_learning_rate: Optional[float] = 0.0
        The minimum learning rate for AdamW optimizer. (only used if lr_warmup_steps or lr_decay_steps are set)

    weight_decay: Optional[float] = 0.0
        The weight decay to apply (if not zero) to all layers except all bias and LayerNorm weights in AdamW optimizer.

    adam_beta1: Optional[float] = 0.9
        The beta1 hyperparameter for the AdamW optimizer.

    adam_beta2: Optional[float] = 0.999
        The beta2 hyperparameter for the AdamW optimizer.

    adam_epsilon: Optional[float] = 1e-8
        The epsilon hyperparameter for the AdamW optimizer.

    max_grad_norm: Optional[float] = 1.0
        Maximum gradient norm (for gradient clipping).

    lr_warmup_steps: Optional[int] = 0
        Number of warmup steps for learning rate scheduler at the beginning of training.

    lr_decay_steps: Optional[int] = 0
        Number of decay steps for learning rate scheduler at the end of training.

    eval_steps: Optional[int] = None
        Run an evaluation every X steps.

    group_by_length: Optional[bool] = True
        Whether or not to group together samples of roughly the same length in the training dataset (to minimize padding applied and be more efficient).

    gradient_accumulation_steps: Optional[int] = 1
        Number of updates steps to accumulate before performing a backward/update pass.

    gradient_checkpointing: Optional[bool] = True
        If True, use gradient checkpointing to save memory at the expense of slower backward pass.

    pad_to_multiple_of: Optional[int] = None
        If set will pad the sequence to a multiple of the provided value.
        This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability >= 7.5 (Volta).

    per_device_train_batch_size: Optional[int] = 8
        The batch size per GPU/TPU core/CPU for training.

    per_device_eval_batch_size: Optional[int] = 8
        The batch size per GPU/TPU core/CPU for evaluation.

    fp16: Optional[bool] = False
        Whether to use 16-bit (mixed) precision training instead of 32-bit training.

    use_8bit_optimizer: Optional[bool] = False
        Whether to use 8-bit optimizer.

    logging_steps: Optional[int] = 100
        Log every X updates steps.

    num_train_epochs: Optional[float] = 3.0
        Total number of training epochs to perform (if not an integer, will perform the decimal part percents of
        the last epoch before stopping training).

    max_steps: Optional[int] = 0
        If set to a positive number, the total number of training steps to perform. Overrides num_train_epochs.

    report_to: Optional[list[str]] = ["none"]
        The list of integrations to report the results and logs to. Supported platforms are "azure_ml",
        "comet_ml", "mlflow", "tensorboard" and "wandb". Use "all" to report to
        all integrations installed, "none" for no integrations.

    save_steps: Optional[int] = 500
        Number of updates steps before two checkpoint saves.

    save_total_limit: Optional[int] = None
        Limit the total amount of checkpoints. Deletes the older checkpoints in the output_dir. Default is unlimited checkpoints

    metric_for_best_model: Optional[str] = "cer"
        The metric to use to compare two different models.

    _n_gpu: Optional[int] = 1
        Numbers of gpus to use for training.

    seed: Optional[int] = None
        Random seed that will be set at the beginning of training. To ensure reproducibility across runs.

    training_step_callbacks: list[Callable[transformers.Trainer, float]] = []
        A list of callbacks that will be called at the end of each training step.
        The trainer, model and the current loss will be passed to those functions: f(trainer: transformers.Trainer, loss: float)

    batch_creation_callbacks: list[Callable[ list[dict[str, list[int]]] ]] = []
        A list of callbacks that will be called at the end of each batch creation.
        The batch will be passed to those functions: f(batch: list[dict[str, list[int]]])

    evaluation_callbacks: list[Callable[dict, list, list]] = []
        A list of callbacks that will be called at the end of each evaluation step.
        The metrics (WER, CER), references and predictions will be passed to those functions: f(metrics: dict, references: list, predictions: list)

    metrics_batch_size: Optional[int] = None
        Batch size to use for evaluation. When this value is specified, the evaluation function will chunk the data into
        batches of the specified size and compute the metrics on each batch.
        After all batches are computed, the function will compute the average metrics over all batches.
        (You will probably need to define this if you have memory issues).

    show_dataset_stats: Optional[bool] = False
        Whether to show dataset stats

    early_stopping_patience: Optional[int] = None
        Use with metric_for_best_model to stop training when the specified metric worsens for early_stopping_patience evaluation calls.

    load_best_model_at_end: Optional[bool] = False
        Whether to load the best model at the end of training.

    """

    overwrite_output_dir: bool = field(default=False)
    ignore_pretrained_weights: bool = field(default=False)
    dataloader_num_workers: int = field(default=0)
    learning_rate: float = field(default=3e-4)
    min_learning_rate: float = field(default=0.0)
    weight_decay: float = field(default=0.0)
    adam_beta1: float = field(default=0.9)
    adam_beta2: float = field(default=0.999)
    adam_epsilon: float = field(default=1e-8)
    max_grad_norm: float = field(default=1.0)
    lr_warmup_steps: int = field(default=0)
    lr_decay_steps: int = field(default=0)
    eval_steps: int = field(default=None)
    group_by_length: bool = field(default=True)
    length_column_name: str = field(default="length")
    gradient_accumulation_steps: int = field(default=1)
    gradient_checkpointing: bool = field(default=True)
    pad_to_multiple_of: int = field(default=None)
    per_device_train_batch_size: int = field(default=8)
    per_device_eval_batch_size: int = field(default=8)
    fp16: bool = field(default=False)
    use_8bit_optimizer: bool = field(default=False)
    logging_steps: int = field(default=100)
    num_train_epochs: float = field(default=3.0)
    max_steps: int = field(default=0)
    report_to: list[str] = field(default_factory=lambda: ["none"])
    save_steps: int = field(default=500)
    save_total_limit: int = field(default=None)
    metric_for_best_model: str = field(default=None)
    _n_gpu: int = field(default=1)
    seed: int = field(default=42)
    training_step_callbacks: list[Callable] = field(default_factory=lambda: [])
    batch_creation_callbacks: list[Callable] = field(default_factory=lambda: [])
    evaluation_callbacks: list[Callable] = field(default_factory=lambda: [])
    metrics_batch_size: int = field(default=None)
    show_dataset_stats: bool = field(default=True)
    early_stopping_patience: int = field(default=None)
    load_best_model_at_end: bool = field(default=False)

class CTCDataCollatorWithPadding:
    """
    Data collator that will dynamically pad the inputs received.

    Parameters:
    ----------
        processor: Wav2Vec2Processor
            The processor used for proccessing the data.

        batch_creation_callbacks: Optional[list[dict[str, Union[list[int], torch.Tensor]]]] = None
            A list of callbacks that will be called at the end of each batch creation.

        pad_to_multiple_of: Optional[int] = None
            If set will pad the sequence to a multiple of the provided value.
            This is especially useful to enable the use of Tensor Cores on NVIDIA hardware with compute capability >= 7.5 (Volta).
    """

    def __init__(self, processor: Wav2Vec2Processor, batch_creation_callbacks: Optional[list[Callable]] = None,
                 pad_to_multiple_of: Optional[int] = None):
        self.processor = processor
        self.batch_creation_callbacks = batch_creation_callbacks
        self.pad_to_multiple_of = pad_to_multiple_of

    def __call__(self, features: list[dict[str, Union[list[int], torch.Tensor]]]) -> dict[str, torch.Tensor]:
        # split inputs and labels since they have to be of different lenghts and need
        # different padding methods

        if self.batch_creation_callbacks is not None:
            for callback in self.batch_creation_callbacks:
                callback(features)

        input_features = [{"input_values": feature["input_values"]} for feature in features]
        labels = [{"input_ids": feature["labels"]} for feature in features]

        batch = self.processor.pad(
            input_features=input_features,
            padding=True,
            pad_to_multiple_of=self.pad_to_multiple_of,
            return_tensors="pt",
        )

        labels_batch = self.processor.pad(
            labels=labels,
            padding=True,
            pad_to_multiple_of=self.pad_to_multiple_of,
            return_tensors="pt",
        )

        # replace padding with -100 to ignore loss correctly
        batch["labels"] = labels_batch["input_ids"].masked_fill(labels_batch.attention_mask.ne(1), -100)

        return batch

class CTCTrainer(Trainer):

    """
    Data collator that will dynamically pad the inputs received.

    Parameters:
    ----------
        min_learning_rate: Optional[float] = None
            The minimum learning rate to use.

        lr_warmup_steps: Optional[int] = None
            The number of steps to use for a warmup.

        lr_decay_steps: Optional[int] = None
            The number of steps to use for a decay.

        training_step_callbacks: list[Callable[transformers.Trainer, float]] = []
            A list of callbacks that will be called at the end of each training step.
            The trainer, model and the current loss will be passed to those functions: f(trainer: transformers.Trainer, loss: float)
    """

    def __init__(self, min_learning_rate=None, lr_warmup_steps=None, lr_decay_steps=None,
                 training_step_callbacks: Optional[list[Callable]] = None, use_8bit_optimizer: Optional[bool] = False, **kwargs):

        super().__init__(**kwargs)
        self.min_learning_rate = min_learning_rate
        self.lr_warmup_steps = lr_warmup_steps
        self.lr_decay_steps = lr_decay_steps
        self.training_step_callbacks = training_step_callbacks
        self.use_8bit_optimizer = use_8bit_optimizer

    def create_optimizer(self):

        decay_parameters = get_parameter_names(self.model, [torch.nn.LayerNorm])
        decay_parameters = [name for name in decay_parameters if "bias" not in name]
        optimizer_grouped_parameters = [
            {
                "params": [p for n, p in self.model.named_parameters() if n in decay_parameters],
                "weight_decay": self.args.weight_decay,
            },
            {
                "params": [p for n, p in self.model.named_parameters() if n not in decay_parameters],
                "weight_decay": 0.0,
            },
        ]

        optimizer_class = torch.optim.AdamW

        if self.use_8bit_optimizer:

            try:
                import bitsandbytes as bnb
            except ImportError:
                raise ImportError("To use 8bit optimizer please install the bitsandbytes from https://github.com/facebookresearch/bitsandbytes")

            optimizer_class = bnb.optim.AdamW

        self.optimizer = optimizer_class(
            params=optimizer_grouped_parameters,
            lr=self.args.learning_rate,
            betas=(self.args.adam_beta1, self.args.adam_beta2),
            eps=self.args.adam_epsilon,
        )

        return self.optimizer

    def create_scheduler(self, num_training_steps: int, optimizer: torch.optim.Optimizer = None):
        """
        Setup the scheduler. This scheduler supports a warmup phase where the learning rate linearly increases
        at the beginning of the training. And a decay phase where the learning rate decreases over time at the end of the training.

        Parameters:
        ----------
            num_training_steps (int): The number of total training steps to do.
        """

        min_lr_ratio = self.min_learning_rate / self.args.learning_rate

        def _lr_lambda(current_step, num_training_steps=num_training_steps, lr_decay_steps=self.lr_decay_steps,
                       lr_warmup_steps=self.lr_warmup_steps, min_lr_ratio=min_lr_ratio):

            if lr_warmup_steps is not None and current_step < lr_warmup_steps: # warmup phase
                return max(
                    min_lr_ratio, float(current_step) / float(max(1, lr_warmup_steps))
                )
            elif lr_decay_steps is not None and current_step >= num_training_steps - lr_decay_steps: # decay phase
                return max(
                    min_lr_ratio, float(num_training_steps - current_step) / float(max(1, lr_decay_steps))
                )
            else: # constant learning rate phase
                return 1

        self.lr_scheduler = torch.optim.lr_scheduler.LambdaLR(self.optimizer, _lr_lambda)

    def training_step(self, model: torch.nn.Module, inputs: dict[str, Union[torch.Tensor, Any]]) -> torch.Tensor:
        """
        Perform a training step on a batch of inputs.

        Parameters:
        ----------
            model (:obj:`nn.Module`):
                The model to train.
            inputs (:obj:`dict[str, Union[torch.Tensor, Any]]`):
                The inputs and targets of the model.

                The dictionary will be unpacked before being fed to the model. Most models expect the targets under the
                argument :obj:`labels`. Check your model's documentation for all accepted arguments.

        Returns:
        ----------
            :obj:`torch.Tensor`: The tensor with training loss on this batch.
        """

        model.train()
        inputs = self._prepare_inputs(inputs)

        if (hasattr(self, 'use_amp') and self.use_amp) or (hasattr(self, 'use_cuda_amp') and self.use_cuda_amp):
            with torch.cuda.amp.autocast():
                loss = self.compute_loss(model, inputs)
        else:
            loss = self.compute_loss(model, inputs)

        if self.args.n_gpu > 1:
            if model.module.config.ctc_loss_reduction == "mean":
                loss = loss.mean()
            elif model.module.config.ctc_loss_reduction == "sum":
                loss = loss.sum() / (inputs["labels"] >= 0).sum()
            else:
                raise ValueError(f"{model.config.ctc_loss_reduction} is not valid. Choose one of ['mean', 'sum']")

        if self.args.gradient_accumulation_steps > 1:
            loss = loss / self.args.gradient_accumulation_steps

        if (hasattr(self, 'use_amp') and self.use_amp) or (hasattr(self, 'use_cuda_amp') and self.use_cuda_amp):
            self.scaler.scale(loss).backward()
        elif self.deepspeed:
            self.deepspeed.backward(loss)
        else:
            loss.backward()

        if self.training_step_callbacks is not None:
            for callback in self.training_step_callbacks:
                callback(self, float(loss.detach()))

        return loss.detach()

def finetune_ctc(model_name_or_path: str, output_dir: str, processor: Wav2Vec2Processor, train_dataset: Dataset,
                 eval_dataset: Optional[Dataset] = None, device: Optional[str] = "cpu",
                 training_args: Optional[TrainingArguments] = None, model_args: Optional[ModelArguments] = None):
    """
    Finetunes the CTC model.

    Parameters:
    ----------

    model_name_or_path : str
        The path to the model or the model identifier from huggingface.co/models.

    output_dir : str
        The output directory where the model checkpoints will be written.

    processor : transformers.Wav2Vec2Processor
        The audio processor object.

    train_dataset : transformers.Dataset
        The training dataset.

    eval_dataset : Optional[transformers.Dataset] = None
        The evaluation dataset.

    device : str = "cpu"
        The device to run the model training on.

    training_args : Optional[TrainingArguments] = None
        The training arguments.

    model_args : Optional[ModelArguments] = None
        The model arguments.

    """

    if training_args is None:
        training_args = TrainingArguments()

    if model_args is None:
        model_args = ModelArguments()

    hf_arg_parser = HfArgumentParser((HFTrainingArguments))
    training_args_dict = asdict(training_args)
    training_args_dict["output_dir"] = output_dir
    training_args_dict["device"] = device
    training_args_dict["no_cuda"] = device == "cpu"
    training_args_dict["do_train"] = True

    if eval_dataset is not None:
        training_args_dict["do_eval"] = True
        training_args_dict["evaluation_strategy"] = "steps"
        training_args_dict["greater_is_better"] = False

    hftraining_args = hf_arg_parser.parse_dict(training_args_dict, allow_extra_keys=True)[0]

    # Set seed before initializing model
    if hftraining_args.seed is not None:
        set_seed(hftraining_args.seed)

    # Detecting last checkpoint.
    last_checkpoint = None
    if os.path.isdir(hftraining_args.output_dir) and not hftraining_args.overwrite_output_dir:
        last_checkpoint = get_last_checkpoint(hftraining_args.output_dir)
        if last_checkpoint is None and len(os.listdir(hftraining_args.output_dir)) > 0:
            raise ValueError(
                f"Output directory ({hftraining_args.output_dir}) already exists and is not empty. "
                "Clear the folder or set TrainingArguments.overwrite_output_dir to True to overcome that."
            )
        elif last_checkpoint is not None:
            logger.info(
                f"Checkpoint detected, resuming training at {last_checkpoint}. To avoid this behavior, change "
                "the model's output directory or set TrainingArguments.overwrite_output_dir to False to train from scratch."
            )

    # Setup logging
    logging.basicConfig(
        format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
        datefmt="%m/%d/%Y %H:%M:%S",
        handlers=[logging.StreamHandler(sys.stdout)],
    )
    logger.setLevel(logging.INFO if is_main_process(hftraining_args.local_rank) else logging.WARN)

    if training_args.show_dataset_stats:
        logger.info("Getting dataset stats...")

        total_train_size_in_seconds = 0
        for sample in train_dataset:
            total_train_size_in_seconds += len(sample["input_values"]) / processor.feature_extractor.sampling_rate
        logger.info(f"Training dataset size: {len(train_dataset)} samples, {total_train_size_in_seconds/3600} hours")

        if eval_dataset is not None:
            total_eval_size_in_seconds = 0
            for sample in eval_dataset:
                total_eval_size_in_seconds += len(sample["input_values"]) / processor.feature_extractor.sampling_rate
            logger.info(f"Evaluation dataset size: {len(eval_dataset)} samples, {total_eval_size_in_seconds/3600} hours")

    # Setup model

    config = AutoConfig.from_pretrained(model_name_or_path)

    config.update(
        {
            "feat_proj_dropout": model_args.feat_proj_dropout,
            "attention_dropout": model_args.attention_dropout,
            "hidden_dropout": model_args.hidden_dropout,
            "final_dropout": model_args.final_dropout,
            "mask_time_prob": model_args.mask_time_prob,
            "mask_time_length": model_args.mask_time_length,
            "mask_feature_prob": model_args.mask_feature_prob,
            "mask_feature_length": model_args.mask_feature_length,
            "layerdrop": model_args.layerdrop,
            "activation_dropout": model_args.activation_dropout,
            "ctc_loss_reduction": model_args.ctc_loss_reduction,
            "pad_token_id": processor.tokenizer.pad_token_id,
            "vocab_size": len(processor.tokenizer),
        }
    )

    if training_args.ignore_pretrained_weights:
        model = AutoModelForCTC.from_config(config=config)
    else:
        model = AutoModelForCTC.from_pretrained(model_name_or_path, config=config)

    if hftraining_args.gradient_checkpointing:
        model.gradient_checkpointing_enable()

    if model_args.freeze_feature_extractor:
        model.freeze_feature_extractor()

    # Defining metrics
    def _compute_metrics(pred, processor=processor, metrics_batch_size=training_args.metrics_batch_size,
                         evaluation_callbacks=training_args.evaluation_callbacks):

        pred_logits = pred.predictions
        padding_mask = pred_logits[:,:,0] == -100
        pred_ids = np.argmax(pred_logits, axis=-1)
        pred_ids[padding_mask] = processor.tokenizer.pad_token_id

        pred.label_ids[pred.label_ids == -100] = processor.tokenizer.pad_token_id

        pred_str = processor.batch_decode(pred_ids)

        # we do not want to group tokens when computing the metrics
        label_str = processor.batch_decode(pred.label_ids, group_tokens=False)

        metrics = {
            "wer": wer(predictions=pred_str, references=label_str, chunk_size=metrics_batch_size),
            "cer": cer(predictions=pred_str, references=label_str, chunk_size=metrics_batch_size),
        }

        if evaluation_callbacks is not None:
            for callback in evaluation_callbacks:
                callback(metrics, label_str, pred_str)

        return metrics

    # Setup data collator

    data_collator = CTCDataCollatorWithPadding(
        processor=processor,
        batch_creation_callbacks=training_args.batch_creation_callbacks,
        pad_to_multiple_of=training_args.pad_to_multiple_of,
    )

    # Initialize Trainer

    logger.info("Building trainer...")

    callbacks = []
    if training_args.early_stopping_patience is not None and training_args.early_stopping_patience > 0:
        callbacks.append(
            EarlyStoppingCallback(early_stopping_patience=training_args.early_stopping_patience)
        )
        hftraining_args.load_best_model_at_end = True

    trainer = CTCTrainer(
        args=hftraining_args,
        model = model,
        data_collator=data_collator,
        train_dataset=train_dataset,
        eval_dataset=eval_dataset,
        compute_metrics=_compute_metrics if eval_dataset is not None else None,
        min_learning_rate=training_args.min_learning_rate,
        lr_warmup_steps=training_args.lr_warmup_steps,
        lr_decay_steps=training_args.lr_decay_steps,
        training_step_callbacks=training_args.training_step_callbacks,
        use_8bit_optimizer=training_args.use_8bit_optimizer,
        callbacks = callbacks
    )

    # Training

    logger.info("Starting training...")

    if last_checkpoint is not None:
        checkpoint = last_checkpoint
    elif os.path.isdir(model_name_or_path):
        checkpoint = model_name_or_path
    else:
        checkpoint = None

    # Saving processor config to output directory

    if is_main_process(hftraining_args.local_rank):
        processor.save_pretrained(hftraining_args.output_dir)

    train_result = trainer.train(resume_from_checkpoint=checkpoint)
    trainer.save_model()

    metrics = train_result.metrics
    metrics["train_samples"] = len(train_dataset)

    trainer.log_metrics("train", metrics)
    trainer.save_metrics("train", metrics)
    trainer.save_state()


import datetime
import json

# The data sent for each action (turning on, fan speed, heating temp etc) all use the same format with the only change being the data in the data key.
# We create a Parent class with this template and all the child classes have their own implementation of it with the added data + the value that will be passed in.

class Commands:
    def __init__(self):
        self.publish_template = {
            'data' : {
            },
            'mode-reason' : 'LAPP',
            'time' : '',
            'msg' : 'STATE-SET'
        }
    def get_time(self):
        date = datetime.datetime.utcnow()
        return date.strftime('%y-%m-%d') + 'T' + date.strftime('%H:%M:%S') + 'Z'

    def publish(self):
        print(self.publish_template)
        self.publish_template['time'] = self.get_time()
        return json.dumps(self.publish_template, indent=2)

    #On, Off or AUTO.
    #Data fmod.
    #FAN = ON | OFF = OFF | AUTO = AUTO.
    # Auto mode automatically maintains air quality and temperature.
    def set_state(self,value):
        publish_data = self.publish_template.copy()
        publish_data['data']['fmod'] = value.upper()
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    #Heating or cooling.
    #Data hmod.
    #HEAT = Heating | OFF = Cooling.
    def set_mode(self, value):
        publish_data = self.publish_template.copy()
        publish_data['data']['hmod'] = value.upper()
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    #Fan speed.
    #Data fnsp.
    #4 digits 0 to 10. Must have 4 digits. We can use rjust(4,'0') to pad the data with zero's. 1 = 0001 | 10 = 0010.
    def set_speed(self, value):
        publish_data = self.publish_template.copy()
        publish_data['data']['fnsp'] = value.rjust(4,'0')
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    #Heating Temperature.
    #Data hmax.
    #The fan itself displays as Celsius. However, we need to send the data as Kelvin. The formula  for that is (c + 273.15). We also need to round it.
    #This is because Kelvin scale does not use the degree symbol. Kelvin is an absolute scale based on absolute 0 while Celsius is based on the properties of water. It then is multiplied by 10.
    def set_temp(self, value):
        publish_data = self.publish_template.copy()
        publish_data['data']['hmax'] = round((int(value) + 273.15) * 10)
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    #Timer
    #Data sltm
    #4 digits required. Data is sent in minutes.
    def set_timer(self, value):
        publish_data = self.publish_template.copy()
        publish_data['data']['sltm'] = value.rjust(4,'0')
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    #Oscillation.
    #Data oson.
    #ON = ON | OFF = OFF.
    def set_osci(self, value):
        publish_data = self.publish_template.copy()
        publish_data['data']['oson'] = value
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    #Night Mode
    #Data nmod
    #ON = ON | OFF = OFF.
    def set_nmod(self, value):
        publish_data = self.publish_template.copy()
        publish_data['data']['nmod'] = value
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    #Diffused Mode
    #Data ffoc
    # OFF = diffused mode on | ON = diffused mode off
    def set_diffused(self, value):
        publish_data = self.publish_template.copy()
        publish_data['data']['ffoc'] = value
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    def get_current_state(self):
        publish_data = {
            'mode-reason' : 'LAPP',
            'time' : '',
            'msg' : 'REQUEST-CURRENT-STATE'
        }
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    def get_env_state(self):
        publish_data = {
            'mode-reason' : 'LAPP',
            'time' : '',
            'msg' : 'REQUEST-ENVIRONMENTAL-CURRENT-SENSOR-DATA'
        }
        publish_data['time'] = self.get_time()
        return json.dumps(publish_data,indent=2)

    def get_help(self, value):
        command_info = {
            'state' : 'Turns the fan ON, OFF or sets it to AUTO mode. Values: FAN = ON | OFF = OFF | AUTO = AUTO.',
            'mode' : 'Turns on Heating or Cooling. Values: HEAT = Heating | OFF = Cooling.',
            'speed' : 'Changes the Fan Speed. Values: 0 to 10.',
            'temp' : 'Sets the Heating temperature. Value in Celsius.',
            'timer' : 'Sets the Timer to turn off. Value in minutes.',
            'osci' : 'Turns the Oscillation ON or OFF. Values ON | OFF.',
            'night' : 'Turns Night Mode ON or OFF. Values ON | OFF.',
            'diffused' : 'Turns Diffused Mode ON or OFF. Values:  OFF = diffused mode on | ON = difused mode off.'
        }
        if value in command_info.keys():
            print(command_info[value])
        else:
            print("Unknown command")


# -*- coding: utf-8 -*-

import random
import pygame
from math import fsum

import classes.board
import classes.drw.ratio_hq
import classes.game_driver as gd
import classes.level_controller as lc
import classes.extras as ex

class Board(gd.BoardGame):
    def __init__(self, mainloop, speaker, config, screen_w, screen_h):
        self.level = lc.Level(self, mainloop, 2, 2)
        gd.BoardGame.__init__(self, mainloop, speaker, config, screen_w, screen_h, 18, 10)

    def create_game_objects(self, level=1):
        self.max_size = 99
        self.board.draw_grid = False

        if self.mainloop.scheme is not None:
            white = self.mainloop.scheme.u_color
            h1 = 170
            h2 = 40
            h3 = 0
            color1 = ex.hsv_to_rgb(h1, 255, 255)
            color2 = ex.hsv_to_rgb(h2, 157, 255)
            color3 = ex.hsv_to_rgb(h2, 57, 255)
            self.bd_color1 = ex.hsv_to_rgb(h1, 127, 155)
            self.bd_color2 = ex.hsv_to_rgb(h2, 127, 155)
            self.bd_color3 = ex.hsv_to_rgb(h3, 57, 155)
        else:
            white = (255, 255, 255)
            step = 255 //3
            h1 = random.randrange(0, 255)
            h2 = (h1 + step) % 255
            h3 = (h1 + step * 2) % 255
            color1 = ex.hsv_to_rgb(h1, 127, 255)
            color2 = ex.hsv_to_rgb(h2, 127, 255)
            color3 = ex.hsv_to_rgb(h3, 127, 255)
            self.bd_color1 = ex.hsv_to_rgb(h1, 187, 200)
            self.bd_color2 = ex.hsv_to_rgb(h2, 187, 200)
            self.bd_color3 = ex.hsv_to_rgb(h3, 187, 200)

        transp = (0, 0, 0, 0)

        self.disabled_font_color = (200, 200, 200)

        data = [18, 10]
        self.data = data

        self.vis_buttons = [0, 0, 0, 0, 1, 1, 1, 0, 0]
        self.mainloop.info.hide_buttonsa(self.vis_buttons)

        self.layout.update_layout(data[0], data[1])
        scale = self.layout.scale
        self.board.level_start(data[0], data[1], scale)
        self.board.board_bg.update_me = True
        self.board.board_bg.line_color = (20, 20, 20)

        self.max_total = 20
        num1 = num2 = num3 = 11
        while num1 + num2 + num3 > self.max_total:
            num1 = random.randint(1, 3)
            num2 = random.randint(1, 3)
            num3 = random.randint(1, 3)
        self.numbers = [num1, num2, num3]

        self.board.add_unit(0, 0, data[1], data[1], classes.board.Label, "", white, "", 0)
        self.fraction_canvas = self.board.units[-1]
        self.fraction = classes.drw.ratio_hq.Ratio(1, self.board.scale * data[1], color1, color2, color3, self.bd_color1, self.bd_color2, self.bd_color3, self.numbers)
        self.fraction_canvas.painting = self.fraction.get_canvas().copy()

        self.board.add_unit(data[1], 2, 2, 2, classes.board.ImgCenteredShip, "", transp,
                            img_src='nav_u_mts.png', alpha=True)
        self.board.ships[-1].set_tint_color(self.bd_color1)

        self.board.add_unit(data[1], 4, 2, 2, classes.board.Label, str(num1), white, "", 31)
        self.nm1 = self.board.units[-1]
        self.board.units[-1].font_color = self.bd_color1

        self.board.add_unit(data[1], 6, 2, 2, classes.board.ImgCenteredShip, "", transp,
                            img_src='nav_d_mts.png', alpha=True)
        self.board.ships[-1].set_tint_color(self.bd_color1)

        self.board.add_unit(data[1] + 2, 4, 1, 2, classes.board.Label, ":", white, "", 31)

        self.board.add_unit(data[1] + 3, 2, 2, 2, classes.board.ImgCenteredShip, "", transp,
                            img_src='nav_u_mts.png', alpha=True)
        self.board.ships[-1].set_tint_color(self.bd_color2)

        self.board.add_unit(data[1] + 3, 4, 2, 2, classes.board.Label, str(num2), white, "", 31)
        self.nm2 = self.board.units[-1]
        self.board.units[-1].font_color = self.bd_color2

        self.board.add_unit(data[1] + 3, 6, 2, 2, classes.board.ImgCenteredShip, "", transp,
                            img_src='nav_d_mts.png', alpha=True)
        self.board.ships[-1].set_tint_color(self.bd_color2)

        self.board.add_unit(data[1] + 5, 4, 1, 2, classes.board.Label, ":", white, "", 31)

        self.board.add_unit(data[1] + 6, 2, 2, 2, classes.board.ImgCenteredShip, "", transp,
                            img_src='nav_u_mts.png', alpha=True)
        self.board.ships[-1].set_tint_color(self.bd_color3)

        self.board.add_unit(data[1] + 6, 4, 2, 2, classes.board.Label, str(num3), white, "", 31)
        self.nm3 = self.board.units[-1]
        self.board.units[-1].font_color = self.bd_color3

        self.board.add_unit(data[1] + 6, 6, 2, 2, classes.board.ImgCenteredShip, "", transp,
                            img_src='nav_d_mts.png', alpha=True)
        self.board.ships[-1].set_tint_color(self.bd_color3)

        for each in self.board.ships:
            each.readable = False
            each.immobilize()

    def handle(self, event):
        gd.BoardGame.handle(self, event)
        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            active = self.board.active_ship
            if active == 0:
                self.change_fract_btn(1, 0, 0)
            elif active == 1:
                self.change_fract_btn(-1, 0, 0)
            elif active == 2:
                self.change_fract_btn(0, 1, 0)
            elif active == 3:
                self.change_fract_btn(0, -1, 0)
            elif active == 4:
                self.change_fract_btn(0, 0, 1)
            elif active == 5:
                self.change_fract_btn(0, 0, -1)

    def change_fract_btn(self, n1, n2, n3):
        if n1 == 1:
            if fsum(self.numbers) < self.max_total:
                self.numbers[0] += 1
                if self.numbers[0] > 1:
                    self.board.ships[1].change_image("nav_d_mts.png")
        if n1 == -1:
            if self.numbers[0] > 1:
                self.numbers[0] -= 1
                if self.numbers[0] == 1:
                    self.board.ships[1].change_image("nav_d_mtsd.png")
        if n2 == 1:
            if fsum(self.numbers) < self.max_total:
                self.numbers[1] += 1
                if self.numbers[1] > 1:
                    self.board.ships[3].change_image("nav_d_mts.png")
        if n2 == -1:
            if self.numbers[1] > 1:
                self.numbers[1] -= 1
                if self.numbers[1] == 1:
                    self.board.ships[3].change_image("nav_d_mtsd.png")
        if n3 == 1:
            if fsum(self.numbers) < self.max_total:
                self.numbers[2] += 1
                if self.numbers[2] > 1:
                    self.board.ships[5].change_image("nav_d_mts.png")
        if n3 == -1:
            if self.numbers[2] > 1:
                self.numbers[2] -= 1
                if self.numbers[2] == 1:
                    self.board.ships[5].change_image("nav_d_mtsd.png")
        if fsum(self.numbers) == self.max_total:
            self.board.ships[0].change_image("nav_u_mtsd.png")
            self.board.ships[2].change_image("nav_u_mtsd.png")
            self.board.ships[4].change_image("nav_u_mtsd.png")
        else:
            self.board.ships[0].change_image("nav_u_mts.png")
            self.board.ships[2].change_image("nav_u_mts.png")
            self.board.ships[4].change_image("nav_u_mts.png")
        for each in self.board.ships:
            each.update_me = True

        self.nm1.set_value(str(self.numbers[0]))
        self.nm2.set_value(str(self.numbers[1]))
        self.nm3.set_value(str(self.numbers[2]))
        self.fraction.update_values(self.numbers)
        self.fraction_canvas.painting = self.fraction.get_canvas().copy()
        self.fraction_canvas.update_me = True
        self.mainloop.redraw_needed[0] = True

    def update(self, game):
        game.fill((255, 255, 255))
        gd.BoardGame.update(self, game)

    def check_result(self):
        pass


from django.db import models
from DjangoUeditor.models import UEditorField

# Create your models here.
class Tag(models.Model):    # 分类模型类
    name = models.CharField(max_length=50,
                            verbose_name='分类名称',
                            unique=True)
    add_time = models.DateTimeField(auto_now_add=True,
                                    verbose_name='添加时间')

    def __str__(self):  # 避免admin后台显示object
        return self.name

    class Meta:
        db_table = 't_tag'
        ordering = ['-add_time']
        verbose_name = '文章分类'
        verbose_name_plural = verbose_name  # 去掉复数s

# class Chapter(models.Model):
#     chapter = models.CharField(max_length=)

class Art(models.Model):    # 文章模型类
    title = models.CharField(max_length=100,
                             verbose_name='标题')
    author = models.CharField(max_length=50,
                              verbose_name='作者')
    state = models.IntegerField(choices=((0, '连载中'), (1, '已完本')),
                                verbose_name='状态', default=0)
    summary = models.CharField(max_length=500,
                               verbose_name='简介')
    # content 可以定义字段,也可以用文章章节类
    # content = models.TextField(verbose_name='正文')

    content = UEditorField(verbose_name='内容',
                           width=640, height=480,
                           imagePath='art/u_images/',
                           filePath='art/u_files/',
                           toolbars='full',
                           blank=True,
                           null=True)
    publish_date = models.DateTimeField(auto_now_add=True,
                                        verbose_name='发布时间')
    last_refresh_date = models.DateTimeField(auto_now_add=True,
                                             verbose_name='更新时间')
    tag = models.ForeignKey(Tag, verbose_name='分类',
                            on_delete=models.SET_NULL,
                            null=True)
    cover = models.ImageField(verbose_name='封面',
                              upload_to='art/images',
                              null=True,
                              blank=True)  # 相对于MEDIA_ROOT

    @property
    def stateName(self):
        return '已完本' if self.state else '连载中'

    def __str__(self):
        return self.title

    class Meta:
        db_table = 't_art'
        verbose_name = '文章'
        verbose_name_plural = verbose_name