===== 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. {{ :tech:universalgurtwickler_ap-frontal.jpg?nolink&200|GW60}} {{:tech:universalhandsender-frontal-rgb.jpg?nolink&200 |ZB40}} Ein preiswerter elektronischer Gurtwickler ist der [[https://www.superrollo-online.de/elektronische-rollladengurtwickler/superrollo-gw60/|GW60]] von SUPERROLLO. Er lässt sich mit einer [[https://www.superrollo-online.de/elektronische-rollladengurtwickler/funkset-zb40/|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 {{https://www.hempel-online.de/umbau-superrollo-gw60-mit-esp8266.html|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 [[https://www.ikea.com/de/de/p/fyrtur-verdunklungsrollo-kabellos-batteriebetrieben-grau-10408206/|FYRTUR]] von IKEA mit [[de:tech:tradfri|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: {{ :tech:schematic_gw60_zigbee.png?direct&600 |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 [[https://www.ebay.de/itm/100x-vergoldete-6mm-Kupfer-Nadeln-Sonden-Spring-Pogo-Pin-Anschluss/372718361027?hash=item56c7bed9c3:g:TvkAAOSwXFldNB8B|Ebay]] in fast allen Varianten. Hier kommt eine 6mm lange Version zum Einsatz. {{ :tech:pogo_pin.png?direct&100|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 [[de:tech:tradfri_mod|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. {{ :tech:gw60_zigbee_pcb.png?direct&200 |}} 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: {{ :de:tech:gw60_zigbee_cover.png?nolink&400 |}} 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. {{:tech:gw60_zigbee_modul.jpg?direct&200|TRÅDFRI Modul}} {{:tech:gw60_all.jpeg?direct&200|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. {{ :tech:pickit3.png?direct&200 |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. {{ :tech:gw60_pickit3.png?direct&400 |GW60 Programming}} MICROCHIP stellt eine kostenlose IDE mit dem Namen [[https://www.microchip.com/en-us/development-tools-tools-and-software/mplab-x-ide|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: - INIT --> wird einmal während der Initialisierung aufgerufen. - ISR --> Erweiteung der Interrupt Service Routine - LOOP --> Main loop - 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: ; 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 #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... {{ :tech:gw60_pgm_if.jpeg?direct&400 |}} ==== Downloads ==== * {{:tech:GW60_ZIGBEE_BOM.pdf | Bestückungsliste (BOM)}} * {{:tech:GW60-ZIGBEE.brd.zip |PCB}} Eagle format (.brd) * {{:tech:GW60_Zigbee v14.stl.zip| Gehäuse}} (STL Datei) ==== Spenden ==== Wenn ihr meine Arbeit unterstützen wollt, so könnt ihr mir gerne einen Cappuccino oder so spenden: .