Table of Contents

GW60 Elektronischer Gurtwickler Umbau

Motivation

Will man bei der Hausautomatisierung auch die Rolladen einbinden so bedarf es einer entsprechenden Steuerung. Wer nicht gleich die Rolladen selbst umrüsten möchte, kann auf elektronische Gurtwickler ausweichen. Diese sind ausgereift benötigen aber eine externe Stromversorgung. Es sind mehrere Hersteller auf dem Markt zu finden, die auch eine drahtlose Fernsteuerung im Program haben. Gemeinsam ist fast allen, dass sie auf eigene Standards setzen und eine Zentrale des jeweiligen Herstellers benötigen. Will man das vermeiden muss eine eigene, offene Lösung her.

GW60 ZB40

Ein preiswerter elektronischer Gurtwickler ist der GW60 von SUPERROLLO. Er lässt sich mit einer Funkfernsteuerung (433,92MHz) mit Keeloq Verschlüsselung erweitern. Mit dieser Fernbedienung sind so einzelne Rollos oder auch Gruppen aus der Ferne bedienbar.
Es gibt auch ein paar Umbaulösungen im Internet, die z.B. mit Hilfe eines ESP8266 die Tastendrücke simulieren. Das funktioniert recht zuverlässig und war bei mir auch 2 Jahre im Einsatz. Der Einbau ist aber schon aufwendig und die aktuelle Position wird nicht übermittelt da die Impulsauswertung dafür sehr aufwendig ist.

Der Traum wäre ein Modul, dass in den vorgesehenen Platz des GW60 Gehäuses passt und auch die aktuelle Position übermitteln kann. Ein Rückkanal steht tatsächlich schon an der Modulschnittstelle zur Verfügung, da RxD und TxD des GW60 Controllers an das Empfangsmodul gehen. Die anliegende Spannung von 5V ist aber nur mit deutlich weniger als 100mA belastbar. Da kommt ein ESP8266 eher nicht in Frage.

Bis der Traum sich verwirklichen lies, musste ich auf die neuen Rollos FYRTUR von IKEA mit TRÅDFRI Einbindung warten. Nach etwas Reverse Engineering steht fest, dass die TRÅDFRI Module mit der Rollo-Steuerung seriell kommunizieren und auch gleich Position, Spannung und Strom austauschen. Der Stromverbrauch liegt im Peak-Bereich bei max. 33mA. Das kann der interne Regler des GW60 wohl verkraften.

Hardware

Die erste Aufgabe ist die Anbindung des TRÅDFRI Moduls an den GW60 sodass es mechanisch wie elektrisch wie der Originalempfänger ZB40 ausschaut. Das TRÅDFRI Modul arbeitet mit 3.3V während der GW60 Controller mit 5V versorgt wird. Der Schaltplan muss daher zwei Level-shifter und einen Spannungsregler enthalten. Meine Lösung sieht wie folgt aus:

Schaltplan

Links ist der Anschluss zum GW60 im Original als Federkontakt ausgelegt. Leider habe ich kein Lieferanten für diese Kontakte gefunden und bin deshalb auf POGO Pins ausgewichen. Die gibt es bei Ebay in fast allen Varianten. Hier kommt eine 6mm lange Version zum Einsatz.

POGO Pin

Rechts sind die Anschlüsse für die Modulprogrammierung als Pads ausgeführt. Damit lässt sich TRÅDFRI Modul flashen. Schliesslich will man ja nicht jedesmal eine teure Rollosteuerung von IKEA kaufen um ein Modul für die eigene Lösung zu ergattern. Doch dazu in einem anderen Artikel mehr. Für die Verbindung mit der TRÅDFRI Steuerung ist der Taster und die LED vorgesehen. Einmal angelernt brauchen wir beides nicht mehr. Der zur Verfügung stehende Platz reicht um alle Komponenten unterzubringen.

Damit auch der Deckel aufs Gehäuse passt, sollte das PCB wie beim Original nur 1mm dick sein.

Gehäuse

Mit einem Messschieber ist das Gehäuse schnell in Fusion360 erstellt und sieht dann so aus:

Es lässt sich wie abgebildet im 3D Drucker ohne Stützstrukturen drucken. Der kleine Block rechts dient zum Schutz der POGO Pins und lässt sich auf diese aufschieben.

TRÅDFRI Modul GW60 mit TRÅDFRI Modul

Die PCB Antenne schaut für einen besseren Empfang aus dem Gehäuse raus. Obwohl das Modul bei Montage des GW60 im Gurtkasten verschwindet gab es bei mir keine Verbindungsprobleme zu den TRÅDFRI Tastern oder zum Gateway.

Software

Das TRÅDFRI Modul ist nun zwar jetzt elektrisch und mechanisch mit dem GW60 verbunden, allerdings kann der GW60 Controller noch nichts mit den Daten anfangen. Dazu muss die vorhandene Firmware erweitert werden. Den GW60 steuert ein PIC16F690 mit on-board Programming Interface. Als preiswerter Programmer bietet sich der PICkit3 an, den es auch als China-Nachbau gibt.

PICkit3

Um als erstes die Original Firmware auslesen zu können, müssen wir die GW60 Platine mit dem PICkit3 verbinden. Als sehr hilfreich erweisen sich die Testpads auf der Rückseite des GW60 PCBs. Dort lassen sich leicht die 5 Kabel anlöten.

GW60 Programming

MICROCHIP stellt eine kostenlose IDE mit dem Namen MPLAB X LAB zur Verfügung. Bestandteil dieses Pakets ist die Programmiersoftware MPLAP IPE. Damit lässt sich die Firmware inkl. EEPROM etc. auslesen. Bei meinen GW60 war zum Glück das FLASH des PIC16F690 nicht geschützt.

Erweiterung

Der eigentliche Teil der Arbeit bestand nun wiederum aus Reverse Engineering. Für die Erweiterung ist genug Platz im FLASHdes PIC16F690. Der Bereich 0600h - 07FFh ist ungenutzt. Um die Änderungen an der Original Software gering zu halten, wird die Softwareerweiterung nur an 4 Stellen in den Originalcode eingebettet:

  1. INIT –> wird einmal während der Initialisierung aufgerufen.
  2. ISR –> Erweiteung der Interrupt Service Routine
  3. LOOP –> Main loop
  4. PANEL –> Auswertung der Tastendrücke

Dazu wird der Code an folgenden Stellen ausgetauscht (jeweils ein 16-bit Word):

 ADDR  CODE
------------
            ; ------------------------------------------------------
            if	1	; 1 = include extension
 0304  2609    call	isr_ext	; ### include isr extension
            else
 0304  00AC    movwf	44	; save received byte in 0x2C
            endif
            ; ------------------------------------------------------

            ; ------------------------------------------------------
            if	1	; 1 = include extension
 03D7  260C    call	init_ext	; ### include init extension
            else
 03D7  17B5    bsf	53,7	; ### original code
            endif
            ; ------------------------------------------------------

            ; ------------------------------------------------------
            if	1	; 1 = include extension
 03FD  261D    call	loop_ext	; ### include extension loop
            else
 03FD  0064    clrwdt	; ### original code  
            endif
            ; ------------------------------------------------------

            ; ------------------------------------------------------
            if	1	; 1 = include extension=1
 04C9  2E00    goto	panel_ext	; ### include panel extension
            else
 04C9  0008    return
            endif
            ; -------------------------------------------------------                           

Vorsicht! Die Adressen sind 16-bit Word Adressen. Die jeweils untere Zeile gibt den Originalcode wieder und die darüber liegende den zu patchenden Code. Das gilt natürlich nur für die mir vorliegende Softwareversion. Allerdings war die Software im Gegensatz zum PCB bei allen meinen zu unterschiedlichen Zeiten gekauften GW60 identisch. Durch den Austausch springt der Controller jeweils in unsere Erweiterungssoftware und kommt wieder als wäre nichts geschehen. Damit bleibt die gesamte Funktionalität des GW60 erhalten.

Die Grösse des Erweiterungscodes liegt bei 374 Words und ist hier einfach mal gelistet:

ADDR 0600h - 0775h
==================
7C18 3014 7C10 FC18 B014 FC10 7C1B 2526 0800 AC00 FC17 0800 FC01 C401 8316 E401 9B30 E500 6530 9800
0830 9B00 4030 9900 0330 9A00 8312 B517 0800 8312 0313 FC1F 232E FC13 9226 6400 0800 5626 8316 3608 
E402 031D 2F2E 8312 7C13 FC14 0800 8312 FC1A 362E 0318 2B2E 8312 0800 0318 342E 2B2E 2030 2C06 031D 
0800 8316 3108 8312 E021 8316 3208 8312 E021 8316 3308 8312 E021 8316 3408 8312 E021 8316 3808 8312 
E021 8316 3908 8312 E021 0800 8316 3108 EA00 3208 EB00 3808 EA02 031C EB03 3908 EB02 FF30 6B06 031D 
672E EA01 EB01 E701 2B27 3108 EC00 3208 ED00 3308 EC02 031C ED03 3408 ED02 5C27 6A08 E400 8312 0800 
0030 E021 FF30 E021 D830 E021 6430 E021 8316 FF30 8312 E021 8316 5308 8312 E021 8316 6408 8312 E021 
8316 6508 8312 E021 8312 0800 4408 031D 9A2E 2C08 031D C72E C40A C72E 4403 031D A32E 2C0A 031D C62E 
982E C401 C72E 0230 4406 031D AC2E 9A30 2C06 031D C62E 982E 0630 4402 0318 B72E 0330 4406 031D B72E 
2C08 C000 982E 0430 4406 031D BE2E 2C08 C100 982E 0530 4406 031D C62E 2C08 C200 C826 C72E C401 0800 
C401 0A30 4006 031D EB2E DD30 4106 031D D72E D730 4206 031D D72E 0327 0800 EE30 4106 031D E12E E430 
4206 031D E12E 0627 0800 CC30 4106 031D EB2E C630 4206 031D EB2E 0927 0800 CC30 4006 031D F82E CC30 
4106 031D F82E 4208 031D F82E 0C27 0800 DD30 4006 031D 022F DD30 4106 4206 031D 022F 0F27 0800 7C14 
7C13 0800 FC14 7C13 0800 FC14 7C13 0800 5626 7826 0800 6430 4106 0319 062F 4108 0319 032F 4108 8316 
B600 8312 5626 8316 3608 E402 0319 0800 7C17 031C 272F 8312 FC16 7C14 0800 8312 FC12 FC14 0800 0310 
EA0D EB0D E701 E70D EA0D EB0D E70D 6708 E800 6B08 ED00 6A08 EC00 0310 EC0D ED0D E80D EC0D ED0D E80D 
EC0D ED0D E80D 6C08 EA07 6D08 0318 6D0F EB07 6808 0318 680F E707 0310 EC0D ED0D E80D 6C08 EA07 6D08 
0318 6D0F EB07 6808 0318 680F E707 0800 E601 E901 1830 CA00 6A0D EB0D E70D E90D E60D EA0D 6C08 E902 
6D08 031C 6D0F E602 0318 6A14 6A18 732F E607 6C08 E907 CA0B 602F 0800

Die Erweiterung in den vier oben genannten Bereichen haben folgende Funktion:

INIT

Hier werden die eigenen Variablen initialisiert und die Baudrate auf 2400 festgelegt. Die Ausführung erfolgt am Ende der INIT Sequenz der Originalsoftware.

ISR

Die Originalsoftware empfängt schon Bytes von der UART. Hier müssen wir nur noch mit einem Flag der Erweiterung signalisieren, dass ein neues Byte empfangen wurde. Auch hier klingt sich die Erweiterung am Ende der ISR ein.

PANEL

Hier werden die Tastendrücke simuliert wenn ein entsprechendes Kommando vom TRÅDFRI Modul kam. Gleichzeitig wird beim Lauf die STOP Taste simuliert, wenn die vorgewählte Zwischenposition erreicht ist.

LOOP

Hier erfolgt züglich die Auswertung eines empfangenden Frames und bei Anfrage wird der Statusframe mit der aktuellen Position gesendet.

Das ASM File der Erweiterung beinhaltet mehr Erklärungen:

GW60_ext.asm
; File:	    GW60_ext.asm
; Target:   PIC16F690
; Author:   DIRB
; Date:     2021-01-15
; Compiler: pic-as (v2.31)
; IDE:      MPLABX v5.45
; ===========================================================================   
; ### Extension code for GW60 firmware to support serial TRÅDFRI protocol ###
; ===========================================================================  
; Free code area in original F/W: 
;   BLOCK0  0x0600 to 0x07FF
; ---------------------------------------------------------------------------
; Free RAM area in original F/W: 
;   BANK0   0x40-42, 0x44, 0x7C, 0x7D
;   BANK1   0xB6, 0xCA, 0xE2-E8, (E9), 0xEA-ED, 0x7C, 0xFD
; ---------------------------------------------------------------------------
; Used RAM in extention:
;
;  BANK0:
;   RAM_ext_stat (0x7C)
;    bit 0:	1 = UP command
;    bit 1:	1 = DOWN command
;    ...
;    bit 5:     0 = DOWN, 1 = UP  direction of new position
;    bit 6:     1 = new position to go to
;    bit 7:	1 = new byte received
;   RAM_rxd_byte (0x7D)	    stores received byte
;   RAM_frm_cnt	(0x44)	    counts received byte of frame
;   RAM_cmd1 	(0x40)	    individual command byte 1
;   RAM_cmd2	(0x41)	    individual command byte 2
;   RAM_cmd3	(0x42)	    individual command byte 3 (check sum)
;
;  BANK1:
;   RAM_newpos (0xB6)		    new position in percent
;   RAM_div16_lp (0xCA)		    DIV16 loop count
;   RAM_p_stat (0xE4)		    actual position in percent
;   RAM_chksum (0xE5)		    checksum of status frame
;   RAM_div16_rem0 (0xE6)	    DIV16 REMB0
;   RAM_div16_tmp1_mm (0xE7)	    DIV16 TEMP1 (MMSB)
;   RAM_div16_tmp2_mm (0xE8)	    DIV16 TEMP2 (MMSB)
;   RAM_div16_rem1 (0xE9)	    DIV16 REMB1
;   RAM_div16_tmp1_l (0xEA)	    DIV16 TEMP1 (LSB)
;   RAM_div16_tmp1_m (0xEB)	    DIV16 TEMP1 (MSB)
;   RAM_div16_tmp2_l (0xEC)	    DIV16 TEMP2 (LSB)
;   RAM_div16_tmp2_m (0xED)	    DIV16 TEMP2 (MSB)    
;    
; RAM used by original F/W:
;   byte_DATA_2C	 RxD from UART
;   byte_DATA_30	 button counter flags (short touch)
;			    0x01 = DOWN
;			    0x02 = UP
;			    0x04 = SUN
;			    0x08 = CLOCK
;			    0x10 = SET
;			    0x20 = REED
;   byte_DATA_53	 actual current
;   byte_DATA_B1	 upper limit (LSB)	
;   byte_DATA_B2	 upper limit (MSB)	
;   byte_DATA_B3	 lower limit (LSB)	
;   byte_DATA_B4	 lower limit (MSB)
;   byte_DATA_B8	 actual position (LSB)	
;   byte_DATA_B9	 actual position (MSB)
; --------------------------------------------------------------------------- 
    PROCESSOR 16F690
    #include	<xc.inc>
    #include	"RAM_def.INC"    
; ---------------------------------------------------------------------------  
; Globals    
    GLOBAL isr_ext, init_ext, loop_ext, panel_ext    
    EXTRN sub_send_byte
; ---------------------------------------------------------------------------   
    PSECT ext,class=CODE,abs,reloc=2,delta=2
    ORG	0x0600
 
    IF EXTEND=1
 
; ===========================================================================
; called by F/W at each button read out from panel 
; ---------------------------------------------------------------------------    
panel_ext:
	btfsc	RAM_ext_stat, 0	; UP command received ?	
	bsf	byte_DATA_30, 0	; then simulate UP button pressed 
	bcf	RAM_ext_stat, 0
	btfsc	RAM_ext_stat, 1	; DOWN command received ?
	bsf	byte_DATA_30, 1 ; then simiulate DOWN button pressed   
	bcf	RAM_ext_stat, 1
	btfsc	RAM_ext_stat, 6	; new position to reach  ?
	call	on_pos		; check if position reached
; ---------------------------------------------------------------------------
	return			; (replaced instruction)
; ---------------------------------------------------------------------------
 
; ===========================================================================	
; called from interrupt service routine 
; --------------------------------------------------------------------------- 
isr_ext:
; ---------------------------------------------------------------------------
	movwf	byte_DATA_2C	; save received byte in 0x2C (replaced instr)
; ---------------------------------------------------------------------------
	bsf	RAM_ext_stat, 7	; set flag for new byte received
	return
 
; ===========================================================================
; called by firmware once at the end of init phase
; --------------------------------------------------------------------------- 
init_ext:
 
	clrf	RAM_ext_stat	; clear extension STATUS
	clrf	RAM_frm_cnt	; clear frame counter
	bsf	RP0
	clrf	RAM_p_stat	; status pos = 0
	movlw	0x9B		; status check sum 
	movwf	RAM_chksum
 
	;movlw	0x0f		; ====== just for devopment version ======
	;movwf	OSCTUNE		; ====== just for devopment version ======
	movlw	0x65		; setup serial I/F
	movwf	TXSTA		; TX9+TXEN+BRGH+TX9D = 1
	movlw	0x08
	movwf	BAUDCTL		; BRG16 = 1
	movlw	0x40		; Baudrate 0x340 = 832 -> 2401 Baud
	movwf	SPBRG		
	movlw	0x03		; Baudrate High
	movwf	SPBRGH		
 
	;movlw	0xA5		; send test byte
	;call	sub_send_byte	; transmit byte
 
	bcf	RP0	
init_exit:
; ---------------------------------------------------------------------------    
	bsf	byte_DATA_35, 7 ; replaced instruction from original code
; ---------------------------------------------------------------------------
	return
 
; ===========================================================================
; called from main loop
; ---------------------------------------------------------------------------
loop_ext:   
	bcf	RP0
	bcf	RP1
 
	; new byte received ?
	btfss	RAM_ext_stat, 7	; new byte received ?
	goto	loop_exit	; NO!
	bcf	RAM_ext_stat, 7
	call	receive_frame	; decode frame byte by byte 
 
	;##### 6-byte RAM monitor
	;call	monitor
loop_exit:
; ---------------------------------------------------------------------------
	clrwdt			; replaced instruction from original code
; ---------------------------------------------------------------------------
	return	
 
 
; ===========================================================================
; Subroutines	
; ===========================================================================
 
; --------------------------------------------------------------------	
; check if on position
on_pos:
	call	calcpos			
	bsf	RP0
	movf	RAM_newpos,w
	subwf	RAM_p_stat
	btfss	ZERO		; already on position ?	
	goto	notpos
reached:
	bcf	RP0
	bcf	RAM_ext_stat, 6	; clear positioning bit	
	bsf	RAM_ext_stat, 1 ; press button to stop	
	return
 
notpos: bcf	RP0	
	btfsc	RAM_ext_stat, 5	; UP direction ?
	goto	its_up
	btfsc	CARRY
	goto	reached	
 
posdone:	
	bcf	RP0
	return
 
its_up:	btfsc	CARRY
	goto	posdone
	goto	reached
 
; --------------------------------------------------------------------	
; debug output only
;
monitor:
	movlw	0x20		; wait for SPACE key
	xorwf	byte_DATA_2C,w
	btfss	STATUS,2    
	return
 
    	bsf	RP0
	movf	byte_DATA_B1,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	bsf	RP0
    	movf	byte_DATA_B2,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
    	bsf	RP0
	movf	byte_DATA_B3,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	bsf	RP0
    	movf	byte_DATA_B4,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
    	bsf	RP0
	movf	byte_DATA_B8,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	bsf	RP0
    	movf	byte_DATA_B9,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	return
 
; --------------------------------------------------------------------			
; calc position in %
;
calcpos:
	; upper limit - actual position
    	bsf	RP0
	movf	byte_DATA_B1,w	; copy upper limit to temp1
	movwf	RAM_div16_tmp1_l
	movf	byte_DATA_B2,w
	movwf	RAM_div16_tmp1_m
	MOVF	byte_DATA_B8,W	; substraction
	SUBWF	RAM_div16_tmp1_l,F
	BTFSS	CARRY  
	DECF	RAM_div16_tmp1_m,F
	MOVF	byte_DATA_B9,W
	SUBWF	RAM_div16_tmp1_m,F
	; check if actual position was higher then the upper limit 
	movlw	0xFF
	xorwf	RAM_div16_tmp1_m,w
	btfss	ZERO	
	goto	noff
	clrf	RAM_div16_tmp1_l
	clrf	RAM_div16_tmp1_m
	; upper limit - lower limit = range
noff:
	clrf	RAM_div16_tmp1_mm	; temp 1 MMSB
 
	; multiply with 100
	call	mul100
 
	movf	byte_DATA_B1,w	; copy upper limit to temp2
	movwf	RAM_div16_tmp2_l
	movf	byte_DATA_B2,w
	movwf	RAM_div16_tmp2_m
	MOVF	byte_DATA_B3,W	; substraction
	SUBWF	RAM_div16_tmp2_l,F
	BTFSS	CARRY  
	DECF	RAM_div16_tmp2_m,F
	MOVF	byte_DATA_B4,W
	SUBWF	RAM_div16_tmp2_m,F
 
	;devide by range 
	call	div16bit	
 
	movf	RAM_div16_tmp1_l,w
	movwf	RAM_p_stat
 
    	bcf	RP0
	return
 
; --------------------------------------------------------------------
; send STATUS:
; 00 FF D8 64 FF 00 00 9B
;              +-------------->	Voltage		
;	          +----------->	Current		
;		     +-------->	Position 0-100%	 (0x00 - 0x64)
;                       +----->	Checksum		
send_status:
    	movlw	0x00		; send test byte
	call	sub_send_byte	; transmit byte
	movlw	0xFF		; send test byte
	call	sub_send_byte	; transmit byte	
	movlw	0xD8		; send test byte
	call	sub_send_byte	; transmit byte
	movlw	0x64		; send test byte
	call	sub_send_byte	; transmit byte
	bsf	RP0
	movlw	0xFF		; max voltage level
	bcf	RP0
	call	sub_send_byte	; transmit byte	
	bsf	RP0
	movf	byte_DATA_53,w	; get actual current
	bcf	RP0
	call	sub_send_byte	; transmit byte	
	bsf	RP0
	movf	RAM_p_stat,w
	bcf	RP0
	call	sub_send_byte	; transmit byte
	bsf	RP0
	movf	RAM_chksum,w
	bcf	RP0
	call	sub_send_byte	; transmit byte
	bcf	RP0
	return
 
; --------------------------------------------------------------------	
; IKEA Tradfri commands:
; UP		0x00 0xFF 0x9A 0x0A 0xDD 0xD7	00FF9A0ADDD7
; DOWN		0x00 0xFF 0x9A 0x0A 0xEE 0xE4   00FF9A0AEEE4
; STOP		0x00 0xFF 0x9A 0x0A 0xCC 0xC6	00FF9A0ACCC6
; GOTO		0x00 0xFF 0x9A 0XDD (POS) (CS)	00FF9ADD32EF
; Status reqest	0x00 0xFF 0x9A 0xCC 0xCC 0x00	00FF9ACCCC00
receive_frame:	
; if (RAM_frm_cnt == 0)
	movf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1650	
; if (RAM_rxd_byte == 0x00)
	movf	byte_DATA_2C,w
	btfss	STATUS,2
	goto	loc_142
loc_1648:	
    ; RAM_frm_cnt++
	incf	RAM_frm_cnt
	goto	loc_142
; else if (RAM_frm_cnt == 1)
loc_1650:
	decf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1658
; if (RAM_rxd_byte == 0xFF)
	incf	byte_DATA_2C,w
	btfss	STATUS,2
	goto	loc_1682
	goto	loc_1648
loc_1656:	
; else;main.c: 43: RAM_frm_cnt = 0
	clrf	RAM_frm_cnt
	goto	loc_142
loc_1658:	
; else if (RAM_frm_cnt == 2)
	movlw	2
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1666
; if (RAM_rxd_byte == 0x9A)
	movlw	0x9A
	xorwf	byte_DATA_2C,w
	btfss	STATUS,2
	goto	loc_1682
	goto	loc_1648
loc_1666:	
; else if (RAM_frm_cnt < 6)	
	movlw	6
	subwf	RAM_frm_cnt,w
	BTFSC	CARRY
	goto	loc_1672
loc_1668:	
; if (RAM_frm_cnt == 3)
	movlw	3
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1672	
; RAM_cmd1 = RAM_rxd_byte
	movf	byte_DATA_2C,w
	movwf	RAM_cmd1
	goto	loc_1648
loc_1672:	
; if (RAM_frm_cnt == 4)
	movlw	4
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1676
; RAM_cmd2 = RAM_rxd_byte;
	movf	byte_DATA_2C,w
	movwf	RAM_cmd2
	goto	loc_1648
loc_1676:	
; if (RAM_frm_cnt == 5) 
	movlw	5
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1682	
; RAM_cmd3 = RAM_rxd_byte;
	movf	byte_DATA_2C,w
	movwf	RAM_cmd3
; decode_frame();
	call	decode_frame
	goto	loc_142
loc_1682:	
; else RAM_frm_cnt = 0;
	clrf	RAM_frm_cnt
loc_142:	
	return
 
; --------------------------------------------------------------------	
decode_frame:
; RAM_frm_cnt = 0;
	clrf	RAM_frm_cnt
; if (RAM_cmd1 == 0x0A) {
	movlw	0x0A
	xorwf	RAM_cmd1,w
	btfss	STATUS,2
	goto	loc_1767	
; if (RAM_cmd2 == 0xDD && RAM_cmd3 == 0xD7) UP();
	movlw	0xDD
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1755
	movlw	0xD7
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1755
	call	cmd_up
	return
loc_1755:	
; if (RAM_cmd2 == 0xEE && RAM_cmd3 == 0xE4) DOWN();
	movlw	0xEE
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1761
	movlw	0xE4
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1761
	call	cmd_down
	return
loc_1761:	
; if (RAM_cmd2 == 0xCC && RAM_cmd3 == 0xC6) STOP();
	movlw	0xCC
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1767
	movlw	0xC6
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1767
	call	cmd_stop
	return
loc_1767:	
; } if (RAM_cmd1 == 0xCC) {
	movlw	0xCC
	xorwf	RAM_cmd1,w
	btfss	STATUS,2
	goto	loc_1775	
; if (RAM_cmd2 == 0xCC && RAM_cmd3 == 0x00) STAT();
	movlw	0xCC
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1775	
	movf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1775
	call	cmd_stat
	return
loc_1775:	
; } if (RAM_cmd1 == 0xDD) {
	movlw	0xDD
	xorwf	RAM_cmd1,w
	btfss	STATUS,2
	goto	loc_1781
; if ((RAM_cmd2 ^ 221) == RAM_cmd3) GOTO();
	movlw	0xDD
	xorwf	RAM_cmd2,w
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1781
	call	cmd_goto
loc_1781:	
	return    
 
; --------------------------------------------------------------------	
; Command: UP
cmd_up:
	bsf	RAM_ext_stat, 0  
	bcf	RAM_ext_stat, 6	; clear new position move
	;movlw	'U'		; send test byte
	;call	sub_send_byte	; transmit byte
	return    
; --------------------------------------------------------------------	
; Command: DOWN
cmd_down:
	bsf	RAM_ext_stat, 1 
	bcf	RAM_ext_stat, 6	; clear new position move
	;movlw	'D'		; send test byte
	;call	sub_send_byte	; transmit byte
	return    
; --------------------------------------------------------------------	
; Command: STOP
cmd_stop:
	bsf	RAM_ext_stat, 1
	bcf	RAM_ext_stat, 6	; clear new position move
	;movlw	'S'		; send test byte
	;call	sub_send_byte	; transmit byte 
	return
; --------------------------------------------------------------------	
; Command: STATUS
cmd_stat:
	call	calcpos
	call	send_status
	return
; --------------------------------------------------------------------	
; Command: GOTO position
; new position in RAM_cmd2
cmd_goto:
	;movlw	'P'		; send test byte
	;call	sub_send_byte	; transmit byte  
	;movf	RAM_cmd2,w	; send test byte
	;call	sub_send_byte	; transmit byte  	
	movlw	0x64
	xorwf	RAM_cmd2,w
	btfsc	STATUS,2
	goto	cmd_down	; new pos = 100% -~ DOWN	
	movf	RAM_cmd2,w
	btfsc	STATUS,2
	goto	cmd_up		; new pos = 0% -~ UP
 
	movf	RAM_cmd2,w	; copy new position
    	bsf	RP0
	movwf	RAM_newpos
	bcf	RP0		
	call	calcpos
	bsf	RP0
	movf	RAM_newpos,w
	subwf	RAM_p_stat
	btfsc	ZERO		; already on position
	return	    
	bsf	RAM_ext_stat, 6	; set new position move
	btfss	CARRY
	goto	go_dn
 
go_up:	bcf	RP0
	bsf	RAM_ext_stat, 5	; direction is up
	bsf	RAM_ext_stat, 0 
	return
 
go_dn:	bcf	RP0
	bcf	RAM_ext_stat, 5	; direction is down
	bsf	RAM_ext_stat, 1 	
	return    
 
; --------------------------------------------------------------------	
; Multiplication * 100
; Author: Generator
; From: http://www.piclist.com/techref/piclist/codegen/constdivmul.htm
;
; ALGORITHM:
; Clear accumulator
; Add input * 64 to accumulator
; Add input * 32 to accumulator
; Add input * 4 to accumulator
; Move accumulator to result
; Approximated constant: 100, Error: 0 %
;    Input: RAM_div16_tmp1_l/EB, 16 bits
;    Output: RAM_div16_tmp1_l/EB + RAM_div16_tmp1_mm, 23 bits
; Code size: 48 instructions
mul100:
;shift accumulator left 2 times
	bcf	CARRY
	rlf	RAM_div16_tmp1_l, f
	rlf	RAM_div16_tmp1_m, f
	clrf	RAM_div16_tmp1_mm
	rlf	RAM_div16_tmp1_mm, f
	rlf	RAM_div16_tmp1_l, f
	rlf	RAM_div16_tmp1_m, f
	rlf	RAM_div16_tmp1_mm, f
;copy accumulator to temporary
	movf	RAM_div16_tmp1_mm, w
	movwf	RAM_div16_tmp2_mm
	movf	RAM_div16_tmp1_m, w
	movwf	RAM_div16_tmp2_m
	movf	RAM_div16_tmp1_l, w
	movwf	RAM_div16_tmp2_l
;shift temporary left 3 times
	bcf	CARRY
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
;add temporary to accumulator
	movf	RAM_div16_tmp2_l, w
	addwf	RAM_div16_tmp1_l, f
	movf	RAM_div16_tmp2_m, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_m, w
	addwf	RAM_div16_tmp1_m, f
	movf	RAM_div16_tmp2_mm, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_mm, w
	addwf	RAM_div16_tmp1_mm, f
;shift temporary left 1 times
	bcf	CARRY
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
;add temporary to accumulator
	movf	RAM_div16_tmp2_l, w
	addwf	RAM_div16_tmp1_l, f
	movf	RAM_div16_tmp2_m, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_m, w
	addwf	RAM_div16_tmp1_m, f
	movf	RAM_div16_tmp2_mm, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_mm, w
	addwf	RAM_div16_tmp1_mm, f
	return
; --------------------------------------------------------------------		
; Division 24bit by 16bit
; Author: Nikolai Golovchenko
; From: http://www.piclist.com/techref/microchip/math/div/24by16.htm
;
;Inputs:
;   Dividend - RAM_div16_tmp1_mm:RAM_div16_tmp1_m:RAM_div16_tmp1_l
;   Divisor  - RAM_div16_tmp2_m:RAM_div16_tmp2_l
;Temporary:
;   Counter  - RAM_div16_lp
;   Remainder- RAM_div16_rem0:RAM_div16_rem1
;Output:
;   Quotient - RAM_div16_tmp1_mm:RAM_div16_tmp1_m:RAM_div16_tmp1_l
div16bit:
        CLRF	RAM_div16_rem0
        CLRF	RAM_div16_rem1
        MOVLW	24
        MOVWF	RAM_div16_lp
LOOPU2416:
        RLF	RAM_div16_tmp1_l, W	;shift dividend left to move next bit to remainder
        RLF	RAM_div16_tmp1_m, F	;
        RLF	RAM_div16_tmp1_mm, F	;
 
        RLF	RAM_div16_rem1, F	;shift carry (next dividend bit) into remainder
        RLF	RAM_div16_rem0, F
 
        RLF	RAM_div16_tmp1_l, F	;finish shifting the dividend and save  carry in RAM_div16_tmp1_l.0,
                                ;since remainder can be 17 bit long in some cases
                                ;(e.g. 0x800000/0xFFFF). This bit will also serve
                                ;as the next result bit.
 
        MOVF	RAM_div16_tmp2_l, W	;substract divisor from 16-bit remainder
        SUBWF	RAM_div16_rem1, F	;
        MOVF	RAM_div16_tmp2_m, W	;
        BTFSS	CARRY	    	;
        INCFSZ	RAM_div16_tmp2_m, W	;
        SUBWF	RAM_div16_rem0, F	;
;here we also need to take into account the 17th bit of remainder, which
;is in RAM_div16_tmp1_l.0. If we don't have a borrow after subtracting from lower
;16 bits of remainder, then there is no borrow regardless of 17th bit 
;value. But, if we have the borrow, then that will depend on 17th bit 
;value. If it is 1, then no final borrow will occur. If it is 0, borrow
;will occur. These values match the borrow flag polarity.
        BTFSC	CARRY           ;if no borrow after 16 bit subtraction
        BSF	RAM_div16_tmp1_l, 0	;then there is no borrow in result. Overwrite
                                ;RAM_div16_tmp1_l.0 with 1 to indicate no
                                ;borrow.
                                ;if borrow did occur, RAM_div16_tmp1_l.0 already
                                ;holds the final borrow value (0-borrow,
                                ;1-no borrow)
        BTFSC	RAM_div16_tmp1_l, 0	;if no borrow after 17-bit subtraction
        GOTO	UOK46LL		;skip remainder restoration.
 
        ADDWF	RAM_div16_rem0, F	;restore higher byte of remainder. (w 
                                ;contains the value subtracted from it
                                ;previously)
        MOVF	RAM_div16_tmp2_l, W	;restore lower byte of remainder
        ADDWF	RAM_div16_rem1, F	;
UOK46LL:
        DECFSZ	RAM_div16_lp, f	;decrement counter
        GOTO	LOOPU2416	;and repeat the loop if not zero.
        RETURN
 
; ===========================================================================	
 
	ENDIF
 
	END

Nach dem Patch der Originalsoftware verhält sich der GW60 wie ein IKEA Rollo. Es kann über die TRÅDFRI Taster und das Gateway bedient werden. Es gibt die aktuelle Position wieder und kann auch eine bestimme Position anfahren. Ist das Gateway mit z.B. Siri verbunden, reicht morgens wie bei mir ein “Hey Siri, alle Rolladen öffnen” und schon wird es hell in der Wohnung.

Fazit

Der Umbau ist bei mir seit April 2020 ohne Probleme im Einsatz. Eine kleine Einschraenkung ergibt sich nur bei manueller Betätigung: dann wird die aktuelle Position erst bei dem nächsten Funkbefehl aktualisiert.
Falls man doch nochmal die Software aktualisieren muss habe ich das Programming Interface nach draussen gelegt. Ist aber sicher nur nice-to-have…

Downloads

Spenden

Wenn ihr meine Arbeit unterstützen wollt, so könnt ihr mir gerne einen Cappuccino oder so spenden: .