#!/usr/bin/env python
# coding: utf-8

# In[ ]:


#Hannes Krueger, 12.Feb.2019
#EV simulator algorithm mimicking EV communication and charging behaviour. EV model, user charging preferences and initial SOC randomly selected from fixed list.


# In[ ]:


print('preprocessing...')


# In[ ]:


#Import required libraries
print('importing libraries...')
from socket import gethostname, gethostbyname #Allows accessing machines network configurations
from flask import Flask, request #For creating rest api server
from flask_restful import Resource, Api
from sys import argv #For using command line arguments (port selection)
from random import uniform, randint, randrange #For (pseudo-)random number generation
import random
from time import time #Allows measuring time and delaying loops if needed
from sys import exit as killscript#Allows script termination at any point
import datetime #For using datetime objects


# In[ ]:


print('defining callable functions...')
def updateSOC(): #Update SOC value
    global SOC, lastSOCupdate, ChargeValue, NEWTARGET
    TimeCharged = time() - lastSOCupdate #Seconds since last call of update SOC
    oldCharge = Capacity*SOC/100
    if ChargeValue > 0:
        newCharge = oldCharge + (0.9*ChargeValue*TimeCharged/3600) #Assume 90% charging efficiency
        #Old charge in kWh + (Charging Value in kW * time in hours)
    else:
        newCharge = oldCharge + (ChargeValue*TimeCharged/(0.9*3600)) #Assume 90% discharging efficiency
    lastSOCupdate = time() #Time now since epoch in seconds
    SOC = newCharge*100/Capacity #NewSOC value
    
    #Only do if absolute max charging rate is above 20kW
    #(otherwise EV not suitable for fast charging and charging profile flat)
    if MaxChargingRateAbsolute > 20:
        #What is the current max charging rate?
        global MaxChargingRate
        oldMaxChargingRate=MaxChargingRate
        index=round(SOC)
        MaxChargingRate=MaxChargingRateAbsolute*ChargeRateSOC[index]/100
        if MaxChargingRate != oldMaxChargingRate: #Only do if max charging rate has changed
            print("Max charging rate now: " + str(MaxChargingRate))
            NEWTARGET = True #Aggregator needs to be informed of change in max charging rate
    if SOC > 100:
        print("Battery pack full!!!")
        SOC = 100
        ChargeValue = 0 #Shut down charging
        print("Charging stopped locally!!!")
        NEWTARGET = True #Aggregator needs to be informed of change in charging value
    if SOC < 0:
        print("Battery pack empty!!!")
        SOC = 0
        ChargeValue = 0 #Shut down charging
        print("Charging stopped locally!!!")
        NEWTARGET = True #Aggregator needs to be informed of change in charging value    
    return 0


# In[ ]:


#Assign Global variables
print('assigning global variables...')

#Network variables
hostname = gethostname()
myIP = gethostbyname(hostname)
myPort = argv[1] #Take first given command line argument as listening port (argument 0 is file name)
if myPort == "-f":
    myPort = 5000 #Default port
print("Network settings: " + hostname + ", " + myIP + ":" + str(myPort))

randomize = True

if argv[2] == "ideal": #Take second given command line argument as randomisation indicator
    print("Using ideal EV model!")
    randomize = False
if argv[2] == "random": #Take second given command line argument as randomisation indicator
    print("Using random number generator without seed!")
else: #Use seeded random number generators
    random.seed(myPort) #Creates same random vehicle for given port number
    print("Using network port as seed for random number generator!")
    
if randomize == True:
    #Vehicle variables 
    Models = ["Audi e-tron 55 quattro","BMW i3","BMW i3","Hyundai Ioniq","Jaguar i-Pace","Kia Soul","Nissan e-NV200","Nissan Leaf","Nissan Leaf","Renault Kangoo Z.E.","Renault ZOE Q90","Renault ZOE R90","Smart Fortwo Electric Drive","Tesla Model 3","Tesla Model S 100D","Tesla Model S 75D","Tesla Model X 100D","Tesla Model X 75D","Volkswagen e-Up","VW e-Golf"]
    Capacities = [95,33.2,42.2,30.5,90,33,40,30,40,33,41,41,17.6,75,100,75,100,75,18.7,35.8] #In kWh
    MaxChargingRates = [150,50,50,70,100,100,50,50,50,7.4,43,22,22,80,120,120,120,120,50,40] #In kW
    #Percentage of max charging rate at SOC equal to index (1% steps)
    global ChargeRateSOC
    ChargeRateSOC=[100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,100.00,98.02,96.08,94.18,92.31,90.48,88.69,86.94,85.21,83.53,81.87,80.25,78.66,77.11,75.58,74.08,72.61,71.18,69.77,68.39,67.03,65.70,64.40,63.13,61.88,60.65,59.45,58.27,57.12,55.99,54.88,53.79,52.73,51.69,50.66,49.66,48.68,47.71,46.77,45.84,44.93,44.04,43.17,42.32,41.48,40.66,39.85,39.06,38.29,37.53,36.79,36.06,35.35,34.65,33.96,33.29,32.63,31.98,31.35,30.73,30.12,29.52,28.94,28.37,27.80,27.25,26.71,26.18,25.67,25.00,22.41,19.83,17.24,14.66,12.07,9.48,6.90,4.31]

    #Pick random Vehicle model from list
    x = randint(0, len(Models)-1)
    Name = Models[x]
    Capacity = Capacities[x]
    global MaxChargingRateAbsolute
    MaxChargingRateAbsolute = MaxChargingRates[x]
    print("Vehicle: " + Name + " with " + str(Capacity) + " kWh capacity")

    #Assign random SOC, location and user preferences
    SOC = uniform(20, 100) #Vehicle battery pack SOC
    MODE = randint(1, 4) #Vehicle charging mode
    NEWTARGET = True #Any changes in user preferences?
    LOCATION = ("5056." + str(randint(1000,9999)) + " N, 123." + str(randint(1000,9999)) + "W, " + ("%.2f" % uniform (50,150))) #Vehicle location
        
    #Assign other parameters depending on mode
    if MODE == 1:
        TARGETSOC = 0 #Target SOC stated by user
        TARGETTIME = "0000-00-00 00:00:00" #Target time stated by user
    elif MODE == 2:
        TARGETSOC = randrange(20, 100, 5) #Target SOC stated by user
        TARGETTIME = "0000-00-00 00:00:00" #Target time stated by user
    elif MODE == 3:
        TARGETSOC = 100 #Target SOC stated by user
        TARGETTIME = "0000-00-00 00:00:00" #Target time stated by user
    elif MODE == 4:
        TARGETSOC = randrange(20, 100, 5) #Target SOC stated by user
        randTime = random.uniform(0.5, 48.0) #Random time until target time, between 30 minutes to 48 hours
        TARGETTIME = datetime.datetime.now() + datetime.timedelta(hours = randTime) #Target time stated by user
        #Round Target time up to next 10 minutes
        TARGETTIME = str(TARGETTIME + (datetime.datetime.min - TARGETTIME) % datetime.timedelta(minutes=10))
        
    #What is the current max charging rate?
    index=round(SOC)
    global MaxChargingRate
    MaxChargingRate=MaxChargingRateAbsolute*ChargeRateSOC[index]/100
    
else: #Define ideal EV model here!
    Name = "Ideal"
    Capacity = 80
    SOC = 50
    MODE = 1
    NEWTARGET = True
    TARGETSOC = 0
    TARGETTIME = "0000-00-00 00:00:00"
    LOCATION = ("5056." + str(randint(1000,9999)) + " N, 123." + str(randint(1000,9999)) + "W, " + ("%.2f" % uniform (50,150))) #Vehicle location

print("   SOC = " + str("%.2f" % SOC))
print("   Mode = " + str(MODE))
print("   TargetSOC = " + str(TARGETSOC))
print("   TargetTime = " + TARGETTIME)
print("   Location = " + LOCATION)

global lastSOCupdate, ChargeValue
lastSOCupdate = time()
ChargeValue = 0.0 #Initialise without any charging


# In[ ]:


app = Flask(__name__) #Initiallise "flask" application (used for restcall support)
api = Api(app)

class INFO_SHORT(Resource): #Respond with true if user preferences have changed and with SOC
    def get(self):
        updateSOC() #Get updated SOC value
        myString = str(NEWTARGET) + ';' + str("%.2f" % SOC)
        return myString
    
class INFO_FULL(Resource): #Respond with full info available
    #All Info, values ONLY, for aggregator use [ManageVehicles.py, Full_Info_Rest_Call()]
    #Aggregator assumes that:
    #Values seperated by ';'
    #Values in Order: Values in Order: Name; Capacity; MaxChargingRate; SOC; ChargeVAL; Mode; Target SOC; Target Date/Time ('YYYY-MM-DD HH:MM:SS'); Location
    #Values in the appropriate format
    def get(self):
        global NEWTARGET
        updateSOC() #Get updated SOC value
        myString = Name + ";" + str(Capacity) + ";" + str(MaxChargingRate) + ";" + str("%.2f" % SOC) + ";" + str("%.2f" % ChargeValue) + ";" + str(MODE) + ";" + str(TARGETSOC) + ";" + TARGETTIME + ";" + LOCATION
        NEWTARGET = False #Aggregator now has the most up-to-date targets
        return myString
    
class CHARGE(Resource): #Recieve charging instruction
    def get(self, charge, chargeVAL):
        global ChargeValue, NEWTARGET 
        updateSOC() #Get updated SOC value (before charging instruction changes)
        if charge == "1":        
            ChargeValue = float(chargeVAL)
            myString = 'charging activated, level set to ' + str(chargeVAL)
        else:
            myString = 'charging deactivated'
            ChargeValue = 0.0
        NEWTARGET = True #Aggregator needs to update info
        return myString
    
class KILL(Resource): #Recieve instruction to terminate simulation
    def get(self):
        myString = 'Terminating Simulator'
        killscript("Process terminated by REST request")

api.add_resource(INFO_SHORT, '/info/short') #Assign classes to REST call extensions
api.add_resource(INFO_FULL, '/info/full')
api.add_resource(CHARGE, '/charge/<charge>/<chargeVAL>')
api.add_resource(KILL, '/kill')


# In[ ]:


print('preprocessing complete, starting car simulation...')


# In[ ]:


#Start listening to specified port for rest calls
if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=myPort) #Run rest api server, listening to public IP address
    #NEVER run in debug mode (debug=True) when using the public IP!!!
    #Any machine on the network could run any python code on this machine!!!
print("Simulation stopped")


# In[ ]:




