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.
- {espzero-0.0.2 → espzero-0.0.3}/PKG-INFO +3 -1
- {espzero-0.0.2 → espzero-0.0.3}/README.md +1 -0
- {espzero-0.0.2 → espzero-0.0.3}/__init__.py +6 -0
- {espzero-0.0.2 → espzero-0.0.3}/_core.py +355 -9
- {espzero-0.0.2 → espzero-0.0.3}/espzero.egg-info/PKG-INFO +3 -1
- {espzero-0.0.2 → espzero-0.0.3}/espzero.egg-info/SOURCES.txt +2 -0
- {espzero-0.0.2 → espzero-0.0.3}/pyproject.toml +2 -1
- espzero-0.0.3/vision.py +59 -0
- {espzero-0.0.2 → espzero-0.0.3}/_hal.py +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/_touch.py +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/_wifi.py +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/espzero.egg-info/dependency_links.txt +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/espzero.egg-info/top_level.txt +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/profiles/__init__.py +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/profiles/_base.py +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/profiles/auto.py +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/profiles/esp32_boards.py +0 -0
- {espzero-0.0.2 → espzero-0.0.3}/setup.cfg +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: espzero
|
|
3
|
-
Version: 0.0.
|
|
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
|
[](https://badge.fury.io/py/espzero)
|
|
18
|
+
[](https://espzero.readthedocs.io/)
|
|
17
19
|
[](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
|
[](https://badge.fury.io/py/espzero)
|
|
4
|
+
[](https://espzero.readthedocs.io/)
|
|
4
5
|
[](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.
|
|
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
|
-
|
|
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
|
-
|
|
790
|
+
pass
|
|
791
|
+
return None
|
|
772
792
|
|
|
773
793
|
|
|
774
794
|
class DistanceSensor(PinsMixin):
|
|
775
|
-
def __init__(self, echo, trigger, max_distance=
|
|
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
|
-
|
|
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
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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.
|
|
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
|
[](https://badge.fury.io/py/espzero)
|
|
18
|
+
[](https://espzero.readthedocs.io/)
|
|
17
19
|
[](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).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "espzero"
|
|
7
|
-
version = "0.0.
|
|
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]
|
espzero-0.0.3/vision.py
ADDED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|