From: amiraj Date: Fri, 28 Jun 2019 23:06:02 +0000 (-0700) Subject: "First commit!" X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=fbcbbcd3abd6dafc027d5d23cab920301833c612;p=smartthings-infrastructure.git "First commit!" --- diff --git a/ContactSensor/contacting.groovy b/ContactSensor/contacting.groovy new file mode 100644 index 0000000..e6d8848 --- /dev/null +++ b/ContactSensor/contacting.groovy @@ -0,0 +1,34 @@ +//Create a class for contact sensor +package ContactSensor + +public class contacting{ + List contacts + int count + + contacting(int count) { + this.count = count + if (count == 1) { + contacts = [new contacts(0, "contact0", "closed", "closed")] + } else if (count == 2) { + contacts = [new contacts(0, "contact0", "closed", "closed"), new contacts(1, "contact1", "closed", "closed")] + } else if (count == 3) { + contacts = [new contacts(0, "contact0", "closed", "closed"), new contacts(1, "contact1", "closed", "closed"), new contacts(2,"contact2", "closed", "closed")] + } + } + + def currentValue(String S) { + if (count == 1) { + contacts[0].currentValue(S) + } else { + contacts*.currentValue(S) + } + } + + def latestValue(String S) { + if (count == 1) { + contacts[0].latestValue(S) + } else { + contacts*.latestValue(S) + } + } +} diff --git a/ContactSensor/contacts.groovy b/ContactSensor/contacts.groovy new file mode 100644 index 0000000..09f64bf --- /dev/null +++ b/ContactSensor/contacts.groovy @@ -0,0 +1,29 @@ +//Create a class for contact sensor +package ContactSensor + +public class contacts { + private int id = 0 + private String displayName + private String currentContact + private String contactLatestValue + + contacts(int id, String displayName, String currentContact, String contactLatestValue) { + this.id = id + this.displayName = displayName + this.currentContact = currentContact + this.contactLatestValue = contactLatestValue + } + + def currentValue(String S) { + if (S == "contact") { + return currentContact + } + } + + def latestValue(String S) { + if (S == "contact") { + return contactLatestValue + } + } +} + diff --git a/Event/Event.groovy b/Event/Event.groovy new file mode 100644 index 0000000..fb738c7 --- /dev/null +++ b/Event/Event.groovy @@ -0,0 +1,20 @@ +//Create a class for Events +package Event + +public class Event { + private int deviceId + private String value + private String linkText + private String displayName + private String name + private String descriptionText + + Event() { + this.deviceId = 0 + this.linkText = "" + this.value = "" + this.displayName = "" + this.name = "" + this.descriptionText = "" + } +} diff --git a/Extractor/App.groovy b/Extractor/App.groovy new file mode 100644 index 0000000..0c52140 --- /dev/null +++ b/Extractor/App.groovy @@ -0,0 +1,124 @@ +/////////////////////////////////////////// +definition( + name: "Enhanced Auto Lock Door", + namespace: "Lock Auto Super Enhanced", + author: "Arnaud", + description: "Automatically locks a specific door after X minutes when closed and unlocks it when open after X seconds.", + category: "Safety & Security", + iconUrl: "http://www.gharexpert.com/mid/4142010105208.jpg", + iconX2Url: "http://www.gharexpert.com/mid/4142010105208.jpg" +) + +preferences{ + section("Select the door lock:") { + input "lock1", "capability.lock", required: true, multiple: true + } + section("Select the door contact sensor:") { + input "contact", "capability.contactSensor", required: true + } + section("Automatically lock the door when closed...") { + input "minutesLater", "number", title: "Delay (in minutes):", required: true + } + section("Automatically unlock the door when open...") { + input "secondsLater", "number", title: "Delay (in seconds):", required: true + } + section( "Notifications" ) { + input("recipients", "contact", title: "Send notifications to", required: false) { + input "phoneNumber", "phone", title: "Warn with text message (optional)", description: "Phone Number", required: false + } + } +} + +def installed(){ + initialize() +} + +def updated(){ + unsubscribe() + unschedule() + initialize() +} + +def initialize(){ + log.debug "Settings: ${settings}" + subscribe(lock1, "lock", doorHandler, [filterEvents: false]) + subscribe(lock1, "unlock", doorHandler, [filterEvents: false]) + subscribe(contact, "contact.open", doorHandler) + subscribe(contact, "contact.closed", doorHandler) +} + +def lockDoor(){ + log.debug "Locking the door." + lock1.lock() + if(location.contactBookEnabled) { + if ( recipients ) { + log.debug ( "Sending Push Notification..." ) + sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients) + } + } + if (phoneNumber) { + log.debug("Sending text message...") + sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!") + } +} + +def unlockDoor(){ + log.debug "Unlocking the door." + lock1.unlock() + if(location.contactBookEnabled) { + if ( recipients ) { + log.debug ( "Sending Push Notification..." ) + sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients) + } + } + if ( phoneNumber ) { + log.debug("Sending text message...") + sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!") + } +} + +def doorHandler(evt){ + log.debug evt.value + log.debug contact.latestValue("contact") + if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then... + //def delay = (secondsLater) // runIn uses seconds + log.debug "1" + runIn( secondsLater, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged. + } + else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then... + log.debug "2" + unschedule( unlockDoor ) // ...we don't need to unlock it later. + } + else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then... + log.debug "3" + unschedule( lockDoor ) // ...we don't need to lock it later. + } + else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then... + log.debug "4" + //def delay = (minutesLater * 60) // runIn uses seconds + runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock. + } + else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door... + log.debug "5" + unschedule( lockDoor ) // ...we don't need to lock it later. + } + else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door... + //def delay = (minutesLater * 60) // runIn uses seconds + log.debug "6" + runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock. + } + else { //Opening or Closing door when locked (in case you have a handle lock) + log.debug "Unlocking the door." + lock1.unlock() + if(location.contactBookEnabled) { + if ( recipients ) { + log.debug ( "Sending Push Notification..." ) + sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients) + } + } + if ( phoneNumber ) { + log.debug("Sending text message...") + sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!") + } + } +} diff --git a/Extractor/ExtractedObjects.groovy b/Extractor/ExtractedObjects.groovy new file mode 100644 index 0000000..7e7e4db --- /dev/null +++ b/Extractor/ExtractedObjects.groovy @@ -0,0 +1,24 @@ +//Global Object for class lock! +@Field def lock1 = new locking(1) +//Global Object for class contactSensor! +@Field def contact = new contacting(1) +//Global variable for number! +@Field def minutesLater = 1 +//Global variable for number! +@Field def secondsLater = 10 +//Global variable for recipients! +@Field def recipients = ['AJ'] +//Global variable for phone number! +@Field def phoneNumber = 9495379373 +//Global Object for functions in subscribe method! +@Field def installed = this.&installed +//Global Object for functions in subscribe method! +@Field def updated = this.&updated +//Global Object for functions in subscribe method! +@Field def initialize = this.&initialize +//Global Object for functions in subscribe method! +@Field def lockDoor = this.&lockDoor +//Global Object for functions in subscribe method! +@Field def unlockDoor = this.&unlockDoor +//Global Object for functions in subscribe method! +@Field def doorHandler = this.&doorHandler diff --git a/Extractor/ExtractorScript.py b/Extractor/ExtractorScript.py new file mode 100644 index 0000000..b9654b4 --- /dev/null +++ b/Extractor/ExtractorScript.py @@ -0,0 +1,219 @@ +readyToReturn = 0 +ToReturn = "" + +def GetToken(f): + global readyToReturn + global ToReturn + Skip = ["(", "\"", ":", ",", "{", "}", ")", '\n', '\t', ' ', "/"] + S = "" + if (readyToReturn): + readyToReturn = 0 + return ToReturn + ToReturn = "" + c = f.read(1) + while(True): + if (c in Skip): + if (S != ""): + if (c == "{" or c == "}"): + readyToReturn = 1 + ToReturn = c + return S + else: + return S + + else: + if (c == "{" or c == "}"): + return c + else: + c = f.read(1) + continue + S += c + c = f.read(1) + if not c: + return "EOF" + + + + + +F = open("Extractor/App.groovy", "r") +Out = open("Extractor/ExtractedObjects.groovy", "w+") +Temp = GetToken(F) + +Objects = [] +Functions = [] + + +while (Temp != "EOF"): + #Extract the global objects for input + if (Temp == "input"): + Object = "" + Type = "" + Temp = GetToken(F) #name or "name" + #input name: "name", type: "type",... + if (Temp == "name"): + Temp = GetToken(F) #"name" + Object = Temp + GetToken(F) #type + Temp = GetToken(F) #"type" + Type = Temp + #input "name", "type",... + else: + Object = Temp + Temp = GetToken(F) #"type" + Type = Temp + Temp = GetToken(F) + Title = "" + Required = "" + Multiple = "" + while (Temp != "input" and Temp != "}"): + if (Temp == "title"): + Temp = GetToken(F) + Title = Temp + elif (Temp == "required"): + Temp = GetToken(F) + Required = Temp + elif (Temp == "multiple"): + Temp = GetToken(F) + Multiple = Temp + Temp = GetToken(F) + if (Type == "capability.lock"): + if (Title != ""): + print(Object+", "+Title) + if (Multiple != "" and Multiple == "true"): + g = raw_input("Enter the number of locks to control: (1, 2, or 3)\n") + Out.write("//Global Object for class lock!\n") + Out.write("@Field def %s = new locking(" % Object) + Out.write("%s)\n" % g) + elif (Multiple == "" or Multiple == "false"): + Out.write("//Global Object for class lock!\n") + Out.write("@Field def %s = new locking(1)\n" % Object) + #elif (Type == "capability.alarm"): + + #elif (Type == "capability.battery"): + + #elif (Type == "capability.beacon"): + + #elif (Type == "capability.carbonMonoxideDetector"): + + #elif (Type == "capability.colorControl"): + + elif (Type == "capability.contactSensor"): + if (Title != ""): + print(Object+", "+Title) + if (Multiple != "" and Multiple == "true"): + g = raw_input("Enter the number of contactSensors to monitor: (1, 2, or 3)\n") + Out.write("//Global Object for class contactSensor!\n") + Out.write("@Field def %s = new contacting(" % Object) + Out.write("%s)\n" % g) + elif (Multiple == "" or Multiple == "false"): + Out.write("//Global Object for class contactSensor!\n") + Out.write("@Field def %s = new contacting(1)\n" % Object) + #elif (Type == "capability.doorControl"): + + #elif (Type == "capability.energyMeter"): + + #elif (Type == "capability.illuminanceMeasurement"): + + #elif (Type == "capability.accelerationSensor"): + + #elif (Type == "capability.motionSensor"): + + #elif (Type == "capability.musicPlayer"): + + #elif (Type == "capability.powerMeter"): + + #elif (Type == "capability.presenceSensor"): + + #elif (Type == "capability.relativeHumidityMeasurement"): + + #elif (Type == "capability.relaySwitch"): + + #elif (Type == "capability.sleepSensor"): + + #elif (Type == "capability.smokeDetector"): + + #elif (Type == "capability.stepSensor"): + + elif (Type == "capability.switch"): + if (Title != ""): + print(Object+", "+Title) + if (Multiple != "" and Multiple == "true"): + g = raw_input("Enter the number of switches to control: (1, 2, or 3)\n") + Out.write("//Global Object for class switch!\n") + Out.write("@Field def %s = new switching(" % Object) + Out.write("%s)\n" % g) + elif (Multiple == "" or Multiple == "false"): + Out.write("//Global Object for class switch!\n") + Out.write("@Field def %s = new switching(1)\n" % Object) + #elif (Type == "capability.switchLevel"): + + #elif (Type == "capability.temperatureMeasurement"): + + #elif (Type == "capability.thermostat"): + + #elif (Type == "capability.valve"): + + #elif (Type == "capability.waterSensor"): + + #elif (Type == "capability.touchSensor"): + + #elif (Type == "capability.imageCapture"): + + #elif (Type == "device.mobilePresence"): + + #elif (Type == "device.aeonKeyFob"): + + elif (Type == "mode"): + if (Title != ""): + print(Object+", "+Title) + g = raw_input("Enter the mode: ") + Out.write("//Global variable for mode!\n") + Out.write("@Field def %s = " % Object) + Out.write("\"%s\"\n" % g) + #elif (Type == "decimal"): + + #elif (Type == "text"): + + elif (Type == "number"): + if (Title != ""): + print(Object+", "+Title) + g = raw_input("Enter the number: ") + Out.write("//Global variable for number!\n") + Out.write("@Field def %s = " % Object) + Out.write("%s\n" % g) + #elif (Type == "time"): + + #elif (Type == "enum"): + + #elif (Type == "bool"): + + elif (Type == "phone"): + if (Title != ""): + print(Object+", "+Title) + g = raw_input("Enter the number to send notification to:\n") + Out.write("//Global variable for phone number!\n") + Out.write("@Field def %s = " % Object) + Out.write("%s\n" % g) + elif (Type == "contact"): + if (Title != ""): + print(Object+", "+Title) + g = raw_input("Enter the name of the recipients:\n") + Out.write("//Global variable for recipients!\n") + g = g.split() + Out.write("@Field def %s = " % Object) + Out.write("%s\n" % g) + #Extract the global object for functions + elif (Temp == "def"): + Temp = GetToken(F) + NameofFunc = Temp + if (GetToken(F) != "="): #We have a function to create object for + Out.write("//Global Object for functions in subscribe method!\n") + Out.write("@Field def %s = this.&" % NameofFunc) + Out.write("%s\n" % NameofFunc) + if (Temp != "input"): + Temp = GetToken(F) + + +F.close() +Out.close() diff --git a/GlobalVariables/GlobalVariables.groovy b/GlobalVariables/GlobalVariables.groovy new file mode 100644 index 0000000..92ce8b9 --- /dev/null +++ b/GlobalVariables/GlobalVariables.groovy @@ -0,0 +1,22 @@ +//create a location object to change the variable inside the class +@Field def location = new locationVar() +//Settings variable defined to settings on purpose +@Field def settings = "Settings" +//Global variable for state[mode] +@Field def state = [home:[],away:[],night:[]] +//Create a global logger object for methods +@Field def log = new Logger() +//Create a global object for app +@Field def app = new Touch(1) +//Create a global list for objects for events on subscribe methods +@Field def ObjectList = [] +//Create a global list for events +@Field def EventList = [] +//Create a global list for function calls based on corresponding events +@Field def FunctionList = [] +//Create a global list for function schedulers +@Field def ListofTimersFunc = [] +//Create a global list for timer schedulers +@Field def ListofTimers = [] + + diff --git a/Location/Phrase.groovy b/Location/Phrase.groovy new file mode 100644 index 0000000..f9f3081 --- /dev/null +++ b/Location/Phrase.groovy @@ -0,0 +1,15 @@ +//Create a class for Phrase +package Location + +class Phrase { + private LinkedHashMap Phrases + + Phrase() { + this.Phrases = [id:0, label:"Good Morning!"] + } + def getPhrases() { + return Phrases + } +} + + diff --git a/Location/locationVar.groovy b/Location/locationVar.groovy new file mode 100644 index 0000000..42e1f56 --- /dev/null +++ b/Location/locationVar.groovy @@ -0,0 +1,21 @@ +//Create a class for location variable +package Location + +class locationVar { + private String modes + private String mode + private int contactBookEnabled + private List CONTACTS + private List PhoneNums + + private Phrase helloHome + + locationVar() { + this.modes = "'home', 'away', 'night'" + this.mode = "home" + this.helloHome = new Phrase() + this.contactBookEnabled = 1 + this.CONTACTS = ['AJ'] + this.PhoneNums = [9495379373] + } +} diff --git a/Lock/locking.groovy b/Lock/locking.groovy new file mode 100644 index 0000000..e8ce9dd --- /dev/null +++ b/Lock/locking.groovy @@ -0,0 +1,55 @@ +//Create a class for lock device +package Lock + +public class locking{ + List locks + int count + + locking(int count) { + this.count = count + if (count == 1) { + locks = [new locks(0, "lock0", "locked", "locked")] + } else if (count == 2) { + locks = [new locks(0, "lock0", "locked", "locked"),new locks(1, "lock1", "locked", "locked")] + } else if (count == 3) { + locks = [new locks(0, "lock0", "locked", "locked"),new locks(1, "lock1", "locked", "locked"),new locks(2, "lock2", "locked", "locked")] + } + } + + def lock() { + if (count == 1) { + locks[0].lock() + } else { + locks*.lock() + } + } + + def unlock() { + if (count == 1) { + locks[0].unlock() + } else { + locks*.unlock() + } + } + + def currentValue(String S) { + if (count == 1) { + locks[0].currentValue(S) + } else { + locks*.currentValue(S) + } + } + + def latestValue(String S) { + if (count == 1) { + locks[0].latestValue(S) + } else { + locks*.latestValue(S) + } + } + + def getAt(int ix) { + locks[ix] + } +} + diff --git a/Lock/locks.groovy b/Lock/locks.groovy new file mode 100644 index 0000000..87671da --- /dev/null +++ b/Lock/locks.groovy @@ -0,0 +1,40 @@ +//Create a class for lock device +package Lock + +public class locks { + private int id = 0 + private String displayName + private String currentLock + private String lockLatestValue + + locks(int id, String displayName, String currentLock, String lockLatestValue) { + this.id = id + this.displayName = displayName + this.currentLock = currentLock + this.lockLatestValue = lockLatestValue + } + + def lock() { + println("the door with id:$id is locked!") + this.lockLatestValue = this.currentLock + this.currentLock = "locked" + } + + def unlock() { + println("the door with id:$id is unlocked!") + this.lockLatestValue = this.currentLock + this.currentLock = "unlocked" + } + + def currentValue(String S) { + if (S == "lock") { + return currentLock + } + } + + def latestValue(String S) { + if (S == "lock") { + return lockLatestValue + } + } +} diff --git a/Logger/Logger.groovy b/Logger/Logger.groovy new file mode 100644 index 0000000..35ca850 --- /dev/null +++ b/Logger/Logger.groovy @@ -0,0 +1,13 @@ +//Adding a Logger class for log.debug +package Logger + +class Logger { + private boolean printToConsole = true + + def methodMissing(String name, args) { + def messsage = args[0] + if (printToConsole) { + println messsage + } + } +} diff --git a/Methods/EventHandler.groovy b/Methods/EventHandler.groovy new file mode 100644 index 0000000..a216725 --- /dev/null +++ b/Methods/EventHandler.groovy @@ -0,0 +1,141 @@ +///////////////////////////////////////////////////////////////////// +def EventHandler() { + while(true) { + List evt = [] + print "Waiting for an event...\n" + def EVENT = System.in.newReader().readLine() + SepLine = EVENT.split() + for (int i = 0; i < EventList.size(); i++) { + if (EventList[i] == SepLine[0]) { + println("The following effect: \n") + evt.add(new Event()) + switch(SepLine[0]) { + case "Touched": + ObjectList[i].touched = 1 + evt[-1].value = "Touched" + evt[-1].linkText = "touched by user" + evt[-1].name = "TouchSensor" + evt[-1].descriptionText = "Touching" + break + case "lock": + if (SepLine[1] == "0") { + ObjectList[i][0].lock() + evt[-1].deviceId = 0 + evt[-1].value = "locked" + evt[-1].linkText = "lock0" + evt[-1].displayName = "lock0" + evt[-1].name = "lock" + evt[-1].descriptionText = "locking" + } else if (SepLine[1] == "1") { + ObjectList[i][1].lock() + evt[-1].deviceId = 1 + evt[-1].value = "locked" + evt[-1].linkText = "lock1" + evt[-1].displayName = "lock1" + evt[-1].name = "lock" + evt[-1].descriptionText = "locking" + } else if (SepLine[1] == "2") { + ObjectList[i][2].lock() + evt[-1].deviceId = 2 + evt[-1].value = "locked" + evt[-1].linkText = "lock2" + evt[-1].displayName = "lock2" + evt[-1].name = "lock" + evt[-1].descriptionText = "locking" + } + break + case "unlock": + if (SepLine[1] == "0") { + ObjectList[i][0].unlock() + evt[-1].deviceId = 0 + evt[-1].value = "unlocked" + evt[-1].linkText = "lock0" + evt[-1].displayName = "lock0" + evt[-1].name = "lock" + evt[-1].descriptionText = "unlocking" + } else if (SepLine[1] == "1") { + ObjectList[i][1].unlock() + evt[-1].deviceId = 0 + evt[-1].value = "unlocked" + evt[-1].linkText = "lock1" + evt[-1].displayName = "lock1" + evt[-1].name = "lock" + evt[-1].descriptionText = "unlocking" + } else if (SepLine[1] == "2") { + ObjectList[i][2].unlock() + evt[-1].deviceId = 2 + evt[-1].value = "unlocked" + evt[-1].linkText = "lock2" + evt[-1].displayName = "lock2" + evt[-1].name = "lock" + evt[-1].descriptionText = "unlocking" + } + break + case "contact.open": + if (SepLine[1] == "0") { + ObjectList[i][0].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][0].currentContact = "open" + evt[-1].deviceId = 0 + evt[-1].value = "contact.open" + evt[-1].linkText = "contact0" + evt[-1].displayName = "contact0" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "opening" + } else if (SepLine[1] == "1") { + ObjectList[i][1].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][1].currentContact = "open" + evt[-1].deviceId = 1 + evt[-1].value = "contact.open" + evt[-1].linkText = "contact1" + evt[-1].displayName = "contact1" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "opening" + } else if (SepLine[1] == "2") { + ObjectList[i][2].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][2].currentContact = "open" + evt[-1].deviceId = 2 + evt[-1].value = "contact.open" + evt[-1].linkText = "contact2" + evt[-1].displayName = "contact2" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "opening" + } + break + case "contact.closed": + if (SepLine[1] == "0") { + ObjectList[i][0].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][0].currentContact = "closed" + evt[-1].deviceId = 0 + evt[-1].value = "contact.closed" + evt[-1].linkText = "contact0" + evt[-1].displayName = "contact0" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "closing" + } else if (SepLine[1] == "1") { + ObjectList[i][1].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][1].currentContact = "closed" + evt[-1].deviceId = 1 + evt[-1].value = "contact.closed" + evt[-1].linkText = "contact1" + evt[-1].displayName = "contact1" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "closing" + } else if (SepLine[1] == "2") { + ObjectList[i][2].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][2].currentContact = "closed" + evt[-1].deviceId = 2 + evt[-1].value = "contact.closed" + evt[-1].linkText = "contact2" + evt[-1].displayName = "contact2" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "closing" + } + break + default: + break + } + FunctionList[i](evt[-1]) + } + } + } +} diff --git a/Methods/definition.groovy b/Methods/definition.groovy new file mode 100644 index 0000000..a5cb5dd --- /dev/null +++ b/Methods/definition.groovy @@ -0,0 +1,5 @@ +///////////////////////////////////////////////////////////////////// +def definition(LinkedHashMap LHM) { + println("IGNORE -- JUST SOME DEFINITION") +} + diff --git a/Methods/preferences.groovy b/Methods/preferences.groovy new file mode 100644 index 0000000..8c98821 --- /dev/null +++ b/Methods/preferences.groovy @@ -0,0 +1,4 @@ +///////////////////////////////////////////////////////////////////// +def preferences(Closure Input) { + println("IGNORE -- JUST SOME DEFINITION") +} diff --git a/Methods/runIn.groovy b/Methods/runIn.groovy new file mode 100644 index 0000000..ce056e3 --- /dev/null +++ b/Methods/runIn.groovy @@ -0,0 +1,7 @@ +///////////////////////////////////////////////////////////////////// +////runIn(time, func) +def runIn(int seconds, Closure Input) { + ListofTimersFunc.add(Input) + ListofTimers.add(new Timer()) + def task = ListofTimers[-1].runAfter(1000*seconds, Input) +} diff --git a/Methods/sendNotificationToContacts.groovy b/Methods/sendNotificationToContacts.groovy new file mode 100644 index 0000000..cab016b --- /dev/null +++ b/Methods/sendNotificationToContacts.groovy @@ -0,0 +1,11 @@ +///////////////////////////////////////////////////////////////////// +////sendNotificationToContacts(text, recipients) +def sendNotificationToContacts(String S, List recipients) { + for (int i = 0;i < recipients.size();i++) { + for (int j = 0;j < location.CONTACTS.size();j++) { + if (recipients[i] == location.CONTACTS[j]) { + println("Sending \""+S+"\" to "+location.PhoneNums[j].toString()) + } + } + } +} diff --git a/Methods/sendSms.groovy b/Methods/sendSms.groovy new file mode 100644 index 0000000..f60a0a1 --- /dev/null +++ b/Methods/sendSms.groovy @@ -0,0 +1,5 @@ +///////////////////////////////////////////////////////////////////// +////sendNotificationToContacts(text, recipients) +def sendSms(long Phone, String S) { + println("Sending \""+S+"\" to "+Phone.toString()) +} diff --git a/Methods/setLocationMode.groovy b/Methods/setLocationMode.groovy new file mode 100644 index 0000000..d2dade1 --- /dev/null +++ b/Methods/setLocationMode.groovy @@ -0,0 +1,5 @@ +///////////////////////////////////////////////////////////////////// +def setLocationMode(String mode) { + location.mode = mode +} + diff --git a/Methods/subscribe.groovy b/Methods/subscribe.groovy new file mode 100644 index 0000000..74200cb --- /dev/null +++ b/Methods/subscribe.groovy @@ -0,0 +1,18 @@ +///////////////////////////////////////////////////////////////////// +////subscribe(app, func) +def subscribe(Object Obj, Closure Input) { + EventList.add("Touched") + FunctionList.add(Input) +} +////subscribe(obj, string, func) +def subscribe(Object Obj, String S, Closure Input) { + ObjectList.add(Obj) + EventList.add(S) + FunctionList.add(Input) +} +////subscribe(obj, string, func, hashmap) +def subscribe(Object Obj, String S, Closure Input, LinkedHashMap LHM) { + ObjectList.add(Obj) + EventList.add(S) + FunctionList.add(Input) +} diff --git a/Methods/unschedule.groovy b/Methods/unschedule.groovy new file mode 100644 index 0000000..8033606 --- /dev/null +++ b/Methods/unschedule.groovy @@ -0,0 +1,9 @@ +///////////////////////////////////////////////////////////////////// +////unschedule(func) +def unschedule(Closure Input) { + for (int i = 0;i < ListofTimersFunc.size();i++) { + if (ListofTimersFunc[i] == Input) { + ListofTimers[i].cancel() + } + } +} diff --git a/README b/README index d0c01ac..28554f7 100644 --- a/README +++ b/README @@ -1,2 +1,7 @@ This is a new repository for the SmartThings execution infrastructure for Groovy. This project attempts to model-check SmartThings Groovy smart apps. +------------------------------------------------------------------------------------------- +1. Put your groovy program in Extractor directory with the name App.groovy. +2. Run the make command in smartthings-infrastructure directory. +3. All the classes are created in the bin folder. +------------------------------------------------------------------------------------------- diff --git a/Runner.py b/Runner.py new file mode 100644 index 0000000..86a4251 --- /dev/null +++ b/Runner.py @@ -0,0 +1,76 @@ +import os + +#Create directory for files to append in the main file +Out = open("main.groovy", "w+") +GlobalVariables = open("GlobalVariables/"+"GlobalVariables.groovy", "r") +definition = open("Methods/"+"definition.groovy", "r") +preferences = open("Methods/"+"preferences.groovy", "r") +setLocationMode = open("Methods/"+"setLocationMode.groovy", "r") +subscribe = open("Methods/"+"subscribe.groovy", "r") +EventHandler = open("Methods/"+"EventHandler.groovy", "r") +runIn = open("Methods/"+"runIn.groovy", "r") +unschedule = open("Methods/"+"unschedule.groovy", "r") +sendNotificationToContacts = open("Methods/"+"sendNotificationToContacts.groovy", "r") +sendSms = open("Methods/"+"sendSms.groovy", "r") +App = open("Extractor/"+"App.groovy", "r") +ExtractedObjects = open("Extractor/"+"ExtractedObjects.groovy", "r") + + +#Extract information from preferences and subscribe method to create required objects +os.system("python Extractor/ExtractorScript.py") + +Out.write("//Infrastructure for SmartThings Application\n") +Out.write("//Importing Libraries\n") +Out.write("import groovy.transform.Field\n") +Out.write("\n") +Out.write("//Importing Classes\n") +Out.write("import ContactSensor.contacting\n") +Out.write("import ContactSensor.contacts\n") +Out.write("import Lock.locking\n") +Out.write("import Lock.locks\n") +Out.write("import Switch.switching\n") +Out.write("import Switch.switches\n") +Out.write("import Event.Event\n") +Out.write("import Logger.Logger\n") +Out.write("import Location.locationVar\n") +Out.write("import Location.Phrase\n") +Out.write("import appTouch.Touch\n") +Out.write("\n") +Out.write("//GlobalVariables\n") +for line in GlobalVariables: + Out.write(line) +Out.write("\n") +Out.write("//ExtractedObjects\n") +for line in ExtractedObjects: + Out.write(line) +Out.write("\n") +Out.write("//Methods\n") +for line in definition: + Out.write(line) +for line in preferences: + Out.write(line) +for line in setLocationMode: + Out.write(line) +for line in subscribe: + Out.write(line) +for line in EventHandler: + Out.write(line) +for line in runIn: + Out.write(line) +for line in unschedule: + Out.write(line) +for line in sendNotificationToContacts: + Out.write(line) +for line in sendSms: + Out.write(line) +Out.write("\n") +for line in App: + Out.write(line) +Out.write("\n") +Out.write("installed()\n") +Out.write("EventHandler()\n") +Out.close() + + + + diff --git a/Switch/switches.groovy b/Switch/switches.groovy new file mode 100644 index 0000000..359248d --- /dev/null +++ b/Switch/switches.groovy @@ -0,0 +1,54 @@ +//Create a class for switch device +package Switch + +public class switches { + private int id = 0 + private String displayName + private String currentSwitch + private String switchLatestValue + + switches(int id, String displayName, String currentSwitch, String switchLatestValue) { + this.id = id + this.displayName = displayName + this.currentSwitch = currentSwitch + this.switchLatestValue = switchLatestValue + } + + def on() { + println("the switch with id:$id is on!") + this.switchLatestValue = this.currentSwitch + this.currentSwitch = "on" + } + + def on(LinkedHashMap LHM) { + sleep(LHM["delay"]) + println("the switch with id:$id is on!") + this.switchLatestValue = this.currentSwitch + this.currentSwitch = "on" + } + + def off() { + println("the switch with id:$id is off!") + this.switchLatestValue = this.currentSwitch + this.currentSwitch = "off" + } + + def off(LinkedHashMap LHM) { + sleep(LHM["delay"]) + println("the switch with id:$id is off!") + this.switchLatestValue = this.currentSwitch + this.currentSwitch = "off" + } + + def currentValue(String S) { + if (S == "switch") { + return currentSwitch + } + } + + def latestValue(String S) { + if (S == "switch") { + return switchLatestValue + } + } +} diff --git a/Switch/switching.groovy b/Switch/switching.groovy new file mode 100644 index 0000000..da2f30c --- /dev/null +++ b/Switch/switching.groovy @@ -0,0 +1,74 @@ +//Create a class for switch device +package Switch + +public class switching{ + List switches + int count + + switching(int count) { + this.count = count + if (count == 1) { + switches = [new switches(0, "switch0", "off", "off")] + } else if (count == 2) { + switches = [new switches(0, "switch0", "off", "off"),new switches(1, "switch1", "off", "off")] + } else if (count == 3) { + switches = [new switches(0, "switch0", "off", "off"),new switches(1, "switch1", "off", "off"),new switches(2, "switch2", "off", "off")] + } + } + + def on() { + if (count == 1) { + switches[0].on() + } else { + switches*.on() + } + } + + def on(LinkedHashMap LHM) { + if (count == 1) { + sleep(LHM["delay"]) + switches[0].on() + } else { + sleep(LHM["delay"]) + switches*.on() + } + } + + def off() { + if (count == 1) { + switches[0].off() + } else { + switches*.off() + } + } + + def off(LinkedHashMap LHM) { + if (count == 1) { + sleep(LHM["delay"]) + switches[0].off() + } else { + sleep(LHM["delay"]) + switches*.off() + } + } + + def currentValue(String S) { + if (count == 1) { + switches[0].currentValue(S) + } else { + switches*.currentValue(S) + } + } + + def latestValue(String S) { + if (count == 1) { + switches[0].latestValue(S) + } else { + switches*.latestValue(S) + } + } + + def getAt(int ix) { + switches[ix] + } +} diff --git a/appTouch/Touch.groovy b/appTouch/Touch.groovy new file mode 100644 index 0000000..19e6816 --- /dev/null +++ b/appTouch/Touch.groovy @@ -0,0 +1,10 @@ +//Create a class for touch +package appTouch + +public class Touch { + private int touched = 0 + + Touch(int touched) { + this.touched = touched + } +} diff --git a/main.groovy b/main.groovy new file mode 100644 index 0000000..11d617c --- /dev/null +++ b/main.groovy @@ -0,0 +1,401 @@ +//Infrastructure for SmartThings Application +//Importing Libraries +import groovy.transform.Field + +//Importing Classes +import ContactSensor.contacting +import ContactSensor.contacts +import Lock.locking +import Lock.locks +import Switch.switching +import Switch.switches +import Event.Event +import Logger.Logger +import Location.locationVar +import Location.Phrase +import appTouch.Touch + +//GlobalVariables +//create a location object to change the variable inside the class +@Field def location = new locationVar() +//Settings variable defined to settings on purpose +@Field def settings = "Settings" +//Global variable for state[mode] +@Field def state = [home:[],away:[],night:[]] +//Create a global logger object for methods +@Field def log = new Logger() +//Create a global object for app +@Field def app = new Touch(1) +//Create a global list for objects for events on subscribe methods +@Field def ObjectList = [] +//Create a global list for events +@Field def EventList = [] +//Create a global list for function calls based on corresponding events +@Field def FunctionList = [] +//Create a global list for function schedulers +@Field def ListofTimersFunc = [] +//Create a global list for timer schedulers +@Field def ListofTimers = [] + + + +//ExtractedObjects +//Global Object for class lock! +@Field def lock1 = new locking(1) +//Global Object for class contactSensor! +@Field def contact = new contacting(1) +//Global variable for number! +@Field def minutesLater = 1 +//Global variable for number! +@Field def secondsLater = 10 +//Global variable for recipients! +@Field def recipients = ['AJ'] +//Global variable for phone number! +@Field def phoneNumber = 9495379373 +//Global Object for functions in subscribe method! +@Field def installed = this.&installed +//Global Object for functions in subscribe method! +@Field def updated = this.&updated +//Global Object for functions in subscribe method! +@Field def initialize = this.&initialize +//Global Object for functions in subscribe method! +@Field def lockDoor = this.&lockDoor +//Global Object for functions in subscribe method! +@Field def unlockDoor = this.&unlockDoor +//Global Object for functions in subscribe method! +@Field def doorHandler = this.&doorHandler + +//Methods +///////////////////////////////////////////////////////////////////// +def definition(LinkedHashMap LHM) { + println("IGNORE -- JUST SOME DEFINITION") +} + +///////////////////////////////////////////////////////////////////// +def preferences(Closure Input) { + println("IGNORE -- JUST SOME DEFINITION") +} +///////////////////////////////////////////////////////////////////// +def setLocationMode(String mode) { + location.mode = mode +} + +///////////////////////////////////////////////////////////////////// +////subscribe(app, func) +def subscribe(Object Obj, Closure Input) { + EventList.add("Touched") + FunctionList.add(Input) +} +////subscribe(obj, string, func) +def subscribe(Object Obj, String S, Closure Input) { + ObjectList.add(Obj) + EventList.add(S) + FunctionList.add(Input) +} +////subscribe(obj, string, func, hashmap) +def subscribe(Object Obj, String S, Closure Input, LinkedHashMap LHM) { + ObjectList.add(Obj) + EventList.add(S) + FunctionList.add(Input) +} +///////////////////////////////////////////////////////////////////// +def EventHandler() { + while(true) { + List evt = [] + print "Waiting for an event...\n" + def EVENT = System.in.newReader().readLine() + SepLine = EVENT.split() + for (int i = 0; i < EventList.size(); i++) { + if (EventList[i] == SepLine[0]) { + println("The following effect: \n") + evt.add(new Event()) + switch(SepLine[0]) { + case "Touched": + ObjectList[i].touched = 1 + evt[-1].value = "Touched" + evt[-1].linkText = "touched by user" + evt[-1].name = "TouchSensor" + evt[-1].descriptionText = "Touching" + break + case "lock": + if (SepLine[1] == "0") { + ObjectList[i][0].lock() + evt[-1].deviceId = 0 + evt[-1].value = "locked" + evt[-1].linkText = "lock0" + evt[-1].displayName = "lock0" + evt[-1].name = "lock" + evt[-1].descriptionText = "locking" + } else if (SepLine[1] == "1") { + ObjectList[i][1].lock() + evt[-1].deviceId = 1 + evt[-1].value = "locked" + evt[-1].linkText = "lock1" + evt[-1].displayName = "lock1" + evt[-1].name = "lock" + evt[-1].descriptionText = "locking" + } else if (SepLine[1] == "2") { + ObjectList[i][2].lock() + evt[-1].deviceId = 2 + evt[-1].value = "locked" + evt[-1].linkText = "lock2" + evt[-1].displayName = "lock2" + evt[-1].name = "lock" + evt[-1].descriptionText = "locking" + } + break + case "unlock": + if (SepLine[1] == "0") { + ObjectList[i][0].unlock() + evt[-1].deviceId = 0 + evt[-1].value = "unlocked" + evt[-1].linkText = "lock0" + evt[-1].displayName = "lock0" + evt[-1].name = "lock" + evt[-1].descriptionText = "unlocking" + } else if (SepLine[1] == "1") { + ObjectList[i][1].unlock() + evt[-1].deviceId = 0 + evt[-1].value = "unlocked" + evt[-1].linkText = "lock1" + evt[-1].displayName = "lock1" + evt[-1].name = "lock" + evt[-1].descriptionText = "unlocking" + } else if (SepLine[1] == "2") { + ObjectList[i][2].unlock() + evt[-1].deviceId = 2 + evt[-1].value = "unlocked" + evt[-1].linkText = "lock2" + evt[-1].displayName = "lock2" + evt[-1].name = "lock" + evt[-1].descriptionText = "unlocking" + } + break + case "contact.open": + if (SepLine[1] == "0") { + ObjectList[i][0].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][0].currentContact = "open" + evt[-1].deviceId = 0 + evt[-1].value = "contact.open" + evt[-1].linkText = "contact0" + evt[-1].displayName = "contact0" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "opening" + } else if (SepLine[1] == "1") { + ObjectList[i][1].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][1].currentContact = "open" + evt[-1].deviceId = 1 + evt[-1].value = "contact.open" + evt[-1].linkText = "contact1" + evt[-1].displayName = "contact1" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "opening" + } else if (SepLine[1] == "2") { + ObjectList[i][2].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][2].currentContact = "open" + evt[-1].deviceId = 2 + evt[-1].value = "contact.open" + evt[-1].linkText = "contact2" + evt[-1].displayName = "contact2" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "opening" + } + break + case "contact.closed": + if (SepLine[1] == "0") { + ObjectList[i][0].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][0].currentContact = "closed" + evt[-1].deviceId = 0 + evt[-1].value = "contact.closed" + evt[-1].linkText = "contact0" + evt[-1].displayName = "contact0" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "closing" + } else if (SepLine[1] == "1") { + ObjectList[i][1].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][1].currentContact = "closed" + evt[-1].deviceId = 1 + evt[-1].value = "contact.closed" + evt[-1].linkText = "contact1" + evt[-1].displayName = "contact1" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "closing" + } else if (SepLine[1] == "2") { + ObjectList[i][2].contactLatestValue = ObjectList[i].currentContact + ObjectList[i][2].currentContact = "closed" + evt[-1].deviceId = 2 + evt[-1].value = "contact.closed" + evt[-1].linkText = "contact2" + evt[-1].displayName = "contact2" + evt[-1].name = "ContactSensor" + evt[-1].descriptionText = "closing" + } + break + default: + break + } + FunctionList[i](evt[-1]) + } + } + } +} +///////////////////////////////////////////////////////////////////// +////runIn(time, func) +def runIn(int seconds, Closure Input) { + ListofTimersFunc.add(Input) + ListofTimers.add(new Timer()) + def task = ListofTimers[-1].runAfter(1000*seconds, Input) +} +///////////////////////////////////////////////////////////////////// +////unschedule(func) +def unschedule(Closure Input) { + for (int i = 0;i < ListofTimersFunc.size();i++) { + if (ListofTimersFunc[i] == Input) { + ListofTimers[i].cancel() + } + } +} +///////////////////////////////////////////////////////////////////// +////sendNotificationToContacts(text, recipients) +def sendNotificationToContacts(String S, List recipients) { + for (int i = 0;i < recipients.size();i++) { + for (int j = 0;j < location.CONTACTS.size();j++) { + if (recipients[i] == location.CONTACTS[j]) { + println("Sending \""+S+"\" to "+location.PhoneNums[j].toString()) + } + } + } +} +///////////////////////////////////////////////////////////////////// +////sendNotificationToContacts(text, recipients) +def sendSms(long Phone, String S) { + println("Sending \""+S+"\" to "+Phone.toString()) +} + +/////////////////////////////////////////// +definition( + name: "Enhanced Auto Lock Door", + namespace: "Lock Auto Super Enhanced", + author: "Arnaud", + description: "Automatically locks a specific door after X minutes when closed and unlocks it when open after X seconds.", + category: "Safety & Security", + iconUrl: "http://www.gharexpert.com/mid/4142010105208.jpg", + iconX2Url: "http://www.gharexpert.com/mid/4142010105208.jpg" +) + +preferences{ + section("Select the door lock:") { + input "lock1", "capability.lock", required: true, multiple: true + } + section("Select the door contact sensor:") { + input "contact", "capability.contactSensor", required: true + } + section("Automatically lock the door when closed...") { + input "minutesLater", "number", title: "Delay (in minutes):", required: true + } + section("Automatically unlock the door when open...") { + input "secondsLater", "number", title: "Delay (in seconds):", required: true + } + section( "Notifications" ) { + input("recipients", "contact", title: "Send notifications to", required: false) { + input "phoneNumber", "phone", title: "Warn with text message (optional)", description: "Phone Number", required: false + } + } +} + +def installed(){ + initialize() +} + +def updated(){ + unsubscribe() + unschedule() + initialize() +} + +def initialize(){ + log.debug "Settings: ${settings}" + subscribe(lock1, "lock", doorHandler, [filterEvents: false]) + subscribe(lock1, "unlock", doorHandler, [filterEvents: false]) + subscribe(contact, "contact.open", doorHandler) + subscribe(contact, "contact.closed", doorHandler) +} + +def lockDoor(){ + log.debug "Locking the door." + lock1.lock() + if(location.contactBookEnabled) { + if ( recipients ) { + log.debug ( "Sending Push Notification..." ) + sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients) + } + } + if (phoneNumber) { + log.debug("Sending text message...") + sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!") + } +} + +def unlockDoor(){ + log.debug "Unlocking the door." + lock1.unlock() + if(location.contactBookEnabled) { + if ( recipients ) { + log.debug ( "Sending Push Notification..." ) + sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients) + } + } + if ( phoneNumber ) { + log.debug("Sending text message...") + sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!") + } +} + +def doorHandler(evt){ + log.debug evt.value + log.debug contact.latestValue("contact") + if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then... + //def delay = (secondsLater) // runIn uses seconds + log.debug "1" + runIn( secondsLater, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged. + } + else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then... + log.debug "2" + unschedule( unlockDoor ) // ...we don't need to unlock it later. + } + else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then... + log.debug "3" + unschedule( lockDoor ) // ...we don't need to lock it later. + } + else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then... + log.debug "4" + //def delay = (minutesLater * 60) // runIn uses seconds + runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock. + } + else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door... + log.debug "5" + unschedule( lockDoor ) // ...we don't need to lock it later. + } + else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door... + //def delay = (minutesLater * 60) // runIn uses seconds + log.debug "6" + runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock. + } + else { //Opening or Closing door when locked (in case you have a handle lock) + log.debug "Unlocking the door." + lock1.unlock() + if(location.contactBookEnabled) { + if ( recipients ) { + log.debug ( "Sending Push Notification..." ) + sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients) + } + } + if ( phoneNumber ) { + log.debug("Sending text message...") + sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!") + } + } +} + +installed() +EventHandler() diff --git a/makefile b/makefile new file mode 100644 index 0000000..5576b0a --- /dev/null +++ b/makefile @@ -0,0 +1,15 @@ +PCC = python +GCC = groovyc +GFLAGS = -d +RMFLAGS = -r + +default: Runner main + +Runner: Runner.py + $(PCC) Runner.py +main: main.groovy + $(GCC) $(GFLAGS) bin/main main.groovy + + +clean: + $(RM) $(RMFLAGS) bin/main