diff --git a/docs/README.md b/docs/README.md index 58739a9..1c65844 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,10 +8,6 @@ To use this library you can import the `modulino` module along with the desired from modulino import ModulinoPixels pixels = ModulinoPixels() - -if not pixels.connected: - print("🤷 No pixel modulino found") - exit() ``` Once the desired object is obtained you can call functions and query properties on these objects such as `pixels.set_all_rgb(255, 0, 0)`. diff --git a/examples/buttons.py b/examples/buttons.py index 63568f0..8c82e9a 100644 --- a/examples/buttons.py +++ b/examples/buttons.py @@ -9,14 +9,9 @@ """ from modulino import ModulinoButtons -from sys import exit buttons = ModulinoButtons() -if not buttons.connected: - print("🤷 No button modulino found") - exit() - buttons.on_button_a_press = lambda : print("Button A pressed") buttons.on_button_a_long_press = lambda : print("Button A long press") buttons.on_button_a_release = lambda : print("Button A released") diff --git a/examples/change_address.py b/examples/change_address.py new file mode 100644 index 0000000..2a0c68b --- /dev/null +++ b/examples/change_address.py @@ -0,0 +1,47 @@ +""" +This example shows how to change the I2C address of a Modulino device. +After changing the address, the device will be reset and the new address will be verified. +From then on, when creating a Modulino object, you should use the new address. +e.g. ModulinoBuzzer(address=0x2A) + +Initial author: Sebastian Romero (s.romero@arduino.cc) +""" + +from sys import exit +from time import sleep +from modulino import Modulino + +print() +devices = Modulino.available_devices() + +if len(devices) == 0: + print("No devices found on the bus. Try resetting the board.") + exit(1) + +print("The following devices were found on the bus:") + +for index, device in enumerate(devices): + print(f"{index + 1}) {device.device_type} at {hex(device.address)}") + +choice = int(input("\nEnter the device number for which you want to change the address: ")) + +if choice < 1 or choice > len(devices): + print("Invalid choice. Please select a valid device number.") + exit(1) + +selected_device = devices[choice - 1] +new_address = int(input("Enter the new address (hexadecimal or decimal): "), 0) + +if new_address < 0 or new_address > 127: + print("Invalid address. Address must be between 0 and 127") + exit(1) + +print(f"Changing address of device at {hex(selected_device.address)} to {hex(new_address)}...") +selected_device.change_address(new_address) +sleep(1) # Give the device time to reset + +# Check if the address was successfully changed +if selected_device.connected: + print(f"✅ Address changed successfully to {hex(new_address)}") +else: + print("❌ Failed to change address. Please try again.") \ No newline at end of file diff --git a/examples/pixels.py b/examples/pixels.py index 5ef6fc7..870540a 100644 --- a/examples/pixels.py +++ b/examples/pixels.py @@ -10,14 +10,9 @@ from modulino import ModulinoPixels, ModulinoColor from time import sleep -from sys import exit pixels = ModulinoPixels() -if not pixels.connected: - print("🤷 No pixel modulino found") - exit() - for index in range(0, 8): color_wheel_colors = [ (255, 0, 0), diff --git a/examples/thermo.py b/examples/thermo.py index 89185ce..e09970b 100644 --- a/examples/thermo.py +++ b/examples/thermo.py @@ -8,14 +8,9 @@ from modulino import ModulinoThermo from time import sleep -from sys import exit thermo_module = ModulinoThermo() -if not thermo_module.connected: - print("🤷 No thermo modulino found") - exit() - while True: temperature = thermo_module.temperature humidity = thermo_module.relative_humidity diff --git a/src/modulino/buttons.py b/src/modulino/buttons.py index 720b599..73825a7 100644 --- a/src/modulino/buttons.py +++ b/src/modulino/buttons.py @@ -144,21 +144,24 @@ def update(self): # Check for press and release if(button_states_changed): - if(new_status[0] == 1 and previous_status[0] == 0 and self._on_button_a_press): + if(new_status[0] == 1 and previous_status[0] == 0): self._last_press_timestamps[0] = ticks_ms() - self._on_button_a_press() + if(self._on_button_a_press): + self._on_button_a_press() elif(new_status[0] == 0 and previous_status[0] == 1 and self._on_button_a_release): self._on_button_a_release() - if(new_status[1] == 1 and previous_status[1] == 0 and self._on_button_b_press): + if(new_status[1] == 1 and previous_status[1] == 0): self._last_press_timestamps[1] = ticks_ms() - self._on_button_b_press() + if(self._on_button_b_press): + self._on_button_b_press() elif(new_status[1] == 0 and previous_status[1] == 1 and self._on_button_b_release): self._on_button_b_release() - if(new_status[2] == 1 and previous_status[2] == 0 and self._on_button_c_press): + if(new_status[2] == 1 and previous_status[2] == 0): self._last_press_timestamps[2] = ticks_ms() - self._on_button_c_press() + if(self._on_button_c_press): + self._on_button_c_press() elif(new_status[2] == 0 and previous_status[2] == 1 and self._on_button_c_release): self._on_button_c_release() diff --git a/src/modulino/modulino.py b/src/modulino/modulino.py index 0368398..634314f 100644 --- a/src/modulino/modulino.py +++ b/src/modulino/modulino.py @@ -15,6 +15,14 @@ "Generic ESP32S3 module" : I2CInterface("hw", 0, None, None), } +PINSTRAP_ADDRESS_MAP = { + 0x3C: "Buzzer", + 0x7C: "Buttons", + 0x76: "Knob", + 0x74: "Knob", + 0x6C: "Pixels" +} + class I2CHelper: """ A helper class for interacting with I2C devices on supported boards. @@ -174,6 +182,31 @@ def pin_strap_address(self): # The first byte is always the pinstrap address return data[0] + @property + def device_type(self): + """ + Returns the type of the modulino based on the pinstrap address. + """ + return PINSTRAP_ADDRESS_MAP.get(self.pin_strap_address, None) + + def change_address(self, new_address): + """ + Sets the address of the i2c device to the given value. + """ + # TODO: Check if device supports this feature by looking at the type + + data = bytearray(40) + # Set the first two bytes to 'C' and 'F' followed by the new address + data[0:2] = b'CF' + data[2] = new_address * 2 + + try: + self.write(data) + except OSError: + pass # Device resets immediately and causes ENODEV to be thrown which is expected + + self.address = new_address + def read(self, amount_of_bytes): """ Reads the given amount of bytes from the i2c device and returns the data. @@ -207,6 +240,19 @@ def has_default_address(self): """ return self.address in self.default_addresses + @staticmethod + def available_devices(): + """ + Finds all devices on the i2c bus and returns a list of Modulino objects. + """ + bus = I2CHelper.get_interface() + device_addresses = bus.scan() + devices = [] + for address in device_addresses: + device = Modulino(i2c_bus=bus, address=address) + devices.append(device) + return devices + @staticmethod def reset_bus(i2c_bus): """