From 3670a2f19fde8df221093e612d8082b0e9204ea0 Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Mon, 4 Dec 2023 15:09:03 -0500 Subject: [PATCH 1/2] Support Mode 7 IR Transmission As described in "LEGO Power Functions RC" PDF https://www.philohome.com/pf/pf.htm --- buildhat/colordistance.py | 334 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 334 insertions(+) diff --git a/buildhat/colordistance.py b/buildhat/colordistance.py index 3447e17..52cae03 100644 --- a/buildhat/colordistance.py +++ b/buildhat/colordistance.py @@ -25,6 +25,9 @@ def __init__(self, port): self.mode(6) self.avg_reads = 4 self._old_color = None + self._ir_channel = 0x0 + self._ir_address = 0x0 + self._ir_toggle = 0x0 def segment_color(self, r, g, b): """Return the color name from HSV @@ -197,6 +200,337 @@ def wait_for_new_color(self): self.callback(None) return self._old_color + @property + def ir_channel(self): + return self._ir_channel + + @ir_channel.setter + def ir_channel(self, channel=1): + """ + Set the IR channel for RC Tx + + :param channel: 1-4 indicating the selected IR channel on the reciever + """ + check_chan = channel + if check_chan > 4: + check_chan = 4 + elif check_chan < 1: + check_chan = 1 + # Internally: 0-3 + self._ir_channel = int(check_chan)-1 + + @property + def ir_address(self): + """ + IR Address space of 0x0 for default PoweredUp or 0x1 for extra space + """ + return self._ir_address + + def toggle_ir_toggle(self): + """ + Toggle the IR toggle bit + + """ + # IYKYK, because the RC documents are not clear + if self._ir_toggle: + self._ir_toggle = 0x0 + else: + self._ir_toggle = 0x1 + return self._ir_toggle + + def send_ir_sop(self, port, mode): + """ + Send an IR message via Power Functions RC Protocol in Single Output PWM mode + https://www.philohome.com/pf/pf.htm + + Port B is blue + + Valid values for mode are: + 0x0: Float output + 0x1: Forward/Clockwise at speed 1 + 0x2: Forward/Clockwise at speed 2 + 0x3: Forward/Clockwise at speed 3 + 0x4: Forward/Clockwise at speed 4 + 0x5: Forward/Clockwise at speed 5 + 0x6: Forward/Clockwise at speed 6 + 0x7: Forward/Clockwise at speed 7 + 0x8: Brake (then float v1.20) + 0x9: Backwards/Counterclockwise at speed 7 + 0xA: Backwards/Counterclockwise at speed 6 + 0xB: Backwards/Counterclockwise at speed 5 + 0xC: Backwards/Counterclockwise at speed 4 + 0xD: Backwards/Counterclockwise at speed 3 + 0xE: Backwards/Counterclockwise at speed 2 + 0xF: Backwards/Counterclockwise at speed 1 + + :param port: 'A' or 'B' + :param mode: 0-15 indicating the port's mode to set + """ + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_single_output = 0x4 + ir_mode = ir_mode_single_output + + so_mode_pwm = 0x0 + so_mode = so_mode_pwm + + output_port_a = 0x0 + output_port_b = 0x1 + + output_port = None + if port == 'A' or port == 'a': + output_port = output_port_a + elif port == 'B' or port == 'b': + output_port = output_port_b + else: + return False + + ir_mode = ir_mode | (so_mode << 1) | output_port + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + # Mode range checked here + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def send_ir_socstid(self, port, mode): + """ + Send an IR message via Power Functions RC Protocol in Single Output Clear/Set/Toggle/Increment/Decrement mode + https://www.philohome.com/pf/pf.htm + + Valid values for mode are: + 0x0: Toggle full Clockwise/Forward (Stop to Clockwise, Clockwise to Stop, Counterclockwise to Clockwise) + 0x1: Toggle direction + 0x2: Increment numerical PWM + 0x3: Decrement numerical PWM + 0x4: Increment PWM + 0x5: Decrement PWM + 0x6: Full Clockwise/Forward + 0x7: Full Counterclockwise/Backward + 0x8: Toggle full (defaults to Forward, first) + 0x9: Clear C1 (C1 to High) + 0xA: Set C1 (C1 to Low) + 0xB: Toggle C1 + 0xC: Clear C2 (C2 to High) + 0xD: Set C2 (C2 to Low) + 0xE: Toggle C2 + 0xF: Toggle full Counterclockwise/Backward (Stop to Clockwise, Counterclockwise to Stop, Clockwise to Counterclockwise) + + :param port: 'A' or 'B' + :param mode: 0-15 indicating the port's mode to set + """ + + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_single_output = 0x4 + ir_mode = ir_mode_single_output + + so_mode_cstid = 0x1 + so_mode = so_mode_cstid + + output_port_a = 0x0 + output_port_b = 0x1 + + output_port = None + if port == 'A' or port == 'a': + output_port = output_port_a + elif port == 'B' or port == 'b': + output_port = output_port_b + else: + return False + + ir_mode = ir_mode | (so_mode << 1) | output_port + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + # Mode range checked here + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def send_ir_combo_pwm(self, port_b_mode, port_a_mode): + """ + Send an IR message via Power Functions RC Protocol in Combo PWM mode + https://www.philohome.com/pf/pf.htm + + Valid values for the modes are: + 0x0 Float + 0x1 PWM Forward step 1 + 0x2 PWM Forward step 2 + 0x3 PWM Forward step 3 + 0x4 PWM Forward step 4 + 0x5 PWM Forward step 5 + 0x6 PWM Forward step 6 + 0x7 PWM Forward step 7 + 0x8 Brake (then float v1.20) + 0x9 PWM Backward step 7 + 0xA PWM Backward step 6 + 0xB PWM Backward step 5 + 0xC PWM Backward step 4 + 0xD PWM Backward step 3 + 0xE PWM Backward step 2 + 0xF PWM Backward step 1 + + :param port_b_mode: 0-15 indicating the command to send to port B + :param port_a_mode: 0-15 indicating the command to send to port A + """ + + escape_combo_pwm = 0x1 + escape = escape_combo_pwm + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + + # Port modes are range checked here + return self._send_ir_nibbles(nibble1, port_b_mode, port_a_mode) + + def send_ir_combo_direct(self, port_b_output, port_a_output): + """ + Send an IR message via Power Functions RC Protocol in Combo Direct mode + https://www.philohome.com/pf/pf.htm + + Valid values for the output variables are: + 0x0: Float output + 0x1: Clockwise/Forward + 0x2: Counterclockwise/Backwards + 0x3: Brake then float + + :param port_b_output: 0-3 indicating the output to send to port B + :param port_a_output: 0-3 indicating the output to send to port A + """ + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_combo_direct = 0x1 + ir_mode = ir_mode_combo_direct + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + if port_b_output > 0x3 or port_a_output > 0x3: + return False + if port_b_output < 0x0 or port_a_output < 0x0: + return False + + nibble3 = (port_b_output << 2) | port_a_output + + return self._send_ir_nibbles(nibble1, nibble2, nibble3) + + def send_ir_extended(self, mode): + """ + Send an IR message via Power Functions RC Protocol in Extended mode + https://www.philohome.com/pf/pf.htm + + Valid values for the mode are: + 0x0: Brake Port A (timeout) + 0x1: Increment Speed on Port A + 0x2: Decrement Speed on Port A + + 0x4: Toggle Forward/Clockwise/Float on Port B + + 0x6: Toggle Address bit + 0x7: Align toggle bit + + :param mode: 0-2,4,6-7 + """ + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_extended = 0x0 + ir_mode = ir_mode_extended + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + if mode < 0x0 or mode == 0x3 or mode == 0x5 or mode > 0x7: + return False + + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def send_ir_single_pin(self, port, pin, mode, timeout): + """ + Send an IR message via Power Functions RC Protocol in Single Pin mode + https://www.philohome.com/pf/pf.htm + + Valid values for the mode are: + 0x0: No-op + 0x1: Clear + 0x2: Set + 0x3: Toggle + + Note: The unlabeled IR receiver (vs the one labeled V2) has a "firmware bug in Single Pin mode" + https://www.philohome.com/pfrec/pfrec.htm + + :param port: 'A' or 'B' + :param pin: 1 or 2 + :param mode: 0-3 indicating the pin's mode to set + :param timeout: True or False + """ + escape_mode = 0x0 + escape = escape_mode + + ir_mode_single_continuous = 0x2 + ir_mode_single_timeout = 0x3 + ir_mode = None + if timeout: + ir_mode = ir_mode_single_timeout + else: + ir_mode = ir_mode_single_continuous + + output_port_a = 0x0 + output_port_b = 0x1 + + output_port = None + if port == 'A' or port == 'a': + output_port = output_port_a + elif port == 'B' or port == 'b': + output_port = output_port_b + else: + return False + + if pin != 1 and pin != 2: + return False + pin_value = pin-1 + + if mode > 0x3 or mode < 0x0: + return False + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + nibble3 = (output_port << 3) | (pin_value << 3) | mode + + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def _send_ir_nibbles(self, nibble1, nibble2, nibble3): + + # M7 IR Tx SI = N/A + # format count=1 type=1 chars=5 dp=0 + # RAW: 00000000 0000FFFF PCT: 00000000 00000064 SI: 00000000 0000FFFF + + mode = 7 + self.mode(mode) + + # The upper bits of data[2] are ignored + if nibble1 > 0xF or nibble2 > 0xF or nibble3 > 0xF: + return False + if nibble1 < 0x0 or nibble2 < 0x0 or nibble3 < 0x0: + return False + + byte_two = (nibble2 << 4) | nibble3 + + data = bytearray(3) + data[0] = (0xc << 4) | mode + data[1] = byte_two + data[2] = nibble1 + + # print(" ".join('{:04b}'.format(nibble1))) + # print(" ".join('{:04b}'.format(nibble2))) + # print(" ".join('{:04b}'.format(nibble3))) + # print(" ".join('{:08b}'.format(n) for n in data)) + + self._write1(data) + return True + def on(self): """Turn on the sensor and LED""" self.reverse() From a306d01951de028d9323d4295905bbcb31a3ceae Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Mon, 4 Dec 2023 15:26:40 -0500 Subject: [PATCH 2/2] Flake8 compliance ... and a bugfix, thanks to flake8... so mad that it actually helped --- buildhat/colordistance.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/buildhat/colordistance.py b/buildhat/colordistance.py index 52cae03..3c722a3 100644 --- a/buildhat/colordistance.py +++ b/buildhat/colordistance.py @@ -202,6 +202,7 @@ def wait_for_new_color(self): @property def ir_channel(self): + """Get the IR channel for message transmission""" return self._ir_channel @ir_channel.setter @@ -217,20 +218,15 @@ def ir_channel(self, channel=1): elif check_chan < 1: check_chan = 1 # Internally: 0-3 - self._ir_channel = int(check_chan)-1 + self._ir_channel = int(check_chan) - 1 @property def ir_address(self): - """ - IR Address space of 0x0 for default PoweredUp or 0x1 for extra space - """ + """IR Address space of 0x0 for default PoweredUp or 0x1 for extra space""" return self._ir_address def toggle_ir_toggle(self): - """ - Toggle the IR toggle bit - - """ + """Toggle the IR toggle bit""" # IYKYK, because the RC documents are not clear if self._ir_toggle: self._ir_toggle = 0x0 @@ -241,7 +237,8 @@ def toggle_ir_toggle(self): def send_ir_sop(self, port, mode): """ Send an IR message via Power Functions RC Protocol in Single Output PWM mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Port B is blue @@ -297,7 +294,8 @@ def send_ir_sop(self, port, mode): def send_ir_socstid(self, port, mode): """ Send an IR message via Power Functions RC Protocol in Single Output Clear/Set/Toggle/Increment/Decrement mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for mode are: 0x0: Toggle full Clockwise/Forward (Stop to Clockwise, Clockwise to Stop, Counterclockwise to Clockwise) @@ -320,7 +318,6 @@ def send_ir_socstid(self, port, mode): :param port: 'A' or 'B' :param mode: 0-15 indicating the port's mode to set """ - escape_modeselect = 0x0 escape = escape_modeselect @@ -352,7 +349,8 @@ def send_ir_socstid(self, port, mode): def send_ir_combo_pwm(self, port_b_mode, port_a_mode): """ Send an IR message via Power Functions RC Protocol in Combo PWM mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the modes are: 0x0 Float @@ -375,7 +373,6 @@ def send_ir_combo_pwm(self, port_b_mode, port_a_mode): :param port_b_mode: 0-15 indicating the command to send to port B :param port_a_mode: 0-15 indicating the command to send to port A """ - escape_combo_pwm = 0x1 escape = escape_combo_pwm @@ -387,7 +384,8 @@ def send_ir_combo_pwm(self, port_b_mode, port_a_mode): def send_ir_combo_direct(self, port_b_output, port_a_output): """ Send an IR message via Power Functions RC Protocol in Combo Direct mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the output variables are: 0x0: Float output @@ -419,7 +417,8 @@ def send_ir_combo_direct(self, port_b_output, port_a_output): def send_ir_extended(self, mode): """ Send an IR message via Power Functions RC Protocol in Extended mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the mode are: 0x0: Brake Port A (timeout) @@ -450,7 +449,8 @@ def send_ir_extended(self, mode): def send_ir_single_pin(self, port, pin, mode, timeout): """ Send an IR message via Power Functions RC Protocol in Single Pin mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the mode are: 0x0: No-op @@ -490,7 +490,7 @@ def send_ir_single_pin(self, port, pin, mode, timeout): if pin != 1 and pin != 2: return False - pin_value = pin-1 + pin_value = pin - 1 if mode > 0x3 or mode < 0x0: return False @@ -499,7 +499,7 @@ def send_ir_single_pin(self, port, pin, mode, timeout): nibble2 = (self._ir_address << 3) | ir_mode nibble3 = (output_port << 3) | (pin_value << 3) | mode - return self._send_ir_nibbles(nibble1, nibble2, mode) + return self._send_ir_nibbles(nibble1, nibble2, nibble3) def _send_ir_nibbles(self, nibble1, nibble2, nibble3): @@ -516,7 +516,7 @@ def _send_ir_nibbles(self, nibble1, nibble2, nibble3): if nibble1 < 0x0 or nibble2 < 0x0 or nibble3 < 0x0: return False - byte_two = (nibble2 << 4) | nibble3 + byte_two = (nibble2 << 4) | nibble3 data = bytearray(3) data[0] = (0xc << 4) | mode