espzero 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: espzero
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Educational tool for physical computing and ESP32/ESP8266 development.
5
5
  Author-email: Neo Neopia-uz <neopia.uz@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/roboticsware/espzero
7
+ Project-URL: Documentation, https://espzero.readthedocs.io
7
8
  Project-URL: Bug Tracker, https://github.com/roboticsware/espzero/issues
8
9
  Classifier: Programming Language :: Python :: 3
9
10
  Classifier: License :: OSI Approved :: MIT License
@@ -14,6 +15,7 @@ Description-Content-Type: text/markdown
14
15
  # espzero
15
16
 
16
17
  [![PyPI version](https://badge.fury.io/py/espzero.svg)](https://badge.fury.io/py/espzero)
18
+ [![Documentation Status](https://readthedocs.org/projects/espzero/badge/?version=latest)](https://espzero.readthedocs.io/)
17
19
  [![License](https://img.shields.io/github/license/roboticsware/espzero)](https://github.com/roboticsware/espzero/blob/main/LICENSE)
18
20
 
19
21
  > A single MicroPython library that ports [picozero](https://github.com/raspberrypilearning/picozero) to the ESP32 family (WROOM, S3, C3, and more).
@@ -1,6 +1,7 @@
1
1
  # espzero
2
2
 
3
3
  [![PyPI version](https://badge.fury.io/py/espzero.svg)](https://badge.fury.io/py/espzero)
4
+ [![Documentation Status](https://readthedocs.org/projects/espzero/badge/?version=latest)](https://espzero.readthedocs.io/)
4
5
  [![License](https://img.shields.io/github/license/roboticsware/espzero)](https://github.com/roboticsware/espzero/blob/main/LICENSE)
5
6
 
6
7
  > A single MicroPython library that ports [picozero](https://github.com/raspberrypilearning/picozero) to the ESP32 family (WROOM, S3, C3, and more).
@@ -63,6 +63,11 @@ def __getattr__(name):
63
63
  )
64
64
  return _esp_led
65
65
  if name == "esp_temp_sensor":
66
+ if _esp_temp_sensor is None:
67
+ raise RuntimeError(
68
+ "[espzero] ERROR: 'esp_temp_sensor' is not initialized. "
69
+ "You must call espzero.begin() first."
70
+ )
66
71
  return _esp_temp_sensor
67
72
  raise AttributeError("module 'espzero' has no attribute '{}'".format(name))
68
73
 
@@ -142,6 +147,7 @@ from ._core import (
142
147
  ESPTemperatureSensor,
143
148
  DigitalOutputDevice, PWMOutputDevice,
144
149
  DigitalInputDevice, AnalogInputDevice,
150
+ I2cLcd, GpioLcd,
145
151
  )
146
152
  from ._wifi import WiFi
147
153
  from ._touch import CapTouch
@@ -330,7 +330,7 @@ def LED(pin, pwm=True, active_high=True, initial_value=False):
330
330
  return DigitalLED(pin=pin, active_high=active_high, initial_value=initial_value)
331
331
 
332
332
 
333
- class NeoPixelLED(OutputDevice):
333
+ class NeoPixelLED(OutputDevice, PinMixin):
334
334
  """WS2812 single-pixel wrapper for boards with a built-in RGB LED.
335
335
  Automatically used when INTERNAL_LED_TYPE == 'neopixel'.
336
336
  """
@@ -339,10 +339,21 @@ class NeoPixelLED(OutputDevice):
339
339
  self._np = neopixel.NeoPixel(Pin(pin), 1)
340
340
  self._color = color
341
341
  self._pin_num = pin
342
+ self._state = False
342
343
  super().__init__(active_high, initial_value)
343
344
 
345
+ def _value_to_state(self, value):
346
+ return bool(self._active_state if value else self._inactive_state)
347
+
348
+ def _state_to_value(self, state):
349
+ return int(bool(state) == self._active_state)
350
+
351
+ def _read(self):
352
+ return self._state_to_value(self._state)
353
+
344
354
  def _write(self, value):
345
- self._np[0] = self._color if self._value_to_state(value) else (0, 0, 0)
355
+ self._state = self._value_to_state(value)
356
+ self._np[0] = self._color if self._state else (0, 0, 0)
346
357
  self._np.write()
347
358
 
348
359
  def close(self):
@@ -766,13 +777,22 @@ class ESPTemperatureSensor:
766
777
  def temp(self):
767
778
  try:
768
779
  import esp32
769
- return esp32.raw_temperature() / 100.0
780
+ if hasattr(esp32, "mcu_temperature"):
781
+ return float(esp32.mcu_temperature())
782
+ elif hasattr(esp32, "raw_temperature"):
783
+ raw = esp32.raw_temperature()
784
+ # On classic ESP32, raw_temperature returns Fahrenheit (~100..150)
785
+ # Convert F to C: (F - 32) * 5/9
786
+ if raw > 60:
787
+ return round((raw - 32.0) * 5.0 / 9.0, 1)
788
+ return float(raw)
770
789
  except Exception:
771
- return None
790
+ pass
791
+ return None
772
792
 
773
793
 
774
794
  class DistanceSensor(PinsMixin):
775
- def __init__(self, echo, trigger, max_distance=1.0):
795
+ def __init__(self, echo, trigger, max_distance=400):
776
796
  self._pin_nums = (echo, trigger)
777
797
  self._max_distance = max_distance
778
798
  self._echo = Pin(echo, mode=Pin.IN, pull=Pin.PULL_DOWN)
@@ -791,7 +811,8 @@ class DistanceSensor(PinsMixin):
791
811
  echo_off = ticks_us()
792
812
  if echo_off is None:
793
813
  return None
794
- dist = ((echo_off - echo_on) * 0.000343) / 2
814
+ # Use 0.0343 for centimeters (matches picozero 0.6.2)
815
+ dist = ((echo_off - echo_on) * 0.0343) / 2
795
816
  return min(dist, self._max_distance)
796
817
 
797
818
  @property
@@ -823,17 +844,33 @@ class Stepper(PinsMixin):
823
844
  self._steps_per_rotation = steps_per_rotation
824
845
  self._current_step = 0
825
846
  self._step_count = 0
847
+ if step_sequence not in self.STEP_SEQUENCES:
848
+ raise ValueError(f"Invalid step_sequence. Must be one of: {list(self.STEP_SEQUENCES.keys())}")
849
+ self._step_sequence = step_sequence
826
850
  self._sequence = self.STEP_SEQUENCES[step_sequence]
827
851
  self._set_step([0,0,0,0])
828
852
 
853
+ def _normalise_direction(self, direction):
854
+ if isinstance(direction, str):
855
+ direction_lower = direction.lower().strip()
856
+ if direction_lower in ("cw", "clockwise"): return 1
857
+ elif direction_lower in ("ccw", "counter-clockwise", "counterclockwise"): return -1
858
+ else: raise ValueError(f"Invalid direction string: '{direction}'.")
859
+ else:
860
+ return 1 if direction >= 0 else -1
861
+
829
862
  def _set_step(self, pattern):
830
863
  for pin, state in zip(self._pins, pattern):
831
864
  pin.value = state
832
865
 
833
866
  def _single_step(self, direction=1):
834
- d = 1 if direction >= 0 else -1
835
- self._current_step = (self._current_step + d) % len(self._sequence)
836
- self._step_count += d
867
+ normalised_direction = self._normalise_direction(direction)
868
+ if normalised_direction > 0:
869
+ self._current_step = (self._current_step + 1) % len(self._sequence)
870
+ self._step_count += 1
871
+ else:
872
+ self._current_step = (self._current_step - 1) % len(self._sequence)
873
+ self._step_count -= 1
837
874
  self._set_step(self._sequence[self._current_step])
838
875
  sleep(self._step_delay)
839
876
 
@@ -841,8 +878,317 @@ class Stepper(PinsMixin):
841
878
  for _ in range(abs(int(steps))):
842
879
  self._single_step(direction)
843
880
 
881
+ def step_to(self, steps, direction):
882
+ target_steps = int(steps)
883
+ current_steps = self._step_count
884
+ normalised_dir = self._normalise_direction(direction)
885
+ if normalised_dir > 0:
886
+ distance = target_steps - current_steps
887
+ if distance > 0: self.step(distance, direction)
888
+ elif distance < 0: self.step(self._steps_per_rotation + distance, direction)
889
+ else:
890
+ if target_steps > 0: self.step(target_steps, direction)
891
+
892
+ def turn(self, angle, direction):
893
+ angle = abs(float(angle))
894
+ steps = int((angle / 360.0) * self._steps_per_rotation)
895
+ self.step(steps, direction)
896
+
897
+ def rotate(self, rotations, direction):
898
+ rotations = abs(float(rotations))
899
+ steps = int(rotations * self._steps_per_rotation)
900
+ self.step(steps, direction)
901
+
902
+ def turn_to(self, angle, direction):
903
+ target_angle = abs(float(angle)) % 360.0
904
+ current_angle = self.angle
905
+ if self._normalise_direction(direction) > 0:
906
+ if target_angle >= current_angle: rotation_angle = target_angle - current_angle
907
+ else: rotation_angle = 360.0 - current_angle + target_angle
908
+ else:
909
+ if target_angle <= current_angle: rotation_angle = current_angle - target_angle
910
+ else: rotation_angle = current_angle + (360.0 - target_angle)
911
+ steps = int((rotation_angle / 360.0) * self._steps_per_rotation)
912
+ if steps > 0: self.step(steps, direction)
913
+
914
+ def reset_position(self): self._step_count = 0
844
915
  def off(self): self._set_step([0,0,0,0])
845
916
 
917
+ def set_speed(self, rpm):
918
+ rpm = float(rpm)
919
+ if rpm <= 0: raise ValueError("RPM must be positive")
920
+ self._step_delay = 60.0 / (rpm * self._steps_per_rotation)
921
+
922
+ def run_continuous(self, seconds=None, direction=1):
923
+ if seconds is None:
924
+ try:
925
+ while True: self._single_step(direction)
926
+ except KeyboardInterrupt:
927
+ self.off()
928
+ else:
929
+ seconds = abs(float(seconds))
930
+ end_time = ticks_ms() + int(seconds * 1000)
931
+ while ticks_ms() < end_time: self._single_step(direction)
932
+ self.off()
933
+
934
+ @property
935
+ def step_delay(self): return self._step_delay
936
+ @step_delay.setter
937
+ def step_delay(self, value): self._step_delay = float(value)
938
+
939
+ @property
940
+ def step_count(self): return self._step_count
941
+
942
+ @property
943
+ def angle(self): return ((self._step_count / self._steps_per_rotation) * 360.0) % 360.0
944
+
945
+ @property
946
+ def steps_per_rotation(self): return self._steps_per_rotation
947
+
846
948
  def close(self):
847
949
  self.off()
848
950
  for p in self._pins: p.close()
951
+
952
+
953
+ # --- LCD Display Classes ---
954
+
955
+ MASK_RS = 0x01
956
+ MASK_RW = 0x02
957
+ MASK_E = 0x04
958
+ SHIFT_BACKLIGHT = 3
959
+ SHIFT_DATA = 4
960
+
961
+ class LcdApi:
962
+ LCD_CLR = 0x01
963
+ LCD_HOME = 0x02
964
+ LCD_ENTRY_MODE = 0x04
965
+ LCD_ENTRY_INC = 0x02
966
+ LCD_ENTRY_SHIFT = 0x01
967
+ LCD_ON_CTRL = 0x08
968
+ LCD_ON_DISPLAY = 0x04
969
+ LCD_ON_CURSOR = 0x02
970
+ LCD_ON_BLINK = 0x01
971
+ LCD_MOVE = 0x10
972
+ LCD_MOVE_DISP = 0x08
973
+ LCD_MOVE_RIGHT = 0x04
974
+ LCD_FUNCTION = 0x20
975
+ LCD_FUNCTION_8BIT = 0x10
976
+ LCD_FUNCTION_2LINES = 0x08
977
+ LCD_FUNCTION_10DOTS = 0x04
978
+ LCD_FUNCTION_RESET = 0x30
979
+ LCD_CGRAM = 0x40
980
+ LCD_DDRAM = 0x80
981
+
982
+ def __init__(self, num_lines, num_columns):
983
+ self.num_lines = min(num_lines, 4)
984
+ self.num_columns = min(num_columns, 40)
985
+ self.cursor_x = 0
986
+ self.cursor_y = 0
987
+ self.implied_newline = False
988
+ self.backlight = True
989
+ self.display_off()
990
+ self.backlight_on()
991
+ self.clear()
992
+ self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)
993
+ self.hide_cursor()
994
+ self.display_on()
995
+
996
+ def clear(self):
997
+ self.hal_write_command(self.LCD_CLR)
998
+ self.hal_write_command(self.LCD_HOME)
999
+ self.cursor_x = 0
1000
+ self.cursor_y = 0
1001
+
1002
+ def show_cursor(self): self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | self.LCD_ON_CURSOR)
1003
+ def hide_cursor(self): self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
1004
+ def blink_cursor_on(self): self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | self.LCD_ON_CURSOR | self.LCD_ON_BLINK)
1005
+ def blink_cursor_off(self): self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | self.LCD_ON_CURSOR)
1006
+ def display_on(self): self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
1007
+ def display_off(self): self.hal_write_command(self.LCD_ON_CTRL)
1008
+
1009
+ def backlight_on(self):
1010
+ self.backlight = True
1011
+ self.hal_backlight_on()
1012
+
1013
+ def backlight_off(self):
1014
+ self.backlight = False
1015
+ self.hal_backlight_off()
1016
+
1017
+ def move_to(self, cursor_x, cursor_y):
1018
+ self.cursor_x = cursor_x
1019
+ self.cursor_y = cursor_y
1020
+ addr = cursor_x & 0x3f
1021
+ if cursor_y & 1: addr += 0x40
1022
+ if cursor_y & 2: addr += self.num_columns
1023
+ self.hal_write_command(self.LCD_DDRAM | addr)
1024
+
1025
+ def putchar(self, char):
1026
+ if char == '\n':
1027
+ if not self.implied_newline:
1028
+ self.cursor_x = self.num_columns
1029
+ else:
1030
+ self.hal_write_data(ord(char))
1031
+ self.cursor_x += 1
1032
+ if self.cursor_x >= self.num_columns:
1033
+ self.cursor_x = 0
1034
+ self.cursor_y += 1
1035
+ self.implied_newline = (char != '\n')
1036
+ if self.cursor_y >= self.num_lines:
1037
+ self.cursor_y = 0
1038
+ self.move_to(self.cursor_x, self.cursor_y)
1039
+
1040
+ def putstr(self, string):
1041
+ for char in string: self.putchar(char)
1042
+
1043
+ def custom_char(self, location, charmap):
1044
+ location &= 0x7
1045
+ self.hal_write_command(self.LCD_CGRAM | (location << 3))
1046
+ self.hal_sleep_us(40)
1047
+ for i in range(8):
1048
+ self.hal_write_data(charmap[i])
1049
+ self.hal_sleep_us(40)
1050
+ self.move_to(self.cursor_x, self.cursor_y)
1051
+
1052
+ def hal_backlight_on(self): pass
1053
+ def hal_backlight_off(self): pass
1054
+ def hal_write_command(self, cmd): raise NotImplementedError
1055
+ def hal_write_data(self, data): raise NotImplementedError
1056
+ def hal_sleep_us(self, usecs): sleep(usecs / 1000000.0)
1057
+
1058
+ class I2cLcd(LcdApi):
1059
+ def __init__(self, i2c_id, scl_pin, sda_pin, i2c_addr=0x27, num_lines=2, num_columns=16):
1060
+ from machine import I2C
1061
+ self.i2c = I2C(i2c_id, scl=Pin(scl_pin), sda=Pin(sda_pin), freq=100000)
1062
+ self.i2c_addr = i2c_addr
1063
+ self.i2c.writeto(self.i2c_addr, bytearray([0]))
1064
+ sleep(0.02)
1065
+ self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
1066
+ sleep(0.005)
1067
+ self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
1068
+ sleep(0.001)
1069
+ self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
1070
+ sleep(0.001)
1071
+ self.hal_write_init_nibble(self.LCD_FUNCTION)
1072
+ sleep(0.001)
1073
+ LcdApi.__init__(self, num_lines, num_columns)
1074
+ cmd = self.LCD_FUNCTION
1075
+ if num_lines > 1: cmd |= self.LCD_FUNCTION_2LINES
1076
+ self.hal_write_command(cmd)
1077
+
1078
+ def hal_write_init_nibble(self, nibble):
1079
+ byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
1080
+ self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
1081
+ self.i2c.writeto(self.i2c_addr, bytearray([byte]))
1082
+
1083
+ def hal_backlight_on(self): self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT]))
1084
+ def hal_backlight_off(self): self.i2c.writeto(self.i2c_addr, bytearray([0]))
1085
+
1086
+ def hal_write_command(self, cmd):
1087
+ byte = ((self.backlight << SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << SHIFT_DATA))
1088
+ self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
1089
+ self.i2c.writeto(self.i2c_addr, bytearray([byte]))
1090
+ byte = ((self.backlight << SHIFT_BACKLIGHT) | ((cmd & 0x0f) << SHIFT_DATA))
1091
+ self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
1092
+ self.i2c.writeto(self.i2c_addr, bytearray([byte]))
1093
+ if cmd <= 3: sleep(0.005)
1094
+
1095
+ def hal_write_data(self, data):
1096
+ byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << SHIFT_DATA))
1097
+ self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
1098
+ self.i2c.writeto(self.i2c_addr, bytearray([byte]))
1099
+ byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | ((data & 0x0f) << SHIFT_DATA))
1100
+ self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
1101
+ self.i2c.writeto(self.i2c_addr, bytearray([byte]))
1102
+
1103
+ class GpioLcd(LcdApi):
1104
+ def __init__(self, rs_pin, enable_pin, d0_pin=None, d1_pin=None, d2_pin=None, d3_pin=None,
1105
+ d4_pin=None, d5_pin=None, d6_pin=None, d7_pin=None, rw_pin=None, backlight_pin=None,
1106
+ num_lines=2, num_columns=16):
1107
+ self.rs_pin = Pin(rs_pin, Pin.OUT) if rs_pin is not None else None
1108
+ self.enable_pin = Pin(enable_pin, Pin.OUT) if enable_pin is not None else None
1109
+ self.rw_pin = Pin(rw_pin, Pin.OUT) if rw_pin is not None else None
1110
+ self.backlight_pin = Pin(backlight_pin, Pin.OUT) if backlight_pin is not None else None
1111
+ self._4bit = True
1112
+ if d4_pin and d5_pin and d6_pin and d7_pin:
1113
+ self.d0_pin = Pin(d0_pin, Pin.OUT) if d0_pin is not None else None
1114
+ self.d1_pin = Pin(d1_pin, Pin.OUT) if d1_pin is not None else None
1115
+ self.d2_pin = Pin(d2_pin, Pin.OUT) if d2_pin is not None else None
1116
+ self.d3_pin = Pin(d3_pin, Pin.OUT) if d3_pin is not None else None
1117
+ self.d4_pin = Pin(d4_pin, Pin.OUT)
1118
+ self.d5_pin = Pin(d5_pin, Pin.OUT)
1119
+ self.d6_pin = Pin(d6_pin, Pin.OUT)
1120
+ self.d7_pin = Pin(d7_pin, Pin.OUT)
1121
+ if self.d0_pin and self.d1_pin and self.d2_pin and self.d3_pin:
1122
+ self._4bit = False
1123
+ else:
1124
+ self.d0_pin = self.d1_pin = self.d2_pin = self.d3_pin = None
1125
+ self.d4_pin = Pin(d0_pin, Pin.OUT)
1126
+ self.d5_pin = Pin(d1_pin, Pin.OUT)
1127
+ self.d6_pin = Pin(d2_pin, Pin.OUT)
1128
+ self.d7_pin = Pin(d3_pin, Pin.OUT)
1129
+
1130
+ self.rs_pin.value(0)
1131
+ if self.rw_pin: self.rw_pin.value(0)
1132
+ self.enable_pin.value(0)
1133
+ self.d4_pin.value(0); self.d5_pin.value(0); self.d6_pin.value(0); self.d7_pin.value(0)
1134
+ if not self._4bit:
1135
+ self.d0_pin.value(0); self.d1_pin.value(0); self.d2_pin.value(0); self.d3_pin.value(0)
1136
+ if self.backlight_pin: self.backlight_pin.value(0)
1137
+
1138
+ sleep(0.02)
1139
+ self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
1140
+ sleep(0.005)
1141
+ self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
1142
+ sleep(0.001)
1143
+ self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
1144
+ sleep(0.001)
1145
+ cmd = self.LCD_FUNCTION
1146
+ if not self._4bit: cmd |= self.LCD_FUNCTION_8BIT
1147
+ self.hal_write_init_nibble(cmd)
1148
+ sleep(0.001)
1149
+ LcdApi.__init__(self, num_lines, num_columns)
1150
+ if num_lines > 1: cmd |= self.LCD_FUNCTION_2LINES
1151
+ self.hal_write_command(cmd)
1152
+
1153
+ def hal_pulse_enable(self):
1154
+ self.enable_pin.value(0); sleep(0.000001)
1155
+ self.enable_pin.value(1); sleep(0.000001)
1156
+ self.enable_pin.value(0); sleep(0.0001)
1157
+
1158
+ def hal_write_init_nibble(self, nibble):
1159
+ self.hal_write_4bits(nibble >> 4)
1160
+
1161
+ def hal_backlight_on(self):
1162
+ if self.backlight_pin: self.backlight_pin.value(1)
1163
+
1164
+ def hal_backlight_off(self):
1165
+ if self.backlight_pin: self.backlight_pin.value(0)
1166
+
1167
+ def hal_write_command(self, cmd):
1168
+ self.rs_pin.value(0)
1169
+ self.hal_write_8bits(cmd)
1170
+ if cmd <= 3: sleep(0.005)
1171
+
1172
+ def hal_write_data(self, data):
1173
+ self.rs_pin.value(1)
1174
+ self.hal_write_8bits(data)
1175
+
1176
+ def hal_write_8bits(self, value):
1177
+ if self.rw_pin: self.rw_pin.value(0)
1178
+ if self._4bit:
1179
+ self.hal_write_4bits(value >> 4)
1180
+ self.hal_write_4bits(value)
1181
+ else:
1182
+ self.d3_pin.value(value & 0x08)
1183
+ self.d2_pin.value(value & 0x04)
1184
+ self.d1_pin.value(value & 0x02)
1185
+ self.d0_pin.value(value & 0x01)
1186
+ self.hal_write_4bits(value >> 4)
1187
+
1188
+ def hal_write_4bits(self, nibble):
1189
+ self.d7_pin.value(nibble & 0x08)
1190
+ self.d6_pin.value(nibble & 0x04)
1191
+ self.d5_pin.value(nibble & 0x02)
1192
+ self.d4_pin.value(nibble & 0x01)
1193
+ self.hal_pulse_enable()
1194
+
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: espzero
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Educational tool for physical computing and ESP32/ESP8266 development.
5
5
  Author-email: Neo Neopia-uz <neopia.uz@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/roboticsware/espzero
7
+ Project-URL: Documentation, https://espzero.readthedocs.io
7
8
  Project-URL: Bug Tracker, https://github.com/roboticsware/espzero/issues
8
9
  Classifier: Programming Language :: Python :: 3
9
10
  Classifier: License :: OSI Approved :: MIT License
@@ -14,6 +15,7 @@ Description-Content-Type: text/markdown
14
15
  # espzero
15
16
 
16
17
  [![PyPI version](https://badge.fury.io/py/espzero.svg)](https://badge.fury.io/py/espzero)
18
+ [![Documentation Status](https://readthedocs.org/projects/espzero/badge/?version=latest)](https://espzero.readthedocs.io/)
17
19
  [![License](https://img.shields.io/github/license/roboticsware/espzero)](https://github.com/roboticsware/espzero/blob/main/LICENSE)
18
20
 
19
21
  > A single MicroPython library that ports [picozero](https://github.com/raspberrypilearning/picozero) to the ESP32 family (WROOM, S3, C3, and more).
@@ -5,11 +5,13 @@ _hal.py
5
5
  _touch.py
6
6
  _wifi.py
7
7
  pyproject.toml
8
+ vision.py
8
9
  ./__init__.py
9
10
  ./_core.py
10
11
  ./_hal.py
11
12
  ./_touch.py
12
13
  ./_wifi.py
14
+ ./vision.py
13
15
  espzero.egg-info/PKG-INFO
14
16
  espzero.egg-info/SOURCES.txt
15
17
  espzero.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "espzero"
7
- version = "0.0.2"
7
+ version = "0.0.3"
8
8
  authors = [
9
9
  { name="Neo Neopia-uz", email="neopia.uz@gmail.com" },
10
10
  ]
@@ -19,6 +19,7 @@ classifiers = [
19
19
 
20
20
  [project.urls]
21
21
  "Homepage" = "https://github.com/roboticsware/espzero"
22
+ "Documentation" = "https://espzero.readthedocs.io"
22
23
  "Bug Tracker" = "https://github.com/roboticsware/espzero/issues"
23
24
 
24
25
  [tool.setuptools]
@@ -0,0 +1,59 @@
1
+ """
2
+ espzero/vision.py
3
+ Provides camera streaming and AI vision utilities for ESP32 camera boards.
4
+ """
5
+ import sys
6
+ import time
7
+
8
+ try:
9
+ import camera
10
+ except ImportError:
11
+ camera = None
12
+ print("[espzero] Warning: 'camera' module not found. Vision features will not work.")
13
+
14
+ def start_stream_to_pc(framesize=None, fps=10):
15
+ """
16
+ Captures JPEG frames from the camera and sends them to stdout.
17
+ The Pico Editor intercepts these frames (looking for FF D8 ... FF D9)
18
+ and passes them to MediaPipe for PC-side AI inference.
19
+ """
20
+ if camera is None:
21
+ print("[Vision] Error: Camera module is missing. Are you using a camera-enabled firmware?")
22
+ return
23
+
24
+ if framesize is None:
25
+ framesize = camera.FRAME_QVGA
26
+
27
+ try:
28
+ # Re-initialize camera to ensure correct format
29
+ camera.deinit()
30
+ except Exception:
31
+ pass
32
+
33
+ try:
34
+ camera.init(0, format=camera.JPEG, framesize=framesize)
35
+ except Exception as e:
36
+ print("[Vision] Camera Init Error:", e)
37
+ return
38
+
39
+ delay = 1.0 / fps
40
+ print("[Vision] Starting PC stream ({} fps)... Press Stop in Editor to halt.".format(fps))
41
+
42
+ # Try to get the raw stdout writer to write binary data.
43
+ # In MicroPython, sys.stdout.buffer exists on some ports.
44
+ writer = getattr(sys.stdout, 'buffer', sys.stdout)
45
+
46
+ while True:
47
+ try:
48
+ buf = camera.capture()
49
+ if buf:
50
+ # JPEGs naturally start with \xff\xd8 and end with \xff\xd9.
51
+ # Write binary buffer directly to serial.
52
+ writer.write(buf)
53
+ time.sleep(delay)
54
+ except KeyboardInterrupt:
55
+ print("\n[Vision] Stream stopped by user.")
56
+ break
57
+ except Exception as e:
58
+ print("[Vision] Capture Error:", e)
59
+ time.sleep(1)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes