#!/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 defined by user. Script creates a data log "EVlog.txt" of any charging instructions received and changes in SOC over time.


# 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


# 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

def log(SOC, ChargeValue, myString):
    logfile = open("EVlog.txt","a+")
    logfile.write(str(datetime.datetime.now()) + ", " + str(SOC)+ ", " + str(ChargeValue) + ", " + myString + "\n")
    logfile.close()


# In[ ]:


#Assign Global variables
print('assigning global variables...')
#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]

#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))
global NetworkID
NetworkID = myIP+":"+str(myPort)

#Determine EV parameters
Name = "Dream EV A"
Capacity = 80
global MaxChargingRateAbsolute
MaxChargingRateAbsolute = 50
SOC = 70 #Vehicle battery pack SOC
MODE = 1 #Vehicle charging mode
NEWTARGET = True #Any changes in user preferences?
TARGETSOC = 50 #Target SOC stated by user
TARGETTIME = "0000-00-00 00:00:00" #Target time stated by user
LOCATION = ("5056." + str(randint(1000,9999)) + " N, 123." + str(randint(1000,9999)) + "W, " + ("%.2f" % uniform (50,150))) #Vehicle location
#What is the current max charging rate?
index=round(SOC)
global MaxChargingRate
MaxChargingRate=MaxChargingRateAbsolute*ChargeRateSOC[index]/100

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
        log(SOC, ChargeValue, myString)
        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[ ]:




