From 5e0d2f6e6889880ad8400fed04b493b5bb0138de Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Wed, 7 Aug 2024 18:53:09 +0200 Subject: [PATCH 1/7] feat: i2c exposed. if Alvik off bms has priority --- arduino_alvik/arduino_alvik.py | 97 ++++++++++++++++++++++++----- arduino_alvik/pinout_definitions.py | 4 +- 2 files changed, 84 insertions(+), 17 deletions(-) diff --git a/arduino_alvik/arduino_alvik.py b/arduino_alvik/arduino_alvik.py index b2bd83e..0f6b322 100644 --- a/arduino_alvik/arduino_alvik.py +++ b/arduino_alvik/arduino_alvik.py @@ -26,6 +26,7 @@ def __new__(cls): return cls._instance def __init__(self): + self.i2c = _ArduinoAlvikI2C(A4, A5) self._packeter = ucPack(200) self.left_wheel = _ArduinoAlvikWheel(self._packeter, ord('L')) self.right_wheel = _ArduinoAlvikWheel(self._packeter, ord('R')) @@ -100,36 +101,42 @@ def _progress_bar(percentage: float) -> None: word = marks_str + f" {percentage}% \t" sys.stdout.write(bytes((word.encode('utf-8')))) - def _idle(self, delay_=1, check_on_thread=False) -> None: + def _lenghty_op(self, iterations=10000000) -> int: + result = 0 + for i in range(1, iterations): + result += i * i + return result + + def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None: """ Alvik's idle mode behaviour :return: """ - NANO_CHK.value(1) - sleep_ms(500) + self.i2c._prio = True + + if blocking: + self._lenghty_op(50000) + else: + sleep_ms(500) led_val = 0 try: while not self.is_on(): if check_on_thread and not self.__class__._update_thread_running: break - _ESP32_SDA = Pin(A4, Pin.OUT) - _ESP32_SCL = Pin(A5, Pin.OUT) - _ESP32_SCL.value(1) - _ESP32_SDA.value(1) - sleep_ms(100) - _ESP32_SCL.value(0) - _ESP32_SDA.value(0) cmd = bytearray(1) cmd[0] = 0x06 - i2c = I2C(0, scl=ESP32_SCL, sda=ESP32_SDA) - i2c.writeto(0x36, cmd) - soc_raw = struct.unpack('h', i2c.readfrom(0x36, 2))[0] + self.i2c.writeto(0x36, cmd, start=True, priority=True) + + soc_raw = struct.unpack('h', self.i2c.readfrom(0x36, 2, priority=True))[0] soc_perc = soc_raw * 0.00390625 self._progress_bar(round(soc_perc)) - sleep_ms(delay_) + if blocking: + self._lenghty_op(10000) + else: + sleep_ms(delay_) if soc_perc > 97: LEDG.value(0) LEDR.value(1) @@ -137,6 +144,7 @@ def _idle(self, delay_=1, check_on_thread=False) -> None: LEDR.value(led_val) LEDG.value(1) led_val = (led_val + 1) % 2 + self.i2c._prio = False print("********** Alvik is on **********") except KeyboardInterrupt: self.stop() @@ -148,6 +156,7 @@ def _idle(self, delay_=1, check_on_thread=False) -> None: LEDR.value(1) LEDG.value(1) NANO_CHK.value(0) + self.i2c._prio = False @staticmethod def _snake_robot(duration: int = 1000): @@ -582,7 +591,7 @@ def _update(self, delay_=1): while True: if not self.is_on(): print("Alvik is off") - self._idle(1000, check_on_thread=True) + self._idle(1000, check_on_thread=True, blocking=True) self._reset_hw() self._flush_uart() sleep_ms(1000) @@ -1249,6 +1258,64 @@ def _stop_events_thread(cls): cls._events_thread_running = False +class _ArduinoAlvikI2C: + + def __init__(self, sda: int, scl: int): + """ + Alvik I2C wrapper + :param sda: + :param scl: + """ + self._lock = _thread.allocate_lock() + self._prio = False + self.sda = sda + self.scl = scl + + def _start(self): + """ + Bitbanging start condition + :return: + """ + _SDA = Pin(self.sda, Pin.OUT) + _SDA.value(1) + sleep_ms(100) + _SDA.value(0) + + def scan(self, start=False, priority=False): + """ + I2C scan method + :return: + """ + if not priority and self._prio: + return [] + with self._lock: + if start: + self._start() + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + out = i2c.scan() + return out + + def readfrom(self, addr, nbytes, stop=True, start=False, priority=False): + if not priority and self._prio: + return None + with self._lock: + if start: + self._start() + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + out = i2c.readfrom(addr, nbytes, stop) + return out + + def writeto(self, addr, buf, stop=True, start=False, priority=False): + if not priority and self._prio: + return None + with self._lock: + if start: + self._start() + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + out = i2c.writeto(addr, buf, stop) + return out + + class _ArduinoAlvikServo: def __init__(self, packeter: ucPack, label: str, servo_id: int, position: list[int | None]): diff --git a/arduino_alvik/pinout_definitions.py b/arduino_alvik/pinout_definitions.py index 06d24a3..8892a38 100644 --- a/arduino_alvik/pinout_definitions.py +++ b/arduino_alvik/pinout_definitions.py @@ -13,8 +13,8 @@ RESET_STM32 = Pin(D3, Pin.OUT) # nano D3 -> STM32 NRST NANO_CHK = Pin(D4, Pin.OUT) # nano D4 -> STM32 NANO_CHK CHECK_STM32 = Pin(A6, Pin.IN, Pin.PULL_DOWN) # nano A6/D23 -> STM32 ROBOT_CHK -ESP32_SDA = Pin(A4, Pin.OUT) # ESP32_SDA -ESP32_SCL = Pin(A5, Pin.OUT) # ESP32_SCL +# ESP32_SDA = Pin(A4, Pin.OUT) # ESP32_SDA +# ESP32_SCL = Pin(A5, Pin.OUT) # ESP32_SCL # LEDS LEDR = Pin(46, Pin.OUT) #RED ESP32 LEDR From 69b100c1464e19adcac2507e1e3b3421531f239e Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 8 Aug 2024 12:02:59 +0200 Subject: [PATCH 2/7] ex: i2c_scan.py --- examples/i2c_scan.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/i2c_scan.py diff --git a/examples/i2c_scan.py b/examples/i2c_scan.py new file mode 100644 index 0000000..9f8388e --- /dev/null +++ b/examples/i2c_scan.py @@ -0,0 +1,20 @@ +from time import sleep_ms + +from arduino_alvik import ArduinoAlvik + +alvik = ArduinoAlvik() +alvik.begin() + +sleep_ms(1000) + +while True: + out = alvik.i2c.scan() + + if len(out) == 0: + print("\nNo device found on I2C") + else: + print("\nList of devices") + for o in out: + print(o) + print(alvik.get_distance()) + sleep_ms(100) \ No newline at end of file From b7232157ddee8a07630e653366cf0a8d5a44ef0a Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 8 Aug 2024 15:09:30 +0200 Subject: [PATCH 3/7] mod: alvik_i2c single_thread operation based on thread id ex: i2c_scan.py better output --- arduino_alvik/arduino_alvik.py | 60 +++++++++++++++++++++++++--------- examples/i2c_scan.py | 4 +-- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/arduino_alvik/arduino_alvik.py b/arduino_alvik/arduino_alvik.py index 0f6b322..f8325c5 100644 --- a/arduino_alvik/arduino_alvik.py +++ b/arduino_alvik/arduino_alvik.py @@ -113,7 +113,7 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None: :return: """ NANO_CHK.value(1) - self.i2c._prio = True + self.i2c.set_single_thread(True) if blocking: self._lenghty_op(50000) @@ -123,14 +123,15 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None: try: while not self.is_on(): + if check_on_thread and not self.__class__._update_thread_running: break cmd = bytearray(1) cmd[0] = 0x06 - self.i2c.writeto(0x36, cmd, start=True, priority=True) + self.i2c.writeto(0x36, cmd, start=True) - soc_raw = struct.unpack('h', self.i2c.readfrom(0x36, 2, priority=True))[0] + soc_raw = struct.unpack('h', self.i2c.readfrom(0x36, 2))[0] soc_perc = soc_raw * 0.00390625 self._progress_bar(round(soc_perc)) if blocking: @@ -144,19 +145,19 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None: LEDR.value(led_val) LEDG.value(1) led_val = (led_val + 1) % 2 - self.i2c._prio = False + self.i2c.set_single_thread(False) print("********** Alvik is on **********") except KeyboardInterrupt: self.stop() sys.exit() except Exception as e: pass - # print(f'Unable to read SOC: {e}') + print(f'Unable to read SOC: {e}') finally: LEDR.value(1) LEDG.value(1) NANO_CHK.value(0) - self.i2c._prio = False + self.i2c.set_single_thread(False) @staticmethod def _snake_robot(duration: int = 1000): @@ -193,7 +194,8 @@ def begin(self) -> int: if not self.is_on(): print("\n********** Please turn on your Arduino Alvik! **********\n") sleep_ms(1000) - self._idle(1000) + self.i2c.set_main_thread(_thread.get_ident()) + self._idle(1000, blocking=True) self._begin_update_thread() sleep_ms(100) @@ -588,6 +590,9 @@ def _update(self, delay_=1): :param delay_: while loop delay (ms) :return: """ + + self.i2c.set_main_thread(_thread.get_ident()) + while True: if not self.is_on(): print("Alvik is off") @@ -1260,6 +1265,8 @@ def _stop_events_thread(cls): class _ArduinoAlvikI2C: + _main_thread_id = None + def __init__(self, sda: int, scl: int): """ Alvik I2C wrapper @@ -1267,10 +1274,33 @@ def __init__(self, sda: int, scl: int): :param scl: """ self._lock = _thread.allocate_lock() - self._prio = False + + self._is_single_thread = False + self.sda = sda self.scl = scl + def set_main_thread(self, thread_id: int): + """ + Sets the main thread of control. It will be the only thread allowed if set_single_thread is True + """ + with self._lock: + print(f"Setting I2C main thread to: {thread_id}") + self.__class__._main_thread_id = thread_id + + def set_single_thread(self, value): + """ + Sets the single thread mode on/off. + In single mode only the main thread is allowed to access the bus + """ + self._is_single_thread = value + + def is_accessible(self): + """ + Returns True if bus is accessible by the current thread + """ + return not self._is_single_thread or _thread.get_ident() == self.__class__._main_thread_id + def _start(self): """ Bitbanging start condition @@ -1281,12 +1311,12 @@ def _start(self): sleep_ms(100) _SDA.value(0) - def scan(self, start=False, priority=False): + def scan(self, start=False): """ I2C scan method :return: """ - if not priority and self._prio: + if not self.is_accessible(): return [] with self._lock: if start: @@ -1295,9 +1325,9 @@ def scan(self, start=False, priority=False): out = i2c.scan() return out - def readfrom(self, addr, nbytes, stop=True, start=False, priority=False): - if not priority and self._prio: - return None + def readfrom(self, addr, nbytes, stop=True, start=False): + if not self.is_accessible(): + return bytes(nbytes) with self._lock: if start: self._start() @@ -1305,8 +1335,8 @@ def readfrom(self, addr, nbytes, stop=True, start=False, priority=False): out = i2c.readfrom(addr, nbytes, stop) return out - def writeto(self, addr, buf, stop=True, start=False, priority=False): - if not priority and self._prio: + def writeto(self, addr, buf, stop=True, start=False): + if not self.is_accessible(): return None with self._lock: if start: diff --git a/examples/i2c_scan.py b/examples/i2c_scan.py index 9f8388e..6996681 100644 --- a/examples/i2c_scan.py +++ b/examples/i2c_scan.py @@ -15,6 +15,6 @@ else: print("\nList of devices") for o in out: - print(o) - print(alvik.get_distance()) + print(hex(o)) + sleep_ms(100) \ No newline at end of file From 99657809955f0f1ac811f99de0d8e687eaf89e13 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 8 Aug 2024 15:20:47 +0200 Subject: [PATCH 4/7] mod: rem start parameter from alvikI2C methods --- arduino_alvik/arduino_alvik.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/arduino_alvik/arduino_alvik.py b/arduino_alvik/arduino_alvik.py index f8325c5..81ac240 100644 --- a/arduino_alvik/arduino_alvik.py +++ b/arduino_alvik/arduino_alvik.py @@ -129,7 +129,9 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None: cmd = bytearray(1) cmd[0] = 0x06 - self.i2c.writeto(0x36, cmd, start=True) + + self.i2c._start() + self.i2c.writeto(0x36, cmd) soc_raw = struct.unpack('h', self.i2c.readfrom(0x36, 2))[0] soc_perc = soc_raw * 0.00390625 @@ -1285,7 +1287,6 @@ def set_main_thread(self, thread_id: int): Sets the main thread of control. It will be the only thread allowed if set_single_thread is True """ with self._lock: - print(f"Setting I2C main thread to: {thread_id}") self.__class__._main_thread_id = thread_id def set_single_thread(self, value): @@ -1311,7 +1312,7 @@ def _start(self): sleep_ms(100) _SDA.value(0) - def scan(self, start=False): + def scan(self): """ I2C scan method :return: @@ -1319,28 +1320,22 @@ def scan(self, start=False): if not self.is_accessible(): return [] with self._lock: - if start: - self._start() i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) out = i2c.scan() return out - def readfrom(self, addr, nbytes, stop=True, start=False): + def readfrom(self, addr, nbytes, stop=True): if not self.is_accessible(): return bytes(nbytes) with self._lock: - if start: - self._start() i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) out = i2c.readfrom(addr, nbytes, stop) return out - def writeto(self, addr, buf, stop=True, start=False): + def writeto(self, addr, buf, stop=True): if not self.is_accessible(): return None with self._lock: - if start: - self._start() i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) out = i2c.writeto(addr, buf, stop) return out From 17b171bfd238a0e9a1d929f97f96f66f49d48a84 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 8 Aug 2024 15:52:31 +0200 Subject: [PATCH 5/7] feat: all i2c methods implemented for AlvikI2C --- arduino_alvik/arduino_alvik.py | 119 +++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/arduino_alvik/arduino_alvik.py b/arduino_alvik/arduino_alvik.py index 81ac240..7fa2468 100644 --- a/arduino_alvik/arduino_alvik.py +++ b/arduino_alvik/arduino_alvik.py @@ -130,7 +130,7 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None: cmd = bytearray(1) cmd[0] = 0x06 - self.i2c._start() + self.i2c.start() self.i2c.writeto(0x36, cmd) soc_raw = struct.unpack('h', self.i2c.readfrom(0x36, 2))[0] @@ -1302,7 +1302,7 @@ def is_accessible(self): """ return not self._is_single_thread or _thread.get_ident() == self.__class__._main_thread_id - def _start(self): + def start(self): """ Bitbanging start condition :return: @@ -1312,7 +1312,28 @@ def _start(self): sleep_ms(100) _SDA.value(0) - def scan(self): + def init(self, scl, sda, freq=400_000) -> None: + """ + init method just for call compatibility + """ + print("AlvikWarning:: init Unsupported. Alvik defines/initializes its own I2C bus") + + def deinit(self): + """ + deinit method just for call compatibility + """ + print("AlvikWarning:: deinit Unsupported. Alvik defines/initializes its own I2C bus") + + def stop(self): + """ Bitbanging stop condition (untested) + :return: + """ + _SDA = Pin(self.sda, Pin.OUT) + _SDA.value(0) + sleep_ms(100) + _SDA.value(1) + + def scan(self) -> list[int]: """ I2C scan method :return: @@ -1321,24 +1342,98 @@ def scan(self): return [] with self._lock: i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) - out = i2c.scan() - return out + return i2c.scan() - def readfrom(self, addr, nbytes, stop=True): + def readfrom(self, addr, nbytes, stop=True) -> bytes: + """ + Wrapping i2c readfrom + """ if not self.is_accessible(): return bytes(nbytes) with self._lock: i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) - out = i2c.readfrom(addr, nbytes, stop) - return out + return i2c.readfrom(addr, nbytes, stop) + + def writeto(self, addr, buf, stop=True) -> int: + """ + Wrapping i2c writeto + """ + if not self.is_accessible(): + return 0 + with self._lock: + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + return i2c.writeto(addr, buf, stop) - def writeto(self, addr, buf, stop=True): + def readinto(self, buf, nack=True) -> None: + """ + Wrapping i2c readinto + """ if not self.is_accessible(): - return None + return with self._lock: i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) - out = i2c.writeto(addr, buf, stop) - return out + return i2c.readinto(buf, nack) + + def write(self, buf) -> int: + """ + Wrapping i2c write + """ + if not self.is_accessible(): + return 0 + with self._lock: + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + return i2c.write(buf) + + def readfrom_into(self, addr, buf, stop=True) -> None: + """ + Wrapping i2c readfrom_into + """ + if not self.is_accessible(): + return + with self._lock: + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + return i2c.readfrom_into(addr, buf, stop) + + def writevto(self, addr, vector, stop=True) -> int: + """ + Wrapping i2c writevto + """ + if not self.is_accessible(): + return 0 + with self._lock: + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + return i2c.writevto(addr, vector, stop) + + def readfrom_mem(self, addr, memaddr, nbytes, addrsize=8) -> bytes: + """ + Wrapping i2c readfrom_mem + """ + if not self.is_accessible(): + return bytes(nbytes) + with self._lock: + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + return i2c.readfrom_mem(addr, memaddr, nbytes, addrsize=addrsize) + + def readfrom_mem_into(self, addr, memaddr, buf, addrsize=8) -> None: + """ + Wrapping i2c readfrom_mem_into + """ + if not self.is_accessible(): + return + with self._lock: + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + return i2c.readfrom_mem_into(addr, memaddr, buf, addrsize=addrsize) + + def writeto_mem(self, addr, memaddr, buf, addrsize=8) -> None: + """ + Wrapping i2c writeto_mem + """ + if not self.is_accessible(): + return + with self._lock: + i2c = I2C(0, scl=Pin(self.scl, Pin.OUT), sda=Pin(self.sda, Pin.OUT)) + return i2c.writeto_mem(addr, memaddr, buf, addrsize=addrsize) + class _ArduinoAlvikServo: From 9716c38e2334f36350b1db61f6602ea7c5d738ea Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 8 Aug 2024 16:13:19 +0200 Subject: [PATCH 6/7] impr: i2c example --- examples/i2c_scan.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/i2c_scan.py b/examples/i2c_scan.py index 6996681..a246787 100644 --- a/examples/i2c_scan.py +++ b/examples/i2c_scan.py @@ -1,4 +1,5 @@ from time import sleep_ms +import sys from arduino_alvik import ArduinoAlvik @@ -8,13 +9,17 @@ sleep_ms(1000) while True: - out = alvik.i2c.scan() + try: + out = alvik.i2c.scan() - if len(out) == 0: - print("\nNo device found on I2C") - else: - print("\nList of devices") - for o in out: - print(hex(o)) + if len(out) == 0: + print("\nNo device found on I2C") + else: + print("\nList of devices") + for o in out: + print(hex(o)) - sleep_ms(100) \ No newline at end of file + sleep_ms(100) + except KeyboardInterrupt as e: + alvik.stop() + sys.exit() \ No newline at end of file From a966f0ac835232428e2e10592df37c0b2332e08d Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 8 Aug 2024 16:25:27 +0200 Subject: [PATCH 7/7] fix: blocking idle makes update thread unstoppable --- arduino_alvik/arduino_alvik.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arduino_alvik/arduino_alvik.py b/arduino_alvik/arduino_alvik.py index 7fa2468..ff5f1aa 100644 --- a/arduino_alvik/arduino_alvik.py +++ b/arduino_alvik/arduino_alvik.py @@ -148,7 +148,8 @@ def _idle(self, delay_=1, check_on_thread=False, blocking=False) -> None: LEDG.value(1) led_val = (led_val + 1) % 2 self.i2c.set_single_thread(False) - print("********** Alvik is on **********") + if self.is_on(): + print("********** Alvik is on **********") except KeyboardInterrupt: self.stop() sys.exit() @@ -197,7 +198,7 @@ def begin(self) -> int: print("\n********** Please turn on your Arduino Alvik! **********\n") sleep_ms(1000) self.i2c.set_main_thread(_thread.get_ident()) - self._idle(1000, blocking=True) + self._idle(1000) self._begin_update_thread() sleep_ms(100) @@ -598,7 +599,7 @@ def _update(self, delay_=1): while True: if not self.is_on(): print("Alvik is off") - self._idle(1000, check_on_thread=True, blocking=True) + self._idle(1000, check_on_thread=True) self._reset_hw() self._flush_uart() sleep_ms(1000)