From da6f20da230721ba97ea709eb09aca12cc1ab743 Mon Sep 17 00:00:00 2001 From: MarkGodwin Date: Sat, 23 Oct 2021 15:34:24 +0100 Subject: [PATCH 01/12] Add controls to allow shutter configuration --- html/index.html | 135 +++++++++++++++++++++++++++++++++++++++- html/operateShutters.js | 69 ++++++++++++++++++++ mywebserver.py | 12 +++- operateShutters.py | 30 ++++++++- 4 files changed, 242 insertions(+), 4 deletions(-) diff --git a/html/index.html b/html/index.html index ed0a922..f8fba51 100755 --- a/html/index.html +++ b/html/index.html @@ -103,10 +103,11 @@

Operation Time (Seconds) Actions
- + +
@@ -316,6 +317,138 @@

Time to program your new shutter...

+ diff --git a/html/operateShutters.js b/html/operateShutters.js index 371f310..9a62e82 100755 --- a/html/operateShutters.js +++ b/html/operateShutters.js @@ -4,6 +4,13 @@ var mymap; var marker; var config; var modalCallerIconElement; +var configShutter; + +const buttonStop = 0x1; +const buttonUp = 0x2; +const buttonDown = 0x4; +const buttonProg = 0x8; + GetStartupInfo(true); $(document).ready(function() { @@ -210,6 +217,21 @@ function programShutter(id) { }, "json"); } +function pressButtons(id, buttons, longPress, confirmMessage) { + var url = baseurl.concat("press"); + $.post(url, + {shutter: id, buttons: buttons, longPress: longPress }, + function(result, status){ + if ((status=="success") && (result.status == "OK")) { + if(confirmMessage != null) { + BootstrapDialog.show({type: BootstrapDialog.TYPE_INFO, title: 'Information', message:confirmMessage}); + } + } else { + BootstrapDialog.show({type: BootstrapDialog.TYPE_DANGER, title: 'Error', message:'Received Error from Server: '+result.message, onhide: function(){GetStartupInfo(false);}}); + } + }, "json"); +} + function deleteShutter(id) { var url = baseurl.concat("deleteShutter"); $.post( url, @@ -705,6 +727,12 @@ function setupListeners() { programShutter($(this).parents("tr").attr('name')); }); + // Edit row on configure button click + $(document).on("click", ".configureShutters", function(){ + configShutter = $(this).parents("tr").attr('name'); + $('#configure-shutter').modal('show'); + }); + // Edit row on edit button click $(document).on("click", ".editSchedule", function(){ $(this).parents("tr").find('.editbox').toggle(); @@ -769,6 +797,47 @@ function setupListeners() { // The hide.bs.modal event will take care of refreshing the main window }); + $(document).on("click", '.press-button-up-short', function(){ + // Fine adjustment of blind up + pressButtons(configShutter, buttonUp, false); + }); + + $(document).on("click", '.press-button-down-short', function(){ + // Fine adjustment of blind down + pressButtons(configShutter, buttonDown, false); + }); + + $(document).on("click", '.press-button-stop-short', function(){ + // Stops blind when it's in motion, or moves to the My position. + pressButtons(configShutter, buttonStop, false); + }); + + $(document).on("click", '.press-button-up-down-long', function(){ + // Used in new installation setup, or when entering blind limit configuration mode + pressButtons(configShutter, buttonUp | buttonDown, true, "The blind should have jogged. If not, try again."); + }); + + $(document).on("click", '.press-button-up-stop-short', function(){ + // Used to set a My position, to reverse the direction of blinds during initial setup, and to confirm limit position settings. + pressButtons(configShutter, buttonUp | buttonStop, false, "The blind should have started moving up. If not, try again."); + }); + + $(document).on("click", '.press-button-down-stop-short', function(){ + // Used to set a My position, to reverse the direction of blinds during initial setup, and to confirm limit position settings. + pressButtons(configShutter, buttonDown | buttonStop, false, "The blind should have started moving down. If not, try again."); + }); + + $(document).on("click", '.press-button-stop-long', function(){ + // Used to set a My position, to reverse the direction of blinds during initial setup, and to confirm limit position settings. + pressButtons(configShutter, buttonStop, true, "The blind should have jogged. If not, try again."); + }); + + $(document).on("click", '.press-button-prog-long', function(){ + // Used to end programming, or to switch a blind into Remote Learning mode + pressButtons(configShutter, buttonProg, true); + }); + + // Shutter Commands $(document).on("click", ".up", function(){ var key = $(this).parents("div").attr('name'); diff --git a/mywebserver.py b/mywebserver.py index 39ca23a..9994167 100644 --- a/mywebserver.py +++ b/mywebserver.py @@ -79,7 +79,7 @@ def processCommand(self, *args, **kwargs): # self.LogDebug("JSON: "+str(request.get_json())) # self.LogDebug("RAW: "+str(request.get_data())) command = args[1]['command'] - if command in ["up", "down", "stop", "program", "getConfig", "addSchedule", "editSchedule", "deleteSchedule", "addShutter", "editShutter", "deleteShutter", "setLocation" ]: + if command in ["up", "down", "stop", "program", "press", "getConfig", "addSchedule", "editSchedule", "deleteSchedule", "addShutter", "editShutter", "deleteShutter", "setLocation" ]: self.LogInfo("processing Command \"" + command + "\" with parameters: "+str(request.values)) result = getattr(self, command)(request.values) return Response(json.dumps(result), status=200) @@ -153,6 +153,16 @@ def program(self, params): self.shutter.program(shutter) return {'status': 'OK'} + def press(self, params): + shutter=params.get('shutter', 0, type=str) + buttons = params.get('buttons', 0, type=int) + longPress = params.get('longPress', 0, type=str) == "true" + self.LogDebug(("long" if longPress else "short") +" press buttons: \"" +str(buttons)+ "\" shutter \""+shutter+"\"") + if (not shutter in self.config.Shutters): + return {'status': 'ERROR', 'message': 'Shutter does not exist'} + self.shutter.pressButtons(shutter, buttons, longPress) + return {'status': 'OK'} + def setLocation(self, params): self.LogDebug("set Location: "+params.get('lat', 0, type=str)+" / "+params.get('lng', 0, type=str)) self.config.setLocation(params.get('lat', 0, type=str), params.get('lng', 0, type=str)) diff --git a/operateShutters.py b/operateShutters.py index 6f39cad..d3ed454 100755 --- a/operateShutters.py +++ b/operateShutters.py @@ -203,6 +203,10 @@ def stop(self, shutterId): # Register command at the end to not impact the lastCommand timer state.registerCommand(None) + # Push a set of buttons for a short or long press. + def pressButtons(self, shutterId, buttons, longPress): + self.sendCommand(shutterId, buttons, 35 if longPress else 1) + def program(self, shutterId): self.sendCommand(shutterId, self.buttonProg, 1) @@ -309,7 +313,7 @@ def sendCommand(self, shutterId, button, repetition): #Sending a frame wf.append(pigpio.pulse(0, 1< Date: Sat, 23 Oct 2021 15:43:08 +0100 Subject: [PATCH 02/12] Update readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6261fda..70c8fc0 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,9 @@ You have 6 ways to operate. The recommended operation mode is mode 5. But the ot -down, -d lower the shutter -stop, -s Stop the shutter -program, -p Send a program signal + -press BTN [BTN...] Simulate a chord press of a the specified remote buttons + ('up', 'down', 'stop'/'my', and 'program') + -long When used with the -press option, simulates a long press -demo lower the shutter, stop after 7 second then raise the shutter -duskdawn DUSKDAWN DUSKDAWN, -dd DUSKDAWN DUSKDAWN Automatically lower the shutter at sunset and rise the @@ -135,6 +138,7 @@ You have 6 ways to operate. The recommended operation mode is mode 5. But the ot -mqtt, -m Enable MQTT integration + **Examples:** All three command the shutter named corridor. The first one will raise it. The second one will lower it. The third one will lower the shutter at sunset and raise it again 60 minutes after sunrise. ```sh @@ -215,7 +219,7 @@ First time you use the Web GUI, it's important that you follow these 3 steps: 1. Set up your location. This is required to correctly determine the time of sunrise and sunset. To do so, navigate to the top menu item "Settings". Use the map to pinpoint your location. You can also use the search functionality on the left-hand side (magnifier icon) and type your address. Press "Save" 1. You will need to set up your shutters and program your remote control. To do so, select the second menu item "Add/Remove Shutters".
![Screenshot](documentation/p1.png)
-Click the "Add" button, select the name for your shutter (this is also the name that the Amazon Alexa app will use later) and click on the "save" icon. Then follow the on-screen instructions for programming your shutter. +Click the "Add" button, select the name for your shutter (this is also the name that the Amazon Alexa app will use later) and click on the "save" icon. Then follow the on-screen instructions for programming your shutter. For installing shutters from the factory default configuration, use the Configure button. 1. Next, make sure your shutters work. The easiest way to verify is to use the "Manual Operations" menu.
![Screenshot](documentation/p2.png)
You can rise and lower your shutters by clicking on the relevant icons. From 9f7bdd86af16d0ebf213948d5bcfb6e8212549ef Mon Sep 17 00:00:00 2001 From: rbswift <50788724+rbswift@users.noreply.github.com> Date: Thu, 28 Oct 2021 20:44:01 +1100 Subject: [PATCH 03/12] Update myconfig.py add seperate down and up durations --- myconfig.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/myconfig.py b/myconfig.py index 45e840a..dd95289 100644 --- a/myconfig.py +++ b/myconfig.py @@ -86,7 +86,10 @@ def LoadConfig(self): param3 = self.ReadValue(key, section="ShutterIntermediatePositions", return_type=int) if (param3 != None) and ((param3 < 0) or (param3 > 100)): param3 = None - self.Shutters[key] = {'name': param1[0], 'code': param2, 'duration': int(param1[2]), 'intermediatePosition': param3} + # If only one duration is specified, use it for both down and up durations. + if len (param1) < 4: + param1.append(param1[2]) + self.Shutters[key] = {'name': param1[0], 'code': param2, 'durationDown': int(param1[2]), 'durationUp': int(param1[3]), 'intermediatePosition': param3} self.ShuttersByName[param1[0]] = key except Exception as e1: self.LogErrorLine("Missing config file or config file entries in Section Shutters for key "+key+": " + str(e1)) From d33503f6f7988f8d1c52eb6cc09533f08b71cd93 Mon Sep 17 00:00:00 2001 From: rbswift <50788724+rbswift@users.noreply.github.com> Date: Thu, 28 Oct 2021 20:48:28 +1100 Subject: [PATCH 04/12] Update operateShutters.py separate up and down durations and remove pointless wait --- operateShutters.py | 58 ++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/operateShutters.py b/operateShutters.py index 6f39cad..09ae210 100755 --- a/operateShutters.py +++ b/operateShutters.py @@ -106,7 +106,7 @@ def lower(self, shutterId): state.registerCommand('down') # wait and set final position only if not interrupted in between - timeToWait = state.position/100*self.config.Shutters[shutterId]['duration'] + timeToWait = state.position/100*self.config.Shutters[shutterId]['durationDown'] t = threading.Thread(target = self.waitAndSetFinalPosition, args = (shutterId, timeToWait, 0)) t.start() @@ -116,7 +116,7 @@ def lowerPartial(self, shutterId, percentage): self.LogInfo("["+self.config.Shutters[shutterId]['name']+"] Going down") self.sendCommand(shutterId, self.buttonDown, self.config.SendRepeat) state.registerCommand('down') - time.sleep((state.position-percentage)/100*self.config.Shutters[shutterId]['duration']) + time.sleep((state.position-percentage)/100*self.config.Shutters[shutterId]['durationDown']) self.LogInfo("["+self.config.Shutters[shutterId]['name']+"] Stop at partial position requested") self.sendCommand(shutterId, self.buttonStop, self.config.SendRepeat) @@ -130,7 +130,7 @@ def rise(self, shutterId): state.registerCommand('up') # wait and set final position only if not interrupted in between - timeToWait = (100-state.position)/100*self.config.Shutters[shutterId]['duration'] + timeToWait = (100-state.position)/100*self.config.Shutters[shutterId]['durationUp'] t = threading.Thread(target = self.waitAndSetFinalPosition, args = (shutterId, timeToWait, 100)) t.start() @@ -140,7 +140,7 @@ def risePartial(self, shutterId, percentage): self.LogInfo("["+self.config.Shutters[shutterId]['name']+"] Going up") self.sendCommand(shutterId, self.buttonUp, self.config.SendRepeat) state.registerCommand('up') - time.sleep((percentage-state.position)/100*self.config.Shutters[shutterId]['duration']) + time.sleep((percentage-state.position)/100*self.config.Shutters[shutterId]['durationUp']) self.LogInfo("["+self.config.Shutters[shutterId]['name']+"] Stop at partial position requested") self.sendCommand(shutterId, self.buttonStop, self.config.SendRepeat) @@ -157,27 +157,34 @@ def stop(self, shutterId): self.LogDebug("["+shutterId+"] Seconds since last command: " + str(secondsSinceLastCommand)) # Compute position based on time elapsed since last command & command direction - setupDuration = self.config.Shutters[shutterId]['duration'] + setupDurationDown = self.config.Shutters[shutterId]['durationDown'] + setupDurationUp = self.config.Shutters[shutterId]['durationUp'] fallback = False - if secondsSinceLastCommand > 0 and secondsSinceLastCommand < setupDuration: - durationPercentage = int(round(secondsSinceLastCommand/setupDuration * 100)) - self.LogDebug("["+shutterId+"] Duration percentage: " + str(durationPercentage) + ", State position: "+ str(state.position)) - if state.lastCommandDirection == 'up': - if state.position > 0: # after rise from previous position - newPosition = min (100 , state.position + durationPercentage) - else: # after rise from fully closed - newPosition = durationPercentage - elif state.lastCommandDirection == 'down': - if state.position < 100: # after lower from previous position - newPosition = max (0 , state.position - durationPercentage) - else: # after down from fully opened - newPosition = 100 - durationPercentage - else: # consecutive stops - self.LogWarn("["+shutterId+"] Stop pressed while stationary.") - fallback = True - else: #fallback - self.LogWarn("["+shutterId+"] Too much time since last command.") + if state.lastCommandDirection == 'up': + if secondsSinceLastCommand > 0 and secondsSinceLastCommand < setupDurationUp: + durationPercentage = int(round(secondsSinceLastCommand/setupDurationUp * 100)) + self.LogDebug("["+shutterId+"] Up duration percentage: " + str(durationPercentage) + ", State position: "+ str(state.position)) + if state.position > 0: # after rise from previous position + newPosition = min (100 , state.position + durationPercentage) + else: # after rise from fully closed + newPosition = durationPercentage + else: #fallback + self.LogWarn("["+shutterId+"] Too much time since up command.") + fallback = True + elif state.lastCommandDirection == 'down': + if secondsSinceLastCommand > 0 and secondsSinceLastCommand < setupDurationDown: + durationPercentage = int(round(secondsSinceLastCommand/setupDurationDown * 100)) + self.LogDebug("["+shutterId+"] Down duration percentage: " + str(durationPercentage) + ", State position: "+ str(state.position)) + if state.position < 100: # after lower from previous position + newPosition = max (0 , state.position - durationPercentage) + else: # after down from fully opened + newPosition = 100 - durationPercentage + else: #fallback + self.LogWarn("["+shutterId+"] Too much time since down command.") + fallback = True + else: # consecutive stops + self.LogWarn("["+shutterId+"] Stop pressed while stationary.") fallback = True if fallback == True: # Let's assume it will end on the intermediate position ! If it exists ! @@ -189,10 +196,11 @@ def stop(self, shutterId): self.LogInfo("["+shutterId+"] Motor expected to move to intermediate position "+str(intermediatePosition)) if state.position > intermediatePosition: state.registerCommand('down') + timeToWait = abs(state.position - intermediatePosition) / 100*self.config.Shutters[shutterId]['durationDown'] else: state.registerCommand('up') + timeToWait = abs(state.position - intermediatePosition) / 100*self.config.Shutters[shutterId]['durationUp'] # wait and set final intermediate position only if not interrupted in between - timeToWait = abs(state.position - intermediatePosition) / 100*self.config.Shutters[shutterId]['duration'] t = threading.Thread(target = self.waitAndSetFinalPosition, args = (shutterId, timeToWait, intermediatePosition)) t.start() return @@ -309,7 +317,7 @@ def sendCommand(self, shutterId, button, repetition): #Sending a frame wf.append(pigpio.pulse(0, 1< Date: Thu, 28 Oct 2021 20:55:13 +1100 Subject: [PATCH 05/12] Update mywebserver.py Use down duration time for principal direction. Up direction could be added to UI later as seperate field --- mywebserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mywebserver.py b/mywebserver.py index 39ca23a..6d2e97f 100644 --- a/mywebserver.py +++ b/mywebserver.py @@ -255,7 +255,7 @@ def getConfig(self, params): durations = {} for k in self.config.Shutters: shutters[k] = self.config.Shutters[k]['name'] - durations[k] = self.config.Shutters[k]['duration'] + durations[k] = self.config.Shutters[k]['durationDown'] obj = {'Latitude': self.config.Latitude, 'Longitude': self.config.Longitude, 'Shutters': shutters, 'ShutterDurations': durations, 'Schedule': self.schedule.getScheduleAsDict()} self.LogDebug("getConfig called, sending: "+json.dumps(obj)) return obj From d2b0eebb9e20e692174e23f77613eb30e67ac1f0 Mon Sep 17 00:00:00 2001 From: rbswift <50788724+rbswift@users.noreply.github.com> Date: Thu, 28 Oct 2021 21:01:42 +1100 Subject: [PATCH 06/12] Update defaultConfig.conf add comments for durations --- defaultConfig.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/defaultConfig.conf b/defaultConfig.conf index c20403f..af8ae1c 100644 --- a/defaultConfig.conf +++ b/defaultConfig.conf @@ -92,6 +92,8 @@ EnableDiscovery = true # followed by a comma deliminated list of # - Userfriednly Name # - indicator if these remote is active or has been (soft-)deleted +# - duration (seconds) for motor to fully lower down +# - duration (seconds) for motor to fully rise up (optional otherwise down duration will be used in both directions) # [Shutters] From 474fd6c9863ece9e190c3c074cb33cc1841cede7 Mon Sep 17 00:00:00 2001 From: Michael <38872689+MichaelB2018@users.noreply.github.com> Date: Sat, 30 Oct 2021 10:51:17 -0400 Subject: [PATCH 07/12] further changes to improve stability during reconnect --- mymqtt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mymqtt.py b/mymqtt.py index 7a1789a..da3c98f 100644 --- a/mymqtt.py +++ b/mymqtt.py @@ -128,13 +128,13 @@ def on_connect(self, client, userdata, flags, rc): self.connected_flag=False def on_disconnect(self, client, userdata, rc=0): + self.connected_flag=False if rc != 0: self.LogInfo("Disconnected from MQTT Server. result code: " + str(rc)) - self.connected_flag=False - while not self.connected_flag: #wait in loop - self.LogInfo("Waiting 30sec for reconnect") - time.sleep(30) - self.t.connect(self.config.MQTT_Server,self.config.MQTT_Port) + #while not self.connected_flag: #wait in loop + # self.LogInfo("Waiting 30sec for reconnect") + # time.sleep(30) + # self.t.connect(self.config.MQTT_Server,self.config.MQTT_Port) def set_state(self, shutterId, level): @@ -177,9 +177,9 @@ def run(self): self.t.loop(timeout=30) # self.t.loop_start() if self.connected_flag == False: - time.sleep(10) self.LogInfo("Re-Connecting to MQTT server") self.t.connect(self.config.MQTT_Server,self.config.MQTT_Port) + time.sleep(10) except Exception as e: error += 1 self.LogInfo("Critical exception " + str(error) + ": "+ str(e.args)) From f6a9c6ec8e70f2112b1630742d4cd13161b16a64 Mon Sep 17 00:00:00 2001 From: metbosch Date: Wed, 3 Mar 2021 07:37:10 +0000 Subject: [PATCH 08/12] Check if pigpio is connected during initialization The pigpio.pi may return without an established connection. Therefore, the connected variable must be verified after the call --- operateShutters.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/operateShutters.py b/operateShutters.py index 3ab1ced..617f7f3 100755 --- a/operateShutters.py +++ b/operateShutters.py @@ -443,12 +443,16 @@ def startPIGPIO(self): try: pi = pigpio.pi() # local GPIO only - self.LogInfo ("pigpio's pi instantiated") + if not pi.connected: + self.LogError("pigpio connection could not be established. Check logs to get more details.") + return False + else: + self.LogInfo("pigpio's pi instantiated.") except Exception as e: start_pigpiod_exception = str(e) - self.LogError ("problem instantiating pi: {}".format(start_pigpiod_exception)) + self.LogError("problem instantiating pi: {}".format(start_pigpiod_exception)) else: - self.LogError ("start pigpiod was unsuccessful.") + self.LogError("start pigpiod was unsuccessful.") return False return True From 0bb4f6358c5bdffd44546947f432564d9b4ec2b9 Mon Sep 17 00:00:00 2001 From: Huan Vu Date: Fri, 24 Jun 2022 21:31:11 -0700 Subject: [PATCH 09/12] Fix config issues with duration for adding and removing shutter --- myconfig.py | 2 +- mywebserver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/myconfig.py b/myconfig.py index dd95289..4681c95 100644 --- a/myconfig.py +++ b/myconfig.py @@ -89,7 +89,7 @@ def LoadConfig(self): # If only one duration is specified, use it for both down and up durations. if len (param1) < 4: param1.append(param1[2]) - self.Shutters[key] = {'name': param1[0], 'code': param2, 'durationDown': int(param1[2]), 'durationUp': int(param1[3]), 'intermediatePosition': param3} + self.Shutters[key] = {'name': param1[0], 'code': param2, 'duration': param1[2], 'durationDown': int(param1[2]), 'durationUp': int(param1[3]), 'intermediatePosition': param3} self.ShuttersByName[param1[0]] = key except Exception as e1: self.LogErrorLine("Missing config file or config file entries in Section Shutters for key "+key+": " + str(e1)) diff --git a/mywebserver.py b/mywebserver.py index 7cf957e..dbf4d5c 100644 --- a/mywebserver.py +++ b/mywebserver.py @@ -202,7 +202,7 @@ def addShutter(self, params): self.config.WriteValue(str(id), str(code), section="ShutterRollingCodes"); self.config.WriteValue(str(id), str(None), section="ShutterIntermediatePositions"); self.config.ShuttersByName[name] = id - self.config.Shutters[id] = {'name': name, 'code': code, 'duration': duration, 'intermediatePosition': None} + self.config.Shutters[id] = {'name': name, 'code': code, 'duration': duration, 'durationDown': int(duration), 'durationUp': int(duration), 'intermediatePosition': None} return {'status': 'OK', 'id': id} def editShutter(self, params): From d1188e50dad3fe1100fc06a86928cdda8337642c Mon Sep 17 00:00:00 2001 From: foormea <48879632+foormea@users.noreply.github.com> Date: Wed, 7 Sep 2022 22:45:58 +0200 Subject: [PATCH 10/12] Update requirements.txt `requests` missing from requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a9cead6..e585876 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ ephem configparser Flask paho-mqtt -pigpio \ No newline at end of file +pigpio +requests From 62438417a0e4b4126eb25ba68a39949b89d3e9f8 Mon Sep 17 00:00:00 2001 From: Michael <38872689+MichaelB2018@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:45:08 -0400 Subject: [PATCH 11/12] Update mymqtt.py compatibility with Home Assistant 2023.08 --- mymqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mymqtt.py b/mymqtt.py index da3c98f..f6f4f72 100644 --- a/mymqtt.py +++ b/mymqtt.py @@ -49,7 +49,7 @@ def __init__(self, shutter, shutterId): self.discovery_msg["position_topic"] = DiscoveryMsg.DISCOVERY_MSG["position_topic"] % shutterId self.discovery_msg["set_position_topic"] = DiscoveryMsg.DISCOVERY_MSG["set_position_topic"] % shutterId self.discovery_msg["unique_id"] = shutterId - self.discovery_msg["device"]["name"] = shutter + self.discovery_msg["device"]["name"] = "Somfy " + shutter.replace('_', ' ').title() self.discovery_msg["device"]["identifiers"] = shutterId def __str__(self): From a96e40a9f5b0c397890ebf0817a9856a646c4941 Mon Sep 17 00:00:00 2001 From: vapouryh <69804041+vapouryh@users.noreply.github.com> Date: Sun, 11 Feb 2024 00:56:15 +0000 Subject: [PATCH 12/12] Alexa Discovery Temp Fix As known, Alexa is unable to discover the blinds when running operateShutters.py -e. At the moment, I created a temporary fix to overcome this by using n8henrie/fauxmo simultaneously with Pi-Somfy. This works by calling the HTTP API from the webserver to open and shut the blinds. You will need to find your shutter id with the networking tools in your web browser when manually operating the blinds via the web UI. The payload will contain the shutters id which you can update in fauxmo_config.json. You will need to reinstall the daemon service to use the new start-up script. To use multiple blinds within the alexa integration, add more devices to the fauxmo_config.json. Hopefully OP implements the up-to-date fauxmo repo by n8henrie and can properly integrate it within the program so the process is much more streamlined and works as it should. Unfortunately, I don't have to time to research further into the project to make this work seamlessly but from what I can gather, Alexa now requires some sort of byte response as mandatory and in it's current configuration, Alexa does not receive a discovery response from the program, it could be because of this? I also saw somewhere that Alexa needs the state of the device - with the current implementation it uses a fake state but when you call Alexa and tell her to open the blinds it will set the device on and off accordingly anyway, it's only really annoying if you use the Alexa app for example and see that the device is 'on' (blinds open) when they are shut; still - turn the device off and on with the app and it works as usual. I did look over and see if there was a state handler within the program but I myself am not very familiarised with OOP and so didn't spend much time investigating. But, for now, this works and is a suitable enough fix for me. Additionally, you will need to alter the n8henrie/fauxmo files. Please see: https://github.com/n8henrie/fauxmo/issues/122 where this seperate issue is documented. That said, I am grateful for this project as I can now control my blinds hands-free. --- .gitignore | 2 ++ fauxmo_config.json | 19 +++++++++++++++++++ shutters.service | 2 +- start.sh | 4 ++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 fauxmo_config.json create mode 100644 start.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..994ca16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.venv +/__pycache__ \ No newline at end of file diff --git a/fauxmo_config.json b/fauxmo_config.json new file mode 100644 index 0000000..36535de --- /dev/null +++ b/fauxmo_config.json @@ -0,0 +1,19 @@ +{ + "FAUXMO": { + "ip_address": "auto" + }, + "PLUGINS": { + "SimpleHTTPPlugin": { + "DEVICES": [ + { + "name": "SOMFY", + "on_cmd": "http://localhost:80/cmd/stop?shutter={SHUTTER_ID}", + "off_cmd": "http://localhost:80/cmd/up?shutter={SHUTTER_ID}", + "method": "POST", + "initial_state": "on", + "use_fake_state": true + } + ] + } + } +} diff --git a/shutters.service b/shutters.service index c0b501f..929c8db 100644 --- a/shutters.service +++ b/shutters.service @@ -5,7 +5,7 @@ After=network-online.target [Service] User=pi -ExecStart=sudo /usr/bin/python3 /home/pi/Pi-Somfy/operateShutters.py -c /home/pi/Pi-Somfy/operateShutters.conf -a -e -m +ExecStart=sudo bash /home/pi/Pi-Somfy/start.sh Environment=PYTHONUNBUFFERED=1 Restart=on-failure Type=exec diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..870b577 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +sudo /usr/bin/python3 /home/pi/Pi-Somfy/operateShutters.py -c /home/pi/Pi-Somfy/operateShutters.conf -a -m & +fauxmo -c /home/pi/Pi-Somfy/fauxmo_config.json -vv \ No newline at end of file