From 041d6d90f93bd79c5317dd7cd09761826635cdd9 Mon Sep 17 00:00:00 2001 From: indy Date: Mon, 21 Aug 2023 14:19:08 -0700 Subject: [PATCH 01/65] test code for pi <-> pico i2c --- dev/i2c-comms/pi_i2c.py | 4 ++++ dev/i2c-comms/pico_i2c.c | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/dev/i2c-comms/pi_i2c.py b/dev/i2c-comms/pi_i2c.py index a4b07c7..02e9b31 100644 --- a/dev/i2c-comms/pi_i2c.py +++ b/dev/i2c-comms/pi_i2c.py @@ -16,3 +16,7 @@ while (True): os.write(i2c_fd, bytes(data)) time.sleep(1) + # read data from pico + incoming_data = os.read(i2c_fd, 3) # read 3 bytes + time.sleep(1) + print("Received data from Pico: ", list(incoming_data)); diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c index 948dc18..288a15d 100644 --- a/dev/i2c-comms/pico_i2c.c +++ b/dev/i2c-comms/pico_i2c.c @@ -15,18 +15,22 @@ int main() { i2c_set_slave_mode(i2c0, true, 0x08); // i2c_set_slave_address(i2c0, 0x08); // address should match pi code - uint8_t data[3]; + uint8_t outgoing_data[3] = {0x11, 0x12, 0x13}; // example data + uint8_t incoming_data[3]; while(1) { // read data from i2c_pi.py - i2c_read_blocking(i2c0, 0x08, data, 3, true); + i2c_read_blocking(i2c0, 0x08, incoming_data, 3, true); // process data for (int i = 0; i < 3; i++) { - printf("Received data %d: 0x%02X\n ", i, data[i]); + printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); } + + // send data to pi + i2c_write_blocking(i2c0, 0x08, outgoing_data, 3, true); } return 0; - + } \ No newline at end of file From fe5794f849499512251c1ec5cafc4ad939f9a4f3 Mon Sep 17 00:00:00 2001 From: indy Date: Mon, 21 Aug 2023 14:45:46 -0700 Subject: [PATCH 02/65] spi tests, have not tested them yet --- dev/i2c-comms/pi_spi.c | 57 ++++++++++++++++++++++++++++++++++++++++ dev/i2c-comms/pico_spi.c | 0 2 files changed, 57 insertions(+) create mode 100644 dev/i2c-comms/pi_spi.c create mode 100644 dev/i2c-comms/pico_spi.c diff --git a/dev/i2c-comms/pi_spi.c b/dev/i2c-comms/pi_spi.c new file mode 100644 index 0000000..6de7862 --- /dev/null +++ b/dev/i2c-comms/pi_spi.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include + +#define SPI_DEVICE "/dev/spidev0.0" +#define SPI_SPEED 1000000 // 1MHz + +int main() { + int spi_fd; + unsigned char tx_data[] = {0xAA, 0xBB, 0xCC, 0xDD}; + unsigned char rx_data[sizeof(tx_data)]; + + spi_fd = open(SPI_DEVICE, O_RDWR); + if (spi_fd < 0) { + perror("Error opening SPI device"); + return -1; + } + + int mode = SPI_MODE_0; + if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) == -1) { + perror("Error setting SPI mode"); + return -1; + } + + if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED) == -1) { + perror("Error setting SPI speed"); + return -1; + } + + struct spi_ioc_transfer transfer = { + .tx_buf = (unsigned long)tx_data, + .rx_buf = (unsigned long)rx_data, + .len = sizeof(tx_data), + .speed_hz = SPI_SPEED, + .bits_per_word = 8, + }; + + while (1) { + if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer) == -1) { + perror("Error during SPI message transfer"); + return -1; + } + + printf("Received data: "); + for (int i = 0; i < sizeof(rx_data); i++) { + printf(" %02X", rx_data[i]); + } + printf("\n"); + sleep(1); + + } + close(spi_fd); + return 0; +} \ No newline at end of file diff --git a/dev/i2c-comms/pico_spi.c b/dev/i2c-comms/pico_spi.c new file mode 100644 index 0000000..e69de29 From df0ab2d63f415cec4173eeaf689746bb3c084a31 Mon Sep 17 00:00:00 2001 From: indy Date: Mon, 21 Aug 2023 14:49:27 -0700 Subject: [PATCH 03/65] pico spi test --- dev/i2c-comms/pico_spi.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dev/i2c-comms/pico_spi.c b/dev/i2c-comms/pico_spi.c index e69de29..2e7be5b 100644 --- a/dev/i2c-comms/pico_spi.c +++ b/dev/i2c-comms/pico_spi.c @@ -0,0 +1,32 @@ +#include +#include "pico/stdlib.h" +#include "hardware/spi.h" + +#define SPI_PORT spi0 +#define PIN_MISO 16 +#define PIN_CS 17 + +int main() { + stdio_init_all(); + + spi_init(SPI_PORT, 1000 * 1000); // init at 1MHz + gpio_set_function(PIN_MISO, GPIO_FUNC_SPI); // set pin to SPI mode + + gpi_init(PIN_CS); + gpio_set_dir(PIN_CS, GPIO_OUT); + + while (1) { + gpio_put(PIN_CS, 1); // set CS high to indiciate start of communication + uint8_t rx_data[4]; + spi_read_blocking(SPI_PORT, 0, rx_data, sizeof(rx_data)); // read data from pi + gpio_put(PIN_CS, 0); // set CS low to indicate end of communication + + printf("Received: "); + for (int i = 0; i < sizeof(rx_data); i++) { + printf(" %02X", rx_data[i]); + } + printf("\n"); + sleep_ms(1000); + } + return 0; +} \ No newline at end of file From 1856419aebc4d910c11088f19728541340bad29b Mon Sep 17 00:00:00 2001 From: Anthony Furman <80181074+inkyant@users.noreply.github.com> Date: Sun, 17 Sep 2023 14:23:46 -0700 Subject: [PATCH 04/65] ishan's code from https://github.com/ishanm0/ReadIncrementalEncoderPio Co-Authored-By: Ishan Madan <19366470+ishanm0@users.noreply.github.com> --- dev/encoder/CMakeLists.txt | 24 +++++++ dev/encoder/README.md | 15 +++++ dev/encoder/pico_sdk_import.cmake | 62 +++++++++++++++++ dev/encoder/pio_rotary_encoder.cpp | 105 +++++++++++++++++++++++++++++ dev/encoder/pio_rotary_encoder.pio | 51 ++++++++++++++ 5 files changed, 257 insertions(+) create mode 100644 dev/encoder/CMakeLists.txt create mode 100644 dev/encoder/README.md create mode 100644 dev/encoder/pico_sdk_import.cmake create mode 100644 dev/encoder/pio_rotary_encoder.cpp create mode 100644 dev/encoder/pio_rotary_encoder.pio diff --git a/dev/encoder/CMakeLists.txt b/dev/encoder/CMakeLists.txt new file mode 100644 index 0000000..a3b57bc --- /dev/null +++ b/dev/encoder/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.12) +include(pico_sdk_import.cmake) +project(pio_rotary_encoder VERSION 1.0.0) + +add_executable(pio_rotary_encoder pio_rotary_encoder.cpp) + +pico_sdk_init() + +target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp) + +target_link_libraries(pio_rotary_encoder PRIVATE + pico_stdlib + hardware_pio + ) + +pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.pio) + +pico_add_extra_outputs(pio_rotary_encoder) + +# enable usb output, disable uart output +pico_enable_stdio_usb(pio_rotary_encoder 1) +pico_enable_stdio_uart(pio_rotary_encoder 0) + + diff --git a/dev/encoder/README.md b/dev/encoder/README.md new file mode 100644 index 0000000..263bc4c --- /dev/null +++ b/dev/encoder/README.md @@ -0,0 +1,15 @@ +# ReadIncrementalEncoderPio +Reading high Speed Incremental Encoders with PIO in Rpi Pico and C++ + +You could just boot the .uf2 file to the pico with encoder connected to pins GPio 16 and 17 + +# Encoder Image +![image](https://res.cloudinary.com/rsc/image/upload/b_rgb:FFFFFF,c_pad,dpr_1.0,f_auto,q_auto,w_700/c_pad,w_700/F7450089-01) +# Pico Image +![image](https://hackster.imgix.net/uploads/attachments/1396743/rp2040_0MscTWuAMG.png?auto=compress%2Cformat&w=740&h=555&fit=max) +# Getting Started + +This simple guide is aimed to help you setup and run the program successfully in no time. +# Software Requirements + + follow this link https://shawnhymel.com/2096/how-to-set-up-raspberry-pi-pico-c-c-toolchain-on-windows-with-vs-code/ diff --git a/dev/encoder/pico_sdk_import.cmake b/dev/encoder/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/dev/encoder/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/encoder/pio_rotary_encoder.cpp b/dev/encoder/pio_rotary_encoder.cpp new file mode 100644 index 0000000..c36b303 --- /dev/null +++ b/dev/encoder/pio_rotary_encoder.cpp @@ -0,0 +1,105 @@ +#include + +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "hardware/irq.h" + +#include "pio_rotary_encoder.pio.h" + +// class to read the rotation of the rotary encoder +class RotaryEncoder +{ +public: + // constructor + // rotary_encoder_A is the pin for the A of the rotary encoder. + // The B of the rotary encoder has to be connected to the next GPIO. + RotaryEncoder(uint rotary_encoder_A) + { + uint8_t rotary_encoder_B = rotary_encoder_A + 1; + // pio 0 is used + PIO pio = pio0; + // state machine 0 + uint8_t sm = 0; + // configure the used pins as input with pull up + pio_gpio_init(pio, rotary_encoder_A); + gpio_set_pulls(rotary_encoder_A, true, false); + pio_gpio_init(pio, rotary_encoder_B); + gpio_set_pulls(rotary_encoder_B, true, false); + // load the pio program into the pio memory + uint offset = pio_add_program(pio, &pio_rotary_encoder_program); + // make a sm config + pio_sm_config c = pio_rotary_encoder_program_get_default_config(offset); + // set the 'in' pins + sm_config_set_in_pins(&c, rotary_encoder_A); + // set shift to left: bits shifted by 'in' enter at the least + // significant bit (LSB), no autopush + sm_config_set_in_shift(&c, false, false, 0); + // set the IRQ handler + irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler); + // enable the IRQ + irq_set_enabled(PIO0_IRQ_0, true); + pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_BITS; + // init the sm. + // Note: the program starts after the jump table -> initial_pc = 16 + pio_sm_init(pio, sm, 16, &c); + // enable the sm + pio_sm_set_enabled(pio, sm, true); + } + + // set the current rotation to a specific value + void set_rotation(int _rotation) + { + rotation = _rotation; + } + + // get the current rotation + int get_rotation(void) + { + return rotation; + } + +private: + static void pio_irq_handler() + { + // test if irq 0 was raised + if (pio0_hw->irq & 1) + { + rotation = rotation - 1; + } + // test if irq 1 was raised + if (pio0_hw->irq & 2) + { + rotation = rotation + 1; + } + // clear both interrupts + pio0_hw->irq = 3; + } + + // the pio instance + PIO pio; + // the state machine + uint sm; + // the current location of rotation + static int rotation; +}; + +// Initialize static member of class Rotary_encoder +int RotaryEncoder::rotation = 0; + +int main() +{ + // needed for printf + stdio_init_all(); + // the A of the rotary encoder is connected to GPIO 16, B to GPIO 17 + RotaryEncoder my_encoder(16); + // initialize the rotatry encoder rotation as 0 + my_encoder.set_rotation(0); + // infinite loop to print the current rotation + while (true) + { + sleep_ms(50); + printf("rotation=%d\n", my_encoder.get_rotation()); + // printf("Hello, pico!\n"); + + } +} \ No newline at end of file diff --git a/dev/encoder/pio_rotary_encoder.pio b/dev/encoder/pio_rotary_encoder.pio new file mode 100644 index 0000000..70479f6 --- /dev/null +++ b/dev/encoder/pio_rotary_encoder.pio @@ -0,0 +1,51 @@ + +.program pio_rotary_encoder +.wrap_target +.origin 0 ; The jump table has to start at 0 + ; it contains the correct jumps for each of the 16 + ; combination of 4 bits formed by A'B'AB + ; A = current reading of pin_A of the rotary encoder + ; A' = previous reading of pin_A of the rotary encoder + ; B = current reading of pin_B of the rotary encoder + ; B' = previous reading of pin_B of the rotary encoder + jmp read ; 0000 = from 00 to 00 = no change in reading + jmp CW ; 0001 = from 00 to 01 = clockwise rotation + jmp CCW ; 0010 = from 00 to 10 = counter clockwise rotation + jmp read ; 0011 = from 00 to 11 = error + + jmp CCW ; 0100 = from 01 to 00 = counter clockwise rotation + jmp read ; 0101 = from 01 to 01 = no change in reading + jmp read ; 0110 = from 01 to 10 = error + jmp CW ; 0111 = from 01 to 11 = clockwise rotation + + jmp CW ; 1000 = from 10 to 00 = clockwise rotation + jmp read ; 1001 = from 10 to 01 = error + jmp read ; 1010 = from 10 to 10 = no change in reading + jmp CCW ; 1011 = from 10 to 11 = counter clockwise rotation + + jmp read ; 1100 = from 11 to 00 = error + jmp CCW ; 1101 = from 11 to 01 = counter clockwise rotation + jmp CW ; 1110 = from 11 to 10 = clockwise rotation + jmp read ; 1111 = from 11 to 11 = no change in reading + +pc_start: ; this is the entry point for the program + in pins 2 ; read the current values of A and B and use + ; them to initialize the previous values (A'B') +read: + mov OSR ISR ; the OSR is (after the next instruction) used to shift + ; the two bits with the previous values into the ISR + out ISR 2 ; shift the previous value into the ISR. This also sets + ; all other bits in the ISR to 0 + in pins 2 ; shift the current value into the ISR + ; the 16 LSB of the ISR now contain 000000000000A'B'AB + ; this represents a jmp instruction to the address A'B'AB + mov exec ISR ; do the jmp encoded in the ISR +CW: ; a clockwise rotation was detected + irq 0 ; signal a clockwise rotation via an IRQ + jmp read ; jump to reading the current values of A and B +CCW: ; a counter clockwise rotation was detected + irq 1 ; signal a counter clockwise rotation via an IRQ +; jmp read ; jump to reading the current values of A and B. + ; the jmp isn't needed because of the .wrap, and the first + ; statement of the program happens to be a jmp read +.wrap \ No newline at end of file From 7807d04b0748c15a846589014dd54593aea351ea Mon Sep 17 00:00:00 2001 From: Anthony Furman <80181074+inkyant@users.noreply.github.com> Date: Sun, 17 Sep 2023 17:08:18 -0700 Subject: [PATCH 05/65] NOT TESTED multicore velocity readings from encoder --- dev/encoder/CMakeLists.txt | 2 ++ dev/encoder/pio_rotary_encoder.cpp | 51 ++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/dev/encoder/CMakeLists.txt b/dev/encoder/CMakeLists.txt index a3b57bc..97f5c98 100644 --- a/dev/encoder/CMakeLists.txt +++ b/dev/encoder/CMakeLists.txt @@ -11,6 +11,8 @@ target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp) target_link_libraries(pio_rotary_encoder PRIVATE pico_stdlib hardware_pio + multicore + pico_multicore ) pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.pio) diff --git a/dev/encoder/pio_rotary_encoder.cpp b/dev/encoder/pio_rotary_encoder.cpp index c36b303..a3ec2bc 100644 --- a/dev/encoder/pio_rotary_encoder.cpp +++ b/dev/encoder/pio_rotary_encoder.cpp @@ -6,6 +6,9 @@ #include "pio_rotary_encoder.pio.h" +#include "pico/multicore.h" + + // class to read the rotation of the rotary encoder class RotaryEncoder { @@ -58,8 +61,18 @@ class RotaryEncoder return rotation; } + // get the current estimated velocity + int get_velocity(void) + { + return velocity; + } + + void setup_velocity_multicore(void) { + multicore_launch_core1(velocityLoop); + } + private: - static void pio_irq_handler() + void pio_irq_handler() { // test if irq 0 was raised if (pio0_hw->irq & 1) @@ -75,31 +88,49 @@ class RotaryEncoder pio0_hw->irq = 3; } + void velocityLoop(int delta_time) { + prev_rotation = rotation + while (true) { + sleep_ms(delta_time); + velocity = (rotation - prev_rotation) / delta_time; + prev_rotation = rotation + } + } + // the pio instance PIO pio; // the state machine uint sm; // the current location of rotation - static int rotation; + int rotation = 0; + // the last velocity loop rotation recorded + int prev_rotation = 0; + // the current estimated velocity + int velocity = 0; }; -// Initialize static member of class Rotary_encoder -int RotaryEncoder::rotation = 0; - int main() { // needed for printf stdio_init_all(); // the A of the rotary encoder is connected to GPIO 16, B to GPIO 17 RotaryEncoder my_encoder(16); - // initialize the rotatry encoder rotation as 0 + // initialize the rotary encoder rotation as 0 my_encoder.set_rotation(0); + + my_encoder.setup_velocity_multicore() + + const short LOOP_TIME = 50; // in ms + + int prev_pos = 0; + // infinite loop to print the current rotation while (true) { - sleep_ms(50); - printf("rotation=%d\n", my_encoder.get_rotation()); - // printf("Hello, pico!\n"); - + sleep_ms(LOOP_TIME); + int pos = my_encoder.get_rotation(); + printf("rotation=%d\n", pos); + printf("velocity=%d\n", (pos-prev_pos) / LOOP_TIME); + printf("velocity multicore=%d\n", my_encoder.get_velocity()); } } \ No newline at end of file From 6f9473ae40b25ef95e314796ba92d58ad4fe2e5a Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Wed, 4 Oct 2023 22:37:58 -0700 Subject: [PATCH 06/65] comments/updates --- dev/i2c-comms/pi_i2c.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/i2c-comms/pi_i2c.py b/dev/i2c-comms/pi_i2c.py index 02e9b31..02250f5 100644 --- a/dev/i2c-comms/pi_i2c.py +++ b/dev/i2c-comms/pi_i2c.py @@ -4,7 +4,7 @@ I2C_PRIM = 0x0703 -# open i2c devices +# open i2c devices (sudo apt install i2c-tools) i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) # set the i2c address of pico @@ -19,4 +19,4 @@ # read data from pico incoming_data = os.read(i2c_fd, 3) # read 3 bytes time.sleep(1) - print("Received data from Pico: ", list(incoming_data)); + print("Received data from Pico: ", list(incoming_data)) From fec2fbb11b6c8ec69d55664e5d1417f5b2758b79 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:48:41 -0700 Subject: [PATCH 07/65] experimenting (broken) --- dev/i2c-comms/pico_i2c.c | 54 ++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c index 288a15d..7c1222c 100644 --- a/dev/i2c-comms/pico_i2c.c +++ b/dev/i2c-comms/pico_i2c.c @@ -2,13 +2,24 @@ #include "pico/stdlib.h" #include "hardware/i2c.h" -int main() { +int main() +{ stdio_init_all(); +#ifndef PICO_DEFAULT_LED_PIN +#warning blink example requires a board with a regular LED +#else + const uint LED_PIN = PICO_DEFAULT_LED_PIN; + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); +#endif + // init i2c at 100kHz i2c_init(i2c0, 100 * 1000); gpio_set_function(0, GPIO_FUNC_I2C); gpio_set_function(1, GPIO_FUNC_I2C); + gpio_pull_up(0); + gpio_pull_up(1); // i2c_pullup_en(i2c0, true); // set i2c address for pico @@ -18,19 +29,48 @@ int main() { uint8_t outgoing_data[3] = {0x11, 0x12, 0x13}; // example data uint8_t incoming_data[3]; - while(1) { + while (1) + { + // reset incoming data + for (int i = 0; i < 3; i++) + { + incoming_data[i] = 0x00; + } + + // if (i2c_get_read_available(i2c0) < 3) + // { + // printf("No data available\n"); + // continue; + // } + // read data from i2c_pi.py - i2c_read_blocking(i2c0, 0x08, incoming_data, 3, true); - + // i2c_read_timeout_us(i2c0, 0x0703, incoming_data, 3, false, 50000); + // i2c_read_raw_blocking(i2c0, incoming_data, 3); + int read_error = i2c_read_blocking(i2c0, 0x08, incoming_data, 3, true); + gpio_put(LED_PIN, 1); + + if (incoming_data[0] == 0x00) + { + printf("No data received %d\n", read_error); + continue; + } + + // sleep_ms(250); + // process data - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 3; i++) + { printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); } // send data to pi - i2c_write_blocking(i2c0, 0x08, outgoing_data, 3, true); + int write_error = i2c_write_timeout_us(i2c0, 0x08, outgoing_data, 3, false, 50000); + printf("Write error: %d\n", write_error); + // i2c_write_raw_blocking(i2c0, outgoing_data, 3); + // i2c_write_blocking(i2c0, 0x08, outgoing_data, 3, true); + gpio_put(LED_PIN, 0); + // sleep_ms(250); } return 0; - } \ No newline at end of file From bf2198418a114dcccc05f6c2a3e2fc46ed5e7ddb Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Thu, 5 Oct 2023 23:53:39 -0700 Subject: [PATCH 08/65] bidirectional i2c :) --- dev/i2c-comms/CMakeLists.txt | 5 +- dev/i2c-comms/pi_i2c.py | 19 +++++-- dev/i2c-comms/pico_i2c.c | 107 ++++++++++++++++++----------------- 3 files changed, 71 insertions(+), 60 deletions(-) diff --git a/dev/i2c-comms/CMakeLists.txt b/dev/i2c-comms/CMakeLists.txt index b961952..2fd69ae 100644 --- a/dev/i2c-comms/CMakeLists.txt +++ b/dev/i2c-comms/CMakeLists.txt @@ -34,7 +34,10 @@ pico_enable_stdio_usb(pico_i2c 1) # Add the standard library to the build target_link_libraries(pico_i2c - pico_stdlib) + pico_stdlib + pico_i2c_slave + hardware_i2c + ) # Add the standard include files to the build target_include_directories(pico_i2c PRIVATE diff --git a/dev/i2c-comms/pi_i2c.py b/dev/i2c-comms/pi_i2c.py index 02250f5..c912136 100644 --- a/dev/i2c-comms/pi_i2c.py +++ b/dev/i2c-comms/pi_i2c.py @@ -13,10 +13,17 @@ # send data to pico data = [0x01, 0x02, 0x03] # example data -while (True): - os.write(i2c_fd, bytes(data)) - time.sleep(1) +while True: + try: + os.write(i2c_fd, bytes(data)) + time.sleep(0.02) + print("Sent data to Pico: ", list(data)) + except OSError: + print("Remote I/O Error") # read data from pico - incoming_data = os.read(i2c_fd, 3) # read 3 bytes - time.sleep(1) - print("Received data from Pico: ", list(incoming_data)) + try: + incoming_data = os.read(i2c_fd, 3) # read 3 bytes + time.sleep(0.02) + print("Received data from Pico: ", list(incoming_data)) + except TimeoutError: + print("Timeout Error") diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c index 7c1222c..3880f90 100644 --- a/dev/i2c-comms/pico_i2c.c +++ b/dev/i2c-comms/pico_i2c.c @@ -1,15 +1,63 @@ #include -#include "pico/stdlib.h" -#include "hardware/i2c.h" +#include +#include +#include + +#ifndef PICO_DEFAULT_LED_PIN +#warning blink requires a board with a regular LED +#else +const uint LED_PIN = PICO_DEFAULT_LED_PIN; +#endif + +uint8_t outgoing_data[3] = {0x11, 0x12, 0x13}; // example data +uint8_t incoming_data[3]; +int data_index = 0; + +static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +{ + switch (event) + { + case I2C_SLAVE_RECEIVE: // master has written some data + for (int i = 0; i < 3; i++) + { + if (incoming_data[i] == 0x00) + { + incoming_data[i] = i2c_read_byte_raw(i2c); + printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); + gpio_put(LED_PIN, 1); + break; + } + } + break; + case I2C_SLAVE_REQUEST: // master is requesting data + i2c_write_byte_raw(i2c, outgoing_data[data_index]); + printf("Sent data %d: 0x%02X\n ", data_index, outgoing_data[data_index]); + gpio_put(LED_PIN, 0); + data_index++; + if (data_index > 2) + { + data_index = 0; + } + break; + case I2C_SLAVE_FINISH: // master has signalled Stop / Restart + data_index = 0; + for (int i = 0; i < 3; i++) + { + incoming_data[i] = 0x00; + } + break; + default: + break; + } +} int main() { stdio_init_all(); #ifndef PICO_DEFAULT_LED_PIN -#warning blink example requires a board with a regular LED +#warning blink requires a board with a regular LED #else - const uint LED_PIN = PICO_DEFAULT_LED_PIN; gpio_init(LED_PIN); gpio_set_dir(LED_PIN, GPIO_OUT); #endif @@ -18,59 +66,12 @@ int main() i2c_init(i2c0, 100 * 1000); gpio_set_function(0, GPIO_FUNC_I2C); gpio_set_function(1, GPIO_FUNC_I2C); - gpio_pull_up(0); - gpio_pull_up(1); - // i2c_pullup_en(i2c0, true); // set i2c address for pico - i2c_set_slave_mode(i2c0, true, 0x08); - // i2c_set_slave_address(i2c0, 0x08); // address should match pi code - - uint8_t outgoing_data[3] = {0x11, 0x12, 0x13}; // example data - uint8_t incoming_data[3]; + i2c_slave_init(i2c0, 0x08, &i2c_slave_handler); while (1) - { - // reset incoming data - for (int i = 0; i < 3; i++) - { - incoming_data[i] = 0x00; - } - - // if (i2c_get_read_available(i2c0) < 3) - // { - // printf("No data available\n"); - // continue; - // } - - // read data from i2c_pi.py - // i2c_read_timeout_us(i2c0, 0x0703, incoming_data, 3, false, 50000); - // i2c_read_raw_blocking(i2c0, incoming_data, 3); - int read_error = i2c_read_blocking(i2c0, 0x08, incoming_data, 3, true); - gpio_put(LED_PIN, 1); - - if (incoming_data[0] == 0x00) - { - printf("No data received %d\n", read_error); - continue; - } - - // sleep_ms(250); - - // process data - for (int i = 0; i < 3; i++) - { - printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); - } - - // send data to pi - int write_error = i2c_write_timeout_us(i2c0, 0x08, outgoing_data, 3, false, 50000); - printf("Write error: %d\n", write_error); - // i2c_write_raw_blocking(i2c0, outgoing_data, 3); - // i2c_write_blocking(i2c0, 0x08, outgoing_data, 3, true); - gpio_put(LED_PIN, 0); - // sleep_ms(250); - } + ; return 0; } \ No newline at end of file From bd4662e594ae53fe62d19fc6b7b0e23ab5bc84fc Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:07:42 -0700 Subject: [PATCH 09/65] renamed handler --- dev/i2c-comms/pico_i2c.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c index 3880f90..ac19145 100644 --- a/dev/i2c-comms/pico_i2c.c +++ b/dev/i2c-comms/pico_i2c.c @@ -13,7 +13,7 @@ uint8_t outgoing_data[3] = {0x11, 0x12, 0x13}; // example data uint8_t incoming_data[3]; int data_index = 0; -static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { switch (event) { @@ -45,6 +45,7 @@ static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { incoming_data[i] = 0x00; } + printf("reset\n"); break; default: break; @@ -68,7 +69,7 @@ int main() gpio_set_function(1, GPIO_FUNC_I2C); // set i2c address for pico - i2c_slave_init(i2c0, 0x08, &i2c_slave_handler); + i2c_slave_init(i2c0, 0x08, &i2c_handler); while (1) ; From 27fe1c805134d3e2ef5a9b5790092097d693c66f Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:22:56 -0700 Subject: [PATCH 10/65] created i2c drive python & pico files, set up pico file? --- dev/swerve/CMakeLists.txt | 54 ++++++++++++ dev/swerve/i2c_drive_pi.py | 29 +++++++ dev/swerve/i2c_drive_pico.c | 143 +++++++++++++++++++++++++++++++ dev/swerve/pico_sdk_import.cmake | 73 ++++++++++++++++ 4 files changed, 299 insertions(+) create mode 100644 dev/swerve/CMakeLists.txt create mode 100644 dev/swerve/i2c_drive_pi.py create mode 100644 dev/swerve/i2c_drive_pico.c create mode 100644 dev/swerve/pico_sdk_import.cmake diff --git a/dev/swerve/CMakeLists.txt b/dev/swerve/CMakeLists.txt new file mode 100644 index 0000000..17ad88c --- /dev/null +++ b/dev/swerve/CMakeLists.txt @@ -0,0 +1,54 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(i2c_drive_pico C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(i2c_drive_pico i2c_drive_pico.c ) + +pico_set_program_name(i2c_drive_pico "i2c_drive_pico") +pico_set_program_version(i2c_drive_pico "0.1") + +pico_enable_stdio_uart(i2c_drive_pico 0) +pico_enable_stdio_usb(i2c_drive_pico 1) + +# Add the standard library to the build +target_link_libraries(i2c_drive_pico + pico_stdlib + pico_i2c_slave + hardware_i2c + ) + +# Add the standard include files to the build +target_include_directories(i2c_drive_pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(i2c_drive_pico + hardware_i2c + ) + +pico_add_extra_outputs(i2c_drive_pico) + diff --git a/dev/swerve/i2c_drive_pi.py b/dev/swerve/i2c_drive_pi.py new file mode 100644 index 0000000..3c5e815 --- /dev/null +++ b/dev/swerve/i2c_drive_pi.py @@ -0,0 +1,29 @@ +import fcntl +import os +import time + +I2C_PRIM = 0x0703 + +# open i2c devices (sudo apt install i2c-tools) +i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) + +# set the i2c address of pico +pico_address = 0x08 +fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) + +# send data to pico +data = [0x01, 0x02, 0x03] # example data +while True: + try: + os.write(i2c_fd, bytes(data)) + time.sleep(0.02) + print("Sent data to Pico: ", list(data)) + except OSError: + print("Remote I/O Error") + # read data from pico + try: + incoming_data = os.read(i2c_fd, 1) # read 3 bytes + time.sleep(0.02) + print("Received data from Pico: ", list(incoming_data)) + except TimeoutError: + print("Timeout Error") diff --git a/dev/swerve/i2c_drive_pico.c b/dev/swerve/i2c_drive_pico.c new file mode 100644 index 0000000..c7080e7 --- /dev/null +++ b/dev/swerve/i2c_drive_pico.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include + +#ifndef PICO_DEFAULT_LED_PIN +#warning blink requires a board with a regular LED +#else +const uint LED_PIN = PICO_DEFAULT_LED_PIN; +#endif + +// define variables +#define I2C_PICO_ADDR 0x08 +#define I2C_SDA_PIN 0 +#define I2C_SCL_PIN 1 +#define I2C_PORT i2c0 +#define I2C_BAUDRATE 100 * 1000 + +// length of data packet: 1 byte for start, 2 bytes for data, 1 byte for stop +#define I2C_DATA_LENGTH 4 + +#define MESSAGE_START 0xFA +#define MESSAGE_STOP 0xFB + +// data received from Pi, stored as doubles +uint8_t incoming_data[I2C_DATA_LENGTH]; +// int data_index = 0; // used to keep track of which byte to write next to the pi + +int input_status = 0; // 0 if input is not ready to send to drive (incomplete), 1 if input is ready to send to drive, 2 if input has been sent to drive and has been erased +int last_event = 0; // 0 if FINISH, 1 if RECEIVE, 2 if REQUEST + +double input[I2C_DATA_LENGTH - 2]; // input data from Pi, stored as doubles (doesn't include start and stop bytes) + +static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +{ + switch (event) + { + case I2C_SLAVE_RECEIVE: // pi has written some data + + // find the first empty spot in the incoming_data array, write to it, and break + // if there is no empty spot, the previous instruction has not yet been sent to the drive + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + if (incoming_data[i] == 0x00) + { + incoming_data[i] = i2c_read_byte_raw(i2c); + printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); + gpio_put(LED_PIN, 1); + last_event = 1; + break; + } + } + last_event = 1; + break; + case I2C_SLAVE_REQUEST: // pi is requesting data + + i2c_write_byte_raw(i2c, (uint8_t)input_status); + // i2c_write_byte_raw(i2c, outgoing_data[data_index]); + printf("Sent data %d: 0x%02X\n ", 0, input_status); + gpio_put(LED_PIN, 0); + + // data_index++; + // if (data_index >= I2C_DATA_LENGTH) + // { + // data_index = 0; + // } + last_event = 2; + break; + case I2C_SLAVE_FINISH: // pi has signalled Stop / Restart - called at the end of every receive/request + // data_index = 0; + // for (int i = 0; i < I2C_DATA_LENGTH; i++) + // { + // incoming_data[i] = 0x00; + // } + // printf("reset\n"); + + if (last_event == 1) + { + if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) + { + printf("Received complete message\n"); + + // convert incoming_data to doubles (doesn't include start and stop bytes) + for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) + { + input[i] = (double)incoming_data[i + 1]; + } + input_status = 1; + + // reset incoming_data + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + incoming_data[i] = 0x00; + } + } + else + { + input_status = 0; + printf("Received incomplete message\n"); + } + } + + last_event = 0; + break; + default: + break; + } +} + +int main() +{ + stdio_init_all(); + +#ifndef PICO_DEFAULT_LED_PIN +#warning blink requires a board with a regular LED +#else + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); +#endif + + // init i2c at 100kHz + i2c_init(I2C_PORT, I2C_BAUDRATE); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + + // set i2c address for pico + i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); + + while (1) + { + if (input_status == 1) { + printf("Input: "); + for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) + { + printf("%f ", input[i]); + } + printf("\n"); + input_status = 2; + } + } + + return 0; +} \ No newline at end of file diff --git a/dev/swerve/pico_sdk_import.cmake b/dev/swerve/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/swerve/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) From 52a245c8fead8cae3551cd790c5584e494fe0636 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 13 Oct 2023 10:52:53 -0700 Subject: [PATCH 11/65] experimentation - iirc this does send data between the pi and pico (pi code is currently on the pi) --- dev/swerve/i2c_drive_pico.c | 88 ++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/dev/swerve/i2c_drive_pico.c b/dev/swerve/i2c_drive_pico.c index c7080e7..da2ef76 100644 --- a/dev/swerve/i2c_drive_pico.c +++ b/dev/swerve/i2c_drive_pico.c @@ -3,11 +3,11 @@ #include #include -#ifndef PICO_DEFAULT_LED_PIN -#warning blink requires a board with a regular LED -#else -const uint LED_PIN = PICO_DEFAULT_LED_PIN; -#endif +// #ifndef PICO_DEFAULT_LED_PIN +// #warning blink requires a board with a regular LED +// #else +// const uint LED_PIN = PICO_DEFAULT_LED_PIN; +// #endif // define variables #define I2C_PICO_ADDR 0x08 @@ -24,66 +24,54 @@ const uint LED_PIN = PICO_DEFAULT_LED_PIN; // data received from Pi, stored as doubles uint8_t incoming_data[I2C_DATA_LENGTH]; -// int data_index = 0; // used to keep track of which byte to write next to the pi -int input_status = 0; // 0 if input is not ready to send to drive (incomplete), 1 if input is ready to send to drive, 2 if input has been sent to drive and has been erased -int last_event = 0; // 0 if FINISH, 1 if RECEIVE, 2 if REQUEST +uint8_t input_status = 0; // 0 if input is not ready to send to drive (incomplete), 1 if input is ready to send to drive, 2 if input has been sent to drive and has been erased +int last_event = 0; // 0 if FINISH, 1 if RECEIVE, 2 if REQUEST +int data_index = 0; -double input[I2C_DATA_LENGTH - 2]; // input data from Pi, stored as doubles (doesn't include start and stop bytes) +int input[I2C_DATA_LENGTH - 2]; // input data from Pi, stored as ints (doesn't include start and stop bytes) static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { switch (event) { case I2C_SLAVE_RECEIVE: // pi has written some data - - // find the first empty spot in the incoming_data array, write to it, and break - // if there is no empty spot, the previous instruction has not yet been sent to the drive - for (int i = 0; i < I2C_DATA_LENGTH; i++) + uint8_t tmp = i2c_read_byte_raw(i2c); + // printf("Received byte: 0x%02X\n", tmp); + if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) { - if (incoming_data[i] == 0x00) - { - incoming_data[i] = i2c_read_byte_raw(i2c); - printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); - gpio_put(LED_PIN, 1); - last_event = 1; - break; - } + break; } + incoming_data[data_index] = tmp; + data_index++; + // printf("Received data %d: 0x%02X\n", data_index, tmp); + // gpio_put(LED_PIN, 1); last_event = 1; break; + case I2C_SLAVE_REQUEST: // pi is requesting data i2c_write_byte_raw(i2c, (uint8_t)input_status); - // i2c_write_byte_raw(i2c, outgoing_data[data_index]); - printf("Sent data %d: 0x%02X\n ", 0, input_status); - gpio_put(LED_PIN, 0); - - // data_index++; - // if (data_index >= I2C_DATA_LENGTH) - // { - // data_index = 0; - // } + printf("Sent data %d: 0x%02X\n", 0, input_status); + // gpio_put(LED_PIN, 0); + last_event = 2; break; + case I2C_SLAVE_FINISH: // pi has signalled Stop / Restart - called at the end of every receive/request - // data_index = 0; - // for (int i = 0; i < I2C_DATA_LENGTH; i++) - // { - // incoming_data[i] = 0x00; - // } - // printf("reset\n"); + // printf("last_event: %d\n", last_event); if (last_event == 1) { if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) { + // input_status = 0; printf("Received complete message\n"); // convert incoming_data to doubles (doesn't include start and stop bytes) for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) { - input[i] = (double)incoming_data[i + 1]; + input[i] = (int)incoming_data[i + 1]; } input_status = 1; @@ -95,9 +83,10 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } else { - input_status = 0; + // input_status = 0; printf("Received incomplete message\n"); } + data_index = 0; } last_event = 0; @@ -107,16 +96,21 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } } +int byte_to_motor_double(int input) +{ + return = (double)input / 255.0 * 2.0 - 1.0; +} + int main() { stdio_init_all(); -#ifndef PICO_DEFAULT_LED_PIN -#warning blink requires a board with a regular LED -#else - gpio_init(LED_PIN); - gpio_set_dir(LED_PIN, GPIO_OUT); -#endif + // #ifndef PICO_DEFAULT_LED_PIN + // #warning blink requires a board with a regular LED + // #else + // gpio_init(LED_PIN); + // gpio_set_dir(LED_PIN, GPIO_OUT); + // #endif // init i2c at 100kHz i2c_init(I2C_PORT, I2C_BAUDRATE); @@ -128,14 +122,16 @@ int main() while (1) { - if (input_status == 1) { + printf("Status: %d\n", input_status); + if (input_status == 1) + { printf("Input: "); for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) { printf("%f ", input[i]); } printf("\n"); - input_status = 2; + // input_status = 2; } } From 2abf76584a34a314705ec9e7f275899ec8ac3a28 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 14 Oct 2023 12:02:38 -0700 Subject: [PATCH 12/65] 2 way pi pico i2c - python --- dev/swerve/i2c_drive_pi.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dev/swerve/i2c_drive_pi.py b/dev/swerve/i2c_drive_pi.py index 3c5e815..1e8e6e4 100644 --- a/dev/swerve/i2c_drive_pi.py +++ b/dev/swerve/i2c_drive_pi.py @@ -12,18 +12,20 @@ fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) # send data to pico -data = [0x01, 0x02, 0x03] # example data +data = [0xFA, 120, 25, 0xFB, 0x00] # example data +delay = 0.05 + while True: try: os.write(i2c_fd, bytes(data)) - time.sleep(0.02) + time.sleep(delay) print("Sent data to Pico: ", list(data)) except OSError: print("Remote I/O Error") # read data from pico try: incoming_data = os.read(i2c_fd, 1) # read 3 bytes - time.sleep(0.02) + time.sleep(delay) print("Received data from Pico: ", list(incoming_data)) except TimeoutError: print("Timeout Error") From 6bde8fd4d266566931eff9bf6189db591fe25b50 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:05:00 -0700 Subject: [PATCH 13/65] continued working on motor/pico/i2c tests Co-authored-by: brendanRose1 Co-authored-by: ananya-manduva --- dev/swerve/i2c_drive_pi.py | 9 ++++++++- dev/swerve/i2c_drive_pico.c | 2 +- dev/swerve/motor_test.py | 10 +++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/dev/swerve/i2c_drive_pi.py b/dev/swerve/i2c_drive_pi.py index 1e8e6e4..42fb087 100644 --- a/dev/swerve/i2c_drive_pi.py +++ b/dev/swerve/i2c_drive_pi.py @@ -1,6 +1,7 @@ import fcntl import os import time +from Controller import Controller I2C_PRIM = 0x0703 @@ -12,10 +13,16 @@ fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) # send data to pico -data = [0xFA, 120, 25, 0xFB, 0x00] # example data +joy = Controller() delay = 0.05 while True: + status = joy.read_self() + x = status.LeftJoystickX + y = status.RightJoystickY + joystickswitch = x>0 + data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] # example data + try: os.write(i2c_fd, bytes(data)) time.sleep(delay) diff --git a/dev/swerve/i2c_drive_pico.c b/dev/swerve/i2c_drive_pico.c index da2ef76..df497c0 100644 --- a/dev/swerve/i2c_drive_pico.c +++ b/dev/swerve/i2c_drive_pico.c @@ -98,7 +98,7 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) int byte_to_motor_double(int input) { - return = (double)input / 255.0 * 2.0 - 1.0; + return (double)input / 255.0 * 2.0 - 1.0; } int main() diff --git a/dev/swerve/motor_test.py b/dev/swerve/motor_test.py index 4996602..ddb07e1 100644 --- a/dev/swerve/motor_test.py +++ b/dev/swerve/motor_test.py @@ -2,13 +2,13 @@ from Controller import Controller -turn_in1_pin = 22 +turn_in1_pin = 17 turn_in2_pin = 27 -turn_pwm_pin = 17 +turn_pwm_pin = 22 -wheel_in1_pin = 4 -wheel_in2_pin = 3 -wheel_pwm_pin = 2 +wheel_in1_pin = 10 +wheel_in2_pin = 9 +wheel_pwm_pin = 11 freq = 500 disable = True From c28954e90ed66baf8e3111efb982dc69aba9dab8 Mon Sep 17 00:00:00 2001 From: Anthony Furman <80181074+inkyant@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:18:48 -0700 Subject: [PATCH 14/65] fix prev_pos not being updated --- dev/encoder/pio_rotary_encoder.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev/encoder/pio_rotary_encoder.cpp b/dev/encoder/pio_rotary_encoder.cpp index a3ec2bc..ef721f1 100644 --- a/dev/encoder/pio_rotary_encoder.cpp +++ b/dev/encoder/pio_rotary_encoder.cpp @@ -128,9 +128,13 @@ int main() while (true) { sleep_ms(LOOP_TIME); + int pos = my_encoder.get_rotation(); + printf("rotation=%d\n", pos); printf("velocity=%d\n", (pos-prev_pos) / LOOP_TIME); printf("velocity multicore=%d\n", my_encoder.get_velocity()); + + prev_pos = pos; } } \ No newline at end of file From d886c5113722bdb29133280a0001c265e4719b85 Mon Sep 17 00:00:00 2001 From: Anthony Furman <80181074+inkyant@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:54:52 -0700 Subject: [PATCH 15/65] move multicore velocity init to constructor --- dev/encoder/pio_rotary_encoder.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/dev/encoder/pio_rotary_encoder.cpp b/dev/encoder/pio_rotary_encoder.cpp index ef721f1..bf2f7d2 100644 --- a/dev/encoder/pio_rotary_encoder.cpp +++ b/dev/encoder/pio_rotary_encoder.cpp @@ -47,6 +47,9 @@ class RotaryEncoder pio_sm_init(pio, sm, 16, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); + + // set up second core on PICO to run velocity loop + multicore_launch_core1(velocityLoop); } // set the current rotation to a specific value @@ -67,10 +70,6 @@ class RotaryEncoder return velocity; } - void setup_velocity_multicore(void) { - multicore_launch_core1(velocityLoop); - } - private: void pio_irq_handler() { @@ -88,12 +87,13 @@ class RotaryEncoder pio0_hw->irq = 3; } - void velocityLoop(int delta_time) { - prev_rotation = rotation + void velocityLoop() { + int delta_time = 50; + prev_rotation = rotation; while (true) { sleep_ms(delta_time); velocity = (rotation - prev_rotation) / delta_time; - prev_rotation = rotation + prev_rotation = rotation; } } @@ -118,8 +118,6 @@ int main() // initialize the rotary encoder rotation as 0 my_encoder.set_rotation(0); - my_encoder.setup_velocity_multicore() - const short LOOP_TIME = 50; // in ms int prev_pos = 0; @@ -128,7 +126,7 @@ int main() while (true) { sleep_ms(LOOP_TIME); - + int pos = my_encoder.get_rotation(); printf("rotation=%d\n", pos); From 42b473a49e35f667342eeafc87a1aa8da9ae6c76 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:36:06 -0700 Subject: [PATCH 16/65] Update motor_test.py --- dev/swerve/motor_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/swerve/motor_test.py b/dev/swerve/motor_test.py index ddb07e1..c5e6860 100644 --- a/dev/swerve/motor_test.py +++ b/dev/swerve/motor_test.py @@ -6,9 +6,9 @@ turn_in2_pin = 27 turn_pwm_pin = 22 -wheel_in1_pin = 10 +wheel_in1_pin = 11 wheel_in2_pin = 9 -wheel_pwm_pin = 11 +wheel_pwm_pin = 10 freq = 500 disable = True From c79b48a0c0285169c974191df1a3266a08a01409 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:32:58 -0700 Subject: [PATCH 17/65] we can get encoder velocity, but it crashes if the motor is going too fast (only in the negative direction though) Co-authored-by: inkyant --- dev/encoder/CMakeLists.txt | 1 - dev/encoder/pio_rotary_encoder.cpp | 35 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/dev/encoder/CMakeLists.txt b/dev/encoder/CMakeLists.txt index 97f5c98..25a3303 100644 --- a/dev/encoder/CMakeLists.txt +++ b/dev/encoder/CMakeLists.txt @@ -11,7 +11,6 @@ target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp) target_link_libraries(pio_rotary_encoder PRIVATE pico_stdlib hardware_pio - multicore pico_multicore ) diff --git a/dev/encoder/pio_rotary_encoder.cpp b/dev/encoder/pio_rotary_encoder.cpp index bf2f7d2..12776be 100644 --- a/dev/encoder/pio_rotary_encoder.cpp +++ b/dev/encoder/pio_rotary_encoder.cpp @@ -8,7 +8,6 @@ #include "pico/multicore.h" - // class to read the rotation of the rotary encoder class RotaryEncoder { @@ -38,7 +37,7 @@ class RotaryEncoder // significant bit (LSB), no autopush sm_config_set_in_shift(&c, false, false, 0); // set the IRQ handler - irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler); + irq_set_exclusive_handler(PIO0_IRQ_0, this->pio_irq_handler); // enable the IRQ irq_set_enabled(PIO0_IRQ_0, true); pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_BITS; @@ -49,7 +48,7 @@ class RotaryEncoder pio_sm_set_enabled(pio, sm, true); // set up second core on PICO to run velocity loop - multicore_launch_core1(velocityLoop); + multicore_launch_core1(this->velocityLoop); } // set the current rotation to a specific value @@ -65,13 +64,13 @@ class RotaryEncoder } // get the current estimated velocity - int get_velocity(void) + float get_velocity(void) { return velocity; } private: - void pio_irq_handler() + static void pio_irq_handler() { // test if irq 0 was raised if (pio0_hw->irq & 1) @@ -87,12 +86,14 @@ class RotaryEncoder pio0_hw->irq = 3; } - void velocityLoop() { + static void velocityLoop() + { int delta_time = 50; prev_rotation = rotation; - while (true) { + while (true) + { sleep_ms(delta_time); - velocity = (rotation - prev_rotation) / delta_time; + velocity = ((float)(rotation - prev_rotation)) / ((float)delta_time); prev_rotation = rotation; } } @@ -102,13 +103,18 @@ class RotaryEncoder // the state machine uint sm; // the current location of rotation - int rotation = 0; + static int rotation; // the last velocity loop rotation recorded - int prev_rotation = 0; + static int prev_rotation; // the current estimated velocity - int velocity = 0; + static float velocity; }; +// Initialize static member of class Rotary_encoder +int RotaryEncoder::rotation = 0; +int RotaryEncoder::prev_rotation = 0; +float RotaryEncoder::velocity = 0; + int main() { // needed for printf @@ -120,8 +126,6 @@ int main() const short LOOP_TIME = 50; // in ms - int prev_pos = 0; - // infinite loop to print the current rotation while (true) { @@ -130,9 +134,6 @@ int main() int pos = my_encoder.get_rotation(); printf("rotation=%d\n", pos); - printf("velocity=%d\n", (pos-prev_pos) / LOOP_TIME); - printf("velocity multicore=%d\n", my_encoder.get_velocity()); - - prev_pos = pos; + printf("velocity=%f\n", my_encoder.get_velocity()); } } \ No newline at end of file From e995e7d4a2271545b3e19c378ba38fe67addb6a6 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Mon, 16 Oct 2023 22:58:57 -0700 Subject: [PATCH 18/65] Moved to quadrature_encoder project based on pico-examples code Co-authored-by: Anthony Furman <80181074+inkyant@users.noreply.github.com> --- dev/encoder/CMakeLists.txt | 17 +-- dev/encoder/pio_rotary_encoder/CMakeLists.txt | 25 ++++ .../pio_rotary_encoder.cpp | 57 ++++--- .../pio_rotary_encoder.pio | 0 dev/encoder/quadrature_encoder.cpp | 114 ++++++++++++++ dev/encoder/quadrature_encoder.pio | 141 ++++++++++++++++++ 6 files changed, 328 insertions(+), 26 deletions(-) create mode 100644 dev/encoder/pio_rotary_encoder/CMakeLists.txt rename dev/encoder/{ => pio_rotary_encoder}/pio_rotary_encoder.cpp (71%) rename dev/encoder/{ => pio_rotary_encoder}/pio_rotary_encoder.pio (100%) create mode 100644 dev/encoder/quadrature_encoder.cpp create mode 100644 dev/encoder/quadrature_encoder.pio diff --git a/dev/encoder/CMakeLists.txt b/dev/encoder/CMakeLists.txt index 25a3303..c2e187c 100644 --- a/dev/encoder/CMakeLists.txt +++ b/dev/encoder/CMakeLists.txt @@ -1,25 +1,24 @@ cmake_minimum_required(VERSION 3.12) include(pico_sdk_import.cmake) -project(pio_rotary_encoder VERSION 1.0.0) +project(quadrature_encoder VERSION 1.0.0) -add_executable(pio_rotary_encoder pio_rotary_encoder.cpp) +add_executable(quadrature_encoder quadrature_encoder.cpp) pico_sdk_init() -target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp) +target_sources(quadrature_encoder PRIVATE quadrature_encoder.cpp) -target_link_libraries(pio_rotary_encoder PRIVATE +target_link_libraries(quadrature_encoder PRIVATE pico_stdlib hardware_pio - pico_multicore ) -pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.pio) +pico_generate_pio_header(quadrature_encoder ${CMAKE_CURRENT_LIST_DIR}/quadrature_encoder.pio) -pico_add_extra_outputs(pio_rotary_encoder) +pico_add_extra_outputs(quadrature_encoder) # enable usb output, disable uart output -pico_enable_stdio_usb(pio_rotary_encoder 1) -pico_enable_stdio_uart(pio_rotary_encoder 0) +pico_enable_stdio_usb(quadrature_encoder 1) +pico_enable_stdio_uart(quadrature_encoder 0) diff --git a/dev/encoder/pio_rotary_encoder/CMakeLists.txt b/dev/encoder/pio_rotary_encoder/CMakeLists.txt new file mode 100644 index 0000000..25a3303 --- /dev/null +++ b/dev/encoder/pio_rotary_encoder/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.12) +include(pico_sdk_import.cmake) +project(pio_rotary_encoder VERSION 1.0.0) + +add_executable(pio_rotary_encoder pio_rotary_encoder.cpp) + +pico_sdk_init() + +target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp) + +target_link_libraries(pio_rotary_encoder PRIVATE + pico_stdlib + hardware_pio + pico_multicore + ) + +pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.pio) + +pico_add_extra_outputs(pio_rotary_encoder) + +# enable usb output, disable uart output +pico_enable_stdio_usb(pio_rotary_encoder 1) +pico_enable_stdio_uart(pio_rotary_encoder 0) + + diff --git a/dev/encoder/pio_rotary_encoder.cpp b/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.cpp similarity index 71% rename from dev/encoder/pio_rotary_encoder.cpp rename to dev/encoder/pio_rotary_encoder/pio_rotary_encoder.cpp index 12776be..1aea5f6 100644 --- a/dev/encoder/pio_rotary_encoder.cpp +++ b/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.cpp @@ -36,11 +36,18 @@ class RotaryEncoder // set shift to left: bits shifted by 'in' enter at the least // significant bit (LSB), no autopush sm_config_set_in_shift(&c, false, false, 0); + + // set the IRQ handler - irq_set_exclusive_handler(PIO0_IRQ_0, this->pio_irq_handler); + irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler_0); // enable the IRQ irq_set_enabled(PIO0_IRQ_0, true); + + // gpio_set_irq_enabled_with_callback(21, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); + // gpio_set_irq_enabled(22, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); + pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_BITS; + // init the sm. // Note: the program starts after the jump table -> initial_pc = 16 pio_sm_init(pio, sm, 16, &c); @@ -48,7 +55,7 @@ class RotaryEncoder pio_sm_set_enabled(pio, sm, true); // set up second core on PICO to run velocity loop - multicore_launch_core1(this->velocityLoop); + // multicore_launch_core1(velocityLoop); } // set the current rotation to a specific value @@ -69,8 +76,16 @@ class RotaryEncoder return velocity; } + // non-blocking step, call every delta_time ms + static void velocityLoopStep(int delta_time) + { + velocity = ((float)(rotation - prev_rotation)) / ((float)delta_time); + prev_rotation = rotation; + } + + private: - static void pio_irq_handler() + static void pio_irq_handler_0() { // test if irq 0 was raised if (pio0_hw->irq & 1) @@ -86,17 +101,22 @@ class RotaryEncoder pio0_hw->irq = 3; } - static void velocityLoop() - { - int delta_time = 50; - prev_rotation = rotation; - while (true) - { - sleep_ms(delta_time); - velocity = ((float)(rotation - prev_rotation)) / ((float)delta_time); - prev_rotation = rotation; - } + static void gpio_callback(uint gpio, uint32_t events) { + printf("%d\n", gpio) } + + + // for running the velocity loop on another core, just pass this function + // static void velocityLoop() + // { + // int delta_time = 50; + // prev_rotation = rotation; + // while (true) + // { + // sleep_ms(delta_time) + // velocityLoopStep(delta_time) + // } + // } // the pio instance PIO pio; @@ -119,12 +139,14 @@ int main() { // needed for printf stdio_init_all(); - // the A of the rotary encoder is connected to GPIO 16, B to GPIO 17 - RotaryEncoder my_encoder(16); + // the A of the rotary encoder is connected to GPIO 14, B to GPIO 15 + RotaryEncoder my_encoder(14); // initialize the rotary encoder rotation as 0 my_encoder.set_rotation(0); - const short LOOP_TIME = 50; // in ms + const short LOOP_TIME = 20; // in ms + + const float TICKS_PER_DEGREE = 374.0 / 360.0; // infinite loop to print the current rotation while (true) @@ -132,8 +154,9 @@ int main() sleep_ms(LOOP_TIME); int pos = my_encoder.get_rotation(); + my_encoder.velocityLoopStep(LOOP_TIME); printf("rotation=%d\n", pos); - printf("velocity=%f\n", my_encoder.get_velocity()); + printf("velocity=%f\n", my_encoder.get_velocity() / TICKS_PER_DEGREE * 1000.0); } } \ No newline at end of file diff --git a/dev/encoder/pio_rotary_encoder.pio b/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.pio similarity index 100% rename from dev/encoder/pio_rotary_encoder.pio rename to dev/encoder/pio_rotary_encoder/pio_rotary_encoder.pio diff --git a/dev/encoder/quadrature_encoder.cpp b/dev/encoder/quadrature_encoder.cpp new file mode 100644 index 0000000..b09cf39 --- /dev/null +++ b/dev/encoder/quadrature_encoder.cpp @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "hardware/timer.h" + +#include "quadrature_encoder.pio.h" + +// +// ---- quadrature encoder interface example +// +// the PIO program reads phase A/B of a quadrature encoder and increments or +// decrements an internal counter to keep the current absolute step count +// updated. At any point, the main code can query the current count by using +// the quadrature_encoder_*_count functions. The counter is kept in a full +// 32 bit register that just wraps around. Two's complement arithmetic means +// that it can be interpreted as a 32-bit signed or unsigned value, and it will +// work anyway. +// +// As an example, a two wheel robot being controlled at 100Hz, can use two +// state machines to read the two encoders and in the main control loop it can +// simply ask for the current encoder counts to get the absolute step count. It +// can also subtract the values from the last sample to check how many steps +// each wheel as done since the last sample period. +// +// One advantage of this approach is that it requires zero CPU time to keep the +// encoder count updated and because of that it supports very high step rates. +// + +class Encoder +{ +public: + Encoder(uint pinA, uint sm, PIO pio, uint offset) + { + this->pio = pio; + this->sm = sm; + quadrature_encoder_program_init(pio, sm, offset, pinA, 0); + } + + void update(int delta_time) + { + pos = quadrature_encoder_get_count(pio, sm); + velocity = ((float)(prev_pos - pos)) / delta_time * 360.0 / 374.0 * 1000.0; + prev_pos = pos; + } + + int get_pos() + { + return pos; + } + + float get_velocity() + { + return velocity; + } + +private: + int prev_pos, pos; + float velocity; + PIO pio; + uint sm; +}; + +int +main() +{ + // int new_value_steer, delta_steer, old_value_steer = 0; + // int new_value_drive, delta_drive, old_value_drive = 0; + + // Base pin to connect the A phase of the encoder. + // The B phase must be connected to the next pin + const uint PIN_STEER = 14; + const uint PIN_DRIVE = 16; + + stdio_init_all(); + + // PIO pio = pio0; + const uint sm_steer = 0; + const uint sm_drive = 1; + + // we don't really need to keep the offset, as this program must be loaded + // at offset 0 + uint offset0 = pio_add_program(pio0, &quadrature_encoder_program); + // quadrature_encoder_program_init(pio0, sm_steer, offset0, PIN_STEER, 0); + // quadrature_encoder_program_init(pio0, sm_drive, offset0, PIN_DRIVE, 0); + Encoder steer = Encoder(PIN_STEER, sm_steer, pio0, offset0); + Encoder drive = Encoder(PIN_DRIVE, sm_drive, pio0, offset0); + + while (1) + { + // note: thanks to two's complement arithmetic delta will always + // be correct even when new_value wraps around MAXINT / MININT + // new_value_steer = quadrature_encoder_get_count(pio0, sm_steer); + // new_value_drive = quadrature_encoder_get_count(pio0, sm_drive); + + // delta_steer = new_value_steer - old_value_steer; + // delta_drive = new_value_drive - old_value_drive; + + // old_value_steer = new_value_steer; + // old_value_drive = new_value_drive; + + steer.update(20); + drive.update(20); + + printf("steer position %8d, velocity %6f\n", steer.get_pos(), steer.get_velocity()); + printf("drive position %8d, velocity %6f\n", drive.get_pos(), drive.get_velocity()); + sleep_ms(20); + } +} diff --git a/dev/encoder/quadrature_encoder.pio b/dev/encoder/quadrature_encoder.pio new file mode 100644 index 0000000..8b1e618 --- /dev/null +++ b/dev/encoder/quadrature_encoder.pio @@ -0,0 +1,141 @@ +; +; Copyright (c) 2023 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program quadrature_encoder + +; the code must be loaded at address 0, because it uses computed jumps +.origin 0 + + +; the code works by running a loop that continuously shifts the 2 phase pins into +; ISR and looks at the lower 4 bits to do a computed jump to an instruction that +; does the proper "do nothing" | "increment" | "decrement" action for that pin +; state change (or no change) + +; ISR holds the last state of the 2 pins during most of the code. The Y register +; keeps the current encoder count and is incremented / decremented according to +; the steps sampled + +; the program keeps trying to write the current count to the RX FIFO without +; blocking. To read the current count, the user code must drain the FIFO first +; and wait for a fresh sample (takes ~4 SM cycles on average). The worst case +; sampling loop takes 10 cycles, so this program is able to read step rates up +; to sysclk / 10 (e.g., sysclk 125MHz, max step rate = 12.5 Msteps/sec) + +; 00 state + JMP update ; read 00 + JMP decrement ; read 01 + JMP increment ; read 10 + JMP update ; read 11 + +; 01 state + JMP increment ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP decrement ; read 11 + +; 10 state + JMP decrement ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP increment ; read 11 + +; to reduce code size, the last 2 states are implemented in place and become the +; target for the other jumps + +; 11 state + JMP update ; read 00 + JMP increment ; read 01 +decrement: + ; note: the target of this instruction must be the next address, so that + ; the effect of the instruction does not depend on the value of Y. The + ; same is true for the "JMP X--" below. Basically "JMP Y--, " + ; is just a pure "decrement Y" instruction, with no other side effects + JMP Y--, update ; read 10 + + ; this is where the main loop starts +.wrap_target +update: + MOV ISR, Y ; read 11 + PUSH noblock + +sample_pins: + ; we shift into ISR the last state of the 2 input pins (now in OSR) and + ; the new state of the 2 pins, thus producing the 4 bit target for the + ; computed jump into the correct action for this state. Both the PUSH + ; above and the OUT below zero out the other bits in ISR + OUT ISR, 2 + IN PINS, 2 + + ; save the state in the OSR, so that we can use ISR for other purposes + MOV OSR, ISR + ; jump to the correct state machine action + MOV PC, ISR + + ; the PIO does not have a increment instruction, so to do that we do a + ; negate, decrement, negate sequence +increment: + MOV Y, ~Y + JMP Y--, increment_cont +increment_cont: + MOV Y, ~Y +.wrap ; the .wrap here avoids one jump instruction and saves a cycle too + + + +% c-sdk { + +#include "hardware/clocks.h" +#include "hardware/gpio.h" + +// max_step_rate is used to lower the clock of the state machine to save power +// if the application doesn't require a very high sampling rate. Passing zero +// will set the clock to the maximum + +static inline void quadrature_encoder_program_init(PIO pio, uint sm, uint offset, uint pin, int max_step_rate) +{ + pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false); + gpio_pull_up(pin); + gpio_pull_up(pin + 1); + + pio_sm_config c = quadrature_encoder_program_get_default_config(offset); + + sm_config_set_in_pins(&c, pin); // for WAIT, IN + sm_config_set_jmp_pin(&c, pin); // for JMP + // shift to left, autopull disabled + sm_config_set_in_shift(&c, false, false, 32); + // don't join FIFO's + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); + + // passing "0" as the sample frequency, + if (max_step_rate == 0) { + sm_config_set_clkdiv(&c, 1.0); + } else { + // one state machine loop takes at most 10 cycles + float div = (float)clock_get_hz(clk_sys) / (10 * max_step_rate); + sm_config_set_clkdiv(&c, div); + } + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +static inline int32_t quadrature_encoder_get_count(PIO pio, uint sm) +{ + uint ret; + int n; + + // if the FIFO has N entries, we fetch them to drain the FIFO, + // plus one entry which will be guaranteed to not be stale + n = pio_sm_get_rx_fifo_level(pio, sm) + 1; + while (n > 0) { + ret = pio_sm_get_blocking(pio, sm); + n--; + } + return ret; +} + +%} From 00e06288648e136c1eeec48605c2f72581033ae4 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Tue, 24 Oct 2023 00:07:23 -0700 Subject: [PATCH 19/65] Created pico control test Converted Pi control of motors in python (motor test) to Pico control of motors in c (cooler motor test) --- dev/swerve/cooler_motor_test.c | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 dev/swerve/cooler_motor_test.c diff --git a/dev/swerve/cooler_motor_test.c b/dev/swerve/cooler_motor_test.c new file mode 100644 index 0000000..c6b5144 --- /dev/null +++ b/dev/swerve/cooler_motor_test.c @@ -0,0 +1,68 @@ +#include "pico/stdlib.h" +#include "hardware/pwm.h" + +// digital low on in# pins indicates direction, both high is no signal +#define turn_in1_pin 4 // 1A, forward direction +#define turn_in2_pin 5 // 1B, backward direction + +#define motor_pwm_pin 9 //2A, 2B take up by motor speed +#define turn_channel 0 // 2A, turn motor speed +#define wheel_channel 1 // 2B, wheel motor speed + +#define wheel_in1_pin 6 // 3A, forward direction +#define wheel_in2_pin 7 // 3B, backard direction + + +#define freq 500 //note: use clock management frequencies to set frequency +#define duty_cycle = 1 +#define count_max = 65535 + +void setup(){ // setup pins for pwm functions + gpio_init(turn_in1_pin); + gpio_init(turn_in2_pin); + gpio_init(wheel_in1_pin); + gpio_init(wheel_in2_pin); + + //check if default output signal is 0, for now put this in + gpio_put(turn_in1_pin, 0); + gpio_put(turn_in2_pin, 0); + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 0); + + gpio_set_dir(turn_in1_pin, GPIO_OUT); + gpio_set_dir(turn_in2_pin, GPIO_OUT); + gpio_set_dir(wheel_in1_pin, GPIO_OUT); + gpio_set_dir(wheel_in2_pin, GPIO_OUT); + + gpio_set_function(motor_pwm_pin, GPIO_FUNC_PWM); +} + +int main(){ + setup(); + int x, y = 0; + + // turn motor + if(x == 0){ // in1 and in2 are high + //pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + }else if(x < 0){ // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + }else{ // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + } + + // wheel motor + if(y == 0){ // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + }else if(y < 0){ // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + }else{ // in1 is low and in2 is high + gpio_put(wheel_in2_pin, 1); + } + + pwm_set_chan_level(motor_pwm_pin,turn_channel,x*655); + pwm_set_chan_level(motor_pwm_pin,wheel_channel,y*655); + return 0; +} From b0de0a18beceda3a015c82612749d4211bab12f5 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Thu, 26 Oct 2023 20:31:57 -0700 Subject: [PATCH 20/65] yes --- dev/swerve/CMakeLists.txt | 2 +- dev/swerve/cooler_motor_test.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/swerve/CMakeLists.txt b/dev/swerve/CMakeLists.txt index 17ad88c..3fb52ee 100644 --- a/dev/swerve/CMakeLists.txt +++ b/dev/swerve/CMakeLists.txt @@ -24,7 +24,7 @@ pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(i2c_drive_pico i2c_drive_pico.c ) +add_executable(i2c_drive_pico cooler_motor_test.c ) pico_set_program_name(i2c_drive_pico "i2c_drive_pico") pico_set_program_version(i2c_drive_pico "0.1") diff --git a/dev/swerve/cooler_motor_test.c b/dev/swerve/cooler_motor_test.c index c6b5144..ba63c84 100644 --- a/dev/swerve/cooler_motor_test.c +++ b/dev/swerve/cooler_motor_test.c @@ -6,8 +6,8 @@ #define turn_in2_pin 5 // 1B, backward direction #define motor_pwm_pin 9 //2A, 2B take up by motor speed -#define turn_channel 0 // 2A, turn motor speed -#define wheel_channel 1 // 2B, wheel motor speed +#define turn_channel 9 // 2A, turn motor speed +#define wheel_channel 8 // 2B, wheel motor speed #define wheel_in1_pin 6 // 3A, forward direction #define wheel_in2_pin 7 // 3B, backard direction From 96961a8ebdb6741fbbd0e9633a80e646ee13d18e Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:38:50 -0700 Subject: [PATCH 21/65] pico motor control works :) Co-authored-by: brendanRose1 Co-authored-by: ananya-manduva --- dev/i2c-comms/pico_i2c.c | 4 +- dev/swerve/CMakeLists.txt | 1 + dev/swerve/cooler_motor_test.c | 134 ++++++++++++++++++++++++--------- 3 files changed, 100 insertions(+), 39 deletions(-) diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c index ac19145..7bde92b 100644 --- a/dev/i2c-comms/pico_i2c.c +++ b/dev/i2c-comms/pico_i2c.c @@ -9,7 +9,7 @@ const uint LED_PIN = PICO_DEFAULT_LED_PIN; #endif -uint8_t outgoing_data[3] = {0x11, 0x12, 0x13}; // example data +uint8_t outgoing_data[4] = {0x11, 0x12, 0x13, 0x14}; // example data uint8_t incoming_data[3]; int data_index = 0; @@ -34,7 +34,7 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) printf("Sent data %d: 0x%02X\n ", data_index, outgoing_data[data_index]); gpio_put(LED_PIN, 0); data_index++; - if (data_index > 2) + if (data_index > 3) { data_index = 0; } diff --git a/dev/swerve/CMakeLists.txt b/dev/swerve/CMakeLists.txt index 3fb52ee..6e8fecb 100644 --- a/dev/swerve/CMakeLists.txt +++ b/dev/swerve/CMakeLists.txt @@ -37,6 +37,7 @@ target_link_libraries(i2c_drive_pico pico_stdlib pico_i2c_slave hardware_i2c + hardware_pwm ) # Add the standard include files to the build diff --git a/dev/swerve/cooler_motor_test.c b/dev/swerve/cooler_motor_test.c index ba63c84..5f0504f 100644 --- a/dev/swerve/cooler_motor_test.c +++ b/dev/swerve/cooler_motor_test.c @@ -1,29 +1,35 @@ -#include "pico/stdlib.h" -#include "hardware/pwm.h" +#include +#include +#include +#include // digital low on in# pins indicates direction, both high is no signal #define turn_in1_pin 4 // 1A, forward direction #define turn_in2_pin 5 // 1B, backward direction -#define motor_pwm_pin 9 //2A, 2B take up by motor speed -#define turn_channel 9 // 2A, turn motor speed -#define wheel_channel 8 // 2B, wheel motor speed +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define turn_pwm_pin 9 // 2A, turn motor speed +#define wheel_pwm_pin 8 // 2B, wheel motor speed +#define pwm_slice 4 +#define turn_channel PWM_CHAN_B +#define wheel_channel PWM_CHAN_A #define wheel_in1_pin 6 // 3A, forward direction #define wheel_in2_pin 7 // 3B, backard direction +#define freq 500 // note: use clock management frequencies to set frequency +#define duty_cycle 1 +#define count_max 65535 -#define freq 500 //note: use clock management frequencies to set frequency -#define duty_cycle = 1 -#define count_max = 65535 - -void setup(){ // setup pins for pwm functions +void setup() +{ // setup pins for pwm functions + stdio_init_all(); gpio_init(turn_in1_pin); gpio_init(turn_in2_pin); gpio_init(wheel_in1_pin); gpio_init(wheel_in2_pin); - //check if default output signal is 0, for now put this in + // check if default output signal is 0, for now put this in gpio_put(turn_in1_pin, 0); gpio_put(turn_in2_pin, 0); gpio_put(wheel_in1_pin, 0); @@ -34,35 +40,89 @@ void setup(){ // setup pins for pwm functions gpio_set_dir(wheel_in1_pin, GPIO_OUT); gpio_set_dir(wheel_in2_pin, GPIO_OUT); - gpio_set_function(motor_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); + pwm_set_wrap(pwm_slice, count_max); + pwm_set_chan_level(pwm_slice, turn_channel, 0); + pwm_set_chan_level(pwm_slice, wheel_channel, 0); + + pwm_set_enabled(pwm_slice, true); } -int main(){ +int main() +{ setup(); - int x, y = 0; - - // turn motor - if(x == 0){ // in1 and in2 are high - //pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 1); - }else if(x < 0){ // in1 is high and in2 is low - gpio_put(turn_in1_pin, 1); - }else{ // in1 is low and in2 is high - gpio_put(turn_in2_pin, 1); - } - - // wheel motor - if(y == 0){ // in1 and in2 are high - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 1); - }else if(y < 0){ // in1 is high and in2 is low - gpio_put(wheel_in1_pin, 1); - }else{ // in1 is low and in2 is high - gpio_put(wheel_in2_pin, 1); + double x = 0; + double y = 0.2; + + int xflip = 0; + int yflip = 0; + + float step = 0.001; + + while (1) + { + // turn motor + if (x == 0) + { // in1 and in2 are high + // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + } + else if (x < 0) + { // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + gpio_put(wheel_in1_pin, 0); + } + + // wheel motor + if (y == 0) + { // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + } + else if (y < 0) + { // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(wheel_in2_pin, 1); + gpio_put(wheel_in1_pin, 0); + } + + pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(y * count_max))); + // printf("hello world\n"); + if (xflip) + { + x -= step; + } + else + { + x += step; + } + if (yflip) + { + y -= step; + } + else + { + y += step; + } + if (x == 1 || x == -1) + xflip = !(xflip); + if (y == 1 || y == -1) + yflip = !(yflip); + + printf("x: %f, y: %f\n", x, y); + sleep_ms(20); } - - pwm_set_chan_level(motor_pwm_pin,turn_channel,x*655); - pwm_set_chan_level(motor_pwm_pin,wheel_channel,y*655); return 0; } From 07f9243cc4a84e3fb13b96063cd59254168be19e Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 3 Nov 2023 23:26:25 -0700 Subject: [PATCH 22/65] organized pico_motor_test & i2c_drive --- dev/{swerve => i2c_drive}/CMakeLists.txt | 2 +- dev/i2c_drive/Controller.py | 258 ++++++++++++++ dev/{swerve => i2c_drive}/i2c_drive_pi.py | 0 dev/{swerve => i2c_drive}/i2c_drive_pico.c | 0 .../pico_sdk_import.cmake | 0 dev/i2c_drive/procon.py | 331 ++++++++++++++++++ dev/pico_motor_test/CMakeLists.txt | 55 +++ .../cooler_motor_test.c | 0 dev/pico_motor_test/pico_sdk_import.cmake | 73 ++++ 9 files changed, 718 insertions(+), 1 deletion(-) rename dev/{swerve => i2c_drive}/CMakeLists.txt (96%) create mode 100644 dev/i2c_drive/Controller.py rename dev/{swerve => i2c_drive}/i2c_drive_pi.py (100%) rename dev/{swerve => i2c_drive}/i2c_drive_pico.c (100%) rename dev/{swerve => i2c_drive}/pico_sdk_import.cmake (100%) create mode 100644 dev/i2c_drive/procon.py create mode 100644 dev/pico_motor_test/CMakeLists.txt rename dev/{swerve => pico_motor_test}/cooler_motor_test.c (100%) create mode 100644 dev/pico_motor_test/pico_sdk_import.cmake diff --git a/dev/swerve/CMakeLists.txt b/dev/i2c_drive/CMakeLists.txt similarity index 96% rename from dev/swerve/CMakeLists.txt rename to dev/i2c_drive/CMakeLists.txt index 6e8fecb..914fc65 100644 --- a/dev/swerve/CMakeLists.txt +++ b/dev/i2c_drive/CMakeLists.txt @@ -24,7 +24,7 @@ pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(i2c_drive_pico cooler_motor_test.c ) +add_executable(i2c_drive_pico i2c_drive_pico.c ) pico_set_program_name(i2c_drive_pico "i2c_drive_pico") pico_set_program_version(i2c_drive_pico "0.1") diff --git a/dev/i2c_drive/Controller.py b/dev/i2c_drive/Controller.py new file mode 100644 index 0000000..97e20b7 --- /dev/null +++ b/dev/i2c_drive/Controller.py @@ -0,0 +1,258 @@ +from inputs import get_gamepad # pip install inputs +import math +import threading +from procon import ProCon + + +class Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) + self.MAX_JOY_VAL = math.pow(2, 7) + self.THRESHOLD = 0.03 + + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=() + ) + self._monitor_thread.daemon = True + self._monitor_thread.start() + + def read(self): # return the buttons/triggers that you care about in this methode + # x = self.LeftJoystickX + # y = self.LeftJoystickY + # a = self.A + # b = self.X # b=1, x=2 + # rb = self.RightBumper + # return [x, y, a, b, rb] + return [ + self.LeftJoystickY, + self.LeftJoystickX, + self.RightJoystickY, + self.RightJoystickX, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + def read_self(self): + return self + + def threshold(self, val): + return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 + + def _monitor_controller(self): + while True: + events = get_gamepad() + for event in events: + if event.code == "ABS_Y": + self.LeftJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_X": + self.LeftJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RY": + self.RightJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RX": + self.RightJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_Z": + self.LeftTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "ABS_RZ": + self.RightTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "BTN_TL": + self.LeftBumper = event.state + elif event.code == "BTN_TR": + self.RightBumper = event.state + elif event.code == "BTN_SOUTH": + self.A = event.state + elif event.code == "BTN_NORTH": + self.Y = event.state # previously switched with X + elif event.code == "BTN_WEST": + self.X = event.state # previously switched with Y + elif event.code == "BTN_EAST": + self.B = event.state + elif event.code == "BTN_THUMBL": + self.LeftThumb = event.state + elif event.code == "BTN_THUMBR": + self.RightThumb = event.state + elif event.code == "BTN_SELECT": + self.Back = event.state + elif event.code == "BTN_START": + self.Start = event.state + elif event.code == "BTN_TRIGGER_HAPPY1": + self.LeftDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY2": + self.RightDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY3": + self.UpDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY4": + self.DownDPad = event.state + + +class GemXboxController(Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) + self.MAX_JOY_VAL = math.pow(2, 15) + self.THRESHOLD = 0.03 + + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=() + ) + self._monitor_thread.daemon = True + self._monitor_thread.start() + + +class NintendoProController(Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) + self.MAX_JOY_VAL = math.pow(2, 15) + self.THRESHOLD = 0.1 + self.controller = ProCon() + + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + self._monitor_thread = threading.Thread( + target=self.controller.start, args=(self.procon_callback_func,) + ) + self._monitor_thread.daemon = True + self._monitor_thread.start() + + def read(self): + return [ + self.LeftJoystickX, + self.LeftJoystickY, + self.RightJoystickX, + self.RightJoystickY, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + def procon_callback_func(self, buttons, l_stick, r_stick, *_): + self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) + self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) + self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) + self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) + self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) + self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) + self.LeftBumper = buttons[ProCon.Button.L] + self.RightBumper = buttons[ProCon.Button.R] + self.A = buttons[ProCon.Button.A] + self.B = buttons[ProCon.Button.B] + self.X = buttons[ProCon.Button.X] + self.Y = buttons[ProCon.Button.Y] + self.LeftThumb = buttons[ProCon.Button.LS] + self.RightThumb = buttons[ProCon.Button.RS] + self.Back = buttons[ProCon.Button.MINUS] + self.Start = buttons[ProCon.Button.PLUS] + self.LeftDPad = buttons[ProCon.Button.LEFT] + self.RightDPad = buttons[ProCon.Button.RIGHT] + self.UpDPad = buttons[ProCon.Button.UP] + self.DownDPad = buttons[ProCon.Button.DOWN] + + +if __name__ == "__main__": + joy = Controller() + # joy = GemXboxController() + # joy = NintendoProController() + while True: + try: + print(joy.read()) + except Exception as e: + print("error!", e) + break diff --git a/dev/swerve/i2c_drive_pi.py b/dev/i2c_drive/i2c_drive_pi.py similarity index 100% rename from dev/swerve/i2c_drive_pi.py rename to dev/i2c_drive/i2c_drive_pi.py diff --git a/dev/swerve/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c similarity index 100% rename from dev/swerve/i2c_drive_pico.c rename to dev/i2c_drive/i2c_drive_pico.c diff --git a/dev/swerve/pico_sdk_import.cmake b/dev/i2c_drive/pico_sdk_import.cmake similarity index 100% rename from dev/swerve/pico_sdk_import.cmake rename to dev/i2c_drive/pico_sdk_import.cmake diff --git a/dev/i2c_drive/procon.py b/dev/i2c_drive/procon.py new file mode 100644 index 0000000..8e04765 --- /dev/null +++ b/dev/i2c_drive/procon.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 + +import math +import time + +import hid # pip install hidapi + + +def to_int16(uint16): + return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 + + +class ProCon: + VENDOR_ID = 0x057E + PRODUCT_ID = 0x2009 + PACKET_SIZE = 64 + CALIBRATION_OFFSET = 0x603D + CALIBRATION_LENGTH = 0x12 + COMMAND_RETRIES = 10 + RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) + RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) + DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) + + class OutputReportID: + RUMBLE_SUBCOMMAND = 0x01 + RUMBLE = 0x10 + COMMAND = 0x80 + + class InputReportID: + SUBCOMMAND_REPLY = 0x21 + CONTROLLER_STATE = 0x30 + COMMAND_ACK = 0x81 + + class CommandID: + HANDSHAKE = 0x02 + HIGH_SPEED = 0x03 + FORCE_USB = 0x04 + + class SubcommandID: + SET_INPUT_REPORT_MODE = 0x03 + SPI_FLASH_READ = 0x10 + SET_PLAYER_LIGHTS = 0x30 + SET_HOME_LIGHT = 0x38 + ENABLE_IMU = 0x40 + SET_IMU_SENSITIVITY = 0x41 + ENABLE_VIBRATION = 0x48 + + class Button: + A = "A" + B = "B" + X = "X" + Y = "Y" + UP = "Up" + DOWN = "Down" + LEFT = "Left" + RIGHT = "Right" + MINUS = "-" + PLUS = "+" + SCREENSHOT = "Screenshot" + HOME = "Home" + L = "L" + ZL = "ZL" + R = "R" + ZR = "ZR" + LS = "LS" + RS = "RS" + + def __init__(self): + self.subcommand_counter = 0 + self.dev = hid.device() + self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) + self.handshake() + self.high_speed() + self.handshake() + self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL + self.rumble_expire = 0 + self.load_stick_calibration() + self.enable_vibration(True) + self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) + self.force_usb() + self.set_player_lights(True, False, False, False) + self.enable_imu(True) + self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) + + def start(self, callback): + while True: + state = self.recv() + if state[0] != ProCon.InputReportID.CONTROLLER_STATE: + continue + buttons = { + ProCon.Button.A: state[3] & 0x08 > 0, + ProCon.Button.B: state[3] & 0x04 > 0, + ProCon.Button.X: state[3] & 0x02 > 0, + ProCon.Button.Y: state[3] & 0x01 > 0, + ProCon.Button.UP: state[5] & 0x02 > 0, + ProCon.Button.DOWN: state[5] & 0x01 > 0, + ProCon.Button.LEFT: state[5] & 0x08 > 0, + ProCon.Button.RIGHT: state[5] & 0x04 > 0, + ProCon.Button.MINUS: state[4] & 0x01 > 0, + ProCon.Button.PLUS: state[4] & 0x02 > 0, + ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, + ProCon.Button.HOME: state[4] & 0x10 > 0, + ProCon.Button.L: state[5] & 0x40 > 0, + ProCon.Button.ZL: state[5] & 0x80 > 0, + ProCon.Button.R: state[3] & 0x40 > 0, + ProCon.Button.ZR: state[3] & 0x80 > 0, + ProCon.Button.LS: state[4] & 0x08 > 0, + ProCon.Button.RS: state[4] & 0x04 > 0, + } + l_x = state[6] | ((state[7] & 0xF) << 8) + l_y = (state[7] >> 4) | (state[8] << 4) + r_x = state[9] | ((state[10] & 0xF) << 8) + r_y = (state[10] >> 4) | (state[11] << 4) + l_x = self.apply_stick_calibration(l_x, 0, 0) + l_y = self.apply_stick_calibration(l_y, 0, 1) + r_x = self.apply_stick_calibration(r_x, 1, 0) + r_y = self.apply_stick_calibration(r_y, 1, 1) + l_stick = (l_x, l_y) + r_stick = (r_x, r_y) + accel = ( + state[13] | state[14] << 8, + state[15] | state[16] << 8, + state[17] | state[18] << 8, + ) + gyro = ( + state[19] | state[20] << 8, + state[21] | state[22] << 8, + state[23] | state[24] << 8, + ) + accel = tuple(map(to_int16, accel)) + gyro = tuple(map(to_int16, gyro)) + battery = (state[2] & 0xF0) >> 4 + callback(buttons, l_stick, r_stick, accel, gyro, battery) + if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: + self.send_rumble(False, False, 0) + + def load_stick_calibration(self): + ok, reply = self.spi_flash_read( + ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH + ) + if not ok: + raise RuntimeError("cannot load stick calibration") + self.stick_calibration = [ + [ + [ + ((reply[27] & 0xF) << 8) | reply[26], + ((reply[24] & 0xF) << 8) | reply[23], + ((reply[21] & 0xF) << 8) | reply[20], + ], + [ + (reply[28] << 4) | (reply[27] >> 4), + (reply[25] << 4) | (reply[24] >> 4), + (reply[22] << 4) | (reply[21] >> 4), + ], + ], + [ + [ + ((reply[33] & 0xF) << 8) | reply[32], + ((reply[30] & 0xF) << 8) | reply[29], + ((reply[36] & 0xF) << 8) | reply[35], + ], + [ + (reply[34] << 4) | (reply[33] >> 4), + (reply[31] << 4) | (reply[30] >> 4), + (reply[37] << 4) | (reply[36] >> 4), + ], + ], + ] + for i in range(len(self.stick_calibration)): + for j in range(len(self.stick_calibration[i])): + for k in range(len(self.stick_calibration[i][j])): + if self.stick_calibration[i][j][k] == 0xFFF: + self.stick_calibration[i][j][k] = 0 + self.stick_extends = [ + [ + [ + -int(self.stick_calibration[0][0][0] * 0.7), + int(self.stick_calibration[0][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[0][1][0] * 0.7), + int(self.stick_calibration[0][1][2] * 0.7), + ], + ], + [ + [ + -int(self.stick_calibration[1][0][0] * 0.7), + int(self.stick_calibration[1][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[1][1][0] * 0.7), + int(self.stick_calibration[1][1][2] * 0.7), + ], + ], + ] + + def apply_stick_calibration(self, value, stick, axis): + value -= self.stick_calibration[stick][axis][1] + if value < self.stick_extends[stick][axis][0]: + self.stick_extends[stick][axis][0] = value + if value > self.stick_extends[stick][axis][1]: + self.stick_extends[stick][axis][1] = value + if value > 0: + return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) + return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) + + def send(self, data): + return self.dev.write(data) == len(data) + + def recv(self): + return self.dev.read(ProCon.PACKET_SIZE) + + def send_command(self, id, wait_for_reply=True): + data = (ProCon.OutputReportID.COMMAND, id) + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True + reply = self.recv() + if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: + return True + return False + + def send_subcommand(self, id, param, wait_for_reply=True): + data = ( + (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + + (id,) + + param + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True, [] + reply = self.recv() + if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: + return True, reply + return False, [] + + def send_rumble(self, low, high, duration): + self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL + self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL + self.rumble_expire = ( + int(time.time() * 1000) + duration if (low or high) and duration else 0 + ) + data = ( + (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if self.send(data): + return True + return False + + def handshake(self): + return self.send_command(ProCon.CommandID.HANDSHAKE) + + def high_speed(self): + return self.send_command(ProCon.CommandID.HIGH_SPEED) + + def force_usb(self): + return self.send_command(ProCon.CommandID.FORCE_USB, False) + + def set_input_report_mode(self, mode): + return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) + + def spi_flash_read(self, addr, l): + param = ( + addr & 0x000000FF, + (addr & 0x0000FF00) >> 8, + (addr & 0x00FF0000) >> 16, + (addr & 0xFF000000) >> 24, + l, + ) + return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) + + def set_player_lights(self, one, two, three, four): + param = (one << 0) | (two << 1) | (three << 2) | (four << 3) + return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) + + def set_home_light(self, brightness): + intensity = 0 + if brightness > 0: + if brightness < 65: + intensity = (brightness + 5) // 10 + else: + intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) + intensity = (intensity & 0xF) << 4 + param = (0x01, intensity, intensity, 0x00) + return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) + + def enable_imu(self, enable): + return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) + + def set_imu_sensitivity(self, sensitivity): + return self.send_subcommand( + ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity + ) + + def enable_vibration(self, enable): + return self.send_subcommand( + ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) + ) + + +def print_state(buttons, l_stick, r_stick, accel, gyro, battery): + print("\33[2JButtons:") + for k, v in buttons.items(): + if v: + print("[{}]".format(k), end=" ") + else: + print(" {} ".format(k), end=" ") + print() + print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) + print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) + print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) + print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) + print("Battery: {}/9".format(battery)) + + +if __name__ == "__main__": + try: + ProCon().start(print_state) + except KeyboardInterrupt: + print("\rGoodbye!") diff --git a/dev/pico_motor_test/CMakeLists.txt b/dev/pico_motor_test/CMakeLists.txt new file mode 100644 index 0000000..a05cd2e --- /dev/null +++ b/dev/pico_motor_test/CMakeLists.txt @@ -0,0 +1,55 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(cooler_motor_test C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(cooler_motor_test cooler_motor_test.c ) + +pico_set_program_name(cooler_motor_test "cooler_motor_test") +pico_set_program_version(cooler_motor_test "0.1") + +pico_enable_stdio_uart(cooler_motor_test 0) +pico_enable_stdio_usb(cooler_motor_test 1) + +# Add the standard library to the build +target_link_libraries(cooler_motor_test + pico_stdlib + pico_i2c_slave + hardware_i2c + hardware_pwm + ) + +# Add the standard include files to the build +target_include_directories(cooler_motor_test PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(cooler_motor_test + hardware_i2c + ) + +pico_add_extra_outputs(cooler_motor_test) + diff --git a/dev/swerve/cooler_motor_test.c b/dev/pico_motor_test/cooler_motor_test.c similarity index 100% rename from dev/swerve/cooler_motor_test.c rename to dev/pico_motor_test/cooler_motor_test.c diff --git a/dev/pico_motor_test/pico_sdk_import.cmake b/dev/pico_motor_test/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/pico_motor_test/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) From 43f15c7f06b05ab21ae492c440f0cfaecef8499d Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 4 Nov 2023 00:26:18 -0700 Subject: [PATCH 23/65] comments --- dev/i2c-comms/pi_spi.c | 22 ++-- dev/i2c-comms/pico_spi.c | 13 +- dev/i2c_drive/Controller.py | 162 ++++++++---------------- dev/i2c_drive/i2c_drive_pi.py | 66 ++++++---- dev/i2c_drive/i2c_drive_pico.c | 75 +++++------ dev/pico_motor_test/cooler_motor_test.c | 53 +++----- dev/swerve/Controller.py | 162 ++++++++---------------- dev/swerve/joystick_sim.py | 55 ++++++-- 8 files changed, 262 insertions(+), 346 deletions(-) diff --git a/dev/i2c-comms/pi_spi.c b/dev/i2c-comms/pi_spi.c index 6de7862..b98a690 100644 --- a/dev/i2c-comms/pi_spi.c +++ b/dev/i2c-comms/pi_spi.c @@ -8,24 +8,28 @@ #define SPI_DEVICE "/dev/spidev0.0" #define SPI_SPEED 1000000 // 1MHz -int main() { +int main() +{ int spi_fd; unsigned char tx_data[] = {0xAA, 0xBB, 0xCC, 0xDD}; unsigned char rx_data[sizeof(tx_data)]; spi_fd = open(SPI_DEVICE, O_RDWR); - if (spi_fd < 0) { + if (spi_fd < 0) + { perror("Error opening SPI device"); return -1; } int mode = SPI_MODE_0; - if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) == -1) { + if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) == -1) + { perror("Error setting SPI mode"); return -1; } - if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED) == -1) { + if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED) == -1) + { perror("Error setting SPI speed"); return -1; } @@ -38,19 +42,21 @@ int main() { .bits_per_word = 8, }; - while (1) { - if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer) == -1) { + while (1) + { + if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer) == -1) + { perror("Error during SPI message transfer"); return -1; } printf("Received data: "); - for (int i = 0; i < sizeof(rx_data); i++) { + for (int i = 0; i < sizeof(rx_data); i++) + { printf(" %02X", rx_data[i]); } printf("\n"); sleep(1); - } close(spi_fd); return 0; diff --git a/dev/i2c-comms/pico_spi.c b/dev/i2c-comms/pico_spi.c index 2e7be5b..a3d8b18 100644 --- a/dev/i2c-comms/pico_spi.c +++ b/dev/i2c-comms/pico_spi.c @@ -6,23 +6,26 @@ #define PIN_MISO 16 #define PIN_CS 17 -int main() { +int main() +{ stdio_init_all(); - spi_init(SPI_PORT, 1000 * 1000); // init at 1MHz + spi_init(SPI_PORT, 1000 * 1000); // init at 1MHz gpio_set_function(PIN_MISO, GPIO_FUNC_SPI); // set pin to SPI mode gpi_init(PIN_CS); gpio_set_dir(PIN_CS, GPIO_OUT); - while (1) { + while (1) + { gpio_put(PIN_CS, 1); // set CS high to indiciate start of communication uint8_t rx_data[4]; spi_read_blocking(SPI_PORT, 0, rx_data, sizeof(rx_data)); // read data from pi - gpio_put(PIN_CS, 0); // set CS low to indicate end of communication + gpio_put(PIN_CS, 0); // set CS low to indicate end of communication printf("Received: "); - for (int i = 0; i < sizeof(rx_data); i++) { + for (int i = 0; i < sizeof(rx_data); i++) + { printf(" %02X", rx_data[i]); } printf("\n"); diff --git a/dev/i2c_drive/Controller.py b/dev/i2c_drive/Controller.py index 97e20b7..a7e9ace 100644 --- a/dev/i2c_drive/Controller.py +++ b/dev/i2c_drive/Controller.py @@ -1,15 +1,23 @@ -from inputs import get_gamepad # pip install inputs import math import threading -from procon import ProCon +from inputs import \ + get_gamepad # Import the get_gamepad function from the inputs module +from procon import ProCon # Import the ProCon class from the procon module -class Controller(object): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 7) - self.THRESHOLD = 0.03 +# This class represents a PS4 Controller +class PS4_Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + # This method resets all controller variables to their initial state + def reset_vars(self): + # Initialize all controller variables to 0 self.LeftJoystickY = 0 self.LeftJoystickX = 0 self.RightJoystickY = 0 @@ -31,19 +39,18 @@ def __init__(self): self.UpDPad = 0 self.DownDPad = 0 + # This method starts a new thread to monitor the controller + def start_thread(self, thread_args=()): self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=() + target=self._monitor_controller, args=thread_args + ) + self._monitor_thread.daemon = ( + True # Set the thread as a daemon so it will end when the main program ends ) - self._monitor_thread.daemon = True - self._monitor_thread.start() + self._monitor_thread.start() # Start the thread - def read(self): # return the buttons/triggers that you care about in this methode - # x = self.LeftJoystickX - # y = self.LeftJoystickY - # a = self.A - # b = self.X # b=1, x=2 - # rb = self.RightBumper - # return [x, y, a, b, rb] + # This method returns the current state of all buttons/triggers + def read(self): return [ self.LeftJoystickY, self.LeftJoystickX, @@ -67,9 +74,11 @@ def read(self): # return the buttons/triggers that you care about in this metho self.DownDPad, ] + # This method returns the controller object itself def read_self(self): return self + # This method applies a threshold to a value def threshold(self, val): return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 @@ -131,99 +140,36 @@ def _monitor_controller(self): self.DownDPad = event.state -class GemXboxController(Controller): +# This class represents the Xbox Controller in WRP used for the CPSRC GEM +class Gem_Xbox_Controller(PS4_Controller): def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 15) - self.THRESHOLD = 0.03 + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller - self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=() - ) - self._monitor_thread.daemon = True - self._monitor_thread.start() - -class NintendoProController(Controller): +# This class represents the Nintendo Pro Controller +class Nintendo_Pro_Controller(PS4_Controller): def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 15) - self.THRESHOLD = 0.1 - self.controller = ProCon() - - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.1 # Threshold for joystick deadzone + self.controller = ProCon() # Initialize the ProCon controller - self._monitor_thread = threading.Thread( - target=self.controller.start, args=(self.procon_callback_func,) - ) - self._monitor_thread.daemon = True - self._monitor_thread.start() - - def read(self): - return [ - self.LeftJoystickX, - self.LeftJoystickY, - self.RightJoystickX, - self.RightJoystickY, - self.LeftTrigger, - self.RightTrigger, - self.LeftBumper, - self.RightBumper, - self.A, - self.B, - self.X, - self.Y, - self.LeftThumb, - self.RightThumb, - self.Back, - self.Start, - self.LeftDPad, - self.RightDPad, - self.UpDPad, - self.DownDPad, - ] + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread( + self.procon_callback_func + ) # Start a new thread to monitor the controller + # This method is called when the ProCon controller state changes def procon_callback_func(self, buttons, l_stick, r_stick, *_): + # Update the controller variables based on the new state + # The joystick values are normalized between -1 and 1 + # The threshold method is used to apply a deadband to the joystick values + # The button values are either 0 or 1 self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) @@ -247,12 +193,12 @@ def procon_callback_func(self, buttons, l_stick, r_stick, *_): if __name__ == "__main__": - joy = Controller() - # joy = GemXboxController() - # joy = NintendoProController() + joy = PS4_Controller() # Initialize a PS4 controller + # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller + # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller while True: try: - print(joy.read()) + print(joy.read()) # Print the current state of the controller except Exception as e: - print("error!", e) - break + print("error!", e) # Print any errors that occur + break # Exit the loop if an error occurs diff --git a/dev/i2c_drive/i2c_drive_pi.py b/dev/i2c_drive/i2c_drive_pi.py index 42fb087..e4eb2bb 100644 --- a/dev/i2c_drive/i2c_drive_pi.py +++ b/dev/i2c_drive/i2c_drive_pi.py @@ -1,38 +1,52 @@ import fcntl import os import time -from Controller import Controller -I2C_PRIM = 0x0703 +from Controller import PS4_Controller -# open i2c devices (sudo apt install i2c-tools) -i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) +I2C_PRIM = 0x0703 # I2C primary address -# set the i2c address of pico -pico_address = 0x08 -fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) +# Open i2c devices (sudo apt install i2c-tools) +i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) # File descriptor for the i2c device -# send data to pico -joy = Controller() -delay = 0.05 +# Set the i2c address of pico +pico_address = 0x08 # Address of the pico on the i2c bus +fcntl.ioctl( + i2c_fd, I2C_PRIM, pico_address +) # Set the address of the i2c device to communicate with + +# Send data to pico +joy = PS4_Controller() # Initialize the controller +delay = 0.05 # Delay between each read/write operation + +while True: # Infinite loop + status = joy.read_self() # Read the status of the controller + x = status.LeftJoystickX # Get the X position of the left joystick + y = status.RightJoystickY # Get the Y position of the right joystick + joystickswitch = x > 0 # Check if the joystick is moved to the right + data = [ + 0xFA, + int(joystickswitch), + int(joystickswitch), + 0xFB, + 0x00, + ] # Prepare the data to be sent -while True: - status = joy.read_self() - x = status.LeftJoystickX - y = status.RightJoystickY - joystickswitch = x>0 - data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] # example data - try: - os.write(i2c_fd, bytes(data)) - time.sleep(delay) - print("Sent data to Pico: ", list(data)) + os.write(i2c_fd, bytes(data)) # Write the data to the i2c device + time.sleep(delay) # Wait for a while + print("Sent data to Pico: ", list(data)) # Print the data that was sent except OSError: - print("Remote I/O Error") - # read data from pico + print( + "Remote I/O Error" + ) # Print an error message if there was a problem with the write operation + + # Read data from pico try: - incoming_data = os.read(i2c_fd, 1) # read 3 bytes - time.sleep(delay) - print("Received data from Pico: ", list(incoming_data)) + incoming_data = os.read(i2c_fd, 1) # Read 1 byte from the i2c device + time.sleep(delay) # Wait for a while + print( + "Received data from Pico: ", list(incoming_data) + ) # Print the data that was received except TimeoutError: - print("Timeout Error") + print("Timeout Error") # Print an error message if there was a timeout error diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index df497c0..3dfde8e 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -3,92 +3,86 @@ #include #include -// #ifndef PICO_DEFAULT_LED_PIN -// #warning blink requires a board with a regular LED -// #else -// const uint LED_PIN = PICO_DEFAULT_LED_PIN; -// #endif - -// define variables +// Define constants for I2C communication #define I2C_PICO_ADDR 0x08 #define I2C_SDA_PIN 0 #define I2C_SCL_PIN 1 #define I2C_PORT i2c0 #define I2C_BAUDRATE 100 * 1000 -// length of data packet: 1 byte for start, 2 bytes for data, 1 byte for stop +// Define the length of the data packet #define I2C_DATA_LENGTH 4 #define MESSAGE_START 0xFA #define MESSAGE_STOP 0xFB -// data received from Pi, stored as doubles +// Buffer for incoming data uint8_t incoming_data[I2C_DATA_LENGTH]; -uint8_t input_status = 0; // 0 if input is not ready to send to drive (incomplete), 1 if input is ready to send to drive, 2 if input has been sent to drive and has been erased -int last_event = 0; // 0 if FINISH, 1 if RECEIVE, 2 if REQUEST +// Status of the input data +uint8_t input_status = 0; + +// Last event that occurred +int last_event = 0; + +// Index of the current data byte int data_index = 0; -int input[I2C_DATA_LENGTH - 2]; // input data from Pi, stored as ints (doesn't include start and stop bytes) +// Buffer for the input data +int input[I2C_DATA_LENGTH - 2]; +// Handler for I2C events static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { switch (event) { - case I2C_SLAVE_RECEIVE: // pi has written some data + case I2C_SLAVE_RECEIVE: // Pi has written some data + // Read the data uint8_t tmp = i2c_read_byte_raw(i2c); - // printf("Received byte: 0x%02X\n", tmp); + // Check if the data is valid + // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) { break; } + // Store the data incoming_data[data_index] = tmp; data_index++; - // printf("Received data %d: 0x%02X\n", data_index, tmp); - // gpio_put(LED_PIN, 1); + // set the event status to received last_event = 1; break; - case I2C_SLAVE_REQUEST: // pi is requesting data - + case I2C_SLAVE_REQUEST: // Pi is requesting data + // Write the data into the void i2c_write_byte_raw(i2c, (uint8_t)input_status); - printf("Sent data %d: 0x%02X\n", 0, input_status); - // gpio_put(LED_PIN, 0); - + // set the event status to sent last_event = 2; break; - case I2C_SLAVE_FINISH: // pi has signalled Stop / Restart - called at the end of every receive/request - - // printf("last_event: %d\n", last_event); + case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart + // if the last event was a receive event and the data is valid if (last_event == 1) { if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) { - // input_status = 0; - printf("Received complete message\n"); - - // convert incoming_data to doubles (doesn't include start and stop bytes) + // move the data into the input array for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) { input[i] = (int)incoming_data[i + 1]; } + // set the input status to ready input_status = 1; - // reset incoming_data + // Reset incoming_data for (int i = 0; i < I2C_DATA_LENGTH; i++) { incoming_data[i] = 0x00; } } - else - { - // input_status = 0; - printf("Received incomplete message\n"); - } data_index = 0; } + // set the event status to finished last_event = 0; break; default: @@ -96,6 +90,7 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } } +// Convert byte to motor double int byte_to_motor_double(int input) { return (double)input / 255.0 * 2.0 - 1.0; @@ -105,19 +100,12 @@ int main() { stdio_init_all(); - // #ifndef PICO_DEFAULT_LED_PIN - // #warning blink requires a board with a regular LED - // #else - // gpio_init(LED_PIN); - // gpio_set_dir(LED_PIN, GPIO_OUT); - // #endif - - // init i2c at 100kHz + // Initialize I2C at 100kHz i2c_init(I2C_PORT, I2C_BAUDRATE); gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); - // set i2c address for pico + // Set I2C address for Pico i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); while (1) @@ -131,7 +119,6 @@ int main() printf("%f ", input[i]); } printf("\n"); - // input_status = 2; } } diff --git a/dev/pico_motor_test/cooler_motor_test.c b/dev/pico_motor_test/cooler_motor_test.c index 5f0504f..313951a 100644 --- a/dev/pico_motor_test/cooler_motor_test.c +++ b/dev/pico_motor_test/cooler_motor_test.c @@ -17,47 +17,45 @@ #define wheel_in1_pin 6 // 3A, forward direction #define wheel_in2_pin 7 // 3B, backard direction -#define freq 500 // note: use clock management frequencies to set frequency -#define duty_cycle 1 -#define count_max 65535 +#define count_max 65535 // number of counts in a cycle --> 1/count_max = freq -void setup() -{ // setup pins for pwm functions +int main() +{ + // setup stdio for printing stdio_init_all(); + + // setup pins for pwm functions gpio_init(turn_in1_pin); gpio_init(turn_in2_pin); gpio_init(wheel_in1_pin); gpio_init(wheel_in2_pin); - // check if default output signal is 0, for now put this in - gpio_put(turn_in1_pin, 0); - gpio_put(turn_in2_pin, 0); - gpio_put(wheel_in1_pin, 0); - gpio_put(wheel_in2_pin, 0); - + // set non-pwm pins to output gpio_set_dir(turn_in1_pin, GPIO_OUT); gpio_set_dir(turn_in2_pin, GPIO_OUT); gpio_set_dir(wheel_in1_pin, GPIO_OUT); gpio_set_dir(wheel_in2_pin, GPIO_OUT); + // setup pwm gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); + + // set pwm slice and channels pwm_set_wrap(pwm_slice, count_max); pwm_set_chan_level(pwm_slice, turn_channel, 0); pwm_set_chan_level(pwm_slice, wheel_channel, 0); pwm_set_enabled(pwm_slice, true); -} -int main() -{ - setup(); + // set initial x (turn) and y (wheel) values double x = 0; double y = 0.2; + // flip values used for oscillate direction between 1 and -1 int xflip = 0; int yflip = 0; + // step size for oscillation float step = 0.001; while (1) @@ -65,7 +63,6 @@ int main() // turn motor if (x == 0) { // in1 and in2 are high - // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle gpio_put(turn_in1_pin, 1); gpio_put(turn_in2_pin, 1); } @@ -97,25 +94,15 @@ int main() gpio_put(wheel_in1_pin, 0); } + // set pwm duty cycle pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(y * count_max))); - // printf("hello world\n"); - if (xflip) - { - x -= step; - } - else - { - x += step; - } - if (yflip) - { - y -= step; - } - else - { - y += step; - } + + // increment x and y + x += step * (xflip ? -1 : 1); + y += step * (yflip ? -1 : 1); + + // flip x and y if they reach 1 or -1 if (x == 1 || x == -1) xflip = !(xflip); if (y == 1 || y == -1) diff --git a/dev/swerve/Controller.py b/dev/swerve/Controller.py index 97e20b7..a7e9ace 100644 --- a/dev/swerve/Controller.py +++ b/dev/swerve/Controller.py @@ -1,15 +1,23 @@ -from inputs import get_gamepad # pip install inputs import math import threading -from procon import ProCon +from inputs import \ + get_gamepad # Import the get_gamepad function from the inputs module +from procon import ProCon # Import the ProCon class from the procon module -class Controller(object): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 7) - self.THRESHOLD = 0.03 +# This class represents a PS4 Controller +class PS4_Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + # This method resets all controller variables to their initial state + def reset_vars(self): + # Initialize all controller variables to 0 self.LeftJoystickY = 0 self.LeftJoystickX = 0 self.RightJoystickY = 0 @@ -31,19 +39,18 @@ def __init__(self): self.UpDPad = 0 self.DownDPad = 0 + # This method starts a new thread to monitor the controller + def start_thread(self, thread_args=()): self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=() + target=self._monitor_controller, args=thread_args + ) + self._monitor_thread.daemon = ( + True # Set the thread as a daemon so it will end when the main program ends ) - self._monitor_thread.daemon = True - self._monitor_thread.start() + self._monitor_thread.start() # Start the thread - def read(self): # return the buttons/triggers that you care about in this methode - # x = self.LeftJoystickX - # y = self.LeftJoystickY - # a = self.A - # b = self.X # b=1, x=2 - # rb = self.RightBumper - # return [x, y, a, b, rb] + # This method returns the current state of all buttons/triggers + def read(self): return [ self.LeftJoystickY, self.LeftJoystickX, @@ -67,9 +74,11 @@ def read(self): # return the buttons/triggers that you care about in this metho self.DownDPad, ] + # This method returns the controller object itself def read_self(self): return self + # This method applies a threshold to a value def threshold(self, val): return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 @@ -131,99 +140,36 @@ def _monitor_controller(self): self.DownDPad = event.state -class GemXboxController(Controller): +# This class represents the Xbox Controller in WRP used for the CPSRC GEM +class Gem_Xbox_Controller(PS4_Controller): def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 15) - self.THRESHOLD = 0.03 + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller - self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=() - ) - self._monitor_thread.daemon = True - self._monitor_thread.start() - -class NintendoProController(Controller): +# This class represents the Nintendo Pro Controller +class Nintendo_Pro_Controller(PS4_Controller): def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 15) - self.THRESHOLD = 0.1 - self.controller = ProCon() - - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.1 # Threshold for joystick deadzone + self.controller = ProCon() # Initialize the ProCon controller - self._monitor_thread = threading.Thread( - target=self.controller.start, args=(self.procon_callback_func,) - ) - self._monitor_thread.daemon = True - self._monitor_thread.start() - - def read(self): - return [ - self.LeftJoystickX, - self.LeftJoystickY, - self.RightJoystickX, - self.RightJoystickY, - self.LeftTrigger, - self.RightTrigger, - self.LeftBumper, - self.RightBumper, - self.A, - self.B, - self.X, - self.Y, - self.LeftThumb, - self.RightThumb, - self.Back, - self.Start, - self.LeftDPad, - self.RightDPad, - self.UpDPad, - self.DownDPad, - ] + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread( + self.procon_callback_func + ) # Start a new thread to monitor the controller + # This method is called when the ProCon controller state changes def procon_callback_func(self, buttons, l_stick, r_stick, *_): + # Update the controller variables based on the new state + # The joystick values are normalized between -1 and 1 + # The threshold method is used to apply a deadband to the joystick values + # The button values are either 0 or 1 self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) @@ -247,12 +193,12 @@ def procon_callback_func(self, buttons, l_stick, r_stick, *_): if __name__ == "__main__": - joy = Controller() - # joy = GemXboxController() - # joy = NintendoProController() + joy = PS4_Controller() # Initialize a PS4 controller + # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller + # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller while True: try: - print(joy.read()) + print(joy.read()) # Print the current state of the controller except Exception as e: - print("error!", e) - break + print("error!", e) # Print any errors that occur + break # Exit the loop if an error occurs diff --git a/dev/swerve/joystick_sim.py b/dev/swerve/joystick_sim.py index 72db964..94c521e 100644 --- a/dev/swerve/joystick_sim.py +++ b/dev/swerve/joystick_sim.py @@ -1,10 +1,11 @@ -import numpy as np import matplotlib.pyplot as plt +import numpy as np # from Controller import GemXboxController from Controller import NintendoProController +# return the vector perpendicular to the given vector def perpendicular(vec): return np.array([-vec[1], vec[0]]) @@ -13,36 +14,45 @@ def perpendicular(vec): # joy = GemXboxController() joy = NintendoProController() + rumble = type(joy) == NintendoProController + # robot radius R = 5 # dt, the delta time of the "animation" DT = 0.001 + # initial robot state + # 3 modules, 120 degrees apart angle_1 = 3.0 / 6.0 * np.pi angle_2 = 7.0 / 6.0 * np.pi angle_3 = 11.0 / 6.0 * np.pi - center_pos = np.array([0.0, 0.0]) - module_dirs = np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi + center_pos = np.array([0.0, 0.0]) # center position + module_dirs = ( + np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi + ) # directions of each module, relative to screen module_pos = np.array( [ [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] for a in module_dirs ] - ) - freeze_pos = center_pos.copy() + ) # absolute positions of each module (as a point) + freeze_pos = ( + center_pos.copy() + ) # position to rotate about when right bumper is pressed while True: try: + # get inputs joy_input = joy.read_self() - if joy_input.Back: + if joy_input.Back: # exit if back button is pressed print("Exiting") break - # get inputs in "full" resolution to allow for gradual moving & changing directions - + # TODO: should replace this by standardizing inputs in the Controller.py class inverts = [False, False, False, False] # Nintendo Pro Controller # inverts = [False, True, True] # Gem Xbox Controller + # use joystick inputs to calculate "strafe" movement left_x = (-1.0 if inverts[0] else 1.0) * round(joy_input.LeftJoystickX, 3) left_y = (-1.0 if inverts[1] else 1.0) * round(joy_input.LeftJoystickY, 3) triggers = joy_input.LeftTrigger - joy_input.RightTrigger @@ -52,30 +62,41 @@ def perpendicular(vec): ## LOGIC (begin) - # calculate movement and rotation using inputs - + # get distance between freeze_pos and center_pos dist = np.hypot( freeze_pos[0] - center_pos[0], freeze_pos[1] - center_pos[1] ) + # if right bumper is not pressed, move robot in direction of joystick & rotate relative to center pos if not joy_input.RightBumper: move = np.array([left_x, left_y]) * 1.0 rotate = 0.1 * triggers - elif dist > R: + elif ( + dist > R + ): # if right bumper is pressed and freeze pos is not "inside" robot, rotate robot around freeze pos + # calculate vector from freeze to center pos x = (freeze_pos[0] - center_pos[0]) / dist y = (freeze_pos[1] - center_pos[1]) / dist + + # calculate new center position, moving robot around freeze pos + # x' = x*cos(theta) - y*sin(theta) + # y' = x*sin(theta) + y*cos(theta) + # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) move = np.array( [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] ) + # rotate robot so direction of modules is the same relative to freeze pos, plus some rotation from triggers rotate = (-1.0 if left_x > 0 else 1.0) * np.hypot( move[0], move[1] ) / dist + 0.1 * triggers + # if left bumper is pressed, make freeze pos the same as center pos if joy_input.LeftBumper: freeze_pos = center_pos.copy() - else: + else: # if left bumper is not pressed, move freeze pos in direction of right joystick freeze_pos += np.array([right_x, right_y]) * 1.0 + # if right bumper is not pressed, move freeze pos in direction of right joystick (relative to center pos) if not joy_input.RightBumper: freeze_pos += move * 1.0 + np.array([right_x, right_y]) * 1.0 @@ -95,12 +116,13 @@ def perpendicular(vec): ] ) - # set box size and aspect ratio + # set box size and aspect ratio for matplotlib plot window box_scale = 10 plt.xlim(-box_scale * R, box_scale * R) plt.ylim(-box_scale * R, box_scale * R) plt.gca().set_aspect("equal", adjustable="box") + # array to store module controls (direction & speed of each module) module_controls = [] # plot robot @@ -118,6 +140,7 @@ def perpendicular(vec): * 10 ) + # add module direction vector to module_controls as degrees & speed module_controls.append( ( round(np.rad2deg(np.arctan2(dir_vec[1], dir_vec[0])), 3), @@ -151,14 +174,18 @@ def perpendicular(vec): scale=0.5, ) + # plot line from center to freeze pos plt.plot( [center_pos[0], freeze_pos[0]], [center_pos[1], freeze_pos[1]], "b" ) # rumble if robot is outside of box - if abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R: + if rumble and ( + abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R + ): joy.controller.send_rumble(False, True, 1) + # pause for DT seconds and clear plot plt.pause(DT) plt.clf() From 5073e7b036748bf9755365a45f0bc2e73a20383d Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 4 Nov 2023 00:29:06 -0700 Subject: [PATCH 24/65] added rotation relative to a point link --- dev/swerve/joystick_sim.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/swerve/joystick_sim.py b/dev/swerve/joystick_sim.py index 94c521e..f989227 100644 --- a/dev/swerve/joystick_sim.py +++ b/dev/swerve/joystick_sim.py @@ -82,6 +82,7 @@ def perpendicular(vec): # x' = x*cos(theta) - y*sin(theta) # y' = x*sin(theta) + y*cos(theta) # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) + # https://academo.org/demos/rotation-about-point/ move = np.array( [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] ) From 6b016082dcb3fffc9c3f0817902957872a50f131 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 4 Nov 2023 14:39:17 -0700 Subject: [PATCH 25/65] encoder works, abstracted behind a factory Co-authored-by: inkyant --- dev/encoder/CMakeLists.txt | 2 + dev/encoder/pio_rotary_encoder/CMakeLists.txt | 25 --- .../pio_rotary_encoder/pio_rotary_encoder.cpp | 162 ------------------ .../pio_rotary_encoder/pio_rotary_encoder.pio | 51 ------ dev/encoder/quadrature_encoder.cpp | 101 +++++++---- 5 files changed, 67 insertions(+), 274 deletions(-) delete mode 100644 dev/encoder/pio_rotary_encoder/CMakeLists.txt delete mode 100644 dev/encoder/pio_rotary_encoder/pio_rotary_encoder.cpp delete mode 100644 dev/encoder/pio_rotary_encoder/pio_rotary_encoder.pio diff --git a/dev/encoder/CMakeLists.txt b/dev/encoder/CMakeLists.txt index c2e187c..fc8ee89 100644 --- a/dev/encoder/CMakeLists.txt +++ b/dev/encoder/CMakeLists.txt @@ -4,6 +4,8 @@ project(quadrature_encoder VERSION 1.0.0) add_executable(quadrature_encoder quadrature_encoder.cpp) +set(PICO_CXX_ENABLE_EXCEPTIONS 1) + pico_sdk_init() target_sources(quadrature_encoder PRIVATE quadrature_encoder.cpp) diff --git a/dev/encoder/pio_rotary_encoder/CMakeLists.txt b/dev/encoder/pio_rotary_encoder/CMakeLists.txt deleted file mode 100644 index 25a3303..0000000 --- a/dev/encoder/pio_rotary_encoder/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.12) -include(pico_sdk_import.cmake) -project(pio_rotary_encoder VERSION 1.0.0) - -add_executable(pio_rotary_encoder pio_rotary_encoder.cpp) - -pico_sdk_init() - -target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp) - -target_link_libraries(pio_rotary_encoder PRIVATE - pico_stdlib - hardware_pio - pico_multicore - ) - -pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.pio) - -pico_add_extra_outputs(pio_rotary_encoder) - -# enable usb output, disable uart output -pico_enable_stdio_usb(pio_rotary_encoder 1) -pico_enable_stdio_uart(pio_rotary_encoder 0) - - diff --git a/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.cpp b/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.cpp deleted file mode 100644 index 1aea5f6..0000000 --- a/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include - -#include "pico/stdlib.h" -#include "hardware/pio.h" -#include "hardware/irq.h" - -#include "pio_rotary_encoder.pio.h" - -#include "pico/multicore.h" - -// class to read the rotation of the rotary encoder -class RotaryEncoder -{ -public: - // constructor - // rotary_encoder_A is the pin for the A of the rotary encoder. - // The B of the rotary encoder has to be connected to the next GPIO. - RotaryEncoder(uint rotary_encoder_A) - { - uint8_t rotary_encoder_B = rotary_encoder_A + 1; - // pio 0 is used - PIO pio = pio0; - // state machine 0 - uint8_t sm = 0; - // configure the used pins as input with pull up - pio_gpio_init(pio, rotary_encoder_A); - gpio_set_pulls(rotary_encoder_A, true, false); - pio_gpio_init(pio, rotary_encoder_B); - gpio_set_pulls(rotary_encoder_B, true, false); - // load the pio program into the pio memory - uint offset = pio_add_program(pio, &pio_rotary_encoder_program); - // make a sm config - pio_sm_config c = pio_rotary_encoder_program_get_default_config(offset); - // set the 'in' pins - sm_config_set_in_pins(&c, rotary_encoder_A); - // set shift to left: bits shifted by 'in' enter at the least - // significant bit (LSB), no autopush - sm_config_set_in_shift(&c, false, false, 0); - - - // set the IRQ handler - irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler_0); - // enable the IRQ - irq_set_enabled(PIO0_IRQ_0, true); - - // gpio_set_irq_enabled_with_callback(21, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); - // gpio_set_irq_enabled(22, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); - - pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_BITS; - - // init the sm. - // Note: the program starts after the jump table -> initial_pc = 16 - pio_sm_init(pio, sm, 16, &c); - // enable the sm - pio_sm_set_enabled(pio, sm, true); - - // set up second core on PICO to run velocity loop - // multicore_launch_core1(velocityLoop); - } - - // set the current rotation to a specific value - void set_rotation(int _rotation) - { - rotation = _rotation; - } - - // get the current rotation - int get_rotation(void) - { - return rotation; - } - - // get the current estimated velocity - float get_velocity(void) - { - return velocity; - } - - // non-blocking step, call every delta_time ms - static void velocityLoopStep(int delta_time) - { - velocity = ((float)(rotation - prev_rotation)) / ((float)delta_time); - prev_rotation = rotation; - } - - -private: - static void pio_irq_handler_0() - { - // test if irq 0 was raised - if (pio0_hw->irq & 1) - { - rotation = rotation - 1; - } - // test if irq 1 was raised - if (pio0_hw->irq & 2) - { - rotation = rotation + 1; - } - // clear both interrupts - pio0_hw->irq = 3; - } - - static void gpio_callback(uint gpio, uint32_t events) { - printf("%d\n", gpio) - } - - - // for running the velocity loop on another core, just pass this function - // static void velocityLoop() - // { - // int delta_time = 50; - // prev_rotation = rotation; - // while (true) - // { - // sleep_ms(delta_time) - // velocityLoopStep(delta_time) - // } - // } - - // the pio instance - PIO pio; - // the state machine - uint sm; - // the current location of rotation - static int rotation; - // the last velocity loop rotation recorded - static int prev_rotation; - // the current estimated velocity - static float velocity; -}; - -// Initialize static member of class Rotary_encoder -int RotaryEncoder::rotation = 0; -int RotaryEncoder::prev_rotation = 0; -float RotaryEncoder::velocity = 0; - -int main() -{ - // needed for printf - stdio_init_all(); - // the A of the rotary encoder is connected to GPIO 14, B to GPIO 15 - RotaryEncoder my_encoder(14); - // initialize the rotary encoder rotation as 0 - my_encoder.set_rotation(0); - - const short LOOP_TIME = 20; // in ms - - const float TICKS_PER_DEGREE = 374.0 / 360.0; - - // infinite loop to print the current rotation - while (true) - { - sleep_ms(LOOP_TIME); - - int pos = my_encoder.get_rotation(); - my_encoder.velocityLoopStep(LOOP_TIME); - - printf("rotation=%d\n", pos); - printf("velocity=%f\n", my_encoder.get_velocity() / TICKS_PER_DEGREE * 1000.0); - } -} \ No newline at end of file diff --git a/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.pio b/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.pio deleted file mode 100644 index 70479f6..0000000 --- a/dev/encoder/pio_rotary_encoder/pio_rotary_encoder.pio +++ /dev/null @@ -1,51 +0,0 @@ - -.program pio_rotary_encoder -.wrap_target -.origin 0 ; The jump table has to start at 0 - ; it contains the correct jumps for each of the 16 - ; combination of 4 bits formed by A'B'AB - ; A = current reading of pin_A of the rotary encoder - ; A' = previous reading of pin_A of the rotary encoder - ; B = current reading of pin_B of the rotary encoder - ; B' = previous reading of pin_B of the rotary encoder - jmp read ; 0000 = from 00 to 00 = no change in reading - jmp CW ; 0001 = from 00 to 01 = clockwise rotation - jmp CCW ; 0010 = from 00 to 10 = counter clockwise rotation - jmp read ; 0011 = from 00 to 11 = error - - jmp CCW ; 0100 = from 01 to 00 = counter clockwise rotation - jmp read ; 0101 = from 01 to 01 = no change in reading - jmp read ; 0110 = from 01 to 10 = error - jmp CW ; 0111 = from 01 to 11 = clockwise rotation - - jmp CW ; 1000 = from 10 to 00 = clockwise rotation - jmp read ; 1001 = from 10 to 01 = error - jmp read ; 1010 = from 10 to 10 = no change in reading - jmp CCW ; 1011 = from 10 to 11 = counter clockwise rotation - - jmp read ; 1100 = from 11 to 00 = error - jmp CCW ; 1101 = from 11 to 01 = counter clockwise rotation - jmp CW ; 1110 = from 11 to 10 = clockwise rotation - jmp read ; 1111 = from 11 to 11 = no change in reading - -pc_start: ; this is the entry point for the program - in pins 2 ; read the current values of A and B and use - ; them to initialize the previous values (A'B') -read: - mov OSR ISR ; the OSR is (after the next instruction) used to shift - ; the two bits with the previous values into the ISR - out ISR 2 ; shift the previous value into the ISR. This also sets - ; all other bits in the ISR to 0 - in pins 2 ; shift the current value into the ISR - ; the 16 LSB of the ISR now contain 000000000000A'B'AB - ; this represents a jmp instruction to the address A'B'AB - mov exec ISR ; do the jmp encoded in the ISR -CW: ; a clockwise rotation was detected - irq 0 ; signal a clockwise rotation via an IRQ - jmp read ; jump to reading the current values of A and B -CCW: ; a counter clockwise rotation was detected - irq 1 ; signal a counter clockwise rotation via an IRQ -; jmp read ; jump to reading the current values of A and B. - ; the jmp isn't needed because of the .wrap, and the first - ; statement of the program happens to be a jmp read -.wrap \ No newline at end of file diff --git a/dev/encoder/quadrature_encoder.cpp b/dev/encoder/quadrature_encoder.cpp index b09cf39..1702fbf 100644 --- a/dev/encoder/quadrature_encoder.cpp +++ b/dev/encoder/quadrature_encoder.cpp @@ -5,6 +5,7 @@ */ #include +#include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/timer.h" @@ -32,83 +33,111 @@ // encoder count updated and because of that it supports very high step rates. // +// 374 pulses per revolution (from the description at https://www.dfrobot.com/product-1462.html) +// 4x encoding bc quadrature (?) +const float ROT_PER_TICK = 1.0 / (4 * 374.0); +// TODO: update pulley ratio +const float PULLEY_RATIO = 0.3185 / 1.528; + class Encoder { public: - Encoder(uint pinA, uint sm, PIO pio, uint offset) + // Create an encoder. Reccommended NOT to use this class, use EncoderFactory::createEncoder() + // @param pinA the pin that encoder A channel is connected to, the B channel should connect to the next pin + // @param sm the state machine to keep track of the encoder, 0-3 + // @param which pio + Encoder(uint pinA, uint sm, PIO pio, float ratio, bool addProgram = true) { this->pio = pio; this->sm = sm; + this->ratio = ratio; + + uint offset = 0; + + // we don't really need to keep the offset, as this program must be loaded + // at offset 0 + if (addProgram) + uint offset = pio_add_program(pio, &quadrature_encoder_program); + quadrature_encoder_program_init(pio, sm, offset, pinA, 0); } + // updates the pos and velocity, call periodically. + // @param delta_time the time, in miliseconds, since last calling update void update(int delta_time) { - pos = quadrature_encoder_get_count(pio, sm); - velocity = ((float)(prev_pos - pos)) / delta_time * 360.0 / 374.0 * 1000.0; + pos = quadrature_encoder_get_count(pio, sm) * ratio; + velocity = ((prev_pos - pos) / delta_time) * 1000; prev_pos = pos; } - int get_pos() + // get position of wheel in rotations. resets on init. + // update() must be called preiodically for this to be accurate + float get_pos() { return pos; } + // get velocity of wheel in rotations per second. + // update() must be called preiodically for this to be accurate float get_velocity() { return velocity; } private: - int prev_pos, pos; + float prev_pos, pos; float velocity; + float ratio; PIO pio; uint sm; }; -int -main() +class EncoderFactory { - // int new_value_steer, delta_steer, old_value_steer = 0; - // int new_value_drive, delta_drive, old_value_drive = 0; +public: + // Create an encoder, automatically configuring the state machine and pio. + // @param pinA the A encoder channel, the B channel should be connected to the next pin + // @param ratio the ratio by which to multiply encoder outputs. ratio of 1 results in tick / sec + static Encoder createEncoder(uint pinA, float ratio) + { + if (encoder_count > 7) + { + throw std::out_of_range("reached encoder limit of 8"); + } - // Base pin to connect the A phase of the encoder. - // The B phase must be connected to the next pin - const uint PIN_STEER = 14; - const uint PIN_DRIVE = 16; + uint sm = encoder_count % 4; + PIO pio = encoder_count < 4 ? pio0 : pio1; - stdio_init_all(); + encoder_count++; + return Encoder(pinA, sm, pio, ratio, sm == 0); + } - // PIO pio = pio0; - const uint sm_steer = 0; - const uint sm_drive = 1; +private: + static uint encoder_count; +}; - // we don't really need to keep the offset, as this program must be loaded - // at offset 0 - uint offset0 = pio_add_program(pio0, &quadrature_encoder_program); - // quadrature_encoder_program_init(pio0, sm_steer, offset0, PIN_STEER, 0); - // quadrature_encoder_program_init(pio0, sm_drive, offset0, PIN_DRIVE, 0); - Encoder steer = Encoder(PIN_STEER, sm_steer, pio0, offset0); - Encoder drive = Encoder(PIN_DRIVE, sm_drive, pio0, offset0); +uint EncoderFactory::encoder_count = 0; - while (1) - { - // note: thanks to two's complement arithmetic delta will always - // be correct even when new_value wraps around MAXINT / MININT - // new_value_steer = quadrature_encoder_get_count(pio0, sm_steer); - // new_value_drive = quadrature_encoder_get_count(pio0, sm_drive); +int main() +{ + stdio_init_all(); - // delta_steer = new_value_steer - old_value_steer; - // delta_drive = new_value_drive - old_value_drive; + // Base pin to connect the A phase of the encoder (yellow wire). + // The B phase must be connected to the next pin (green wire) + const uint PIN_STEER = 14; + const uint PIN_DRIVE = 16; - // old_value_steer = new_value_steer; - // old_value_drive = new_value_drive; + Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * PULLEY_RATIO); + Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK); + while (1) + { steer.update(20); drive.update(20); - printf("steer position %8d, velocity %6f\n", steer.get_pos(), steer.get_velocity()); - printf("drive position %8d, velocity %6f\n", drive.get_pos(), drive.get_velocity()); + printf("steer position %8f, velocity %6f\n", steer.get_pos(), steer.get_velocity()); + printf("drive position %8f, velocity %6f\n", drive.get_pos(), drive.get_velocity()); sleep_ms(20); } } From 38aa2d9be7584850f37ff84215ba4632d6dde9b5 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 4 Nov 2023 15:00:57 -0700 Subject: [PATCH 26/65] encoder degrees --- dev/encoder/quadrature_encoder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/encoder/quadrature_encoder.cpp b/dev/encoder/quadrature_encoder.cpp index 1702fbf..79d2ca8 100644 --- a/dev/encoder/quadrature_encoder.cpp +++ b/dev/encoder/quadrature_encoder.cpp @@ -38,6 +38,7 @@ const float ROT_PER_TICK = 1.0 / (4 * 374.0); // TODO: update pulley ratio const float PULLEY_RATIO = 0.3185 / 1.528; +const float DEG_PER_ROT = 360.0; class Encoder { @@ -66,7 +67,7 @@ class Encoder // @param delta_time the time, in miliseconds, since last calling update void update(int delta_time) { - pos = quadrature_encoder_get_count(pio, sm) * ratio; + pos = quadrature_encoder_get_count(pio, sm) * ratio * DEG_PER_ROT; velocity = ((prev_pos - pos) / delta_time) * 1000; prev_pos = pos; } From 2c3567b4da9eee1de0dc46b3e4dd0268dee21870 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:34:23 -0700 Subject: [PATCH 27/65] more testing, wrote a byte to double function & started adapting motor test for multiple motors + classes Co-authored-by: ananya-manduva Co-authored-by: brendanRose1 Co-authored-by: inkyant --- dev/i2c_drive/i2c_drive_pico.c | 20 +- dev/pico_motor_test/CMakeLists.txt | 2 +- dev/pico_motor_test/cooler_motor_test.c | 115 --------- dev/pico_motor_test/cooler_motor_test.cpp | 285 ++++++++++++++++++++++ 4 files changed, 303 insertions(+), 119 deletions(-) delete mode 100644 dev/pico_motor_test/cooler_motor_test.c create mode 100644 dev/pico_motor_test/cooler_motor_test.cpp diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index 3dfde8e..5bd9e6e 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -91,9 +91,19 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } // Convert byte to motor double -int byte_to_motor_double(int input) -{ - return (double)input / 255.0 * 2.0 - 1.0; +// int byte_to_motor_double(int input) +// { +// return (double)input / 255.0 * 2.0 - 1.0; +// } + +void byte_to_motor_double(float& output, int arr[], int num){//output float to assign byte to, array of byte input, number/position of byte in float + char byte = 0b00000000; //start with empty byte + for(int i=0; i < 8; i++){ + byte = (char)(byte | arr[i]); //since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte + if(i!=7) //dont shift on first value, as you are directly writing from the array into byte + byte = byte<<1; //shift lsb to the left for new bit in array + } + *((unsigned char*)(&output)+num) = byte;//cast reference of output to char (shift to 1 byte), set value directly } int main() @@ -116,10 +126,14 @@ int main() printf("Input: "); for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) { + test = test | input[i]; + test << 1; printf("%f ", input[i]); + byte_to_motor_double(input); } printf("\n"); } + } return 0; diff --git a/dev/pico_motor_test/CMakeLists.txt b/dev/pico_motor_test/CMakeLists.txt index a05cd2e..1eddf0d 100644 --- a/dev/pico_motor_test/CMakeLists.txt +++ b/dev/pico_motor_test/CMakeLists.txt @@ -24,7 +24,7 @@ pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(cooler_motor_test cooler_motor_test.c ) +add_executable(cooler_motor_test cooler_motor_test.cpp ) pico_set_program_name(cooler_motor_test "cooler_motor_test") pico_set_program_version(cooler_motor_test "0.1") diff --git a/dev/pico_motor_test/cooler_motor_test.c b/dev/pico_motor_test/cooler_motor_test.c deleted file mode 100644 index 313951a..0000000 --- a/dev/pico_motor_test/cooler_motor_test.c +++ /dev/null @@ -1,115 +0,0 @@ -#include -#include -#include -#include - -// digital low on in# pins indicates direction, both high is no signal -#define turn_in1_pin 4 // 1A, forward direction -#define turn_in2_pin 5 // 1B, backward direction - -// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed -#define turn_pwm_pin 9 // 2A, turn motor speed -#define wheel_pwm_pin 8 // 2B, wheel motor speed -#define pwm_slice 4 -#define turn_channel PWM_CHAN_B -#define wheel_channel PWM_CHAN_A - -#define wheel_in1_pin 6 // 3A, forward direction -#define wheel_in2_pin 7 // 3B, backard direction - -#define count_max 65535 // number of counts in a cycle --> 1/count_max = freq - -int main() -{ - // setup stdio for printing - stdio_init_all(); - - // setup pins for pwm functions - gpio_init(turn_in1_pin); - gpio_init(turn_in2_pin); - gpio_init(wheel_in1_pin); - gpio_init(wheel_in2_pin); - - // set non-pwm pins to output - gpio_set_dir(turn_in1_pin, GPIO_OUT); - gpio_set_dir(turn_in2_pin, GPIO_OUT); - gpio_set_dir(wheel_in1_pin, GPIO_OUT); - gpio_set_dir(wheel_in2_pin, GPIO_OUT); - - // setup pwm - gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); - gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); - - // set pwm slice and channels - pwm_set_wrap(pwm_slice, count_max); - pwm_set_chan_level(pwm_slice, turn_channel, 0); - pwm_set_chan_level(pwm_slice, wheel_channel, 0); - - pwm_set_enabled(pwm_slice, true); - - // set initial x (turn) and y (wheel) values - double x = 0; - double y = 0.2; - - // flip values used for oscillate direction between 1 and -1 - int xflip = 0; - int yflip = 0; - - // step size for oscillation - float step = 0.001; - - while (1) - { - // turn motor - if (x == 0) - { // in1 and in2 are high - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 1); - } - else if (x < 0) - { // in1 is high and in2 is low - gpio_put(turn_in1_pin, 1); - gpio_put(wheel_in2_pin, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_in2_pin, 1); - gpio_put(wheel_in1_pin, 0); - } - - // wheel motor - if (y == 0) - { // in1 and in2 are high - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 1); - } - else if (y < 0) - { // in1 is high and in2 is low - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 0); - } - else - { // in1 is low and in2 is high - gpio_put(wheel_in2_pin, 1); - gpio_put(wheel_in1_pin, 0); - } - - // set pwm duty cycle - pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); - pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(y * count_max))); - - // increment x and y - x += step * (xflip ? -1 : 1); - y += step * (yflip ? -1 : 1); - - // flip x and y if they reach 1 or -1 - if (x == 1 || x == -1) - xflip = !(xflip); - if (y == 1 || y == -1) - yflip = !(yflip); - - printf("x: %f, y: %f\n", x, y); - sleep_ms(20); - } - return 0; -} diff --git a/dev/pico_motor_test/cooler_motor_test.cpp b/dev/pico_motor_test/cooler_motor_test.cpp new file mode 100644 index 0000000..a29bf16 --- /dev/null +++ b/dev/pico_motor_test/cooler_motor_test.cpp @@ -0,0 +1,285 @@ +// #include +// #include +// #include +// #include +// #include + +// // digital low on in# pins indicates direction, both high is no signal +// #define steer_in1_pin 4 // 1A, forward direction +// #define steer_in2_pin 5 // 1B, backward direction + +// #define drive_in1_pin 6 // 3A, forward direction +// #define drive_in2_pin 7 // 3B, backard direction + +// // #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +// #define steer_pwm_pin 9 // 2A, steer motor speed +// #define drive_pwm_pin 8 // 2B, drive motor speed +// #define pwm_slice 4 +// #define steer_channel PWM_CHAN_B +// #define drive_channel PWM_CHAN_A + +// #define count_max 65535 // number of counts in a cycle --> 1/count_max = freq + +// /* class Motor +// { +// public: +// Motor(uint gpio_dir_pin, uint pwm_pin) +// { +// this->pwm_pin = pwm_pin; +// this->gpio_dir_pin = gpio_dir_pin; + +// // setup pins for pwm functions +// gpio_init(gpio_dir_pin); +// gpio_init(gpio_dir_pin + 1); + +// // set non-pwm pins to output +// gpio_set_dir(gpio_dir_pin, GPIO_OUT); +// gpio_set_dir(gpio_dir_pin + 1, GPIO_OUT); + +// // setup pwm +// gpio_set_function(pwm_pin, GPIO_FUNC_PWM); + +// // set pwm slice and channels +// pwm_set_wrap(pwm_pin / 2, count_max); +// pwm_set_chan_level(pwm_pin / 2, pwm_pin % 2 == 0 ? PWM_CHAN_A : PWM_CHAN_B, 0); + +// pwm_set_enabled(pwm_pin / 2, true); +// } + +// void set(int power) +// { +// power = power > 1 ? 1 : power; +// power = power < -1 ? -1 : power; +// if (power == 0) +// { +// // setting both to high stops motor +// gpio_put(steer_in1_pin, 1); +// gpio_put(steer_in2_pin, 1); +// } +// else +// { +// gpio_put(steer_in1_pin, power < 0 ? 1 : 0); +// gpio_put(steer_in2_pin, power < 0 ? 0 : 1); +// } + +// pwm_set_chan_level(pwm_pin / 2, pwm_pin % 2 == 0 ? PWM_CHAN_A : PWM_CHAN_B, abs((int)(power * count_max))); +// } + +// private: +// uint pwm_pin; +// uint gpio_dir_pin; +// }; */ + +// int main() +// { +// // setup stdio for printing +// stdio_init_all(); + +// // setup pins for pwm functions +// gpio_init(steer_in1_pin); +// gpio_init(steer_in2_pin); +// gpio_init(drive_in1_pin); +// gpio_init(drive_in2_pin); + +// // set non-pwm pins to output +// gpio_set_dir(steer_in1_pin, GPIO_OUT); +// gpio_set_dir(steer_in2_pin, GPIO_OUT); +// gpio_set_dir(drive_in1_pin, GPIO_OUT); +// gpio_set_dir(drive_in2_pin, GPIO_OUT); + +// // setup pwm +// gpio_set_function(steer_pwm_pin, GPIO_FUNC_PWM); +// gpio_set_function(drive_pwm_pin, GPIO_FUNC_PWM); + +// // set pwm slice and channels +// pwm_set_wrap(pwm_slice, count_max); +// pwm_set_chan_level(pwm_slice, steer_channel, 0); +// pwm_set_chan_level(pwm_slice, drive_channel, 0); + +// pwm_set_enabled(pwm_slice, true); + +// // step size for oscillation +// float step = 0.01; +// int a = 0; + +// while (1) +// { +// float power = sin(a * step); +// // steer motor +// if (power == 0) +// { // in1 and in2 are high +// gpio_put(steer_in1_pin, 1); +// gpio_put(steer_in2_pin, 1); +// } +// else if (power < 0) +// { // in1 is high and in2 is low +// gpio_put(steer_in1_pin, 1); +// gpio_put(steer_in2_pin, 0); +// } +// else +// { // in1 is low and in2 is high +// gpio_put(steer_in1_pin, 0); +// gpio_put(steer_in2_pin, 1); +// } + +// // drive motor +// if (power == 0) +// { // in1 and in2 are high +// gpio_put(drive_in1_pin, 1); +// gpio_put(drive_in2_pin, 1); +// } +// else if (power < 0) +// { // in1 is high and in2 is low +// gpio_put(drive_in1_pin, 1); +// gpio_put(drive_in2_pin, 0); +// } +// else +// { // in1 is low and in2 is high +// gpio_put(drive_in1_pin, 0); +// gpio_put(drive_in2_pin, 1); +// } + +// // set pwm duty cycle +// pwm_set_chan_level(pwm_slice, steer_channel, abs((int)(power * count_max))); +// pwm_set_chan_level(pwm_slice, drive_channel, abs((int)(power * count_max))); + +// printf("power: %f %d\n", power, a); + +// a++; +// if (a * step >= 6.28) +// a = 0; + +// sleep_ms(20); +// } +// return 0; +// } + + +#include +#include +#include +#include + +// digital low on in# pins indicates direction, both high is no signal +#define turn_in1_pin 4 // 1A, forward direction +#define turn_in2_pin 5 // 1B, backward direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define turn_pwm_pin 9 // 2A, turn motor speed +#define wheel_pwm_pin 8 // 2B, wheel motor speed +#define pwm_slice 4 +#define turn_channel PWM_CHAN_B +#define wheel_channel PWM_CHAN_A + +#define wheel_in1_pin 6 // 3A, forward direction +#define wheel_in2_pin 7 // 3B, backard direction + +#define freq 500 // note: use clock management frequencies to set frequency +#define duty_cycle 1 +#define count_max 65535 + +void setup() +{ // setup pins for pwm functions + stdio_init_all(); + gpio_init(turn_in1_pin); + gpio_init(turn_in2_pin); + gpio_init(wheel_in1_pin); + gpio_init(wheel_in2_pin); + + // check if default output signal is 0, for now put this in + gpio_put(turn_in1_pin, 0); + gpio_put(turn_in2_pin, 0); + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 0); + + gpio_set_dir(turn_in1_pin, GPIO_OUT); + gpio_set_dir(turn_in2_pin, GPIO_OUT); + gpio_set_dir(wheel_in1_pin, GPIO_OUT); + gpio_set_dir(wheel_in2_pin, GPIO_OUT); + + gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); + pwm_set_wrap(pwm_slice, count_max); + pwm_set_chan_level(pwm_slice, turn_channel, 0); + pwm_set_chan_level(pwm_slice, wheel_channel, 0); + + pwm_set_enabled(pwm_slice, true); +} + +int main() +{ + setup(); + double x = 0; + double y = 0; + + int xflip = 0; + int yflip = 0; + + float step = 0.001; + + while (1) + { + // // turn motor + if (x == 0) + { // in1 and in2 are high + // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + } + else if (x < 0) + { // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + gpio_put(turn_in1_pin, 0); + } + + // wheel motor + if (x == 0) + { // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + } + else if (x < 0) + { // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(wheel_in2_pin, 1); + gpio_put(wheel_in1_pin, 0); + } + + // pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(x * count_max))); + // printf("hello world\n"); + if (xflip) + { + x -= step; + } + else + { + x += step; + } + // if (xflip) + // { + // x -= step; + // } + // else + // { + // x += step; + // } + if (x >= 1 || x <= -1) + xflip = !(xflip); + // if (x >= 1 || x <= -1) + // xflip = !(xflip); + + printf("x: %f, x: %f\n", x, x); + sleep_ms(20); + } + return 0; +} \ No newline at end of file From 7ee15fddad16074634b453407b38e9911813e98f Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:57:14 -0800 Subject: [PATCH 28/65] updated comments, ratios --- dev/encoder/README.md | 15 --------------- dev/encoder/quadrature_encoder.cpp | 21 +++++++++++---------- 2 files changed, 11 insertions(+), 25 deletions(-) delete mode 100644 dev/encoder/README.md diff --git a/dev/encoder/README.md b/dev/encoder/README.md deleted file mode 100644 index 263bc4c..0000000 --- a/dev/encoder/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# ReadIncrementalEncoderPio -Reading high Speed Incremental Encoders with PIO in Rpi Pico and C++ - -You could just boot the .uf2 file to the pico with encoder connected to pins GPio 16 and 17 - -# Encoder Image -![image](https://res.cloudinary.com/rsc/image/upload/b_rgb:FFFFFF,c_pad,dpr_1.0,f_auto,q_auto,w_700/c_pad,w_700/F7450089-01) -# Pico Image -![image](https://hackster.imgix.net/uploads/attachments/1396743/rp2040_0MscTWuAMG.png?auto=compress%2Cformat&w=740&h=555&fit=max) -# Getting Started - -This simple guide is aimed to help you setup and run the program successfully in no time. -# Software Requirements - - follow this link https://shawnhymel.com/2096/how-to-set-up-raspberry-pi-pico-c-c-toolchain-on-windows-with-vs-code/ diff --git a/dev/encoder/quadrature_encoder.cpp b/dev/encoder/quadrature_encoder.cpp index 79d2ca8..a1e3079 100644 --- a/dev/encoder/quadrature_encoder.cpp +++ b/dev/encoder/quadrature_encoder.cpp @@ -34,9 +34,9 @@ // // 374 pulses per revolution (from the description at https://www.dfrobot.com/product-1462.html) -// 4x encoding bc quadrature (?) +// 4x because the encoder has 2 sensors each w/ rising & falling edges, so each pulse results in +// 4 counts received (https://deltamotion.com/support/webhelp/rmctools/Controller_Features/Transducer_Basics/Quadrature_Fundamentals.htm) const float ROT_PER_TICK = 1.0 / (4 * 374.0); -// TODO: update pulley ratio const float PULLEY_RATIO = 0.3185 / 1.528; const float DEG_PER_ROT = 360.0; @@ -47,7 +47,8 @@ class Encoder // @param pinA the pin that encoder A channel is connected to, the B channel should connect to the next pin // @param sm the state machine to keep track of the encoder, 0-3 // @param which pio - Encoder(uint pinA, uint sm, PIO pio, float ratio, bool addProgram = true) + // @param ratio the ratio by which to multiply encoder ticks + Encoder(uint pinA, uint sm, PIO pio, float ratio = 1.0, bool addProgram = true) { this->pio = pio; this->sm = sm; @@ -72,15 +73,15 @@ class Encoder prev_pos = pos; } - // get position of wheel in rotations. resets on init. - // update() must be called preiodically for this to be accurate + // get position of wheel in ticks, multiplied by any provided ratio. resets on init. + // update() must be called periodically for this to be accurate float get_pos() { return pos; } - // get velocity of wheel in rotations per second. - // update() must be called preiodically for this to be accurate + // get velocity of wheel in ticks per second, multiplied by any provided ratio. + // update() must be called periodically for this to be accurate float get_velocity() { return velocity; @@ -100,7 +101,7 @@ class EncoderFactory // Create an encoder, automatically configuring the state machine and pio. // @param pinA the A encoder channel, the B channel should be connected to the next pin // @param ratio the ratio by which to multiply encoder outputs. ratio of 1 results in tick / sec - static Encoder createEncoder(uint pinA, float ratio) + static Encoder createEncoder(uint pinA, float ratio = 1.0) { if (encoder_count > 7) { @@ -129,8 +130,8 @@ int main() const uint PIN_STEER = 14; const uint PIN_DRIVE = 16; - Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * PULLEY_RATIO); - Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK); + Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); + Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK * DEG_PER_ROT); while (1) { From f431f37300c5148933d03e26b5ecfdd6b7ec23cd Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:43:57 -0800 Subject: [PATCH 29/65] Renamed byte_to_motor_double & added endianess test in i2c_drive_pico.c Added test for endianess to correct order of bytes assigned to returned float in byte_to_motor_double byte_to_motor_double renamed to byte_to_motor_float for better clarity on return type --- dev/i2c_drive/i2c_drive_pico.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index 5bd9e6e..af25e14 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -96,14 +96,21 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // return (double)input / 255.0 * 2.0 - 1.0; // } -void byte_to_motor_double(float& output, int arr[], int num){//output float to assign byte to, array of byte input, number/position of byte in float +void byte_to_motor_float(float& output, int arr[], int num){//output float to assign byte to, array of byte input, number/position of byte in float char byte = 0b00000000; //start with empty byte for(int i=0; i < 8; i++){ byte = (char)(byte | arr[i]); //since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte if(i!=7) //dont shift on first value, as you are directly writing from the array into byte byte = byte<<1; //shift lsb to the left for new bit in array } - *((unsigned char*)(&output)+num) = byte;//cast reference of output to char (shift to 1 byte), set value directly + //test for endianess + int x = 1; + char *y = (char*)&x; + //cast reference of output to char (shift to 1 byte), set value directly + if(*y) + *((unsigned char*)(&output)+(3-num)) = byte; //assignment based on little endianess + else + *((unsigned char*)(&output)+num) = byte; //assignment based on big endianess } int main() From 1c4dc8483bfd66907206d3cf9c03a3dbb92ed644 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Thu, 9 Nov 2023 00:18:37 -0800 Subject: [PATCH 30/65] Read joyX and joyY bytes from pi Set floats joyX and joyY to the values sent from the pico in main() --- dev/i2c_drive/i2c_drive_pico.c | 37 +++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index af25e14..350ca38 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -95,14 +95,18 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // { // return (double)input / 255.0 * 2.0 - 1.0; // } - -void byte_to_motor_float(float& output, int arr[], int num){//output float to assign byte to, array of byte input, number/position of byte in float +char array_to_byte(int bytearray[]){ char byte = 0b00000000; //start with empty byte for(int i=0; i < 8; i++){ byte = (char)(byte | arr[i]); //since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte if(i!=7) //dont shift on first value, as you are directly writing from the array into byte byte = byte<<1; //shift lsb to the left for new bit in array } + return byte; +} + +void byte_to_motor_float(float& output, int arr[], int num){//output float to assign byte to, array of byte input, number/position of byte in float + char byte = array_to_byte(arr); //test for endianess int x = 1; char *y = (char*)&x; @@ -113,6 +117,12 @@ void byte_to_motor_float(float& output, int arr[], int num){//output float to as *((unsigned char*)(&output)+num) = byte; //assignment based on big endianess } +int byteoffset = 0; +bool read = false; + +float joyX = 0.0f; +float joyY = 0.0f; + int main() { stdio_init_all(); @@ -130,15 +140,22 @@ int main() printf("Status: %d\n", input_status); if (input_status == 1) { - printf("Input: "); - for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) - { - test = test | input[i]; - test << 1; - printf("%f ", input[i]); - byte_to_motor_double(input); + //Byte order is 0xFF, joyX, joyY, 0xFb, 0x00, looking when 0xFF is read and ending when 0xFb is read + if(array_to_byte(input)==0xFF){ + printf("Message start") + read = true; } - printf("\n"); + if(read){ + if(byteoffset < 4){ + byte_to_motor_float(joyX, input, byteoffset); + }else{ + byte_to_motor_float(joyY, input, byteoffset - 4); + } + byteoffset++; + if(byteoffset >= 8) + read = false; + } + } } From f4bbc99fc2a11d77a8761e8356452aa726690eab Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Wed, 15 Nov 2023 21:34:56 -0800 Subject: [PATCH 31/65] pico motor control w/ classes works! Co-authored-by: Anthony Furman --- dev/pico_motor_test/CMakeLists.txt | 3 +- dev/pico_motor_test/class_motor_test.cpp | 113 +++++++++++++ dev/pico_motor_test/cooler_motor_test.cpp | 196 +++------------------- 3 files changed, 135 insertions(+), 177 deletions(-) create mode 100644 dev/pico_motor_test/class_motor_test.cpp diff --git a/dev/pico_motor_test/CMakeLists.txt b/dev/pico_motor_test/CMakeLists.txt index 1eddf0d..95e237d 100644 --- a/dev/pico_motor_test/CMakeLists.txt +++ b/dev/pico_motor_test/CMakeLists.txt @@ -24,7 +24,8 @@ pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(cooler_motor_test cooler_motor_test.cpp ) +# add_executable(cooler_motor_test cooler_motor_test.cpp ) +add_executable(cooler_motor_test class_motor_test.cpp ) pico_set_program_name(cooler_motor_test "cooler_motor_test") pico_set_program_version(cooler_motor_test "0.1") diff --git a/dev/pico_motor_test/class_motor_test.cpp b/dev/pico_motor_test/class_motor_test.cpp new file mode 100644 index 0000000..0af26c5 --- /dev/null +++ b/dev/pico_motor_test/class_motor_test.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +// digital low on in# pins indicates direction, both high is no signal +#define steer_in1_pin 4 // 1A, forward direction +#define steer_in2_pin 5 // 1B, backward direction + +#define drive_in1_pin 6 // 3A, forward direction +#define drive_in2_pin 7 // 3B, backard direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define steer_pwm_pin 9 // 2A, steer motor speed +#define drive_pwm_pin 8 // 2B, drive motor speed +#define pwm_slice 4 +#define steer_channel PWM_CHAN_B +#define drive_channel PWM_CHAN_A + +#define count_max 65535 // number of counts in a cycle --> 1/count_max = freq + +class Motor +{ +public: + Motor(uint pin1, uint pin2, uint pwm_pin, uint slice_num, pwm_chan channel, bool slice = true) + { + this->pwm_pin = pwm_pin; + this->pin1 = pin1; + this->pin2 = pin2; + this->slice_num = slice_num; + this->channel = channel; + + // setup pins for pwm functions + gpio_init(pin1); + gpio_init(pin2); + + // set non-pwm pins to output + gpio_set_dir(pin1, GPIO_OUT); + gpio_set_dir(pin2, GPIO_OUT); + + // setup pwm + gpio_set_function(pwm_pin, GPIO_FUNC_PWM); + + // set pwm slice and channel + if (slice) + pwm_set_wrap(slice_num, count_max); + + pwm_set_chan_level(slice_num, channel, 0); + + if (slice) + pwm_set_enabled(slice_num, true); + } + + void set(float power) + { + power = power > 1 ? 1 : power; + power = power < -1 ? -1 : power; + printf("input power: %f\n", power); + + if (power == 0) + { // in1 and in2 are high + gpio_put(this->pin1, 1); + gpio_put(this->pin2, 1); + } + else if (power < 0) + { // in1 is high and in2 is low + gpio_put(this->pin1, 1); + gpio_put(this->pin2, 0); + } + else + { // in1 is low and in2 is high + gpio_put(this->pin1, 0); + gpio_put(this->pin2, 1); + } + pwm_set_chan_level(this->slice_num, this->channel, abs((int)(power * count_max))); + } + +private: + uint pwm_pin; + uint pin1; + uint pin2; + uint slice_num; + pwm_chan channel; +}; + +int main() +{ + // setup stdio for printing + stdio_init_all(); + + Motor steer = Motor(steer_in1_pin, steer_in2_pin, steer_pwm_pin, pwm_slice, steer_channel, true); + Motor drive = Motor(drive_in1_pin, drive_in2_pin, drive_pwm_pin, pwm_slice, drive_channel, false); + + // step size for oscillation + float step = 0.01; + int a = 0; + + while (1) + { + float power = sin(a * step); + + steer.set(power); + drive.set(power); + + a++; + if (a * step >= 6.28) + a = 0; + + sleep_ms(20); + } + return 0; +} diff --git a/dev/pico_motor_test/cooler_motor_test.cpp b/dev/pico_motor_test/cooler_motor_test.cpp index a29bf16..3061a73 100644 --- a/dev/pico_motor_test/cooler_motor_test.cpp +++ b/dev/pico_motor_test/cooler_motor_test.cpp @@ -1,160 +1,3 @@ -// #include -// #include -// #include -// #include -// #include - -// // digital low on in# pins indicates direction, both high is no signal -// #define steer_in1_pin 4 // 1A, forward direction -// #define steer_in2_pin 5 // 1B, backward direction - -// #define drive_in1_pin 6 // 3A, forward direction -// #define drive_in2_pin 7 // 3B, backard direction - -// // #define motor_pwm_pin 9 // 2A, 2B take up by motor speed -// #define steer_pwm_pin 9 // 2A, steer motor speed -// #define drive_pwm_pin 8 // 2B, drive motor speed -// #define pwm_slice 4 -// #define steer_channel PWM_CHAN_B -// #define drive_channel PWM_CHAN_A - -// #define count_max 65535 // number of counts in a cycle --> 1/count_max = freq - -// /* class Motor -// { -// public: -// Motor(uint gpio_dir_pin, uint pwm_pin) -// { -// this->pwm_pin = pwm_pin; -// this->gpio_dir_pin = gpio_dir_pin; - -// // setup pins for pwm functions -// gpio_init(gpio_dir_pin); -// gpio_init(gpio_dir_pin + 1); - -// // set non-pwm pins to output -// gpio_set_dir(gpio_dir_pin, GPIO_OUT); -// gpio_set_dir(gpio_dir_pin + 1, GPIO_OUT); - -// // setup pwm -// gpio_set_function(pwm_pin, GPIO_FUNC_PWM); - -// // set pwm slice and channels -// pwm_set_wrap(pwm_pin / 2, count_max); -// pwm_set_chan_level(pwm_pin / 2, pwm_pin % 2 == 0 ? PWM_CHAN_A : PWM_CHAN_B, 0); - -// pwm_set_enabled(pwm_pin / 2, true); -// } - -// void set(int power) -// { -// power = power > 1 ? 1 : power; -// power = power < -1 ? -1 : power; -// if (power == 0) -// { -// // setting both to high stops motor -// gpio_put(steer_in1_pin, 1); -// gpio_put(steer_in2_pin, 1); -// } -// else -// { -// gpio_put(steer_in1_pin, power < 0 ? 1 : 0); -// gpio_put(steer_in2_pin, power < 0 ? 0 : 1); -// } - -// pwm_set_chan_level(pwm_pin / 2, pwm_pin % 2 == 0 ? PWM_CHAN_A : PWM_CHAN_B, abs((int)(power * count_max))); -// } - -// private: -// uint pwm_pin; -// uint gpio_dir_pin; -// }; */ - -// int main() -// { -// // setup stdio for printing -// stdio_init_all(); - -// // setup pins for pwm functions -// gpio_init(steer_in1_pin); -// gpio_init(steer_in2_pin); -// gpio_init(drive_in1_pin); -// gpio_init(drive_in2_pin); - -// // set non-pwm pins to output -// gpio_set_dir(steer_in1_pin, GPIO_OUT); -// gpio_set_dir(steer_in2_pin, GPIO_OUT); -// gpio_set_dir(drive_in1_pin, GPIO_OUT); -// gpio_set_dir(drive_in2_pin, GPIO_OUT); - -// // setup pwm -// gpio_set_function(steer_pwm_pin, GPIO_FUNC_PWM); -// gpio_set_function(drive_pwm_pin, GPIO_FUNC_PWM); - -// // set pwm slice and channels -// pwm_set_wrap(pwm_slice, count_max); -// pwm_set_chan_level(pwm_slice, steer_channel, 0); -// pwm_set_chan_level(pwm_slice, drive_channel, 0); - -// pwm_set_enabled(pwm_slice, true); - -// // step size for oscillation -// float step = 0.01; -// int a = 0; - -// while (1) -// { -// float power = sin(a * step); -// // steer motor -// if (power == 0) -// { // in1 and in2 are high -// gpio_put(steer_in1_pin, 1); -// gpio_put(steer_in2_pin, 1); -// } -// else if (power < 0) -// { // in1 is high and in2 is low -// gpio_put(steer_in1_pin, 1); -// gpio_put(steer_in2_pin, 0); -// } -// else -// { // in1 is low and in2 is high -// gpio_put(steer_in1_pin, 0); -// gpio_put(steer_in2_pin, 1); -// } - -// // drive motor -// if (power == 0) -// { // in1 and in2 are high -// gpio_put(drive_in1_pin, 1); -// gpio_put(drive_in2_pin, 1); -// } -// else if (power < 0) -// { // in1 is high and in2 is low -// gpio_put(drive_in1_pin, 1); -// gpio_put(drive_in2_pin, 0); -// } -// else -// { // in1 is low and in2 is high -// gpio_put(drive_in1_pin, 0); -// gpio_put(drive_in2_pin, 1); -// } - -// // set pwm duty cycle -// pwm_set_chan_level(pwm_slice, steer_channel, abs((int)(power * count_max))); -// pwm_set_chan_level(pwm_slice, drive_channel, abs((int)(power * count_max))); - -// printf("power: %f %d\n", power, a); - -// a++; -// if (a * step >= 6.28) -// a = 0; - -// sleep_ms(20); -// } -// return 0; -// } - - #include #include #include @@ -174,8 +17,8 @@ #define wheel_in1_pin 6 // 3A, forward direction #define wheel_in2_pin 7 // 3B, backard direction -#define freq 500 // note: use clock management frequencies to set frequency -#define duty_cycle 1 +// #define freq 500 // note: use clock management frequencies to set frequency +// #define duty_cycle 1 #define count_max 65535 void setup() @@ -200,6 +43,7 @@ void setup() gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); pwm_set_wrap(pwm_slice, count_max); + pwm_set_chan_level(pwm_slice, turn_channel, 0); pwm_set_chan_level(pwm_slice, wheel_channel, 0); @@ -210,7 +54,7 @@ int main() { setup(); double x = 0; - double y = 0; + double y = 0.5; int xflip = 0; int yflip = 0; @@ -238,24 +82,24 @@ int main() } // wheel motor - if (x == 0) + if (y == 0) { // in1 and in2 are high gpio_put(wheel_in1_pin, 1); gpio_put(wheel_in2_pin, 1); } - else if (x < 0) + else if (y < 0) { // in1 is high and in2 is low gpio_put(wheel_in1_pin, 1); gpio_put(wheel_in2_pin, 0); } else { // in1 is low and in2 is high - gpio_put(wheel_in2_pin, 1); gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 1); } - // pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); - pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(x * count_max))); + pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(y * count_max))); // printf("hello world\n"); if (xflip) { @@ -265,20 +109,20 @@ int main() { x += step; } - // if (xflip) - // { - // x -= step; - // } - // else - // { - // x += step; - // } + if (yflip) + { + y -= step; + } + else + { + y += step; + } if (x >= 1 || x <= -1) xflip = !(xflip); - // if (x >= 1 || x <= -1) - // xflip = !(xflip); + if (y >= 1 || y <= -1) + yflip = !(yflip); - printf("x: %f, x: %f\n", x, x); + printf("x: %f, y: %f\n", x, y); sleep_ms(20); } return 0; From 42f11f1a9d2a0e8c4562b2a6d52ef7ddf7d4191a Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Wed, 15 Nov 2023 21:35:22 -0800 Subject: [PATCH 32/65] updates to i2c_drive_pico, doesn't work yet Co-authored-by: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> --- dev/i2c_drive/i2c_drive_pico.c | 60 ++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index 350ca38..b845cb7 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -95,26 +95,29 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // { // return (double)input / 255.0 * 2.0 - 1.0; // } -char array_to_byte(int bytearray[]){ - char byte = 0b00000000; //start with empty byte - for(int i=0; i < 8; i++){ - byte = (char)(byte | arr[i]); //since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte - if(i!=7) //dont shift on first value, as you are directly writing from the array into byte - byte = byte<<1; //shift lsb to the left for new bit in array +char array_to_byte(int bytearray[]) +{ + char byte = 0b00000000; // start with empty byte + for (int i = 0; i < 8; i++) + { + byte = (char)(byte | bytearray[i]); // since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte + if (i != 7) // dont shift on first value, as you are directly writing from the array into byte + byte = byte << 1; // shift lsb to the left for new bit in array } return byte; } -void byte_to_motor_float(float& output, int arr[], int num){//output float to assign byte to, array of byte input, number/position of byte in float +void byte_to_motor_float(float *output, int arr[], int num) +{ // output float to assign byte to, array of byte input, number/position of byte in float char byte = array_to_byte(arr); - //test for endianess + // test for endianess int x = 1; - char *y = (char*)&x; - //cast reference of output to char (shift to 1 byte), set value directly - if(*y) - *((unsigned char*)(&output)+(3-num)) = byte; //assignment based on little endianess + char *y = (char *)&x; + // cast reference of output to char (shift to 1 byte), set value directly + if (*y) + *((unsigned char *)(output) + (3 - num)) = byte; // assignment based on little endianess else - *((unsigned char*)(&output)+num) = byte; //assignment based on big endianess + *((unsigned char *)(output) + num) = byte; // assignment based on big endianess } int byteoffset = 0; @@ -140,24 +143,33 @@ int main() printf("Status: %d\n", input_status); if (input_status == 1) { - //Byte order is 0xFF, joyX, joyY, 0xFb, 0x00, looking when 0xFF is read and ending when 0xFb is read - if(array_to_byte(input)==0xFF){ - printf("Message start") + // print output of array_to_byte(input) + printf("Byte: %d\n", array_to_byte(input)); + // Byte order is 0xFF, joyX, joyY, 0xFb, 0x00, looking when 0xFF is read and ending when 0xFb is read + if (array_to_byte(input) == MESSAGE_START) + { + printf("Message start"); read = true; } - if(read){ - if(byteoffset < 4){ - byte_to_motor_float(joyX, input, byteoffset); - }else{ - byte_to_motor_float(joyY, input, byteoffset - 4); + if (read) + { + if (byteoffset < 4) + { + byte_to_motor_float(&joyX, input, byteoffset); + } + else + { + byte_to_motor_float(&joyY, input, byteoffset - 4); } byteoffset++; - if(byteoffset >= 8) + if (byteoffset >= 8) + { read = false; + byteoffset = 0; + } + printf("JoyX: %f, JoyY: %f\n", joyX, joyY); } - } - } return 0; From 6c3b8cfd1e21a2bed0adae2109406d0eabb4241d Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 18 Nov 2023 16:36:10 -0800 Subject: [PATCH 33/65] send data with struct Co-authored-by: Caleb Cho <43869819+CALEBCH0@users.noreply.github.com> Co-authored-by: Sierra Janson <60485703+sierrajanson@users.noreply.github.com> Co-authored-by: Ananya Manduva <147952617+ananya-manduva@users.noreply.github.com> --- dev/i2c_drive/i2c_drive_pi.py | 23 +++++++++++++++-------- dev/i2c_drive/i2c_drive_pico.c | 14 +++++++++----- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pi.py b/dev/i2c_drive/i2c_drive_pi.py index e4eb2bb..230f98e 100644 --- a/dev/i2c_drive/i2c_drive_pi.py +++ b/dev/i2c_drive/i2c_drive_pi.py @@ -1,6 +1,7 @@ import fcntl import os import time +import struct from Controller import PS4_Controller @@ -24,16 +25,22 @@ x = status.LeftJoystickX # Get the X position of the left joystick y = status.RightJoystickY # Get the Y position of the right joystick joystickswitch = x > 0 # Check if the joystick is moved to the right - data = [ - 0xFA, - int(joystickswitch), - int(joystickswitch), - 0xFB, - 0x00, - ] # Prepare the data to be sent + + x_b = struct.pack('f', x) + y_b = struct.pack('f', y) + + data = bytes([0xFA]) + \ + x_b + \ + y_b + \ + bytes([0xFB, 0x00]) # Prepare the data to be sent + # data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] + + print(len(data)) + print(bytes(data)) try: - os.write(i2c_fd, bytes(data)) # Write the data to the i2c device + os.write(i2c_fd, data) # Write the data to the i2c device + # os.write(i2c_fd, bytes(data)) # Write the data to the i2c device time.sleep(delay) # Wait for a while print("Sent data to Pico: ", list(data)) # Print the data that was sent except OSError: diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index b845cb7..6bf1c65 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -11,7 +11,7 @@ #define I2C_BAUDRATE 100 * 1000 // Define the length of the data packet -#define I2C_DATA_LENGTH 4 +#define I2C_DATA_LENGTH 10 #define MESSAGE_START 0xFA #define MESSAGE_STOP 0xFB @@ -41,12 +41,14 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) uint8_t tmp = i2c_read_byte_raw(i2c); // Check if the data is valid // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff + printf("Invalid data %x\n", tmp); if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) { break; } // Store the data incoming_data[data_index] = tmp; + printf("Data: %d\n", incoming_data[data_index]); data_index++; // set the event status to received last_event = 1; @@ -82,6 +84,8 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) data_index = 0; } + printf("Input: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8], input[9]); + // set the event status to finished last_event = 0; break; @@ -97,11 +101,11 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // } char array_to_byte(int bytearray[]) { - char byte = 0b00000000; // start with empty byte - for (int i = 0; i < 8; i++) + char byte = 0b0000; // start with empty byte + for (int i = 0; i < 4; i++) { byte = (char)(byte | bytearray[i]); // since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte - if (i != 7) // dont shift on first value, as you are directly writing from the array into byte + if (i != 3) // dont shift on first value, as you are directly writing from the array into byte byte = byte << 1; // shift lsb to the left for new bit in array } return byte; @@ -140,7 +144,7 @@ int main() while (1) { - printf("Status: %d\n", input_status); + // printf("Status: %d\n", input_status); if (input_status == 1) { // print output of array_to_byte(input) From b3bd3035ae4134c3a8364a1d4e979639d2f7161b Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:54:07 -0800 Subject: [PATCH 34/65] unsuccessful attempt to get floats out of byte data - endianness issues? Co-authored-by: AriSinervo <129624882+AriSinervo@users.noreply.github.com> Co-authored-by: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> --- dev/i2c_drive/i2c_drive_pico.c | 106 ++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index 6bf1c65..c6885ee 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -29,7 +29,8 @@ int last_event = 0; int data_index = 0; // Buffer for the input data -int input[I2C_DATA_LENGTH - 2]; +uint8_t input[I2C_DATA_LENGTH - 2]; +float joy[2]; // Handler for I2C events static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) @@ -85,6 +86,40 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } printf("Input: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8], input[9]); + uint32_t tmp_float = 0; + for (int i = 3; i > -1; i--) + { + tmp_float |= input[i]; + tmp_float = tmp_float << 8; + } + joy[0] = (float)tmp_float; + tmp_float = 0; + for (int i = 3; i > -1; i--) + { + tmp_float |= input[i]; + tmp_float = tmp_float << 8; + } + joy[1] = (float)tmp_float; + + printf("JoyX: %f, JoyY: %f\n", joy[0], joy[1]); + printf("x JoyX: %x, JoyY: %x\n", joy[0], joy[1]); + + // int byteoffset = 0; + + // if (byteoffset < 4) + // { + // byte_to_motor_float(&joy[0], input, byteoffset); + // } + // else + // { + // byte_to_motor_float(&joy[1], input, byteoffset - 4); + // } + // byteoffset++; + // if (byteoffset >= 8) + // { + // read = false; + // byteoffset = 0; + // } // set the event status to finished last_event = 0; @@ -99,19 +134,20 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // { // return (double)input / 255.0 * 2.0 - 1.0; // } -char array_to_byte(int bytearray[]) +char array_to_byte(uint8_t bytearray[]) { - char byte = 0b0000; // start with empty byte + // char byte = 0b00000000; // start with empty byte + uint32_t byte = 0; for (int i = 0; i < 4; i++) { byte = (char)(byte | bytearray[i]); // since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte - if (i != 3) // dont shift on first value, as you are directly writing from the array into byte - byte = byte << 1; // shift lsb to the left for new bit in array + if (i != 31) // dont shift on first value, as you are directly writing from the array into byte + byte = byte << 8; // shift lsb to the left for new bit in array } return byte; } -void byte_to_motor_float(float *output, int arr[], int num) +void byte_to_motor_float(float *output, uint8_t arr[], int num) { // output float to assign byte to, array of byte input, number/position of byte in float char byte = array_to_byte(arr); // test for endianess @@ -145,35 +181,35 @@ int main() while (1) { // printf("Status: %d\n", input_status); - if (input_status == 1) - { - // print output of array_to_byte(input) - printf("Byte: %d\n", array_to_byte(input)); - // Byte order is 0xFF, joyX, joyY, 0xFb, 0x00, looking when 0xFF is read and ending when 0xFb is read - if (array_to_byte(input) == MESSAGE_START) - { - printf("Message start"); - read = true; - } - if (read) - { - if (byteoffset < 4) - { - byte_to_motor_float(&joyX, input, byteoffset); - } - else - { - byte_to_motor_float(&joyY, input, byteoffset - 4); - } - byteoffset++; - if (byteoffset >= 8) - { - read = false; - byteoffset = 0; - } - printf("JoyX: %f, JoyY: %f\n", joyX, joyY); - } - } + // if (input_status == 1) + // { + // // print output of array_to_byte(input) + // printf("Byte: %d\n", array_to_byte(input)); + // // Byte order is 0xFF, joyX, joyY, 0xFb, 0x00, looking when 0xFF is read and ending when 0xFb is read + // if (array_to_byte(input) == MESSAGE_START) + // { + // printf("Message start"); + // read = true; + // } + // if (read) + // { + // if (byteoffset < 4) + // { + // byte_to_motor_float(&joyX, input, byteoffset); + // } + // else + // { + // byte_to_motor_float(&joyY, input, byteoffset - 4); + // } + // byteoffset++; + // if (byteoffset >= 8) + // { + // read = false; + // byteoffset = 0; + // } + // } + // } + // printf("JoyX: %f, JoyY: %f\n", joy[0], joy[1]); } return 0; From bdcb2ef4bbe7ed922348bf43a25899ed9a80c708 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sun, 3 Dec 2023 00:52:58 -0800 Subject: [PATCH 35/65] byte to float conversion isn't working Co-authored-by: AriSinervo Co-authored-by: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> --- dev/i2c_drive/i2c_drive_pico.c | 51 ++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index c6885ee..0dc3814 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -32,6 +32,13 @@ int data_index = 0; uint8_t input[I2C_DATA_LENGTH - 2]; float joy[2]; +void dump(const void *data, size_t len) { + const unsigned char *x = data; + printf("%02x", x[0]); + for (size_t k = 1; k < len; k++) printf(" %02x", x[k]); + puts(""); +} + // Handler for I2C events static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { @@ -42,9 +49,9 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) uint8_t tmp = i2c_read_byte_raw(i2c); // Check if the data is valid // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff - printf("Invalid data %x\n", tmp); if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) { + printf("Invalid data %x\n", tmp); break; } // Store the data @@ -87,25 +94,45 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) printf("Input: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8], input[9]); uint32_t tmp_float = 0; - for (int i = 3; i > -1; i--) + for (int i = 0; i < 4; i++) { - tmp_float |= input[i]; tmp_float = tmp_float << 8; + tmp_float |= input[i]; } - joy[0] = (float)tmp_float; + dump(&tmp_float, 4); + float jx = *((float*)&tmp_float); + printf("tester : <%.3f>\n", jx); + printf("len of jx : <%i>\n", sizeof(jx)); + + // joy[0] = *((float*)&tmp_float); tmp_float = 0; - for (int i = 3; i > -1; i--) + for (int i = 4; i < 8; i++) { - tmp_float |= input[i]; tmp_float = tmp_float << 8; + tmp_float |= input[i]; } - joy[1] = (float)tmp_float; + dump(&tmp_float, 4); + float jy = *((float*)&tmp_float); + printf("tester : <%.3f>\n", jy); + printf("len of jy : <%i>\n", sizeof(jy)); + // joy[1] = *((float*)&tmp_float); - printf("JoyX: %f, JoyY: %f\n", joy[0], joy[1]); - printf("x JoyX: %x, JoyY: %x\n", joy[0], joy[1]); + printf("JoyX: %f, JoyY: %f\n", jx, jy); + // printf("JoyX: %.3f, JoyY: %.3f\n", joy[0], joy[1]); - // int byteoffset = 0; + uint32_t tester = 0x3f9d70a4; + float f_test; + f_test = *((float*)&tester); + printf("tester : <%.3f>\n", f_test); + // printf("x JoyX: %x, JoyY: %x\n", joy[0], joy[1]); + // dump(&joy[0], 4); + // dump(&joy[1], 4); + // float x = *((float*)&0x3f7e0000) ; + // dump(&x, 4); + + + // int byteoffset = 0; // if (byteoffset < 4) // { // byte_to_motor_float(&joy[0], input, byteoffset); @@ -134,7 +161,7 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // { // return (double)input / 255.0 * 2.0 - 1.0; // } -char array_to_byte(uint8_t bytearray[]) +uint32_t array_to_byte(uint8_t bytearray[]) { // char byte = 0b00000000; // start with empty byte uint32_t byte = 0; @@ -149,7 +176,7 @@ char array_to_byte(uint8_t bytearray[]) void byte_to_motor_float(float *output, uint8_t arr[], int num) { // output float to assign byte to, array of byte input, number/position of byte in float - char byte = array_to_byte(arr); + uint32_t byte = array_to_byte(arr); // test for endianess int x = 1; char *y = (char *)&x; From 089f7e608436df04c06d767be77343b3f01323f8 Mon Sep 17 00:00:00 2001 From: ShahVishrut <53956360+ShahVishrut@users.noreply.github.com> Date: Sat, 13 Jan 2024 21:23:54 -0800 Subject: [PATCH 36/65] Update pi_i2c.py with support for multiple i2c devices Builds off of code from another branch --- dev/i2c-comms/pi_i2c.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/dev/i2c-comms/pi_i2c.py b/dev/i2c-comms/pi_i2c.py index a4b07c7..7ed5ed5 100644 --- a/dev/i2c-comms/pi_i2c.py +++ b/dev/i2c-comms/pi_i2c.py @@ -2,17 +2,33 @@ import os import time + I2C_PRIM = 0x0703 -# open i2c devices -i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) +# edit these two arrays for each i2c device +bus_numbers = [1] +pico_addresses = [0x08] -# set the i2c address of pico -pico_address = 0x08 -fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) +# open i2c devices (sudo apt install i2c-tools) +i2c_fds = [] +for i in range(len(bus_numbers)): + i2c_fds.append(os.open("/dev/i2c-" + bus_numbers[i], os.O_RDWR)) + fcntl.ioctl(i2c_fds[i], I2C_PRIM, pico_addresses[i]) # send data to pico data = [0x01, 0x02, 0x03] # example data -while (True): - os.write(i2c_fd, bytes(data)) - time.sleep(1) +while True: + for i in range(len(bus_numbers)): + try: + os.write(i2c_fds[i], bytes(data)) + time.sleep(0.02) + print("Sent data to Pico: ", list(data)) + except OSError: + print("Remote I/O Error") + # read data from pico + try: + incoming_data = os.read(i2c_fds[i], 3) # read 3 bytes + time.sleep(0.02) + print("Received data from Pico: ", list(incoming_data)) + except TimeoutError: + print("Timeout Error") From 7af874a99520ee206c60c88ab8d12ef3b5d53db0 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Sat, 20 Jan 2024 17:55:22 -0800 Subject: [PATCH 37/65] work work --- .vscode/settings.json | 3 + dev/i2c_drive/i2c_drive_pico.c | 112 +++++++++++++++++---------------- 2 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f0d6e10 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "C:/Users/Name/Documents/modbot/dev/i2c_drive" +} \ No newline at end of file diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index 0dc3814..606d5a9 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -32,10 +32,12 @@ int data_index = 0; uint8_t input[I2C_DATA_LENGTH - 2]; float joy[2]; -void dump(const void *data, size_t len) { +void dump(const void *data, size_t len) +{ const unsigned char *x = data; printf("%02x", x[0]); - for (size_t k = 1; k < len; k++) printf(" %02x", x[k]); + for (size_t k = 1; k < len; k++) + printf(" %02x", x[k]); puts(""); } @@ -44,23 +46,25 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { switch (event) { - case I2C_SLAVE_RECEIVE: // Pi has written some data + case I2C_SLAVE_RECEIVE:{ // Read the data uint8_t tmp = i2c_read_byte_raw(i2c); // Check if the data is valid // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) { - printf("Invalid data %x\n", tmp); + printf("Invalid data %x\n", tmp); break; } // Store the data incoming_data[data_index] = tmp; - printf("Data: %d\n", incoming_data[data_index]); + // printf("Data: %d\n", incoming_data[data_index]); data_index++; // set the event status to received last_event = 1; break; + } + case I2C_SLAVE_REQUEST: // Pi is requesting data // Write the data into the void @@ -72,7 +76,7 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart // if the last event was a receive event and the data is valid if (last_event == 1) - { + printf(""); //can use brackets, need to figure out where to wrap if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) { // move the data into the input array @@ -90,40 +94,34 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } } data_index = 0; - } + - printf("Input: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8], input[9]); - uint32_t tmp_float = 0; - for (int i = 0; i < 4; i++) - { - tmp_float = tmp_float << 8; - tmp_float |= input[i]; - } - dump(&tmp_float, 4); - float jx = *((float*)&tmp_float); - printf("tester : <%.3f>\n", jx); - printf("len of jx : <%i>\n", sizeof(jx)); + // printf("Input: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8], input[9]); + + // float jx = *((float *)&tmp_float); + // printf("tester : <%.3f>\n", jx); + // printf("len of jx : <%i>\n", sizeof(jx)); // joy[0] = *((float*)&tmp_float); - tmp_float = 0; - for (int i = 4; i < 8; i++) - { - tmp_float = tmp_float << 8; - tmp_float |= input[i]; - } - dump(&tmp_float, 4); - float jy = *((float*)&tmp_float); - printf("tester : <%.3f>\n", jy); - printf("len of jy : <%i>\n", sizeof(jy)); - // joy[1] = *((float*)&tmp_float); + // tmp_float = 0; + // for (int i = 4; i < 8; i++) + // { + // tmp_float = tmp_float << 8; + // tmp_float |= input[i]; + // } + // dump(&tmp_float, 4); + // float jy = *((float *)&tmp_float); + // printf("tester : <%.3f>\n", jy); + // printf("len of jy : <%i>\n", sizeof(jy)); + // // joy[1] = *((float*)&tmp_float); - printf("JoyX: %f, JoyY: %f\n", jx, jy); - // printf("JoyX: %.3f, JoyY: %.3f\n", joy[0], joy[1]); + // printf("JoyX: %f, JoyY: %f\n", jx, jy); + // // printf("JoyX: %.3f, JoyY: %.3f\n", joy[0], joy[1]); - uint32_t tester = 0x3f9d70a4; - float f_test; - f_test = *((float*)&tester); - printf("tester : <%.3f>\n", f_test); + // uint32_t tester = 0x3f9d70a4; + // float f_test; + // f_test = *((float *)&tester); + // printf("tester : <%.3f>\n", f_test); // printf("x JoyX: %x, JoyY: %x\n", joy[0], joy[1]); // dump(&joy[0], 4); @@ -131,25 +129,9 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // float x = *((float*)&0x3f7e0000) ; // dump(&x, 4); - - // int byteoffset = 0; - // if (byteoffset < 4) - // { - // byte_to_motor_float(&joy[0], input, byteoffset); - // } - // else - // { - // byte_to_motor_float(&joy[1], input, byteoffset - 4); - // } - // byteoffset++; - // if (byteoffset >= 8) - // { - // read = false; - // byteoffset = 0; - // } - + // set the event status to finished - last_event = 0; + // last_event = 0; break; default: break; @@ -168,7 +150,7 @@ uint32_t array_to_byte(uint8_t bytearray[]) for (int i = 0; i < 4; i++) { byte = (char)(byte | bytearray[i]); // since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte - if (i != 31) // dont shift on first value, as you are directly writing from the array into byte + if (i != 31) // dont shift on first value, as you are directly writing from the array into byte byte = byte << 8; // shift lsb to the left for new bit in array } return byte; @@ -207,6 +189,28 @@ int main() while (1) { + if (input_status ==1){ + uint64_t tmp_float = 0; + for (int i = 7; i >= 0; i--) + { + tmp_float |= /* tmp_float | */ input[i]; + if(i!=0) + tmp_float<<=8; + printf("%02x ", input[i]); + } + printf("\n"); + // printf("%x\n", tmp_float); + int x = *(((int*)&tmp_float)+1); + int y = *(((int*)&tmp_float)); + float f_x = *(((float*)&tmp_float)+1); + float f_y = *(((float*)&tmp_float)+0); + printf("%i %x ",x, x); + printf("%i %x\n",y, y); + printf("%f %x ",f_x, f_x); + printf("%f %x\n",f_y, f_y); + // printf("\n"); + tmp_float = 0; + } // printf("Status: %d\n", input_status); // if (input_status == 1) // { From 4b61ea0ee6fdee16c948c3939f1dc8492e6cfe1c Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Sat, 20 Jan 2024 18:55:47 -0800 Subject: [PATCH 38/65] Merge I2C and PWM control Completed pi to pico pipeline :D -todo for later: test deadband signal for motor --- dev/i2c_drive/i2c_drive_pico.c | 227 +++++++++++++++------------------ 1 file changed, 100 insertions(+), 127 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index 606d5a9..763bad3 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include // Define constants for I2C communication #define I2C_PICO_ADDR 0x08 @@ -16,6 +18,26 @@ #define MESSAGE_START 0xFA #define MESSAGE_STOP 0xFB + +// digital low on in# pins indicates direction, both high is no signal +#define turn_in1_pin 4 // 1A, forward direction +#define turn_in2_pin 5 // 1B, backward direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define turn_pwm_pin 9 // 2A, turn motor speed +#define wheel_pwm_pin 8 // 2B, wheel motor speed +#define pwm_slice 4 +#define turn_channel PWM_CHAN_B +#define wheel_channel PWM_CHAN_A + +#define wheel_in1_pin 6 // 3A, forward direction +#define wheel_in2_pin 7 // 3B, backard direction + +// #define freq 500 // note: use clock management frequencies to set frequency +// #define duty_cycle 1 +#define count_max 65535 + + // Buffer for incoming data uint8_t incoming_data[I2C_DATA_LENGTH]; @@ -30,16 +52,6 @@ int data_index = 0; // Buffer for the input data uint8_t input[I2C_DATA_LENGTH - 2]; -float joy[2]; - -void dump(const void *data, size_t len) -{ - const unsigned char *x = data; - printf("%02x", x[0]); - for (size_t k = 1; k < len; k++) - printf(" %02x", x[k]); - puts(""); -} // Handler for I2C events static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) @@ -76,7 +88,6 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart // if the last event was a receive event and the data is valid if (last_event == 1) - printf(""); //can use brackets, need to figure out where to wrap if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) { // move the data into the input array @@ -94,89 +105,50 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } } data_index = 0; - - - // printf("Input: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8], input[9]); - - // float jx = *((float *)&tmp_float); - // printf("tester : <%.3f>\n", jx); - // printf("len of jx : <%i>\n", sizeof(jx)); - - // joy[0] = *((float*)&tmp_float); - // tmp_float = 0; - // for (int i = 4; i < 8; i++) - // { - // tmp_float = tmp_float << 8; - // tmp_float |= input[i]; - // } - // dump(&tmp_float, 4); - // float jy = *((float *)&tmp_float); - // printf("tester : <%.3f>\n", jy); - // printf("len of jy : <%i>\n", sizeof(jy)); - // // joy[1] = *((float*)&tmp_float); - - // printf("JoyX: %f, JoyY: %f\n", jx, jy); - // // printf("JoyX: %.3f, JoyY: %.3f\n", joy[0], joy[1]); - - // uint32_t tester = 0x3f9d70a4; - // float f_test; - // f_test = *((float *)&tester); - // printf("tester : <%.3f>\n", f_test); - - // printf("x JoyX: %x, JoyY: %x\n", joy[0], joy[1]); - // dump(&joy[0], 4); - // dump(&joy[1], 4); - // float x = *((float*)&0x3f7e0000) ; - // dump(&x, 4); - - - // set the event status to finished - // last_event = 0; break; default: break; } } -// Convert byte to motor double -// int byte_to_motor_double(int input) -// { -// return (double)input / 255.0 * 2.0 - 1.0; -// } -uint32_t array_to_byte(uint8_t bytearray[]) -{ - // char byte = 0b00000000; // start with empty byte - uint32_t byte = 0; - for (int i = 0; i < 4; i++) - { - byte = (char)(byte | bytearray[i]); // since byte is empty and we only have 0 or 1, we can assign to lsb using | operator which doesn't modify the rest of the byte - if (i != 31) // dont shift on first value, as you are directly writing from the array into byte - byte = byte << 8; // shift lsb to the left for new bit in array - } - return byte; -} - -void byte_to_motor_float(float *output, uint8_t arr[], int num) -{ // output float to assign byte to, array of byte input, number/position of byte in float - uint32_t byte = array_to_byte(arr); - // test for endianess - int x = 1; - char *y = (char *)&x; - // cast reference of output to char (shift to 1 byte), set value directly - if (*y) - *((unsigned char *)(output) + (3 - num)) = byte; // assignment based on little endianess - else - *((unsigned char *)(output) + num) = byte; // assignment based on big endianess -} -int byteoffset = 0; bool read = false; float joyX = 0.0f; float joyY = 0.0f; +void setup() +{ // setup pins for pwm functions + stdio_init_all(); + gpio_init(turn_in1_pin); + gpio_init(turn_in2_pin); + gpio_init(wheel_in1_pin); + gpio_init(wheel_in2_pin); + + // check if default output signal is 0, for now put this in + gpio_put(turn_in1_pin, 0); + gpio_put(turn_in2_pin, 0); + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 0); + + gpio_set_dir(turn_in1_pin, GPIO_OUT); + gpio_set_dir(turn_in2_pin, GPIO_OUT); + gpio_set_dir(wheel_in1_pin, GPIO_OUT); + gpio_set_dir(wheel_in2_pin, GPIO_OUT); + + gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); + pwm_set_wrap(pwm_slice, count_max); + + pwm_set_chan_level(pwm_slice, turn_channel, 0); + pwm_set_chan_level(pwm_slice, wheel_channel, 0); + + pwm_set_enabled(pwm_slice, true); +} + int main() { + stdio_init_all(); // Initialize I2C at 100kHz @@ -187,60 +159,61 @@ int main() // Set I2C address for Pico i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); + setup();//setup for pwm + while (1) { if (input_status ==1){ - uint64_t tmp_float = 0; - for (int i = 7; i >= 0; i--) + uint64_t tmp_float = 0; //use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + for (int i = 7; i >= 0; i--) //bytes are stored in int8 array (64 bits), pico reads backwards { - tmp_float |= /* tmp_float | */ input[i]; + tmp_float |= input[i]; //write byte at a time to tmp_float and shift if(i!=0) - tmp_float<<=8; - printf("%02x ", input[i]); + tmp_float<<=8; //preserves order of bits in 64bit int } - printf("\n"); - // printf("%x\n", tmp_float); - int x = *(((int*)&tmp_float)+1); - int y = *(((int*)&tmp_float)); - float f_x = *(((float*)&tmp_float)+1); - float f_y = *(((float*)&tmp_float)+0); - printf("%i %x ",x, x); - printf("%i %x\n",y, y); - printf("%f %x ",f_x, f_x); - printf("%f %x\n",f_y, f_y); - // printf("\n"); - tmp_float = 0; + joyX = *(((float*)&tmp_float)+1); //convert to interprit bits as float (32 bits) + joyY = *(((float*)&tmp_float)); + printf("%f ", joyX); + printf("%f\n", joyY); //printing floats in console + tmp_float = 0; //clear float + } + + if (joyX == 0) + { // in1 and in2 are high + // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + } + else if (joyX < 0) + { // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + gpio_put(turn_in1_pin, 0); + } + + // wheel motor + if (joyY == 0) + { // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + } + else if (joyY < 0) + { // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); } - // printf("Status: %d\n", input_status); - // if (input_status == 1) - // { - // // print output of array_to_byte(input) - // printf("Byte: %d\n", array_to_byte(input)); - // // Byte order is 0xFF, joyX, joyY, 0xFb, 0x00, looking when 0xFF is read and ending when 0xFb is read - // if (array_to_byte(input) == MESSAGE_START) - // { - // printf("Message start"); - // read = true; - // } - // if (read) - // { - // if (byteoffset < 4) - // { - // byte_to_motor_float(&joyX, input, byteoffset); - // } - // else - // { - // byte_to_motor_float(&joyY, input, byteoffset - 4); - // } - // byteoffset++; - // if (byteoffset >= 8) - // { - // read = false; - // byteoffset = 0; - // } - // } - // } - // printf("JoyX: %f, JoyY: %f\n", joy[0], joy[1]); + else + { // in1 is low and in2 is high + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 1); + } + + pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(joyX* count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(joyY * count_max))); } return 0; From 8fe6c2e21afdf74c732cdf7f00bc52cbc4be6bda Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Sat, 20 Jan 2024 19:04:51 -0800 Subject: [PATCH 39/65] changed x-y assignment bits loaded in backwards, changed assignment of x and y --- dev/i2c_drive/i2c_drive_pico.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c index 763bad3..1b89574 100644 --- a/dev/i2c_drive/i2c_drive_pico.c +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -171,8 +171,8 @@ int main() if(i!=0) tmp_float<<=8; //preserves order of bits in 64bit int } - joyX = *(((float*)&tmp_float)+1); //convert to interprit bits as float (32 bits) - joyY = *(((float*)&tmp_float)); + joyX = *(((float*)&tmp_float)); //convert to interprit bits as float (32 bits) + joyY = *(((float*)&tmp_float)+1); printf("%f ", joyX); printf("%f\n", joyY); //printing floats in console tmp_float = 0; //clear float From 239d34eb83b5952b9964b1360f91e005d10345a6 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:25:00 -0800 Subject: [PATCH 40/65] swerve drive math test added function to output direction and orientation of each motor --- dev/basic_swerve/CMakeLists.txt | 55 ++++ dev/basic_swerve/Controller.py | 204 +++++++++++++++ dev/basic_swerve/basic_swerve_pi.py | 59 +++++ dev/basic_swerve/basic_swerve_pico.c | 220 ++++++++++++++++ dev/basic_swerve/joystick_sim.py | 195 +++++++++++++++ dev/basic_swerve/pico_sdk_import.cmake | 73 ++++++ dev/basic_swerve/procon.py | 331 +++++++++++++++++++++++++ dev/basic_swerve/swerve_test.py | 67 +++++ 8 files changed, 1204 insertions(+) create mode 100644 dev/basic_swerve/CMakeLists.txt create mode 100644 dev/basic_swerve/Controller.py create mode 100644 dev/basic_swerve/basic_swerve_pi.py create mode 100644 dev/basic_swerve/basic_swerve_pico.c create mode 100644 dev/basic_swerve/joystick_sim.py create mode 100644 dev/basic_swerve/pico_sdk_import.cmake create mode 100644 dev/basic_swerve/procon.py create mode 100644 dev/basic_swerve/swerve_test.py diff --git a/dev/basic_swerve/CMakeLists.txt b/dev/basic_swerve/CMakeLists.txt new file mode 100644 index 0000000..7e1ada4 --- /dev/null +++ b/dev/basic_swerve/CMakeLists.txt @@ -0,0 +1,55 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(basic_swerve_pico C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(basic_swerve_pico basic_swerve_pico.c ) + +pico_set_program_name(basic_swerve_pico "basic_swerve_pico") +pico_set_program_version(basic_swerve_pico "0.1") + +pico_enable_stdio_uart(basic_swerve_pico 0) +pico_enable_stdio_usb(basic_swerve_pico 1) + +# Add the standard library to the build +target_link_libraries(basic_swerve_pico + pico_stdlib + pico_i2c_slave + hardware_i2c + hardware_pwm + ) + +# Add the standard include files to the build +target_include_directories(basic_swerve_pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(basic_swerve_pico + hardware_i2c + ) + +pico_add_extra_outputs(basic_swerve_pico) + diff --git a/dev/basic_swerve/Controller.py b/dev/basic_swerve/Controller.py new file mode 100644 index 0000000..23c64ce --- /dev/null +++ b/dev/basic_swerve/Controller.py @@ -0,0 +1,204 @@ +import math +import threading + +from inputs import \ + get_gamepad # Import the get_gamepad function from the inputs module +from procon import ProCon # Import the ProCon class from the procon module + + +# This class represents a PS4 Controller +class PS4_Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + # This method resets all controller variables to their initial state + def reset_vars(self): + # Initialize all controller variables to 0 + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + # This method starts a new thread to monitor the controller + def start_thread(self, thread_args=()): + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=thread_args + ) + self._monitor_thread.daemon = ( + True # Set the thread as a daemon so it will end when the main program ends + ) + self._monitor_thread.start() # Start the thread + + # This method returns the current state of all buttons/triggers + def read(self): + return [ + self.LeftJoystickY, + self.LeftJoystickX, + self.RightJoystickY, + self.RightJoystickX, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + # This method returns the controller object itself + def read_self(self): + return self + + # This method applies a threshold to a value + def threshold(self, val): + return val if abs(val) > self.THRESHOLD else 0 + + def _monitor_controller(self): + while True: + events = get_gamepad() + for event in events: + if event.code == "ABS_Y": + self.LeftJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_X": + self.LeftJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RY": + self.RightJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RX": + self.RightJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_Z": + self.LeftTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "ABS_RZ": + self.RightTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "BTN_TL": + self.LeftBumper = event.state + elif event.code == "BTN_TR": + self.RightBumper = event.state + elif event.code == "BTN_SOUTH": + self.A = event.state + elif event.code == "BTN_NORTH": + self.Y = event.state # previously switched with X + elif event.code == "BTN_WEST": + self.X = event.state # previously switched with Y + elif event.code == "BTN_EAST": + self.B = event.state + elif event.code == "BTN_THUMBL": + self.LeftThumb = event.state + elif event.code == "BTN_THUMBR": + self.RightThumb = event.state + elif event.code == "BTN_SELECT": + self.Back = event.state + elif event.code == "BTN_START": + self.Start = event.state + elif event.code == "BTN_TRIGGER_HAPPY1": + self.LeftDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY2": + self.RightDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY3": + self.UpDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY4": + self.DownDPad = event.state + + +# This class represents the Xbox Controller in WRP used for the CPSRC GEM +class Gem_Xbox_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + +# This class represents the Nintendo Pro Controller +class Nintendo_Pro_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.1 # Threshold for joystick deadzone + self.controller = ProCon() # Initialize the ProCon controller + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread( + self.procon_callback_func + ) # Start a new thread to monitor the controller + + # This method is called when the ProCon controller state changes + def procon_callback_func(self, buttons, l_stick, r_stick, *_): + # Update the controller variables based on the new state + # The joystick values are normalized between -1 and 1 + # The threshold method is used to apply a deadband to the joystick values + # The button values are either 0 or 1 + self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) + self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) + self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) + self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) + self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) + self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) + self.LeftBumper = buttons[ProCon.Button.L] + self.RightBumper = buttons[ProCon.Button.R] + self.A = buttons[ProCon.Button.A] + self.B = buttons[ProCon.Button.B] + self.X = buttons[ProCon.Button.X] + self.Y = buttons[ProCon.Button.Y] + self.LeftThumb = buttons[ProCon.Button.LS] + self.RightThumb = buttons[ProCon.Button.RS] + self.Back = buttons[ProCon.Button.MINUS] + self.Start = buttons[ProCon.Button.PLUS] + self.LeftDPad = buttons[ProCon.Button.LEFT] + self.RightDPad = buttons[ProCon.Button.RIGHT] + self.UpDPad = buttons[ProCon.Button.UP] + self.DownDPad = buttons[ProCon.Button.DOWN] + + +if __name__ == "__main__": + joy = PS4_Controller() # Initialize a PS4 controller + # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller + # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller + while True: + try: + print(joy.read()) # Print the current state of the controller + except Exception as e: + print("error!", e) # Print any errors that occur + break # Exit the loop if an error occurs diff --git a/dev/basic_swerve/basic_swerve_pi.py b/dev/basic_swerve/basic_swerve_pi.py new file mode 100644 index 0000000..230f98e --- /dev/null +++ b/dev/basic_swerve/basic_swerve_pi.py @@ -0,0 +1,59 @@ +import fcntl +import os +import time +import struct + +from Controller import PS4_Controller + +I2C_PRIM = 0x0703 # I2C primary address + +# Open i2c devices (sudo apt install i2c-tools) +i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) # File descriptor for the i2c device + +# Set the i2c address of pico +pico_address = 0x08 # Address of the pico on the i2c bus +fcntl.ioctl( + i2c_fd, I2C_PRIM, pico_address +) # Set the address of the i2c device to communicate with + +# Send data to pico +joy = PS4_Controller() # Initialize the controller +delay = 0.05 # Delay between each read/write operation + +while True: # Infinite loop + status = joy.read_self() # Read the status of the controller + x = status.LeftJoystickX # Get the X position of the left joystick + y = status.RightJoystickY # Get the Y position of the right joystick + joystickswitch = x > 0 # Check if the joystick is moved to the right + + x_b = struct.pack('f', x) + y_b = struct.pack('f', y) + + data = bytes([0xFA]) + \ + x_b + \ + y_b + \ + bytes([0xFB, 0x00]) # Prepare the data to be sent + # data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] + + print(len(data)) + print(bytes(data)) + + try: + os.write(i2c_fd, data) # Write the data to the i2c device + # os.write(i2c_fd, bytes(data)) # Write the data to the i2c device + time.sleep(delay) # Wait for a while + print("Sent data to Pico: ", list(data)) # Print the data that was sent + except OSError: + print( + "Remote I/O Error" + ) # Print an error message if there was a problem with the write operation + + # Read data from pico + try: + incoming_data = os.read(i2c_fd, 1) # Read 1 byte from the i2c device + time.sleep(delay) # Wait for a while + print( + "Received data from Pico: ", list(incoming_data) + ) # Print the data that was received + except TimeoutError: + print("Timeout Error") # Print an error message if there was a timeout error diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c new file mode 100644 index 0000000..1b89574 --- /dev/null +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include +#include + +// Define constants for I2C communication +#define I2C_PICO_ADDR 0x08 +#define I2C_SDA_PIN 0 +#define I2C_SCL_PIN 1 +#define I2C_PORT i2c0 +#define I2C_BAUDRATE 100 * 1000 + +// Define the length of the data packet +#define I2C_DATA_LENGTH 10 + +#define MESSAGE_START 0xFA +#define MESSAGE_STOP 0xFB + + +// digital low on in# pins indicates direction, both high is no signal +#define turn_in1_pin 4 // 1A, forward direction +#define turn_in2_pin 5 // 1B, backward direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define turn_pwm_pin 9 // 2A, turn motor speed +#define wheel_pwm_pin 8 // 2B, wheel motor speed +#define pwm_slice 4 +#define turn_channel PWM_CHAN_B +#define wheel_channel PWM_CHAN_A + +#define wheel_in1_pin 6 // 3A, forward direction +#define wheel_in2_pin 7 // 3B, backard direction + +// #define freq 500 // note: use clock management frequencies to set frequency +// #define duty_cycle 1 +#define count_max 65535 + + +// Buffer for incoming data +uint8_t incoming_data[I2C_DATA_LENGTH]; + +// Status of the input data +uint8_t input_status = 0; + +// Last event that occurred +int last_event = 0; + +// Index of the current data byte +int data_index = 0; + +// Buffer for the input data +uint8_t input[I2C_DATA_LENGTH - 2]; + +// Handler for I2C events +static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +{ + switch (event) + { + case I2C_SLAVE_RECEIVE:{ + // Read the data + uint8_t tmp = i2c_read_byte_raw(i2c); + // Check if the data is valid + // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff + if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) + { + printf("Invalid data %x\n", tmp); + break; + } + // Store the data + incoming_data[data_index] = tmp; + // printf("Data: %d\n", incoming_data[data_index]); + data_index++; + // set the event status to received + last_event = 1; + break; + } + + + case I2C_SLAVE_REQUEST: // Pi is requesting data + // Write the data into the void + i2c_write_byte_raw(i2c, (uint8_t)input_status); + // set the event status to sent + last_event = 2; + break; + + case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart + // if the last event was a receive event and the data is valid + if (last_event == 1) + if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) + { + // move the data into the input array + for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) + { + input[i] = (int)incoming_data[i + 1]; + } + // set the input status to ready + input_status = 1; + + // Reset incoming_data + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + incoming_data[i] = 0x00; + } + } + data_index = 0; + break; + default: + break; + } +} + + +bool read = false; + +float joyX = 0.0f; +float joyY = 0.0f; + +void setup() +{ // setup pins for pwm functions + stdio_init_all(); + gpio_init(turn_in1_pin); + gpio_init(turn_in2_pin); + gpio_init(wheel_in1_pin); + gpio_init(wheel_in2_pin); + + // check if default output signal is 0, for now put this in + gpio_put(turn_in1_pin, 0); + gpio_put(turn_in2_pin, 0); + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 0); + + gpio_set_dir(turn_in1_pin, GPIO_OUT); + gpio_set_dir(turn_in2_pin, GPIO_OUT); + gpio_set_dir(wheel_in1_pin, GPIO_OUT); + gpio_set_dir(wheel_in2_pin, GPIO_OUT); + + gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); + pwm_set_wrap(pwm_slice, count_max); + + pwm_set_chan_level(pwm_slice, turn_channel, 0); + pwm_set_chan_level(pwm_slice, wheel_channel, 0); + + pwm_set_enabled(pwm_slice, true); +} + +int main() +{ + + stdio_init_all(); + + // Initialize I2C at 100kHz + i2c_init(I2C_PORT, I2C_BAUDRATE); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + + // Set I2C address for Pico + i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); + + setup();//setup for pwm + + while (1) + { + if (input_status ==1){ + uint64_t tmp_float = 0; //use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + for (int i = 7; i >= 0; i--) //bytes are stored in int8 array (64 bits), pico reads backwards + { + tmp_float |= input[i]; //write byte at a time to tmp_float and shift + if(i!=0) + tmp_float<<=8; //preserves order of bits in 64bit int + } + joyX = *(((float*)&tmp_float)); //convert to interprit bits as float (32 bits) + joyY = *(((float*)&tmp_float)+1); + printf("%f ", joyX); + printf("%f\n", joyY); //printing floats in console + tmp_float = 0; //clear float + } + + if (joyX == 0) + { // in1 and in2 are high + // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + } + else if (joyX < 0) + { // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + gpio_put(turn_in1_pin, 0); + } + + // wheel motor + if (joyY == 0) + { // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + } + else if (joyY < 0) + { // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 1); + } + + pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(joyX* count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(joyY * count_max))); + } + + return 0; +} \ No newline at end of file diff --git a/dev/basic_swerve/joystick_sim.py b/dev/basic_swerve/joystick_sim.py new file mode 100644 index 0000000..4aa9f93 --- /dev/null +++ b/dev/basic_swerve/joystick_sim.py @@ -0,0 +1,195 @@ +import matplotlib.pyplot as plt +import numpy as np +from Controller import (Gem_Xbox_Controller, Nintendo_Pro_Controller, + PS4_Controller) + + +# return the vector perpendicular to the given vector +def perpendicular(vec): + return np.array([-vec[1], vec[0]]) + + +# NOTE: make sure to account for max motor speed when programming real motors, and normalize +# for example, if the swerve math commands one motor to spin higher than it's max speed, +# then it will only spin at the max speed, thus making the ratio of motor powers wrong and the robot will move wrong + + +if __name__ == "__main__": + # joy = Gem_Xbox_Controller() + # joy = Nintendo_Pro_Controller() + joy = PS4_Controller() + + rumble = type(joy) == Nintendo_Pro_Controller + + # robot radius + R = 5 + # dt, the delta time of the "animation" + DT = 0.001 + + # initial robot state + center_pos = np.array([0.0, 0.0]) # center position + module_dirs = ( + np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi + ) # directions of each module, relative to screen + module_pos = np.array( + [ + [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] + for a in module_dirs + ] + ) # absolute positions of each module (as a point) + freeze_pos = ( + center_pos.copy() + ) # position to rotate about when right bumper is pressed + + while True: + try: + # get inputs + joy_input = joy.read_self() + if joy_input.Back: # exit if back button is pressed + print("Exiting") + break + + # TODO: should replace this by standardizing inverts in the Controller.py class + inverts = [False, False, False, False] # Nintendo Pro Controller + # inverts = [False, True, True] # Gem Xbox Controller + + # use joystick inputs to calculate "strafe" movement + left_x = (-1.0 if inverts[0] else 1.0) * round(joy_input.LeftJoystickX, 3) + left_y = (-1.0 if inverts[1] else 1.0) * round(joy_input.LeftJoystickY, 3) + triggers = joy_input.LeftTrigger - joy_input.RightTrigger + + right_x = (-1.0 if inverts[2] else 1.0) * round(joy_input.RightJoystickX, 3) + right_y = (-1.0 if inverts[3] else 1.0) * round(joy_input.RightJoystickY, 3) + + ## LOGIC (begin) + + # get distance between freeze_pos and center_pos + dist = np.hypot( + freeze_pos[0] - center_pos[0], freeze_pos[1] - center_pos[1] + ) + + # if right bumper is not pressed, move robot in direction of joystick & rotate relative to center pos + if not joy_input.RightBumper: + move = np.array([left_x, left_y]) * 1.0 + rotate = 0.1 * triggers + + # if right bumper is pressed and freeze pos is not "inside" robot, rotate robot around freeze pos + elif dist > R: + # calculate vector from freeze to center pos + x = (freeze_pos[0] - center_pos[0]) / dist + y = (freeze_pos[1] - center_pos[1]) / dist + + # calculate new center position, moving robot around freeze pos + # x' = x*cos(theta) - y*sin(theta) + # y' = x*sin(theta) + y*cos(theta) + # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) + # https://academo.org/demos/rotation-about-point/ + move = np.array( + [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] + ) + # rotate robot so direction of modules is the same relative to freeze pos, plus some rotation from triggers + rotate = (-1.0 if left_x > 0 else 1.0) * np.hypot( + move[0], move[1] + ) / dist + 0.1 * triggers + + # if left bumper is pressed, make freeze pos the same as center pos + if joy_input.LeftBumper: + freeze_pos = center_pos.copy() + else: # if left bumper is not pressed, move freeze pos in direction of right joystick + freeze_pos += np.array([right_x, right_y]) * 1.0 + + # if right bumper is not pressed, move freeze pos in direction of right joystick (relative to center pos) + if not joy_input.RightBumper: + freeze_pos += move * 1.0 + np.array([right_x, right_y]) * 1.0 + + # update center position + center_pos += move + + # update module directions + module_dirs += rotate + + ## LOGIC (end) + + # update module positions using module directions and center position + module_pos = np.array( + [ + [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] + for a in module_dirs + ] + ) + + # set box size and aspect ratio for matplotlib plot window + box_scale = 10 + plt.xlim(-box_scale * R, box_scale * R) + plt.ylim(-box_scale * R, box_scale * R) + plt.gca().set_aspect("equal", adjustable="box") + + # array to store module controls (direction & speed of each module) + module_controls = [] + + # plot robot + for i, module in enumerate(module_pos): + # plot line from center to module + plt.plot( + [center_pos[0], module[0]], [center_pos[1], module[1]], "black" + ) + + # calculate module direction vector using robot movement vector & rotation + dir_vec = ( + move + + np.array([-np.sin(module_dirs[i]), np.cos(module_dirs[i])]) + * rotate + * 10 + ) + + # add module direction vector to module_controls as degrees & speed + module_controls.append( + ( + round(np.rad2deg(np.arctan2(dir_vec[1], dir_vec[0])), 3), + round(np.hypot(dir_vec[0], dir_vec[1]), 3), + ) + ) + + # plot module direction vectors + plt.quiver( + module[0], + module[1], + dir_vec[0], + dir_vec[1], + color="red", + angles="xy", + scale_units="xy", + scale=0.5, + ) + + # print(module_controls) + + # plot center direction vector + plt.quiver( + center_pos[0], + center_pos[1], + move[0], + move[1], + color="green", + angles="xy", + scale_units="xy", + scale=0.5, + ) + + # plot line from center to freeze pos + plt.plot( + [center_pos[0], freeze_pos[0]], [center_pos[1], freeze_pos[1]], "b" + ) + + # rumble if robot is outside of box + if rumble and ( + abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R + ): + joy.controller.send_rumble(False, True, 1) + + # pause for DT seconds and clear plot + plt.pause(DT) + plt.clf() + + except Exception as e: + print(e) diff --git a/dev/basic_swerve/pico_sdk_import.cmake b/dev/basic_swerve/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/basic_swerve/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/basic_swerve/procon.py b/dev/basic_swerve/procon.py new file mode 100644 index 0000000..8e04765 --- /dev/null +++ b/dev/basic_swerve/procon.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 + +import math +import time + +import hid # pip install hidapi + + +def to_int16(uint16): + return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 + + +class ProCon: + VENDOR_ID = 0x057E + PRODUCT_ID = 0x2009 + PACKET_SIZE = 64 + CALIBRATION_OFFSET = 0x603D + CALIBRATION_LENGTH = 0x12 + COMMAND_RETRIES = 10 + RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) + RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) + DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) + + class OutputReportID: + RUMBLE_SUBCOMMAND = 0x01 + RUMBLE = 0x10 + COMMAND = 0x80 + + class InputReportID: + SUBCOMMAND_REPLY = 0x21 + CONTROLLER_STATE = 0x30 + COMMAND_ACK = 0x81 + + class CommandID: + HANDSHAKE = 0x02 + HIGH_SPEED = 0x03 + FORCE_USB = 0x04 + + class SubcommandID: + SET_INPUT_REPORT_MODE = 0x03 + SPI_FLASH_READ = 0x10 + SET_PLAYER_LIGHTS = 0x30 + SET_HOME_LIGHT = 0x38 + ENABLE_IMU = 0x40 + SET_IMU_SENSITIVITY = 0x41 + ENABLE_VIBRATION = 0x48 + + class Button: + A = "A" + B = "B" + X = "X" + Y = "Y" + UP = "Up" + DOWN = "Down" + LEFT = "Left" + RIGHT = "Right" + MINUS = "-" + PLUS = "+" + SCREENSHOT = "Screenshot" + HOME = "Home" + L = "L" + ZL = "ZL" + R = "R" + ZR = "ZR" + LS = "LS" + RS = "RS" + + def __init__(self): + self.subcommand_counter = 0 + self.dev = hid.device() + self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) + self.handshake() + self.high_speed() + self.handshake() + self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL + self.rumble_expire = 0 + self.load_stick_calibration() + self.enable_vibration(True) + self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) + self.force_usb() + self.set_player_lights(True, False, False, False) + self.enable_imu(True) + self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) + + def start(self, callback): + while True: + state = self.recv() + if state[0] != ProCon.InputReportID.CONTROLLER_STATE: + continue + buttons = { + ProCon.Button.A: state[3] & 0x08 > 0, + ProCon.Button.B: state[3] & 0x04 > 0, + ProCon.Button.X: state[3] & 0x02 > 0, + ProCon.Button.Y: state[3] & 0x01 > 0, + ProCon.Button.UP: state[5] & 0x02 > 0, + ProCon.Button.DOWN: state[5] & 0x01 > 0, + ProCon.Button.LEFT: state[5] & 0x08 > 0, + ProCon.Button.RIGHT: state[5] & 0x04 > 0, + ProCon.Button.MINUS: state[4] & 0x01 > 0, + ProCon.Button.PLUS: state[4] & 0x02 > 0, + ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, + ProCon.Button.HOME: state[4] & 0x10 > 0, + ProCon.Button.L: state[5] & 0x40 > 0, + ProCon.Button.ZL: state[5] & 0x80 > 0, + ProCon.Button.R: state[3] & 0x40 > 0, + ProCon.Button.ZR: state[3] & 0x80 > 0, + ProCon.Button.LS: state[4] & 0x08 > 0, + ProCon.Button.RS: state[4] & 0x04 > 0, + } + l_x = state[6] | ((state[7] & 0xF) << 8) + l_y = (state[7] >> 4) | (state[8] << 4) + r_x = state[9] | ((state[10] & 0xF) << 8) + r_y = (state[10] >> 4) | (state[11] << 4) + l_x = self.apply_stick_calibration(l_x, 0, 0) + l_y = self.apply_stick_calibration(l_y, 0, 1) + r_x = self.apply_stick_calibration(r_x, 1, 0) + r_y = self.apply_stick_calibration(r_y, 1, 1) + l_stick = (l_x, l_y) + r_stick = (r_x, r_y) + accel = ( + state[13] | state[14] << 8, + state[15] | state[16] << 8, + state[17] | state[18] << 8, + ) + gyro = ( + state[19] | state[20] << 8, + state[21] | state[22] << 8, + state[23] | state[24] << 8, + ) + accel = tuple(map(to_int16, accel)) + gyro = tuple(map(to_int16, gyro)) + battery = (state[2] & 0xF0) >> 4 + callback(buttons, l_stick, r_stick, accel, gyro, battery) + if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: + self.send_rumble(False, False, 0) + + def load_stick_calibration(self): + ok, reply = self.spi_flash_read( + ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH + ) + if not ok: + raise RuntimeError("cannot load stick calibration") + self.stick_calibration = [ + [ + [ + ((reply[27] & 0xF) << 8) | reply[26], + ((reply[24] & 0xF) << 8) | reply[23], + ((reply[21] & 0xF) << 8) | reply[20], + ], + [ + (reply[28] << 4) | (reply[27] >> 4), + (reply[25] << 4) | (reply[24] >> 4), + (reply[22] << 4) | (reply[21] >> 4), + ], + ], + [ + [ + ((reply[33] & 0xF) << 8) | reply[32], + ((reply[30] & 0xF) << 8) | reply[29], + ((reply[36] & 0xF) << 8) | reply[35], + ], + [ + (reply[34] << 4) | (reply[33] >> 4), + (reply[31] << 4) | (reply[30] >> 4), + (reply[37] << 4) | (reply[36] >> 4), + ], + ], + ] + for i in range(len(self.stick_calibration)): + for j in range(len(self.stick_calibration[i])): + for k in range(len(self.stick_calibration[i][j])): + if self.stick_calibration[i][j][k] == 0xFFF: + self.stick_calibration[i][j][k] = 0 + self.stick_extends = [ + [ + [ + -int(self.stick_calibration[0][0][0] * 0.7), + int(self.stick_calibration[0][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[0][1][0] * 0.7), + int(self.stick_calibration[0][1][2] * 0.7), + ], + ], + [ + [ + -int(self.stick_calibration[1][0][0] * 0.7), + int(self.stick_calibration[1][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[1][1][0] * 0.7), + int(self.stick_calibration[1][1][2] * 0.7), + ], + ], + ] + + def apply_stick_calibration(self, value, stick, axis): + value -= self.stick_calibration[stick][axis][1] + if value < self.stick_extends[stick][axis][0]: + self.stick_extends[stick][axis][0] = value + if value > self.stick_extends[stick][axis][1]: + self.stick_extends[stick][axis][1] = value + if value > 0: + return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) + return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) + + def send(self, data): + return self.dev.write(data) == len(data) + + def recv(self): + return self.dev.read(ProCon.PACKET_SIZE) + + def send_command(self, id, wait_for_reply=True): + data = (ProCon.OutputReportID.COMMAND, id) + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True + reply = self.recv() + if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: + return True + return False + + def send_subcommand(self, id, param, wait_for_reply=True): + data = ( + (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + + (id,) + + param + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True, [] + reply = self.recv() + if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: + return True, reply + return False, [] + + def send_rumble(self, low, high, duration): + self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL + self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL + self.rumble_expire = ( + int(time.time() * 1000) + duration if (low or high) and duration else 0 + ) + data = ( + (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if self.send(data): + return True + return False + + def handshake(self): + return self.send_command(ProCon.CommandID.HANDSHAKE) + + def high_speed(self): + return self.send_command(ProCon.CommandID.HIGH_SPEED) + + def force_usb(self): + return self.send_command(ProCon.CommandID.FORCE_USB, False) + + def set_input_report_mode(self, mode): + return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) + + def spi_flash_read(self, addr, l): + param = ( + addr & 0x000000FF, + (addr & 0x0000FF00) >> 8, + (addr & 0x00FF0000) >> 16, + (addr & 0xFF000000) >> 24, + l, + ) + return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) + + def set_player_lights(self, one, two, three, four): + param = (one << 0) | (two << 1) | (three << 2) | (four << 3) + return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) + + def set_home_light(self, brightness): + intensity = 0 + if brightness > 0: + if brightness < 65: + intensity = (brightness + 5) // 10 + else: + intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) + intensity = (intensity & 0xF) << 4 + param = (0x01, intensity, intensity, 0x00) + return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) + + def enable_imu(self, enable): + return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) + + def set_imu_sensitivity(self, sensitivity): + return self.send_subcommand( + ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity + ) + + def enable_vibration(self, enable): + return self.send_subcommand( + ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) + ) + + +def print_state(buttons, l_stick, r_stick, accel, gyro, battery): + print("\33[2JButtons:") + for k, v in buttons.items(): + if v: + print("[{}]".format(k), end=" ") + else: + print(" {} ".format(k), end=" ") + print() + print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) + print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) + print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) + print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) + print("Battery: {}/9".format(battery)) + + +if __name__ == "__main__": + try: + ProCon().start(print_state) + except KeyboardInterrupt: + print("\rGoodbye!") diff --git a/dev/basic_swerve/swerve_test.py b/dev/basic_swerve/swerve_test.py new file mode 100644 index 0000000..911a455 --- /dev/null +++ b/dev/basic_swerve/swerve_test.py @@ -0,0 +1,67 @@ +import math + +# radius of swerve drive base in meters +radius = 1 + +# vectors of swerve drive, using [speed (m/s), orientation (deg)] in global reference frame +swerve = [0,0] + +# vectors of motors in global frame +m1 = [0,0] +m2 = [0,0] +m3 = [0,0] + +# add two vectors +def add_two_vec(vec1, vec2): + + # get difference between two vectors, treating one vector as perpendicular to x axis + theta_diff = vec2[1] - vec1[1] + + # since vec1 is x axis, vector 1 contributes in only the x axis + # when breaking into components, just add vec2 in x to vec1 magnitude to get x + # vec2 in y to get y + # sqrt(a^2+b^2) to get magnitude, and arctan(y/x) to get angle relative to x (add to vec1 orientation) + x_comp = vec1[0] + math.cos(theta_diff) * vec2[0] + y_comp = math.sin(theta_diff) * vec2[0] + f_mag = math.sqrt(x_comp^2 + y_comp^2) + f_angle = math.atan(y_comp / x_comp) + vec1[1] + return [f_mag, f_angle] + + +# input velocity [speed (m/s), orientation (deg)] in local reference frame +# rotation (rad/s) +def convert(velocity, rotation): + + # the vector for each motor in global reference frame is given by adding + # 1) velocity (relative to global reference frame) + # 2) rotation vector (vector perpendicular to each motor relative to the body reference frame) + # because the frame is circular, magnitude of vector is calculated by V = rw (formula for tangential speed relative to radius and rad/s) + + # setting all vectors to velocity relative to local reference frame + m1 = [velocity[0], velocity[1]] + m2 = [velocity[0], velocity[1]-120] + m3 = [velocity[0], velocity[1]-240] + + # rotation vector relative to global reference + # three motors, with motor 1 having an offset of 0 relative to frame + # because rotation vectors must be perpendicular, add 90 to each + # to convert from local to global, add orientation of frame in global reference frame + # rot_ang_m1 = 0+90+swerve[1] + # rot_ang_m2 = 120+90+swerve[1] + # rot_ang_m3 = 240+90+swerve[1] + + # create magnitude for each vector + rot_mag = rotation * radius + dir = 1 + + if rot_mag < 0: + dir *= -1 + rot_mag *= -1 + + # add two vectors (in local frame) based on direction and magnitude + m1 = add_two_vec(m1, [rot_mag, 90*dir]) + m2 = add_two_vec(m2, [rot_mag, 90*dir]) + m3 = add_two_vec(m3, [rot_mag, 90*dir]) + + print(m1, m2, m3) + From ceb3bdd26cb6866bf7e8d98ee77159297c5d24de Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:54:55 -0800 Subject: [PATCH 41/65] Update swerve_test.py --- dev/basic_swerve/swerve_test.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/dev/basic_swerve/swerve_test.py b/dev/basic_swerve/swerve_test.py index 911a455..236f265 100644 --- a/dev/basic_swerve/swerve_test.py +++ b/dev/basic_swerve/swerve_test.py @@ -13,18 +13,30 @@ # add two vectors def add_two_vec(vec1, vec2): - + print(vec1,vec2) # get difference between two vectors, treating one vector as perpendicular to x axis theta_diff = vec2[1] - vec1[1] - + theta_diff = math.radians(theta_diff) + print(theta_diff) + # since vec1 is x axis, vector 1 contributes in only the x axis # when breaking into components, just add vec2 in x to vec1 magnitude to get x # vec2 in y to get y # sqrt(a^2+b^2) to get magnitude, and arctan(y/x) to get angle relative to x (add to vec1 orientation) x_comp = vec1[0] + math.cos(theta_diff) * vec2[0] + print(x_comp) y_comp = math.sin(theta_diff) * vec2[0] - f_mag = math.sqrt(x_comp^2 + y_comp^2) - f_angle = math.atan(y_comp / x_comp) + vec1[1] + print(y_comp) + f_mag = math.sqrt(x_comp**2 + y_comp**2) + print(f_mag) + f_angle = math.atan2(y_comp, x_comp) + math.radians(vec1[1]) + print(f_angle) + f_angle = math.degrees(f_angle) + print(f_angle) + if f_angle < -180: + f_angle += 360 + elif f_angle > 180: + f_angle -= 360 return [f_mag, f_angle] @@ -57,7 +69,7 @@ def convert(velocity, rotation): if rot_mag < 0: dir *= -1 rot_mag *= -1 - + print(rot_mag) # add two vectors (in local frame) based on direction and magnitude m1 = add_two_vec(m1, [rot_mag, 90*dir]) m2 = add_two_vec(m2, [rot_mag, 90*dir]) @@ -65,3 +77,4 @@ def convert(velocity, rotation): print(m1, m2, m3) +convert([10,0],0) \ No newline at end of file From ae3f889d57467ba2eb0b866b810cc6b9f4bcf533 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 23 Feb 2024 03:33:40 -0800 Subject: [PATCH 42/65] basic swerve seems to work --- dev/basic_swerve/Controller.py | 7 +- dev/basic_swerve/basic_swerve_pi.py | 25 +- dev/basic_swerve/basic_swerve_pico.c | 385 ++++++++++++++++++++++----- 3 files changed, 335 insertions(+), 82 deletions(-) diff --git a/dev/basic_swerve/Controller.py b/dev/basic_swerve/Controller.py index 23c64ce..34c5ab8 100644 --- a/dev/basic_swerve/Controller.py +++ b/dev/basic_swerve/Controller.py @@ -1,8 +1,7 @@ import math import threading -from inputs import \ - get_gamepad # Import the get_gamepad function from the inputs module +from inputs import get_gamepad # Import the get_gamepad function from the inputs module from procon import ProCon # Import the ProCon class from the procon module @@ -10,7 +9,7 @@ class PS4_Controller(object): def __init__(self): self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input self.THRESHOLD = 0.03 # Threshold for joystick deadzone self.reset_vars() # Reset all controller variables to their initial state self.start_thread(()) # Start a new thread to monitor the controller @@ -80,7 +79,7 @@ def read_self(self): # This method applies a threshold to a value def threshold(self, val): - return val if abs(val) > self.THRESHOLD else 0 + return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 def _monitor_controller(self): while True: diff --git a/dev/basic_swerve/basic_swerve_pi.py b/dev/basic_swerve/basic_swerve_pi.py index 230f98e..0c43083 100644 --- a/dev/basic_swerve/basic_swerve_pi.py +++ b/dev/basic_swerve/basic_swerve_pi.py @@ -23,20 +23,23 @@ while True: # Infinite loop status = joy.read_self() # Read the status of the controller x = status.LeftJoystickX # Get the X position of the left joystick - y = status.RightJoystickY # Get the Y position of the right joystick + y = -1 * status.RightJoystickY # Get the Y position of the right joystick joystickswitch = x > 0 # Check if the joystick is moved to the right - x_b = struct.pack('f', x) - y_b = struct.pack('f', y) + x_b = struct.pack("f", x) + y_b = struct.pack("f", y) data = bytes([0xFA]) + \ x_b + \ y_b + \ - bytes([0xFB, 0x00]) # Prepare the data to be sent + bytes([0xFB]) # Prepare the data to be sent + # bytes([0xFB, 0x00]) # Prepare the data to be sent + # data = x_b + y_b + bytes([0x00]) # Prepare the data to be sent # data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] - print(len(data)) - print(bytes(data)) + # print(len(data)) + # print(bytes(data)) + # print(x, y) try: os.write(i2c_fd, data) # Write the data to the i2c device @@ -52,8 +55,12 @@ try: incoming_data = os.read(i2c_fd, 1) # Read 1 byte from the i2c device time.sleep(delay) # Wait for a while - print( - "Received data from Pico: ", list(incoming_data) - ) # Print the data that was received + # print( + # "Received data from Pico: ", list(incoming_data) + # ) # Print the data that was received except TimeoutError: print("Timeout Error") # Print an error message if there was a timeout error + except OSError: + print( + "Remote I/O Error" + ) diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c index 1b89574..5777112 100644 --- a/dev/basic_swerve/basic_swerve_pico.c +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -1,8 +1,8 @@ +#include #include #include #include #include -#include #include // Define constants for I2C communication @@ -13,12 +13,11 @@ #define I2C_BAUDRATE 100 * 1000 // Define the length of the data packet -#define I2C_DATA_LENGTH 10 +#define I2C_DATA_LENGTH 128 #define MESSAGE_START 0xFA #define MESSAGE_STOP 0xFB - // digital low on in# pins indicates direction, both high is no signal #define turn_in1_pin 4 // 1A, forward direction #define turn_in2_pin 5 // 1B, backward direction @@ -33,16 +32,51 @@ #define wheel_in1_pin 6 // 3A, forward direction #define wheel_in2_pin 7 // 3B, backard direction +#define wheel_1_pwm 2 +#define wheel_1_slice 1 +#define wheel_1_channel PWM_CHAN_A +#define wheel_1a 3 +#define wheel_1b 4 + +#define turn_1a 5 +#define turn_1b 6 +#define turn_1_pwm 7 +#define turn_1_slice 3 +#define turn_1_channel PWM_CHAN_B + +#define wheel_2_pwm 8 +#define wheel_2_slice 4 +#define wheel_2_channel PWM_CHAN_A +#define wheel_2a 9 +#define wheel_2b 10 + +#define turn_2a 11 +#define turn_2b 12 +#define turn_2_pwm 13 +#define turn_2_slice 6 +#define turn_2_channel PWM_CHAN_B + +#define turn_3_pwm 16 +#define turn_3_slice 0 +#define turn_3_channel PWM_CHAN_A +#define turn_3b 17 +#define turn_3a 18 + +#define wheel_3b 19 +#define wheel_3a 20 +#define wheel_3_pwm 21 +#define wheel_3_slice 2 +#define wheel_3_channel PWM_CHAN_B + // #define freq 500 // note: use clock management frequencies to set frequency // #define duty_cycle 1 #define count_max 65535 - // Buffer for incoming data -uint8_t incoming_data[I2C_DATA_LENGTH]; +// uint8_t incoming_data[I2C_DATA_LENGTH]; // Status of the input data -uint8_t input_status = 0; +int input_status = 0; // Last event that occurred int last_event = 0; @@ -50,33 +84,87 @@ int last_event = 0; // Index of the current data byte int data_index = 0; +int count = 0; + // Buffer for the input data -uint8_t input[I2C_DATA_LENGTH - 2]; +// uint8_t input[I2C_DATA_LENGTH - 2]; + +// make a circular buffer for the incoming data packets +typedef struct dataT +{ + uint8_t data[I2C_DATA_LENGTH]; + uint8_t len; +} *data; + +typedef struct buffT +{ + data buff[I2C_DATA_LENGTH]; + uint8_t head; + uint8_t tail; + uint8_t full; +} *buff; + +buff buffer; +data tmp; + +// Initialize the circular buffer +void init_buff(buff *b, data *d) +{ + *b = malloc(sizeof(struct buffT)); + (*b)->head = 0; + (*b)->tail = 0; + (*b)->full = 0; + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + (*b)->buff[i] = malloc(sizeof(struct dataT)); + (*b)->buff[i]->len = 0; + for (int j = 0; j < I2C_DATA_LENGTH; j++) + { + (*b)->buff[i]->data[j] = 0; + } + } + + *d = malloc(sizeof(struct dataT)); + (*d)->len = 0; + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + (*d)->data[i] = 0; + } +} // Handler for I2C events static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { switch (event) { - case I2C_SLAVE_RECEIVE:{ + case I2C_SLAVE_RECEIVE: + { // Read the data - uint8_t tmp = i2c_read_byte_raw(i2c); + uint8_t tmp_byte = i2c_read_byte_raw(i2c); // Check if the data is valid - // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff - if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) + // if ((tmp->data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) + if (tmp->len >= I2C_DATA_LENGTH) { - printf("Invalid data %x\n", tmp); + printf("Invalid data %x, len %x\n", tmp_byte, tmp->len); break; } // Store the data - incoming_data[data_index] = tmp; + // incoming_data[data_index] = tmp; + tmp->data[tmp->len] = tmp_byte; // printf("Data: %d\n", incoming_data[data_index]); - data_index++; + tmp->len++; + // data_index++; // set the event status to received last_event = 1; + + // for (int i = 0; i < tmp->len; i++) + // { + // printf("%d ", tmp->data[i]); + // } + // printf("\n"); + break; } - case I2C_SLAVE_REQUEST: // Pi is requesting data // Write the data into the void @@ -87,31 +175,58 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart // if the last event was a receive event and the data is valid - if (last_event == 1) - if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) + if (last_event == 1 && !buffer->full) + { + if (tmp->data[0] == MESSAGE_START && tmp->data[tmp->len - 1] == MESSAGE_STOP) { - // move the data into the input array - for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) + // printf("Buffer not full, putting this into buffer:\n" + for (int i = 0; i < tmp->len; i++) { - input[i] = (int)incoming_data[i + 1]; + // input[i] = (int)incoming_data[i + 1]; + buffer->buff[buffer->tail]->data[i] = tmp->data[i]; + // printf("%d ", buffer->buff[buffer->tail]->data[i]); } + // printf("\n"); + buffer->buff[buffer->tail]->len = tmp->len; // set the input status to ready input_status = 1; - // Reset incoming_data - for (int i = 0; i < I2C_DATA_LENGTH; i++) + // printf("buffer not full\n"); + + // for (int i = 0; i < buffer->buff[buffer->tail]->len; i++) + // { + // printf("%d ", buffer->buff[buffer->tail]->data[i]); + // } + // printf("\n"); + + // move the tail of the buffer + buffer->tail = (buffer->tail + 1) % I2C_DATA_LENGTH; + + // check if the buffer is full + if (buffer->tail == buffer->head) { - incoming_data[i] = 0x00; + buffer->full = 1; } + tmp->len = 0; + // printf("Data received pt1\n"); } - data_index = 0; + } + else + { + // printf("Buffer full, attempted to put this into buffer:\n"); + // for (int i = 0; i < tmp->len; i++) + // { + // printf("%d ", tmp->data[i]); + // } + // printf("\n"); + tmp->len = 0; + } break; default: break; } } - bool read = false; float joyX = 0.0f; @@ -120,35 +235,95 @@ float joyY = 0.0f; void setup() { // setup pins for pwm functions stdio_init_all(); - gpio_init(turn_in1_pin); - gpio_init(turn_in2_pin); - gpio_init(wheel_in1_pin); - gpio_init(wheel_in2_pin); + gpio_init(turn_1a); + gpio_init(turn_1b); + gpio_init(wheel_1a); + gpio_init(wheel_1b); + + gpio_init(turn_2a); + gpio_init(turn_2b); + gpio_init(wheel_2a); + gpio_init(wheel_2b); + + gpio_init(turn_3a); + gpio_init(turn_3b); + gpio_init(wheel_3a); + gpio_init(wheel_3b); + + gpio_set_dir(turn_1a, GPIO_OUT); + gpio_set_dir(turn_1b, GPIO_OUT); + gpio_set_dir(wheel_1a, GPIO_OUT); + gpio_set_dir(wheel_1b, GPIO_OUT); + + gpio_set_dir(turn_2a, GPIO_OUT); + gpio_set_dir(turn_2b, GPIO_OUT); + gpio_set_dir(wheel_2a, GPIO_OUT); + gpio_set_dir(wheel_2b, GPIO_OUT); + + gpio_set_dir(turn_3a, GPIO_OUT); + gpio_set_dir(turn_3b, GPIO_OUT); + gpio_set_dir(wheel_3a, GPIO_OUT); + gpio_set_dir(wheel_3b, GPIO_OUT); // check if default output signal is 0, for now put this in - gpio_put(turn_in1_pin, 0); - gpio_put(turn_in2_pin, 0); - gpio_put(wheel_in1_pin, 0); - gpio_put(wheel_in2_pin, 0); + gpio_put(turn_1a, 0); + gpio_put(turn_1b, 0); + gpio_put(wheel_1a, 0); + gpio_put(wheel_1b, 0); - gpio_set_dir(turn_in1_pin, GPIO_OUT); - gpio_set_dir(turn_in2_pin, GPIO_OUT); - gpio_set_dir(wheel_in1_pin, GPIO_OUT); - gpio_set_dir(wheel_in2_pin, GPIO_OUT); + gpio_put(turn_2a, 0); + gpio_put(turn_2b, 0); + gpio_put(wheel_2a, 0); + gpio_put(wheel_2b, 0); - gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); - gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); - pwm_set_wrap(pwm_slice, count_max); + gpio_put(turn_3a, 0); + gpio_put(turn_3b, 0); + gpio_put(wheel_3a, 0); + gpio_put(wheel_3b, 0); - pwm_set_chan_level(pwm_slice, turn_channel, 0); - pwm_set_chan_level(pwm_slice, wheel_channel, 0); + gpio_set_function(turn_1_pwm, GPIO_FUNC_PWM); + gpio_set_function(wheel_1_pwm, GPIO_FUNC_PWM); - pwm_set_enabled(pwm_slice, true); + gpio_set_function(turn_2_pwm, GPIO_FUNC_PWM); + gpio_set_function(wheel_2_pwm, GPIO_FUNC_PWM); + + gpio_set_function(turn_3_pwm, GPIO_FUNC_PWM); + gpio_set_function(wheel_3_pwm, GPIO_FUNC_PWM); + + pwm_set_wrap(turn_1_slice, count_max); + pwm_set_wrap(wheel_1_slice, count_max); + + pwm_set_wrap(turn_2_slice, count_max); + pwm_set_wrap(wheel_2_slice, count_max); + + pwm_set_wrap(turn_3_slice, count_max); + pwm_set_wrap(wheel_3_slice, count_max); + + pwm_set_chan_level(turn_1_slice, turn_1_channel, 0); + pwm_set_chan_level(wheel_1_slice, wheel_1_channel, 0); + + pwm_set_chan_level(turn_2_slice, turn_2_channel, 0); + pwm_set_chan_level(wheel_2_slice, wheel_2_channel, 0); + + pwm_set_chan_level(turn_3_slice, turn_3_channel, 0); + pwm_set_chan_level(wheel_3_slice, wheel_3_channel, 0); + + pwm_set_enabled(turn_1_slice, true); + pwm_set_enabled(wheel_1_slice, true); + + pwm_set_enabled(turn_2_slice, true); + pwm_set_enabled(wheel_2_slice, true); + + pwm_set_enabled(turn_3_slice, true); + pwm_set_enabled(wheel_3_slice, true); } int main() { - + const uint LED_PIN = PICO_DEFAULT_LED_PIN; + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + stdio_init_all(); // Initialize I2C at 100kHz @@ -156,64 +331,136 @@ int main() gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + init_buff(&buffer, &tmp); + // Set I2C address for Pico i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); - setup();//setup for pwm + setup(); // setup for pwm while (1) { - if (input_status ==1){ - uint64_t tmp_float = 0; //use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex - for (int i = 7; i >= 0; i--) //bytes are stored in int8 array (64 bits), pico reads backwards + // while (true) { + gpio_put(LED_PIN, 1); + // sleep_ms(250); + // sleep_ms(250); + // } + // count++; + // printf("Count: %d\n", input_status); + if (input_status == 1) + { + input_status = 0; + // printf("Data received\n"); + uint64_t tmp_float = 0; // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + // printf("dati len: %d\n", buffer->buff[buffer->head]->len); + for (int i = 7; i >= 0; i--) // bytes are stored in int8 array (64 bits), pico reads backwards { - tmp_float |= input[i]; //write byte at a time to tmp_float and shift - if(i!=0) - tmp_float<<=8; //preserves order of bits in 64bit int + // tmp_float |= input[i]; // write byte at a time to tmp_float and shift + tmp_float |= buffer->buff[buffer->head]->data[i + 1]; // write byte at a time to tmp_float and shift + printf("%d ", buffer->buff[buffer->head]->data[i + 1]); + if (i != 0) + tmp_float <<= 8; // preserves order of bits in 64bit int } - joyX = *(((float*)&tmp_float)); //convert to interprit bits as float (32 bits) - joyY = *(((float*)&tmp_float)+1); + printf("\n"); + joyX = *(((float *)&tmp_float)); // convert to interpret bits as float (32 bits) + joyY = *(((float *)&tmp_float) + 1); + printf("%f ", joyX); + printf("%f\n", joyY); // printing floats in console + tmp_float = 0; // clear float + buffer->head = (buffer->head + 1) % I2C_DATA_LENGTH; + buffer->full = 0; + // input_status = 0; + } + else + { printf("%f ", joyX); - printf("%f\n", joyY); //printing floats in console - tmp_float = 0; //clear float + printf("%f\n", joyY); // printing floats in console + // printf("No data\n"); + // printf(""); } + gpio_put(LED_PIN, 0); if (joyX == 0) { // in1 and in2 are high // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 1); + gpio_put(turn_1a, 1); + gpio_put(turn_1b, 1); + + gpio_put(turn_2a, 1); + gpio_put(turn_2b, 1); + + gpio_put(turn_3a, 1); + gpio_put(turn_3b, 1); } else if (joyX < 0) { // in1 is high and in2 is low - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 0); + gpio_put(turn_1a, 1); + gpio_put(turn_1b, 0); + + gpio_put(turn_2a, 1); + gpio_put(turn_2b, 0); + + gpio_put(turn_3a, 1); + gpio_put(turn_3b, 0); } else { // in1 is low and in2 is high - gpio_put(turn_in2_pin, 1); - gpio_put(turn_in1_pin, 0); + gpio_put(turn_1b, 1); + gpio_put(turn_1a, 0); + + gpio_put(turn_2b, 1); + gpio_put(turn_2a, 0); + + gpio_put(turn_3b, 1); + gpio_put(turn_3a, 0); } // wheel motor if (joyY == 0) { // in1 and in2 are high - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 1); + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 1); + + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 1); + + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 1); } else if (joyY < 0) { // in1 is high and in2 is low - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 0); + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 0); + + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 0); + + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 0); } else { // in1 is low and in2 is high - gpio_put(wheel_in1_pin, 0); - gpio_put(wheel_in2_pin, 1); + gpio_put(wheel_1a, 0); + gpio_put(wheel_1b, 1); + + gpio_put(wheel_2a, 0); + gpio_put(wheel_2b, 1); + + gpio_put(wheel_3a, 0); + gpio_put(wheel_3b, 1); } - pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(joyX* count_max))); - pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(joyY * count_max))); + pwm_set_chan_level(turn_1_slice, turn_1_channel, abs((int)(joyX * count_max))); + pwm_set_chan_level(wheel_1_slice, wheel_1_channel, abs((int)(joyY * count_max))); + + pwm_set_chan_level(turn_2_slice, turn_2_channel, abs((int)(joyX * count_max))); + pwm_set_chan_level(wheel_2_slice, wheel_2_channel, abs((int)(joyY * count_max))); + + pwm_set_chan_level(turn_3_slice, turn_3_channel, abs((int)(joyX * count_max))); + pwm_set_chan_level(wheel_3_slice, wheel_3_channel, abs((int)(joyY * count_max))); + // pwm_set_chan_level(wheel_2_slice, wheel_2_channel, (int)(0.5 * count_max)); + + // sleep_ms(20); // used for debugging - reduces the number of loops w/ no new i2c data } return 0; From 025d767c65984ddefd4a28fac832ae5eed55f7ed Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 23 Feb 2024 03:36:12 -0800 Subject: [PATCH 43/65] cleaned up --- dev/basic_swerve/basic_swerve_pico.c | 170 ++++----------------------- 1 file changed, 24 insertions(+), 146 deletions(-) diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c index 5777112..3145672 100644 --- a/dev/basic_swerve/basic_swerve_pico.c +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -18,19 +18,8 @@ #define MESSAGE_START 0xFA #define MESSAGE_STOP 0xFB -// digital low on in# pins indicates direction, both high is no signal -#define turn_in1_pin 4 // 1A, forward direction -#define turn_in2_pin 5 // 1B, backward direction - -// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed -#define turn_pwm_pin 9 // 2A, turn motor speed -#define wheel_pwm_pin 8 // 2B, wheel motor speed -#define pwm_slice 4 -#define turn_channel PWM_CHAN_B -#define wheel_channel PWM_CHAN_A - -#define wheel_in1_pin 6 // 3A, forward direction -#define wheel_in2_pin 7 // 3B, backard direction +// digital low on a/b pins indicates direction, both high is no signal (stop) +// pwm signal on pwm pin controls speed #define wheel_1_pwm 2 #define wheel_1_slice 1 @@ -69,12 +58,8 @@ #define wheel_3_channel PWM_CHAN_B // #define freq 500 // note: use clock management frequencies to set frequency -// #define duty_cycle 1 #define count_max 65535 -// Buffer for incoming data -// uint8_t incoming_data[I2C_DATA_LENGTH]; - // Status of the input data int input_status = 0; @@ -87,9 +72,6 @@ int data_index = 0; int count = 0; // Buffer for the input data -// uint8_t input[I2C_DATA_LENGTH - 2]; - -// make a circular buffer for the incoming data packets typedef struct dataT { uint8_t data[I2C_DATA_LENGTH]; @@ -142,27 +124,17 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) // Read the data uint8_t tmp_byte = i2c_read_byte_raw(i2c); // Check if the data is valid - // if ((tmp->data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) if (tmp->len >= I2C_DATA_LENGTH) { printf("Invalid data %x, len %x\n", tmp_byte, tmp->len); break; } // Store the data - // incoming_data[data_index] = tmp; tmp->data[tmp->len] = tmp_byte; - // printf("Data: %d\n", incoming_data[data_index]); tmp->len++; - // data_index++; // set the event status to received last_event = 1; - // for (int i = 0; i < tmp->len; i++) - // { - // printf("%d ", tmp->data[i]); - // } - // printf("\n"); - break; } @@ -179,26 +151,15 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { if (tmp->data[0] == MESSAGE_START && tmp->data[tmp->len - 1] == MESSAGE_STOP) { - // printf("Buffer not full, putting this into buffer:\n" for (int i = 0; i < tmp->len; i++) { - // input[i] = (int)incoming_data[i + 1]; buffer->buff[buffer->tail]->data[i] = tmp->data[i]; - // printf("%d ", buffer->buff[buffer->tail]->data[i]); } - // printf("\n"); buffer->buff[buffer->tail]->len = tmp->len; + // set the input status to ready input_status = 1; - // printf("buffer not full\n"); - - // for (int i = 0; i < buffer->buff[buffer->tail]->len; i++) - // { - // printf("%d ", buffer->buff[buffer->tail]->data[i]); - // } - // printf("\n"); - // move the tail of the buffer buffer->tail = (buffer->tail + 1) % I2C_DATA_LENGTH; @@ -208,7 +169,6 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) buffer->full = 1; } tmp->len = 0; - // printf("Data received pt1\n"); } } else @@ -227,95 +187,20 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } } -bool read = false; - float joyX = 0.0f; float joyY = 0.0f; -void setup() -{ // setup pins for pwm functions - stdio_init_all(); - gpio_init(turn_1a); - gpio_init(turn_1b); - gpio_init(wheel_1a); - gpio_init(wheel_1b); - - gpio_init(turn_2a); - gpio_init(turn_2b); - gpio_init(wheel_2a); - gpio_init(wheel_2b); - - gpio_init(turn_3a); - gpio_init(turn_3b); - gpio_init(wheel_3a); - gpio_init(wheel_3b); - - gpio_set_dir(turn_1a, GPIO_OUT); - gpio_set_dir(turn_1b, GPIO_OUT); - gpio_set_dir(wheel_1a, GPIO_OUT); - gpio_set_dir(wheel_1b, GPIO_OUT); - - gpio_set_dir(turn_2a, GPIO_OUT); - gpio_set_dir(turn_2b, GPIO_OUT); - gpio_set_dir(wheel_2a, GPIO_OUT); - gpio_set_dir(wheel_2b, GPIO_OUT); - - gpio_set_dir(turn_3a, GPIO_OUT); - gpio_set_dir(turn_3b, GPIO_OUT); - gpio_set_dir(wheel_3a, GPIO_OUT); - gpio_set_dir(wheel_3b, GPIO_OUT); - - // check if default output signal is 0, for now put this in - gpio_put(turn_1a, 0); - gpio_put(turn_1b, 0); - gpio_put(wheel_1a, 0); - gpio_put(wheel_1b, 0); - - gpio_put(turn_2a, 0); - gpio_put(turn_2b, 0); - gpio_put(wheel_2a, 0); - gpio_put(wheel_2b, 0); - - gpio_put(turn_3a, 0); - gpio_put(turn_3b, 0); - gpio_put(wheel_3a, 0); - gpio_put(wheel_3b, 0); - - gpio_set_function(turn_1_pwm, GPIO_FUNC_PWM); - gpio_set_function(wheel_1_pwm, GPIO_FUNC_PWM); - - gpio_set_function(turn_2_pwm, GPIO_FUNC_PWM); - gpio_set_function(wheel_2_pwm, GPIO_FUNC_PWM); - - gpio_set_function(turn_3_pwm, GPIO_FUNC_PWM); - gpio_set_function(wheel_3_pwm, GPIO_FUNC_PWM); - - pwm_set_wrap(turn_1_slice, count_max); - pwm_set_wrap(wheel_1_slice, count_max); - - pwm_set_wrap(turn_2_slice, count_max); - pwm_set_wrap(wheel_2_slice, count_max); - - pwm_set_wrap(turn_3_slice, count_max); - pwm_set_wrap(wheel_3_slice, count_max); - - pwm_set_chan_level(turn_1_slice, turn_1_channel, 0); - pwm_set_chan_level(wheel_1_slice, wheel_1_channel, 0); - - pwm_set_chan_level(turn_2_slice, turn_2_channel, 0); - pwm_set_chan_level(wheel_2_slice, wheel_2_channel, 0); - - pwm_set_chan_level(turn_3_slice, turn_3_channel, 0); - pwm_set_chan_level(wheel_3_slice, wheel_3_channel, 0); - - pwm_set_enabled(turn_1_slice, true); - pwm_set_enabled(wheel_1_slice, true); - - pwm_set_enabled(turn_2_slice, true); - pwm_set_enabled(wheel_2_slice, true); - - pwm_set_enabled(turn_3_slice, true); - pwm_set_enabled(wheel_3_slice, true); +void setup_pins(int a, int b, int pwm, int slice, int channel) +{ + gpio_init(a); + gpio_init(b); + gpio_init(pwm); + gpio_set_dir(a, GPIO_OUT); + gpio_set_dir(b, GPIO_OUT); + gpio_set_function(pwm, GPIO_FUNC_PWM); + pwm_set_wrap(slice, count_max); + pwm_set_chan_level(slice, channel, 0); + pwm_set_enabled(slice, true); } int main() @@ -336,26 +221,24 @@ int main() // Set I2C address for Pico i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); - setup(); // setup for pwm + // Set up the pins for the motors + setup_pins(wheel_1a, wheel_1b, wheel_1_pwm, wheel_1_slice, wheel_1_channel); + setup_pins(wheel_2a, wheel_2b, wheel_2_pwm, wheel_2_slice, wheel_2_channel); + setup_pins(wheel_3a, wheel_3b, wheel_3_pwm, wheel_3_slice, wheel_3_channel); + + setup_pins(turn_1a, turn_1b, turn_1_pwm, turn_1_slice, turn_1_channel); + setup_pins(turn_2a, turn_2b, turn_2_pwm, turn_2_slice, turn_2_channel); + setup_pins(turn_3a, turn_3b, turn_3_pwm, turn_3_slice, turn_3_channel); - while (1) + while (true) { - // while (true) { gpio_put(LED_PIN, 1); - // sleep_ms(250); - // sleep_ms(250); - // } - // count++; - // printf("Count: %d\n", input_status); if (input_status == 1) { input_status = 0; - // printf("Data received\n"); - uint64_t tmp_float = 0; // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex - // printf("dati len: %d\n", buffer->buff[buffer->head]->len); + uint64_t tmp_float = 0; // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex for (int i = 7; i >= 0; i--) // bytes are stored in int8 array (64 bits), pico reads backwards { - // tmp_float |= input[i]; // write byte at a time to tmp_float and shift tmp_float |= buffer->buff[buffer->head]->data[i + 1]; // write byte at a time to tmp_float and shift printf("%d ", buffer->buff[buffer->head]->data[i + 1]); if (i != 0) @@ -369,20 +252,16 @@ int main() tmp_float = 0; // clear float buffer->head = (buffer->head + 1) % I2C_DATA_LENGTH; buffer->full = 0; - // input_status = 0; } else { printf("%f ", joyX); printf("%f\n", joyY); // printing floats in console - // printf("No data\n"); - // printf(""); } gpio_put(LED_PIN, 0); if (joyX == 0) { // in1 and in2 are high - // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle gpio_put(turn_1a, 1); gpio_put(turn_1b, 1); @@ -458,7 +337,6 @@ int main() pwm_set_chan_level(turn_3_slice, turn_3_channel, abs((int)(joyX * count_max))); pwm_set_chan_level(wheel_3_slice, wheel_3_channel, abs((int)(joyY * count_max))); - // pwm_set_chan_level(wheel_2_slice, wheel_2_channel, (int)(0.5 * count_max)); // sleep_ms(20); // used for debugging - reduces the number of loops w/ no new i2c data } From f159ef73be056b997bb230f406bb2883900554ea Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Fri, 23 Feb 2024 04:12:05 -0800 Subject: [PATCH 44/65] swerve vector control? --- dev/basic_swerve/Controller.py | 2 +- dev/basic_swerve/basic_swerve_pi.py | 100 +++++++++++--- dev/basic_swerve/basic_swerve_pico.c | 189 +++++++++++++++++++-------- 3 files changed, 218 insertions(+), 73 deletions(-) diff --git a/dev/basic_swerve/Controller.py b/dev/basic_swerve/Controller.py index 34c5ab8..af696be 100644 --- a/dev/basic_swerve/Controller.py +++ b/dev/basic_swerve/Controller.py @@ -10,7 +10,7 @@ class PS4_Controller(object): def __init__(self): self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input - self.THRESHOLD = 0.03 # Threshold for joystick deadzone + self.THRESHOLD = 0.04 # Threshold for joystick deadzone self.reset_vars() # Reset all controller variables to their initial state self.start_thread(()) # Start a new thread to monitor the controller diff --git a/dev/basic_swerve/basic_swerve_pi.py b/dev/basic_swerve/basic_swerve_pi.py index 0c43083..dd40ba2 100644 --- a/dev/basic_swerve/basic_swerve_pi.py +++ b/dev/basic_swerve/basic_swerve_pi.py @@ -20,26 +20,94 @@ joy = PS4_Controller() # Initialize the controller delay = 0.05 # Delay between each read/write operation +import math + +# radius of swerve drive base in meters +radius = 1 + + +# add two vectors +def add_two_vec(v1, v2): + # get difference between two vectors, treating one vector as perpendicular to x axis + theta_diff = v2[1] - v1[1] + theta_diff = theta_diff + + # since vec1 is x axis, vector 1 contributes in only the x axis + # when breaking into components, just add vec2 in x to vec1 magnitude to get x + # vec2 in y to get y + # sqrt(a^2+b^2) to get magnitude, and arctan(y/x) to get angle relative to x (add to vec1 orientation) + x_comp = v1[0] + math.cos(theta_diff) * v2[0] + y_comp = math.sin(theta_diff) * v2[0] + f_mag = math.sqrt(x_comp**2 + y_comp**2) + f_angle = math.atan2(y_comp, x_comp) + v1[1] + f_angle = f_angle + if f_angle < -math.pi: + f_angle += 2 * math.pi + elif f_angle > math.pi: + f_angle -= 2 * math.pi + return [f_mag, f_angle] + + +# input velocity [speed (m/s), orientation (deg)] in local reference frame +# rotation (rad/s) +def convert(v_vec, omega): + + # the vector for each motor in global reference frame is given by adding + # 1) velocity (relative to global reference frame) + # 2) rotation vector (vector perpendicular to each motor relative to the body reference frame) + # because the frame is circular, magnitude of vector is calculated by V = rw (formula for tangential speed relative to radius and rad/s) + + # setting all vectors to velocity relative to local reference frame + m1 = [v_vec[0], v_vec[1]] + m2 = [v_vec[0], v_vec[1] - 2 * math.pi / 3] + m3 = [v_vec[0], v_vec[1] - 4 * math.pi / 3] + + # create magnitude for each vector + rot_mag = omega * radius + dir = 1 + + if rot_mag < 0: + dir *= -1 + rot_mag *= -1 + # add two vectors (in local frame) based on direction and magnitude + m1 = add_two_vec(m1, [rot_mag, math.pi / 2 * dir]) + m2 = add_two_vec(m2, [rot_mag, math.pi / 2 * dir]) + m3 = add_two_vec(m3, [rot_mag, math.pi / 2 * dir]) + + return [m1, m2, m3] + + while True: # Infinite loop status = joy.read_self() # Read the status of the controller x = status.LeftJoystickX # Get the X position of the left joystick - y = -1 * status.RightJoystickY # Get the Y position of the right joystick - joystickswitch = x > 0 # Check if the joystick is moved to the right + y = -1 * status.LeftJoystickY # Get the Y position of the left joystick + w = status.RightJoystickX # Get the X position of the right joystick - x_b = struct.pack("f", x) - y_b = struct.pack("f", y) + v = (math.sqrt(x**2 + y**2), math.atan2(y, x)) + m = convert(v, w) - data = bytes([0xFA]) + \ - x_b + \ - y_b + \ - bytes([0xFB]) # Prepare the data to be sent - # bytes([0xFB, 0x00]) # Prepare the data to be sent - # data = x_b + y_b + bytes([0x00]) # Prepare the data to be sent - # data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] + m1_v = struct.pack("f", m[0][0]) + m1_w = struct.pack("f", math.degrees(m[0][1])) - # print(len(data)) - # print(bytes(data)) - # print(x, y) + m2_v = struct.pack("f", m[1][0]) + m2_w = struct.pack("f", math.degrees(m[1][1])) + + m3_v = struct.pack("f", m[2][0]) + m3_w = struct.pack("f", math.degrees(m[2][1])) + + data = ( + bytes([0xFA]) + m1_v + m1_w + m2_v + m2_w + m3_v + m3_w + bytes([0xFB]) + ) # Prepare the data to be sent + # data = ( + # bytes([0xFA]) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + bytes([0xFB]) + # ) # Prepare the data to be sent try: os.write(i2c_fd, data) # Write the data to the i2c device @@ -61,6 +129,4 @@ except TimeoutError: print("Timeout Error") # Print an error message if there was a timeout error except OSError: - print( - "Remote I/O Error" - ) + print("Remote I/O Error") diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c index 3145672..60cb8ad 100644 --- a/dev/basic_swerve/basic_swerve_pico.c +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -187,8 +187,16 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) } } -float joyX = 0.0f; -float joyY = 0.0f; +// linear & angular velocity for each wheel +float v1 = 0.0f; +float v2 = 0.0f; +float v3 = 0.0f; + +float w1 = 0.0f; +float w2 = 0.0f; +float w3 = 0.0f; + +const int num_floats = 6; void setup_pins(int a, int b, int pwm, int slice, int channel) { @@ -236,107 +244,178 @@ int main() if (input_status == 1) { input_status = 0; - uint64_t tmp_float = 0; // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex - for (int i = 7; i >= 0; i--) // bytes are stored in int8 array (64 bits), pico reads backwards + // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + uint32_t tmp_float[num_floats]; + // for (int i = (num_floats * 4) - 1; i >= 0; i--) // bytes are stored in int8 array (64 bits), pico reads backwards + // { + // tmp_float |= buffer->buff[buffer->head]->data[i + 1]; // write byte at a time to tmp_float and shift + // printf("%d ", buffer->buff[buffer->head]->data[i + 1]); + // if (i != 0) + // tmp_float <<= 8; // preserves order of bits in 64bit int + // } + + // for (int i = 0; i < buffer->buff[buffer->head]->len; i++) + // { + // printf("%d ", buffer->buff[buffer->head]->data[i]); + // } + // printf("\n"); + + for (int i = 0; i < num_floats; i++) { - tmp_float |= buffer->buff[buffer->head]->data[i + 1]; // write byte at a time to tmp_float and shift - printf("%d ", buffer->buff[buffer->head]->data[i + 1]); - if (i != 0) - tmp_float <<= 8; // preserves order of bits in 64bit int + tmp_float[i] = 0; + for (int j = 0; j < 4; j++) + { + tmp_float[i] |= buffer->buff[buffer->head]->data[(i * 4) + j + 1] << (8 * j); + // printf("%d:%d ", (i * 4) + j + 1, buffer->buff[buffer->head]->data[(i * 4) + j + 1]); + } + // printf("\n %x\n", tmp_float[i]); } - printf("\n"); - joyX = *(((float *)&tmp_float)); // convert to interpret bits as float (32 bits) - joyY = *(((float *)&tmp_float) + 1); - printf("%f ", joyX); - printf("%f\n", joyY); // printing floats in console - tmp_float = 0; // clear float + // printf("\n"); + // joyX = *(((float *)&tmp_float)); // convert to interpret bits as float (32 bits) + // joyY = *(((float *)&tmp_float) + 1); + v1 = *(((float *)&tmp_float[0])); + w1 = *(((float *)&tmp_float[1])); + v2 = *(((float *)&tmp_float[2])); + w2 = *(((float *)&tmp_float[3])); + v3 = *(((float *)&tmp_float[4])); + w3 = *(((float *)&tmp_float[5])); + + // printing floats in console + printf("%f ", v1); + printf("%f, ", w1); + printf("%f ", v2); + printf("%f, ", w2); + printf("%f ", v3); + printf("%f\n", w3); + + // tmp_float = 0; // clear float buffer->head = (buffer->head + 1) % I2C_DATA_LENGTH; buffer->full = 0; } else { - printf("%f ", joyX); - printf("%f\n", joyY); // printing floats in console + // printf("%f ", v1); + // printf("%f ", w1); + // printf("%f ", v2); + // printf("%f ", w2); + // printf("%f ", v3); + // printf("%f\n", w3); } gpio_put(LED_PIN, 0); - if (joyX == 0) + if (w1 == 0) { // in1 and in2 are high gpio_put(turn_1a, 1); gpio_put(turn_1b, 1); - - gpio_put(turn_2a, 1); - gpio_put(turn_2b, 1); - - gpio_put(turn_3a, 1); - gpio_put(turn_3b, 1); } - else if (joyX < 0) + else if (w1 < 0) { // in1 is high and in2 is low gpio_put(turn_1a, 1); gpio_put(turn_1b, 0); - - gpio_put(turn_2a, 1); - gpio_put(turn_2b, 0); - - gpio_put(turn_3a, 1); - gpio_put(turn_3b, 0); } else { // in1 is low and in2 is high gpio_put(turn_1b, 1); gpio_put(turn_1a, 0); + } + if (w2 == 0) + { // in1 and in2 are high + gpio_put(turn_2a, 1); + gpio_put(turn_2b, 1); + } + else if (w2 < 0) + { // in1 is high and in2 is low + gpio_put(turn_2a, 1); + gpio_put(turn_2b, 0); + } + else + { // in1 is low and in2 is high gpio_put(turn_2b, 1); gpio_put(turn_2a, 0); + } + if (w3 == 0) + { // in1 and in2 are high + gpio_put(turn_3a, 1); + gpio_put(turn_3b, 1); + } + else if (w3 < 0) + { // in1 is high and in2 is low + gpio_put(turn_3a, 1); + gpio_put(turn_3b, 0); + } + else + { // in1 is low and in2 is high gpio_put(turn_3b, 1); gpio_put(turn_3a, 0); } - // wheel motor - if (joyY == 0) - { // in1 and in2 are high + if (v1 == 0) + { + // in1 and in2 are high gpio_put(wheel_1a, 1); gpio_put(wheel_1b, 1); - - gpio_put(wheel_2a, 1); - gpio_put(wheel_2b, 1); - - gpio_put(wheel_3a, 1); - gpio_put(wheel_3b, 1); } - else if (joyY < 0) - { // in1 is high and in2 is low + else if (v1 < 0) + { + // in1 is high and in2 is low gpio_put(wheel_1a, 1); gpio_put(wheel_1b, 0); + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_1b, 1); + gpio_put(wheel_1a, 0); + } + if (v2 == 0) + { + // in1 and in2 are high + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 1); + } + else if (v2 < 0) + { + // in1 is high and in2 is low gpio_put(wheel_2a, 1); gpio_put(wheel_2b, 0); + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_2b, 1); + gpio_put(wheel_2a, 0); + } + if (v3 == 0) + { + // in1 and in2 are high + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 1); + } + else if (v3 < 0) + { + // in1 is high and in2 is low gpio_put(wheel_3a, 1); gpio_put(wheel_3b, 0); } else - { // in1 is low and in2 is high - gpio_put(wheel_1a, 0); - gpio_put(wheel_1b, 1); - - gpio_put(wheel_2a, 0); - gpio_put(wheel_2b, 1); - - gpio_put(wheel_3a, 0); + { + // in1 is low and in2 is high gpio_put(wheel_3b, 1); + gpio_put(wheel_3a, 0); } - pwm_set_chan_level(turn_1_slice, turn_1_channel, abs((int)(joyX * count_max))); - pwm_set_chan_level(wheel_1_slice, wheel_1_channel, abs((int)(joyY * count_max))); + pwm_set_chan_level(turn_1_slice, turn_1_channel, abs((int)(w1 * count_max))); + pwm_set_chan_level(wheel_1_slice, wheel_1_channel, abs((int)(v1 * count_max))); - pwm_set_chan_level(turn_2_slice, turn_2_channel, abs((int)(joyX * count_max))); - pwm_set_chan_level(wheel_2_slice, wheel_2_channel, abs((int)(joyY * count_max))); + pwm_set_chan_level(turn_2_slice, turn_2_channel, abs((int)(w2 * count_max))); + pwm_set_chan_level(wheel_2_slice, wheel_2_channel, abs((int)(v2 * count_max))); - pwm_set_chan_level(turn_3_slice, turn_3_channel, abs((int)(joyX * count_max))); - pwm_set_chan_level(wheel_3_slice, wheel_3_channel, abs((int)(joyY * count_max))); + pwm_set_chan_level(turn_3_slice, turn_3_channel, abs((int)(w3 * count_max))); + pwm_set_chan_level(wheel_3_slice, wheel_3_channel, abs((int)(v3 * count_max))); // sleep_ms(20); // used for debugging - reduces the number of loops w/ no new i2c data } From c51bd3777f8bdb8863e2659824e4befc3793640b Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 2 Mar 2024 14:37:02 -0800 Subject: [PATCH 45/65] worked on steering velocity scaling Co-authored-by: AriSinervo <129624882+AriSinervo@users.noreply.github.com> --- dev/basic_swerve/basic_swerve_pico.c | 48 ++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c index 60cb8ad..8be58e9 100644 --- a/dev/basic_swerve/basic_swerve_pico.c +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -4,6 +4,7 @@ #include #include #include +#include // Define constants for I2C communication #define I2C_PICO_ADDR 0x08 @@ -198,6 +199,13 @@ float w3 = 0.0f; const int num_floats = 6; +float rot1, rot2, rot3; // current working rotation to calc delta +float delt1, delt2, delt3; // change in rotation after subtracting omega +float u1, u2, u3; // interim omega values + +int flip1 = 1, flip2 = 1, flip3 = 1; +// const float scale = + void setup_pins(int a, int b, int pwm, int slice, int channel) { gpio_init(a); @@ -281,12 +289,13 @@ int main() w3 = *(((float *)&tmp_float[5])); // printing floats in console - printf("%f ", v1); - printf("%f, ", w1); - printf("%f ", v2); - printf("%f, ", w2); - printf("%f ", v3); - printf("%f\n", w3); + // printf("%f ", v1); + // printf("%f, ", w1); + // printf("%f ", v2); + // printf("%f, ", w2); + // printf("%f ", v3); + // printf("%f\n", w3); + printf("%u ", to_ms_since_boot(get_absolute_time())); // tmp_float = 0; // clear float buffer->head = (buffer->head + 1) % I2C_DATA_LENGTH; @@ -303,6 +312,27 @@ int main() } gpio_put(LED_PIN, 0); + // get delta for wheel 1 + delt1 = w1 - rot1; + if (abs(delt1) > 90) { + flip1 *= -1; + delt1 = (90)*(delt1<0?-1:1)-delt1; + } + + // get delta for wheel 2 + delt2 = w2 - rot2; + if (abs(delt2) > 90) { + flip2 *= -1; + delt2 = (90)*(delt2<0?-1:1)-delt2; + } + + // get delta for wheel 3 + delt3 = w3 - rot3; + if (abs(delt3) > 90) { + flip3 *= -1; + delt3 = (90)*(delt3<0?-1:1)-delt3; + } + if (w1 == 0) { // in1 and in2 are high gpio_put(turn_1a, 1); @@ -357,7 +387,7 @@ int main() gpio_put(wheel_1a, 1); gpio_put(wheel_1b, 1); } - else if (v1 < 0) + else if (v1 * flip1 < 0) { // in1 is high and in2 is low gpio_put(wheel_1a, 1); @@ -376,7 +406,7 @@ int main() gpio_put(wheel_2a, 1); gpio_put(wheel_2b, 1); } - else if (v2 < 0) + else if (v2 * flip2 < 0) { // in1 is high and in2 is low gpio_put(wheel_2a, 1); @@ -395,7 +425,7 @@ int main() gpio_put(wheel_3a, 1); gpio_put(wheel_3b, 1); } - else if (v3 < 0) + else if (v3 * flip3 < 0) { // in1 is high and in2 is low gpio_put(wheel_3a, 1); From b00e2fe6bbc58827830254b707f2b3bb73532ff3 Mon Sep 17 00:00:00 2001 From: Ishan Madan <19366470+ishanm0@users.noreply.github.com> Date: Sat, 2 Mar 2024 15:09:05 -0800 Subject: [PATCH 46/65] testing --- dev/basic_swerve/basic_swerve_pico.c | 260 +++++++++++++++------------ 1 file changed, 146 insertions(+), 114 deletions(-) diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c index 8be58e9..f814c8c 100644 --- a/dev/basic_swerve/basic_swerve_pico.c +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -199,12 +199,12 @@ float w3 = 0.0f; const int num_floats = 6; -float rot1, rot2, rot3; // current working rotation to calc delta +float rot1, rot2, rot3; // current working rotation to calc delta float delt1, delt2, delt3; // change in rotation after subtracting omega -float u1, u2, u3; // interim omega values +float u1, u2, u3; // interim omega values int flip1 = 1, flip2 = 1, flip3 = 1; -// const float scale = +// const float scale = void setup_pins(int a, int b, int pwm, int slice, int channel) { @@ -246,7 +246,12 @@ int main() setup_pins(turn_2a, turn_2b, turn_2_pwm, turn_2_slice, turn_2_channel); setup_pins(turn_3a, turn_3b, turn_3_pwm, turn_3_slice, turn_3_channel); - while (true) + sleep_ms(1000); // wait for the i2c to be ready + + absolute_time_t start_time = get_absolute_time(); + + // turns 180 degrees (approximately) - use 11660 to help scale the degrees needed to turn to the # of timesteps necessary to complete that turn at full speed + for (int timetest = 0; timetest < 11660; timetest++) { gpio_put(LED_PIN, 1); if (input_status == 1) @@ -282,11 +287,11 @@ int main() // joyX = *(((float *)&tmp_float)); // convert to interpret bits as float (32 bits) // joyY = *(((float *)&tmp_float) + 1); v1 = *(((float *)&tmp_float[0])); - w1 = *(((float *)&tmp_float[1])); + w1 = 1.0; v2 = *(((float *)&tmp_float[2])); - w2 = *(((float *)&tmp_float[3])); + w2 = 1.0; v3 = *(((float *)&tmp_float[4])); - w3 = *(((float *)&tmp_float[5])); + w3 = 1.0; // printing floats in console // printf("%f ", v1); @@ -295,7 +300,7 @@ int main() // printf("%f, ", w2); // printf("%f ", v3); // printf("%f\n", w3); - printf("%u ", to_ms_since_boot(get_absolute_time())); + printf("i2c %u ", to_ms_since_boot(get_absolute_time())); // tmp_float = 0; // clear float buffer->head = (buffer->head + 1) % I2C_DATA_LENGTH; @@ -312,132 +317,148 @@ int main() } gpio_put(LED_PIN, 0); + if (timetest < 11649) + { + w1 = 1.0; + w2 = 1.0; + w3 = 1.0; + } + else + { + w1 = 0.0; + w2 = 0.0; + w3 = 0.0; + } + // get delta for wheel 1 delt1 = w1 - rot1; - if (abs(delt1) > 90) { + if (abs(delt1) > 90) + { flip1 *= -1; - delt1 = (90)*(delt1<0?-1:1)-delt1; + delt1 = (90) * (delt1 < 0 ? -1 : 1) - delt1; } // get delta for wheel 2 delt2 = w2 - rot2; - if (abs(delt2) > 90) { + if (abs(delt2) > 90) + { flip2 *= -1; - delt2 = (90)*(delt2<0?-1:1)-delt2; + delt2 = (90) * (delt2 < 0 ? -1 : 1) - delt2; } // get delta for wheel 3 delt3 = w3 - rot3; - if (abs(delt3) > 90) { + if (abs(delt3) > 90) + { flip3 *= -1; - delt3 = (90)*(delt3<0?-1:1)-delt3; - } - - if (w1 == 0) - { // in1 and in2 are high - gpio_put(turn_1a, 1); - gpio_put(turn_1b, 1); - } - else if (w1 < 0) - { // in1 is high and in2 is low - gpio_put(turn_1a, 1); - gpio_put(turn_1b, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_1b, 1); - gpio_put(turn_1a, 0); + delt3 = (90) * (delt3 < 0 ? -1 : 1) - delt3; } + { + if (w1 == 0) + { // in1 and in2 are high + gpio_put(turn_1a, 1); + gpio_put(turn_1b, 1); + } + else if (w1 < 0) + { // in1 is high and in2 is low + gpio_put(turn_1a, 1); + gpio_put(turn_1b, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_1b, 1); + gpio_put(turn_1a, 0); + } - if (w2 == 0) - { // in1 and in2 are high - gpio_put(turn_2a, 1); - gpio_put(turn_2b, 1); - } - else if (w2 < 0) - { // in1 is high and in2 is low - gpio_put(turn_2a, 1); - gpio_put(turn_2b, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_2b, 1); - gpio_put(turn_2a, 0); - } + if (w2 == 0) + { // in1 and in2 are high + gpio_put(turn_2a, 1); + gpio_put(turn_2b, 1); + } + else if (w2 < 0) + { // in1 is high and in2 is low + gpio_put(turn_2a, 1); + gpio_put(turn_2b, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_2b, 1); + gpio_put(turn_2a, 0); + } - if (w3 == 0) - { // in1 and in2 are high - gpio_put(turn_3a, 1); - gpio_put(turn_3b, 1); - } - else if (w3 < 0) - { // in1 is high and in2 is low - gpio_put(turn_3a, 1); - gpio_put(turn_3b, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_3b, 1); - gpio_put(turn_3a, 0); - } + if (w3 == 0) + { // in1 and in2 are high + gpio_put(turn_3a, 1); + gpio_put(turn_3b, 1); + } + else if (w3 < 0) + { // in1 is high and in2 is low + gpio_put(turn_3a, 1); + gpio_put(turn_3b, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_3b, 1); + gpio_put(turn_3a, 0); + } - if (v1 == 0) - { - // in1 and in2 are high - gpio_put(wheel_1a, 1); - gpio_put(wheel_1b, 1); - } - else if (v1 * flip1 < 0) - { - // in1 is high and in2 is low - gpio_put(wheel_1a, 1); - gpio_put(wheel_1b, 0); - } - else - { - // in1 is low and in2 is high - gpio_put(wheel_1b, 1); - gpio_put(wheel_1a, 0); - } + if (v1 == 0) + { + // in1 and in2 are high + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 1); + } + else if (v1 * flip1 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 0); + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_1b, 1); + gpio_put(wheel_1a, 0); + } - if (v2 == 0) - { - // in1 and in2 are high - gpio_put(wheel_2a, 1); - gpio_put(wheel_2b, 1); - } - else if (v2 * flip2 < 0) - { - // in1 is high and in2 is low - gpio_put(wheel_2a, 1); - gpio_put(wheel_2b, 0); - } - else - { - // in1 is low and in2 is high - gpio_put(wheel_2b, 1); - gpio_put(wheel_2a, 0); - } + if (v2 == 0) + { + // in1 and in2 are high + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 1); + } + else if (v2 * flip2 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 0); + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_2b, 1); + gpio_put(wheel_2a, 0); + } - if (v3 == 0) - { - // in1 and in2 are high - gpio_put(wheel_3a, 1); - gpio_put(wheel_3b, 1); - } - else if (v3 * flip3 < 0) - { - // in1 is high and in2 is low - gpio_put(wheel_3a, 1); - gpio_put(wheel_3b, 0); - } - else - { - // in1 is low and in2 is high - gpio_put(wheel_3b, 1); - gpio_put(wheel_3a, 0); + if (v3 == 0) + { + // in1 and in2 are high + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 1); + } + else if (v3 * flip3 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 0); + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_3b, 1); + gpio_put(wheel_3a, 0); + } } - pwm_set_chan_level(turn_1_slice, turn_1_channel, abs((int)(w1 * count_max))); pwm_set_chan_level(wheel_1_slice, wheel_1_channel, abs((int)(v1 * count_max))); @@ -448,6 +469,17 @@ int main() pwm_set_chan_level(wheel_3_slice, wheel_3_channel, abs((int)(v3 * count_max))); // sleep_ms(20); // used for debugging - reduces the number of loops w/ no new i2c data + printf("%u %u\n", to_ms_since_boot(get_absolute_time()), to_ms_since_boot(start_time)); + } + + printf("%u\n", to_ms_since_boot(get_absolute_time())); + + while (1) + { + gpio_put(LED_PIN, 1); + sleep_ms(500); + gpio_put(LED_PIN, 0); + sleep_ms(500); } return 0; From 23489feef6ba5c729b2cdb95e5854d8ae872d409 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:47:50 -0800 Subject: [PATCH 47/65] Update basic_swerve_pico.c --- dev/basic_swerve/basic_swerve_pico.c | 223 ++++++++------------------- 1 file changed, 65 insertions(+), 158 deletions(-) diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c index f814c8c..9f4bf53 100644 --- a/dev/basic_swerve/basic_swerve_pico.c +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -251,7 +251,8 @@ int main() absolute_time_t start_time = get_absolute_time(); // turns 180 degrees (approximately) - use 11660 to help scale the degrees needed to turn to the # of timesteps necessary to complete that turn at full speed - for (int timetest = 0; timetest < 11660; timetest++) + // for (int timetest = 0; timetest < 11660; timetest++) + while (1) { gpio_put(LED_PIN, 1); if (input_status == 1) @@ -259,19 +260,6 @@ int main() input_status = 0; // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex uint32_t tmp_float[num_floats]; - // for (int i = (num_floats * 4) - 1; i >= 0; i--) // bytes are stored in int8 array (64 bits), pico reads backwards - // { - // tmp_float |= buffer->buff[buffer->head]->data[i + 1]; // write byte at a time to tmp_float and shift - // printf("%d ", buffer->buff[buffer->head]->data[i + 1]); - // if (i != 0) - // tmp_float <<= 8; // preserves order of bits in 64bit int - // } - - // for (int i = 0; i < buffer->buff[buffer->head]->len; i++) - // { - // printf("%d ", buffer->buff[buffer->head]->data[i]); - // } - // printf("\n"); for (int i = 0; i < num_floats; i++) { @@ -283,55 +271,20 @@ int main() } // printf("\n %x\n", tmp_float[i]); } - // printf("\n"); - // joyX = *(((float *)&tmp_float)); // convert to interpret bits as float (32 bits) - // joyY = *(((float *)&tmp_float) + 1); v1 = *(((float *)&tmp_float[0])); - w1 = 1.0; v2 = *(((float *)&tmp_float[2])); - w2 = 1.0; v3 = *(((float *)&tmp_float[4])); - w3 = 1.0; - - // printing floats in console - // printf("%f ", v1); - // printf("%f, ", w1); - // printf("%f ", v2); - // printf("%f, ", w2); - // printf("%f ", v3); - // printf("%f\n", w3); + printf("i2c %u ", to_ms_since_boot(get_absolute_time())); // tmp_float = 0; // clear float buffer->head = (buffer->head + 1) % I2C_DATA_LENGTH; buffer->full = 0; } - else - { - // printf("%f ", v1); - // printf("%f ", w1); - // printf("%f ", v2); - // printf("%f ", w2); - // printf("%f ", v3); - // printf("%f\n", w3); - } gpio_put(LED_PIN, 0); - if (timetest < 11649) - { - w1 = 1.0; - w2 = 1.0; - w3 = 1.0; - } - else - { - w1 = 0.0; - w2 = 0.0; - w3 = 0.0; - } - // get delta for wheel 1 - delt1 = w1 - rot1; + delt1 = v1 - rot1; if (abs(delt1) > 90) { flip1 *= -1; @@ -339,7 +292,7 @@ int main() } // get delta for wheel 2 - delt2 = w2 - rot2; + delt2 = v2 - rot2; if (abs(delt2) > 90) { flip2 *= -1; @@ -347,125 +300,79 @@ int main() } // get delta for wheel 3 - delt3 = w3 - rot3; + delt3 = v3 - rot3; if (abs(delt3) > 90) { flip3 *= -1; delt3 = (90) * (delt3 < 0 ? -1 : 1) - delt3; } - { - if (w1 == 0) - { // in1 and in2 are high - gpio_put(turn_1a, 1); - gpio_put(turn_1b, 1); - } - else if (w1 < 0) - { // in1 is high and in2 is low - gpio_put(turn_1a, 1); - gpio_put(turn_1b, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_1b, 1); - gpio_put(turn_1a, 0); - } - - if (w2 == 0) - { // in1 and in2 are high - gpio_put(turn_2a, 1); - gpio_put(turn_2b, 1); - } - else if (w2 < 0) - { // in1 is high and in2 is low - gpio_put(turn_2a, 1); - gpio_put(turn_2b, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_2b, 1); - gpio_put(turn_2a, 0); - } - if (w3 == 0) - { // in1 and in2 are high - gpio_put(turn_3a, 1); - gpio_put(turn_3b, 1); - } - else if (w3 < 0) - { // in1 is high and in2 is low - gpio_put(turn_3a, 1); - gpio_put(turn_3b, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_3b, 1); - gpio_put(turn_3a, 0); - } - - if (v1 == 0) - { - // in1 and in2 are high - gpio_put(wheel_1a, 1); - gpio_put(wheel_1b, 1); - } - else if (v1 * flip1 < 0) - { - // in1 is high and in2 is low - gpio_put(wheel_1a, 1); - gpio_put(wheel_1b, 0); - } - else - { - // in1 is low and in2 is high - gpio_put(wheel_1b, 1); - gpio_put(wheel_1a, 0); - } + if (v1 == 0) + { + // in1 and in2 are high + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 1); + } + else if (v1 * flip1 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 0); + rot1 += 0.01543739279 + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_1b, 1); + gpio_put(wheel_1a, 0); + rot1 -= 0.01543739279 + } - if (v2 == 0) - { - // in1 and in2 are high - gpio_put(wheel_2a, 1); - gpio_put(wheel_2b, 1); - } - else if (v2 * flip2 < 0) - { - // in1 is high and in2 is low - gpio_put(wheel_2a, 1); - gpio_put(wheel_2b, 0); - } - else - { - // in1 is low and in2 is high - gpio_put(wheel_2b, 1); - gpio_put(wheel_2a, 0); - } + if (v2 == 0) + { + // in1 and in2 are high + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 1); + } + else if (v2 * flip2 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 0); + rot2 += 0.01543739279 + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_2b, 1); + gpio_put(wheel_2a, 0); + rot2 -= 0.01543739279 + } - if (v3 == 0) - { - // in1 and in2 are high - gpio_put(wheel_3a, 1); - gpio_put(wheel_3b, 1); - } - else if (v3 * flip3 < 0) - { - // in1 is high and in2 is low - gpio_put(wheel_3a, 1); - gpio_put(wheel_3b, 0); - } - else - { - // in1 is low and in2 is high - gpio_put(wheel_3b, 1); - gpio_put(wheel_3a, 0); - } + if (v3 == 0) + { + // in1 and in2 are high + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 1); + } + else if (v3 * flip3 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 0); + rot3 += 0.01543739279 + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_3b, 1); + gpio_put(wheel_3a, 0); + rot3 -= 0.01543739279 } - pwm_set_chan_level(turn_1_slice, turn_1_channel, abs((int)(w1 * count_max))); pwm_set_chan_level(wheel_1_slice, wheel_1_channel, abs((int)(v1 * count_max))); - pwm_set_chan_level(turn_2_slice, turn_2_channel, abs((int)(w2 * count_max))); pwm_set_chan_level(wheel_2_slice, wheel_2_channel, abs((int)(v2 * count_max))); - pwm_set_chan_level(turn_3_slice, turn_3_channel, abs((int)(w3 * count_max))); pwm_set_chan_level(wheel_3_slice, wheel_3_channel, abs((int)(v3 * count_max))); // sleep_ms(20); // used for debugging - reduces the number of loops w/ no new i2c data From 1052dc17ba30485177ce40165cc11df061be47ae Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Thu, 2 May 2024 15:11:34 -0700 Subject: [PATCH 48/65] cleanup file cleanup --- dev/i2c-comms/CMakeLists.txt | 51 - dev/i2c-comms/pi_i2c.py | 18 - dev/i2c-comms/pico_i2c.c | 32 - dev/i2c-comms/pico_sdk_import.cmake | 73 - dev/motor_test_python/Controller.py | 258 -- dev/motor_test_python/motor_test.py | 107 - dev/motor_test_python/procon.py | 331 -- dev/swerve_sim_joystick/Controller.py | 204 - dev/swerve_sim_joystick/joystick_sim.py | 197 - dev/swerve_sim_joystick/procon.py | 331 -- dev/swerve_sim_keyboard/README.md | 22 - dev/swerve_sim_keyboard/poetry.lock | 3302 ----------------- dev/swerve_sim_keyboard/pyproject.toml | 18 - dev/swerve_sim_keyboard/src/main.py | 167 - dev/swerve_sim_keyboard/test/keyboard_test.py | 40 - 15 files changed, 5151 deletions(-) delete mode 100644 dev/i2c-comms/CMakeLists.txt delete mode 100644 dev/i2c-comms/pi_i2c.py delete mode 100644 dev/i2c-comms/pico_i2c.c delete mode 100644 dev/i2c-comms/pico_sdk_import.cmake delete mode 100644 dev/motor_test_python/Controller.py delete mode 100644 dev/motor_test_python/motor_test.py delete mode 100644 dev/motor_test_python/procon.py delete mode 100644 dev/swerve_sim_joystick/Controller.py delete mode 100644 dev/swerve_sim_joystick/joystick_sim.py delete mode 100644 dev/swerve_sim_joystick/procon.py delete mode 100644 dev/swerve_sim_keyboard/README.md delete mode 100644 dev/swerve_sim_keyboard/poetry.lock delete mode 100644 dev/swerve_sim_keyboard/pyproject.toml delete mode 100755 dev/swerve_sim_keyboard/src/main.py delete mode 100755 dev/swerve_sim_keyboard/test/keyboard_test.py diff --git a/dev/i2c-comms/CMakeLists.txt b/dev/i2c-comms/CMakeLists.txt deleted file mode 100644 index b961952..0000000 --- a/dev/i2c-comms/CMakeLists.txt +++ /dev/null @@ -1,51 +0,0 @@ -# Generated Cmake Pico project file - -cmake_minimum_required(VERSION 3.13) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) - -# Initialise pico_sdk from installed location -# (note this can come from environment, CMake cache etc) - -set(PICO_BOARD pico CACHE STRING "Board type") - -# Pull in Raspberry Pi Pico SDK (must be before project) -include(pico_sdk_import.cmake) - -if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") - message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") -endif() - -project(pico_i2c C CXX ASM) - -# Initialise the Raspberry Pi Pico SDK -pico_sdk_init() - -# Add executable. Default name is the project name, version 0.1 - -add_executable(pico_i2c pico_i2c.c ) - -pico_set_program_name(pico_i2c "pico_i2c") -pico_set_program_version(pico_i2c "0.1") - -pico_enable_stdio_uart(pico_i2c 0) -pico_enable_stdio_usb(pico_i2c 1) - -# Add the standard library to the build -target_link_libraries(pico_i2c - pico_stdlib) - -# Add the standard include files to the build -target_include_directories(pico_i2c PRIVATE - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required -) - -# Add any user requested libraries -target_link_libraries(pico_i2c - hardware_i2c - ) - -pico_add_extra_outputs(pico_i2c) - diff --git a/dev/i2c-comms/pi_i2c.py b/dev/i2c-comms/pi_i2c.py deleted file mode 100644 index a4b07c7..0000000 --- a/dev/i2c-comms/pi_i2c.py +++ /dev/null @@ -1,18 +0,0 @@ -import fcntl -import os -import time - -I2C_PRIM = 0x0703 - -# open i2c devices -i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) - -# set the i2c address of pico -pico_address = 0x08 -fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) - -# send data to pico -data = [0x01, 0x02, 0x03] # example data -while (True): - os.write(i2c_fd, bytes(data)) - time.sleep(1) diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c deleted file mode 100644 index 948dc18..0000000 --- a/dev/i2c-comms/pico_i2c.c +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include "pico/stdlib.h" -#include "hardware/i2c.h" - -int main() { - stdio_init_all(); - - // init i2c at 100kHz - i2c_init(i2c0, 100 * 1000); - gpio_set_function(0, GPIO_FUNC_I2C); - gpio_set_function(1, GPIO_FUNC_I2C); - // i2c_pullup_en(i2c0, true); - - // set i2c address for pico - i2c_set_slave_mode(i2c0, true, 0x08); - // i2c_set_slave_address(i2c0, 0x08); // address should match pi code - - uint8_t data[3]; - - while(1) { - // read data from i2c_pi.py - i2c_read_blocking(i2c0, 0x08, data, 3, true); - - // process data - for (int i = 0; i < 3; i++) { - printf("Received data %d: 0x%02X\n ", i, data[i]); - } - } - - return 0; - -} \ No newline at end of file diff --git a/dev/i2c-comms/pico_sdk_import.cmake b/dev/i2c-comms/pico_sdk_import.cmake deleted file mode 100644 index 65f8a6f..0000000 --- a/dev/i2c-comms/pico_sdk_import.cmake +++ /dev/null @@ -1,73 +0,0 @@ -# This is a copy of /external/pico_sdk_import.cmake - -# This can be dropped into an external project to help locate this SDK -# It should be include()ed prior to project() - -if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) - set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) - message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) - set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) - message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) - set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) - message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") -endif () - -set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") -set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") -set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") - -if (NOT PICO_SDK_PATH) - if (PICO_SDK_FETCH_FROM_GIT) - include(FetchContent) - set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) - if (PICO_SDK_FETCH_FROM_GIT_PATH) - get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") - endif () - # GIT_SUBMODULES_RECURSE was added in 3.17 - if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - GIT_SUBMODULES_RECURSE FALSE - ) - else () - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - ) - endif () - - if (NOT pico_sdk) - message("Downloading Raspberry Pi Pico SDK") - FetchContent_Populate(pico_sdk) - set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) - endif () - set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) - else () - message(FATAL_ERROR - "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." - ) - endif () -endif () - -get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") -if (NOT EXISTS ${PICO_SDK_PATH}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") -endif () - -set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) -if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") -endif () - -set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) - -include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/motor_test_python/Controller.py b/dev/motor_test_python/Controller.py deleted file mode 100644 index 97e20b7..0000000 --- a/dev/motor_test_python/Controller.py +++ /dev/null @@ -1,258 +0,0 @@ -from inputs import get_gamepad # pip install inputs -import math -import threading -from procon import ProCon - - -class Controller(object): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 7) - self.THRESHOLD = 0.03 - - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 - - self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=() - ) - self._monitor_thread.daemon = True - self._monitor_thread.start() - - def read(self): # return the buttons/triggers that you care about in this methode - # x = self.LeftJoystickX - # y = self.LeftJoystickY - # a = self.A - # b = self.X # b=1, x=2 - # rb = self.RightBumper - # return [x, y, a, b, rb] - return [ - self.LeftJoystickY, - self.LeftJoystickX, - self.RightJoystickY, - self.RightJoystickX, - self.LeftTrigger, - self.RightTrigger, - self.LeftBumper, - self.RightBumper, - self.A, - self.B, - self.X, - self.Y, - self.LeftThumb, - self.RightThumb, - self.Back, - self.Start, - self.LeftDPad, - self.RightDPad, - self.UpDPad, - self.DownDPad, - ] - - def read_self(self): - return self - - def threshold(self, val): - return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 - - def _monitor_controller(self): - while True: - events = get_gamepad() - for event in events: - if event.code == "ABS_Y": - self.LeftJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_X": - self.LeftJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RY": - self.RightJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RX": - self.RightJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_Z": - self.LeftTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "ABS_RZ": - self.RightTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "BTN_TL": - self.LeftBumper = event.state - elif event.code == "BTN_TR": - self.RightBumper = event.state - elif event.code == "BTN_SOUTH": - self.A = event.state - elif event.code == "BTN_NORTH": - self.Y = event.state # previously switched with X - elif event.code == "BTN_WEST": - self.X = event.state # previously switched with Y - elif event.code == "BTN_EAST": - self.B = event.state - elif event.code == "BTN_THUMBL": - self.LeftThumb = event.state - elif event.code == "BTN_THUMBR": - self.RightThumb = event.state - elif event.code == "BTN_SELECT": - self.Back = event.state - elif event.code == "BTN_START": - self.Start = event.state - elif event.code == "BTN_TRIGGER_HAPPY1": - self.LeftDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY2": - self.RightDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY3": - self.UpDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY4": - self.DownDPad = event.state - - -class GemXboxController(Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 15) - self.THRESHOLD = 0.03 - - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 - - self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=() - ) - self._monitor_thread.daemon = True - self._monitor_thread.start() - - -class NintendoProController(Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) - self.MAX_JOY_VAL = math.pow(2, 15) - self.THRESHOLD = 0.1 - self.controller = ProCon() - - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 - - self._monitor_thread = threading.Thread( - target=self.controller.start, args=(self.procon_callback_func,) - ) - self._monitor_thread.daemon = True - self._monitor_thread.start() - - def read(self): - return [ - self.LeftJoystickX, - self.LeftJoystickY, - self.RightJoystickX, - self.RightJoystickY, - self.LeftTrigger, - self.RightTrigger, - self.LeftBumper, - self.RightBumper, - self.A, - self.B, - self.X, - self.Y, - self.LeftThumb, - self.RightThumb, - self.Back, - self.Start, - self.LeftDPad, - self.RightDPad, - self.UpDPad, - self.DownDPad, - ] - - def procon_callback_func(self, buttons, l_stick, r_stick, *_): - self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) - self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) - self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) - self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) - self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) - self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) - self.LeftBumper = buttons[ProCon.Button.L] - self.RightBumper = buttons[ProCon.Button.R] - self.A = buttons[ProCon.Button.A] - self.B = buttons[ProCon.Button.B] - self.X = buttons[ProCon.Button.X] - self.Y = buttons[ProCon.Button.Y] - self.LeftThumb = buttons[ProCon.Button.LS] - self.RightThumb = buttons[ProCon.Button.RS] - self.Back = buttons[ProCon.Button.MINUS] - self.Start = buttons[ProCon.Button.PLUS] - self.LeftDPad = buttons[ProCon.Button.LEFT] - self.RightDPad = buttons[ProCon.Button.RIGHT] - self.UpDPad = buttons[ProCon.Button.UP] - self.DownDPad = buttons[ProCon.Button.DOWN] - - -if __name__ == "__main__": - joy = Controller() - # joy = GemXboxController() - # joy = NintendoProController() - while True: - try: - print(joy.read()) - except Exception as e: - print("error!", e) - break diff --git a/dev/motor_test_python/motor_test.py b/dev/motor_test_python/motor_test.py deleted file mode 100644 index 1e1112a..0000000 --- a/dev/motor_test_python/motor_test.py +++ /dev/null @@ -1,107 +0,0 @@ -# Import the necessary libraries -import lgpio # sudo apt install python3-lgpio -from Controller import Controller - -# Define the GPIO pins for the motor controller -turn_in1_pin = 22 -turn_in2_pin = 27 -turn_pwm_pin = 17 - -wheel_in1_pin = 4 -wheel_in2_pin = 3 -wheel_pwm_pin = 2 - -# Define the frequency for the PWM signal -freq = 500 - -# Initialize the disable flag and the controller -disable = True -joy = Controller() - -# Open the GPIO chip -h = lgpio.gpiochip_open(0) - -# Claim the GPIO pins as output -lgpio.gpio_claim_output(h, turn_in1_pin) -lgpio.gpio_claim_output(h, turn_in2_pin) -lgpio.gpio_claim_output(h, wheel_in1_pin) -lgpio.gpio_claim_output(h, wheel_in2_pin) - -# Initialize the exit counter -exit_count = 0 - -try: - # Main loop - while True: - # Read the controller status - status = joy.read_self() - - # If the X button is pressed, disable the motors - if status.X: - if not disable: - print("disabling!") - disable = True - exit_count += 1 - # If the B button is pressed, enable the motors - elif status.B: - if disable: - print("enabling!") - disable = False - exit_count = 0 - - # If the motors are disabled, write 1 to all GPIO pins - if disable: - lgpio.gpio_write(h, turn_in1_pin, 1) - lgpio.gpio_write(h, turn_in2_pin, 1) - lgpio.gpio_write(h, wheel_in1_pin, 1) - lgpio.gpio_write(h, wheel_in2_pin, 1) - - # If the exit counter exceeds 50000, break the loop - if exit_count > 50000: - break - - continue - - # Read the joystick values - x = status.LeftJoystickX - y = status.RightJoystickY - - # Control the turning of the motors based on the X value of the joystick - if x == 0: - lgpio.gpio_write(h, turn_in1_pin, 1) - lgpio.gpio_write(h, turn_in2_pin, 1) - elif x < 0: - lgpio.gpio_write(h, turn_in1_pin, 1) - lgpio.gpio_write(h, turn_in2_pin, 0) - else: - lgpio.gpio_write(h, turn_in1_pin, 0) - lgpio.gpio_write(h, turn_in2_pin, 1) - - # Control the movement of the motors based on the Y value of the joystick - if y == 0: - lgpio.gpio_write(h, wheel_in1_pin, 1) - lgpio.gpio_write(h, wheel_in2_pin, 1) - elif y < 0: - lgpio.gpio_write(h, wheel_in1_pin, 1) - lgpio.gpio_write(h, wheel_in2_pin, 0) - else: - lgpio.gpio_write(h, wheel_in1_pin, 0) - lgpio.gpio_write(h, wheel_in2_pin, 1) - - # Generate the PWM signal for the motors - lgpio.tx_pwm(h, turn_pwm_pin, freq, round(abs(x) * 100.0, 2)) - lgpio.tx_pwm(h, wheel_pwm_pin, freq, round(abs(y) * 100.0, 2)) -except KeyboardInterrupt: - # If the program is interrupted, pass the exception - pass - -# When the program ends, stop the motors and release the GPIO pins -lgpio.tx_pwm(h, turn_pwm_pin, freq, 0) -lgpio.gpio_write(h, turn_in1_pin, 1) -lgpio.gpio_write(h, turn_in2_pin, 1) - -lgpio.tx_pwm(h, wheel_pwm_pin, freq, 0) -lgpio.gpio_write(h, wheel_in1_pin, 1) -lgpio.gpio_write(h, wheel_in2_pin, 1) - -lgpio.gpiochip_close(h) diff --git a/dev/motor_test_python/procon.py b/dev/motor_test_python/procon.py deleted file mode 100644 index 8e04765..0000000 --- a/dev/motor_test_python/procon.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env python3 - -import math -import time - -import hid # pip install hidapi - - -def to_int16(uint16): - return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 - - -class ProCon: - VENDOR_ID = 0x057E - PRODUCT_ID = 0x2009 - PACKET_SIZE = 64 - CALIBRATION_OFFSET = 0x603D - CALIBRATION_LENGTH = 0x12 - COMMAND_RETRIES = 10 - RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) - RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) - DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) - - class OutputReportID: - RUMBLE_SUBCOMMAND = 0x01 - RUMBLE = 0x10 - COMMAND = 0x80 - - class InputReportID: - SUBCOMMAND_REPLY = 0x21 - CONTROLLER_STATE = 0x30 - COMMAND_ACK = 0x81 - - class CommandID: - HANDSHAKE = 0x02 - HIGH_SPEED = 0x03 - FORCE_USB = 0x04 - - class SubcommandID: - SET_INPUT_REPORT_MODE = 0x03 - SPI_FLASH_READ = 0x10 - SET_PLAYER_LIGHTS = 0x30 - SET_HOME_LIGHT = 0x38 - ENABLE_IMU = 0x40 - SET_IMU_SENSITIVITY = 0x41 - ENABLE_VIBRATION = 0x48 - - class Button: - A = "A" - B = "B" - X = "X" - Y = "Y" - UP = "Up" - DOWN = "Down" - LEFT = "Left" - RIGHT = "Right" - MINUS = "-" - PLUS = "+" - SCREENSHOT = "Screenshot" - HOME = "Home" - L = "L" - ZL = "ZL" - R = "R" - ZR = "ZR" - LS = "LS" - RS = "RS" - - def __init__(self): - self.subcommand_counter = 0 - self.dev = hid.device() - self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) - self.handshake() - self.high_speed() - self.handshake() - self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL - self.rumble_expire = 0 - self.load_stick_calibration() - self.enable_vibration(True) - self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) - self.force_usb() - self.set_player_lights(True, False, False, False) - self.enable_imu(True) - self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) - - def start(self, callback): - while True: - state = self.recv() - if state[0] != ProCon.InputReportID.CONTROLLER_STATE: - continue - buttons = { - ProCon.Button.A: state[3] & 0x08 > 0, - ProCon.Button.B: state[3] & 0x04 > 0, - ProCon.Button.X: state[3] & 0x02 > 0, - ProCon.Button.Y: state[3] & 0x01 > 0, - ProCon.Button.UP: state[5] & 0x02 > 0, - ProCon.Button.DOWN: state[5] & 0x01 > 0, - ProCon.Button.LEFT: state[5] & 0x08 > 0, - ProCon.Button.RIGHT: state[5] & 0x04 > 0, - ProCon.Button.MINUS: state[4] & 0x01 > 0, - ProCon.Button.PLUS: state[4] & 0x02 > 0, - ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, - ProCon.Button.HOME: state[4] & 0x10 > 0, - ProCon.Button.L: state[5] & 0x40 > 0, - ProCon.Button.ZL: state[5] & 0x80 > 0, - ProCon.Button.R: state[3] & 0x40 > 0, - ProCon.Button.ZR: state[3] & 0x80 > 0, - ProCon.Button.LS: state[4] & 0x08 > 0, - ProCon.Button.RS: state[4] & 0x04 > 0, - } - l_x = state[6] | ((state[7] & 0xF) << 8) - l_y = (state[7] >> 4) | (state[8] << 4) - r_x = state[9] | ((state[10] & 0xF) << 8) - r_y = (state[10] >> 4) | (state[11] << 4) - l_x = self.apply_stick_calibration(l_x, 0, 0) - l_y = self.apply_stick_calibration(l_y, 0, 1) - r_x = self.apply_stick_calibration(r_x, 1, 0) - r_y = self.apply_stick_calibration(r_y, 1, 1) - l_stick = (l_x, l_y) - r_stick = (r_x, r_y) - accel = ( - state[13] | state[14] << 8, - state[15] | state[16] << 8, - state[17] | state[18] << 8, - ) - gyro = ( - state[19] | state[20] << 8, - state[21] | state[22] << 8, - state[23] | state[24] << 8, - ) - accel = tuple(map(to_int16, accel)) - gyro = tuple(map(to_int16, gyro)) - battery = (state[2] & 0xF0) >> 4 - callback(buttons, l_stick, r_stick, accel, gyro, battery) - if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: - self.send_rumble(False, False, 0) - - def load_stick_calibration(self): - ok, reply = self.spi_flash_read( - ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH - ) - if not ok: - raise RuntimeError("cannot load stick calibration") - self.stick_calibration = [ - [ - [ - ((reply[27] & 0xF) << 8) | reply[26], - ((reply[24] & 0xF) << 8) | reply[23], - ((reply[21] & 0xF) << 8) | reply[20], - ], - [ - (reply[28] << 4) | (reply[27] >> 4), - (reply[25] << 4) | (reply[24] >> 4), - (reply[22] << 4) | (reply[21] >> 4), - ], - ], - [ - [ - ((reply[33] & 0xF) << 8) | reply[32], - ((reply[30] & 0xF) << 8) | reply[29], - ((reply[36] & 0xF) << 8) | reply[35], - ], - [ - (reply[34] << 4) | (reply[33] >> 4), - (reply[31] << 4) | (reply[30] >> 4), - (reply[37] << 4) | (reply[36] >> 4), - ], - ], - ] - for i in range(len(self.stick_calibration)): - for j in range(len(self.stick_calibration[i])): - for k in range(len(self.stick_calibration[i][j])): - if self.stick_calibration[i][j][k] == 0xFFF: - self.stick_calibration[i][j][k] = 0 - self.stick_extends = [ - [ - [ - -int(self.stick_calibration[0][0][0] * 0.7), - int(self.stick_calibration[0][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[0][1][0] * 0.7), - int(self.stick_calibration[0][1][2] * 0.7), - ], - ], - [ - [ - -int(self.stick_calibration[1][0][0] * 0.7), - int(self.stick_calibration[1][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[1][1][0] * 0.7), - int(self.stick_calibration[1][1][2] * 0.7), - ], - ], - ] - - def apply_stick_calibration(self, value, stick, axis): - value -= self.stick_calibration[stick][axis][1] - if value < self.stick_extends[stick][axis][0]: - self.stick_extends[stick][axis][0] = value - if value > self.stick_extends[stick][axis][1]: - self.stick_extends[stick][axis][1] = value - if value > 0: - return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) - return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) - - def send(self, data): - return self.dev.write(data) == len(data) - - def recv(self): - return self.dev.read(ProCon.PACKET_SIZE) - - def send_command(self, id, wait_for_reply=True): - data = (ProCon.OutputReportID.COMMAND, id) - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True - reply = self.recv() - if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: - return True - return False - - def send_subcommand(self, id, param, wait_for_reply=True): - data = ( - (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - + (id,) - + param - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True, [] - reply = self.recv() - if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: - return True, reply - return False, [] - - def send_rumble(self, low, high, duration): - self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL - self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL - self.rumble_expire = ( - int(time.time() * 1000) + duration if (low or high) and duration else 0 - ) - data = ( - (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if self.send(data): - return True - return False - - def handshake(self): - return self.send_command(ProCon.CommandID.HANDSHAKE) - - def high_speed(self): - return self.send_command(ProCon.CommandID.HIGH_SPEED) - - def force_usb(self): - return self.send_command(ProCon.CommandID.FORCE_USB, False) - - def set_input_report_mode(self, mode): - return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) - - def spi_flash_read(self, addr, l): - param = ( - addr & 0x000000FF, - (addr & 0x0000FF00) >> 8, - (addr & 0x00FF0000) >> 16, - (addr & 0xFF000000) >> 24, - l, - ) - return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) - - def set_player_lights(self, one, two, three, four): - param = (one << 0) | (two << 1) | (three << 2) | (four << 3) - return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) - - def set_home_light(self, brightness): - intensity = 0 - if brightness > 0: - if brightness < 65: - intensity = (brightness + 5) // 10 - else: - intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) - intensity = (intensity & 0xF) << 4 - param = (0x01, intensity, intensity, 0x00) - return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) - - def enable_imu(self, enable): - return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) - - def set_imu_sensitivity(self, sensitivity): - return self.send_subcommand( - ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity - ) - - def enable_vibration(self, enable): - return self.send_subcommand( - ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) - ) - - -def print_state(buttons, l_stick, r_stick, accel, gyro, battery): - print("\33[2JButtons:") - for k, v in buttons.items(): - if v: - print("[{}]".format(k), end=" ") - else: - print(" {} ".format(k), end=" ") - print() - print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) - print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) - print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) - print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) - print("Battery: {}/9".format(battery)) - - -if __name__ == "__main__": - try: - ProCon().start(print_state) - except KeyboardInterrupt: - print("\rGoodbye!") diff --git a/dev/swerve_sim_joystick/Controller.py b/dev/swerve_sim_joystick/Controller.py deleted file mode 100644 index a7e9ace..0000000 --- a/dev/swerve_sim_joystick/Controller.py +++ /dev/null @@ -1,204 +0,0 @@ -import math -import threading - -from inputs import \ - get_gamepad # Import the get_gamepad function from the inputs module -from procon import ProCon # Import the ProCon class from the procon module - - -# This class represents a PS4 Controller -class PS4_Controller(object): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input - self.THRESHOLD = 0.03 # Threshold for joystick deadzone - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread(()) # Start a new thread to monitor the controller - - # This method resets all controller variables to their initial state - def reset_vars(self): - # Initialize all controller variables to 0 - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 - - # This method starts a new thread to monitor the controller - def start_thread(self, thread_args=()): - self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=thread_args - ) - self._monitor_thread.daemon = ( - True # Set the thread as a daemon so it will end when the main program ends - ) - self._monitor_thread.start() # Start the thread - - # This method returns the current state of all buttons/triggers - def read(self): - return [ - self.LeftJoystickY, - self.LeftJoystickX, - self.RightJoystickY, - self.RightJoystickX, - self.LeftTrigger, - self.RightTrigger, - self.LeftBumper, - self.RightBumper, - self.A, - self.B, - self.X, - self.Y, - self.LeftThumb, - self.RightThumb, - self.Back, - self.Start, - self.LeftDPad, - self.RightDPad, - self.UpDPad, - self.DownDPad, - ] - - # This method returns the controller object itself - def read_self(self): - return self - - # This method applies a threshold to a value - def threshold(self, val): - return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 - - def _monitor_controller(self): - while True: - events = get_gamepad() - for event in events: - if event.code == "ABS_Y": - self.LeftJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_X": - self.LeftJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RY": - self.RightJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RX": - self.RightJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_Z": - self.LeftTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "ABS_RZ": - self.RightTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "BTN_TL": - self.LeftBumper = event.state - elif event.code == "BTN_TR": - self.RightBumper = event.state - elif event.code == "BTN_SOUTH": - self.A = event.state - elif event.code == "BTN_NORTH": - self.Y = event.state # previously switched with X - elif event.code == "BTN_WEST": - self.X = event.state # previously switched with Y - elif event.code == "BTN_EAST": - self.B = event.state - elif event.code == "BTN_THUMBL": - self.LeftThumb = event.state - elif event.code == "BTN_THUMBR": - self.RightThumb = event.state - elif event.code == "BTN_SELECT": - self.Back = event.state - elif event.code == "BTN_START": - self.Start = event.state - elif event.code == "BTN_TRIGGER_HAPPY1": - self.LeftDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY2": - self.RightDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY3": - self.UpDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY4": - self.DownDPad = event.state - - -# This class represents the Xbox Controller in WRP used for the CPSRC GEM -class Gem_Xbox_Controller(PS4_Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input - self.THRESHOLD = 0.03 # Threshold for joystick deadzone - - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread(()) # Start a new thread to monitor the controller - - -# This class represents the Nintendo Pro Controller -class Nintendo_Pro_Controller(PS4_Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input - self.THRESHOLD = 0.1 # Threshold for joystick deadzone - self.controller = ProCon() # Initialize the ProCon controller - - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread( - self.procon_callback_func - ) # Start a new thread to monitor the controller - - # This method is called when the ProCon controller state changes - def procon_callback_func(self, buttons, l_stick, r_stick, *_): - # Update the controller variables based on the new state - # The joystick values are normalized between -1 and 1 - # The threshold method is used to apply a deadband to the joystick values - # The button values are either 0 or 1 - self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) - self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) - self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) - self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) - self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) - self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) - self.LeftBumper = buttons[ProCon.Button.L] - self.RightBumper = buttons[ProCon.Button.R] - self.A = buttons[ProCon.Button.A] - self.B = buttons[ProCon.Button.B] - self.X = buttons[ProCon.Button.X] - self.Y = buttons[ProCon.Button.Y] - self.LeftThumb = buttons[ProCon.Button.LS] - self.RightThumb = buttons[ProCon.Button.RS] - self.Back = buttons[ProCon.Button.MINUS] - self.Start = buttons[ProCon.Button.PLUS] - self.LeftDPad = buttons[ProCon.Button.LEFT] - self.RightDPad = buttons[ProCon.Button.RIGHT] - self.UpDPad = buttons[ProCon.Button.UP] - self.DownDPad = buttons[ProCon.Button.DOWN] - - -if __name__ == "__main__": - joy = PS4_Controller() # Initialize a PS4 controller - # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller - # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller - while True: - try: - print(joy.read()) # Print the current state of the controller - except Exception as e: - print("error!", e) # Print any errors that occur - break # Exit the loop if an error occurs diff --git a/dev/swerve_sim_joystick/joystick_sim.py b/dev/swerve_sim_joystick/joystick_sim.py deleted file mode 100644 index 619913b..0000000 --- a/dev/swerve_sim_joystick/joystick_sim.py +++ /dev/null @@ -1,197 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np - -# from Controller import Gem_Xbox_Controller -from Controller import Nintendo_Pro_Controller -from Controller import PS4_Controller - - -# return the vector perpendicular to the given vector -def perpendicular(vec): - return np.array([-vec[1], vec[0]]) - - -# NOTE: make sure to account for max motor speed when programming real motors, and normalize -# for example, if the swerve math commands one motor to spin higher than it's max speed, -# then it will only spin at the max speed, thus making the ratio of motor powers wrong and the robot will move wrong - - -if __name__ == "__main__": - # joy = Gem_Xbox_Controller() - # joy = Nintendo_Pro_Controller() - joy = PS4_Controller() - - rumble = type(joy) == Nintendo_Pro_Controller - - # robot radius - R = 5 - # dt, the delta time of the "animation" - DT = 0.001 - - # initial robot state - center_pos = np.array([0.0, 0.0]) # center position - module_dirs = ( - np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi - ) # directions of each module, relative to screen - module_pos = np.array( - [ - [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] - for a in module_dirs - ] - ) # absolute positions of each module (as a point) - freeze_pos = ( - center_pos.copy() - ) # position to rotate about when right bumper is pressed - - while True: - try: - # get inputs - joy_input = joy.read_self() - if joy_input.Back: # exit if back button is pressed - print("Exiting") - break - - # TODO: should replace this by standardizing inverts in the Controller.py class - inverts = [False, False, False, False] # Nintendo Pro Controller - # inverts = [False, True, True] # Gem Xbox Controller - - # use joystick inputs to calculate "strafe" movement - left_x = (-1.0 if inverts[0] else 1.0) * round(joy_input.LeftJoystickX, 3) - left_y = (-1.0 if inverts[1] else 1.0) * round(joy_input.LeftJoystickY, 3) - triggers = joy_input.LeftTrigger - joy_input.RightTrigger - - right_x = (-1.0 if inverts[2] else 1.0) * round(joy_input.RightJoystickX, 3) - right_y = (-1.0 if inverts[3] else 1.0) * round(joy_input.RightJoystickY, 3) - - ## LOGIC (begin) - - # get distance between freeze_pos and center_pos - dist = np.hypot( - freeze_pos[0] - center_pos[0], freeze_pos[1] - center_pos[1] - ) - - # if right bumper is not pressed, move robot in direction of joystick & rotate relative to center pos - if not joy_input.RightBumper: - move = np.array([left_x, left_y]) * 1.0 - rotate = 0.1 * triggers - - # if right bumper is pressed and freeze pos is not "inside" robot, rotate robot around freeze pos - elif dist > R: - # calculate vector from freeze to center pos - x = (freeze_pos[0] - center_pos[0]) / dist - y = (freeze_pos[1] - center_pos[1]) / dist - - # calculate new center position, moving robot around freeze pos - # x' = x*cos(theta) - y*sin(theta) - # y' = x*sin(theta) + y*cos(theta) - # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) - # https://academo.org/demos/rotation-about-point/ - move = np.array( - [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] - ) - # rotate robot so direction of modules is the same relative to freeze pos, plus some rotation from triggers - rotate = (-1.0 if left_x > 0 else 1.0) * np.hypot( - move[0], move[1] - ) / dist + 0.1 * triggers - - # if left bumper is pressed, make freeze pos the same as center pos - if joy_input.LeftBumper: - freeze_pos = center_pos.copy() - else: # if left bumper is not pressed, move freeze pos in direction of right joystick - freeze_pos += np.array([right_x, right_y]) * 1.0 - - # if right bumper is not pressed, move freeze pos in direction of right joystick (relative to center pos) - if not joy_input.RightBumper: - freeze_pos += move * 1.0 + np.array([right_x, right_y]) * 1.0 - - # update center position - center_pos += move - - # update module directions - module_dirs += rotate - - ## LOGIC (end) - - # update module positions using module directions and center position - module_pos = np.array( - [ - [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] - for a in module_dirs - ] - ) - - # set box size and aspect ratio for matplotlib plot window - box_scale = 10 - plt.xlim(-box_scale * R, box_scale * R) - plt.ylim(-box_scale * R, box_scale * R) - plt.gca().set_aspect("equal", adjustable="box") - - # array to store module controls (direction & speed of each module) - module_controls = [] - - # plot robot - for i, module in enumerate(module_pos): - # plot line from center to module - plt.plot( - [center_pos[0], module[0]], [center_pos[1], module[1]], "black" - ) - - # calculate module direction vector using robot movement vector & rotation - dir_vec = ( - move - + np.array([-np.sin(module_dirs[i]), np.cos(module_dirs[i])]) - * rotate - * 10 - ) - - # add module direction vector to module_controls as degrees & speed - module_controls.append( - ( - round(np.rad2deg(np.arctan2(dir_vec[1], dir_vec[0])), 3), - round(np.hypot(dir_vec[0], dir_vec[1]), 3), - ) - ) - - # plot module direction vectors - plt.quiver( - module[0], - module[1], - dir_vec[0], - dir_vec[1], - color="red", - angles="xy", - scale_units="xy", - scale=0.5, - ) - - print(module_controls) - - # plot center direction vector - plt.quiver( - center_pos[0], - center_pos[1], - move[0], - move[1], - color="green", - angles="xy", - scale_units="xy", - scale=0.5, - ) - - # plot line from center to freeze pos - plt.plot( - [center_pos[0], freeze_pos[0]], [center_pos[1], freeze_pos[1]], "b" - ) - - # rumble if robot is outside of box - if rumble and ( - abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R - ): - joy.controller.send_rumble(False, True, 1) - - # pause for DT seconds and clear plot - plt.pause(DT) - plt.clf() - - except Exception as e: - print(e) diff --git a/dev/swerve_sim_joystick/procon.py b/dev/swerve_sim_joystick/procon.py deleted file mode 100644 index 8e04765..0000000 --- a/dev/swerve_sim_joystick/procon.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env python3 - -import math -import time - -import hid # pip install hidapi - - -def to_int16(uint16): - return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 - - -class ProCon: - VENDOR_ID = 0x057E - PRODUCT_ID = 0x2009 - PACKET_SIZE = 64 - CALIBRATION_OFFSET = 0x603D - CALIBRATION_LENGTH = 0x12 - COMMAND_RETRIES = 10 - RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) - RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) - DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) - - class OutputReportID: - RUMBLE_SUBCOMMAND = 0x01 - RUMBLE = 0x10 - COMMAND = 0x80 - - class InputReportID: - SUBCOMMAND_REPLY = 0x21 - CONTROLLER_STATE = 0x30 - COMMAND_ACK = 0x81 - - class CommandID: - HANDSHAKE = 0x02 - HIGH_SPEED = 0x03 - FORCE_USB = 0x04 - - class SubcommandID: - SET_INPUT_REPORT_MODE = 0x03 - SPI_FLASH_READ = 0x10 - SET_PLAYER_LIGHTS = 0x30 - SET_HOME_LIGHT = 0x38 - ENABLE_IMU = 0x40 - SET_IMU_SENSITIVITY = 0x41 - ENABLE_VIBRATION = 0x48 - - class Button: - A = "A" - B = "B" - X = "X" - Y = "Y" - UP = "Up" - DOWN = "Down" - LEFT = "Left" - RIGHT = "Right" - MINUS = "-" - PLUS = "+" - SCREENSHOT = "Screenshot" - HOME = "Home" - L = "L" - ZL = "ZL" - R = "R" - ZR = "ZR" - LS = "LS" - RS = "RS" - - def __init__(self): - self.subcommand_counter = 0 - self.dev = hid.device() - self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) - self.handshake() - self.high_speed() - self.handshake() - self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL - self.rumble_expire = 0 - self.load_stick_calibration() - self.enable_vibration(True) - self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) - self.force_usb() - self.set_player_lights(True, False, False, False) - self.enable_imu(True) - self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) - - def start(self, callback): - while True: - state = self.recv() - if state[0] != ProCon.InputReportID.CONTROLLER_STATE: - continue - buttons = { - ProCon.Button.A: state[3] & 0x08 > 0, - ProCon.Button.B: state[3] & 0x04 > 0, - ProCon.Button.X: state[3] & 0x02 > 0, - ProCon.Button.Y: state[3] & 0x01 > 0, - ProCon.Button.UP: state[5] & 0x02 > 0, - ProCon.Button.DOWN: state[5] & 0x01 > 0, - ProCon.Button.LEFT: state[5] & 0x08 > 0, - ProCon.Button.RIGHT: state[5] & 0x04 > 0, - ProCon.Button.MINUS: state[4] & 0x01 > 0, - ProCon.Button.PLUS: state[4] & 0x02 > 0, - ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, - ProCon.Button.HOME: state[4] & 0x10 > 0, - ProCon.Button.L: state[5] & 0x40 > 0, - ProCon.Button.ZL: state[5] & 0x80 > 0, - ProCon.Button.R: state[3] & 0x40 > 0, - ProCon.Button.ZR: state[3] & 0x80 > 0, - ProCon.Button.LS: state[4] & 0x08 > 0, - ProCon.Button.RS: state[4] & 0x04 > 0, - } - l_x = state[6] | ((state[7] & 0xF) << 8) - l_y = (state[7] >> 4) | (state[8] << 4) - r_x = state[9] | ((state[10] & 0xF) << 8) - r_y = (state[10] >> 4) | (state[11] << 4) - l_x = self.apply_stick_calibration(l_x, 0, 0) - l_y = self.apply_stick_calibration(l_y, 0, 1) - r_x = self.apply_stick_calibration(r_x, 1, 0) - r_y = self.apply_stick_calibration(r_y, 1, 1) - l_stick = (l_x, l_y) - r_stick = (r_x, r_y) - accel = ( - state[13] | state[14] << 8, - state[15] | state[16] << 8, - state[17] | state[18] << 8, - ) - gyro = ( - state[19] | state[20] << 8, - state[21] | state[22] << 8, - state[23] | state[24] << 8, - ) - accel = tuple(map(to_int16, accel)) - gyro = tuple(map(to_int16, gyro)) - battery = (state[2] & 0xF0) >> 4 - callback(buttons, l_stick, r_stick, accel, gyro, battery) - if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: - self.send_rumble(False, False, 0) - - def load_stick_calibration(self): - ok, reply = self.spi_flash_read( - ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH - ) - if not ok: - raise RuntimeError("cannot load stick calibration") - self.stick_calibration = [ - [ - [ - ((reply[27] & 0xF) << 8) | reply[26], - ((reply[24] & 0xF) << 8) | reply[23], - ((reply[21] & 0xF) << 8) | reply[20], - ], - [ - (reply[28] << 4) | (reply[27] >> 4), - (reply[25] << 4) | (reply[24] >> 4), - (reply[22] << 4) | (reply[21] >> 4), - ], - ], - [ - [ - ((reply[33] & 0xF) << 8) | reply[32], - ((reply[30] & 0xF) << 8) | reply[29], - ((reply[36] & 0xF) << 8) | reply[35], - ], - [ - (reply[34] << 4) | (reply[33] >> 4), - (reply[31] << 4) | (reply[30] >> 4), - (reply[37] << 4) | (reply[36] >> 4), - ], - ], - ] - for i in range(len(self.stick_calibration)): - for j in range(len(self.stick_calibration[i])): - for k in range(len(self.stick_calibration[i][j])): - if self.stick_calibration[i][j][k] == 0xFFF: - self.stick_calibration[i][j][k] = 0 - self.stick_extends = [ - [ - [ - -int(self.stick_calibration[0][0][0] * 0.7), - int(self.stick_calibration[0][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[0][1][0] * 0.7), - int(self.stick_calibration[0][1][2] * 0.7), - ], - ], - [ - [ - -int(self.stick_calibration[1][0][0] * 0.7), - int(self.stick_calibration[1][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[1][1][0] * 0.7), - int(self.stick_calibration[1][1][2] * 0.7), - ], - ], - ] - - def apply_stick_calibration(self, value, stick, axis): - value -= self.stick_calibration[stick][axis][1] - if value < self.stick_extends[stick][axis][0]: - self.stick_extends[stick][axis][0] = value - if value > self.stick_extends[stick][axis][1]: - self.stick_extends[stick][axis][1] = value - if value > 0: - return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) - return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) - - def send(self, data): - return self.dev.write(data) == len(data) - - def recv(self): - return self.dev.read(ProCon.PACKET_SIZE) - - def send_command(self, id, wait_for_reply=True): - data = (ProCon.OutputReportID.COMMAND, id) - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True - reply = self.recv() - if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: - return True - return False - - def send_subcommand(self, id, param, wait_for_reply=True): - data = ( - (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - + (id,) - + param - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True, [] - reply = self.recv() - if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: - return True, reply - return False, [] - - def send_rumble(self, low, high, duration): - self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL - self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL - self.rumble_expire = ( - int(time.time() * 1000) + duration if (low or high) and duration else 0 - ) - data = ( - (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if self.send(data): - return True - return False - - def handshake(self): - return self.send_command(ProCon.CommandID.HANDSHAKE) - - def high_speed(self): - return self.send_command(ProCon.CommandID.HIGH_SPEED) - - def force_usb(self): - return self.send_command(ProCon.CommandID.FORCE_USB, False) - - def set_input_report_mode(self, mode): - return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) - - def spi_flash_read(self, addr, l): - param = ( - addr & 0x000000FF, - (addr & 0x0000FF00) >> 8, - (addr & 0x00FF0000) >> 16, - (addr & 0xFF000000) >> 24, - l, - ) - return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) - - def set_player_lights(self, one, two, three, four): - param = (one << 0) | (two << 1) | (three << 2) | (four << 3) - return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) - - def set_home_light(self, brightness): - intensity = 0 - if brightness > 0: - if brightness < 65: - intensity = (brightness + 5) // 10 - else: - intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) - intensity = (intensity & 0xF) << 4 - param = (0x01, intensity, intensity, 0x00) - return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) - - def enable_imu(self, enable): - return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) - - def set_imu_sensitivity(self, sensitivity): - return self.send_subcommand( - ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity - ) - - def enable_vibration(self, enable): - return self.send_subcommand( - ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) - ) - - -def print_state(buttons, l_stick, r_stick, accel, gyro, battery): - print("\33[2JButtons:") - for k, v in buttons.items(): - if v: - print("[{}]".format(k), end=" ") - else: - print(" {} ".format(k), end=" ") - print() - print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) - print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) - print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) - print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) - print("Battery: {}/9".format(battery)) - - -if __name__ == "__main__": - try: - ProCon().start(print_state) - except KeyboardInterrupt: - print("\rGoodbye!") diff --git a/dev/swerve_sim_keyboard/README.md b/dev/swerve_sim_keyboard/README.md deleted file mode 100644 index 86c5360..0000000 --- a/dev/swerve_sim_keyboard/README.md +++ /dev/null @@ -1,22 +0,0 @@ -### ModBot - Swerve Algorithm -## Environment setup -For setting up any python project and to avoid "It works on my machine"! conversations.. - -The following commands are for Debian based systems, but should work apporpriatley with WSL (Windows), OSX, and other Linux distros. - -Step 0: Make sure repo is cloned or updated - -Step 1: ```sudo -s``` to enter root user. - -Step 2: Install poetry ```curl -sSL https://install.python-poetry.org | python3 -``` - -Step 3: Add to path ```export PATH="/root/.local/bin:$PATH"``` - -Step 3.5: Check it installed correctly ```poetry --version``` - -Step 4: Navigate to the same directory as *pyproject.toml* and install the local enviornment ```poetry install``` - -Step 5: Run the sim ```poetry run python3 src/main.py``` - -Documentation on poetry, its very popular in industry and in the open source scene: https://python-poetry.org/docs/ - diff --git a/dev/swerve_sim_keyboard/poetry.lock b/dev/swerve_sim_keyboard/poetry.lock deleted file mode 100644 index a80ed45..0000000 --- a/dev/swerve_sim_keyboard/poetry.lock +++ /dev/null @@ -1,3302 +0,0 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. - -[[package]] -name = "contourpy" -version = "1.2.0" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.9" -files = [ - {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, - {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, - {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, - {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, - {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, - {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, - {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, - {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, - {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, - {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, - {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, - {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, - {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, - {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, - {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, - {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, - {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, - {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, - {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, - {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, - {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, - {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, - {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, -] - -[package.dependencies] -numpy = ">=1.20,<2.0" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "fonttools" -version = "4.44.0" -description = "Tools to manipulate font files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1cd1c6bb097e774d68402499ff66185190baaa2629ae2f18515a2c50b93db0c"}, - {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9eab7f9837fdaa2a10a524fbcc2ec24bf60637c044b6e4a59c3f835b90f0fae"}, - {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f412954275e594f7a51c16f3b3edd850acb0d842fefc33856b63a17e18499a5"}, - {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d25893885e80a5955186791eed5579f1e75921751539cc1dc3ffd1160b48cf"}, - {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:22ea8aa7b3712450b42b044702bd3a64fd118006bad09a6f94bd1b227088492e"}, - {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df40daa6c03b98652ffe8110ae014fe695437f6e1cb5a07e16ea37f40e73ac86"}, - {file = "fonttools-4.44.0-cp310-cp310-win32.whl", hash = "sha256:bca49da868e8bde569ef36f0cc1b6de21d56bf9c3be185c503b629c19a185287"}, - {file = "fonttools-4.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:dbac86d83d96099890e731cc2af97976ff2c98f4ba432fccde657c5653a32f1c"}, - {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ff7d19a6804bfd561cfcec9b4200dd1788e28f7de4be70189801530c47c1b3"}, - {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8a1fa9a718de0bc026979c93e1e9b55c5efde60d76f91561fd713387573817d"}, - {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05064f95aacdfc06f21e55096c964b2228d942b8675fa26995a2551f6329d2d"}, - {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b38528f25bc662401e6ffae14b3eb7f1e820892fd80369a37155e3b636a2f4"}, - {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05d7c4d2c95b9490e669f3cb83918799bf1c838619ac6d3bad9ea017cfc63f2e"}, - {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6999e80a125b0cd8e068d0210b63323f17338038c2ecd2e11b9209ec430fe7f2"}, - {file = "fonttools-4.44.0-cp311-cp311-win32.whl", hash = "sha256:a7aec7f5d14dfcd71fb3ebc299b3f000c21fdc4043079101777ed2042ba5b7c5"}, - {file = "fonttools-4.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:518a945dbfe337744bfff31423c1430303b8813c5275dffb0f2577f0734a1189"}, - {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:59b6ad83cce067d10f4790c037a5904424f45bebb5e7be2eb2db90402f288267"}, - {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c2de1fb18198acd400c45ffe2aef5420c8d55fde903e91cba705596099550f3b"}, - {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f308b7a8d28208d54315d11d35f9888d6d607673dd4d42d60b463682ee0400"}, - {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66bc6efd829382f7a7e6cf33c2fb32b13edc8a239eb15f32acbf197dce7a0165"}, - {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a8b99713d3a0d0e876b6aecfaada5e7dc9fe979fcd90ef9fa0ba1d9b9aed03f2"}, - {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b63da598d9cbc52e2381f922da0e94d60c0429f92207bd3fb04d112fc82ea7cb"}, - {file = "fonttools-4.44.0-cp312-cp312-win32.whl", hash = "sha256:f611c97678604e302b725f71626edea113a5745a7fb557c958b39edb6add87d5"}, - {file = "fonttools-4.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:58af428746fa73a2edcbf26aff33ac4ef3c11c8d75bb200eaea2f7e888d2de4e"}, - {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ee8692e23028564c13d924004495f284df8ac016a19f17a87251210e1f1f928"}, - {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dab3d00d27b1a79ae4d4a240e8ceea8af0ff049fd45f05adb4f860d93744110d"}, - {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53526668beccdb3409c6055a4ffe50987a7f05af6436fa55d61f5e7bd450219"}, - {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da036b016c975c2d8c69005bdc4d5d16266f948a7fab950244e0f58301996a"}, - {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b99fe8ef4093f672d00841569d2d05691e50334d79f4d9c15c1265d76d5580d2"}, - {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d16d9634ff1e5cea2cf4a8cbda9026f766e4b5f30b48f8180f0e99133d3abfc"}, - {file = "fonttools-4.44.0-cp38-cp38-win32.whl", hash = "sha256:3d29509f6e05e8d725db59c2d8c076223d793e4e35773040be6632a0349f2f97"}, - {file = "fonttools-4.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4fa4f4bc8fd86579b8cdbe5e948f35d82c0eda0091c399d009b2a5a6b61c040"}, - {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c794de4086f06ae609b71ac944ec7deb09f34ecf73316fddc041087dd24bba39"}, - {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2db63941fee3122e31a21dd0f5b2138ce9906b661a85b63622421d3654a74ae2"}, - {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb01c49c8aa035d5346f46630209923d4927ed15c2493db38d31da9f811eb70d"}, - {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c79af80a835410874683b5779b6c1ec1d5a285e11c45b5193e79dd691eb111"}, - {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6e6aa2d066f8dafd06d8d0799b4944b5d5a1f015dd52ac01bdf2895ebe169a0"}, - {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63a3112f753baef8c6ac2f5f574bb9ac8001b86c8c0c0380039db47a7f512d20"}, - {file = "fonttools-4.44.0-cp39-cp39-win32.whl", hash = "sha256:54efed22b2799a85475e6840e907c402ba49892c614565dc770aa97a53621b2b"}, - {file = "fonttools-4.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e91e19b583961979e2e5a701269d3cfc07418963bee717f8160b0a24332826b"}, - {file = "fonttools-4.44.0-py3-none-any.whl", hash = "sha256:b9beb0fa6ff3ea808ad4a6962d68ac0f140ddab080957b20d9e268e4d67fb335"}, - {file = "fonttools-4.44.0.tar.gz", hash = "sha256:4e90dd81b6e0d97ebfe52c0d12a17a9ef7f305d6bfbb93081265057d6092f252"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] -lxml = ["lxml (>=4.0,<5)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "importlib-resources" -version = "6.1.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, - {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] - -[[package]] -name = "keyboard" -version = "0.13.5" -description = "Hook and simulate keyboard events on Windows and Linux" -optional = false -python-versions = "*" -files = [ - {file = "keyboard-0.13.5-py3-none-any.whl", hash = "sha256:8e9c2422f1217e0bd84489b9ecd361027cc78415828f4fe4f88dd4acd587947b"}, - {file = "keyboard-0.13.5.zip", hash = "sha256:63ed83305955939ca5c9a73755e5cc43e8242263f5ad5fd3bb7e0b032f3d308b"}, -] - -[package.dependencies] -pyobjc = {version = "*", markers = "sys_platform == \"darwin\""} - -[[package]] -name = "kiwisolver" -version = "1.4.5" -description = "A fast implementation of the Cassowary constraint solver" -optional = false -python-versions = ">=3.7" -files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, -] - -[[package]] -name = "matplotlib" -version = "3.8.1" -description = "Python plotting package" -optional = false -python-versions = ">=3.9" -files = [ - {file = "matplotlib-3.8.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e11ab864323fa73ac1b7849688d9671c47a2665242e899785b4db1a375b547e1"}, - {file = "matplotlib-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43a9d40feb63c9e31a0b8b069dcbd74a912f59bdc0095d187126694cd26977e4"}, - {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:608ea2951838d391e45dec2e644888db6899c752d3c29e157af9dcefb3d7d8d5"}, - {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82ec95b02e894561c21e066bd0c716e4b410df141ce9441aa5af6cd937e4ade2"}, - {file = "matplotlib-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e3ad1759ad4a5245172c6d32b8ada603a6020d03211524c39d78d25c9a7dc0d2"}, - {file = "matplotlib-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:20a0fdfd3ee836179047f3782be060057b878ad37f5abe29edf006a1ff3ecd73"}, - {file = "matplotlib-3.8.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7658b7073c1d6a2922ecc0ed41602410fae88586cb8a54f7a2063d537b6beaf7"}, - {file = "matplotlib-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf6889643d4560fcc56f9f0941f078e4df0d72a6c3e4ca548841fc13c5642664"}, - {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff842e27bc6a80de08c40e0bfdce460bd08080e8a94af131162b6a1b8948f2cc"}, - {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f99d07c0e753717775be7be39ab383453b4d8b629c9fa174596b970c6555890"}, - {file = "matplotlib-3.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f34b46dbb1db1f09bfa937cd5853e5f2af232caeeff509c3ab6e43fd33780eae"}, - {file = "matplotlib-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1fcb49b6baf0375281979cbf26695ec10bd1cada1e311893e89533b3b70143e7"}, - {file = "matplotlib-3.8.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e17674ee127f78f26fea237e7f4d5cf910a8be82beb6260fedf358b88075b823"}, - {file = "matplotlib-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d921c0270647ab11c3ef283efaaa3d46fd005ba233bfb3aea75231cdf3656de8"}, - {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2afe7d2f8c9e35e94fbcfcfd9b28f29cb32f0a9068cba469cf907428379c8db9"}, - {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a504ff40f81d6233603475a45497a6dca37a873393fa20ae6f7dd6596ef72b"}, - {file = "matplotlib-3.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cd54bbf089953140905768ed4626d7223e1ad1d7e2a138410a9c4d3b865ccd80"}, - {file = "matplotlib-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:27502d2452208ae784c19504644f09f83742809143bbeae147617640930aa344"}, - {file = "matplotlib-3.8.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f55fb5ff02d999a100be28bf6ffe826e1867a54c7b465409685332c9dd48ffa5"}, - {file = "matplotlib-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:afb72822ae410d62aa1a2920c6563cb5680de9078358f0e9474396c6c3e06be2"}, - {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43cf368a4a1d8cbc426944806e5e183cead746647a64d2cdb786441546235967"}, - {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c54c55457c7f5ea4dfdba0020004fc7667f5c10c8d9b8010d735345acc06c9b8"}, - {file = "matplotlib-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e3bb809b743653b5aab5d72ee45c8c937c28e147b0846b0826a54bece898608c"}, - {file = "matplotlib-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:c1b0ecaa0d1f4fe1e30f625a2347f0034a89a7d17c39efbb502e554d92ee2f61"}, - {file = "matplotlib-3.8.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca84deaa38cb64b7dd160ca2046b45f7b5dbff2b0179642e1339fadc337446c9"}, - {file = "matplotlib-3.8.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed3b29f54f6bbf3eaca4cbd23bc260155153ace63b7f597c474fa6fc6f386530"}, - {file = "matplotlib-3.8.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d24c47a1bb47e392fbcd26fe322e4ff3431653ac1e8718e4e147d450ae97a44"}, - {file = "matplotlib-3.8.1.tar.gz", hash = "sha256:044df81c1f6f3a8e52d70c4cfcb44e77ea9632a10929932870dfaa90de94365d"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} -kiwisolver = ">=1.3.1" -numpy = ">=1.21,<2" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - -[[package]] -name = "numpy" -version = "1.26.1" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, - {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, - {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, - {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, - {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, - {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, - {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, - {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, - {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, - {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, - {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, - {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, - {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, - {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, - {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, - {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, - {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, - {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, - {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, - {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, - {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, - {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, - {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, - {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, - {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, - {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, - {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, - {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, - {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pillow" -version = "10.1.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, - {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, - {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "pyobjc" -version = "10.0" -description = "Python<->ObjC Interoperability Module" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-10.0-py3-none-any.whl", hash = "sha256:0f30bd3f25425a6c435f37713bc062382c85022d07ddb1f199a9211d859846db"}, - {file = "pyobjc-10.0.tar.gz", hash = "sha256:9987a79e30cdd0de31e58ed41240e2fc4cbf2c085c9fd8988f7de5d0ae06b101"}, -] - -[package.dependencies] -pyobjc-core = "10.0" -pyobjc-framework-Accessibility = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-Accounts = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-AddressBook = "10.0" -pyobjc-framework-AdServices = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-AdSupport = {version = "10.0", markers = "platform_release >= \"18.0\""} -pyobjc-framework-AppleScriptKit = "10.0" -pyobjc-framework-AppleScriptObjC = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-ApplicationServices = "10.0" -pyobjc-framework-AppTrackingTransparency = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-AudioVideoBridging = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-AuthenticationServices = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-AutomaticAssessmentConfiguration = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-Automator = "10.0" -pyobjc-framework-AVFoundation = {version = "10.0", markers = "platform_release >= \"11.0\""} -pyobjc-framework-AVKit = {version = "10.0", markers = "platform_release >= \"13.0\""} -pyobjc-framework-AVRouting = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-BackgroundAssets = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-BusinessChat = {version = "10.0", markers = "platform_release >= \"18.0\""} -pyobjc-framework-CalendarStore = {version = "10.0", markers = "platform_release >= \"9.0\""} -pyobjc-framework-CallKit = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-CFNetwork = "10.0" -pyobjc-framework-Cinematic = {version = "10.0", markers = "platform_release >= \"23.0\""} -pyobjc-framework-ClassKit = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-CloudKit = {version = "10.0", markers = "platform_release >= \"14.0\""} -pyobjc-framework-Cocoa = "10.0" -pyobjc-framework-Collaboration = {version = "10.0", markers = "platform_release >= \"9.0\""} -pyobjc-framework-ColorSync = {version = "10.0", markers = "platform_release >= \"17.0\""} -pyobjc-framework-Contacts = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-ContactsUI = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-CoreAudio = "10.0" -pyobjc-framework-CoreAudioKit = "10.0" -pyobjc-framework-CoreBluetooth = {version = "10.0", markers = "platform_release >= \"14.0\""} -pyobjc-framework-CoreData = "10.0" -pyobjc-framework-CoreHaptics = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-CoreLocation = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-CoreMedia = {version = "10.0", markers = "platform_release >= \"11.0\""} -pyobjc-framework-CoreMediaIO = {version = "10.0", markers = "platform_release >= \"11.0\""} -pyobjc-framework-CoreMIDI = "10.0" -pyobjc-framework-CoreML = {version = "10.0", markers = "platform_release >= \"17.0\""} -pyobjc-framework-CoreMotion = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-CoreServices = "10.0" -pyobjc-framework-CoreSpotlight = {version = "10.0", markers = "platform_release >= \"17.0\""} -pyobjc-framework-CoreText = "10.0" -pyobjc-framework-CoreWLAN = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-CryptoTokenKit = {version = "10.0", markers = "platform_release >= \"14.0\""} -pyobjc-framework-DataDetection = {version = "10.0", markers = "platform_release >= \"21.0\""} -pyobjc-framework-DeviceCheck = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-DictionaryServices = {version = "10.0", markers = "platform_release >= \"9.0\""} -pyobjc-framework-DiscRecording = "10.0" -pyobjc-framework-DiscRecordingUI = "10.0" -pyobjc-framework-DiskArbitration = "10.0" -pyobjc-framework-DVDPlayback = "10.0" -pyobjc-framework-EventKit = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-ExceptionHandling = "10.0" -pyobjc-framework-ExecutionPolicy = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-ExtensionKit = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-ExternalAccessory = {version = "10.0", markers = "platform_release >= \"17.0\""} -pyobjc-framework-FileProvider = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-FileProviderUI = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-FinderSync = {version = "10.0", markers = "platform_release >= \"14.0\""} -pyobjc-framework-FSEvents = {version = "10.0", markers = "platform_release >= \"9.0\""} -pyobjc-framework-GameCenter = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-GameController = {version = "10.0", markers = "platform_release >= \"13.0\""} -pyobjc-framework-GameKit = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-GameplayKit = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-HealthKit = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-ImageCaptureCore = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-InputMethodKit = {version = "10.0", markers = "platform_release >= \"9.0\""} -pyobjc-framework-InstallerPlugins = "10.0" -pyobjc-framework-InstantMessage = {version = "10.0", markers = "platform_release >= \"9.0\""} -pyobjc-framework-Intents = {version = "10.0", markers = "platform_release >= \"16.0\""} -pyobjc-framework-IntentsUI = {version = "10.0", markers = "platform_release >= \"21.0\""} -pyobjc-framework-IOBluetooth = "10.0" -pyobjc-framework-IOBluetoothUI = "10.0" -pyobjc-framework-IOSurface = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-iTunesLibrary = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-KernelManagement = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-LatentSemanticMapping = "10.0" -pyobjc-framework-LaunchServices = "10.0" -pyobjc-framework-libdispatch = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-libxpc = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-LinkPresentation = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-LocalAuthentication = {version = "10.0", markers = "platform_release >= \"14.0\""} -pyobjc-framework-LocalAuthenticationEmbeddedUI = {version = "10.0", markers = "platform_release >= \"21.0\""} -pyobjc-framework-MailKit = {version = "10.0", markers = "platform_release >= \"21.0\""} -pyobjc-framework-MapKit = {version = "10.0", markers = "platform_release >= \"13.0\""} -pyobjc-framework-MediaAccessibility = {version = "10.0", markers = "platform_release >= \"13.0\""} -pyobjc-framework-MediaLibrary = {version = "10.0", markers = "platform_release >= \"13.0\""} -pyobjc-framework-MediaPlayer = {version = "10.0", markers = "platform_release >= \"16.0\""} -pyobjc-framework-MediaToolbox = {version = "10.0", markers = "platform_release >= \"13.0\""} -pyobjc-framework-Metal = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-MetalFX = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-MetalKit = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-MetalPerformanceShaders = {version = "10.0", markers = "platform_release >= \"17.0\""} -pyobjc-framework-MetalPerformanceShadersGraph = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-MetricKit = {version = "10.0", markers = "platform_release >= \"21.0\""} -pyobjc-framework-MLCompute = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-ModelIO = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-MultipeerConnectivity = {version = "10.0", markers = "platform_release >= \"14.0\""} -pyobjc-framework-NaturalLanguage = {version = "10.0", markers = "platform_release >= \"18.0\""} -pyobjc-framework-NetFS = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-Network = {version = "10.0", markers = "platform_release >= \"18.0\""} -pyobjc-framework-NetworkExtension = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-NotificationCenter = {version = "10.0", markers = "platform_release >= \"14.0\""} -pyobjc-framework-OpenDirectory = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-OSAKit = "10.0" -pyobjc-framework-OSLog = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-PassKit = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-PencilKit = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-PHASE = {version = "10.0", markers = "platform_release >= \"21.0\""} -pyobjc-framework-Photos = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-PhotosUI = {version = "10.0", markers = "platform_release >= \"15.0\""} -pyobjc-framework-PreferencePanes = "10.0" -pyobjc-framework-PubSub = {version = "10.0", markers = "platform_release >= \"9.0\" and platform_release < \"18.0\""} -pyobjc-framework-PushKit = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-Quartz = "10.0" -pyobjc-framework-QuickLookThumbnailing = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-ReplayKit = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-SafariServices = {version = "10.0", markers = "platform_release >= \"16.0\""} -pyobjc-framework-SafetyKit = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-SceneKit = {version = "10.0", markers = "platform_release >= \"11.0\""} -pyobjc-framework-ScreenCaptureKit = {version = "10.0", markers = "platform_release >= \"21.4\""} -pyobjc-framework-ScreenSaver = "10.0" -pyobjc-framework-ScreenTime = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-ScriptingBridge = {version = "10.0", markers = "platform_release >= \"9.0\""} -pyobjc-framework-SearchKit = "10.0" -pyobjc-framework-Security = "10.0" -pyobjc-framework-SecurityFoundation = "10.0" -pyobjc-framework-SecurityInterface = "10.0" -pyobjc-framework-SensitiveContentAnalysis = {version = "10.0", markers = "platform_release >= \"23.0\""} -pyobjc-framework-ServiceManagement = {version = "10.0", markers = "platform_release >= \"10.0\""} -pyobjc-framework-SharedWithYou = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-SharedWithYouCore = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-ShazamKit = {version = "10.0", markers = "platform_release >= \"21.0\""} -pyobjc-framework-Social = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-SoundAnalysis = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-Speech = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-SpriteKit = {version = "10.0", markers = "platform_release >= \"13.0\""} -pyobjc-framework-StoreKit = {version = "10.0", markers = "platform_release >= \"11.0\""} -pyobjc-framework-Symbols = {version = "10.0", markers = "platform_release >= \"23.0\""} -pyobjc-framework-SyncServices = "10.0" -pyobjc-framework-SystemConfiguration = "10.0" -pyobjc-framework-SystemExtensions = {version = "10.0", markers = "platform_release >= \"19.0\""} -pyobjc-framework-ThreadNetwork = {version = "10.0", markers = "platform_release >= \"22.0\""} -pyobjc-framework-UniformTypeIdentifiers = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-UserNotifications = {version = "10.0", markers = "platform_release >= \"18.0\""} -pyobjc-framework-UserNotificationsUI = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-VideoSubscriberAccount = {version = "10.0", markers = "platform_release >= \"18.0\""} -pyobjc-framework-VideoToolbox = {version = "10.0", markers = "platform_release >= \"12.0\""} -pyobjc-framework-Virtualization = {version = "10.0", markers = "platform_release >= \"20.0\""} -pyobjc-framework-Vision = {version = "10.0", markers = "platform_release >= \"17.0\""} -pyobjc-framework-WebKit = "10.0" - -[package.extras] -allbindings = ["pyobjc-core (==10.0)", "pyobjc-framework-AVFoundation (==10.0)", "pyobjc-framework-AVKit (==10.0)", "pyobjc-framework-AVRouting (==10.0)", "pyobjc-framework-Accessibility (==10.0)", "pyobjc-framework-Accounts (==10.0)", "pyobjc-framework-AdServices (==10.0)", "pyobjc-framework-AdSupport (==10.0)", "pyobjc-framework-AddressBook (==10.0)", "pyobjc-framework-AppTrackingTransparency (==10.0)", "pyobjc-framework-AppleScriptKit (==10.0)", "pyobjc-framework-AppleScriptObjC (==10.0)", "pyobjc-framework-ApplicationServices (==10.0)", "pyobjc-framework-AudioVideoBridging (==10.0)", "pyobjc-framework-AuthenticationServices (==10.0)", "pyobjc-framework-AutomaticAssessmentConfiguration (==10.0)", "pyobjc-framework-Automator (==10.0)", "pyobjc-framework-BackgroundAssets (==10.0)", "pyobjc-framework-BusinessChat (==10.0)", "pyobjc-framework-CFNetwork (==10.0)", "pyobjc-framework-CalendarStore (==10.0)", "pyobjc-framework-CallKit (==10.0)", "pyobjc-framework-Cinematic (==10.0)", "pyobjc-framework-ClassKit (==10.0)", "pyobjc-framework-CloudKit (==10.0)", "pyobjc-framework-Cocoa (==10.0)", "pyobjc-framework-Collaboration (==10.0)", "pyobjc-framework-ColorSync (==10.0)", "pyobjc-framework-Contacts (==10.0)", "pyobjc-framework-ContactsUI (==10.0)", "pyobjc-framework-CoreAudio (==10.0)", "pyobjc-framework-CoreAudioKit (==10.0)", "pyobjc-framework-CoreBluetooth (==10.0)", "pyobjc-framework-CoreData (==10.0)", "pyobjc-framework-CoreHaptics (==10.0)", "pyobjc-framework-CoreLocation (==10.0)", "pyobjc-framework-CoreMIDI (==10.0)", "pyobjc-framework-CoreML (==10.0)", "pyobjc-framework-CoreMedia (==10.0)", "pyobjc-framework-CoreMediaIO (==10.0)", "pyobjc-framework-CoreMotion (==10.0)", "pyobjc-framework-CoreServices (==10.0)", "pyobjc-framework-CoreSpotlight (==10.0)", "pyobjc-framework-CoreText (==10.0)", "pyobjc-framework-CoreWLAN (==10.0)", "pyobjc-framework-CryptoTokenKit (==10.0)", "pyobjc-framework-DVDPlayback (==10.0)", "pyobjc-framework-DataDetection (==10.0)", "pyobjc-framework-DeviceCheck (==10.0)", "pyobjc-framework-DictionaryServices (==10.0)", "pyobjc-framework-DiscRecording (==10.0)", "pyobjc-framework-DiscRecordingUI (==10.0)", "pyobjc-framework-DiskArbitration (==10.0)", "pyobjc-framework-EventKit (==10.0)", "pyobjc-framework-ExceptionHandling (==10.0)", "pyobjc-framework-ExecutionPolicy (==10.0)", "pyobjc-framework-ExtensionKit (==10.0)", "pyobjc-framework-ExternalAccessory (==10.0)", "pyobjc-framework-FSEvents (==10.0)", "pyobjc-framework-FileProvider (==10.0)", "pyobjc-framework-FileProviderUI (==10.0)", "pyobjc-framework-FinderSync (==10.0)", "pyobjc-framework-GameCenter (==10.0)", "pyobjc-framework-GameController (==10.0)", "pyobjc-framework-GameKit (==10.0)", "pyobjc-framework-GameplayKit (==10.0)", "pyobjc-framework-HealthKit (==10.0)", "pyobjc-framework-IOBluetooth (==10.0)", "pyobjc-framework-IOBluetoothUI (==10.0)", "pyobjc-framework-IOSurface (==10.0)", "pyobjc-framework-ImageCaptureCore (==10.0)", "pyobjc-framework-InputMethodKit (==10.0)", "pyobjc-framework-InstallerPlugins (==10.0)", "pyobjc-framework-InstantMessage (==10.0)", "pyobjc-framework-Intents (==10.0)", "pyobjc-framework-IntentsUI (==10.0)", "pyobjc-framework-KernelManagement (==10.0)", "pyobjc-framework-LatentSemanticMapping (==10.0)", "pyobjc-framework-LaunchServices (==10.0)", "pyobjc-framework-LinkPresentation (==10.0)", "pyobjc-framework-LocalAuthentication (==10.0)", "pyobjc-framework-LocalAuthenticationEmbeddedUI (==10.0)", "pyobjc-framework-MLCompute (==10.0)", "pyobjc-framework-MailKit (==10.0)", "pyobjc-framework-MapKit (==10.0)", "pyobjc-framework-MediaAccessibility (==10.0)", "pyobjc-framework-MediaLibrary (==10.0)", "pyobjc-framework-MediaPlayer (==10.0)", "pyobjc-framework-MediaToolbox (==10.0)", "pyobjc-framework-Metal (==10.0)", "pyobjc-framework-MetalFX (==10.0)", "pyobjc-framework-MetalKit (==10.0)", "pyobjc-framework-MetalPerformanceShaders (==10.0)", "pyobjc-framework-MetalPerformanceShadersGraph (==10.0)", "pyobjc-framework-MetricKit (==10.0)", "pyobjc-framework-ModelIO (==10.0)", "pyobjc-framework-MultipeerConnectivity (==10.0)", "pyobjc-framework-NaturalLanguage (==10.0)", "pyobjc-framework-NetFS (==10.0)", "pyobjc-framework-Network (==10.0)", "pyobjc-framework-NetworkExtension (==10.0)", "pyobjc-framework-NotificationCenter (==10.0)", "pyobjc-framework-OSAKit (==10.0)", "pyobjc-framework-OSLog (==10.0)", "pyobjc-framework-OpenDirectory (==10.0)", "pyobjc-framework-PHASE (==10.0)", "pyobjc-framework-PassKit (==10.0)", "pyobjc-framework-PencilKit (==10.0)", "pyobjc-framework-Photos (==10.0)", "pyobjc-framework-PhotosUI (==10.0)", "pyobjc-framework-PreferencePanes (==10.0)", "pyobjc-framework-PubSub (==10.0)", "pyobjc-framework-PushKit (==10.0)", "pyobjc-framework-Quartz (==10.0)", "pyobjc-framework-QuickLookThumbnailing (==10.0)", "pyobjc-framework-ReplayKit (==10.0)", "pyobjc-framework-SafariServices (==10.0)", "pyobjc-framework-SafetyKit (==10.0)", "pyobjc-framework-SceneKit (==10.0)", "pyobjc-framework-ScreenCaptureKit (==10.0)", "pyobjc-framework-ScreenSaver (==10.0)", "pyobjc-framework-ScreenTime (==10.0)", "pyobjc-framework-ScriptingBridge (==10.0)", "pyobjc-framework-SearchKit (==10.0)", "pyobjc-framework-Security (==10.0)", "pyobjc-framework-SecurityFoundation (==10.0)", "pyobjc-framework-SecurityInterface (==10.0)", "pyobjc-framework-SensitiveContentAnalysis (==10.0)", "pyobjc-framework-ServiceManagement (==10.0)", "pyobjc-framework-SharedWithYou (==10.0)", "pyobjc-framework-SharedWithYouCore (==10.0)", "pyobjc-framework-ShazamKit (==10.0)", "pyobjc-framework-Social (==10.0)", "pyobjc-framework-SoundAnalysis (==10.0)", "pyobjc-framework-Speech (==10.0)", "pyobjc-framework-SpriteKit (==10.0)", "pyobjc-framework-StoreKit (==10.0)", "pyobjc-framework-Symbols (==10.0)", "pyobjc-framework-SyncServices (==10.0)", "pyobjc-framework-SystemConfiguration (==10.0)", "pyobjc-framework-SystemExtensions (==10.0)", "pyobjc-framework-ThreadNetwork (==10.0)", "pyobjc-framework-UniformTypeIdentifiers (==10.0)", "pyobjc-framework-UserNotifications (==10.0)", "pyobjc-framework-UserNotificationsUI (==10.0)", "pyobjc-framework-VideoSubscriberAccount (==10.0)", "pyobjc-framework-VideoToolbox (==10.0)", "pyobjc-framework-Virtualization (==10.0)", "pyobjc-framework-Vision (==10.0)", "pyobjc-framework-WebKit (==10.0)", "pyobjc-framework-iTunesLibrary (==10.0)", "pyobjc-framework-libdispatch (==10.0)", "pyobjc-framework-libxpc (==10.0)"] - -[[package]] -name = "pyobjc-core" -version = "10.0" -description = "Python<->ObjC Interoperability Module" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-core-10.0.tar.gz", hash = "sha256:3dd0a7b3acd7e0b8ffd3f5331b29a3aaebe79a03323e61efeece38627a6020b3"}, - {file = "pyobjc_core-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61ea5112a672d21b5b0ed945778707c655b17c400672aef144705674c4b95499"}, - {file = "pyobjc_core-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:99b72cda4593e0c66037b25a178f2bcc6efffb6d5d9dcd477ecca859a1f9ae8e"}, - {file = "pyobjc_core-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2843ca32e86a01ccee67d7ad82a325ddd72d754929d1f2c0d96bc8741dc9af09"}, - {file = "pyobjc_core-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a886b9d2a93210cab4ae72601ab005ca6f627fa2f0cc62c43c03ef1405067a11"}, - {file = "pyobjc_core-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:166666b5c380a49e8aa1ad1dda978c581e29a00703d82203216f3c65a3f397a4"}, - {file = "pyobjc_core-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:198a0360f64e4c0148eed07b42d1de0545f56c498c356d1d5524422bb3352907"}, -] - -[[package]] -name = "pyobjc-framework-accessibility" -version = "10.0" -description = "Wrappers for the framework Accessibility on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Accessibility-10.0.tar.gz", hash = "sha256:5aa152201ccc235a6bbba271b698de42445a4a058b8dceca982d70384c195255"}, - {file = "pyobjc_framework_Accessibility-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e23bcd2e43ab8bc800255ee2b09c1af924b2d6b4602a4ec94719a08d181fdf62"}, - {file = "pyobjc_framework_Accessibility-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:911295caa0ad264787580e2aa8680d3c7c1957e22e9db3ccdc0d9a95a27f3333"}, - {file = "pyobjc_framework_Accessibility-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c2f95c08ae2d7d0c8745d81a68d74aba00a3de026a7831fb67561f85a5886c0b"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-accounts" -version = "10.0" -description = "Wrappers for the framework Accounts on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Accounts-10.0.tar.gz", hash = "sha256:5679caa87b3bd597f776e154c43dbeb460251798165d55daf79f105a131fdf2c"}, - {file = "pyobjc_framework_Accounts-10.0-py2.py3-none-any.whl", hash = "sha256:72c67d4b1f174d2045558d7b1348d5dce642ea0907ab3dfb79d2f449e601ad42"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-addressbook" -version = "10.0" -description = "Wrappers for the framework AddressBook on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AddressBook-10.0.tar.gz", hash = "sha256:e61dbd593113721ff45bbc706884727dc483502eb4d514fd4c53f56b9a86bef7"}, - {file = "pyobjc_framework_AddressBook-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:71a0667a4d89bf9a50e2fda57705d296ab04ae24063ee67f377226fe6693699d"}, - {file = "pyobjc_framework_AddressBook-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bce60351f7e8db831b9044702e368eee09daacb7bcc51d036f3b95a13b634316"}, - {file = "pyobjc_framework_AddressBook-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:75c7d8dc47dc1f3c01e9bab9af33fb012950b9b0926d8fd0cf3774c6c3a2a2ca"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-adservices" -version = "10.0" -description = "Wrappers for the framework AdServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AdServices-10.0.tar.gz", hash = "sha256:a49b6f57e0bebef2e5484deebd68828106ec3f2e70683f75cb3414a080689983"}, - {file = "pyobjc_framework_AdServices-10.0-py2.py3-none-any.whl", hash = "sha256:d3c9bb0c7da4c98879368ad22c6e558fff73c6d3d971ee5ac8bdd38f723f811b"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-adsupport" -version = "10.0" -description = "Wrappers for the framework AdSupport on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AdSupport-10.0.tar.gz", hash = "sha256:d208bcbd4462b2ffa686e9137aa1a028fa2c1cd5dda1d31e409b7e935e843565"}, - {file = "pyobjc_framework_AdSupport-10.0-py2.py3-none-any.whl", hash = "sha256:9fe3295892d2906f46ee36c982cf1b41a94dc9c5a316937174966512d61a7939"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-applescriptkit" -version = "10.0" -description = "Wrappers for the framework AppleScriptKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AppleScriptKit-10.0.tar.gz", hash = "sha256:8d6192e4f8a9ca89b2b32e9f6f18e0cb9dd4c026d112d28b18235b1c9f8016fb"}, - {file = "pyobjc_framework_AppleScriptKit-10.0-py2.py3-none-any.whl", hash = "sha256:567123701b86833118236f0177ce7979cd91be5c2d0fe26afb7b73499812f673"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-applescriptobjc" -version = "10.0" -description = "Wrappers for the framework AppleScriptObjC on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AppleScriptObjC-10.0.tar.gz", hash = "sha256:3adb92fe129543c5b763e95788b8b408292ce6ff83436fec4182bcbda6493c28"}, - {file = "pyobjc_framework_AppleScriptObjC-10.0-py2.py3-none-any.whl", hash = "sha256:ef800eae5cd54a41f88c8bf83fcc7ab3c902ecaf104e469a6f1ead15da517479"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-applicationservices" -version = "10.0" -description = "Wrappers for the framework ApplicationServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ApplicationServices-10.0.tar.gz", hash = "sha256:8a667da95c09202def58746c42d5093f90be5762a52e6f5ad8beb334b51dca20"}, - {file = "pyobjc_framework_ApplicationServices-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b1688abb30cfd6607ae70dd07d3118a5f2d3593916342ffb842a208e7ff52f2b"}, - {file = "pyobjc_framework_ApplicationServices-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ebbe656c8b1a8fb3880d47ebef5cb7c95b9a646c84bc369cf132347dc4754143"}, - {file = "pyobjc_framework_ApplicationServices-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9cf969781616261ec7a2c427120a9fb4ac861e62bc2854de6cabdd2e4ea47141"}, - {file = "pyobjc_framework_ApplicationServices-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:49c4b3e6399572552ba8167bff16ef6cd2eeba6e417547a67d26316acb80f612"}, - {file = "pyobjc_framework_ApplicationServices-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b26b16077fbd3e32f4216b05798a9fe79b4c00c498581adf15c4f47db13ed270"}, - {file = "pyobjc_framework_ApplicationServices-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b20d97f6aa63c4935363f4d9bad736d92b67f46ccd9c8c8c84d08e03b2583691"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-apptrackingtransparency" -version = "10.0" -description = "Wrappers for the framework AppTrackingTransparency on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AppTrackingTransparency-10.0.tar.gz", hash = "sha256:846b148300b0c588cfdfc016cf2ace3f77ee4470ca8112907fb2ef00b1271660"}, - {file = "pyobjc_framework_AppTrackingTransparency-10.0-py2.py3-none-any.whl", hash = "sha256:20d1c8516c2ac568b90f3daf7d93b91a37ea61aa874b4a541d276c7fdac623e4"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-audiovideobridging" -version = "10.0" -description = "Wrappers for the framework AudioVideoBridging on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AudioVideoBridging-10.0.tar.gz", hash = "sha256:ca0b947dca87a831328aa8da16232f98699d7a144ed4d088d6b5a388552d85fb"}, - {file = "pyobjc_framework_AudioVideoBridging-10.0-py2.py3-none-any.whl", hash = "sha256:979081558ec3a8cd875515120027448fbe24fa0605b96cf13c7541bffab281bc"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-authenticationservices" -version = "10.0" -description = "Wrappers for the framework AuthenticationServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AuthenticationServices-10.0.tar.gz", hash = "sha256:0ee315ccae58e9821d92052ac937f26d4a033b1fbbda1e213b1752b10653ba5b"}, - {file = "pyobjc_framework_AuthenticationServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b9aafe7b5e6f46ebb1e52f94d562bc4c137ff2cbbcebf7aebce7a0d0e4a2431f"}, - {file = "pyobjc_framework_AuthenticationServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e20a06e250547c3bf29ea209e38f59eb471b3081a160dcb2ef26f05c8ff46412"}, - {file = "pyobjc_framework_AuthenticationServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:019ebeeb191938bc34058cec587b137aee6b7cfcfaa01e23a4073fa633a04cdc"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-automaticassessmentconfiguration" -version = "10.0" -description = "Wrappers for the framework AutomaticAssessmentConfiguration on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AutomaticAssessmentConfiguration-10.0.tar.gz", hash = "sha256:008599dc2b2af1175e574ebce2be950c5bb67a2c9eb7391535dac4f514e158a2"}, - {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:46dcc05d21ebd6253c266acd40b2392725823455ea730b3dcb62a42764f28bec"}, - {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:46a318317123e7a2915bf04f93a11ab11f97f21706aff2152cdcdcc1835252f4"}, - {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:520eeb727edd2f9f91dd261b31ed3de2baafb40da59f2f7120b3f39166cbafbf"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-automator" -version = "10.0" -description = "Wrappers for the framework Automator on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Automator-10.0.tar.gz", hash = "sha256:c6d8591650e17a1d9d92f62fd83848d3afbf70b08dfc12a205fb78684ab4b9ac"}, - {file = "pyobjc_framework_Automator-10.0-py2.py3-none-any.whl", hash = "sha256:261e36071f1a662f387bab48f711059e6e468ddd5054c0f2bae7af7e619a7aba"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-avfoundation" -version = "10.0" -description = "Wrappers for the framework AVFoundation on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AVFoundation-10.0.tar.gz", hash = "sha256:40366a8c6bb964e7b7263e8cf060350f69ad365e6a5356d6ccab9f256a9987f7"}, - {file = "pyobjc_framework_AVFoundation-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b9b2e6731a64425f297bed68c6fc6e31e20965277c96012e62f7fa9059ff544e"}, - {file = "pyobjc_framework_AVFoundation-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:917185ff4e3f262b98cca2789ed68d43b0b111b161b9c8bda0bc7e6ab6def41c"}, - {file = "pyobjc_framework_AVFoundation-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d2bf8c4cfe72a24a4632d4152522c6b1b9b69b1bfadc7d76fd1082e7cc3cec7e"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreAudio = ">=10.0" -pyobjc-framework-CoreMedia = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-avkit" -version = "10.0" -description = "Wrappers for the framework AVKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AVKit-10.0.tar.gz", hash = "sha256:53f8b74a76c948c0d9a96f331d99e1a6ab7a1ce87af7d9bbfffd267532bea98c"}, - {file = "pyobjc_framework_AVKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:96e153fc74339c5634a8f49614b8039028995cde08b3dd8b024a46ebe4cb4286"}, - {file = "pyobjc_framework_AVKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:44df99e60a3ab5359d92ccc840c0abbed2b8072d27a483fac46ec73800e128bd"}, - {file = "pyobjc_framework_AVKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5bc830421f74c1b78aa85605d2e72c5aa18b6d74b24b82824a1b6e519b66cc64"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-avrouting" -version = "10.0" -description = "Wrappers for the framework AVRouting on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-AVRouting-10.0.tar.gz", hash = "sha256:41213eb9fdff4ec58dddee240de7100601cef74e458265623763b460a422438c"}, - {file = "pyobjc_framework_AVRouting-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cc38ebfc114467ec14b39e774c93d4f1e4345bc4a723ba555a42bacbb5e45dd3"}, - {file = "pyobjc_framework_AVRouting-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0cd4531d5edd0c6be1ba53db037f04c32ad91caf9572d03666608b8ab93c07ae"}, - {file = "pyobjc_framework_AVRouting-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b9089d3e2b53c10f131d4f23f14ccd2c89dff59b47666ba5383616a2a6026fa2"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-backgroundassets" -version = "10.0" -description = "Wrappers for the framework BackgroundAssets on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-BackgroundAssets-10.0.tar.gz", hash = "sha256:d2b9a7a46a632d0adeaa6ba411ddb829d8efa3273a93d7918cc143dfe9dfb54b"}, - {file = "pyobjc_framework_BackgroundAssets-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9b9c236a3ccaf1e68bfaade6b5c6d843f628bc4d3636b093be78cd7bb7d9c9f6"}, - {file = "pyobjc_framework_BackgroundAssets-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:93736d6fb6bda52cfddf4006ffbdcf6bfe414826c04901d4c5b644cc380ade44"}, - {file = "pyobjc_framework_BackgroundAssets-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a248a110092983f0b19d9c5b21d7027987954ae4d58775411d540139e6972a69"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-businesschat" -version = "10.0" -description = "Wrappers for the framework BusinessChat on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-BusinessChat-10.0.tar.gz", hash = "sha256:fb929e4ab2b8fe618ac8038a12745d32972e0f6d1dd7c3eb41395542e0200207"}, - {file = "pyobjc_framework_BusinessChat-10.0-py2.py3-none-any.whl", hash = "sha256:2eb35f6f3585302c32cab1af13501b13f97badd13c0ed885c4ecd66ed24add15"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-calendarstore" -version = "10.0" -description = "Wrappers for the framework CalendarStore on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CalendarStore-10.0.tar.gz", hash = "sha256:bf73fc69e306456a36417b97a56398013637ae551cdfae1ba53e2a86935afa52"}, - {file = "pyobjc_framework_CalendarStore-10.0-py2.py3-none-any.whl", hash = "sha256:1e0da82b7d1a0d1f34991795d1f7dc8e186f79faf9a4b0ef5fe1a74112ac70a4"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-callkit" -version = "10.0" -description = "Wrappers for the framework CallKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CallKit-10.0.tar.gz", hash = "sha256:640bc3175b494f6cd0e2a56a453025d2d7d18c97af18800c24dcd8e257209101"}, - {file = "pyobjc_framework_CallKit-10.0-py2.py3-none-any.whl", hash = "sha256:3c431115a3d3c826268a9c4272c0b261e5a15206e9468915a859cde52f32d190"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-cfnetwork" -version = "10.0" -description = "Wrappers for the framework CFNetwork on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CFNetwork-10.0.tar.gz", hash = "sha256:18118d62e05e37692e3cfa5b1ab8c0b82079aad72240bcb6d626740aa4405480"}, - {file = "pyobjc_framework_CFNetwork-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8d9d3badde8adac25c2fef5bf768792a0ee1cbaff9b5d9f416a8a77b0729f2d7"}, - {file = "pyobjc_framework_CFNetwork-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc16d60fdba0aa41ea10353ca4108c464c18eb6caadb457f845bdd32de6a236e"}, - {file = "pyobjc_framework_CFNetwork-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b935f6f43fa42d9f2617e9858e243e0381338aaa4c2a47c59efbefd310d6faa6"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-cinematic" -version = "10.0" -description = "Wrappers for the framework Cinematic on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Cinematic-10.0.tar.gz", hash = "sha256:5cc96e10e76dc617f11a327ea351078a44b1a4c918187626d8d7e9e9c3d7bcd7"}, - {file = "pyobjc_framework_Cinematic-10.0-py2.py3-none-any.whl", hash = "sha256:667197227d10add7869dbcfd8396faa251682ff62a702c125ddaf7566469c25b"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-AVFoundation = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreMedia = ">=10.0" -pyobjc-framework-Metal = ">=10.0" - -[[package]] -name = "pyobjc-framework-classkit" -version = "10.0" -description = "Wrappers for the framework ClassKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ClassKit-10.0.tar.gz", hash = "sha256:6f0c6dbba20945f515b5a3540c88d91e9c00c1af854c889fb56491179dc064be"}, - {file = "pyobjc_framework_ClassKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:586e5a5d26d4444614cde1be7acc51483ed806057c87c035c3a22bcf6d910d37"}, - {file = "pyobjc_framework_ClassKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea01948a1711a680fb2209adbc72931759b3d96621b10d207d3ec41a3663a3e1"}, - {file = "pyobjc_framework_ClassKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ccd9f14c9ecbd35af3264e017871d26ea51d335d011ebd6ecc6c11b0cd605f3d"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-cloudkit" -version = "10.0" -description = "Wrappers for the framework CloudKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CloudKit-10.0.tar.gz", hash = "sha256:05dab3798e9126625a0a72ca1987a768d5bf5c3293b594c9eb8d4e1eb02d26ec"}, - {file = "pyobjc_framework_CloudKit-10.0-py2.py3-none-any.whl", hash = "sha256:cf58196fc29c0fec8f5471172d0fc2f4fe03ded5ccb4d4c1075967283164aad3"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Accounts = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreData = ">=10.0" -pyobjc-framework-CoreLocation = ">=10.0" - -[[package]] -name = "pyobjc-framework-cocoa" -version = "10.0" -description = "Wrappers for the Cocoa frameworks on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Cocoa-10.0.tar.gz", hash = "sha256:723421eff4f59e4ca9a9bb8ec6dafbc0f778141236fa85a49fdd86732d58a74c"}, - {file = "pyobjc_framework_Cocoa-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:80c22a8fc7f085746d9cd222adeca8fe6790e3e6ad7eed5fc70b32aa87c10adb"}, - {file = "pyobjc_framework_Cocoa-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0187cba228976a45f41116c74aab079b64bacb3ffc3c886a4bd8e472bf9be581"}, - {file = "pyobjc_framework_Cocoa-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a81dabdc40268591e3196087388e680c6570fed1b521df9b04733cb3ece0414e"}, - {file = "pyobjc_framework_Cocoa-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a23db9ab99e338e1d8a268d873cc15408f78cec9946308393ca2241820c18b8"}, - {file = "pyobjc_framework_Cocoa-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a3c66fe56a5156a818fbf056c589f8140a5fdb1dcb1f1075cb34d3755474d900"}, - {file = "pyobjc_framework_Cocoa-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bf9020e85ead569021b15272dcd90207aab6c754093f520b11d4210a2efbdd06"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" - -[[package]] -name = "pyobjc-framework-collaboration" -version = "10.0" -description = "Wrappers for the framework Collaboration on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Collaboration-10.0.tar.gz", hash = "sha256:242148c98010f44b4f09d6037b9aa963e54038c1769474643997e7c4618f2c2a"}, - {file = "pyobjc_framework_Collaboration-10.0-py2.py3-none-any.whl", hash = "sha256:971e75adb91bc3f39750ce3f5332e72500f82d04f6e95cb1e8dd1dc468826530"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-colorsync" -version = "10.0" -description = "Wrappers for the framework ColorSync on Mac OS X" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ColorSync-10.0.tar.gz", hash = "sha256:e5722486f02a3c2330996e76207e7e26dde1597122503659259715a7dedf73da"}, - {file = "pyobjc_framework_ColorSync-10.0-py2.py3-none-any.whl", hash = "sha256:5c5d361ebdcf72f4a2665cf0c68adc153b6621ea7ea0df6bbc60a4a69ec1e2b0"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-contacts" -version = "10.0" -description = "Wrappers for the framework Contacts on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Contacts-10.0.tar.gz", hash = "sha256:7130d83be467c4bb877716a73b2e1a7768f19f2c43bf3bbff2d9ae412008d4a8"}, - {file = "pyobjc_framework_Contacts-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8f3b8cbf337d6a674374d6b90292871bdda0304c58aa7d4e23b96c40816534db"}, - {file = "pyobjc_framework_Contacts-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e344065964de463cc18aaac5d684ae900a3867889a1ad24e0fa1937f03ceac0c"}, - {file = "pyobjc_framework_Contacts-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:03d0a882fef637ebdc709c3915ae437fdc5afe63141fb6c1c3e6041c4831c2b6"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-contactsui" -version = "10.0" -description = "Wrappers for the framework ContactsUI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ContactsUI-10.0.tar.gz", hash = "sha256:38f885b861af10e5773f4dbbabd4170bcd35610d879763caac47623ff7a410a9"}, - {file = "pyobjc_framework_ContactsUI-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d73a991b76238161d97de16e717e1de0a1359dd5439f7a23277a9cddaf9f2d35"}, - {file = "pyobjc_framework_ContactsUI-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5ea57e3eecc9a8dc451c91a21eb2b03a6a94b23c5c61dbf26d774abedb604313"}, - {file = "pyobjc_framework_ContactsUI-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0fe6ad4f5d1fb15a7c749512458c71da8d5ffe46170e992b1a1d0f07dafd98a3"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Contacts = ">=10.0" - -[[package]] -name = "pyobjc-framework-coreaudio" -version = "10.0" -description = "Wrappers for the framework CoreAudio on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreAudio-10.0.tar.gz", hash = "sha256:6042e9fea80bf5c23a8a3a4a2888243b7152316275ab863ed6bc289eabdef9f1"}, - {file = "pyobjc_framework_CoreAudio-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bd83cb720352e59c99d3c60a06670babad27c04ca0266dabf127a4575f3899bf"}, - {file = "pyobjc_framework_CoreAudio-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ae64cfc907b7bc7fb275b6072e6aedf0755f9ec57f862bf74c53ae6fef6e340"}, - {file = "pyobjc_framework_CoreAudio-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:41c75e7a2e17619841c55a0be8c3c0666fad190a7142f1a80f01451184832cf3"}, - {file = "pyobjc_framework_CoreAudio-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0802a45702bf8c2c0cbc5e80863a3c7924d1a6b07ffcd21e3aeac22a590772a1"}, - {file = "pyobjc_framework_CoreAudio-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c7b4af7752e7c99e4b958313a00697b19a3475adb611469909ab5431c0d6ef08"}, - {file = "pyobjc_framework_CoreAudio-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ee779edf528b56bc5dcd2b4d5dda4795516a68d3443453341e485f461e14301"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coreaudiokit" -version = "10.0" -description = "Wrappers for the framework CoreAudioKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreAudioKit-10.0.tar.gz", hash = "sha256:0e604b47fb04303399d6cdeda5e83c0fed53ee61348052d44321ffbe898b08dc"}, - {file = "pyobjc_framework_CoreAudioKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b199359b9140df894a96673bb805f29ef40004167d1bdbea899222844ed3d5e2"}, - {file = "pyobjc_framework_CoreAudioKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:096d04184ed27dc415bcd6c7652f4db6901efbd6df544c1b18f69c1e67c5c03e"}, - {file = "pyobjc_framework_CoreAudioKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4e06731de042dded49b0854c946b6e5cd67b37d3a892609985ffbb2d60169224"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreAudio = ">=10.0" - -[[package]] -name = "pyobjc-framework-corebluetooth" -version = "10.0" -description = "Wrappers for the framework CoreBluetooth on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreBluetooth-10.0.tar.gz", hash = "sha256:dddc9020cd2ca008c7037c61026641fff5d91a608b9e3bda51d4ba6afbb04e3c"}, - {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5dc85f0acea4ec8fb59ed91f89e677133b0e5efab21fe14694328ddcdbdc22c1"}, - {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:155b2f9814970a07c602286ce19d3d3c3a812951a62b9f19aaa80475f9b49f05"}, - {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:119c4747dcc8a1b9980b8bac4f2ffd90a3950b048b9fbf03e70656eaaaefe7d4"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coredata" -version = "10.0" -description = "Wrappers for the framework CoreData on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreData-10.0.tar.gz", hash = "sha256:6799c3ab2ad5d609df8d8801d19740abdbe8ea70851abfe8a660bcb91818238d"}, - {file = "pyobjc_framework_CoreData-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:98803ba27319308305cb498947e8edad829ab4564c945217563a0a4d13607702"}, - {file = "pyobjc_framework_CoreData-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ad6c11c9d1fd9c368291546bdaaf9355c1410bce662f782509249863dd8368ef"}, - {file = "pyobjc_framework_CoreData-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1c897103683737761f8d1c248011affbad3f2bc08f0c9f312a051da6134931a2"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-corehaptics" -version = "10.0" -description = "Wrappers for the framework CoreHaptics on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreHaptics-10.0.tar.gz", hash = "sha256:3ea964b452763e27b24373fc61adf65fe6553bd815e8b9b3399f43ee10ab600c"}, - {file = "pyobjc_framework_CoreHaptics-10.0-py2.py3-none-any.whl", hash = "sha256:5c7bbc18db031be82bdbdde8f96045220a0309e200e8779bc7e361eb2d482892"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-corelocation" -version = "10.0" -description = "Wrappers for the framework CoreLocation on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreLocation-10.0.tar.gz", hash = "sha256:d84001ab5ef58441514bd92ed9b2fd4225faf0241d2a09ab503592fbc6a6066d"}, - {file = "pyobjc_framework_CoreLocation-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0a9d535f00a0369d493f49bd898e68d5ce7227ce161a3f0df0d9e6668e396a77"}, - {file = "pyobjc_framework_CoreLocation-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cf39e4f092d7a94a8ad516bda2603872fd0952aeac0bb0143e7ec2e2244a172d"}, - {file = "pyobjc_framework_CoreLocation-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4ec1c23b92285f7f33bdc86dc4e6cbccb8788ceca6ea6205f420859ed172abee"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coremedia" -version = "10.0" -description = "Wrappers for the framework CoreMedia on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreMedia-10.0.tar.gz", hash = "sha256:27d0755cbd3ae3b487ace5e3233f0598b976905f43357b71fd73489865f7b9e1"}, - {file = "pyobjc_framework_CoreMedia-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0fd980d45d710c54e668e96268cb94dd6c877526a141581d2749bfdce4e6791"}, - {file = "pyobjc_framework_CoreMedia-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:064c35991073fe18f27f321cb33cac1d484052d154cee5222260c7fd179bc3fe"}, - {file = "pyobjc_framework_CoreMedia-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d8bf02036e60c5f47b904a259e0665b7774d915eda95810566ca1b82a1be27e"}, - {file = "pyobjc_framework_CoreMedia-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb6d6dd75febc83d22e986b2894307bd6bac1980535e7f70931bcac034bdbded"}, - {file = "pyobjc_framework_CoreMedia-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:df15b39eec3420b68f878b6de698c8e94fc2a46a2bd5a16928c9cfa2ae24e8ee"}, - {file = "pyobjc_framework_CoreMedia-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b1251c455e593dc0af52eb38c61da9847f349b9dfe6b6449a24f15aea938a31"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coremediaio" -version = "10.0" -description = "Wrappers for the framework CoreMediaIO on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreMediaIO-10.0.tar.gz", hash = "sha256:d535c67d287d21e25d739c72ae9f7ce8b0f96eacfd3e19758da69ba355553483"}, - {file = "pyobjc_framework_CoreMediaIO-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2e430da4065cadd8857b1615c5641874484c6d857805f9fd2185de3a8fc4ef53"}, - {file = "pyobjc_framework_CoreMediaIO-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d20f7c39b065f24d12b403b663f1cc09ce794ebcd1a02a199ca335bf4b5fc26"}, - {file = "pyobjc_framework_CoreMediaIO-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ad5fcce52f5f8628b3bad5ddfc9c60f4f74a5509ed59a022cc57d583b279d78d"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coremidi" -version = "10.0" -description = "Wrappers for the framework CoreMIDI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreMIDI-10.0.tar.gz", hash = "sha256:7e464775fb6bd77148394b5f53caa61c36e3426f61cc621f299bca91931eb3a4"}, - {file = "pyobjc_framework_CoreMIDI-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:7ddb0b6222046b35a93601e3e4562bcbb32e4abe6ffa510e80660d718e45eaf8"}, - {file = "pyobjc_framework_CoreMIDI-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:68d0a9ac864d2ee1b7ba6977e68698d8db853149e64c18279149c1cc6ac39748"}, - {file = "pyobjc_framework_CoreMIDI-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e5e97f794154425def217d041ccf50f4c820b827d27ae48ad5eedb09eaf2f6f6"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coreml" -version = "10.0" -description = "Wrappers for the framework CoreML on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreML-10.0.tar.gz", hash = "sha256:11b70aaa34d45b2a325231ddc571686b8e5c6404b74eb647c84c0cb2cf51052a"}, - {file = "pyobjc_framework_CoreML-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c3b6ce1fc80a77909aa1bd0938da7516b3e8a0b04a5800036bdc1456e01c084d"}, - {file = "pyobjc_framework_CoreML-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4bb7b240be5e184a68527db3a4d85a7af1263fca258fb25ee9d5b84a90b6e553"}, - {file = "pyobjc_framework_CoreML-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:beb7eae3e2dce823c2e4d2ceb480884f09271e29784c8b5016b268df8b987378"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coremotion" -version = "10.0" -description = "Wrappers for the framework CoreMotion on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreMotion-10.0.tar.gz", hash = "sha256:d735668ffe028450c09499bca57467dbf77e0360b6f0623f1e054b2fe723fffb"}, - {file = "pyobjc_framework_CoreMotion-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8d0fd4ab5f6642f716becd2ba3dfe45d83e3a042ba184bf5056d8d2064bf716"}, - {file = "pyobjc_framework_CoreMotion-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2e0f6657464d5ba212691e5918f3d777a1c36d1694080ad029ef3d987c25b29c"}, - {file = "pyobjc_framework_CoreMotion-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5a2082d1e3acbfde753909680a869c8cc7b11c1a494aa497496ea9c9c98fdf29"}, - {file = "pyobjc_framework_CoreMotion-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd4f635d73a3bdcfe1f6d6b9dc47816d7eda1152e1c9f4f2e2f4de1b4111cf38"}, - {file = "pyobjc_framework_CoreMotion-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d1947bbb8348bc576fcf412781f616a35bc58bc6a8fef58630e5b801ee0e36cc"}, - {file = "pyobjc_framework_CoreMotion-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3bd7725e0f764d861d1ec6a531fa1ae046970ff6d9fcb62fcb804ca86bc28316"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coreservices" -version = "10.0" -description = "Wrappers for the framework CoreServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreServices-10.0.tar.gz", hash = "sha256:a6e80770ead727979e9ffd4ea97c30889e1fdec49873bb5129bf3ef3c5b90005"}, - {file = "pyobjc_framework_CoreServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5846d8fcd2b352c479b5517176a9c6939cb50599041d9f68ddf55804d58f5751"}, - {file = "pyobjc_framework_CoreServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b8638b63f83246f6659624ad20e068ba11fdfe915f7c6318de7e3213cd2b0aac"}, - {file = "pyobjc_framework_CoreServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6ecf120cc89401b410d8f307cd499c6d80f4f52d1d1291150507b85a69bbc12c"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-FSEvents = ">=10.0" - -[[package]] -name = "pyobjc-framework-corespotlight" -version = "10.0" -description = "Wrappers for the framework CoreSpotlight on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreSpotlight-10.0.tar.gz", hash = "sha256:393767c63f2513ab4056c688aecdaf1ae67357f8d99fa963d765cfbdc9ccba47"}, - {file = "pyobjc_framework_CoreSpotlight-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:dc07cfa097f79946c15fcc3bb2aea5db17822f2e9f85478c5b07d2f5f194db4f"}, - {file = "pyobjc_framework_CoreSpotlight-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8760252792ce351a97b9bfed9e3f84999b698b63fcf759e8b9df5978c67236ea"}, - {file = "pyobjc_framework_CoreSpotlight-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4370d760d93cfc58691fa7fb585f20067157a18c06cd4c3bfc3dd7c9824eda5a"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-coretext" -version = "10.0" -description = "Wrappers for the framework CoreText on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreText-10.0.tar.gz", hash = "sha256:2c157d82373b8128afb9a61df26cbf029896adf86bf86876ce3f8cc3c3f3cb1b"}, - {file = "pyobjc_framework_CoreText-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dfba918655fcd37018f38e6ca9db557c67b63ddd2de93319eb05c07f492cca32"}, - {file = "pyobjc_framework_CoreText-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c8ec3d634b440b53727adf45212bb34639ee404b891be87a62f0f2253b7a92e5"}, - {file = "pyobjc_framework_CoreText-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ab1f76d3f6bf6470217478d2edf62360c5e934dfd66a4d0d35a8bf07086bee65"}, - {file = "pyobjc_framework_CoreText-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:543f37fe792ec75d72bead5616f6dc29ab2d8e26d824080fd7625efc015ecc50"}, - {file = "pyobjc_framework_CoreText-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1c4026c3d15922c7ec372b2e43a5673ba0ed436e59e046afd860a3d6a5676c25"}, - {file = "pyobjc_framework_CoreText-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca269a49bb76fccb6d50eef3c72650bc686ae19a4cc9be6288fd6e758fa67768"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-corewlan" -version = "10.0" -description = "Wrappers for the framework CoreWLAN on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CoreWLAN-10.0.tar.gz", hash = "sha256:f71594ca1d2741f5979688d6d3880237c469943b49a030de131102357cdccb2a"}, - {file = "pyobjc_framework_CoreWLAN-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f245de3d5d7ff2f49cfe72c0bf499f5fb97ad2e930efd485f8c01ec11d749136"}, - {file = "pyobjc_framework_CoreWLAN-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7d7cb58fba9272a6dd20a84733006e574092128d9d581f346f4c777cb6c353ff"}, - {file = "pyobjc_framework_CoreWLAN-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6211ad500131bf1bd0680847f67a948478551043054a4514f90879067c854bb6"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-cryptotokenkit" -version = "10.0" -description = "Wrappers for the framework CryptoTokenKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-CryptoTokenKit-10.0.tar.gz", hash = "sha256:314fe7067cecc0901602173a47bcdb3107ddbae6a22052b0e217f79b7d388153"}, - {file = "pyobjc_framework_CryptoTokenKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:94fe71998c6821d4f45de60227ee0541ad71baf758b3051e7a3c84e0abcccaac"}, - {file = "pyobjc_framework_CryptoTokenKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f30f0165b5914da418450cc3f8b870d02052f8eb9b14f27dd61aff7928cd1eb5"}, - {file = "pyobjc_framework_CryptoTokenKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:9d4db0b30f395d0d4e26b48645ee7b219f499c243124fbd6495406620576ca31"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-datadetection" -version = "10.0" -description = "Wrappers for the framework DataDetection on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-DataDetection-10.0.tar.gz", hash = "sha256:026b44718fc71750f7ba258be461ecb1463ebeac0ea3bf26e559cd10dfd7bd57"}, - {file = "pyobjc_framework_DataDetection-10.0-py2.py3-none-any.whl", hash = "sha256:6f6420b187475cccf20757577b005bc16b4a606dd8d9d431b59151e571fa6b12"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-devicecheck" -version = "10.0" -description = "Wrappers for the framework DeviceCheck on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-DeviceCheck-10.0.tar.gz", hash = "sha256:00a02c603020a9e9369f894d0b6931217ca0c9606f4947c34bfb4f413cc736a7"}, - {file = "pyobjc_framework_DeviceCheck-10.0-py2.py3-none-any.whl", hash = "sha256:e930659cb8cb3096b88f43c237951364dbd1b29d98390e0b55b48aec0442cc92"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-dictionaryservices" -version = "10.0" -description = "Wrappers for the framework DictionaryServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-DictionaryServices-10.0.tar.gz", hash = "sha256:a250ead7a3a0504ea860ed4ebefce3bec4b308b91ea760c33bfcc14af5054873"}, - {file = "pyobjc_framework_DictionaryServices-10.0-py2.py3-none-any.whl", hash = "sha256:8bc50b80e8f77e411b707827062609b67695bc5ae619452388eb02bdeea19f05"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-CoreServices = ">=10.0" - -[[package]] -name = "pyobjc-framework-discrecording" -version = "10.0" -description = "Wrappers for the framework DiscRecording on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-DiscRecording-10.0.tar.gz", hash = "sha256:1b4a9a702f0695ed87392693ab916cc120c179547d6fa7bf3e59708fe218ec22"}, - {file = "pyobjc_framework_DiscRecording-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6d4d737f0536d1d4cd4efc88787d4b20a74b71e2f91e5554346e8b1b993a2f97"}, - {file = "pyobjc_framework_DiscRecording-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:11fecd1d6e464582e0ef0a0a4469e1ed1ea36e45c27d2bbd77cd42dca4f0dadd"}, - {file = "pyobjc_framework_DiscRecording-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d547d69e3ac8f9c735456af7c440c0c318752e1e8b55868f8a2da0aae0bef8ee"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-discrecordingui" -version = "10.0" -description = "Wrappers for the framework DiscRecordingUI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-DiscRecordingUI-10.0.tar.gz", hash = "sha256:9a77cd9fb311ececab84b682ebfbb573a13f6f9f67b39733c1920fcea83dfd31"}, - {file = "pyobjc_framework_DiscRecordingUI-10.0-py2.py3-none-any.whl", hash = "sha256:c80135d65bb25f1d4c3c40af9a50c3b15125c54703d6e65cf4316fe3ed3bd0e7"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-DiscRecording = ">=10.0" - -[[package]] -name = "pyobjc-framework-diskarbitration" -version = "10.0" -description = "Wrappers for the framework DiskArbitration on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-DiskArbitration-10.0.tar.gz", hash = "sha256:cfd8e4c7dbef6b69832fa8e2425c53bf8bbc83b8c78bb5a098a787335df7bf8f"}, - {file = "pyobjc_framework_DiskArbitration-10.0-py2.py3-none-any.whl", hash = "sha256:cf7dadef895980e08dc7dd646c6d819ea3b4b8321abd2af512d9bde5de389895"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-dvdplayback" -version = "10.0" -description = "Wrappers for the framework DVDPlayback on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-DVDPlayback-10.0.tar.gz", hash = "sha256:fe0bf9cfbf1be9888685aedd3b25b6793d072e947c6eddcc0ccb2c4a07bee453"}, - {file = "pyobjc_framework_DVDPlayback-10.0-py2.py3-none-any.whl", hash = "sha256:ea31f045edf56abda6e1fc2aa9ff0bee267fd549b7787bbaf7e437e4fa58135e"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-eventkit" -version = "10.0" -description = "Wrappers for the framework Accounts on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-EventKit-10.0.tar.gz", hash = "sha256:8219a650edf1b6842c92306a077502488f95473d138fd842068d4a1e56621989"}, - {file = "pyobjc_framework_EventKit-10.0-py2.py3-none-any.whl", hash = "sha256:48d65edd47efd0864d93e5bbe3f05121c413d4006b7c0f0a3f0592b58d80a0db"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-exceptionhandling" -version = "10.0" -description = "Wrappers for the framework ExceptionHandling on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ExceptionHandling-10.0.tar.gz", hash = "sha256:288b99a86e29999dc0f3f1a6bb90c8dc0b79ed51b265ee4c9e673d660e959cb2"}, - {file = "pyobjc_framework_ExceptionHandling-10.0-py2.py3-none-any.whl", hash = "sha256:3c7669d6e93d4f4d472de8c7b8e3b5ecd42dda16161e24b3bf796713fc20eb1a"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-executionpolicy" -version = "10.0" -description = "Wrappers for the framework ExecutionPolicy on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ExecutionPolicy-10.0.tar.gz", hash = "sha256:cd6c14970a090f033673e5870804051a2bc41b060d02420eac0e7816b9e2c034"}, - {file = "pyobjc_framework_ExecutionPolicy-10.0-py2.py3-none-any.whl", hash = "sha256:823eda14ad797436101f365cb3a5cd7bc46bb8a8972418851427d478b9274ded"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-extensionkit" -version = "10.0" -description = "Wrappers for the framework ExtensionKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ExtensionKit-10.0.tar.gz", hash = "sha256:ed9c596728819a58803841bb36d0a5773929d6bd32279b924dcd004266a901df"}, - {file = "pyobjc_framework_ExtensionKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e8b4bf58985300019f64ed8ce3dc6c9ba73621646dacdc5273d93c5ffdc9238b"}, - {file = "pyobjc_framework_ExtensionKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6613e71395266e948d4bd8917245adf77ca58166d37aac73e340081a4ad6ff8b"}, - {file = "pyobjc_framework_ExtensionKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:144cd81e9e2ff3de8553c236a9fb7b9fbe2023f3886d32328dc324442be7ab07"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-externalaccessory" -version = "10.0" -description = "Wrappers for the framework ExternalAccessory on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ExternalAccessory-10.0.tar.gz", hash = "sha256:4b00f07e6ec8e68974d89242789720bfecdc474c26bf0f2b2b2d648e6b6155cc"}, - {file = "pyobjc_framework_ExternalAccessory-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9d51574d483719d646dc4327125ed1038c5bbaae626ae08b178cac3269f0285"}, - {file = "pyobjc_framework_ExternalAccessory-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0d4101c0f3f7a4e0c7cc5c0bc0357dd564b03b5f9c41efa8f347d998806c8df1"}, - {file = "pyobjc_framework_ExternalAccessory-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a2885e163ebbae023bd6990a3864f2ca24aa49fffbff3be54f23225bac64ca6e"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-fileprovider" -version = "10.0" -description = "Wrappers for the framework FileProvider on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-FileProvider-10.0.tar.gz", hash = "sha256:432165e8ae9e85437bd4b36be4fe1a467f03f5e9d6aca07228ac5385a96b2d44"}, - {file = "pyobjc_framework_FileProvider-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b596718c562d72bfc9dacdc53cef494b8afe9f2c9d715a16c3b209c628edcba"}, - {file = "pyobjc_framework_FileProvider-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:69427a57d2238a3274fe351e39d98918a097bafc54eebba68edc07624086e38a"}, - {file = "pyobjc_framework_FileProvider-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b9820f94be07aaae649d0eae55d7661f828fc12b605c0e61c08aeb33ed0c1747"}, - {file = "pyobjc_framework_FileProvider-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b03250d3e0c5ebfbd71e2134c67577a19c3856ef3c4b849d98a00e22801c7d14"}, - {file = "pyobjc_framework_FileProvider-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7caa8541f36a4842210ac8e82f107cd109d6e835f86d0391d5bbc41433f4b384"}, - {file = "pyobjc_framework_FileProvider-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a453bc7a76ae0bc9a574bfd0b38446166d344c62e59113a19da11ade8413eaf4"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-fileproviderui" -version = "10.0" -description = "Wrappers for the framework FileProviderUI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-FileProviderUI-10.0.tar.gz", hash = "sha256:895c3de1ba34ab011d012906623bcc2d2addc7b32af201ad19d59718e933d2ff"}, - {file = "pyobjc_framework_FileProviderUI-10.0-py2.py3-none-any.whl", hash = "sha256:bca5613525ffb757e033803060d63f592612820fbe7ff024e931a5e3745ec08b"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-FileProvider = ">=10.0" - -[[package]] -name = "pyobjc-framework-findersync" -version = "10.0" -description = "Wrappers for the framework FinderSync on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-FinderSync-10.0.tar.gz", hash = "sha256:6915d934236efca382926e93ded216b18a23e7dd03bf70b751cb7e86bbf237f6"}, - {file = "pyobjc_framework_FinderSync-10.0-py2.py3-none-any.whl", hash = "sha256:b2d166fa8af4cd7516fc860c896531bcf9921e5251106c99ac6cd726bf41d020"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-fsevents" -version = "10.0" -description = "Wrappers for the framework FSEvents on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-FSEvents-10.0.tar.gz", hash = "sha256:a462c1ad6d6c93d9542c9780b970915e5e9fa0f70391187f7145b5b1c64e57d5"}, - {file = "pyobjc_framework_FSEvents-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:eb3b590a56c31eee60adddc9657f67e319a80bfe0d91f23cac5c78dd22893404"}, - {file = "pyobjc_framework_FSEvents-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a548e660f2f2e63e9f2cd57db472b2b21056d89748541a167803ba4c9b6287f1"}, - {file = "pyobjc_framework_FSEvents-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:68113dbd448619f1d07e847c6088f8b4371001f2468300638dc78236ffa10c05"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-gamecenter" -version = "10.0" -description = "Wrappers for the framework GameCenter on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-GameCenter-10.0.tar.gz", hash = "sha256:3157d1389bde7afd6fa7d5d1aa64578e99c5fd50a1400178b1f58443013d6669"}, - {file = "pyobjc_framework_GameCenter-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9a83d8c13ae716cd3216e288531fb9e3668bf4e8e27fc1c669b1511e801aa02b"}, - {file = "pyobjc_framework_GameCenter-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b333d53bffcbfce4929f7db22dec9f0d8bb47e387ef2e5df4a49250069e76c86"}, - {file = "pyobjc_framework_GameCenter-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:baea3dd2e75ede21c81cc81ecd2f525b2507556bee7bf76fb6a0a292f39f8346"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-gamecontroller" -version = "10.0" -description = "Wrappers for the framework GameController on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-GameController-10.0.tar.gz", hash = "sha256:c042d6f581a37d39564c6e7b998d6a2775adb4b937aa33e60b8aa99475157c2d"}, - {file = "pyobjc_framework_GameController-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bd8a4e7920df5f6f60d6034af31094fe756210efc75c58455735259e201385a6"}, - {file = "pyobjc_framework_GameController-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1f384ae252be210a32ec1ae6a460a108e004d13d5bac1324b25c9b3932171da9"}, - {file = "pyobjc_framework_GameController-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f7293f4c13ac372f9234d587e5370aac16de562b01c5dcd6e561564adb80211f"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-gamekit" -version = "10.0" -description = "Wrappers for the framework GameKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-GameKit-10.0.tar.gz", hash = "sha256:6febacef9b003b58eeb6ca936cd83825bd22fe55475b965e0deb29b48d5912c5"}, - {file = "pyobjc_framework_GameKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cee6d712b20ef17bea9a92681635e92628ecefd78965e016b1ede0ff9c15ac11"}, - {file = "pyobjc_framework_GameKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d7f7d7a3ed81d02f2191e3d6fc8336840e672f70984b64cefb36165abac371a"}, - {file = "pyobjc_framework_GameKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:53859c370583322acfc9ba03b4b0ba258541836eb16420e8de44ab185caba8de"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-gameplaykit" -version = "10.0" -description = "Wrappers for the framework GameplayKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-GameplayKit-10.0.tar.gz", hash = "sha256:7e5cf3197a53344638a1957e1827cd86018cf7549a6da73193346cd8c40b1d52"}, - {file = "pyobjc_framework_GameplayKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ee4495804fbb6b1aaef1be5b2dcafb676aabfe9bbdcce319484b4e5e2e9d3526"}, - {file = "pyobjc_framework_GameplayKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7fad056f82b75700139d2479e56a1bdd1e2cabb2367783321b31f4abe240084a"}, - {file = "pyobjc_framework_GameplayKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ea527d8b7ee32f5cc9864afb172bb70bf0a1a02bd29d9d57d9f0278bac56aa0e"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-SpriteKit = ">=10.0" - -[[package]] -name = "pyobjc-framework-healthkit" -version = "10.0" -description = "Wrappers for the framework HealthKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-HealthKit-10.0.tar.gz", hash = "sha256:0abe3e003927998728db217d2a023f59d9e8f52072e81cc01469888731b7ebf5"}, - {file = "pyobjc_framework_HealthKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:02e92b6f875bb707516f957d33e94a4ef98b915e2e628356d77d2cf8edbcd4c6"}, - {file = "pyobjc_framework_HealthKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f24e683081c09050667b8d2410ef6ba06c6c314e2d3e42f9d947df87663ff9ef"}, - {file = "pyobjc_framework_HealthKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:888530d05ec6954fb5e6993c8f5089889baabda4527aafbbcca7c7d9c8705f0c"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-imagecapturecore" -version = "10.0" -description = "Wrappers for the framework ImageCaptureCore on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ImageCaptureCore-10.0.tar.gz", hash = "sha256:9660faa140806dd0f2c50c39062863c23188c6b9596e2946234dd3c35882d3c7"}, - {file = "pyobjc_framework_ImageCaptureCore-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be85524a36f83e753898f93529145b6299760af5891521e4370f09944813942"}, - {file = "pyobjc_framework_ImageCaptureCore-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:43d9fc0dc3f78aac627668aba6627888e95f034b3422a30f3727a0027222fec3"}, - {file = "pyobjc_framework_ImageCaptureCore-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3f0790981bf67416d02004cf83f0f68e8edda4aba48933596317e8653a173403"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-inputmethodkit" -version = "10.0" -description = "Wrappers for the framework InputMethodKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-InputMethodKit-10.0.tar.gz", hash = "sha256:dc2f10752ab62e4c7b2306938d617d83bef6d52752862a0998ed57db472e36ae"}, - {file = "pyobjc_framework_InputMethodKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:895771f0a47f588f69ed930868ba48ee380ccecd7ef384ad97c4e78220e12a0a"}, - {file = "pyobjc_framework_InputMethodKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ab33c90206934137b093d71ea8e4b73626e337d7767c16154d4a41b502c9a2a"}, - {file = "pyobjc_framework_InputMethodKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f9d4f8025303549dbcc284bd7bb9ca6e58c06f398eb085ed9ca43e3ca65ab7fe"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-installerplugins" -version = "10.0" -description = "Wrappers for the framework InstallerPlugins on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-InstallerPlugins-10.0.tar.gz", hash = "sha256:417f51877cd2ddd95e341d7d4b5db7d152a3e9d4f6537db20758bce0f9235c3d"}, - {file = "pyobjc_framework_InstallerPlugins-10.0-py2.py3-none-any.whl", hash = "sha256:1dfee60017bdf9c2e1566dd26972a288f9f9ace878c25ab5681164b2221d1e70"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-instantmessage" -version = "10.0" -description = "Wrappers for the framework InstantMessage on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-InstantMessage-10.0.tar.gz", hash = "sha256:7b5cd6217fb0d867b03ea7c15ab55c66fe63ab0beaef63c1527e734b16780405"}, - {file = "pyobjc_framework_InstantMessage-10.0-py2.py3-none-any.whl", hash = "sha256:c53dd8ddf2b28dd87cdb67c21798b15d432d659abc633fc3c0a27433bc7a241a"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-intents" -version = "10.0" -description = "Wrappers for the framework Intents on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Intents-10.0.tar.gz", hash = "sha256:228177cd32e63b2b2c76befdb80e520c4db81be7186549753c3dc7b9f74d4a4b"}, - {file = "pyobjc_framework_Intents-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5cc9ec8240b261578cac95b97156883f1ad80ac2771ec5e2fa7fe41e112f169b"}, - {file = "pyobjc_framework_Intents-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c4fb57078f797362169f8c41218248028132e6120b98546e7d22bf6a995c640b"}, - {file = "pyobjc_framework_Intents-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aea0e15602ffac9ef237fa5bda102372918e8066630d0d4c5e919fb8f647b090"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-intentsui" -version = "10.0" -description = "Wrappers for the framework Intents on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-IntentsUI-10.0.tar.gz", hash = "sha256:27dbc84df229700c8e187ba9bfc089fe7dea63cfa20ee7e3c3f09c9f8b8c37d0"}, - {file = "pyobjc_framework_IntentsUI-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:930a3b7bde1292a67e2f62b0bbe11778b7d0a77e29a716b6b0ee55f3270515c1"}, - {file = "pyobjc_framework_IntentsUI-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6ad1aaa9fc424605b9800ca848d57058903ad26d1dabaef33a8339051746f1d5"}, - {file = "pyobjc_framework_IntentsUI-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c122eee00aa5bdc471399431088c9359508988e88f57289c25d18cbd95bd190c"}, - {file = "pyobjc_framework_IntentsUI-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28b1d6fc8e0a416f65959a7506672425cff960e152538dc7fbb7fc3f3237871f"}, - {file = "pyobjc_framework_IntentsUI-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:829bd2b67c2d753e43ec6d8d0b3dc544f9a576cbe00cbe3264c1636e367d157d"}, - {file = "pyobjc_framework_IntentsUI-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70335349188f82b5be762b926938fddb01ec24bece963690b6ed6ba50bf480c4"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Intents = ">=10.0" - -[[package]] -name = "pyobjc-framework-iobluetooth" -version = "10.0" -description = "Wrappers for the framework IOBluetooth on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-IOBluetooth-10.0.tar.gz", hash = "sha256:5e6ddcdb8132124fa18c2eb0d0dab9b51e32be14e7ab7a2df12daee3940ec431"}, - {file = "pyobjc_framework_IOBluetooth-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c0dd178608ad97e9c981992f6d9396c2dc05844d24e25095a68aa28d1013f10f"}, - {file = "pyobjc_framework_IOBluetooth-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f73913e8700a1106da8b566160cf1ec5ba7bd69b0983772cc8ca1aa7dc450fa0"}, - {file = "pyobjc_framework_IOBluetooth-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c3645fb7ed7425640eeca0a8e9bb4f1cee337bfa6e9b4b8db3b199b8611b87f6"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-iobluetoothui" -version = "10.0" -description = "Wrappers for the framework IOBluetoothUI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-IOBluetoothUI-10.0.tar.gz", hash = "sha256:5cd1e6fb2100020c90af6cfcfdea5089634469d2b29cdba9c749791943274bfd"}, - {file = "pyobjc_framework_IOBluetoothUI-10.0-py2.py3-none-any.whl", hash = "sha256:d8e15a2eb39f9d76613fb6ea241ef5c4bd94ae2f21e0fc15661ae44090bea43f"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-IOBluetooth = ">=10.0" - -[[package]] -name = "pyobjc-framework-iosurface" -version = "10.0" -description = "Wrappers for the framework IOSurface on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-IOSurface-10.0.tar.gz", hash = "sha256:c233b39e4f5f000e0f9014feb9ec54d36d3a11675a6bcfc8d05d058be965940f"}, - {file = "pyobjc_framework_IOSurface-10.0-py2.py3-none-any.whl", hash = "sha256:a3df57071d833c58ca019bf880a54c92aaeb11accc006a1fb4eb7f215cf8a1a1"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-ituneslibrary" -version = "10.0" -description = "Wrappers for the framework iTunesLibrary on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-iTunesLibrary-10.0.tar.gz", hash = "sha256:3891793569bfe5a2dcfbe075dbd9a92b0937ebc47e233d78a2f65ca6bc92d13b"}, - {file = "pyobjc_framework_iTunesLibrary-10.0-py2.py3-none-any.whl", hash = "sha256:2d3d8457f9ba6bf415535263dee6973e468f140b04b3cf436481551a25c8f07f"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-kernelmanagement" -version = "10.0" -description = "Wrappers for the framework KernelManagement on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-KernelManagement-10.0.tar.gz", hash = "sha256:52b9546ed192dd9390351fd3c530e658122348c9b6f033f94e5737ce760f9bb2"}, - {file = "pyobjc_framework_KernelManagement-10.0-py2.py3-none-any.whl", hash = "sha256:d3573fb51b0132b6814a0fd56a7fb7d648fd627b459ea3157c3d778a5ea4cdbd"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-latentsemanticmapping" -version = "10.0" -description = "Wrappers for the framework LatentSemanticMapping on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-LatentSemanticMapping-10.0.tar.gz", hash = "sha256:ad74bb661109cca52543fa9fb5747c6dc3ad352d74771db6c18312b6468098e9"}, - {file = "pyobjc_framework_LatentSemanticMapping-10.0-py2.py3-none-any.whl", hash = "sha256:01dc811aad11914c1e01daa018ef1833da144095f42ca2dfe810e4768a540a86"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-launchservices" -version = "10.0" -description = "Wrappers for the framework LaunchServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-LaunchServices-10.0.tar.gz", hash = "sha256:0fb7e8d17db9c6a9c8d9333c88703734ef4325c0d36a319183ac3febccef8a9c"}, - {file = "pyobjc_framework_LaunchServices-10.0-py2.py3-none-any.whl", hash = "sha256:f86c70574c7d7c9586fd1908e15fff9df297ab285d7067759337c8e03955427c"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-CoreServices = ">=10.0" - -[[package]] -name = "pyobjc-framework-libdispatch" -version = "10.0" -description = "Wrappers for libdispatch on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-libdispatch-10.0.tar.gz", hash = "sha256:228adf364c895d2a0e8b08bd06f7a23cfbd8e82e9ea6cfdba73bdee0651a4e1f"}, - {file = "pyobjc_framework_libdispatch-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:67fe10a671117fe7e8c35b7eaf8914084fbd0c64f3d635a1147782e3408dbc40"}, - {file = "pyobjc_framework_libdispatch-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:386d53ffd2389c2494a806e3bad2d709ef5d597d1500cb643a817464c20ab8f8"}, - {file = "pyobjc_framework_libdispatch-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d2fbd247ebac8fa57bb0d7a2c4e769118c995bfd81bfc7f189a8ebe297399776"}, - {file = "pyobjc_framework_libdispatch-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:66dc959837b898bdb83a85ed34c2c368d0d38aaed9883c3769d4d1ca1aad23d7"}, - {file = "pyobjc_framework_libdispatch-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c4a1e13172617916b894246b03579814f4e8151f2600403f4a799e4d4c7a032e"}, - {file = "pyobjc_framework_libdispatch-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a55a33fc71e385ff7a8c9f1d29276f44dc0f7ff5b0597d2fd769620438058152"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" - -[[package]] -name = "pyobjc-framework-libxpc" -version = "10.0" -description = "Wrappers for xpc on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-libxpc-10.0.tar.gz", hash = "sha256:ece6fc3158f61c3f33a5ed0d767f2aeb64e4575f367716f3f1642cb80221b02c"}, - {file = "pyobjc_framework_libxpc-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:029157c4af899a835122443d299e2f04030a5c801cd34ab8f5724033ff0e88e5"}, - {file = "pyobjc_framework_libxpc-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2a3c98da602f90bbb0ed0da49193381e439e0716b747b6ecb2b0b07951d0de4"}, - {file = "pyobjc_framework_libxpc-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:918e7defb445ac089736714b2c30665ea566b51fc6dae5c555751f52cced9399"}, - {file = "pyobjc_framework_libxpc-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd6e1ce4e503a45611a9f6fc22b189fa5cc4db0aa779dc09be5e7f321bdd894e"}, - {file = "pyobjc_framework_libxpc-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:862896fb2c4e8c706196ed5f9a8ca8fe88b6bf73a92cc8bc0ec6e00449d6cd20"}, - {file = "pyobjc_framework_libxpc-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d66712e657d3217ef5c8c9181e706ee21370d425ec99aa091c44b92d43dfa341"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" - -[[package]] -name = "pyobjc-framework-linkpresentation" -version = "10.0" -description = "Wrappers for the framework LinkPresentation on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-LinkPresentation-10.0.tar.gz", hash = "sha256:e48af9dc9a1a33313c85a11d667fcf00461638e8778f8f99ce6b2e967a0a8579"}, - {file = "pyobjc_framework_LinkPresentation-10.0-py2.py3-none-any.whl", hash = "sha256:a3de92916daa214da87afe402feef42536e3896b6ed392e040296d01ddd927f7"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-localauthentication" -version = "10.0" -description = "Wrappers for the framework LocalAuthentication on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-LocalAuthentication-10.0.tar.gz", hash = "sha256:c7ca39512babcd08464b12586908d895efe3477289325cd12ab14768a194ed16"}, - {file = "pyobjc_framework_LocalAuthentication-10.0-py2.py3-none-any.whl", hash = "sha256:6d55c6df7a6337903b3a0c61e48c6e1fe7059005024885244ff8c937bf570aae"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Security = ">=10.0" - -[[package]] -name = "pyobjc-framework-localauthenticationembeddedui" -version = "10.0" -description = "Wrappers for the framework LocalAuthenticationEmbeddedUI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-LocalAuthenticationEmbeddedUI-10.0.tar.gz", hash = "sha256:dd3e53030a2d71c50f5f9dd5e2e7082672ff741a9adfa3da7efe3c9a7691a86a"}, - {file = "pyobjc_framework_LocalAuthenticationEmbeddedUI-10.0-py2.py3-none-any.whl", hash = "sha256:136725e321929fd840905751adf158b4bba561951984ec75a4e534ef0be76c30"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-LocalAuthentication = ">=10.0" - -[[package]] -name = "pyobjc-framework-mailkit" -version = "10.0" -description = "Wrappers for the framework MailKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MailKit-10.0.tar.gz", hash = "sha256:2611649443f3307c8fbfd78d36276fd854373c0dc4516928d3bc51419b34852e"}, - {file = "pyobjc_framework_MailKit-10.0-py2.py3-none-any.whl", hash = "sha256:b95f61745c01d41b2548ff6b6d0efc1476b718874a115fe0f17c06b5b3a1d300"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-mapkit" -version = "10.0" -description = "Wrappers for the framework MapKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MapKit-10.0.tar.gz", hash = "sha256:35a4ac2a9ae3b13699290a6fb592d1914498e4de1b90a2b60394069cd0a02c5b"}, - {file = "pyobjc_framework_MapKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:af80e9f2debe0dfd0f614fc797b4238cc298eb753704c747667f400dc9f57169"}, - {file = "pyobjc_framework_MapKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cded4f7e60ad9595d7cc665d5738c8fcd5eda41d84475b67a3abc0cd493ab808"}, - {file = "pyobjc_framework_MapKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5d33b49430d2d7229121916c57875218c0943caba6faae9caad948839657768e"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreLocation = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-mediaaccessibility" -version = "10.0" -description = "Wrappers for the framework MediaAccessibility on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MediaAccessibility-10.0.tar.gz", hash = "sha256:0f60ed2ac0bdd4c01457619408230d61a0547b16a19ce0d6770a8f4fa7379fbe"}, - {file = "pyobjc_framework_MediaAccessibility-10.0-py2.py3-none-any.whl", hash = "sha256:20b7d0dfd0680e6b19de9683025e35d2cdbdaa76ddb66ae79fea9c0deb5ac3b5"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-medialibrary" -version = "10.0" -description = "Wrappers for the framework MediaLibrary on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MediaLibrary-10.0.tar.gz", hash = "sha256:5a4909257d6c67245b4687327996a4a3f8c038c31e6ea2ea7cc916fd8e44bed5"}, - {file = "pyobjc_framework_MediaLibrary-10.0-py2.py3-none-any.whl", hash = "sha256:e7d0f3353a954abc801bcdb7c02713f38d76835eb8ff4912fab5d005b95d5459"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-mediaplayer" -version = "10.0" -description = "Wrappers for the framework MediaPlayer on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MediaPlayer-10.0.tar.gz", hash = "sha256:e3c66443fd13e5ddede01f15fdd9b635492edc239c4cd88fa540b866a76c1602"}, - {file = "pyobjc_framework_MediaPlayer-10.0-py2.py3-none-any.whl", hash = "sha256:19afc844bc204e008eac5f59699b93bae84e6235fa030d72651200414b019fc2"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-AVFoundation = ">=10.0" - -[[package]] -name = "pyobjc-framework-mediatoolbox" -version = "10.0" -description = "Wrappers for the framework MediaToolbox on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MediaToolbox-10.0.tar.gz", hash = "sha256:8bd24724b26a0bdcdec7e078261d8777018c9ec275b553dd8e1372afc60778d1"}, - {file = "pyobjc_framework_MediaToolbox-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:73bb05d629914f1eca277e1e7aa9ca429408121a49874259f190ce7e37dbf646"}, - {file = "pyobjc_framework_MediaToolbox-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65475deb4a9da96504df4ad677ff470afb0f50128bd4a140788db923b6638b12"}, - {file = "pyobjc_framework_MediaToolbox-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:68c6340e33f60a23acc375935abad73b876705ad460cf7f09be0bc000d219d15"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-metal" -version = "10.0" -description = "Wrappers for the framework Metal on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Metal-10.0.tar.gz", hash = "sha256:2e50b1fc34b11654a0ecb0d6ea98f691dc5794c53e18cb70f71d6460f68dbbf3"}, - {file = "pyobjc_framework_Metal-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0336f14c65a7e064d283b785487522c52f894d23348b1cc49114a919bb0db32c"}, - {file = "pyobjc_framework_Metal-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9eade923110db0db7a57c11761c8bac0c780fb786493ca7f504261c749184dfc"}, - {file = "pyobjc_framework_Metal-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:33c35ee2bd8062786c103149949713eb6d7d90618d69a2acb9c0b732824cad70"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-metalfx" -version = "10.0" -description = "Wrappers for the framework MetalFX on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MetalFX-10.0.tar.gz", hash = "sha256:79edcf90b59276023a143c637d37a1be563a921f5f73f526bb2d970fc08949a3"}, - {file = "pyobjc_framework_MetalFX-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4ff51b35c29573a5b93b76334d165030055e0cfbf0a8d0b0839f510ca2d9d1ff"}, - {file = "pyobjc_framework_MetalFX-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:eac982bd2e8316e4d6b65d9bac2d62365db65f2f9bf4be4bf1a8111b7b0a08e3"}, - {file = "pyobjc_framework_MetalFX-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:376f78e3edcd67f64ff6c2f682e9e94cdbcddf6bf27a32c73f1b8aefb49fc748"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Metal = ">=10.0" - -[[package]] -name = "pyobjc-framework-metalkit" -version = "10.0" -description = "Wrappers for the framework MetalKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MetalKit-10.0.tar.gz", hash = "sha256:a29951ae30dae738bb9d1bab5bcc6fa1150815f671923b6e6705a10d7bab2f8c"}, - {file = "pyobjc_framework_MetalKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:31f15e033abaad2a6212bafc39e2f5d6e7d6bc7a6c93c6a24fc64a4b2db01fe9"}, - {file = "pyobjc_framework_MetalKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8cf2cc8f7fa79bb919c4b6b864edce5e1cd789a88af07ad8846dec985808940d"}, - {file = "pyobjc_framework_MetalKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d13c0a8ae8e65b87c56ea63d84a26f6cb06dafe0f34beabcf86b930c39088748"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Metal = ">=10.0" - -[[package]] -name = "pyobjc-framework-metalperformanceshaders" -version = "10.0" -description = "Wrappers for the framework MetalPerformanceShaders on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MetalPerformanceShaders-10.0.tar.gz", hash = "sha256:eeb3d9e5b44db876ebc93dd3d492dbc4a52b6fee96558d13a66fb283b7757ee4"}, - {file = "pyobjc_framework_MetalPerformanceShaders-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1094595da9dd3fbcbaff278538ad88871347d6155fe84d1fe2f49737831bb6d6"}, - {file = "pyobjc_framework_MetalPerformanceShaders-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4dbc6e8d902ca9e4ceb3699182006495f9e3da84b1efdbc3821e1ba4c23cf808"}, - {file = "pyobjc_framework_MetalPerformanceShaders-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:fa91c8e325c592e2a3db5940109efca9d874a4c19dd238047a4f7bd327015263"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Metal = ">=10.0" - -[[package]] -name = "pyobjc-framework-metalperformanceshadersgraph" -version = "10.0" -description = "Wrappers for the framework MetalPerformanceShadersGraph on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MetalPerformanceShadersGraph-10.0.tar.gz", hash = "sha256:f9b6be9ef300b82a4d228107122fc153c2763c2a1b3c7e311ded52c565bbcf58"}, - {file = "pyobjc_framework_MetalPerformanceShadersGraph-10.0-py2.py3-none-any.whl", hash = "sha256:824f9721eb724de171c9e4515931a59daacbc743890eef5fe00aa70ad1927f30"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-MetalPerformanceShaders = ">=10.0" - -[[package]] -name = "pyobjc-framework-metrickit" -version = "10.0" -description = "Wrappers for the framework MetricKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MetricKit-10.0.tar.gz", hash = "sha256:15f4d384f95ab3656ae183d2fa15e1c59e91b6a5566a4edd105684a70c79401b"}, - {file = "pyobjc_framework_MetricKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:660bfe9654cb21ac450d3be4d4cbc568e503d4d1b04a97c4c90240be632ec1b9"}, - {file = "pyobjc_framework_MetricKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1f83c96d09a6ef03b39da0df5e21235b32145c726455a7e9be40ec5b2d35b5ba"}, - {file = "pyobjc_framework_MetricKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:526ebe3c0d7a4612608668fdfbcb57cdeb9b5324a9e8b481246040013ae5ce6f"}, - {file = "pyobjc_framework_MetricKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:542745e902eeda8cdcb5ca2f0517ae7832142f53a110ad59bd7c8189f31d65d9"}, - {file = "pyobjc_framework_MetricKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b694b1ed197015f14e7f6c35abc855fe790fbbbe17b16ba224c727e185279fc7"}, - {file = "pyobjc_framework_MetricKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5ff9c29a6dda4a19dbdf40e513f06b00bb8f032d2b98655b030fc3a35e71c2d7"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-mlcompute" -version = "10.0" -description = "Wrappers for the framework MLCompute on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MLCompute-10.0.tar.gz", hash = "sha256:1ffbeeb3f4850c1ffada9b253afd2d4fe4448e0e52861701e1c5ab6a56961526"}, - {file = "pyobjc_framework_MLCompute-10.0-py2.py3-none-any.whl", hash = "sha256:8ba3eba33549a22acfdf589818ede36f65031425c6968eb193a9dad143d3cc64"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-modelio" -version = "10.0" -description = "Wrappers for the framework ModelIO on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ModelIO-10.0.tar.gz", hash = "sha256:1629db056d3bebdd79c582637e48c9da5c5aa76a073439dcb3820e00e3f75227"}, - {file = "pyobjc_framework_ModelIO-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:36510ff0567310da48bc5cdd8b8f63e2bf158eb29b598d7b40e26189546c984e"}, - {file = "pyobjc_framework_ModelIO-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:91aa11bc6005b98c73541eebbdb69caf71b5ef4a9d1da032a58ef90b043e4b80"}, - {file = "pyobjc_framework_ModelIO-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:91a80adda076760390ea00cd39d861384455f794673924a923fa7957e4225c52"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-multipeerconnectivity" -version = "10.0" -description = "Wrappers for the framework MultipeerConnectivity on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-MultipeerConnectivity-10.0.tar.gz", hash = "sha256:c2641b9c6d2eb2dccd3c69417f5291bd141a23afc3835f7a7822a8cfa45a1153"}, - {file = "pyobjc_framework_MultipeerConnectivity-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:71cbe835d30a81ec7fcdd3706344dcc5351af4eaa9cf17dada28b88023549953"}, - {file = "pyobjc_framework_MultipeerConnectivity-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:feeb828c8005bd9a941ec437ad1c4dc67843a110d9f80e33c6426b0178faef8e"}, - {file = "pyobjc_framework_MultipeerConnectivity-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e3ef039148b7215c3886636a65baad7dbf1083f1c6dc09c782632085f5efeeec"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-naturallanguage" -version = "10.0" -description = "Wrappers for the framework NaturalLanguage on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-NaturalLanguage-10.0.tar.gz", hash = "sha256:00b055806a0fe096c8d9e2af0f610951ef0fc892d2f496301f2bda794bca781a"}, - {file = "pyobjc_framework_NaturalLanguage-10.0-py2.py3-none-any.whl", hash = "sha256:8924630ff802486dd16a426d75fddfc7e6cd917fecd5ff3902b84107051130cb"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-netfs" -version = "10.0" -description = "Wrappers for the framework NetFS on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-NetFS-10.0.tar.gz", hash = "sha256:31becccbbff5cb4336b736e97f61f92d5df0c40b5e3be7d4e5964527e1b1e3b4"}, - {file = "pyobjc_framework_NetFS-10.0-py2.py3-none-any.whl", hash = "sha256:1ad29eb81bd4774259377a716fa3dd8b3e105e5f8021e295f640a8e036847cc0"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-network" -version = "10.0" -description = "Wrappers for the framework Network on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Network-10.0.tar.gz", hash = "sha256:4e92b1271f999dea0297a844cc101b5c0c908168428d77caab054d25ca8e4e69"}, - {file = "pyobjc_framework_Network-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2402ddcac58735333420a4e467eb415df59f54cf893cd4401f81cce64449dd77"}, - {file = "pyobjc_framework_Network-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:93327d81b58294065f6d2f6db746e992c218cab94b992f5c778dd15a4ecc6f51"}, - {file = "pyobjc_framework_Network-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:53fa04ba5e0ecdc40dd0139074740d4bc3459c829ef550b89141e4cc71562c5a"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-networkextension" -version = "10.0" -description = "Wrappers for the framework NetworkExtension on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-NetworkExtension-10.0.tar.gz", hash = "sha256:cd17420c9763c240343fcfedaddff11db8c0f4f1b54c060c24d6f414234d6b5d"}, - {file = "pyobjc_framework_NetworkExtension-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:fa42c5e57247827647fcbc63341f38799403dba28c5e1ebc68fae57b84727f10"}, - {file = "pyobjc_framework_NetworkExtension-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:301bb43a39069af6e28dfd73de4dee48bd87e0c0473d605b58ab92ce7d1cface"}, - {file = "pyobjc_framework_NetworkExtension-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:325b67c507d69f0d97ad5e612bea68426f5523fae2e8f39792a8c46a88d2067d"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-notificationcenter" -version = "10.0" -description = "Wrappers for the framework NotificationCenter on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-NotificationCenter-10.0.tar.gz", hash = "sha256:64f85eaea8e8811afbfa265e56d3d07ab8b0e57a4a7b5b33a9f72a50a3ede83b"}, - {file = "pyobjc_framework_NotificationCenter-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d06c29f7fd1f0c8b0f7867c80475dfb4d8df491cb92a48932befe47810b6c440"}, - {file = "pyobjc_framework_NotificationCenter-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:95960b70654160083e93cb7c47663a045a68b00ce457e408046062c705b2056e"}, - {file = "pyobjc_framework_NotificationCenter-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1b9534f6a07a6cfeb1a5b5585178a58fb544e2f0f58935afec5ba668b7567170"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-opendirectory" -version = "10.0" -description = "Wrappers for the framework OpenDirectory on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-OpenDirectory-10.0.tar.gz", hash = "sha256:94e0313910b343a1e9738a7a006a1651f4d2995125f743576535ecca9cbb141f"}, - {file = "pyobjc_framework_OpenDirectory-10.0-py2.py3-none-any.whl", hash = "sha256:a58211a1cecb4e1d52377dfe60eecdd4579a3dfc44ff50b92cc3bb123a413189"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-osakit" -version = "10.0" -description = "Wrappers for the framework OSAKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-OSAKit-10.0.tar.gz", hash = "sha256:eaf442ca46219c19f14d3f12612a37325ab7d2a9b5f67eef64a289877500ea75"}, - {file = "pyobjc_framework_OSAKit-10.0-py2.py3-none-any.whl", hash = "sha256:b87bb4ac330da116c33ffefa2da0b7946ac8a840150da848cafd7fff19f7e674"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-oslog" -version = "10.0" -description = "Wrappers for the framework OSLog on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-OSLog-10.0.tar.gz", hash = "sha256:3a169df2fe5fdbd6ca8db28e5c51d89f8759b369636ea7cc2672cde11f4a09fb"}, - {file = "pyobjc_framework_OSLog-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1c2ac445b647edf4e2e925efc4d2471f9a6952dcb2d5929f1e570946941f622d"}, - {file = "pyobjc_framework_OSLog-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:05eaf07e2d4b5c51a9859d0f5e170f51975268ee14782fa626c51b19740d0e68"}, - {file = "pyobjc_framework_OSLog-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:432861f5bd5ad45b119c1327cb17d1feb5e2a5700d753a79d0abdc49a2123496"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreMedia = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-passkit" -version = "10.0" -description = "Wrappers for the framework PassKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-PassKit-10.0.tar.gz", hash = "sha256:da2c5b12c341e2e826b5345798854219966c7bef4bfdb8df306816877df22abb"}, - {file = "pyobjc_framework_PassKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bb315a01d67865f06c751ca4f590c7340c27847a51ebdb645bd31dc48a07f478"}, - {file = "pyobjc_framework_PassKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2c7c4b12957ac615759e385d0baf02e82f5218049369e60a3e74cbd97f7730a0"}, - {file = "pyobjc_framework_PassKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:78d82720f05df6447579495de6e1cca17a18ce203b670fb67b8d8b7317fe2b46"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-pencilkit" -version = "10.0" -description = "Wrappers for the framework PencilKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-PencilKit-10.0.tar.gz", hash = "sha256:68f19a4d9ebab2d002667dce8820bf200d36cc8e1a2351a47f44f3e1f99bb194"}, - {file = "pyobjc_framework_PencilKit-10.0-py2.py3-none-any.whl", hash = "sha256:162bd4797749247e34414ddfb91336b97ff8c31fa79abe27a2885188cbe3fed8"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-phase" -version = "10.0" -description = "Wrappers for the framework PHASE on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-PHASE-10.0.tar.gz", hash = "sha256:9141baca910edc8935a5f22b82444e3b5a001c9275562752f13adce034529377"}, - {file = "pyobjc_framework_PHASE-10.0-py2.py3-none-any.whl", hash = "sha256:d16c38d58065d22c9b688f0fa753fc0a32d9a24bcda23830dab7fd34105c5432"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-AVFoundation = ">=10.0" - -[[package]] -name = "pyobjc-framework-photos" -version = "10.0" -description = "Wrappers for the framework Photos on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Photos-10.0.tar.gz", hash = "sha256:b284e2ede913081570f862fde99fe22c5f254a36b53105fedad4ce66d4dd93af"}, - {file = "pyobjc_framework_Photos-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5809848be530e5f49fbfbe5c6de6255ebe9127da94eab637a8c4f68cef263c80"}, - {file = "pyobjc_framework_Photos-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2058ced6eccf6ffef45f67175d8486d84b07892056338a71aca609f961b807db"}, - {file = "pyobjc_framework_Photos-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:567bbf0287700a32dd3baa0d94da63dd4eece13f622e12ee011a269704880d03"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-photosui" -version = "10.0" -description = "Wrappers for the framework PhotosUI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-PhotosUI-10.0.tar.gz", hash = "sha256:aa521325e7c86d1c739306cd5a14f3f7f69f5db654dc8884f1630001ad72aa7c"}, - {file = "pyobjc_framework_PhotosUI-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9234c231d6e970fabf1b609933e362bade59d6fd40ebfba0b0bfefec7603308"}, - {file = "pyobjc_framework_PhotosUI-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6812342b1588e4675afacff5e6af1376e1569784081699e7c2e865c206f78b27"}, - {file = "pyobjc_framework_PhotosUI-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:794972421c21f541898a05fb3ffc7efc027c15b5f3b19af9cafd424cb5c29613"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-preferencepanes" -version = "10.0" -description = "Wrappers for the framework PreferencePanes on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-PreferencePanes-10.0.tar.gz", hash = "sha256:4e25e9f192252e4d76e9c68fbeae6a7bf832f0d3ab8f18561c65689f344b70c8"}, - {file = "pyobjc_framework_PreferencePanes-10.0-py2.py3-none-any.whl", hash = "sha256:992765158f0cae73957178109338bde94bbac5c91ca6e1ada884c3dc43868e18"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-pubsub" -version = "10.0" -description = "Wrappers for the framework PubSub on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-PubSub-10.0.tar.gz", hash = "sha256:b5632265d86bb114444ce56923633c45be930d3ff224fc76390d2711742df0f8"}, - {file = "pyobjc_framework_PubSub-10.0-py2.py3-none-any.whl", hash = "sha256:7d04a4594c232650f4caf3dbb7d3e6e9c7ec1e87847c147bb4f1c5d412efe5ce"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-pushkit" -version = "10.0" -description = "Wrappers for the framework PushKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-PushKit-10.0.tar.gz", hash = "sha256:54e0b9f3374ba26bdd2c08993080862e7dfc5ccd5c74ad2d5c1c4f9c4c0caa32"}, - {file = "pyobjc_framework_PushKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8fa158ef13bb6a6e0d5cbb25b60cc7f82ed8f048fccedbe6f38df5d27ae8ff26"}, - {file = "pyobjc_framework_PushKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f50e93124ed0df1c9d3c83f24d877bc286c8a3005fc59b85864c4675cfe078a8"}, - {file = "pyobjc_framework_PushKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:014a293a5d11f4213c222fb10284094eed85a37ce8dc5543a5b5e68ed3fb628d"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-quartz" -version = "10.0" -description = "Wrappers for the Quartz frameworks on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Quartz-10.0.tar.gz", hash = "sha256:ff7c938d9c8adff87d577d63e58f9be6e4bc75274384715fa7a20032a1ce8b0e"}, - {file = "pyobjc_framework_Quartz-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f06be24fdd2112c9f5b96ede54ec48ad7623e107b85ebbd5b4155d0b1da4d45f"}, - {file = "pyobjc_framework_Quartz-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:52848a5e283a508c6895a73cb0c950bd4dca9b1186b70dd73ddc8f436d64fd42"}, - {file = "pyobjc_framework_Quartz-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0cc89890de411a341e90d2c4148831b6d241fca66e734b5470d27869c04e33c"}, - {file = "pyobjc_framework_Quartz-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc8d6edcdb0bb3dab4dbe6e6a6d420c28aa0caca53715a3e49d7f299601a723f"}, - {file = "pyobjc_framework_Quartz-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c4b03fade2c4aff2682cd5eae8469f3f15e089c7dd09641e24e5b54d015edfae"}, - {file = "pyobjc_framework_Quartz-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6975be7f776ad4c3a41655b90d67e45a9c7fa2d715b189d8599e8d227f790280"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-quicklookthumbnailing" -version = "10.0" -description = "Wrappers for the framework QuickLookThumbnailing on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-QuickLookThumbnailing-10.0.tar.gz", hash = "sha256:13858592b6cd03c26e0020de47721d74ba992f44e9030ef70f47a99d7660b71c"}, - {file = "pyobjc_framework_QuickLookThumbnailing-10.0-py2.py3-none-any.whl", hash = "sha256:7572750f84477bd9ef4bd6d1fbb88dd3fb3b39567b004307b347b97de861163a"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-replaykit" -version = "10.0" -description = "Wrappers for the framework ReplayKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ReplayKit-10.0.tar.gz", hash = "sha256:83a95c5c95d1a1af731fc9fba71e194d13ceded46799422908d8f95376a4a5ac"}, - {file = "pyobjc_framework_ReplayKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:dea16e14c493a6dc976a4d762fd09b5713dac70f49390df07ac0457dc4fdc759"}, - {file = "pyobjc_framework_ReplayKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1b87a5400c05eba3734bfdd5110a9ef186b98aae0c36a1de3a6dacf3802f5c4f"}, - {file = "pyobjc_framework_ReplayKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e2edfcdf3140bf111e21471fbfdc8fc105e3475c342b1ecd240a8d3e8b8ac368"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-safariservices" -version = "10.0" -description = "Wrappers for the framework SafariServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SafariServices-10.0.tar.gz", hash = "sha256:7f7a477b77b17161e22bdddc8a16fb3000eeccc430a730cb144e1a84a5f6e4e3"}, - {file = "pyobjc_framework_SafariServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:60edbbb667e26654a197f3e72528094f63a1aafb20f73bbd4b182e988275edd2"}, - {file = "pyobjc_framework_SafariServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed51d96673f579e3f4379e1130f3e2cde1c427f916e3172332acd948314f3b7f"}, - {file = "pyobjc_framework_SafariServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:91e336dce2622fdf87d5da8de4a43746ca13f3e85410102da067e1bf0dc4eecb"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-safetykit" -version = "10.0" -description = "Wrappers for the framework SafetyKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SafetyKit-10.0.tar.gz", hash = "sha256:8f6408bdd4ba749d1840700e1a7f1719a5068ae15a2dfdab9d533333b2adda20"}, - {file = "pyobjc_framework_SafetyKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:18d26db21af00838d907527ec1edfcd59217561cc86ed2cfa4fad4788f58ecf7"}, - {file = "pyobjc_framework_SafetyKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:208285c0460a0ed9459c2c0c636fee941a3f2644d07832533140f22de3cc9f11"}, - {file = "pyobjc_framework_SafetyKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b94d9f17bcaef877dad6024b6990265a9e6d9152bd7f734539bf5da9e95e2a91"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-scenekit" -version = "10.0" -description = "Wrappers for the framework SceneKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SceneKit-10.0.tar.gz", hash = "sha256:205a6706ffe271f3961255f1c55ab60b47d797c7a4154a5c9cc0a3b263c433d6"}, - {file = "pyobjc_framework_SceneKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:aabc098a6870b34af54be2aaf0010f1050d3a30e8e86a478b48f7cc2046a5bee"}, - {file = "pyobjc_framework_SceneKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b4fd50030066bf71e24c793a3d8200b75051f9b6d6daa5b10eb92663e08f0b64"}, - {file = "pyobjc_framework_SceneKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:326a588c89801f96fc10a7629446336f385f5615d03c0cce10bb18824ac5c021"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-screencapturekit" -version = "10.0" -description = "Wrappers for the framework ScreenCaptureKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ScreenCaptureKit-10.0.tar.gz", hash = "sha256:d6abaccf2620d01af9bcb408fc47713f813839a35899caea8fa0a96a147597b9"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4064835492904889290d450b5f4f7b8147235620be0f2b8c455a8ca03e532779"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3b80a295b2545b0da492f4f5b8df5b62dc3e1b69141fe1b8982901839635d6e1"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d963acbc24f1d2e0bcc3d0a4d5515dc680259ef7c3b6e80159c82a05774c2862"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c9aa29cb9628d8d1afdd7c8d650ccf90c228aabded792058ca82ee72682c44f"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:f69c623e1e11068c1af01c7f2e359941e1287b7e840b4cd93a9de2eddcd608aa"}, - {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eefc981b906e80b6b51694472d9b573f547d6633c7e9e2f160ad464fbb2b36ab"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-CoreMedia = ">=10.0" - -[[package]] -name = "pyobjc-framework-screensaver" -version = "10.0" -description = "Wrappers for the framework ScreenSaver on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ScreenSaver-10.0.tar.gz", hash = "sha256:84b658c81469305f29aaad61ac29aaad4db27ef9e9b8a13568ddb3a6bfbb902d"}, - {file = "pyobjc_framework_ScreenSaver-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6f0e4a278349997ed1b36ae27ebbbeb18d4a8e766f68c65749e861e4388a5f5"}, - {file = "pyobjc_framework_ScreenSaver-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cd0d3104140be9ef10becae43d222ff0904e4cb655cbe83441f51898e20164e0"}, - {file = "pyobjc_framework_ScreenSaver-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ad0826d5cb11e2615c66e1e0576a5a23f35b6b83c05625efca5ac4ce31e06b33"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-screentime" -version = "10.0" -description = "Wrappers for the framework ScreenTime on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ScreenTime-10.0.tar.gz", hash = "sha256:77d927c7aec657902ef5bdc1cb4be44bc3cd3d1ea51c70f66b8b891b9f97e8ff"}, - {file = "pyobjc_framework_ScreenTime-10.0-py2.py3-none-any.whl", hash = "sha256:800cbb0f5e1bc2ef04e1328e6263b5ec7585538e16989265a3fa8c33957744ed"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-scriptingbridge" -version = "10.0" -description = "Wrappers for the framework ScriptingBridge on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ScriptingBridge-10.0.tar.gz", hash = "sha256:dc8ee394c84caabef9512eaf784ba91459b9560556da5fd5762aa7a6ef5e4612"}, - {file = "pyobjc_framework_ScriptingBridge-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cc1d6d40280b183b34ca24b92d28dbe9ad14e351a53e60262209e44b7da1c98c"}, - {file = "pyobjc_framework_ScriptingBridge-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d3a236e10ef6cdd6a57954950cde0dd4833e0f041b8807da2e14e44645b256bb"}, - {file = "pyobjc_framework_ScriptingBridge-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:632cbf4fd887e3cfc17c7b12ff68879e75f3930d0c54600ab72b41ed2d828901"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-searchkit" -version = "10.0" -description = "Wrappers for the framework SearchKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SearchKit-10.0.tar.gz", hash = "sha256:953ade5f21aed098db366673885cd2e8a3e94574e0fb6e0ccebb063ffc8559ed"}, - {file = "pyobjc_framework_SearchKit-10.0-py2.py3-none-any.whl", hash = "sha256:21921a722f3f1e3868ae38c4582c6d51bad35b13290e90cca62802a477d7f8d1"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-CoreServices = ">=10.0" - -[[package]] -name = "pyobjc-framework-security" -version = "10.0" -description = "Wrappers for the framework Security on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Security-10.0.tar.gz", hash = "sha256:89837b93aaae053d80430da6a3dbd6430ca9d889aa43c3d53ed4ce81afa99462"}, - {file = "pyobjc_framework_Security-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:257abf4821df4a9824f970df7b27acd05c8b7a544c424ca29c63c1bf963b0011"}, - {file = "pyobjc_framework_Security-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e4917cfeca742b790a8f5053b39051be83a132e85f5ad9af2cd3a31527960143"}, - {file = "pyobjc_framework_Security-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a7d9cae84018bcb6ff2967a9cd158b2298e0c5fd95cf6deef12b4b44464e1797"}, - {file = "pyobjc_framework_Security-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71522a2adc3b30c28508156a510b5b8796d5f6ad003bd35b4d86c121bf4f7957"}, - {file = "pyobjc_framework_Security-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:be52243da7a143e898b8e726201140f4be0bd5803b90e56b22d2cc6ad1edde0f"}, - {file = "pyobjc_framework_Security-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ef948582c47593895e27be1a1401d96b19a8edcbed223fa9cf3185345a2bc117"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-securityfoundation" -version = "10.0" -description = "Wrappers for the framework SecurityFoundation on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SecurityFoundation-10.0.tar.gz", hash = "sha256:9871cc1cb7e15b694c7c406d8125acbe990b28c6b15d5833df53a38906836342"}, - {file = "pyobjc_framework_SecurityFoundation-10.0-py2.py3-none-any.whl", hash = "sha256:c7c8bc25d3297eb6c4684ef0c9680b619a1966ddc0cfd33a2122a46cd7963f57"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Security = ">=10.0" - -[[package]] -name = "pyobjc-framework-securityinterface" -version = "10.0" -description = "Wrappers for the framework SecurityInterface on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SecurityInterface-10.0.tar.gz", hash = "sha256:fb3e660b7e1e2054597a87237a885ca62212c9889702bd634d34792d84fcc9ab"}, - {file = "pyobjc_framework_SecurityInterface-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:997a018d6f550ccb82e33bd33317bc586e2911ce1645533f7d16f27973d1d439"}, - {file = "pyobjc_framework_SecurityInterface-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:573d31308465ad1842352e982f7a64cfcaf9d599fe0765ce02e66d0c452a172a"}, - {file = "pyobjc_framework_SecurityInterface-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:06567ae02e1757070bdd3dd21bb0ff3b214b6fff4635d7b0b575f304b02386d4"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Security = ">=10.0" - -[[package]] -name = "pyobjc-framework-sensitivecontentanalysis" -version = "10.0" -description = "Wrappers for the framework SensitiveContentAnalysis on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SensitiveContentAnalysis-10.0.tar.gz", hash = "sha256:212ebb393b7e8a7d9eebd9025a0cc96e20edd0934e570cd57fd0a8a7e5e6b860"}, - {file = "pyobjc_framework_SensitiveContentAnalysis-10.0-py2.py3-none-any.whl", hash = "sha256:99262f5d8a049973531a44113e9157874bba274ed8541b8b778878c664472042"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-servicemanagement" -version = "10.0" -description = "Wrappers for the framework ServiceManagement on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ServiceManagement-10.0.tar.gz", hash = "sha256:0a578e879adf126b4997feca02b6aebee8fc92ef96e4f1d5d76b53860f8b14fa"}, - {file = "pyobjc_framework_ServiceManagement-10.0-py2.py3-none-any.whl", hash = "sha256:a27685c393c1c91b42c5701e0e18326b58d50f0b0c2a194190bc3078d53b5df1"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-sharedwithyou" -version = "10.0" -description = "Wrappers for the framework SharedWithYou on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SharedWithYou-10.0.tar.gz", hash = "sha256:2d19cd38d54c3c5e85488e6f6264f83638984810d9d1601916abddd0984e6b8d"}, - {file = "pyobjc_framework_SharedWithYou-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:706d0c17ad64c22da16eeb1e10a677ea29712164e7f517ac14d866148f2ba437"}, - {file = "pyobjc_framework_SharedWithYou-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:496c9bd0403e1a9896a3cf21d1ae5f1bbbeaefc94322f1063626d2c489b87b8b"}, - {file = "pyobjc_framework_SharedWithYou-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5fa3394b4cf289c798eebb6ee90295221ef2b02b6bd315ac5bd87d9b0ed8b339"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-SharedWithYouCore = ">=10.0" - -[[package]] -name = "pyobjc-framework-sharedwithyoucore" -version = "10.0" -description = "Wrappers for the framework SharedWithYouCore on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SharedWithYouCore-10.0.tar.gz", hash = "sha256:b07e79716e496270a4a84bd2645c1a1dc48b557ff3faaf268c8d5d4c79de9ede"}, - {file = "pyobjc_framework_SharedWithYouCore-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1e13c7b0c057b2467b9a2417066257266570ae6932032aa0eb1e796790ba85d1"}, - {file = "pyobjc_framework_SharedWithYouCore-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:43e730bd17023c51a0895ec3678856662764eebb13c5a19e9d775fc1ee3e5c6c"}, - {file = "pyobjc_framework_SharedWithYouCore-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a029e5ba6e1123baff788888ffb49a4afd83e95320fdcf377423992415c1f037"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-shazamkit" -version = "10.0" -description = "Wrappers for the framework ShazamKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ShazamKit-10.0.tar.gz", hash = "sha256:f5a84113307bac14460abf522ed2e5fc99c5ac1816e652d2bdb437623ada3429"}, - {file = "pyobjc_framework_ShazamKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:71c2a0927df93276abe299ee49d256a76ce7b32015825085dd7cc572d82cf369"}, - {file = "pyobjc_framework_ShazamKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:428e55cb4115eb38e45994a9f339f7909c2f3d62c40aa37478e772e79e455639"}, - {file = "pyobjc_framework_ShazamKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e8071f802b91d0312cd31cb888bb3de0388f01d7c975111c6cd80e571bd5609d"}, - {file = "pyobjc_framework_ShazamKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:266fdbea9491deb29aa34f08b26a9016d9823585348d4e2714acbb5bf133e4f3"}, - {file = "pyobjc_framework_ShazamKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:ce7a1b842dfe4af9e523ac5635297276810e85fc734be43d5a2ae0237b25acfa"}, - {file = "pyobjc_framework_ShazamKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d2694a3a242844f324a5983fbb8594a7087722308b3777c7cd6d2435387cdbd"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-social" -version = "10.0" -description = "Wrappers for the framework Social on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Social-10.0.tar.gz", hash = "sha256:29c4d039b25a73d0499ae37d5eba9c30c12e68209cb85f1bdd94b78274421764"}, - {file = "pyobjc_framework_Social-10.0-py2.py3-none-any.whl", hash = "sha256:05d2cc1b62e2c1ffbe9ed8868e70fb846eb0f4d7157b87c8db77cd13bf0f2a92"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-soundanalysis" -version = "10.0" -description = "Wrappers for the framework SoundAnalysis on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SoundAnalysis-10.0.tar.gz", hash = "sha256:9a2db7edfb506aa296968abfa86f67534b1e02c726aa26c516750c04e7b21d12"}, - {file = "pyobjc_framework_SoundAnalysis-10.0-py2.py3-none-any.whl", hash = "sha256:b2de7dc4ee724fc7940a777ee50aa8b96b836aade84a408737bacf8818b9bde5"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-speech" -version = "10.0" -description = "Wrappers for the framework Speech on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Speech-10.0.tar.gz", hash = "sha256:ffcd35855246432f02ebd96e6eb97da319f3ff108d8b62266e83da9c5eec8497"}, - {file = "pyobjc_framework_Speech-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3664cebcb74d48a131fe02bf2716d72378798ed504fad85e72661f2923a8cd9"}, - {file = "pyobjc_framework_Speech-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6755395bb80b28b2ccf4f02e7a1d850c80e5bf8e590f1359daa2b55a7ec3d108"}, - {file = "pyobjc_framework_Speech-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0ab33eecad4a053a281c6f71f9c6b70d0ad19706887cee3e30509df75cfdbe8d"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-spritekit" -version = "10.0" -description = "Wrappers for the framework SpriteKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SpriteKit-10.0.tar.gz", hash = "sha256:c9db030232e251426575674bbe61b7bdb1cfc4a587a0a1e0d1a59e704658dc30"}, - {file = "pyobjc_framework_SpriteKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4dfaa8a541f60a3c56b38029e4da154f2672cc231c70ceeb558c18af423f822"}, - {file = "pyobjc_framework_SpriteKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5582ef597d381fb9113099bbd07065c565d9db966193f4807327cd09363043b4"}, - {file = "pyobjc_framework_SpriteKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:71042fd8986d0a6544588b42198b8840fe9afd335a3329d4d1dfa4b4ae432327"}, - {file = "pyobjc_framework_SpriteKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c83378276161c3ed043fd8fea6d2d8fcfcb74fb30dbb77a13be6bcdd9914496d"}, - {file = "pyobjc_framework_SpriteKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:93c19908a3aaf1cbf68192f3f312885a2f829dee349d015162d301b379f8046a"}, - {file = "pyobjc_framework_SpriteKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2884623c261a3ae5f12fcad8c30c15e631e5cd86b7f16581b750b07ee31005f3"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-storekit" -version = "10.0" -description = "Wrappers for the framework StoreKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-StoreKit-10.0.tar.gz", hash = "sha256:5835de40067e2ea4374babb41da4ebc0bbe087b770c352bdababfa6871e9590a"}, - {file = "pyobjc_framework_StoreKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e6c607c76edd85444eacf81da5d1823deb5cd1c312f4544d10299f05ae99f87"}, - {file = "pyobjc_framework_StoreKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:df9186ce3ccac15050b3bb90738d9d2abf6617c793d738ac55b95908578c468e"}, - {file = "pyobjc_framework_StoreKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c235266a035b9f3ef9575cd71dda0303e85af90b166cd73466fb668e180c72da"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-symbols" -version = "10.0" -description = "Wrappers for the framework Symbols on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Symbols-10.0.tar.gz", hash = "sha256:16aa2273cb28af944de81039df86bdef04df4b3cf3c776d84d2520fb550a1b6d"}, - {file = "pyobjc_framework_Symbols-10.0-py2.py3-none-any.whl", hash = "sha256:fd1bfc2958d860aef26b15994714abcbb6b3340eda2c67df31c12df0740a661f"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-syncservices" -version = "10.0" -description = "Wrappers for the framework SyncServices on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SyncServices-10.0.tar.gz", hash = "sha256:3060a5b66c42a276b3a5765f7c41fe6a80491685977b0f78b67ef2e8f2325673"}, - {file = "pyobjc_framework_SyncServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:398c95d5b306eddc8534fa7411092d39f8d4aeafa68de7349e890ab7f6d8bc3c"}, - {file = "pyobjc_framework_SyncServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:05cbef4425859dd372545d238cc868d53edc1265be752ebcb73a311d1a2fd9e4"}, - {file = "pyobjc_framework_SyncServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3a89a3b4a627fddcda323c8879e969742a99dc0c312ab4bc04c167ca3d19be5b"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreData = ">=10.0" - -[[package]] -name = "pyobjc-framework-systemconfiguration" -version = "10.0" -description = "Wrappers for the framework SystemConfiguration on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SystemConfiguration-10.0.tar.gz", hash = "sha256:f9ab1759933c77688615810f8278519158273a658f11fc3d75a1a2446fd0f774"}, - {file = "pyobjc_framework_SystemConfiguration-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e64f7a0011ad4a0f86302bd243ada159dfbc25525cfd48270d230fadd24f7dfa"}, - {file = "pyobjc_framework_SystemConfiguration-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:106c98eff3552611fa9bf456f162a3c578958e6c8bea3cb5cfc9478f3cc09005"}, - {file = "pyobjc_framework_SystemConfiguration-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:df000aee4b8350fa413d00370309d0626e503d2773a9882b64b521105e795d8f"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-systemextensions" -version = "10.0" -description = "Wrappers for the framework SystemExtensions on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-SystemExtensions-10.0.tar.gz", hash = "sha256:0c71c2d3db048fd55d931137402e9d0550178f65aacc6597538d4c1c9debb729"}, - {file = "pyobjc_framework_SystemExtensions-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b1908397e50d70abe618383dd4b205fd3d5e8ddd3b9b7ff5d2dd6b330530296a"}, - {file = "pyobjc_framework_SystemExtensions-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6dcb3ca8c0598e35a6f7332f4ced3b83560d10e01254f0db76beaee68c1211c8"}, - {file = "pyobjc_framework_SystemExtensions-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4967721a4ba3d8d5ea812d31f0494fe2f876a26e4eae929fcb3681e062349623"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-threadnetwork" -version = "10.0" -description = "Wrappers for the framework ThreadNetwork on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-ThreadNetwork-10.0.tar.gz", hash = "sha256:8d014eacbd195367f93c24b1cf08690728f42f20b33d5f1fdc38bd6b114b1f13"}, - {file = "pyobjc_framework_ThreadNetwork-10.0-py2.py3-none-any.whl", hash = "sha256:f4f24ad1457e2a89c80f3aa5133e8015e67cbd0e2654d8f08abe0f4690eb7cb3"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-uniformtypeidentifiers" -version = "10.0" -description = "Wrappers for the framework UniformTypeIdentifiers on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-UniformTypeIdentifiers-10.0.tar.gz", hash = "sha256:60254b6d3bce2cc79fee6044ebce828a6c7715b218710e0b5cf3a896bba324ea"}, - {file = "pyobjc_framework_UniformTypeIdentifiers-10.0-py2.py3-none-any.whl", hash = "sha256:04ddee19fcac2cb3f56c69a6a70fe889515d2f03cc2fcecfb5e414c5bf588032"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-usernotifications" -version = "10.0" -description = "Wrappers for the framework UserNotifications on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-UserNotifications-10.0.tar.gz", hash = "sha256:d2646747d4ddbf9abb8e41937364ae074742449e2fd9d33b3138049ad686d555"}, - {file = "pyobjc_framework_UserNotifications-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:965d38c993efa23e0bb24b63d64bfbbf396172bd9846f397ad3c5b896645648f"}, - {file = "pyobjc_framework_UserNotifications-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0c7a535581b0c72c68b91333dc40c7341a05db666cd57ede812b316ed05534c9"}, - {file = "pyobjc_framework_UserNotifications-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3499b79aa5aa735dfe09a73e72f14313d93675c28769e59847ae98ba31114fc3"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-usernotificationsui" -version = "10.0" -description = "Wrappers for the framework UserNotificationsUI on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-UserNotificationsUI-10.0.tar.gz", hash = "sha256:50ee261a50798e8a9dfe5152f66ed18cf584b8fcb3ef9cb6283b61413d376a3a"}, - {file = "pyobjc_framework_UserNotificationsUI-10.0-py2.py3-none-any.whl", hash = "sha256:3732661248a507a61ec551846b5f03d136d719ee402a434f9e77cee044983d75"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-UserNotifications = ">=10.0" - -[[package]] -name = "pyobjc-framework-videosubscriberaccount" -version = "10.0" -description = "Wrappers for the framework VideoSubscriberAccount on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-VideoSubscriberAccount-10.0.tar.gz", hash = "sha256:14f2d6d644df7634d186ae0a64c68317018f3ef191d9ff78658c2bfd9bad394a"}, - {file = "pyobjc_framework_VideoSubscriberAccount-10.0-py2.py3-none-any.whl", hash = "sha256:d7616cc2302372211a415e5afb83f3b52b9582b2f1381ba83b0cf955180ca2ba"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-videotoolbox" -version = "10.0" -description = "Wrappers for the framework VideoToolbox on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-VideoToolbox-10.0.tar.gz", hash = "sha256:3c1d112ca55b56eee913697f044d69f5de6959a7503fd2fcb0822ebad45f7b6c"}, - {file = "pyobjc_framework_VideoToolbox-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:fa1f8442096aa52f6043a02f4ad5566bf3c8783a66e51fcd6f165de700e4244c"}, - {file = "pyobjc_framework_VideoToolbox-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ac6279db7a907d98e2e09ed75aafadfc3dc8662d9e3f0120b48d1e371bc9a9f8"}, - {file = "pyobjc_framework_VideoToolbox-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ff43e2138b5e4ae50ed1373ef436a4d0a9e9cc6b1a296e3687549e519fc73364"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreMedia = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-virtualization" -version = "10.0" -description = "Wrappers for the framework Virtualization on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Virtualization-10.0.tar.gz", hash = "sha256:6387103c8285fe1226f1f35583a11c3aa208d0fea994923cfb405413985cac91"}, - {file = "pyobjc_framework_Virtualization-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ffa8d1a1f588f38bf45b8631f6759ad2d8bb74e4c1c0409f33e99a68bf97b676"}, - {file = "pyobjc_framework_Virtualization-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:62592a97e29d6fb2e999ccfa109b03301e8d7bd91f957b1ddff44dd53afb4b94"}, - {file = "pyobjc_framework_Virtualization-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:95da5017b799c1443edf654cc8d3cbae29f71bb7924976a00c721043d8ccb0a6"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyobjc-framework-vision" -version = "10.0" -description = "Wrappers for the framework Vision on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-Vision-10.0.tar.gz", hash = "sha256:c78244e68b7601682f0940b3d205ca087db4177e3fcc17ee29ae6f0fa811f492"}, - {file = "pyobjc_framework_Vision-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b688e439129d06d7352924e531d5cc49badf499892272fb9e95c99539f941eb7"}, - {file = "pyobjc_framework_Vision-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76943a64cbb9cbdf06653e801e11b69ce721edccc76b6b86cddb027303b65244"}, - {file = "pyobjc_framework_Vision-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7fbb163e66a65e382123f7dc0056a525c3711e0650186ac4d05b09f21a403ca5"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" -pyobjc-framework-CoreML = ">=10.0" -pyobjc-framework-Quartz = ">=10.0" - -[[package]] -name = "pyobjc-framework-webkit" -version = "10.0" -description = "Wrappers for the framework WebKit on macOS" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyobjc-framework-WebKit-10.0.tar.gz", hash = "sha256:847a69aeeb2e743c5ff838628f3a0031e538de4e011e29df52272955ed0b11df"}, - {file = "pyobjc_framework_WebKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:98104c829ecc169fe4ffd0fe499bec21e5fec0aec1974b3edd1ffac1fca0db21"}, - {file = "pyobjc_framework_WebKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:30850ed65f411bd1d54d15ec4937d36856e1e390ea70878022d45c5a08f33aa0"}, - {file = "pyobjc_framework_WebKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:42936e1af1a4cf328ce05e3dcd56dc937f348e7971642c68d33128550b4cb169"}, -] - -[package.dependencies] -pyobjc-core = ">=10.0" -pyobjc-framework-Cocoa = ">=10.0" - -[[package]] -name = "pyparsing" -version = "3.1.1" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyqt5" -version = "5.15.10" -description = "Python bindings for the Qt cross platform application toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyQt5-5.15.10-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:93288d62ebd47b1933d80c27f5d43c7c435307b84d480af689cef2474e87e4c8"}, - {file = "PyQt5-5.15.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:862cea3be95b4b0a2b9678003b3a18edf7bd5eafd673860f58820f246d4bf616"}, - {file = "PyQt5-5.15.10-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:b89478d16d4118664ff58ed609e0a804d002703c9420118de7e4e70fa1cb5486"}, - {file = "PyQt5-5.15.10-cp37-abi3-win32.whl", hash = "sha256:ff99b4f91aa8eb60510d5889faad07116d3340041916e46c07d519f7cad344e1"}, - {file = "PyQt5-5.15.10-cp37-abi3-win_amd64.whl", hash = "sha256:501355f327e9a2c38db0428e1a236d25ebcb99304cd6e668c05d1188d514adec"}, - {file = "PyQt5-5.15.10.tar.gz", hash = "sha256:d46b7804b1b10a4ff91753f8113e5b5580d2b4462f3226288e2d84497334898a"}, -] - -[package.dependencies] -PyQt5-Qt5 = ">=5.15.2" -PyQt5-sip = ">=12.13,<13" - -[[package]] -name = "pyqt5-qt5" -version = "5.15.11" -description = "The subset of a Qt installation needed by PyQt5." -optional = false -python-versions = "*" -files = [ - {file = "PyQt5_Qt5-5.15.11-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:109c418d221538751e4d7755a81c978ee31abbd65facb3f1f361dca74a1b758a"}, - {file = "PyQt5_Qt5-5.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3cccb21942eb49d21fb0193f28e3e8ae3d35395f158e8a9d4d58e23efa3a2ea7"}, -] - -[[package]] -name = "pyqt5-sip" -version = "12.13.0" -description = "The sip module support for PyQt5" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyQt5_sip-12.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca"}, - {file = "PyQt5_sip-12.13.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53"}, - {file = "PyQt5_sip-12.13.0-cp310-cp310-win32.whl", hash = "sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866"}, - {file = "PyQt5_sip-12.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb"}, - {file = "PyQt5_sip-12.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412"}, - {file = "PyQt5_sip-12.13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec"}, - {file = "PyQt5_sip-12.13.0-cp311-cp311-win32.whl", hash = "sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a"}, - {file = "PyQt5_sip-12.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c"}, - {file = "PyQt5_sip-12.13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf"}, - {file = "PyQt5_sip-12.13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876"}, - {file = "PyQt5_sip-12.13.0-cp312-cp312-win32.whl", hash = "sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf"}, - {file = "PyQt5_sip-12.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c"}, - {file = "PyQt5_sip-12.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587"}, - {file = "PyQt5_sip-12.13.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade"}, - {file = "PyQt5_sip-12.13.0-cp38-cp38-win32.whl", hash = "sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce"}, - {file = "PyQt5_sip-12.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137"}, - {file = "PyQt5_sip-12.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92"}, - {file = "PyQt5_sip-12.13.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf"}, - {file = "PyQt5_sip-12.13.0-cp39-cp39-win32.whl", hash = "sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61"}, - {file = "PyQt5_sip-12.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01"}, - {file = "PyQt5_sip-12.13.0.tar.gz", hash = "sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91"}, -] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.9, <3.13" -content-hash = "bc9c1f3cdf8d32483fcb17c5505fcfd64a68f3b639827511fddddc591ea19047" diff --git a/dev/swerve_sim_keyboard/pyproject.toml b/dev/swerve_sim_keyboard/pyproject.toml deleted file mode 100644 index 7225917..0000000 --- a/dev/swerve_sim_keyboard/pyproject.toml +++ /dev/null @@ -1,18 +0,0 @@ -[tool.poetry] -name = "swerve_sim_keyboard" -version = "0.1.0" -description = "Modbot Swerve Simulation" -authors = ["Autoslug UCSC "] -readme = "README.md" - -[tool.poetry.dependencies] -python = ">=3.9, <3.13" -numpy = "^1.26.0" -matplotlib = "^3.8.0" -keyboard = "^0.13.5" -pyqt5 = "^5.15.9" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/dev/swerve_sim_keyboard/src/main.py b/dev/swerve_sim_keyboard/src/main.py deleted file mode 100755 index 8725b5e..0000000 --- a/dev/swerve_sim_keyboard/src/main.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python3 -import numpy as np -import matplotlib -import matplotlib.pyplot as plt - -matplotlib.use("Qt5Agg") -import keyboard - - -################################# -## IMPORTANT: DEPRECATED FILE -## See swerve_sim_joystick -################################# - - - - - -############################################## -### USER DEFINED FUNC ### -############################################## - - -def chassisStateToModuleStates(vel, omega, module_distances) -> list: - states = [] - - for module_distance in module_distances: - states.append(vel + omega * perpendicular(module_distance)) - - return np.array(states) - - -def perpendicular(vector): - return np.array([-vector[1], vector[0]]) - - -def normalizeModules(moduleStates): - max_speed = max([np.linalg.norm(vec) for vec in moduleStates]) - if max_speed <= 1: - return moduleStates - - new_states = [] - for state in moduleStates: - new_states.append(state / max_speed) - - return np.array(new_states) - - -def plot_vec(vector, origin=[0, 0], color="black"): - plt.quiver( - *origin, - vector[0], - vector[1], - color=color, - angles="xy", - scale_units="xy", - scale=1 - ) - - -################################## -### MAIN ### -################################## - -if __name__ == "__main__": - # module radius, i.e distance of modules from center - R = 5 - # dt, the delta time of the "animation" - DT = 0.01 - angle_1 = 3.0 / 6.0 * np.pi - angle_2 = 7.0 / 6.0 * np.pi - angle_3 = 11.0 / 6.0 * np.pi - modules = ( - np.array( - [ - [np.cos(angle_1), np.sin(angle_1)], - [np.cos(angle_2), np.sin(angle_2)], - [np.cos(angle_3), np.sin(angle_3)], - ] - ) - * R - ) - - plot_vec([modules[:, 0], modules[:, 1]], origin=[[0, 0, 0], [0, 0, 0]]) - - plt.pause(DT) - - omega = 0 - - while True: # making a loop - try: - # QUIT BY PRESSING Q ANYTIME - if keyboard.is_pressed("q"): - print("Q pressed, quitting...") - break - - # get keyboard inputs - left = 1 if keyboard.is_pressed("left") else 0 - right = 1 if keyboard.is_pressed("right") else 0 - up = 1 if keyboard.is_pressed("up") else 0 - down = 1 if keyboard.is_pressed("down") else 0 - - # good enough for testing, just very basic 8 directions - dir = ( - np.array([-1, 0]) * left - + np.array([1, 0]) * right - + np.array([0, 1]) * up - + np.array([0, -1]) * down - ) - - # if not zero (avoid divide by zero error) then make it a unit vector - if dir.sum() != 0: - dir = dir / np.linalg.norm(dir) - - # W and E to rotate - if keyboard.is_pressed("w"): - if omega < 6: - omega += 0.75 - elif keyboard.is_pressed("e"): - if omega > -6: - omega -= 0.75 - else: - if omega < 0.75 and omega > -0.75: - omega = 0 - elif omega > 0: - omega -= 0.75 - elif omega < 0: - omega += 0.75 - - # spin the modules by the raidans/sec * sec - angle_1 += omega * DT - angle_2 += omega * DT - angle_3 += omega * DT - modules = ( - np.array( - [ - [np.cos(angle_1), np.sin(angle_1)], - [np.cos(angle_2), np.sin(angle_2)], - [np.cos(angle_3), np.sin(angle_3)], - ] - ) - * R - ) - module_dirs = chassisStateToModuleStates(dir, omega / R, modules) - - module_dirs = normalizeModules(module_dirs) * 2 - - # i spent too much time writing these three lines of code fml - plt.xlim(-2 * R, 2 * R) - plt.ylim(-2 * R, 2 * R) - plt.gca().set_aspect("equal", adjustable="box") - - # [:,0] is fancy way to get all first elements of the lists - plot_vec([modules[:, 0], modules[:, 1]], origin=[[0, 0, 0], [0, 0, 0]]) - plot_vec( - [2 * module_dirs[:, 0], 2 * module_dirs[:, 1]], - origin=[modules[:, 0], modules[:, 1]], - color="r", - ) - - plot_vec(module_dirs[0] + module_dirs[1] + module_dirs[2], color="g") - - plt.pause(DT) - plt.clf() - - except Exception as error: - print("") diff --git a/dev/swerve_sim_keyboard/test/keyboard_test.py b/dev/swerve_sim_keyboard/test/keyboard_test.py deleted file mode 100755 index 129be15..0000000 --- a/dev/swerve_sim_keyboard/test/keyboard_test.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 - -import keyboard as kb - -while True: # making a loop - try: - # QUIT BY PRESSING Q ANYTIME - if kb.is_pressed('q'): - print('Q pressed, quitting...') - break - - # get keyboard inputs - if kb.is_pressed('left'): - left = 1 - print("Pressed Left") - else: - left = 0 - - if kb.is_pressed('right'): - right = 1 - print("Pressed right") - else: - right = 0 - - if kb.is_pressed('up'): - up = 1 - print("Pressed up") - else: - up = 0 - - if kb.is_pressed('down'): - down = 1 - print("Pressed down") - else: - down = 0 - - except: - pass - - \ No newline at end of file From 582b2f803937fc775e40f3d9ea793d5518c9d1a1 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Fri, 3 May 2024 05:42:03 -0700 Subject: [PATCH 49/65] swerve-final -new finalized branch for swerve module -consolidates code into one folder -try to make code match software map -rewrite i2c-controller into separate classes -remove old files & branches --- .vscode/settings.json | 3 - dev/{i2c_drive => _OLD}/CMakeLists.txt | 0 .../basic_swerve_pico.c | 0 dev/{i2c_drive => _OLD}/i2c_drive_pico.c | 0 .../pico_sdk_import.cmake | 0 dev/basic_swerve/Controller.py | 203 ----------- dev/basic_swerve/basic_swerve_pi.py | 132 ------- dev/basic_swerve/joystick_sim.py | 195 ----------- dev/basic_swerve/procon.py | 331 ------------------ dev/basic_swerve/swerve_test.py | 80 ----- .../CMakeLists.txt | 0 .../pico_sdk_import.cmake | 0 .../quadrature_encoder.cpp | 48 +-- .../quadrature_encoder.pio | 0 dev/i2c-comms/pi_i2c.py | 29 -- dev/i2c-comms/pi_spi.c | 63 ---- dev/i2c-comms/pico_i2c.c | 78 ----- dev/i2c-comms/pico_spi.c | 35 -- dev/i2c_drive/Controller.py | 204 ----------- dev/i2c_drive/i2c_drive_pi.py | 59 ---- dev/i2c_drive/procon.py | 331 ------------------ dev/pico_motor_test/CMakeLists.txt | 56 --- dev/pico_motor_test/class_motor_test.cpp | 113 ------ dev/pico_motor_test/cooler_motor_test.cpp | 129 ------- .../CMakeLists.txt | 20 +- dev/pwm_h_bridge_control/SwerveModule.cpp | 117 +++++++ .../pico_sdk_import.cmake | 0 dev/swerve/motor_test.py | 88 ----- .../CMakeLists.txt | 23 +- .../pico_sdk_import.cmake | 0 dev/swerve_decoder/swerve_decoder.cpp | 38 ++ dev/swerve_sim_joystick/joystick_sim.py | 195 ----------- 32 files changed, 201 insertions(+), 2369 deletions(-) delete mode 100644 .vscode/settings.json rename dev/{i2c_drive => _OLD}/CMakeLists.txt (100%) rename dev/{basic_swerve => _OLD}/basic_swerve_pico.c (100%) rename dev/{i2c_drive => _OLD}/i2c_drive_pico.c (100%) rename dev/{basic_swerve => _OLD}/pico_sdk_import.cmake (100%) delete mode 100644 dev/basic_swerve/Controller.py delete mode 100644 dev/basic_swerve/basic_swerve_pi.py delete mode 100644 dev/basic_swerve/joystick_sim.py delete mode 100644 dev/basic_swerve/procon.py delete mode 100644 dev/basic_swerve/swerve_test.py rename dev/{encoder => encoder_script}/CMakeLists.txt (100%) rename dev/{encoder => encoder_script}/pico_sdk_import.cmake (100%) rename dev/{encoder => encoder_script}/quadrature_encoder.cpp (83%) rename dev/{encoder => encoder_script}/quadrature_encoder.pio (100%) delete mode 100644 dev/i2c-comms/pi_i2c.py delete mode 100644 dev/i2c-comms/pi_spi.c delete mode 100644 dev/i2c-comms/pico_i2c.c delete mode 100644 dev/i2c-comms/pico_spi.c delete mode 100644 dev/i2c_drive/Controller.py delete mode 100644 dev/i2c_drive/i2c_drive_pi.py delete mode 100644 dev/i2c_drive/procon.py delete mode 100644 dev/pico_motor_test/CMakeLists.txt delete mode 100644 dev/pico_motor_test/class_motor_test.cpp delete mode 100644 dev/pico_motor_test/cooler_motor_test.cpp rename dev/{basic_swerve => pwm_h_bridge_control}/CMakeLists.txt (69%) create mode 100644 dev/pwm_h_bridge_control/SwerveModule.cpp rename dev/{i2c_drive => pwm_h_bridge_control}/pico_sdk_import.cmake (100%) delete mode 100644 dev/swerve/motor_test.py rename dev/{i2c-comms => swerve_decoder}/CMakeLists.txt (67%) rename dev/{pico_motor_test => swerve_decoder}/pico_sdk_import.cmake (100%) create mode 100644 dev/swerve_decoder/swerve_decoder.cpp delete mode 100644 dev/swerve_sim_joystick/joystick_sim.py diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index f0d6e10..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cmake.sourceDirectory": "C:/Users/Name/Documents/modbot/dev/i2c_drive" -} \ No newline at end of file diff --git a/dev/i2c_drive/CMakeLists.txt b/dev/_OLD/CMakeLists.txt similarity index 100% rename from dev/i2c_drive/CMakeLists.txt rename to dev/_OLD/CMakeLists.txt diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/_OLD/basic_swerve_pico.c similarity index 100% rename from dev/basic_swerve/basic_swerve_pico.c rename to dev/_OLD/basic_swerve_pico.c diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/_OLD/i2c_drive_pico.c similarity index 100% rename from dev/i2c_drive/i2c_drive_pico.c rename to dev/_OLD/i2c_drive_pico.c diff --git a/dev/basic_swerve/pico_sdk_import.cmake b/dev/_OLD/pico_sdk_import.cmake similarity index 100% rename from dev/basic_swerve/pico_sdk_import.cmake rename to dev/_OLD/pico_sdk_import.cmake diff --git a/dev/basic_swerve/Controller.py b/dev/basic_swerve/Controller.py deleted file mode 100644 index af696be..0000000 --- a/dev/basic_swerve/Controller.py +++ /dev/null @@ -1,203 +0,0 @@ -import math -import threading - -from inputs import get_gamepad # Import the get_gamepad function from the inputs module -from procon import ProCon # Import the ProCon class from the procon module - - -# This class represents a PS4 Controller -class PS4_Controller(object): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input - self.THRESHOLD = 0.04 # Threshold for joystick deadzone - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread(()) # Start a new thread to monitor the controller - - # This method resets all controller variables to their initial state - def reset_vars(self): - # Initialize all controller variables to 0 - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 - - # This method starts a new thread to monitor the controller - def start_thread(self, thread_args=()): - self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=thread_args - ) - self._monitor_thread.daemon = ( - True # Set the thread as a daemon so it will end when the main program ends - ) - self._monitor_thread.start() # Start the thread - - # This method returns the current state of all buttons/triggers - def read(self): - return [ - self.LeftJoystickY, - self.LeftJoystickX, - self.RightJoystickY, - self.RightJoystickX, - self.LeftTrigger, - self.RightTrigger, - self.LeftBumper, - self.RightBumper, - self.A, - self.B, - self.X, - self.Y, - self.LeftThumb, - self.RightThumb, - self.Back, - self.Start, - self.LeftDPad, - self.RightDPad, - self.UpDPad, - self.DownDPad, - ] - - # This method returns the controller object itself - def read_self(self): - return self - - # This method applies a threshold to a value - def threshold(self, val): - return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 - - def _monitor_controller(self): - while True: - events = get_gamepad() - for event in events: - if event.code == "ABS_Y": - self.LeftJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_X": - self.LeftJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RY": - self.RightJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RX": - self.RightJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_Z": - self.LeftTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "ABS_RZ": - self.RightTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "BTN_TL": - self.LeftBumper = event.state - elif event.code == "BTN_TR": - self.RightBumper = event.state - elif event.code == "BTN_SOUTH": - self.A = event.state - elif event.code == "BTN_NORTH": - self.Y = event.state # previously switched with X - elif event.code == "BTN_WEST": - self.X = event.state # previously switched with Y - elif event.code == "BTN_EAST": - self.B = event.state - elif event.code == "BTN_THUMBL": - self.LeftThumb = event.state - elif event.code == "BTN_THUMBR": - self.RightThumb = event.state - elif event.code == "BTN_SELECT": - self.Back = event.state - elif event.code == "BTN_START": - self.Start = event.state - elif event.code == "BTN_TRIGGER_HAPPY1": - self.LeftDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY2": - self.RightDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY3": - self.UpDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY4": - self.DownDPad = event.state - - -# This class represents the Xbox Controller in WRP used for the CPSRC GEM -class Gem_Xbox_Controller(PS4_Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input - self.THRESHOLD = 0.03 # Threshold for joystick deadzone - - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread(()) # Start a new thread to monitor the controller - - -# This class represents the Nintendo Pro Controller -class Nintendo_Pro_Controller(PS4_Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input - self.THRESHOLD = 0.1 # Threshold for joystick deadzone - self.controller = ProCon() # Initialize the ProCon controller - - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread( - self.procon_callback_func - ) # Start a new thread to monitor the controller - - # This method is called when the ProCon controller state changes - def procon_callback_func(self, buttons, l_stick, r_stick, *_): - # Update the controller variables based on the new state - # The joystick values are normalized between -1 and 1 - # The threshold method is used to apply a deadband to the joystick values - # The button values are either 0 or 1 - self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) - self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) - self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) - self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) - self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) - self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) - self.LeftBumper = buttons[ProCon.Button.L] - self.RightBumper = buttons[ProCon.Button.R] - self.A = buttons[ProCon.Button.A] - self.B = buttons[ProCon.Button.B] - self.X = buttons[ProCon.Button.X] - self.Y = buttons[ProCon.Button.Y] - self.LeftThumb = buttons[ProCon.Button.LS] - self.RightThumb = buttons[ProCon.Button.RS] - self.Back = buttons[ProCon.Button.MINUS] - self.Start = buttons[ProCon.Button.PLUS] - self.LeftDPad = buttons[ProCon.Button.LEFT] - self.RightDPad = buttons[ProCon.Button.RIGHT] - self.UpDPad = buttons[ProCon.Button.UP] - self.DownDPad = buttons[ProCon.Button.DOWN] - - -if __name__ == "__main__": - joy = PS4_Controller() # Initialize a PS4 controller - # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller - # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller - while True: - try: - print(joy.read()) # Print the current state of the controller - except Exception as e: - print("error!", e) # Print any errors that occur - break # Exit the loop if an error occurs diff --git a/dev/basic_swerve/basic_swerve_pi.py b/dev/basic_swerve/basic_swerve_pi.py deleted file mode 100644 index dd40ba2..0000000 --- a/dev/basic_swerve/basic_swerve_pi.py +++ /dev/null @@ -1,132 +0,0 @@ -import fcntl -import os -import time -import struct - -from Controller import PS4_Controller - -I2C_PRIM = 0x0703 # I2C primary address - -# Open i2c devices (sudo apt install i2c-tools) -i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) # File descriptor for the i2c device - -# Set the i2c address of pico -pico_address = 0x08 # Address of the pico on the i2c bus -fcntl.ioctl( - i2c_fd, I2C_PRIM, pico_address -) # Set the address of the i2c device to communicate with - -# Send data to pico -joy = PS4_Controller() # Initialize the controller -delay = 0.05 # Delay between each read/write operation - -import math - -# radius of swerve drive base in meters -radius = 1 - - -# add two vectors -def add_two_vec(v1, v2): - # get difference between two vectors, treating one vector as perpendicular to x axis - theta_diff = v2[1] - v1[1] - theta_diff = theta_diff - - # since vec1 is x axis, vector 1 contributes in only the x axis - # when breaking into components, just add vec2 in x to vec1 magnitude to get x - # vec2 in y to get y - # sqrt(a^2+b^2) to get magnitude, and arctan(y/x) to get angle relative to x (add to vec1 orientation) - x_comp = v1[0] + math.cos(theta_diff) * v2[0] - y_comp = math.sin(theta_diff) * v2[0] - f_mag = math.sqrt(x_comp**2 + y_comp**2) - f_angle = math.atan2(y_comp, x_comp) + v1[1] - f_angle = f_angle - if f_angle < -math.pi: - f_angle += 2 * math.pi - elif f_angle > math.pi: - f_angle -= 2 * math.pi - return [f_mag, f_angle] - - -# input velocity [speed (m/s), orientation (deg)] in local reference frame -# rotation (rad/s) -def convert(v_vec, omega): - - # the vector for each motor in global reference frame is given by adding - # 1) velocity (relative to global reference frame) - # 2) rotation vector (vector perpendicular to each motor relative to the body reference frame) - # because the frame is circular, magnitude of vector is calculated by V = rw (formula for tangential speed relative to radius and rad/s) - - # setting all vectors to velocity relative to local reference frame - m1 = [v_vec[0], v_vec[1]] - m2 = [v_vec[0], v_vec[1] - 2 * math.pi / 3] - m3 = [v_vec[0], v_vec[1] - 4 * math.pi / 3] - - # create magnitude for each vector - rot_mag = omega * radius - dir = 1 - - if rot_mag < 0: - dir *= -1 - rot_mag *= -1 - # add two vectors (in local frame) based on direction and magnitude - m1 = add_two_vec(m1, [rot_mag, math.pi / 2 * dir]) - m2 = add_two_vec(m2, [rot_mag, math.pi / 2 * dir]) - m3 = add_two_vec(m3, [rot_mag, math.pi / 2 * dir]) - - return [m1, m2, m3] - - -while True: # Infinite loop - status = joy.read_self() # Read the status of the controller - x = status.LeftJoystickX # Get the X position of the left joystick - y = -1 * status.LeftJoystickY # Get the Y position of the left joystick - w = status.RightJoystickX # Get the X position of the right joystick - - v = (math.sqrt(x**2 + y**2), math.atan2(y, x)) - m = convert(v, w) - - m1_v = struct.pack("f", m[0][0]) - m1_w = struct.pack("f", math.degrees(m[0][1])) - - m2_v = struct.pack("f", m[1][0]) - m2_w = struct.pack("f", math.degrees(m[1][1])) - - m3_v = struct.pack("f", m[2][0]) - m3_w = struct.pack("f", math.degrees(m[2][1])) - - data = ( - bytes([0xFA]) + m1_v + m1_w + m2_v + m2_w + m3_v + m3_w + bytes([0xFB]) - ) # Prepare the data to be sent - # data = ( - # bytes([0xFA]) - # + struct.pack("f", 0.5) - # + struct.pack("f", 0.5) - # + struct.pack("f", 0.5) - # + struct.pack("f", 0.5) - # + struct.pack("f", 0.5) - # + struct.pack("f", 0.5) - # + bytes([0xFB]) - # ) # Prepare the data to be sent - - try: - os.write(i2c_fd, data) # Write the data to the i2c device - # os.write(i2c_fd, bytes(data)) # Write the data to the i2c device - time.sleep(delay) # Wait for a while - print("Sent data to Pico: ", list(data)) # Print the data that was sent - except OSError: - print( - "Remote I/O Error" - ) # Print an error message if there was a problem with the write operation - - # Read data from pico - try: - incoming_data = os.read(i2c_fd, 1) # Read 1 byte from the i2c device - time.sleep(delay) # Wait for a while - # print( - # "Received data from Pico: ", list(incoming_data) - # ) # Print the data that was received - except TimeoutError: - print("Timeout Error") # Print an error message if there was a timeout error - except OSError: - print("Remote I/O Error") diff --git a/dev/basic_swerve/joystick_sim.py b/dev/basic_swerve/joystick_sim.py deleted file mode 100644 index 4aa9f93..0000000 --- a/dev/basic_swerve/joystick_sim.py +++ /dev/null @@ -1,195 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np -from Controller import (Gem_Xbox_Controller, Nintendo_Pro_Controller, - PS4_Controller) - - -# return the vector perpendicular to the given vector -def perpendicular(vec): - return np.array([-vec[1], vec[0]]) - - -# NOTE: make sure to account for max motor speed when programming real motors, and normalize -# for example, if the swerve math commands one motor to spin higher than it's max speed, -# then it will only spin at the max speed, thus making the ratio of motor powers wrong and the robot will move wrong - - -if __name__ == "__main__": - # joy = Gem_Xbox_Controller() - # joy = Nintendo_Pro_Controller() - joy = PS4_Controller() - - rumble = type(joy) == Nintendo_Pro_Controller - - # robot radius - R = 5 - # dt, the delta time of the "animation" - DT = 0.001 - - # initial robot state - center_pos = np.array([0.0, 0.0]) # center position - module_dirs = ( - np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi - ) # directions of each module, relative to screen - module_pos = np.array( - [ - [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] - for a in module_dirs - ] - ) # absolute positions of each module (as a point) - freeze_pos = ( - center_pos.copy() - ) # position to rotate about when right bumper is pressed - - while True: - try: - # get inputs - joy_input = joy.read_self() - if joy_input.Back: # exit if back button is pressed - print("Exiting") - break - - # TODO: should replace this by standardizing inverts in the Controller.py class - inverts = [False, False, False, False] # Nintendo Pro Controller - # inverts = [False, True, True] # Gem Xbox Controller - - # use joystick inputs to calculate "strafe" movement - left_x = (-1.0 if inverts[0] else 1.0) * round(joy_input.LeftJoystickX, 3) - left_y = (-1.0 if inverts[1] else 1.0) * round(joy_input.LeftJoystickY, 3) - triggers = joy_input.LeftTrigger - joy_input.RightTrigger - - right_x = (-1.0 if inverts[2] else 1.0) * round(joy_input.RightJoystickX, 3) - right_y = (-1.0 if inverts[3] else 1.0) * round(joy_input.RightJoystickY, 3) - - ## LOGIC (begin) - - # get distance between freeze_pos and center_pos - dist = np.hypot( - freeze_pos[0] - center_pos[0], freeze_pos[1] - center_pos[1] - ) - - # if right bumper is not pressed, move robot in direction of joystick & rotate relative to center pos - if not joy_input.RightBumper: - move = np.array([left_x, left_y]) * 1.0 - rotate = 0.1 * triggers - - # if right bumper is pressed and freeze pos is not "inside" robot, rotate robot around freeze pos - elif dist > R: - # calculate vector from freeze to center pos - x = (freeze_pos[0] - center_pos[0]) / dist - y = (freeze_pos[1] - center_pos[1]) / dist - - # calculate new center position, moving robot around freeze pos - # x' = x*cos(theta) - y*sin(theta) - # y' = x*sin(theta) + y*cos(theta) - # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) - # https://academo.org/demos/rotation-about-point/ - move = np.array( - [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] - ) - # rotate robot so direction of modules is the same relative to freeze pos, plus some rotation from triggers - rotate = (-1.0 if left_x > 0 else 1.0) * np.hypot( - move[0], move[1] - ) / dist + 0.1 * triggers - - # if left bumper is pressed, make freeze pos the same as center pos - if joy_input.LeftBumper: - freeze_pos = center_pos.copy() - else: # if left bumper is not pressed, move freeze pos in direction of right joystick - freeze_pos += np.array([right_x, right_y]) * 1.0 - - # if right bumper is not pressed, move freeze pos in direction of right joystick (relative to center pos) - if not joy_input.RightBumper: - freeze_pos += move * 1.0 + np.array([right_x, right_y]) * 1.0 - - # update center position - center_pos += move - - # update module directions - module_dirs += rotate - - ## LOGIC (end) - - # update module positions using module directions and center position - module_pos = np.array( - [ - [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] - for a in module_dirs - ] - ) - - # set box size and aspect ratio for matplotlib plot window - box_scale = 10 - plt.xlim(-box_scale * R, box_scale * R) - plt.ylim(-box_scale * R, box_scale * R) - plt.gca().set_aspect("equal", adjustable="box") - - # array to store module controls (direction & speed of each module) - module_controls = [] - - # plot robot - for i, module in enumerate(module_pos): - # plot line from center to module - plt.plot( - [center_pos[0], module[0]], [center_pos[1], module[1]], "black" - ) - - # calculate module direction vector using robot movement vector & rotation - dir_vec = ( - move - + np.array([-np.sin(module_dirs[i]), np.cos(module_dirs[i])]) - * rotate - * 10 - ) - - # add module direction vector to module_controls as degrees & speed - module_controls.append( - ( - round(np.rad2deg(np.arctan2(dir_vec[1], dir_vec[0])), 3), - round(np.hypot(dir_vec[0], dir_vec[1]), 3), - ) - ) - - # plot module direction vectors - plt.quiver( - module[0], - module[1], - dir_vec[0], - dir_vec[1], - color="red", - angles="xy", - scale_units="xy", - scale=0.5, - ) - - # print(module_controls) - - # plot center direction vector - plt.quiver( - center_pos[0], - center_pos[1], - move[0], - move[1], - color="green", - angles="xy", - scale_units="xy", - scale=0.5, - ) - - # plot line from center to freeze pos - plt.plot( - [center_pos[0], freeze_pos[0]], [center_pos[1], freeze_pos[1]], "b" - ) - - # rumble if robot is outside of box - if rumble and ( - abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R - ): - joy.controller.send_rumble(False, True, 1) - - # pause for DT seconds and clear plot - plt.pause(DT) - plt.clf() - - except Exception as e: - print(e) diff --git a/dev/basic_swerve/procon.py b/dev/basic_swerve/procon.py deleted file mode 100644 index 8e04765..0000000 --- a/dev/basic_swerve/procon.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env python3 - -import math -import time - -import hid # pip install hidapi - - -def to_int16(uint16): - return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 - - -class ProCon: - VENDOR_ID = 0x057E - PRODUCT_ID = 0x2009 - PACKET_SIZE = 64 - CALIBRATION_OFFSET = 0x603D - CALIBRATION_LENGTH = 0x12 - COMMAND_RETRIES = 10 - RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) - RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) - DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) - - class OutputReportID: - RUMBLE_SUBCOMMAND = 0x01 - RUMBLE = 0x10 - COMMAND = 0x80 - - class InputReportID: - SUBCOMMAND_REPLY = 0x21 - CONTROLLER_STATE = 0x30 - COMMAND_ACK = 0x81 - - class CommandID: - HANDSHAKE = 0x02 - HIGH_SPEED = 0x03 - FORCE_USB = 0x04 - - class SubcommandID: - SET_INPUT_REPORT_MODE = 0x03 - SPI_FLASH_READ = 0x10 - SET_PLAYER_LIGHTS = 0x30 - SET_HOME_LIGHT = 0x38 - ENABLE_IMU = 0x40 - SET_IMU_SENSITIVITY = 0x41 - ENABLE_VIBRATION = 0x48 - - class Button: - A = "A" - B = "B" - X = "X" - Y = "Y" - UP = "Up" - DOWN = "Down" - LEFT = "Left" - RIGHT = "Right" - MINUS = "-" - PLUS = "+" - SCREENSHOT = "Screenshot" - HOME = "Home" - L = "L" - ZL = "ZL" - R = "R" - ZR = "ZR" - LS = "LS" - RS = "RS" - - def __init__(self): - self.subcommand_counter = 0 - self.dev = hid.device() - self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) - self.handshake() - self.high_speed() - self.handshake() - self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL - self.rumble_expire = 0 - self.load_stick_calibration() - self.enable_vibration(True) - self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) - self.force_usb() - self.set_player_lights(True, False, False, False) - self.enable_imu(True) - self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) - - def start(self, callback): - while True: - state = self.recv() - if state[0] != ProCon.InputReportID.CONTROLLER_STATE: - continue - buttons = { - ProCon.Button.A: state[3] & 0x08 > 0, - ProCon.Button.B: state[3] & 0x04 > 0, - ProCon.Button.X: state[3] & 0x02 > 0, - ProCon.Button.Y: state[3] & 0x01 > 0, - ProCon.Button.UP: state[5] & 0x02 > 0, - ProCon.Button.DOWN: state[5] & 0x01 > 0, - ProCon.Button.LEFT: state[5] & 0x08 > 0, - ProCon.Button.RIGHT: state[5] & 0x04 > 0, - ProCon.Button.MINUS: state[4] & 0x01 > 0, - ProCon.Button.PLUS: state[4] & 0x02 > 0, - ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, - ProCon.Button.HOME: state[4] & 0x10 > 0, - ProCon.Button.L: state[5] & 0x40 > 0, - ProCon.Button.ZL: state[5] & 0x80 > 0, - ProCon.Button.R: state[3] & 0x40 > 0, - ProCon.Button.ZR: state[3] & 0x80 > 0, - ProCon.Button.LS: state[4] & 0x08 > 0, - ProCon.Button.RS: state[4] & 0x04 > 0, - } - l_x = state[6] | ((state[7] & 0xF) << 8) - l_y = (state[7] >> 4) | (state[8] << 4) - r_x = state[9] | ((state[10] & 0xF) << 8) - r_y = (state[10] >> 4) | (state[11] << 4) - l_x = self.apply_stick_calibration(l_x, 0, 0) - l_y = self.apply_stick_calibration(l_y, 0, 1) - r_x = self.apply_stick_calibration(r_x, 1, 0) - r_y = self.apply_stick_calibration(r_y, 1, 1) - l_stick = (l_x, l_y) - r_stick = (r_x, r_y) - accel = ( - state[13] | state[14] << 8, - state[15] | state[16] << 8, - state[17] | state[18] << 8, - ) - gyro = ( - state[19] | state[20] << 8, - state[21] | state[22] << 8, - state[23] | state[24] << 8, - ) - accel = tuple(map(to_int16, accel)) - gyro = tuple(map(to_int16, gyro)) - battery = (state[2] & 0xF0) >> 4 - callback(buttons, l_stick, r_stick, accel, gyro, battery) - if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: - self.send_rumble(False, False, 0) - - def load_stick_calibration(self): - ok, reply = self.spi_flash_read( - ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH - ) - if not ok: - raise RuntimeError("cannot load stick calibration") - self.stick_calibration = [ - [ - [ - ((reply[27] & 0xF) << 8) | reply[26], - ((reply[24] & 0xF) << 8) | reply[23], - ((reply[21] & 0xF) << 8) | reply[20], - ], - [ - (reply[28] << 4) | (reply[27] >> 4), - (reply[25] << 4) | (reply[24] >> 4), - (reply[22] << 4) | (reply[21] >> 4), - ], - ], - [ - [ - ((reply[33] & 0xF) << 8) | reply[32], - ((reply[30] & 0xF) << 8) | reply[29], - ((reply[36] & 0xF) << 8) | reply[35], - ], - [ - (reply[34] << 4) | (reply[33] >> 4), - (reply[31] << 4) | (reply[30] >> 4), - (reply[37] << 4) | (reply[36] >> 4), - ], - ], - ] - for i in range(len(self.stick_calibration)): - for j in range(len(self.stick_calibration[i])): - for k in range(len(self.stick_calibration[i][j])): - if self.stick_calibration[i][j][k] == 0xFFF: - self.stick_calibration[i][j][k] = 0 - self.stick_extends = [ - [ - [ - -int(self.stick_calibration[0][0][0] * 0.7), - int(self.stick_calibration[0][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[0][1][0] * 0.7), - int(self.stick_calibration[0][1][2] * 0.7), - ], - ], - [ - [ - -int(self.stick_calibration[1][0][0] * 0.7), - int(self.stick_calibration[1][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[1][1][0] * 0.7), - int(self.stick_calibration[1][1][2] * 0.7), - ], - ], - ] - - def apply_stick_calibration(self, value, stick, axis): - value -= self.stick_calibration[stick][axis][1] - if value < self.stick_extends[stick][axis][0]: - self.stick_extends[stick][axis][0] = value - if value > self.stick_extends[stick][axis][1]: - self.stick_extends[stick][axis][1] = value - if value > 0: - return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) - return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) - - def send(self, data): - return self.dev.write(data) == len(data) - - def recv(self): - return self.dev.read(ProCon.PACKET_SIZE) - - def send_command(self, id, wait_for_reply=True): - data = (ProCon.OutputReportID.COMMAND, id) - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True - reply = self.recv() - if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: - return True - return False - - def send_subcommand(self, id, param, wait_for_reply=True): - data = ( - (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - + (id,) - + param - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True, [] - reply = self.recv() - if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: - return True, reply - return False, [] - - def send_rumble(self, low, high, duration): - self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL - self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL - self.rumble_expire = ( - int(time.time() * 1000) + duration if (low or high) and duration else 0 - ) - data = ( - (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if self.send(data): - return True - return False - - def handshake(self): - return self.send_command(ProCon.CommandID.HANDSHAKE) - - def high_speed(self): - return self.send_command(ProCon.CommandID.HIGH_SPEED) - - def force_usb(self): - return self.send_command(ProCon.CommandID.FORCE_USB, False) - - def set_input_report_mode(self, mode): - return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) - - def spi_flash_read(self, addr, l): - param = ( - addr & 0x000000FF, - (addr & 0x0000FF00) >> 8, - (addr & 0x00FF0000) >> 16, - (addr & 0xFF000000) >> 24, - l, - ) - return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) - - def set_player_lights(self, one, two, three, four): - param = (one << 0) | (two << 1) | (three << 2) | (four << 3) - return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) - - def set_home_light(self, brightness): - intensity = 0 - if brightness > 0: - if brightness < 65: - intensity = (brightness + 5) // 10 - else: - intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) - intensity = (intensity & 0xF) << 4 - param = (0x01, intensity, intensity, 0x00) - return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) - - def enable_imu(self, enable): - return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) - - def set_imu_sensitivity(self, sensitivity): - return self.send_subcommand( - ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity - ) - - def enable_vibration(self, enable): - return self.send_subcommand( - ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) - ) - - -def print_state(buttons, l_stick, r_stick, accel, gyro, battery): - print("\33[2JButtons:") - for k, v in buttons.items(): - if v: - print("[{}]".format(k), end=" ") - else: - print(" {} ".format(k), end=" ") - print() - print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) - print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) - print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) - print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) - print("Battery: {}/9".format(battery)) - - -if __name__ == "__main__": - try: - ProCon().start(print_state) - except KeyboardInterrupt: - print("\rGoodbye!") diff --git a/dev/basic_swerve/swerve_test.py b/dev/basic_swerve/swerve_test.py deleted file mode 100644 index 236f265..0000000 --- a/dev/basic_swerve/swerve_test.py +++ /dev/null @@ -1,80 +0,0 @@ -import math - -# radius of swerve drive base in meters -radius = 1 - -# vectors of swerve drive, using [speed (m/s), orientation (deg)] in global reference frame -swerve = [0,0] - -# vectors of motors in global frame -m1 = [0,0] -m2 = [0,0] -m3 = [0,0] - -# add two vectors -def add_two_vec(vec1, vec2): - print(vec1,vec2) - # get difference between two vectors, treating one vector as perpendicular to x axis - theta_diff = vec2[1] - vec1[1] - theta_diff = math.radians(theta_diff) - print(theta_diff) - - # since vec1 is x axis, vector 1 contributes in only the x axis - # when breaking into components, just add vec2 in x to vec1 magnitude to get x - # vec2 in y to get y - # sqrt(a^2+b^2) to get magnitude, and arctan(y/x) to get angle relative to x (add to vec1 orientation) - x_comp = vec1[0] + math.cos(theta_diff) * vec2[0] - print(x_comp) - y_comp = math.sin(theta_diff) * vec2[0] - print(y_comp) - f_mag = math.sqrt(x_comp**2 + y_comp**2) - print(f_mag) - f_angle = math.atan2(y_comp, x_comp) + math.radians(vec1[1]) - print(f_angle) - f_angle = math.degrees(f_angle) - print(f_angle) - if f_angle < -180: - f_angle += 360 - elif f_angle > 180: - f_angle -= 360 - return [f_mag, f_angle] - - -# input velocity [speed (m/s), orientation (deg)] in local reference frame -# rotation (rad/s) -def convert(velocity, rotation): - - # the vector for each motor in global reference frame is given by adding - # 1) velocity (relative to global reference frame) - # 2) rotation vector (vector perpendicular to each motor relative to the body reference frame) - # because the frame is circular, magnitude of vector is calculated by V = rw (formula for tangential speed relative to radius and rad/s) - - # setting all vectors to velocity relative to local reference frame - m1 = [velocity[0], velocity[1]] - m2 = [velocity[0], velocity[1]-120] - m3 = [velocity[0], velocity[1]-240] - - # rotation vector relative to global reference - # three motors, with motor 1 having an offset of 0 relative to frame - # because rotation vectors must be perpendicular, add 90 to each - # to convert from local to global, add orientation of frame in global reference frame - # rot_ang_m1 = 0+90+swerve[1] - # rot_ang_m2 = 120+90+swerve[1] - # rot_ang_m3 = 240+90+swerve[1] - - # create magnitude for each vector - rot_mag = rotation * radius - dir = 1 - - if rot_mag < 0: - dir *= -1 - rot_mag *= -1 - print(rot_mag) - # add two vectors (in local frame) based on direction and magnitude - m1 = add_two_vec(m1, [rot_mag, 90*dir]) - m2 = add_two_vec(m2, [rot_mag, 90*dir]) - m3 = add_two_vec(m3, [rot_mag, 90*dir]) - - print(m1, m2, m3) - -convert([10,0],0) \ No newline at end of file diff --git a/dev/encoder/CMakeLists.txt b/dev/encoder_script/CMakeLists.txt similarity index 100% rename from dev/encoder/CMakeLists.txt rename to dev/encoder_script/CMakeLists.txt diff --git a/dev/encoder/pico_sdk_import.cmake b/dev/encoder_script/pico_sdk_import.cmake similarity index 100% rename from dev/encoder/pico_sdk_import.cmake rename to dev/encoder_script/pico_sdk_import.cmake diff --git a/dev/encoder/quadrature_encoder.cpp b/dev/encoder_script/quadrature_encoder.cpp similarity index 83% rename from dev/encoder/quadrature_encoder.cpp rename to dev/encoder_script/quadrature_encoder.cpp index a1e3079..54f68e2 100644 --- a/dev/encoder/quadrature_encoder.cpp +++ b/dev/encoder_script/quadrature_encoder.cpp @@ -119,27 +119,27 @@ class EncoderFactory static uint encoder_count; }; -uint EncoderFactory::encoder_count = 0; - -int main() -{ - stdio_init_all(); - - // Base pin to connect the A phase of the encoder (yellow wire). - // The B phase must be connected to the next pin (green wire) - const uint PIN_STEER = 14; - const uint PIN_DRIVE = 16; - - Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); - Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK * DEG_PER_ROT); - - while (1) - { - steer.update(20); - drive.update(20); - - printf("steer position %8f, velocity %6f\n", steer.get_pos(), steer.get_velocity()); - printf("drive position %8f, velocity %6f\n", drive.get_pos(), drive.get_velocity()); - sleep_ms(20); - } -} +// uint EncoderFactory::encoder_count = 0; + +// int main() +// { +// stdio_init_all(); + +// // Base pin to connect the A phase of the encoder (yellow wire). +// // The B phase must be connected to the next pin (green wire) +// const uint PIN_STEER = 14; +// const uint PIN_DRIVE = 16; + +// Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); +// Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK * DEG_PER_ROT); + +// while (1) +// { +// steer.update(20); +// drive.update(20); + +// printf("steer position %8f, velocity %6f\n", steer.get_pos(), steer.get_velocity()); +// printf("drive position %8f, velocity %6f\n", drive.get_pos(), drive.get_velocity()); +// sleep_ms(20); +// } +// } diff --git a/dev/encoder/quadrature_encoder.pio b/dev/encoder_script/quadrature_encoder.pio similarity index 100% rename from dev/encoder/quadrature_encoder.pio rename to dev/encoder_script/quadrature_encoder.pio diff --git a/dev/i2c-comms/pi_i2c.py b/dev/i2c-comms/pi_i2c.py deleted file mode 100644 index c912136..0000000 --- a/dev/i2c-comms/pi_i2c.py +++ /dev/null @@ -1,29 +0,0 @@ -import fcntl -import os -import time - -I2C_PRIM = 0x0703 - -# open i2c devices (sudo apt install i2c-tools) -i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) - -# set the i2c address of pico -pico_address = 0x08 -fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) - -# send data to pico -data = [0x01, 0x02, 0x03] # example data -while True: - try: - os.write(i2c_fd, bytes(data)) - time.sleep(0.02) - print("Sent data to Pico: ", list(data)) - except OSError: - print("Remote I/O Error") - # read data from pico - try: - incoming_data = os.read(i2c_fd, 3) # read 3 bytes - time.sleep(0.02) - print("Received data from Pico: ", list(incoming_data)) - except TimeoutError: - print("Timeout Error") diff --git a/dev/i2c-comms/pi_spi.c b/dev/i2c-comms/pi_spi.c deleted file mode 100644 index b98a690..0000000 --- a/dev/i2c-comms/pi_spi.c +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define SPI_DEVICE "/dev/spidev0.0" -#define SPI_SPEED 1000000 // 1MHz - -int main() -{ - int spi_fd; - unsigned char tx_data[] = {0xAA, 0xBB, 0xCC, 0xDD}; - unsigned char rx_data[sizeof(tx_data)]; - - spi_fd = open(SPI_DEVICE, O_RDWR); - if (spi_fd < 0) - { - perror("Error opening SPI device"); - return -1; - } - - int mode = SPI_MODE_0; - if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) == -1) - { - perror("Error setting SPI mode"); - return -1; - } - - if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED) == -1) - { - perror("Error setting SPI speed"); - return -1; - } - - struct spi_ioc_transfer transfer = { - .tx_buf = (unsigned long)tx_data, - .rx_buf = (unsigned long)rx_data, - .len = sizeof(tx_data), - .speed_hz = SPI_SPEED, - .bits_per_word = 8, - }; - - while (1) - { - if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer) == -1) - { - perror("Error during SPI message transfer"); - return -1; - } - - printf("Received data: "); - for (int i = 0; i < sizeof(rx_data); i++) - { - printf(" %02X", rx_data[i]); - } - printf("\n"); - sleep(1); - } - close(spi_fd); - return 0; -} \ No newline at end of file diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c deleted file mode 100644 index 7bde92b..0000000 --- a/dev/i2c-comms/pico_i2c.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include -#include - -#ifndef PICO_DEFAULT_LED_PIN -#warning blink requires a board with a regular LED -#else -const uint LED_PIN = PICO_DEFAULT_LED_PIN; -#endif - -uint8_t outgoing_data[4] = {0x11, 0x12, 0x13, 0x14}; // example data -uint8_t incoming_data[3]; -int data_index = 0; - -static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) -{ - switch (event) - { - case I2C_SLAVE_RECEIVE: // master has written some data - for (int i = 0; i < 3; i++) - { - if (incoming_data[i] == 0x00) - { - incoming_data[i] = i2c_read_byte_raw(i2c); - printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); - gpio_put(LED_PIN, 1); - break; - } - } - break; - case I2C_SLAVE_REQUEST: // master is requesting data - i2c_write_byte_raw(i2c, outgoing_data[data_index]); - printf("Sent data %d: 0x%02X\n ", data_index, outgoing_data[data_index]); - gpio_put(LED_PIN, 0); - data_index++; - if (data_index > 3) - { - data_index = 0; - } - break; - case I2C_SLAVE_FINISH: // master has signalled Stop / Restart - data_index = 0; - for (int i = 0; i < 3; i++) - { - incoming_data[i] = 0x00; - } - printf("reset\n"); - break; - default: - break; - } -} - -int main() -{ - stdio_init_all(); - -#ifndef PICO_DEFAULT_LED_PIN -#warning blink requires a board with a regular LED -#else - gpio_init(LED_PIN); - gpio_set_dir(LED_PIN, GPIO_OUT); -#endif - - // init i2c at 100kHz - i2c_init(i2c0, 100 * 1000); - gpio_set_function(0, GPIO_FUNC_I2C); - gpio_set_function(1, GPIO_FUNC_I2C); - - // set i2c address for pico - i2c_slave_init(i2c0, 0x08, &i2c_handler); - - while (1) - ; - - return 0; -} \ No newline at end of file diff --git a/dev/i2c-comms/pico_spi.c b/dev/i2c-comms/pico_spi.c deleted file mode 100644 index a3d8b18..0000000 --- a/dev/i2c-comms/pico_spi.c +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include "pico/stdlib.h" -#include "hardware/spi.h" - -#define SPI_PORT spi0 -#define PIN_MISO 16 -#define PIN_CS 17 - -int main() -{ - stdio_init_all(); - - spi_init(SPI_PORT, 1000 * 1000); // init at 1MHz - gpio_set_function(PIN_MISO, GPIO_FUNC_SPI); // set pin to SPI mode - - gpi_init(PIN_CS); - gpio_set_dir(PIN_CS, GPIO_OUT); - - while (1) - { - gpio_put(PIN_CS, 1); // set CS high to indiciate start of communication - uint8_t rx_data[4]; - spi_read_blocking(SPI_PORT, 0, rx_data, sizeof(rx_data)); // read data from pi - gpio_put(PIN_CS, 0); // set CS low to indicate end of communication - - printf("Received: "); - for (int i = 0; i < sizeof(rx_data); i++) - { - printf(" %02X", rx_data[i]); - } - printf("\n"); - sleep_ms(1000); - } - return 0; -} \ No newline at end of file diff --git a/dev/i2c_drive/Controller.py b/dev/i2c_drive/Controller.py deleted file mode 100644 index a7e9ace..0000000 --- a/dev/i2c_drive/Controller.py +++ /dev/null @@ -1,204 +0,0 @@ -import math -import threading - -from inputs import \ - get_gamepad # Import the get_gamepad function from the inputs module -from procon import ProCon # Import the ProCon class from the procon module - - -# This class represents a PS4 Controller -class PS4_Controller(object): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input - self.THRESHOLD = 0.03 # Threshold for joystick deadzone - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread(()) # Start a new thread to monitor the controller - - # This method resets all controller variables to their initial state - def reset_vars(self): - # Initialize all controller variables to 0 - self.LeftJoystickY = 0 - self.LeftJoystickX = 0 - self.RightJoystickY = 0 - self.RightJoystickX = 0 - self.LeftTrigger = 0 - self.RightTrigger = 0 - self.LeftBumper = 0 - self.RightBumper = 0 - self.A = 0 - self.X = 0 - self.Y = 0 - self.B = 0 - self.LeftThumb = 0 - self.RightThumb = 0 - self.Back = 0 - self.Start = 0 - self.LeftDPad = 0 - self.RightDPad = 0 - self.UpDPad = 0 - self.DownDPad = 0 - - # This method starts a new thread to monitor the controller - def start_thread(self, thread_args=()): - self._monitor_thread = threading.Thread( - target=self._monitor_controller, args=thread_args - ) - self._monitor_thread.daemon = ( - True # Set the thread as a daemon so it will end when the main program ends - ) - self._monitor_thread.start() # Start the thread - - # This method returns the current state of all buttons/triggers - def read(self): - return [ - self.LeftJoystickY, - self.LeftJoystickX, - self.RightJoystickY, - self.RightJoystickX, - self.LeftTrigger, - self.RightTrigger, - self.LeftBumper, - self.RightBumper, - self.A, - self.B, - self.X, - self.Y, - self.LeftThumb, - self.RightThumb, - self.Back, - self.Start, - self.LeftDPad, - self.RightDPad, - self.UpDPad, - self.DownDPad, - ] - - # This method returns the controller object itself - def read_self(self): - return self - - # This method applies a threshold to a value - def threshold(self, val): - return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 - - def _monitor_controller(self): - while True: - events = get_gamepad() - for event in events: - if event.code == "ABS_Y": - self.LeftJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_X": - self.LeftJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RY": - self.RightJoystickY = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_RX": - self.RightJoystickX = self.threshold( - event.state / self.MAX_JOY_VAL - ) # normalize between -1 and 1 - elif event.code == "ABS_Z": - self.LeftTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "ABS_RZ": - self.RightTrigger = self.threshold( - event.state / self.MAX_TRIG_VAL - ) # normalize between 0 and 1 - elif event.code == "BTN_TL": - self.LeftBumper = event.state - elif event.code == "BTN_TR": - self.RightBumper = event.state - elif event.code == "BTN_SOUTH": - self.A = event.state - elif event.code == "BTN_NORTH": - self.Y = event.state # previously switched with X - elif event.code == "BTN_WEST": - self.X = event.state # previously switched with Y - elif event.code == "BTN_EAST": - self.B = event.state - elif event.code == "BTN_THUMBL": - self.LeftThumb = event.state - elif event.code == "BTN_THUMBR": - self.RightThumb = event.state - elif event.code == "BTN_SELECT": - self.Back = event.state - elif event.code == "BTN_START": - self.Start = event.state - elif event.code == "BTN_TRIGGER_HAPPY1": - self.LeftDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY2": - self.RightDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY3": - self.UpDPad = event.state - elif event.code == "BTN_TRIGGER_HAPPY4": - self.DownDPad = event.state - - -# This class represents the Xbox Controller in WRP used for the CPSRC GEM -class Gem_Xbox_Controller(PS4_Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input - self.THRESHOLD = 0.03 # Threshold for joystick deadzone - - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread(()) # Start a new thread to monitor the controller - - -# This class represents the Nintendo Pro Controller -class Nintendo_Pro_Controller(PS4_Controller): - def __init__(self): - self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input - self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input - self.THRESHOLD = 0.1 # Threshold for joystick deadzone - self.controller = ProCon() # Initialize the ProCon controller - - self.reset_vars() # Reset all controller variables to their initial state - self.start_thread( - self.procon_callback_func - ) # Start a new thread to monitor the controller - - # This method is called when the ProCon controller state changes - def procon_callback_func(self, buttons, l_stick, r_stick, *_): - # Update the controller variables based on the new state - # The joystick values are normalized between -1 and 1 - # The threshold method is used to apply a deadband to the joystick values - # The button values are either 0 or 1 - self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) - self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) - self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) - self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) - self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) - self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) - self.LeftBumper = buttons[ProCon.Button.L] - self.RightBumper = buttons[ProCon.Button.R] - self.A = buttons[ProCon.Button.A] - self.B = buttons[ProCon.Button.B] - self.X = buttons[ProCon.Button.X] - self.Y = buttons[ProCon.Button.Y] - self.LeftThumb = buttons[ProCon.Button.LS] - self.RightThumb = buttons[ProCon.Button.RS] - self.Back = buttons[ProCon.Button.MINUS] - self.Start = buttons[ProCon.Button.PLUS] - self.LeftDPad = buttons[ProCon.Button.LEFT] - self.RightDPad = buttons[ProCon.Button.RIGHT] - self.UpDPad = buttons[ProCon.Button.UP] - self.DownDPad = buttons[ProCon.Button.DOWN] - - -if __name__ == "__main__": - joy = PS4_Controller() # Initialize a PS4 controller - # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller - # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller - while True: - try: - print(joy.read()) # Print the current state of the controller - except Exception as e: - print("error!", e) # Print any errors that occur - break # Exit the loop if an error occurs diff --git a/dev/i2c_drive/i2c_drive_pi.py b/dev/i2c_drive/i2c_drive_pi.py deleted file mode 100644 index 230f98e..0000000 --- a/dev/i2c_drive/i2c_drive_pi.py +++ /dev/null @@ -1,59 +0,0 @@ -import fcntl -import os -import time -import struct - -from Controller import PS4_Controller - -I2C_PRIM = 0x0703 # I2C primary address - -# Open i2c devices (sudo apt install i2c-tools) -i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) # File descriptor for the i2c device - -# Set the i2c address of pico -pico_address = 0x08 # Address of the pico on the i2c bus -fcntl.ioctl( - i2c_fd, I2C_PRIM, pico_address -) # Set the address of the i2c device to communicate with - -# Send data to pico -joy = PS4_Controller() # Initialize the controller -delay = 0.05 # Delay between each read/write operation - -while True: # Infinite loop - status = joy.read_self() # Read the status of the controller - x = status.LeftJoystickX # Get the X position of the left joystick - y = status.RightJoystickY # Get the Y position of the right joystick - joystickswitch = x > 0 # Check if the joystick is moved to the right - - x_b = struct.pack('f', x) - y_b = struct.pack('f', y) - - data = bytes([0xFA]) + \ - x_b + \ - y_b + \ - bytes([0xFB, 0x00]) # Prepare the data to be sent - # data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] - - print(len(data)) - print(bytes(data)) - - try: - os.write(i2c_fd, data) # Write the data to the i2c device - # os.write(i2c_fd, bytes(data)) # Write the data to the i2c device - time.sleep(delay) # Wait for a while - print("Sent data to Pico: ", list(data)) # Print the data that was sent - except OSError: - print( - "Remote I/O Error" - ) # Print an error message if there was a problem with the write operation - - # Read data from pico - try: - incoming_data = os.read(i2c_fd, 1) # Read 1 byte from the i2c device - time.sleep(delay) # Wait for a while - print( - "Received data from Pico: ", list(incoming_data) - ) # Print the data that was received - except TimeoutError: - print("Timeout Error") # Print an error message if there was a timeout error diff --git a/dev/i2c_drive/procon.py b/dev/i2c_drive/procon.py deleted file mode 100644 index 8e04765..0000000 --- a/dev/i2c_drive/procon.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env python3 - -import math -import time - -import hid # pip install hidapi - - -def to_int16(uint16): - return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 - - -class ProCon: - VENDOR_ID = 0x057E - PRODUCT_ID = 0x2009 - PACKET_SIZE = 64 - CALIBRATION_OFFSET = 0x603D - CALIBRATION_LENGTH = 0x12 - COMMAND_RETRIES = 10 - RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) - RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) - DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) - - class OutputReportID: - RUMBLE_SUBCOMMAND = 0x01 - RUMBLE = 0x10 - COMMAND = 0x80 - - class InputReportID: - SUBCOMMAND_REPLY = 0x21 - CONTROLLER_STATE = 0x30 - COMMAND_ACK = 0x81 - - class CommandID: - HANDSHAKE = 0x02 - HIGH_SPEED = 0x03 - FORCE_USB = 0x04 - - class SubcommandID: - SET_INPUT_REPORT_MODE = 0x03 - SPI_FLASH_READ = 0x10 - SET_PLAYER_LIGHTS = 0x30 - SET_HOME_LIGHT = 0x38 - ENABLE_IMU = 0x40 - SET_IMU_SENSITIVITY = 0x41 - ENABLE_VIBRATION = 0x48 - - class Button: - A = "A" - B = "B" - X = "X" - Y = "Y" - UP = "Up" - DOWN = "Down" - LEFT = "Left" - RIGHT = "Right" - MINUS = "-" - PLUS = "+" - SCREENSHOT = "Screenshot" - HOME = "Home" - L = "L" - ZL = "ZL" - R = "R" - ZR = "ZR" - LS = "LS" - RS = "RS" - - def __init__(self): - self.subcommand_counter = 0 - self.dev = hid.device() - self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) - self.handshake() - self.high_speed() - self.handshake() - self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL - self.rumble_expire = 0 - self.load_stick_calibration() - self.enable_vibration(True) - self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) - self.force_usb() - self.set_player_lights(True, False, False, False) - self.enable_imu(True) - self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) - - def start(self, callback): - while True: - state = self.recv() - if state[0] != ProCon.InputReportID.CONTROLLER_STATE: - continue - buttons = { - ProCon.Button.A: state[3] & 0x08 > 0, - ProCon.Button.B: state[3] & 0x04 > 0, - ProCon.Button.X: state[3] & 0x02 > 0, - ProCon.Button.Y: state[3] & 0x01 > 0, - ProCon.Button.UP: state[5] & 0x02 > 0, - ProCon.Button.DOWN: state[5] & 0x01 > 0, - ProCon.Button.LEFT: state[5] & 0x08 > 0, - ProCon.Button.RIGHT: state[5] & 0x04 > 0, - ProCon.Button.MINUS: state[4] & 0x01 > 0, - ProCon.Button.PLUS: state[4] & 0x02 > 0, - ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, - ProCon.Button.HOME: state[4] & 0x10 > 0, - ProCon.Button.L: state[5] & 0x40 > 0, - ProCon.Button.ZL: state[5] & 0x80 > 0, - ProCon.Button.R: state[3] & 0x40 > 0, - ProCon.Button.ZR: state[3] & 0x80 > 0, - ProCon.Button.LS: state[4] & 0x08 > 0, - ProCon.Button.RS: state[4] & 0x04 > 0, - } - l_x = state[6] | ((state[7] & 0xF) << 8) - l_y = (state[7] >> 4) | (state[8] << 4) - r_x = state[9] | ((state[10] & 0xF) << 8) - r_y = (state[10] >> 4) | (state[11] << 4) - l_x = self.apply_stick_calibration(l_x, 0, 0) - l_y = self.apply_stick_calibration(l_y, 0, 1) - r_x = self.apply_stick_calibration(r_x, 1, 0) - r_y = self.apply_stick_calibration(r_y, 1, 1) - l_stick = (l_x, l_y) - r_stick = (r_x, r_y) - accel = ( - state[13] | state[14] << 8, - state[15] | state[16] << 8, - state[17] | state[18] << 8, - ) - gyro = ( - state[19] | state[20] << 8, - state[21] | state[22] << 8, - state[23] | state[24] << 8, - ) - accel = tuple(map(to_int16, accel)) - gyro = tuple(map(to_int16, gyro)) - battery = (state[2] & 0xF0) >> 4 - callback(buttons, l_stick, r_stick, accel, gyro, battery) - if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: - self.send_rumble(False, False, 0) - - def load_stick_calibration(self): - ok, reply = self.spi_flash_read( - ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH - ) - if not ok: - raise RuntimeError("cannot load stick calibration") - self.stick_calibration = [ - [ - [ - ((reply[27] & 0xF) << 8) | reply[26], - ((reply[24] & 0xF) << 8) | reply[23], - ((reply[21] & 0xF) << 8) | reply[20], - ], - [ - (reply[28] << 4) | (reply[27] >> 4), - (reply[25] << 4) | (reply[24] >> 4), - (reply[22] << 4) | (reply[21] >> 4), - ], - ], - [ - [ - ((reply[33] & 0xF) << 8) | reply[32], - ((reply[30] & 0xF) << 8) | reply[29], - ((reply[36] & 0xF) << 8) | reply[35], - ], - [ - (reply[34] << 4) | (reply[33] >> 4), - (reply[31] << 4) | (reply[30] >> 4), - (reply[37] << 4) | (reply[36] >> 4), - ], - ], - ] - for i in range(len(self.stick_calibration)): - for j in range(len(self.stick_calibration[i])): - for k in range(len(self.stick_calibration[i][j])): - if self.stick_calibration[i][j][k] == 0xFFF: - self.stick_calibration[i][j][k] = 0 - self.stick_extends = [ - [ - [ - -int(self.stick_calibration[0][0][0] * 0.7), - int(self.stick_calibration[0][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[0][1][0] * 0.7), - int(self.stick_calibration[0][1][2] * 0.7), - ], - ], - [ - [ - -int(self.stick_calibration[1][0][0] * 0.7), - int(self.stick_calibration[1][0][2] * 0.7), - ], - [ - -int(self.stick_calibration[1][1][0] * 0.7), - int(self.stick_calibration[1][1][2] * 0.7), - ], - ], - ] - - def apply_stick_calibration(self, value, stick, axis): - value -= self.stick_calibration[stick][axis][1] - if value < self.stick_extends[stick][axis][0]: - self.stick_extends[stick][axis][0] = value - if value > self.stick_extends[stick][axis][1]: - self.stick_extends[stick][axis][1] = value - if value > 0: - return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) - return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) - - def send(self, data): - return self.dev.write(data) == len(data) - - def recv(self): - return self.dev.read(ProCon.PACKET_SIZE) - - def send_command(self, id, wait_for_reply=True): - data = (ProCon.OutputReportID.COMMAND, id) - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True - reply = self.recv() - if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: - return True - return False - - def send_subcommand(self, id, param, wait_for_reply=True): - data = ( - (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - + (id,) - + param - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if not self.send(data): - continue - if not wait_for_reply: - return True, [] - reply = self.recv() - if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: - return True, reply - return False, [] - - def send_rumble(self, low, high, duration): - self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL - self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL - self.rumble_expire = ( - int(time.time() * 1000) + duration if (low or high) and duration else 0 - ) - data = ( - (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) - + self.rumble_low - + self.rumble_high - ) - self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF - for _ in range(ProCon.COMMAND_RETRIES): - if self.send(data): - return True - return False - - def handshake(self): - return self.send_command(ProCon.CommandID.HANDSHAKE) - - def high_speed(self): - return self.send_command(ProCon.CommandID.HIGH_SPEED) - - def force_usb(self): - return self.send_command(ProCon.CommandID.FORCE_USB, False) - - def set_input_report_mode(self, mode): - return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) - - def spi_flash_read(self, addr, l): - param = ( - addr & 0x000000FF, - (addr & 0x0000FF00) >> 8, - (addr & 0x00FF0000) >> 16, - (addr & 0xFF000000) >> 24, - l, - ) - return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) - - def set_player_lights(self, one, two, three, four): - param = (one << 0) | (two << 1) | (three << 2) | (four << 3) - return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) - - def set_home_light(self, brightness): - intensity = 0 - if brightness > 0: - if brightness < 65: - intensity = (brightness + 5) // 10 - else: - intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) - intensity = (intensity & 0xF) << 4 - param = (0x01, intensity, intensity, 0x00) - return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) - - def enable_imu(self, enable): - return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) - - def set_imu_sensitivity(self, sensitivity): - return self.send_subcommand( - ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity - ) - - def enable_vibration(self, enable): - return self.send_subcommand( - ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) - ) - - -def print_state(buttons, l_stick, r_stick, accel, gyro, battery): - print("\33[2JButtons:") - for k, v in buttons.items(): - if v: - print("[{}]".format(k), end=" ") - else: - print(" {} ".format(k), end=" ") - print() - print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) - print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) - print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) - print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) - print("Battery: {}/9".format(battery)) - - -if __name__ == "__main__": - try: - ProCon().start(print_state) - except KeyboardInterrupt: - print("\rGoodbye!") diff --git a/dev/pico_motor_test/CMakeLists.txt b/dev/pico_motor_test/CMakeLists.txt deleted file mode 100644 index 95e237d..0000000 --- a/dev/pico_motor_test/CMakeLists.txt +++ /dev/null @@ -1,56 +0,0 @@ -# Generated Cmake Pico project file - -cmake_minimum_required(VERSION 3.5) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) - -# Initialise pico_sdk from installed location -# (note this can come from environment, CMake cache etc) - -set(PICO_BOARD pico CACHE STRING "Board type") - -# Pull in Raspberry Pi Pico SDK (must be before project) -include(pico_sdk_import.cmake) - -if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") - message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") -endif() - -project(cooler_motor_test C CXX ASM) - -# Initialise the Raspberry Pi Pico SDK -pico_sdk_init() - -# Add executable. Default name is the project name, version 0.1 - -# add_executable(cooler_motor_test cooler_motor_test.cpp ) -add_executable(cooler_motor_test class_motor_test.cpp ) - -pico_set_program_name(cooler_motor_test "cooler_motor_test") -pico_set_program_version(cooler_motor_test "0.1") - -pico_enable_stdio_uart(cooler_motor_test 0) -pico_enable_stdio_usb(cooler_motor_test 1) - -# Add the standard library to the build -target_link_libraries(cooler_motor_test - pico_stdlib - pico_i2c_slave - hardware_i2c - hardware_pwm - ) - -# Add the standard include files to the build -target_include_directories(cooler_motor_test PRIVATE - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required -) - -# Add any user requested libraries -target_link_libraries(cooler_motor_test - hardware_i2c - ) - -pico_add_extra_outputs(cooler_motor_test) - diff --git a/dev/pico_motor_test/class_motor_test.cpp b/dev/pico_motor_test/class_motor_test.cpp deleted file mode 100644 index 0af26c5..0000000 --- a/dev/pico_motor_test/class_motor_test.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include -#include -#include -#include -#include - -// digital low on in# pins indicates direction, both high is no signal -#define steer_in1_pin 4 // 1A, forward direction -#define steer_in2_pin 5 // 1B, backward direction - -#define drive_in1_pin 6 // 3A, forward direction -#define drive_in2_pin 7 // 3B, backard direction - -// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed -#define steer_pwm_pin 9 // 2A, steer motor speed -#define drive_pwm_pin 8 // 2B, drive motor speed -#define pwm_slice 4 -#define steer_channel PWM_CHAN_B -#define drive_channel PWM_CHAN_A - -#define count_max 65535 // number of counts in a cycle --> 1/count_max = freq - -class Motor -{ -public: - Motor(uint pin1, uint pin2, uint pwm_pin, uint slice_num, pwm_chan channel, bool slice = true) - { - this->pwm_pin = pwm_pin; - this->pin1 = pin1; - this->pin2 = pin2; - this->slice_num = slice_num; - this->channel = channel; - - // setup pins for pwm functions - gpio_init(pin1); - gpio_init(pin2); - - // set non-pwm pins to output - gpio_set_dir(pin1, GPIO_OUT); - gpio_set_dir(pin2, GPIO_OUT); - - // setup pwm - gpio_set_function(pwm_pin, GPIO_FUNC_PWM); - - // set pwm slice and channel - if (slice) - pwm_set_wrap(slice_num, count_max); - - pwm_set_chan_level(slice_num, channel, 0); - - if (slice) - pwm_set_enabled(slice_num, true); - } - - void set(float power) - { - power = power > 1 ? 1 : power; - power = power < -1 ? -1 : power; - printf("input power: %f\n", power); - - if (power == 0) - { // in1 and in2 are high - gpio_put(this->pin1, 1); - gpio_put(this->pin2, 1); - } - else if (power < 0) - { // in1 is high and in2 is low - gpio_put(this->pin1, 1); - gpio_put(this->pin2, 0); - } - else - { // in1 is low and in2 is high - gpio_put(this->pin1, 0); - gpio_put(this->pin2, 1); - } - pwm_set_chan_level(this->slice_num, this->channel, abs((int)(power * count_max))); - } - -private: - uint pwm_pin; - uint pin1; - uint pin2; - uint slice_num; - pwm_chan channel; -}; - -int main() -{ - // setup stdio for printing - stdio_init_all(); - - Motor steer = Motor(steer_in1_pin, steer_in2_pin, steer_pwm_pin, pwm_slice, steer_channel, true); - Motor drive = Motor(drive_in1_pin, drive_in2_pin, drive_pwm_pin, pwm_slice, drive_channel, false); - - // step size for oscillation - float step = 0.01; - int a = 0; - - while (1) - { - float power = sin(a * step); - - steer.set(power); - drive.set(power); - - a++; - if (a * step >= 6.28) - a = 0; - - sleep_ms(20); - } - return 0; -} diff --git a/dev/pico_motor_test/cooler_motor_test.cpp b/dev/pico_motor_test/cooler_motor_test.cpp deleted file mode 100644 index 3061a73..0000000 --- a/dev/pico_motor_test/cooler_motor_test.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include -#include -#include -#include - -// digital low on in# pins indicates direction, both high is no signal -#define turn_in1_pin 4 // 1A, forward direction -#define turn_in2_pin 5 // 1B, backward direction - -// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed -#define turn_pwm_pin 9 // 2A, turn motor speed -#define wheel_pwm_pin 8 // 2B, wheel motor speed -#define pwm_slice 4 -#define turn_channel PWM_CHAN_B -#define wheel_channel PWM_CHAN_A - -#define wheel_in1_pin 6 // 3A, forward direction -#define wheel_in2_pin 7 // 3B, backard direction - -// #define freq 500 // note: use clock management frequencies to set frequency -// #define duty_cycle 1 -#define count_max 65535 - -void setup() -{ // setup pins for pwm functions - stdio_init_all(); - gpio_init(turn_in1_pin); - gpio_init(turn_in2_pin); - gpio_init(wheel_in1_pin); - gpio_init(wheel_in2_pin); - - // check if default output signal is 0, for now put this in - gpio_put(turn_in1_pin, 0); - gpio_put(turn_in2_pin, 0); - gpio_put(wheel_in1_pin, 0); - gpio_put(wheel_in2_pin, 0); - - gpio_set_dir(turn_in1_pin, GPIO_OUT); - gpio_set_dir(turn_in2_pin, GPIO_OUT); - gpio_set_dir(wheel_in1_pin, GPIO_OUT); - gpio_set_dir(wheel_in2_pin, GPIO_OUT); - - gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); - gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); - pwm_set_wrap(pwm_slice, count_max); - - pwm_set_chan_level(pwm_slice, turn_channel, 0); - pwm_set_chan_level(pwm_slice, wheel_channel, 0); - - pwm_set_enabled(pwm_slice, true); -} - -int main() -{ - setup(); - double x = 0; - double y = 0.5; - - int xflip = 0; - int yflip = 0; - - float step = 0.001; - - while (1) - { - // // turn motor - if (x == 0) - { // in1 and in2 are high - // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 1); - } - else if (x < 0) - { // in1 is high and in2 is low - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 0); - } - else - { // in1 is low and in2 is high - gpio_put(turn_in2_pin, 1); - gpio_put(turn_in1_pin, 0); - } - - // wheel motor - if (y == 0) - { // in1 and in2 are high - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 1); - } - else if (y < 0) - { // in1 is high and in2 is low - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 0); - } - else - { // in1 is low and in2 is high - gpio_put(wheel_in1_pin, 0); - gpio_put(wheel_in2_pin, 1); - } - - pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); - pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(y * count_max))); - // printf("hello world\n"); - if (xflip) - { - x -= step; - } - else - { - x += step; - } - if (yflip) - { - y -= step; - } - else - { - y += step; - } - if (x >= 1 || x <= -1) - xflip = !(xflip); - if (y >= 1 || y <= -1) - yflip = !(yflip); - - printf("x: %f, y: %f\n", x, y); - sleep_ms(20); - } - return 0; -} \ No newline at end of file diff --git a/dev/basic_swerve/CMakeLists.txt b/dev/pwm_h_bridge_control/CMakeLists.txt similarity index 69% rename from dev/basic_swerve/CMakeLists.txt rename to dev/pwm_h_bridge_control/CMakeLists.txt index 7e1ada4..914fc65 100644 --- a/dev/basic_swerve/CMakeLists.txt +++ b/dev/pwm_h_bridge_control/CMakeLists.txt @@ -17,23 +17,23 @@ if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() -project(basic_swerve_pico C CXX ASM) +project(i2c_drive_pico C CXX ASM) # Initialise the Raspberry Pi Pico SDK pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(basic_swerve_pico basic_swerve_pico.c ) +add_executable(i2c_drive_pico i2c_drive_pico.c ) -pico_set_program_name(basic_swerve_pico "basic_swerve_pico") -pico_set_program_version(basic_swerve_pico "0.1") +pico_set_program_name(i2c_drive_pico "i2c_drive_pico") +pico_set_program_version(i2c_drive_pico "0.1") -pico_enable_stdio_uart(basic_swerve_pico 0) -pico_enable_stdio_usb(basic_swerve_pico 1) +pico_enable_stdio_uart(i2c_drive_pico 0) +pico_enable_stdio_usb(i2c_drive_pico 1) # Add the standard library to the build -target_link_libraries(basic_swerve_pico +target_link_libraries(i2c_drive_pico pico_stdlib pico_i2c_slave hardware_i2c @@ -41,15 +41,15 @@ target_link_libraries(basic_swerve_pico ) # Add the standard include files to the build -target_include_directories(basic_swerve_pico PRIVATE +target_include_directories(i2c_drive_pico PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ) # Add any user requested libraries -target_link_libraries(basic_swerve_pico +target_link_libraries(i2c_drive_pico hardware_i2c ) -pico_add_extra_outputs(basic_swerve_pico) +pico_add_extra_outputs(i2c_drive_pico) diff --git a/dev/pwm_h_bridge_control/SwerveModule.cpp b/dev/pwm_h_bridge_control/SwerveModule.cpp new file mode 100644 index 0000000..6208c7c --- /dev/null +++ b/dev/pwm_h_bridge_control/SwerveModule.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include + +#define COUNT_MAX 65535 + +// class to instantiate swerve module +class SwerveModule { + public: + // store all pin ids + SwerveModule(int turnPin, int wheelPin, int pwmPin, int countMax = COUNT_MAX, int swapPwmChan = 0) { + // two motors per module, each requiring 3 pins + // two pins to define h-bridge control and one pin for pwm (since there are 2 motors, there are 2 pwm pins) + // constructor assumes that all pins are in sets of 2 and will be next to eachother on an even interval (e.g pin 0 and 1, pin 2 and 4, etc) + this->turn_in1_pin = turnPin; // turn pin set + this->turn_in2_pin = turnPin + 1; + this->wheel_in1_pin = wheelPin; // wheel pin set + this->wheel_in2_pin = wheelPin + 1; + this->turn_pwm_pin = pwmPin; // pwm pin set + this->wheel_pwm_pin = pwmPin + 1; + // pwm slices take up two gpio pins, assuming you take up two pins for pwm with the first pin being even, + // dividing by 2 is an easier way to get the id without having to specify in constructor + // the slice is whats activated/outputs a signal and is separate from the pins + // the pwm pins must be configured to let the signal pass through + this->pwm_slice = pwmPin / 2; + //code to check & override default values + if(countMax != COUNT_MAX) + this->countMax = countMax; + if(swapPwmChan){ + this->wheelChan = PWM_CHAN_B; + this->turnChan = PWM_CHAN_A; + } + } + + // initialize all pins + void Setup() { + stdio_init_all(); + // h-bridge requires two binary signals for direction, initialize the in1, in2 pins + gpio_init(this->turn_in1_pin); + gpio_init(this->turn_in2_pin); + gpio_init(this->wheel_in1_pin); + gpio_init(this->wheel_in2_pin); + + // TODO: check if default output signal is 0, for now put this in + // sets all output signals to 0 + gpio_put(this->turn_in1_pin, 0); + gpio_put(this->turn_in2_pin, 0); + gpio_put(this->wheel_in1_pin, 0); + gpio_put(this->wheel_in2_pin, 0); + + // define pin output for gpio pins + gpio_set_dir(this->turn_in1_pin, GPIO_OUT); + gpio_set_dir(this->turn_in2_pin, GPIO_OUT); + gpio_set_dir(this->wheel_in1_pin, GPIO_OUT); + gpio_set_dir(this->wheel_in2_pin, GPIO_OUT); + + // define pin output for pwm pins + gpio_set_function(this->turn_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(this->wheel_pwm_pin, GPIO_FUNC_PWM); + + // set max cycle count of pwm slice (this does counting) + pwm_set_wrap(this->pwm_slice, count_max); + + // set output signal to 0 + // pwm pins can output anything from 0 to countmax of the pwm_slice (this outputs based on the counter) + pwm_set_chan_level(this->pwm_slice, this->turn_channel, 0); + pwm_set_chan_level(this->pwm_slice, this->wheel_channel, 0); + + // activate pwm slice + pwm_set_enabled(this->pwm_slice, true); + } + + void Drive(float turn, float wheel) { // currently assuming turn & wheel are from -1 to 1, probably need to change later + if (turn == 0) { // in1 and in2 are high + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + } else if (turn < 0) { // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 0); + } else { // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + gpio_put(turn_in1_pin, 0); + } + + // wheel motor + if (wheel == 0) { // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + } else if (wheel < 0) { // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); + } else { // in1 is low and in2 is high + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 1); + } + + // set pwm pin output as % of slice output + pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(turn * count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(wheel * count_max))); + } + + //private variables for storing pins and constants + private: + int turn_in1_pin; + int turn_in2_pin; + int wheel_in1_pin; + int wheel_in2_pin; + int turn_pwm_pin; + int wheel_pwm_pin; + int pwm_slice; + int wheel_channel = PWM_CHAN_A; + int turn_channel = PWM_CHAN_B; + int countMax = COUNT_MAX; +}; \ No newline at end of file diff --git a/dev/i2c_drive/pico_sdk_import.cmake b/dev/pwm_h_bridge_control/pico_sdk_import.cmake similarity index 100% rename from dev/i2c_drive/pico_sdk_import.cmake rename to dev/pwm_h_bridge_control/pico_sdk_import.cmake diff --git a/dev/swerve/motor_test.py b/dev/swerve/motor_test.py deleted file mode 100644 index c5e6860..0000000 --- a/dev/swerve/motor_test.py +++ /dev/null @@ -1,88 +0,0 @@ -import lgpio # sudo apt install python3-lgpio -from Controller import Controller - - -turn_in1_pin = 17 -turn_in2_pin = 27 -turn_pwm_pin = 22 - -wheel_in1_pin = 11 -wheel_in2_pin = 9 -wheel_pwm_pin = 10 - -freq = 500 -disable = True -joy = Controller() - -h = lgpio.gpiochip_open(0) - -lgpio.gpio_claim_output(h, turn_in1_pin) -lgpio.gpio_claim_output(h, turn_in2_pin) -lgpio.gpio_claim_output(h, wheel_in1_pin) -lgpio.gpio_claim_output(h, wheel_in2_pin) - -exit_count = 0 - -try: - while True: - status = joy.read_self() - - if status.X: - if not disable: - print("disabling!") - disable = True - exit_count += 1 - elif status.B: - if disable: - print("enabling!") - disable = False - exit_count = 0 - - if disable: - lgpio.gpio_write(h, turn_in1_pin, 1) - lgpio.gpio_write(h, turn_in2_pin, 1) - lgpio.gpio_write(h, wheel_in1_pin, 1) - lgpio.gpio_write(h, wheel_in2_pin, 1) - - if exit_count > 50000: - break - - continue - - x = status.LeftJoystickX - y = status.RightJoystickY - - if x == 0: - lgpio.gpio_write(h, turn_in1_pin, 1) - lgpio.gpio_write(h, turn_in2_pin, 1) - elif x < 0: - lgpio.gpio_write(h, turn_in1_pin, 1) - lgpio.gpio_write(h, turn_in2_pin, 0) - else: - lgpio.gpio_write(h, turn_in1_pin, 0) - lgpio.gpio_write(h, turn_in2_pin, 1) - - if y == 0: - lgpio.gpio_write(h, wheel_in1_pin, 1) - lgpio.gpio_write(h, wheel_in2_pin, 1) - elif y < 0: - lgpio.gpio_write(h, wheel_in1_pin, 1) - lgpio.gpio_write(h, wheel_in2_pin, 0) - else: - lgpio.gpio_write(h, wheel_in1_pin, 0) - lgpio.gpio_write(h, wheel_in2_pin, 1) - - lgpio.tx_pwm(h, turn_pwm_pin, freq, round(abs(x) * 100.0, 2)) - lgpio.tx_pwm(h, wheel_pwm_pin, freq, round(abs(y) * 100.0, 2)) -except KeyboardInterrupt: - pass - -lgpio.tx_pwm(h, turn_pwm_pin, freq, 0) -lgpio.gpio_write(h, turn_in1_pin, 1) -lgpio.gpio_write(h, turn_in2_pin, 1) - -lgpio.tx_pwm(h, wheel_pwm_pin, freq, 0) -lgpio.gpio_write(h, wheel_in1_pin, 1) -lgpio.gpio_write(h, wheel_in2_pin, 1) - -lgpio.gpiochip_close(h) diff --git a/dev/i2c-comms/CMakeLists.txt b/dev/swerve_decoder/CMakeLists.txt similarity index 67% rename from dev/i2c-comms/CMakeLists.txt rename to dev/swerve_decoder/CMakeLists.txt index 2fd69ae..914fc65 100644 --- a/dev/i2c-comms/CMakeLists.txt +++ b/dev/swerve_decoder/CMakeLists.txt @@ -1,6 +1,6 @@ # Generated Cmake Pico project file -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.5) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) @@ -17,38 +17,39 @@ if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() -project(pico_i2c C CXX ASM) +project(i2c_drive_pico C CXX ASM) # Initialise the Raspberry Pi Pico SDK pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(pico_i2c pico_i2c.c ) +add_executable(i2c_drive_pico i2c_drive_pico.c ) -pico_set_program_name(pico_i2c "pico_i2c") -pico_set_program_version(pico_i2c "0.1") +pico_set_program_name(i2c_drive_pico "i2c_drive_pico") +pico_set_program_version(i2c_drive_pico "0.1") -pico_enable_stdio_uart(pico_i2c 0) -pico_enable_stdio_usb(pico_i2c 1) +pico_enable_stdio_uart(i2c_drive_pico 0) +pico_enable_stdio_usb(i2c_drive_pico 1) # Add the standard library to the build -target_link_libraries(pico_i2c +target_link_libraries(i2c_drive_pico pico_stdlib pico_i2c_slave hardware_i2c + hardware_pwm ) # Add the standard include files to the build -target_include_directories(pico_i2c PRIVATE +target_include_directories(i2c_drive_pico PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ) # Add any user requested libraries -target_link_libraries(pico_i2c +target_link_libraries(i2c_drive_pico hardware_i2c ) -pico_add_extra_outputs(pico_i2c) +pico_add_extra_outputs(i2c_drive_pico) diff --git a/dev/pico_motor_test/pico_sdk_import.cmake b/dev/swerve_decoder/pico_sdk_import.cmake similarity index 100% rename from dev/pico_motor_test/pico_sdk_import.cmake rename to dev/swerve_decoder/pico_sdk_import.cmake diff --git a/dev/swerve_decoder/swerve_decoder.cpp b/dev/swerve_decoder/swerve_decoder.cpp new file mode 100644 index 0000000..cd605c1 --- /dev/null +++ b/dev/swerve_decoder/swerve_decoder.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include + +// use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + +// small class to store data from read buffer, it converts ints to floats mainly just here to abstract c code +class Decoder { + public: + Decoder(int size){ // initialize class, set size, allocate storage + this->size = size; + float array[size]; + this->floats = array; + for(int i=0;isize; + for(int i=0;ifloats+(i)) = *(((float*) flipped_binary_array)+(size-1-i)); + } + float* GetValues() { + return floats; + } + + private: + float* floats; + int size; +}; \ No newline at end of file diff --git a/dev/swerve_sim_joystick/joystick_sim.py b/dev/swerve_sim_joystick/joystick_sim.py deleted file mode 100644 index 88eb379..0000000 --- a/dev/swerve_sim_joystick/joystick_sim.py +++ /dev/null @@ -1,195 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np -from Controller import (Gem_Xbox_Controller, Nintendo_Pro_Controller, - PS4_Controller) - - -# return the vector perpendicular to the given vector -def perpendicular(vec): - return np.array([-vec[1], vec[0]]) - - -# NOTE: make sure to account for max motor speed when programming real motors, and normalize -# for example, if the swerve math commands one motor to spin higher than it's max speed, -# then it will only spin at the max speed, thus making the ratio of motor powers wrong and the robot will move wrong - - -if __name__ == "__main__": - # joy = Gem_Xbox_Controller() - # joy = Nintendo_Pro_Controller() - joy = PS4_Controller() - - rumble = type(joy) == Nintendo_Pro_Controller - - # robot radius - R = 5 - # dt, the delta time of the "animation" - DT = 0.001 - - # initial robot state - center_pos = np.array([0.0, 0.0]) # center position - module_dirs = ( - np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi - ) # directions of each module, relative to screen - module_pos = np.array( - [ - [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] - for a in module_dirs - ] - ) # absolute positions of each module (as a point) - freeze_pos = ( - center_pos.copy() - ) # position to rotate about when right bumper is pressed - - while True: - try: - # get inputs - joy_input = joy.read_self() - if joy_input.Back: # exit if back button is pressed - print("Exiting") - break - - # TODO: should replace this by standardizing inverts in the Controller.py class - inverts = [False, False, False, False] # Nintendo Pro Controller - # inverts = [False, True, True] # Gem Xbox Controller - - # use joystick inputs to calculate "strafe" movement - left_x = (-1.0 if inverts[0] else 1.0) * round(joy_input.LeftJoystickX, 3) - left_y = (-1.0 if inverts[1] else 1.0) * round(joy_input.LeftJoystickY, 3) - triggers = joy_input.LeftTrigger - joy_input.RightTrigger - - right_x = (-1.0 if inverts[2] else 1.0) * round(joy_input.RightJoystickX, 3) - right_y = (-1.0 if inverts[3] else 1.0) * round(joy_input.RightJoystickY, 3) - - ## LOGIC (begin) - - # get distance between freeze_pos and center_pos - dist = np.hypot( - freeze_pos[0] - center_pos[0], freeze_pos[1] - center_pos[1] - ) - - # if right bumper is not pressed, move robot in direction of joystick & rotate relative to center pos - if not joy_input.RightBumper: - move = np.array([left_x, left_y]) * 1.0 - rotate = 0.1 * triggers - - # if right bumper is pressed and freeze pos is not "inside" robot, rotate robot around freeze pos - elif dist > R: - # calculate vector from freeze to center pos - x = (freeze_pos[0] - center_pos[0]) / dist - y = (freeze_pos[1] - center_pos[1]) / dist - - # calculate new center position, moving robot around freeze pos - # x' = x*cos(theta) - y*sin(theta) - # y' = x*sin(theta) + y*cos(theta) - # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) - # https://academo.org/demos/rotation-about-point/ - move = np.array( - [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] - ) - # rotate robot so direction of modules is the same relative to freeze pos, plus some rotation from triggers - rotate = (-1.0 if left_x > 0 else 1.0) * np.hypot( - move[0], move[1] - ) / dist + 0.1 * triggers - - # if left bumper is pressed, make freeze pos the same as center pos - if joy_input.LeftBumper: - freeze_pos = center_pos.copy() - else: # if left bumper is not pressed, move freeze pos in direction of right joystick - freeze_pos += np.array([right_x, right_y]) * 1.0 - - # if right bumper is not pressed, move freeze pos in direction of right joystick (relative to center pos) - if not joy_input.RightBumper: - freeze_pos += move * 1.0 + np.array([right_x, right_y]) * 1.0 - - # update center position - center_pos += move - - # update module directions - module_dirs += rotate - - ## LOGIC (end) - - # update module positions using module directions and center position - module_pos = np.array( - [ - [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] - for a in module_dirs - ] - ) - - # set box size and aspect ratio for matplotlib plot window - box_scale = 10 - plt.xlim(-box_scale * R, box_scale * R) - plt.ylim(-box_scale * R, box_scale * R) - plt.gca().set_aspect("equal", adjustable="box") - - # array to store module controls (direction & speed of each module) - module_controls = [] - - # plot robot - for i, module in enumerate(module_pos): - # plot line from center to module - plt.plot( - [center_pos[0], module[0]], [center_pos[1], module[1]], "black" - ) - - # calculate module direction vector using robot movement vector & rotation - dir_vec = ( - move - + np.array([-np.sin(module_dirs[i]), np.cos(module_dirs[i])]) - * rotate - * 10 - ) - - # add module direction vector to module_controls as degrees & speed - module_controls.append( - ( - round(np.rad2deg(np.arctan2(dir_vec[1], dir_vec[0])), 3), - round(np.hypot(dir_vec[0], dir_vec[1]), 3), - ) - ) - - # plot module direction vectors - plt.quiver( - module[0], - module[1], - dir_vec[0], - dir_vec[1], - color="red", - angles="xy", - scale_units="xy", - scale=0.5, - ) - - print(module_controls) - - # plot center direction vector - plt.quiver( - center_pos[0], - center_pos[1], - move[0], - move[1], - color="green", - angles="xy", - scale_units="xy", - scale=0.5, - ) - - # plot line from center to freeze pos - plt.plot( - [center_pos[0], freeze_pos[0]], [center_pos[1], freeze_pos[1]], "b" - ) - - # rumble if robot is outside of box - if rumble and ( - abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R - ): - joy.controller.send_rumble(False, True, 1) - - # pause for DT seconds and clear plot - plt.pause(DT) - plt.clf() - - except Exception as e: - print(e) From 96036bcd31edbaf9b0a168d427ef895938d16886 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Sat, 4 May 2024 03:48:58 -0700 Subject: [PATCH 50/65] added swerve decoder and pid barebone unfinished 4 am code, hopefully a step forward? --- dev/pid_control/CMakeLists.txt | 55 ++++++++++++++++++ dev/pid_control/pico_sdk_import.cmake | 73 ++++++++++++++++++++++++ dev/pid_control/pid.cpp | 33 +++++++++++ dev/zeroing_script/CMakeLists.txt | 55 ++++++++++++++++++ dev/zeroing_script/pico_sdk_import.cmake | 73 ++++++++++++++++++++++++ dev/zeroing_script/zeroing.cpp | 31 ++++++++++ 6 files changed, 320 insertions(+) create mode 100644 dev/pid_control/CMakeLists.txt create mode 100644 dev/pid_control/pico_sdk_import.cmake create mode 100644 dev/pid_control/pid.cpp create mode 100644 dev/zeroing_script/CMakeLists.txt create mode 100644 dev/zeroing_script/pico_sdk_import.cmake create mode 100644 dev/zeroing_script/zeroing.cpp diff --git a/dev/pid_control/CMakeLists.txt b/dev/pid_control/CMakeLists.txt new file mode 100644 index 0000000..914fc65 --- /dev/null +++ b/dev/pid_control/CMakeLists.txt @@ -0,0 +1,55 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(i2c_drive_pico C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(i2c_drive_pico i2c_drive_pico.c ) + +pico_set_program_name(i2c_drive_pico "i2c_drive_pico") +pico_set_program_version(i2c_drive_pico "0.1") + +pico_enable_stdio_uart(i2c_drive_pico 0) +pico_enable_stdio_usb(i2c_drive_pico 1) + +# Add the standard library to the build +target_link_libraries(i2c_drive_pico + pico_stdlib + pico_i2c_slave + hardware_i2c + hardware_pwm + ) + +# Add the standard include files to the build +target_include_directories(i2c_drive_pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(i2c_drive_pico + hardware_i2c + ) + +pico_add_extra_outputs(i2c_drive_pico) + diff --git a/dev/pid_control/pico_sdk_import.cmake b/dev/pid_control/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/pid_control/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/pid_control/pid.cpp b/dev/pid_control/pid.cpp new file mode 100644 index 0000000..ca222c7 --- /dev/null +++ b/dev/pid_control/pid.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include + +class pid { //pid loop + public: + pid() { + //init pid constants + } + float calculate(float input, float timeStep){ + float output = this->P * input + this-> I * this -> Integral; + output += this-> D * LastOut - output / timeStep; + Integral+=(output*timeStep) + return output + } + + private: + bool callback(repeating_timer_t *rt){ + //calculate + } + void loop(){ + repeating_timer_t timer; // (add to main, look at periodic sampler) + add_repeating_timer_ms(100, callback, NULL, timer); + } + float P = 1; + float I = 1; + float D = 1; + float Integral = 0; + float LastOut = 0; +}; \ No newline at end of file diff --git a/dev/zeroing_script/CMakeLists.txt b/dev/zeroing_script/CMakeLists.txt new file mode 100644 index 0000000..914fc65 --- /dev/null +++ b/dev/zeroing_script/CMakeLists.txt @@ -0,0 +1,55 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(i2c_drive_pico C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(i2c_drive_pico i2c_drive_pico.c ) + +pico_set_program_name(i2c_drive_pico "i2c_drive_pico") +pico_set_program_version(i2c_drive_pico "0.1") + +pico_enable_stdio_uart(i2c_drive_pico 0) +pico_enable_stdio_usb(i2c_drive_pico 1) + +# Add the standard library to the build +target_link_libraries(i2c_drive_pico + pico_stdlib + pico_i2c_slave + hardware_i2c + hardware_pwm + ) + +# Add the standard include files to the build +target_include_directories(i2c_drive_pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(i2c_drive_pico + hardware_i2c + ) + +pico_add_extra_outputs(i2c_drive_pico) + diff --git a/dev/zeroing_script/pico_sdk_import.cmake b/dev/zeroing_script/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/zeroing_script/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/zeroing_script/zeroing.cpp b/dev/zeroing_script/zeroing.cpp new file mode 100644 index 0000000..f3b90ae --- /dev/null +++ b/dev/zeroing_script/zeroing.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include +#include + +class Zeroing { //store value of sensor + public: + Zeroing(int pin) { + this->readPin = pin; + } + void Setup() { + stdio_init_all(); + gpio_init(this->readPin); + gpio_set_dir(this->readPin, GPIO_IN); + } + int Read() { + this->zerod = gpio_get(this->readPin); + return this->zerod; + } + + private: + void zero() { //put in here for now + while (!(this->Read())) { + // spin + } + } + int readPin; + int zerod = false; +}; \ No newline at end of file From 25d027b4846212c8bbbcbf2b415fa1d36dc58f4e Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Sun, 5 May 2024 20:33:54 -0700 Subject: [PATCH 51/65] update pid --- dev/pid_control/pid.cpp | 38 +++++++++++++---------- dev/pwm_h_bridge_control/SwerveModule.cpp | 2 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/dev/pid_control/pid.cpp b/dev/pid_control/pid.cpp index ca222c7..c4cb351 100644 --- a/dev/pid_control/pid.cpp +++ b/dev/pid_control/pid.cpp @@ -5,29 +5,33 @@ #include #include +float calculate(float input, float timeStep); +void updateIntegralDerivative(); class pid { //pid loop public: - pid() { - //init pid constants + pid(float P, float I, float D) { + this->P = P; + this->I = I; + this->D = D; } - float calculate(float input, float timeStep){ - float output = this->P * input + this-> I * this -> Integral; - output += this-> D * LastOut - output / timeStep; - Integral+=(output*timeStep) - return output + + setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks + this->timer = repeating_timer_t timer; + add_repeating_timer_ms(100, calculate, NULL, timer); + add_repeating_timer_ms(10, updateIntegralDerivative, NULL, timer); } private: - bool callback(repeating_timer_t *rt){ - //calculate - } - void loop(){ - repeating_timer_t timer; // (add to main, look at periodic sampler) - add_repeating_timer_ms(100, callback, NULL, timer); + + void calculate(float input, float timeStep){ + this->output = this->P * input + this-> I * this -> Integral + this-> D * this -> Derivative; } - float P = 1; - float I = 1; - float D = 1; + + float P; + float I; + float D; + repeating_timer_t timer = {}; float Integral = 0; - float LastOut = 0; + float Derivative = 0; + float output = 0; }; \ No newline at end of file diff --git a/dev/pwm_h_bridge_control/SwerveModule.cpp b/dev/pwm_h_bridge_control/SwerveModule.cpp index 6208c7c..b667f97 100644 --- a/dev/pwm_h_bridge_control/SwerveModule.cpp +++ b/dev/pwm_h_bridge_control/SwerveModule.cpp @@ -36,7 +36,7 @@ class SwerveModule { } // initialize all pins - void Setup() { + void Setup() { //PICO SPECIFIC stdio_init_all(); // h-bridge requires two binary signals for direction, initialize the in1, in2 pins gpio_init(this->turn_in1_pin); From 413b6576f0cbab8f2182a062dd025515ea65aa1f Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Tue, 14 May 2024 15:56:16 -0700 Subject: [PATCH 52/65] commit i forgot to commit this, i think it finalizes changes -still need to add integral windup correction --- dev/pid_control/pid.cpp | 50 ++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/dev/pid_control/pid.cpp b/dev/pid_control/pid.cpp index c4cb351..f6f192b 100644 --- a/dev/pid_control/pid.cpp +++ b/dev/pid_control/pid.cpp @@ -5,33 +5,61 @@ #include #include -float calculate(float input, float timeStep); -void updateIntegralDerivative(); +#include "quadrature_encoder.cpp" + +void calculate(); +void updateErrorIntegralDerivative(); class pid { //pid loop public: - pid(float P, float I, float D) { + pid(float P, float I, float D, Encoder encoder, int timestep = 100, int substep = 10) { this->P = P; this->I = I; this->D = D; + this->encoder = encoder; + this->timestep = timestep //timestep for updating output + this->substep = this->timestep / (float)substep // how many times to update integral/derivative per timestep + } - setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks + void setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks this->timer = repeating_timer_t timer; - add_repeating_timer_ms(100, calculate, NULL, timer); - add_repeating_timer_ms(10, updateIntegralDerivative, NULL, timer); + add_repeating_timer_ms(this->timestep, calculate, NULL, timer); + add_repeating_timer_ms(10, updateErrorIntegralDerivative, NULL, timer); + } + + void updateTarget(float target){ + //Idea; pid loop is autonomous and will automatically do whatever due to repeating timers + //if we just update the target with a function call we might be okay + this->target = target; } + float output(){ + return this->output; + } private: - - void calculate(float input, float timeStep){ - this->output = this->P * input + this-> I * this -> Integral + this-> D * this -> Derivative; + //privated functions for pid function + void calculate(){ + this->output = this->P * this->target + this->I * this->Integral + this->D * this->Derivative; + } + + void updateErrorIntegralDerivative(){ + error = this->encoder.getPosition()-this->target; //error term + this->Integral += error * this->substep; //left riemann sum at fixed substep + this->Derivative = (this->encoder.getPosition()-this->output)/2; //estimate of derivatvie at fixed substep } float P; float I; float D; - repeating_timer_t timer = {}; + + float target = 0; + float output = 0; + float input = 0; + float Integral = 0; float Derivative = 0; - float output = 0; + repeating_timer_t timer = {}; + int timestep; + int substep; + Encoder encoder; }; \ No newline at end of file From 023404a6bcaa763e928ca9bebaacb8a5feca7154 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Tue, 21 May 2024 15:48:06 -0700 Subject: [PATCH 53/65] small tweaks to software small tweaks to software structure, added small TODO notes changes made with meeting with Ishan on ~5/16/24 --- dev/encoder_script/quadrature_encoder.cpp | 43 +++++++++++++---------- dev/pid_control/pid.cpp | 20 ++++++----- dev/pwm_h_bridge_control/SwerveModule.cpp | 33 +++++++++++++++-- dev/swerve_decoder/swerve_decoder.cpp | 2 +- dev/zeroing_script/zeroing.cpp | 14 ++++++-- 5 files changed, 79 insertions(+), 33 deletions(-) diff --git a/dev/encoder_script/quadrature_encoder.cpp b/dev/encoder_script/quadrature_encoder.cpp index 54f68e2..1e178b1 100644 --- a/dev/encoder_script/quadrature_encoder.cpp +++ b/dev/encoder_script/quadrature_encoder.cpp @@ -12,6 +12,8 @@ #include "quadrature_encoder.pio.h" +// #define encoder_test + // // ---- quadrature encoder interface example // @@ -119,27 +121,30 @@ class EncoderFactory static uint encoder_count; }; -// uint EncoderFactory::encoder_count = 0; +#ifdef encoder_test -// int main() -// { -// stdio_init_all(); +uint EncoderFactory::encoder_count = 0; -// // Base pin to connect the A phase of the encoder (yellow wire). -// // The B phase must be connected to the next pin (green wire) -// const uint PIN_STEER = 14; -// const uint PIN_DRIVE = 16; +int main() +{ + stdio_init_all(); -// Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); -// Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK * DEG_PER_ROT); + // Base pin to connect the A phase of the encoder (yellow wire). + // The B phase must be connected to the next pin (green wire) + const uint PIN_STEER = 14; + const uint PIN_DRIVE = 16; -// while (1) -// { -// steer.update(20); -// drive.update(20); + Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); + Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK * DEG_PER_ROT); -// printf("steer position %8f, velocity %6f\n", steer.get_pos(), steer.get_velocity()); -// printf("drive position %8f, velocity %6f\n", drive.get_pos(), drive.get_velocity()); -// sleep_ms(20); -// } -// } + while (1) + { + steer.update(20); + drive.update(20); + + printf("steer position %8f, velocity %6f\n", steer.get_pos(), steer.get_velocity()); + printf("drive position %8f, velocity %6f\n", drive.get_pos(), drive.get_velocity()); + sleep_ms(20); + } +} +#endif \ No newline at end of file diff --git a/dev/pid_control/pid.cpp b/dev/pid_control/pid.cpp index f6f192b..4c2e14f 100644 --- a/dev/pid_control/pid.cpp +++ b/dev/pid_control/pid.cpp @@ -9,22 +9,22 @@ void calculate(); void updateErrorIntegralDerivative(); -class pid { //pid loop +class PID { //pid loop public: - pid(float P, float I, float D, Encoder encoder, int timestep = 100, int substep = 10) { + PID(float P, float I, float D, Encoder encoder, int timestep = 100, int substep = 10) { this->P = P; this->I = I; this->D = D; this->encoder = encoder; this->timestep = timestep //timestep for updating output this->substep = this->timestep / (float)substep // how many times to update integral/derivative per timestep - + this->swerveModule = swerveModule; } void setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks this->timer = repeating_timer_t timer; add_repeating_timer_ms(this->timestep, calculate, NULL, timer); - add_repeating_timer_ms(10, updateErrorIntegralDerivative, NULL, timer); + add_repeating_timer_ms(10, updateErrorIntegralDerivative, NULL, timer); // TODO: why is it 10? } void updateTarget(float target){ @@ -33,9 +33,9 @@ class pid { //pid loop this->target = target; } - float output(){ - return this->output; - } + // float getOutput(){ //random messy function for debugging? :( (TLDR: WHY IS THIS HERE IM JUST GONNA KEEP IT HERE ;-;) + // return this->output; + // } private: //privated functions for pid function void calculate(){ @@ -44,8 +44,9 @@ class pid { //pid loop void updateErrorIntegralDerivative(){ error = this->encoder.getPosition()-this->target; //error term - this->Integral += error * this->substep; //left riemann sum at fixed substep - this->Derivative = (this->encoder.getPosition()-this->output)/2; //estimate of derivatvie at fixed substep + this->Integral += error * (this->substep / 1000); //left riemann sum at fixed substep + this->Derivative = (error-this->lastError)/(this->substep / 1000); //estimate of derivatvie at fixed substep + this->lastError = error; } float P; @@ -55,6 +56,7 @@ class pid { //pid loop float target = 0; float output = 0; float input = 0; + float lastError = 0; float Integral = 0; float Derivative = 0; diff --git a/dev/pwm_h_bridge_control/SwerveModule.cpp b/dev/pwm_h_bridge_control/SwerveModule.cpp index b667f97..c17eeda 100644 --- a/dev/pwm_h_bridge_control/SwerveModule.cpp +++ b/dev/pwm_h_bridge_control/SwerveModule.cpp @@ -5,13 +5,23 @@ #include #include +#include "../pid_control/pid.cpp" + #define COUNT_MAX 65535 +// enum for wheel and turn pid loop selection +typedef enum { + PID_TURN_SELECTION = 0, + PID_WHEEL_SELECTION, + PID_BOTH_SELECTION +}PID_Selection; + // class to instantiate swerve module class SwerveModule { public: // store all pin ids - SwerveModule(int turnPin, int wheelPin, int pwmPin, int countMax = COUNT_MAX, int swapPwmChan = 0) { + // swerveModule is controlled by PID loops, module control is abstracted to PID only! + SwerveModule(int turnPin, int wheelPin, int pwmPin, PID wheelPID, PID turnPID, int countMax = COUNT_MAX, int swapPwmChan = 0) { // two motors per module, each requiring 3 pins // two pins to define h-bridge control and one pin for pwm (since there are 2 motors, there are 2 pwm pins) // constructor assumes that all pins are in sets of 2 and will be next to eachother on an even interval (e.g pin 0 and 1, pin 2 and 4, etc) @@ -33,6 +43,9 @@ class SwerveModule { this->wheelChan = PWM_CHAN_B; this->turnChan = PWM_CHAN_A; } + //PID loop variable setup + this->wheelPID = wheelPID; + this->turnPID = turnPID; } // initialize all pins @@ -73,6 +86,21 @@ class SwerveModule { pwm_set_enabled(this->pwm_slice, true); } + void updatePID(float turn, float wheel, int selection){ + if(selection == PID_Selection.PID_TURN_SELECTION){ + this->wheelPID.updateTarget(turn); + }else if(selection == PID_Selection.PID_WHEEL_SELECTION){ + this->turnPID.updateTarget(wheel); + }else { + this->wheelPID.updateTarget(turn); + this->turnPID.updateTarget(wheel); + } + } + + + + private: + //private drive function control entirely by PID loops void Drive(float turn, float wheel) { // currently assuming turn & wheel are from -1 to 1, probably need to change later if (turn == 0) { // in1 and in2 are high gpio_put(turn_in1_pin, 1); @@ -103,7 +131,6 @@ class SwerveModule { } //private variables for storing pins and constants - private: int turn_in1_pin; int turn_in2_pin; int wheel_in1_pin; @@ -114,4 +141,6 @@ class SwerveModule { int wheel_channel = PWM_CHAN_A; int turn_channel = PWM_CHAN_B; int countMax = COUNT_MAX; + PID wheelPID; + PID turnPID; }; \ No newline at end of file diff --git a/dev/swerve_decoder/swerve_decoder.cpp b/dev/swerve_decoder/swerve_decoder.cpp index cd605c1..d5bc4ed 100644 --- a/dev/swerve_decoder/swerve_decoder.cpp +++ b/dev/swerve_decoder/swerve_decoder.cpp @@ -35,4 +35,4 @@ class Decoder { private: float* floats; int size; -}; \ No newline at end of file +};//put that into pico comms have pico comms d odecoding \ No newline at end of file diff --git a/dev/zeroing_script/zeroing.cpp b/dev/zeroing_script/zeroing.cpp index f3b90ae..60deafd 100644 --- a/dev/zeroing_script/zeroing.cpp +++ b/dev/zeroing_script/zeroing.cpp @@ -5,10 +5,15 @@ #include #include +#include "pid.cpp" + class Zeroing { //store value of sensor public: - Zeroing(int pin) { + Zeroing(int pin, PID motor1, PID motor2, PID motor3) { this->readPin = pin; + this->motor1 = motor1; + this->motor2 = motor2; + this->motor3 = motor3; } void Setup() { stdio_init_all(); @@ -23,9 +28,14 @@ class Zeroing { //store value of sensor private: void zero() { //put in here for now while (!(this->Read())) { - // spin + this->motor1.update(1); + this->motor2.update(1); + this->motor3.update(1); } } int readPin; int zerod = false; + PID motor1; + PID motor2; + PID motor3; }; \ No newline at end of file From 73f743569ab2dcdc8ce0719873efa3998d8d3d47 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Tue, 21 May 2024 15:52:25 -0700 Subject: [PATCH 54/65] restructured swerve_final branch moved everything into a new top-level folder to keep consistent with rest of repo --- {dev => swerve_pico}/_OLD/CMakeLists.txt | 0 {dev => swerve_pico}/_OLD/basic_swerve_pico.c | 0 {dev => swerve_pico}/_OLD/i2c_drive_pico.c | 0 {dev => swerve_pico}/_OLD/pico_sdk_import.cmake | 0 {dev => swerve_pico}/encoder_script/CMakeLists.txt | 0 {dev => swerve_pico}/encoder_script/pico_sdk_import.cmake | 0 {dev => swerve_pico}/encoder_script/quadrature_encoder.cpp | 0 {dev => swerve_pico}/encoder_script/quadrature_encoder.pio | 0 {dev => swerve_pico}/pid_control/CMakeLists.txt | 0 {dev => swerve_pico}/pid_control/pico_sdk_import.cmake | 0 {dev => swerve_pico}/pid_control/pid.cpp | 0 {dev => swerve_pico}/pwm_h_bridge_control/CMakeLists.txt | 0 {dev => swerve_pico}/pwm_h_bridge_control/SwerveModule.cpp | 0 {dev => swerve_pico}/pwm_h_bridge_control/pico_sdk_import.cmake | 0 {dev => swerve_pico}/swerve_decoder/CMakeLists.txt | 0 {dev => swerve_pico}/swerve_decoder/pico_sdk_import.cmake | 0 {dev => swerve_pico}/swerve_decoder/swerve_decoder.cpp | 0 {dev => swerve_pico}/zeroing_script/CMakeLists.txt | 0 {dev => swerve_pico}/zeroing_script/pico_sdk_import.cmake | 0 {dev => swerve_pico}/zeroing_script/zeroing.cpp | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename {dev => swerve_pico}/_OLD/CMakeLists.txt (100%) rename {dev => swerve_pico}/_OLD/basic_swerve_pico.c (100%) rename {dev => swerve_pico}/_OLD/i2c_drive_pico.c (100%) rename {dev => swerve_pico}/_OLD/pico_sdk_import.cmake (100%) rename {dev => swerve_pico}/encoder_script/CMakeLists.txt (100%) rename {dev => swerve_pico}/encoder_script/pico_sdk_import.cmake (100%) rename {dev => swerve_pico}/encoder_script/quadrature_encoder.cpp (100%) rename {dev => swerve_pico}/encoder_script/quadrature_encoder.pio (100%) rename {dev => swerve_pico}/pid_control/CMakeLists.txt (100%) rename {dev => swerve_pico}/pid_control/pico_sdk_import.cmake (100%) rename {dev => swerve_pico}/pid_control/pid.cpp (100%) rename {dev => swerve_pico}/pwm_h_bridge_control/CMakeLists.txt (100%) rename {dev => swerve_pico}/pwm_h_bridge_control/SwerveModule.cpp (100%) rename {dev => swerve_pico}/pwm_h_bridge_control/pico_sdk_import.cmake (100%) rename {dev => swerve_pico}/swerve_decoder/CMakeLists.txt (100%) rename {dev => swerve_pico}/swerve_decoder/pico_sdk_import.cmake (100%) rename {dev => swerve_pico}/swerve_decoder/swerve_decoder.cpp (100%) rename {dev => swerve_pico}/zeroing_script/CMakeLists.txt (100%) rename {dev => swerve_pico}/zeroing_script/pico_sdk_import.cmake (100%) rename {dev => swerve_pico}/zeroing_script/zeroing.cpp (100%) diff --git a/dev/_OLD/CMakeLists.txt b/swerve_pico/_OLD/CMakeLists.txt similarity index 100% rename from dev/_OLD/CMakeLists.txt rename to swerve_pico/_OLD/CMakeLists.txt diff --git a/dev/_OLD/basic_swerve_pico.c b/swerve_pico/_OLD/basic_swerve_pico.c similarity index 100% rename from dev/_OLD/basic_swerve_pico.c rename to swerve_pico/_OLD/basic_swerve_pico.c diff --git a/dev/_OLD/i2c_drive_pico.c b/swerve_pico/_OLD/i2c_drive_pico.c similarity index 100% rename from dev/_OLD/i2c_drive_pico.c rename to swerve_pico/_OLD/i2c_drive_pico.c diff --git a/dev/_OLD/pico_sdk_import.cmake b/swerve_pico/_OLD/pico_sdk_import.cmake similarity index 100% rename from dev/_OLD/pico_sdk_import.cmake rename to swerve_pico/_OLD/pico_sdk_import.cmake diff --git a/dev/encoder_script/CMakeLists.txt b/swerve_pico/encoder_script/CMakeLists.txt similarity index 100% rename from dev/encoder_script/CMakeLists.txt rename to swerve_pico/encoder_script/CMakeLists.txt diff --git a/dev/encoder_script/pico_sdk_import.cmake b/swerve_pico/encoder_script/pico_sdk_import.cmake similarity index 100% rename from dev/encoder_script/pico_sdk_import.cmake rename to swerve_pico/encoder_script/pico_sdk_import.cmake diff --git a/dev/encoder_script/quadrature_encoder.cpp b/swerve_pico/encoder_script/quadrature_encoder.cpp similarity index 100% rename from dev/encoder_script/quadrature_encoder.cpp rename to swerve_pico/encoder_script/quadrature_encoder.cpp diff --git a/dev/encoder_script/quadrature_encoder.pio b/swerve_pico/encoder_script/quadrature_encoder.pio similarity index 100% rename from dev/encoder_script/quadrature_encoder.pio rename to swerve_pico/encoder_script/quadrature_encoder.pio diff --git a/dev/pid_control/CMakeLists.txt b/swerve_pico/pid_control/CMakeLists.txt similarity index 100% rename from dev/pid_control/CMakeLists.txt rename to swerve_pico/pid_control/CMakeLists.txt diff --git a/dev/pid_control/pico_sdk_import.cmake b/swerve_pico/pid_control/pico_sdk_import.cmake similarity index 100% rename from dev/pid_control/pico_sdk_import.cmake rename to swerve_pico/pid_control/pico_sdk_import.cmake diff --git a/dev/pid_control/pid.cpp b/swerve_pico/pid_control/pid.cpp similarity index 100% rename from dev/pid_control/pid.cpp rename to swerve_pico/pid_control/pid.cpp diff --git a/dev/pwm_h_bridge_control/CMakeLists.txt b/swerve_pico/pwm_h_bridge_control/CMakeLists.txt similarity index 100% rename from dev/pwm_h_bridge_control/CMakeLists.txt rename to swerve_pico/pwm_h_bridge_control/CMakeLists.txt diff --git a/dev/pwm_h_bridge_control/SwerveModule.cpp b/swerve_pico/pwm_h_bridge_control/SwerveModule.cpp similarity index 100% rename from dev/pwm_h_bridge_control/SwerveModule.cpp rename to swerve_pico/pwm_h_bridge_control/SwerveModule.cpp diff --git a/dev/pwm_h_bridge_control/pico_sdk_import.cmake b/swerve_pico/pwm_h_bridge_control/pico_sdk_import.cmake similarity index 100% rename from dev/pwm_h_bridge_control/pico_sdk_import.cmake rename to swerve_pico/pwm_h_bridge_control/pico_sdk_import.cmake diff --git a/dev/swerve_decoder/CMakeLists.txt b/swerve_pico/swerve_decoder/CMakeLists.txt similarity index 100% rename from dev/swerve_decoder/CMakeLists.txt rename to swerve_pico/swerve_decoder/CMakeLists.txt diff --git a/dev/swerve_decoder/pico_sdk_import.cmake b/swerve_pico/swerve_decoder/pico_sdk_import.cmake similarity index 100% rename from dev/swerve_decoder/pico_sdk_import.cmake rename to swerve_pico/swerve_decoder/pico_sdk_import.cmake diff --git a/dev/swerve_decoder/swerve_decoder.cpp b/swerve_pico/swerve_decoder/swerve_decoder.cpp similarity index 100% rename from dev/swerve_decoder/swerve_decoder.cpp rename to swerve_pico/swerve_decoder/swerve_decoder.cpp diff --git a/dev/zeroing_script/CMakeLists.txt b/swerve_pico/zeroing_script/CMakeLists.txt similarity index 100% rename from dev/zeroing_script/CMakeLists.txt rename to swerve_pico/zeroing_script/CMakeLists.txt diff --git a/dev/zeroing_script/pico_sdk_import.cmake b/swerve_pico/zeroing_script/pico_sdk_import.cmake similarity index 100% rename from dev/zeroing_script/pico_sdk_import.cmake rename to swerve_pico/zeroing_script/pico_sdk_import.cmake diff --git a/dev/zeroing_script/zeroing.cpp b/swerve_pico/zeroing_script/zeroing.cpp similarity index 100% rename from dev/zeroing_script/zeroing.cpp rename to swerve_pico/zeroing_script/zeroing.cpp From 0f0118aa1f00e030b84f2c0781f12315014f6786 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Tue, 21 May 2024 15:59:03 -0700 Subject: [PATCH 55/65] merged basic-swerve added basic-swerve dev folders --- dev/basic_swerve/CMakeLists.txt | 55 + dev/basic_swerve/Controller.py | 203 + dev/basic_swerve/basic_swerve_pi.py | 132 + dev/basic_swerve/basic_swerve_pico.c | 393 ++ dev/basic_swerve/joystick_sim.py | 195 + dev/basic_swerve/pico_sdk_import.cmake | 73 + dev/basic_swerve/procon.py | 331 ++ dev/basic_swerve/swerve_test.py | 80 + dev/i2c-comms/CMakeLists.txt | 54 + dev/i2c-comms/pi_i2c.py | 29 + dev/i2c-comms/pi_spi.c | 63 + dev/i2c-comms/pico_i2c.c | 78 + dev/i2c-comms/pico_sdk_import.cmake | 73 + dev/i2c-comms/pico_spi.c | 35 + dev/i2c_drive/CMakeLists.txt | 55 + dev/i2c_drive/Controller.py | 204 + dev/i2c_drive/i2c_drive_pi.py | 59 + dev/i2c_drive/i2c_drive_pico.c | 220 ++ dev/i2c_drive/pico_sdk_import.cmake | 73 + dev/i2c_drive/procon.py | 331 ++ dev/motor_test_python/Controller.py | 258 ++ dev/motor_test_python/motor_test.py | 107 + dev/motor_test_python/procon.py | 331 ++ dev/pico_motor_test/CMakeLists.txt | 56 + dev/pico_motor_test/class_motor_test.cpp | 113 + dev/pico_motor_test/cooler_motor_test.cpp | 129 + dev/pico_motor_test/pico_sdk_import.cmake | 73 + dev/swerve/motor_test.py | 88 + dev/swerve_sim_joystick/Controller.py | 204 + dev/swerve_sim_joystick/joystick_sim.py | 195 + dev/swerve_sim_joystick/procon.py | 331 ++ dev/swerve_sim_keyboard/README.md | 22 + dev/swerve_sim_keyboard/poetry.lock | 3302 +++++++++++++++++ dev/swerve_sim_keyboard/pyproject.toml | 18 + dev/swerve_sim_keyboard/src/main.py | 167 + dev/swerve_sim_keyboard/test/keyboard_test.py | 40 + 36 files changed, 8170 insertions(+) create mode 100644 dev/basic_swerve/CMakeLists.txt create mode 100644 dev/basic_swerve/Controller.py create mode 100644 dev/basic_swerve/basic_swerve_pi.py create mode 100644 dev/basic_swerve/basic_swerve_pico.c create mode 100644 dev/basic_swerve/joystick_sim.py create mode 100644 dev/basic_swerve/pico_sdk_import.cmake create mode 100644 dev/basic_swerve/procon.py create mode 100644 dev/basic_swerve/swerve_test.py create mode 100644 dev/i2c-comms/CMakeLists.txt create mode 100644 dev/i2c-comms/pi_i2c.py create mode 100644 dev/i2c-comms/pi_spi.c create mode 100644 dev/i2c-comms/pico_i2c.c create mode 100644 dev/i2c-comms/pico_sdk_import.cmake create mode 100644 dev/i2c-comms/pico_spi.c create mode 100644 dev/i2c_drive/CMakeLists.txt create mode 100644 dev/i2c_drive/Controller.py create mode 100644 dev/i2c_drive/i2c_drive_pi.py create mode 100644 dev/i2c_drive/i2c_drive_pico.c create mode 100644 dev/i2c_drive/pico_sdk_import.cmake create mode 100644 dev/i2c_drive/procon.py create mode 100644 dev/motor_test_python/Controller.py create mode 100644 dev/motor_test_python/motor_test.py create mode 100644 dev/motor_test_python/procon.py create mode 100644 dev/pico_motor_test/CMakeLists.txt create mode 100644 dev/pico_motor_test/class_motor_test.cpp create mode 100644 dev/pico_motor_test/cooler_motor_test.cpp create mode 100644 dev/pico_motor_test/pico_sdk_import.cmake create mode 100644 dev/swerve/motor_test.py create mode 100644 dev/swerve_sim_joystick/Controller.py create mode 100644 dev/swerve_sim_joystick/joystick_sim.py create mode 100644 dev/swerve_sim_joystick/procon.py create mode 100644 dev/swerve_sim_keyboard/README.md create mode 100644 dev/swerve_sim_keyboard/poetry.lock create mode 100644 dev/swerve_sim_keyboard/pyproject.toml create mode 100644 dev/swerve_sim_keyboard/src/main.py create mode 100644 dev/swerve_sim_keyboard/test/keyboard_test.py diff --git a/dev/basic_swerve/CMakeLists.txt b/dev/basic_swerve/CMakeLists.txt new file mode 100644 index 0000000..7e1ada4 --- /dev/null +++ b/dev/basic_swerve/CMakeLists.txt @@ -0,0 +1,55 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(basic_swerve_pico C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(basic_swerve_pico basic_swerve_pico.c ) + +pico_set_program_name(basic_swerve_pico "basic_swerve_pico") +pico_set_program_version(basic_swerve_pico "0.1") + +pico_enable_stdio_uart(basic_swerve_pico 0) +pico_enable_stdio_usb(basic_swerve_pico 1) + +# Add the standard library to the build +target_link_libraries(basic_swerve_pico + pico_stdlib + pico_i2c_slave + hardware_i2c + hardware_pwm + ) + +# Add the standard include files to the build +target_include_directories(basic_swerve_pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(basic_swerve_pico + hardware_i2c + ) + +pico_add_extra_outputs(basic_swerve_pico) + diff --git a/dev/basic_swerve/Controller.py b/dev/basic_swerve/Controller.py new file mode 100644 index 0000000..af696be --- /dev/null +++ b/dev/basic_swerve/Controller.py @@ -0,0 +1,203 @@ +import math +import threading + +from inputs import get_gamepad # Import the get_gamepad function from the inputs module +from procon import ProCon # Import the ProCon class from the procon module + + +# This class represents a PS4 Controller +class PS4_Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input + self.THRESHOLD = 0.04 # Threshold for joystick deadzone + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + # This method resets all controller variables to their initial state + def reset_vars(self): + # Initialize all controller variables to 0 + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + # This method starts a new thread to monitor the controller + def start_thread(self, thread_args=()): + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=thread_args + ) + self._monitor_thread.daemon = ( + True # Set the thread as a daemon so it will end when the main program ends + ) + self._monitor_thread.start() # Start the thread + + # This method returns the current state of all buttons/triggers + def read(self): + return [ + self.LeftJoystickY, + self.LeftJoystickX, + self.RightJoystickY, + self.RightJoystickX, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + # This method returns the controller object itself + def read_self(self): + return self + + # This method applies a threshold to a value + def threshold(self, val): + return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 + + def _monitor_controller(self): + while True: + events = get_gamepad() + for event in events: + if event.code == "ABS_Y": + self.LeftJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_X": + self.LeftJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RY": + self.RightJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RX": + self.RightJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_Z": + self.LeftTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "ABS_RZ": + self.RightTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "BTN_TL": + self.LeftBumper = event.state + elif event.code == "BTN_TR": + self.RightBumper = event.state + elif event.code == "BTN_SOUTH": + self.A = event.state + elif event.code == "BTN_NORTH": + self.Y = event.state # previously switched with X + elif event.code == "BTN_WEST": + self.X = event.state # previously switched with Y + elif event.code == "BTN_EAST": + self.B = event.state + elif event.code == "BTN_THUMBL": + self.LeftThumb = event.state + elif event.code == "BTN_THUMBR": + self.RightThumb = event.state + elif event.code == "BTN_SELECT": + self.Back = event.state + elif event.code == "BTN_START": + self.Start = event.state + elif event.code == "BTN_TRIGGER_HAPPY1": + self.LeftDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY2": + self.RightDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY3": + self.UpDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY4": + self.DownDPad = event.state + + +# This class represents the Xbox Controller in WRP used for the CPSRC GEM +class Gem_Xbox_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + +# This class represents the Nintendo Pro Controller +class Nintendo_Pro_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.1 # Threshold for joystick deadzone + self.controller = ProCon() # Initialize the ProCon controller + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread( + self.procon_callback_func + ) # Start a new thread to monitor the controller + + # This method is called when the ProCon controller state changes + def procon_callback_func(self, buttons, l_stick, r_stick, *_): + # Update the controller variables based on the new state + # The joystick values are normalized between -1 and 1 + # The threshold method is used to apply a deadband to the joystick values + # The button values are either 0 or 1 + self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) + self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) + self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) + self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) + self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) + self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) + self.LeftBumper = buttons[ProCon.Button.L] + self.RightBumper = buttons[ProCon.Button.R] + self.A = buttons[ProCon.Button.A] + self.B = buttons[ProCon.Button.B] + self.X = buttons[ProCon.Button.X] + self.Y = buttons[ProCon.Button.Y] + self.LeftThumb = buttons[ProCon.Button.LS] + self.RightThumb = buttons[ProCon.Button.RS] + self.Back = buttons[ProCon.Button.MINUS] + self.Start = buttons[ProCon.Button.PLUS] + self.LeftDPad = buttons[ProCon.Button.LEFT] + self.RightDPad = buttons[ProCon.Button.RIGHT] + self.UpDPad = buttons[ProCon.Button.UP] + self.DownDPad = buttons[ProCon.Button.DOWN] + + +if __name__ == "__main__": + joy = PS4_Controller() # Initialize a PS4 controller + # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller + # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller + while True: + try: + print(joy.read()) # Print the current state of the controller + except Exception as e: + print("error!", e) # Print any errors that occur + break # Exit the loop if an error occurs diff --git a/dev/basic_swerve/basic_swerve_pi.py b/dev/basic_swerve/basic_swerve_pi.py new file mode 100644 index 0000000..dd40ba2 --- /dev/null +++ b/dev/basic_swerve/basic_swerve_pi.py @@ -0,0 +1,132 @@ +import fcntl +import os +import time +import struct + +from Controller import PS4_Controller + +I2C_PRIM = 0x0703 # I2C primary address + +# Open i2c devices (sudo apt install i2c-tools) +i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) # File descriptor for the i2c device + +# Set the i2c address of pico +pico_address = 0x08 # Address of the pico on the i2c bus +fcntl.ioctl( + i2c_fd, I2C_PRIM, pico_address +) # Set the address of the i2c device to communicate with + +# Send data to pico +joy = PS4_Controller() # Initialize the controller +delay = 0.05 # Delay between each read/write operation + +import math + +# radius of swerve drive base in meters +radius = 1 + + +# add two vectors +def add_two_vec(v1, v2): + # get difference between two vectors, treating one vector as perpendicular to x axis + theta_diff = v2[1] - v1[1] + theta_diff = theta_diff + + # since vec1 is x axis, vector 1 contributes in only the x axis + # when breaking into components, just add vec2 in x to vec1 magnitude to get x + # vec2 in y to get y + # sqrt(a^2+b^2) to get magnitude, and arctan(y/x) to get angle relative to x (add to vec1 orientation) + x_comp = v1[0] + math.cos(theta_diff) * v2[0] + y_comp = math.sin(theta_diff) * v2[0] + f_mag = math.sqrt(x_comp**2 + y_comp**2) + f_angle = math.atan2(y_comp, x_comp) + v1[1] + f_angle = f_angle + if f_angle < -math.pi: + f_angle += 2 * math.pi + elif f_angle > math.pi: + f_angle -= 2 * math.pi + return [f_mag, f_angle] + + +# input velocity [speed (m/s), orientation (deg)] in local reference frame +# rotation (rad/s) +def convert(v_vec, omega): + + # the vector for each motor in global reference frame is given by adding + # 1) velocity (relative to global reference frame) + # 2) rotation vector (vector perpendicular to each motor relative to the body reference frame) + # because the frame is circular, magnitude of vector is calculated by V = rw (formula for tangential speed relative to radius and rad/s) + + # setting all vectors to velocity relative to local reference frame + m1 = [v_vec[0], v_vec[1]] + m2 = [v_vec[0], v_vec[1] - 2 * math.pi / 3] + m3 = [v_vec[0], v_vec[1] - 4 * math.pi / 3] + + # create magnitude for each vector + rot_mag = omega * radius + dir = 1 + + if rot_mag < 0: + dir *= -1 + rot_mag *= -1 + # add two vectors (in local frame) based on direction and magnitude + m1 = add_two_vec(m1, [rot_mag, math.pi / 2 * dir]) + m2 = add_two_vec(m2, [rot_mag, math.pi / 2 * dir]) + m3 = add_two_vec(m3, [rot_mag, math.pi / 2 * dir]) + + return [m1, m2, m3] + + +while True: # Infinite loop + status = joy.read_self() # Read the status of the controller + x = status.LeftJoystickX # Get the X position of the left joystick + y = -1 * status.LeftJoystickY # Get the Y position of the left joystick + w = status.RightJoystickX # Get the X position of the right joystick + + v = (math.sqrt(x**2 + y**2), math.atan2(y, x)) + m = convert(v, w) + + m1_v = struct.pack("f", m[0][0]) + m1_w = struct.pack("f", math.degrees(m[0][1])) + + m2_v = struct.pack("f", m[1][0]) + m2_w = struct.pack("f", math.degrees(m[1][1])) + + m3_v = struct.pack("f", m[2][0]) + m3_w = struct.pack("f", math.degrees(m[2][1])) + + data = ( + bytes([0xFA]) + m1_v + m1_w + m2_v + m2_w + m3_v + m3_w + bytes([0xFB]) + ) # Prepare the data to be sent + # data = ( + # bytes([0xFA]) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + struct.pack("f", 0.5) + # + bytes([0xFB]) + # ) # Prepare the data to be sent + + try: + os.write(i2c_fd, data) # Write the data to the i2c device + # os.write(i2c_fd, bytes(data)) # Write the data to the i2c device + time.sleep(delay) # Wait for a while + print("Sent data to Pico: ", list(data)) # Print the data that was sent + except OSError: + print( + "Remote I/O Error" + ) # Print an error message if there was a problem with the write operation + + # Read data from pico + try: + incoming_data = os.read(i2c_fd, 1) # Read 1 byte from the i2c device + time.sleep(delay) # Wait for a while + # print( + # "Received data from Pico: ", list(incoming_data) + # ) # Print the data that was received + except TimeoutError: + print("Timeout Error") # Print an error message if there was a timeout error + except OSError: + print("Remote I/O Error") diff --git a/dev/basic_swerve/basic_swerve_pico.c b/dev/basic_swerve/basic_swerve_pico.c new file mode 100644 index 0000000..9f4bf53 --- /dev/null +++ b/dev/basic_swerve/basic_swerve_pico.c @@ -0,0 +1,393 @@ +#include +#include +#include +#include +#include +#include +#include + +// Define constants for I2C communication +#define I2C_PICO_ADDR 0x08 +#define I2C_SDA_PIN 0 +#define I2C_SCL_PIN 1 +#define I2C_PORT i2c0 +#define I2C_BAUDRATE 100 * 1000 + +// Define the length of the data packet +#define I2C_DATA_LENGTH 128 + +#define MESSAGE_START 0xFA +#define MESSAGE_STOP 0xFB + +// digital low on a/b pins indicates direction, both high is no signal (stop) +// pwm signal on pwm pin controls speed + +#define wheel_1_pwm 2 +#define wheel_1_slice 1 +#define wheel_1_channel PWM_CHAN_A +#define wheel_1a 3 +#define wheel_1b 4 + +#define turn_1a 5 +#define turn_1b 6 +#define turn_1_pwm 7 +#define turn_1_slice 3 +#define turn_1_channel PWM_CHAN_B + +#define wheel_2_pwm 8 +#define wheel_2_slice 4 +#define wheel_2_channel PWM_CHAN_A +#define wheel_2a 9 +#define wheel_2b 10 + +#define turn_2a 11 +#define turn_2b 12 +#define turn_2_pwm 13 +#define turn_2_slice 6 +#define turn_2_channel PWM_CHAN_B + +#define turn_3_pwm 16 +#define turn_3_slice 0 +#define turn_3_channel PWM_CHAN_A +#define turn_3b 17 +#define turn_3a 18 + +#define wheel_3b 19 +#define wheel_3a 20 +#define wheel_3_pwm 21 +#define wheel_3_slice 2 +#define wheel_3_channel PWM_CHAN_B + +// #define freq 500 // note: use clock management frequencies to set frequency +#define count_max 65535 + +// Status of the input data +int input_status = 0; + +// Last event that occurred +int last_event = 0; + +// Index of the current data byte +int data_index = 0; + +int count = 0; + +// Buffer for the input data +typedef struct dataT +{ + uint8_t data[I2C_DATA_LENGTH]; + uint8_t len; +} *data; + +typedef struct buffT +{ + data buff[I2C_DATA_LENGTH]; + uint8_t head; + uint8_t tail; + uint8_t full; +} *buff; + +buff buffer; +data tmp; + +// Initialize the circular buffer +void init_buff(buff *b, data *d) +{ + *b = malloc(sizeof(struct buffT)); + (*b)->head = 0; + (*b)->tail = 0; + (*b)->full = 0; + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + (*b)->buff[i] = malloc(sizeof(struct dataT)); + (*b)->buff[i]->len = 0; + for (int j = 0; j < I2C_DATA_LENGTH; j++) + { + (*b)->buff[i]->data[j] = 0; + } + } + + *d = malloc(sizeof(struct dataT)); + (*d)->len = 0; + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + (*d)->data[i] = 0; + } +} + +// Handler for I2C events +static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +{ + switch (event) + { + case I2C_SLAVE_RECEIVE: + { + // Read the data + uint8_t tmp_byte = i2c_read_byte_raw(i2c); + // Check if the data is valid + if (tmp->len >= I2C_DATA_LENGTH) + { + printf("Invalid data %x, len %x\n", tmp_byte, tmp->len); + break; + } + // Store the data + tmp->data[tmp->len] = tmp_byte; + tmp->len++; + // set the event status to received + last_event = 1; + + break; + } + + case I2C_SLAVE_REQUEST: // Pi is requesting data + // Write the data into the void + i2c_write_byte_raw(i2c, (uint8_t)input_status); + // set the event status to sent + last_event = 2; + break; + + case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart + // if the last event was a receive event and the data is valid + if (last_event == 1 && !buffer->full) + { + if (tmp->data[0] == MESSAGE_START && tmp->data[tmp->len - 1] == MESSAGE_STOP) + { + for (int i = 0; i < tmp->len; i++) + { + buffer->buff[buffer->tail]->data[i] = tmp->data[i]; + } + buffer->buff[buffer->tail]->len = tmp->len; + + // set the input status to ready + input_status = 1; + + // move the tail of the buffer + buffer->tail = (buffer->tail + 1) % I2C_DATA_LENGTH; + + // check if the buffer is full + if (buffer->tail == buffer->head) + { + buffer->full = 1; + } + tmp->len = 0; + } + } + else + { + // printf("Buffer full, attempted to put this into buffer:\n"); + // for (int i = 0; i < tmp->len; i++) + // { + // printf("%d ", tmp->data[i]); + // } + // printf("\n"); + tmp->len = 0; + } + break; + default: + break; + } +} + +// linear & angular velocity for each wheel +float v1 = 0.0f; +float v2 = 0.0f; +float v3 = 0.0f; + +float w1 = 0.0f; +float w2 = 0.0f; +float w3 = 0.0f; + +const int num_floats = 6; + +float rot1, rot2, rot3; // current working rotation to calc delta +float delt1, delt2, delt3; // change in rotation after subtracting omega +float u1, u2, u3; // interim omega values + +int flip1 = 1, flip2 = 1, flip3 = 1; +// const float scale = + +void setup_pins(int a, int b, int pwm, int slice, int channel) +{ + gpio_init(a); + gpio_init(b); + gpio_init(pwm); + gpio_set_dir(a, GPIO_OUT); + gpio_set_dir(b, GPIO_OUT); + gpio_set_function(pwm, GPIO_FUNC_PWM); + pwm_set_wrap(slice, count_max); + pwm_set_chan_level(slice, channel, 0); + pwm_set_enabled(slice, true); +} + +int main() +{ + const uint LED_PIN = PICO_DEFAULT_LED_PIN; + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + stdio_init_all(); + + // Initialize I2C at 100kHz + i2c_init(I2C_PORT, I2C_BAUDRATE); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + + init_buff(&buffer, &tmp); + + // Set I2C address for Pico + i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); + + // Set up the pins for the motors + setup_pins(wheel_1a, wheel_1b, wheel_1_pwm, wheel_1_slice, wheel_1_channel); + setup_pins(wheel_2a, wheel_2b, wheel_2_pwm, wheel_2_slice, wheel_2_channel); + setup_pins(wheel_3a, wheel_3b, wheel_3_pwm, wheel_3_slice, wheel_3_channel); + + setup_pins(turn_1a, turn_1b, turn_1_pwm, turn_1_slice, turn_1_channel); + setup_pins(turn_2a, turn_2b, turn_2_pwm, turn_2_slice, turn_2_channel); + setup_pins(turn_3a, turn_3b, turn_3_pwm, turn_3_slice, turn_3_channel); + + sleep_ms(1000); // wait for the i2c to be ready + + absolute_time_t start_time = get_absolute_time(); + + // turns 180 degrees (approximately) - use 11660 to help scale the degrees needed to turn to the # of timesteps necessary to complete that turn at full speed + // for (int timetest = 0; timetest < 11660; timetest++) + while (1) + { + gpio_put(LED_PIN, 1); + if (input_status == 1) + { + input_status = 0; + // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + uint32_t tmp_float[num_floats]; + + for (int i = 0; i < num_floats; i++) + { + tmp_float[i] = 0; + for (int j = 0; j < 4; j++) + { + tmp_float[i] |= buffer->buff[buffer->head]->data[(i * 4) + j + 1] << (8 * j); + // printf("%d:%d ", (i * 4) + j + 1, buffer->buff[buffer->head]->data[(i * 4) + j + 1]); + } + // printf("\n %x\n", tmp_float[i]); + } + v1 = *(((float *)&tmp_float[0])); + v2 = *(((float *)&tmp_float[2])); + v3 = *(((float *)&tmp_float[4])); + + printf("i2c %u ", to_ms_since_boot(get_absolute_time())); + + // tmp_float = 0; // clear float + buffer->head = (buffer->head + 1) % I2C_DATA_LENGTH; + buffer->full = 0; + } + gpio_put(LED_PIN, 0); + + // get delta for wheel 1 + delt1 = v1 - rot1; + if (abs(delt1) > 90) + { + flip1 *= -1; + delt1 = (90) * (delt1 < 0 ? -1 : 1) - delt1; + } + + // get delta for wheel 2 + delt2 = v2 - rot2; + if (abs(delt2) > 90) + { + flip2 *= -1; + delt2 = (90) * (delt2 < 0 ? -1 : 1) - delt2; + } + + // get delta for wheel 3 + delt3 = v3 - rot3; + if (abs(delt3) > 90) + { + flip3 *= -1; + delt3 = (90) * (delt3 < 0 ? -1 : 1) - delt3; + } + + if (v1 == 0) + { + // in1 and in2 are high + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 1); + } + else if (v1 * flip1 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_1a, 1); + gpio_put(wheel_1b, 0); + rot1 += 0.01543739279 + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_1b, 1); + gpio_put(wheel_1a, 0); + rot1 -= 0.01543739279 + } + + if (v2 == 0) + { + // in1 and in2 are high + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 1); + } + else if (v2 * flip2 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_2a, 1); + gpio_put(wheel_2b, 0); + rot2 += 0.01543739279 + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_2b, 1); + gpio_put(wheel_2a, 0); + rot2 -= 0.01543739279 + } + + if (v3 == 0) + { + // in1 and in2 are high + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 1); + } + else if (v3 * flip3 < 0) + { + // in1 is high and in2 is low + gpio_put(wheel_3a, 1); + gpio_put(wheel_3b, 0); + rot3 += 0.01543739279 + } + else + { + // in1 is low and in2 is high + gpio_put(wheel_3b, 1); + gpio_put(wheel_3a, 0); + rot3 -= 0.01543739279 + } + pwm_set_chan_level(wheel_1_slice, wheel_1_channel, abs((int)(v1 * count_max))); + + pwm_set_chan_level(wheel_2_slice, wheel_2_channel, abs((int)(v2 * count_max))); + + pwm_set_chan_level(wheel_3_slice, wheel_3_channel, abs((int)(v3 * count_max))); + + // sleep_ms(20); // used for debugging - reduces the number of loops w/ no new i2c data + printf("%u %u\n", to_ms_since_boot(get_absolute_time()), to_ms_since_boot(start_time)); + } + + printf("%u\n", to_ms_since_boot(get_absolute_time())); + + while (1) + { + gpio_put(LED_PIN, 1); + sleep_ms(500); + gpio_put(LED_PIN, 0); + sleep_ms(500); + } + + return 0; +} \ No newline at end of file diff --git a/dev/basic_swerve/joystick_sim.py b/dev/basic_swerve/joystick_sim.py new file mode 100644 index 0000000..4aa9f93 --- /dev/null +++ b/dev/basic_swerve/joystick_sim.py @@ -0,0 +1,195 @@ +import matplotlib.pyplot as plt +import numpy as np +from Controller import (Gem_Xbox_Controller, Nintendo_Pro_Controller, + PS4_Controller) + + +# return the vector perpendicular to the given vector +def perpendicular(vec): + return np.array([-vec[1], vec[0]]) + + +# NOTE: make sure to account for max motor speed when programming real motors, and normalize +# for example, if the swerve math commands one motor to spin higher than it's max speed, +# then it will only spin at the max speed, thus making the ratio of motor powers wrong and the robot will move wrong + + +if __name__ == "__main__": + # joy = Gem_Xbox_Controller() + # joy = Nintendo_Pro_Controller() + joy = PS4_Controller() + + rumble = type(joy) == Nintendo_Pro_Controller + + # robot radius + R = 5 + # dt, the delta time of the "animation" + DT = 0.001 + + # initial robot state + center_pos = np.array([0.0, 0.0]) # center position + module_dirs = ( + np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi + ) # directions of each module, relative to screen + module_pos = np.array( + [ + [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] + for a in module_dirs + ] + ) # absolute positions of each module (as a point) + freeze_pos = ( + center_pos.copy() + ) # position to rotate about when right bumper is pressed + + while True: + try: + # get inputs + joy_input = joy.read_self() + if joy_input.Back: # exit if back button is pressed + print("Exiting") + break + + # TODO: should replace this by standardizing inverts in the Controller.py class + inverts = [False, False, False, False] # Nintendo Pro Controller + # inverts = [False, True, True] # Gem Xbox Controller + + # use joystick inputs to calculate "strafe" movement + left_x = (-1.0 if inverts[0] else 1.0) * round(joy_input.LeftJoystickX, 3) + left_y = (-1.0 if inverts[1] else 1.0) * round(joy_input.LeftJoystickY, 3) + triggers = joy_input.LeftTrigger - joy_input.RightTrigger + + right_x = (-1.0 if inverts[2] else 1.0) * round(joy_input.RightJoystickX, 3) + right_y = (-1.0 if inverts[3] else 1.0) * round(joy_input.RightJoystickY, 3) + + ## LOGIC (begin) + + # get distance between freeze_pos and center_pos + dist = np.hypot( + freeze_pos[0] - center_pos[0], freeze_pos[1] - center_pos[1] + ) + + # if right bumper is not pressed, move robot in direction of joystick & rotate relative to center pos + if not joy_input.RightBumper: + move = np.array([left_x, left_y]) * 1.0 + rotate = 0.1 * triggers + + # if right bumper is pressed and freeze pos is not "inside" robot, rotate robot around freeze pos + elif dist > R: + # calculate vector from freeze to center pos + x = (freeze_pos[0] - center_pos[0]) / dist + y = (freeze_pos[1] - center_pos[1]) / dist + + # calculate new center position, moving robot around freeze pos + # x' = x*cos(theta) - y*sin(theta) + # y' = x*sin(theta) + y*cos(theta) + # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) + # https://academo.org/demos/rotation-about-point/ + move = np.array( + [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] + ) + # rotate robot so direction of modules is the same relative to freeze pos, plus some rotation from triggers + rotate = (-1.0 if left_x > 0 else 1.0) * np.hypot( + move[0], move[1] + ) / dist + 0.1 * triggers + + # if left bumper is pressed, make freeze pos the same as center pos + if joy_input.LeftBumper: + freeze_pos = center_pos.copy() + else: # if left bumper is not pressed, move freeze pos in direction of right joystick + freeze_pos += np.array([right_x, right_y]) * 1.0 + + # if right bumper is not pressed, move freeze pos in direction of right joystick (relative to center pos) + if not joy_input.RightBumper: + freeze_pos += move * 1.0 + np.array([right_x, right_y]) * 1.0 + + # update center position + center_pos += move + + # update module directions + module_dirs += rotate + + ## LOGIC (end) + + # update module positions using module directions and center position + module_pos = np.array( + [ + [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] + for a in module_dirs + ] + ) + + # set box size and aspect ratio for matplotlib plot window + box_scale = 10 + plt.xlim(-box_scale * R, box_scale * R) + plt.ylim(-box_scale * R, box_scale * R) + plt.gca().set_aspect("equal", adjustable="box") + + # array to store module controls (direction & speed of each module) + module_controls = [] + + # plot robot + for i, module in enumerate(module_pos): + # plot line from center to module + plt.plot( + [center_pos[0], module[0]], [center_pos[1], module[1]], "black" + ) + + # calculate module direction vector using robot movement vector & rotation + dir_vec = ( + move + + np.array([-np.sin(module_dirs[i]), np.cos(module_dirs[i])]) + * rotate + * 10 + ) + + # add module direction vector to module_controls as degrees & speed + module_controls.append( + ( + round(np.rad2deg(np.arctan2(dir_vec[1], dir_vec[0])), 3), + round(np.hypot(dir_vec[0], dir_vec[1]), 3), + ) + ) + + # plot module direction vectors + plt.quiver( + module[0], + module[1], + dir_vec[0], + dir_vec[1], + color="red", + angles="xy", + scale_units="xy", + scale=0.5, + ) + + # print(module_controls) + + # plot center direction vector + plt.quiver( + center_pos[0], + center_pos[1], + move[0], + move[1], + color="green", + angles="xy", + scale_units="xy", + scale=0.5, + ) + + # plot line from center to freeze pos + plt.plot( + [center_pos[0], freeze_pos[0]], [center_pos[1], freeze_pos[1]], "b" + ) + + # rumble if robot is outside of box + if rumble and ( + abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R + ): + joy.controller.send_rumble(False, True, 1) + + # pause for DT seconds and clear plot + plt.pause(DT) + plt.clf() + + except Exception as e: + print(e) diff --git a/dev/basic_swerve/pico_sdk_import.cmake b/dev/basic_swerve/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/basic_swerve/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/basic_swerve/procon.py b/dev/basic_swerve/procon.py new file mode 100644 index 0000000..8e04765 --- /dev/null +++ b/dev/basic_swerve/procon.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 + +import math +import time + +import hid # pip install hidapi + + +def to_int16(uint16): + return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 + + +class ProCon: + VENDOR_ID = 0x057E + PRODUCT_ID = 0x2009 + PACKET_SIZE = 64 + CALIBRATION_OFFSET = 0x603D + CALIBRATION_LENGTH = 0x12 + COMMAND_RETRIES = 10 + RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) + RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) + DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) + + class OutputReportID: + RUMBLE_SUBCOMMAND = 0x01 + RUMBLE = 0x10 + COMMAND = 0x80 + + class InputReportID: + SUBCOMMAND_REPLY = 0x21 + CONTROLLER_STATE = 0x30 + COMMAND_ACK = 0x81 + + class CommandID: + HANDSHAKE = 0x02 + HIGH_SPEED = 0x03 + FORCE_USB = 0x04 + + class SubcommandID: + SET_INPUT_REPORT_MODE = 0x03 + SPI_FLASH_READ = 0x10 + SET_PLAYER_LIGHTS = 0x30 + SET_HOME_LIGHT = 0x38 + ENABLE_IMU = 0x40 + SET_IMU_SENSITIVITY = 0x41 + ENABLE_VIBRATION = 0x48 + + class Button: + A = "A" + B = "B" + X = "X" + Y = "Y" + UP = "Up" + DOWN = "Down" + LEFT = "Left" + RIGHT = "Right" + MINUS = "-" + PLUS = "+" + SCREENSHOT = "Screenshot" + HOME = "Home" + L = "L" + ZL = "ZL" + R = "R" + ZR = "ZR" + LS = "LS" + RS = "RS" + + def __init__(self): + self.subcommand_counter = 0 + self.dev = hid.device() + self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) + self.handshake() + self.high_speed() + self.handshake() + self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL + self.rumble_expire = 0 + self.load_stick_calibration() + self.enable_vibration(True) + self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) + self.force_usb() + self.set_player_lights(True, False, False, False) + self.enable_imu(True) + self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) + + def start(self, callback): + while True: + state = self.recv() + if state[0] != ProCon.InputReportID.CONTROLLER_STATE: + continue + buttons = { + ProCon.Button.A: state[3] & 0x08 > 0, + ProCon.Button.B: state[3] & 0x04 > 0, + ProCon.Button.X: state[3] & 0x02 > 0, + ProCon.Button.Y: state[3] & 0x01 > 0, + ProCon.Button.UP: state[5] & 0x02 > 0, + ProCon.Button.DOWN: state[5] & 0x01 > 0, + ProCon.Button.LEFT: state[5] & 0x08 > 0, + ProCon.Button.RIGHT: state[5] & 0x04 > 0, + ProCon.Button.MINUS: state[4] & 0x01 > 0, + ProCon.Button.PLUS: state[4] & 0x02 > 0, + ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, + ProCon.Button.HOME: state[4] & 0x10 > 0, + ProCon.Button.L: state[5] & 0x40 > 0, + ProCon.Button.ZL: state[5] & 0x80 > 0, + ProCon.Button.R: state[3] & 0x40 > 0, + ProCon.Button.ZR: state[3] & 0x80 > 0, + ProCon.Button.LS: state[4] & 0x08 > 0, + ProCon.Button.RS: state[4] & 0x04 > 0, + } + l_x = state[6] | ((state[7] & 0xF) << 8) + l_y = (state[7] >> 4) | (state[8] << 4) + r_x = state[9] | ((state[10] & 0xF) << 8) + r_y = (state[10] >> 4) | (state[11] << 4) + l_x = self.apply_stick_calibration(l_x, 0, 0) + l_y = self.apply_stick_calibration(l_y, 0, 1) + r_x = self.apply_stick_calibration(r_x, 1, 0) + r_y = self.apply_stick_calibration(r_y, 1, 1) + l_stick = (l_x, l_y) + r_stick = (r_x, r_y) + accel = ( + state[13] | state[14] << 8, + state[15] | state[16] << 8, + state[17] | state[18] << 8, + ) + gyro = ( + state[19] | state[20] << 8, + state[21] | state[22] << 8, + state[23] | state[24] << 8, + ) + accel = tuple(map(to_int16, accel)) + gyro = tuple(map(to_int16, gyro)) + battery = (state[2] & 0xF0) >> 4 + callback(buttons, l_stick, r_stick, accel, gyro, battery) + if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: + self.send_rumble(False, False, 0) + + def load_stick_calibration(self): + ok, reply = self.spi_flash_read( + ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH + ) + if not ok: + raise RuntimeError("cannot load stick calibration") + self.stick_calibration = [ + [ + [ + ((reply[27] & 0xF) << 8) | reply[26], + ((reply[24] & 0xF) << 8) | reply[23], + ((reply[21] & 0xF) << 8) | reply[20], + ], + [ + (reply[28] << 4) | (reply[27] >> 4), + (reply[25] << 4) | (reply[24] >> 4), + (reply[22] << 4) | (reply[21] >> 4), + ], + ], + [ + [ + ((reply[33] & 0xF) << 8) | reply[32], + ((reply[30] & 0xF) << 8) | reply[29], + ((reply[36] & 0xF) << 8) | reply[35], + ], + [ + (reply[34] << 4) | (reply[33] >> 4), + (reply[31] << 4) | (reply[30] >> 4), + (reply[37] << 4) | (reply[36] >> 4), + ], + ], + ] + for i in range(len(self.stick_calibration)): + for j in range(len(self.stick_calibration[i])): + for k in range(len(self.stick_calibration[i][j])): + if self.stick_calibration[i][j][k] == 0xFFF: + self.stick_calibration[i][j][k] = 0 + self.stick_extends = [ + [ + [ + -int(self.stick_calibration[0][0][0] * 0.7), + int(self.stick_calibration[0][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[0][1][0] * 0.7), + int(self.stick_calibration[0][1][2] * 0.7), + ], + ], + [ + [ + -int(self.stick_calibration[1][0][0] * 0.7), + int(self.stick_calibration[1][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[1][1][0] * 0.7), + int(self.stick_calibration[1][1][2] * 0.7), + ], + ], + ] + + def apply_stick_calibration(self, value, stick, axis): + value -= self.stick_calibration[stick][axis][1] + if value < self.stick_extends[stick][axis][0]: + self.stick_extends[stick][axis][0] = value + if value > self.stick_extends[stick][axis][1]: + self.stick_extends[stick][axis][1] = value + if value > 0: + return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) + return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) + + def send(self, data): + return self.dev.write(data) == len(data) + + def recv(self): + return self.dev.read(ProCon.PACKET_SIZE) + + def send_command(self, id, wait_for_reply=True): + data = (ProCon.OutputReportID.COMMAND, id) + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True + reply = self.recv() + if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: + return True + return False + + def send_subcommand(self, id, param, wait_for_reply=True): + data = ( + (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + + (id,) + + param + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True, [] + reply = self.recv() + if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: + return True, reply + return False, [] + + def send_rumble(self, low, high, duration): + self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL + self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL + self.rumble_expire = ( + int(time.time() * 1000) + duration if (low or high) and duration else 0 + ) + data = ( + (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if self.send(data): + return True + return False + + def handshake(self): + return self.send_command(ProCon.CommandID.HANDSHAKE) + + def high_speed(self): + return self.send_command(ProCon.CommandID.HIGH_SPEED) + + def force_usb(self): + return self.send_command(ProCon.CommandID.FORCE_USB, False) + + def set_input_report_mode(self, mode): + return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) + + def spi_flash_read(self, addr, l): + param = ( + addr & 0x000000FF, + (addr & 0x0000FF00) >> 8, + (addr & 0x00FF0000) >> 16, + (addr & 0xFF000000) >> 24, + l, + ) + return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) + + def set_player_lights(self, one, two, three, four): + param = (one << 0) | (two << 1) | (three << 2) | (four << 3) + return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) + + def set_home_light(self, brightness): + intensity = 0 + if brightness > 0: + if brightness < 65: + intensity = (brightness + 5) // 10 + else: + intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) + intensity = (intensity & 0xF) << 4 + param = (0x01, intensity, intensity, 0x00) + return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) + + def enable_imu(self, enable): + return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) + + def set_imu_sensitivity(self, sensitivity): + return self.send_subcommand( + ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity + ) + + def enable_vibration(self, enable): + return self.send_subcommand( + ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) + ) + + +def print_state(buttons, l_stick, r_stick, accel, gyro, battery): + print("\33[2JButtons:") + for k, v in buttons.items(): + if v: + print("[{}]".format(k), end=" ") + else: + print(" {} ".format(k), end=" ") + print() + print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) + print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) + print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) + print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) + print("Battery: {}/9".format(battery)) + + +if __name__ == "__main__": + try: + ProCon().start(print_state) + except KeyboardInterrupt: + print("\rGoodbye!") diff --git a/dev/basic_swerve/swerve_test.py b/dev/basic_swerve/swerve_test.py new file mode 100644 index 0000000..236f265 --- /dev/null +++ b/dev/basic_swerve/swerve_test.py @@ -0,0 +1,80 @@ +import math + +# radius of swerve drive base in meters +radius = 1 + +# vectors of swerve drive, using [speed (m/s), orientation (deg)] in global reference frame +swerve = [0,0] + +# vectors of motors in global frame +m1 = [0,0] +m2 = [0,0] +m3 = [0,0] + +# add two vectors +def add_two_vec(vec1, vec2): + print(vec1,vec2) + # get difference between two vectors, treating one vector as perpendicular to x axis + theta_diff = vec2[1] - vec1[1] + theta_diff = math.radians(theta_diff) + print(theta_diff) + + # since vec1 is x axis, vector 1 contributes in only the x axis + # when breaking into components, just add vec2 in x to vec1 magnitude to get x + # vec2 in y to get y + # sqrt(a^2+b^2) to get magnitude, and arctan(y/x) to get angle relative to x (add to vec1 orientation) + x_comp = vec1[0] + math.cos(theta_diff) * vec2[0] + print(x_comp) + y_comp = math.sin(theta_diff) * vec2[0] + print(y_comp) + f_mag = math.sqrt(x_comp**2 + y_comp**2) + print(f_mag) + f_angle = math.atan2(y_comp, x_comp) + math.radians(vec1[1]) + print(f_angle) + f_angle = math.degrees(f_angle) + print(f_angle) + if f_angle < -180: + f_angle += 360 + elif f_angle > 180: + f_angle -= 360 + return [f_mag, f_angle] + + +# input velocity [speed (m/s), orientation (deg)] in local reference frame +# rotation (rad/s) +def convert(velocity, rotation): + + # the vector for each motor in global reference frame is given by adding + # 1) velocity (relative to global reference frame) + # 2) rotation vector (vector perpendicular to each motor relative to the body reference frame) + # because the frame is circular, magnitude of vector is calculated by V = rw (formula for tangential speed relative to radius and rad/s) + + # setting all vectors to velocity relative to local reference frame + m1 = [velocity[0], velocity[1]] + m2 = [velocity[0], velocity[1]-120] + m3 = [velocity[0], velocity[1]-240] + + # rotation vector relative to global reference + # three motors, with motor 1 having an offset of 0 relative to frame + # because rotation vectors must be perpendicular, add 90 to each + # to convert from local to global, add orientation of frame in global reference frame + # rot_ang_m1 = 0+90+swerve[1] + # rot_ang_m2 = 120+90+swerve[1] + # rot_ang_m3 = 240+90+swerve[1] + + # create magnitude for each vector + rot_mag = rotation * radius + dir = 1 + + if rot_mag < 0: + dir *= -1 + rot_mag *= -1 + print(rot_mag) + # add two vectors (in local frame) based on direction and magnitude + m1 = add_two_vec(m1, [rot_mag, 90*dir]) + m2 = add_two_vec(m2, [rot_mag, 90*dir]) + m3 = add_two_vec(m3, [rot_mag, 90*dir]) + + print(m1, m2, m3) + +convert([10,0],0) \ No newline at end of file diff --git a/dev/i2c-comms/CMakeLists.txt b/dev/i2c-comms/CMakeLists.txt new file mode 100644 index 0000000..2fd69ae --- /dev/null +++ b/dev/i2c-comms/CMakeLists.txt @@ -0,0 +1,54 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(pico_i2c C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(pico_i2c pico_i2c.c ) + +pico_set_program_name(pico_i2c "pico_i2c") +pico_set_program_version(pico_i2c "0.1") + +pico_enable_stdio_uart(pico_i2c 0) +pico_enable_stdio_usb(pico_i2c 1) + +# Add the standard library to the build +target_link_libraries(pico_i2c + pico_stdlib + pico_i2c_slave + hardware_i2c + ) + +# Add the standard include files to the build +target_include_directories(pico_i2c PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(pico_i2c + hardware_i2c + ) + +pico_add_extra_outputs(pico_i2c) + diff --git a/dev/i2c-comms/pi_i2c.py b/dev/i2c-comms/pi_i2c.py new file mode 100644 index 0000000..c912136 --- /dev/null +++ b/dev/i2c-comms/pi_i2c.py @@ -0,0 +1,29 @@ +import fcntl +import os +import time + +I2C_PRIM = 0x0703 + +# open i2c devices (sudo apt install i2c-tools) +i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) + +# set the i2c address of pico +pico_address = 0x08 +fcntl.ioctl(i2c_fd, I2C_PRIM, pico_address) + +# send data to pico +data = [0x01, 0x02, 0x03] # example data +while True: + try: + os.write(i2c_fd, bytes(data)) + time.sleep(0.02) + print("Sent data to Pico: ", list(data)) + except OSError: + print("Remote I/O Error") + # read data from pico + try: + incoming_data = os.read(i2c_fd, 3) # read 3 bytes + time.sleep(0.02) + print("Received data from Pico: ", list(incoming_data)) + except TimeoutError: + print("Timeout Error") diff --git a/dev/i2c-comms/pi_spi.c b/dev/i2c-comms/pi_spi.c new file mode 100644 index 0000000..b98a690 --- /dev/null +++ b/dev/i2c-comms/pi_spi.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include + +#define SPI_DEVICE "/dev/spidev0.0" +#define SPI_SPEED 1000000 // 1MHz + +int main() +{ + int spi_fd; + unsigned char tx_data[] = {0xAA, 0xBB, 0xCC, 0xDD}; + unsigned char rx_data[sizeof(tx_data)]; + + spi_fd = open(SPI_DEVICE, O_RDWR); + if (spi_fd < 0) + { + perror("Error opening SPI device"); + return -1; + } + + int mode = SPI_MODE_0; + if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) == -1) + { + perror("Error setting SPI mode"); + return -1; + } + + if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED) == -1) + { + perror("Error setting SPI speed"); + return -1; + } + + struct spi_ioc_transfer transfer = { + .tx_buf = (unsigned long)tx_data, + .rx_buf = (unsigned long)rx_data, + .len = sizeof(tx_data), + .speed_hz = SPI_SPEED, + .bits_per_word = 8, + }; + + while (1) + { + if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer) == -1) + { + perror("Error during SPI message transfer"); + return -1; + } + + printf("Received data: "); + for (int i = 0; i < sizeof(rx_data); i++) + { + printf(" %02X", rx_data[i]); + } + printf("\n"); + sleep(1); + } + close(spi_fd); + return 0; +} \ No newline at end of file diff --git a/dev/i2c-comms/pico_i2c.c b/dev/i2c-comms/pico_i2c.c new file mode 100644 index 0000000..7bde92b --- /dev/null +++ b/dev/i2c-comms/pico_i2c.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#ifndef PICO_DEFAULT_LED_PIN +#warning blink requires a board with a regular LED +#else +const uint LED_PIN = PICO_DEFAULT_LED_PIN; +#endif + +uint8_t outgoing_data[4] = {0x11, 0x12, 0x13, 0x14}; // example data +uint8_t incoming_data[3]; +int data_index = 0; + +static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +{ + switch (event) + { + case I2C_SLAVE_RECEIVE: // master has written some data + for (int i = 0; i < 3; i++) + { + if (incoming_data[i] == 0x00) + { + incoming_data[i] = i2c_read_byte_raw(i2c); + printf("Received data %d: 0x%02X\n ", i, incoming_data[i]); + gpio_put(LED_PIN, 1); + break; + } + } + break; + case I2C_SLAVE_REQUEST: // master is requesting data + i2c_write_byte_raw(i2c, outgoing_data[data_index]); + printf("Sent data %d: 0x%02X\n ", data_index, outgoing_data[data_index]); + gpio_put(LED_PIN, 0); + data_index++; + if (data_index > 3) + { + data_index = 0; + } + break; + case I2C_SLAVE_FINISH: // master has signalled Stop / Restart + data_index = 0; + for (int i = 0; i < 3; i++) + { + incoming_data[i] = 0x00; + } + printf("reset\n"); + break; + default: + break; + } +} + +int main() +{ + stdio_init_all(); + +#ifndef PICO_DEFAULT_LED_PIN +#warning blink requires a board with a regular LED +#else + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); +#endif + + // init i2c at 100kHz + i2c_init(i2c0, 100 * 1000); + gpio_set_function(0, GPIO_FUNC_I2C); + gpio_set_function(1, GPIO_FUNC_I2C); + + // set i2c address for pico + i2c_slave_init(i2c0, 0x08, &i2c_handler); + + while (1) + ; + + return 0; +} \ No newline at end of file diff --git a/dev/i2c-comms/pico_sdk_import.cmake b/dev/i2c-comms/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/i2c-comms/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/i2c-comms/pico_spi.c b/dev/i2c-comms/pico_spi.c new file mode 100644 index 0000000..a3d8b18 --- /dev/null +++ b/dev/i2c-comms/pico_spi.c @@ -0,0 +1,35 @@ +#include +#include "pico/stdlib.h" +#include "hardware/spi.h" + +#define SPI_PORT spi0 +#define PIN_MISO 16 +#define PIN_CS 17 + +int main() +{ + stdio_init_all(); + + spi_init(SPI_PORT, 1000 * 1000); // init at 1MHz + gpio_set_function(PIN_MISO, GPIO_FUNC_SPI); // set pin to SPI mode + + gpi_init(PIN_CS); + gpio_set_dir(PIN_CS, GPIO_OUT); + + while (1) + { + gpio_put(PIN_CS, 1); // set CS high to indiciate start of communication + uint8_t rx_data[4]; + spi_read_blocking(SPI_PORT, 0, rx_data, sizeof(rx_data)); // read data from pi + gpio_put(PIN_CS, 0); // set CS low to indicate end of communication + + printf("Received: "); + for (int i = 0; i < sizeof(rx_data); i++) + { + printf(" %02X", rx_data[i]); + } + printf("\n"); + sleep_ms(1000); + } + return 0; +} \ No newline at end of file diff --git a/dev/i2c_drive/CMakeLists.txt b/dev/i2c_drive/CMakeLists.txt new file mode 100644 index 0000000..914fc65 --- /dev/null +++ b/dev/i2c_drive/CMakeLists.txt @@ -0,0 +1,55 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(i2c_drive_pico C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(i2c_drive_pico i2c_drive_pico.c ) + +pico_set_program_name(i2c_drive_pico "i2c_drive_pico") +pico_set_program_version(i2c_drive_pico "0.1") + +pico_enable_stdio_uart(i2c_drive_pico 0) +pico_enable_stdio_usb(i2c_drive_pico 1) + +# Add the standard library to the build +target_link_libraries(i2c_drive_pico + pico_stdlib + pico_i2c_slave + hardware_i2c + hardware_pwm + ) + +# Add the standard include files to the build +target_include_directories(i2c_drive_pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(i2c_drive_pico + hardware_i2c + ) + +pico_add_extra_outputs(i2c_drive_pico) + diff --git a/dev/i2c_drive/Controller.py b/dev/i2c_drive/Controller.py new file mode 100644 index 0000000..a7e9ace --- /dev/null +++ b/dev/i2c_drive/Controller.py @@ -0,0 +1,204 @@ +import math +import threading + +from inputs import \ + get_gamepad # Import the get_gamepad function from the inputs module +from procon import ProCon # Import the ProCon class from the procon module + + +# This class represents a PS4 Controller +class PS4_Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + # This method resets all controller variables to their initial state + def reset_vars(self): + # Initialize all controller variables to 0 + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + # This method starts a new thread to monitor the controller + def start_thread(self, thread_args=()): + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=thread_args + ) + self._monitor_thread.daemon = ( + True # Set the thread as a daemon so it will end when the main program ends + ) + self._monitor_thread.start() # Start the thread + + # This method returns the current state of all buttons/triggers + def read(self): + return [ + self.LeftJoystickY, + self.LeftJoystickX, + self.RightJoystickY, + self.RightJoystickX, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + # This method returns the controller object itself + def read_self(self): + return self + + # This method applies a threshold to a value + def threshold(self, val): + return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 + + def _monitor_controller(self): + while True: + events = get_gamepad() + for event in events: + if event.code == "ABS_Y": + self.LeftJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_X": + self.LeftJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RY": + self.RightJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RX": + self.RightJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_Z": + self.LeftTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "ABS_RZ": + self.RightTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "BTN_TL": + self.LeftBumper = event.state + elif event.code == "BTN_TR": + self.RightBumper = event.state + elif event.code == "BTN_SOUTH": + self.A = event.state + elif event.code == "BTN_NORTH": + self.Y = event.state # previously switched with X + elif event.code == "BTN_WEST": + self.X = event.state # previously switched with Y + elif event.code == "BTN_EAST": + self.B = event.state + elif event.code == "BTN_THUMBL": + self.LeftThumb = event.state + elif event.code == "BTN_THUMBR": + self.RightThumb = event.state + elif event.code == "BTN_SELECT": + self.Back = event.state + elif event.code == "BTN_START": + self.Start = event.state + elif event.code == "BTN_TRIGGER_HAPPY1": + self.LeftDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY2": + self.RightDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY3": + self.UpDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY4": + self.DownDPad = event.state + + +# This class represents the Xbox Controller in WRP used for the CPSRC GEM +class Gem_Xbox_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + +# This class represents the Nintendo Pro Controller +class Nintendo_Pro_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.1 # Threshold for joystick deadzone + self.controller = ProCon() # Initialize the ProCon controller + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread( + self.procon_callback_func + ) # Start a new thread to monitor the controller + + # This method is called when the ProCon controller state changes + def procon_callback_func(self, buttons, l_stick, r_stick, *_): + # Update the controller variables based on the new state + # The joystick values are normalized between -1 and 1 + # The threshold method is used to apply a deadband to the joystick values + # The button values are either 0 or 1 + self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) + self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) + self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) + self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) + self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) + self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) + self.LeftBumper = buttons[ProCon.Button.L] + self.RightBumper = buttons[ProCon.Button.R] + self.A = buttons[ProCon.Button.A] + self.B = buttons[ProCon.Button.B] + self.X = buttons[ProCon.Button.X] + self.Y = buttons[ProCon.Button.Y] + self.LeftThumb = buttons[ProCon.Button.LS] + self.RightThumb = buttons[ProCon.Button.RS] + self.Back = buttons[ProCon.Button.MINUS] + self.Start = buttons[ProCon.Button.PLUS] + self.LeftDPad = buttons[ProCon.Button.LEFT] + self.RightDPad = buttons[ProCon.Button.RIGHT] + self.UpDPad = buttons[ProCon.Button.UP] + self.DownDPad = buttons[ProCon.Button.DOWN] + + +if __name__ == "__main__": + joy = PS4_Controller() # Initialize a PS4 controller + # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller + # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller + while True: + try: + print(joy.read()) # Print the current state of the controller + except Exception as e: + print("error!", e) # Print any errors that occur + break # Exit the loop if an error occurs diff --git a/dev/i2c_drive/i2c_drive_pi.py b/dev/i2c_drive/i2c_drive_pi.py new file mode 100644 index 0000000..230f98e --- /dev/null +++ b/dev/i2c_drive/i2c_drive_pi.py @@ -0,0 +1,59 @@ +import fcntl +import os +import time +import struct + +from Controller import PS4_Controller + +I2C_PRIM = 0x0703 # I2C primary address + +# Open i2c devices (sudo apt install i2c-tools) +i2c_fd = os.open("/dev/i2c-1", os.O_RDWR) # File descriptor for the i2c device + +# Set the i2c address of pico +pico_address = 0x08 # Address of the pico on the i2c bus +fcntl.ioctl( + i2c_fd, I2C_PRIM, pico_address +) # Set the address of the i2c device to communicate with + +# Send data to pico +joy = PS4_Controller() # Initialize the controller +delay = 0.05 # Delay between each read/write operation + +while True: # Infinite loop + status = joy.read_self() # Read the status of the controller + x = status.LeftJoystickX # Get the X position of the left joystick + y = status.RightJoystickY # Get the Y position of the right joystick + joystickswitch = x > 0 # Check if the joystick is moved to the right + + x_b = struct.pack('f', x) + y_b = struct.pack('f', y) + + data = bytes([0xFA]) + \ + x_b + \ + y_b + \ + bytes([0xFB, 0x00]) # Prepare the data to be sent + # data = [0xFA, int(joystickswitch), int(joystickswitch), 0xFB, 0x00] + + print(len(data)) + print(bytes(data)) + + try: + os.write(i2c_fd, data) # Write the data to the i2c device + # os.write(i2c_fd, bytes(data)) # Write the data to the i2c device + time.sleep(delay) # Wait for a while + print("Sent data to Pico: ", list(data)) # Print the data that was sent + except OSError: + print( + "Remote I/O Error" + ) # Print an error message if there was a problem with the write operation + + # Read data from pico + try: + incoming_data = os.read(i2c_fd, 1) # Read 1 byte from the i2c device + time.sleep(delay) # Wait for a while + print( + "Received data from Pico: ", list(incoming_data) + ) # Print the data that was received + except TimeoutError: + print("Timeout Error") # Print an error message if there was a timeout error diff --git a/dev/i2c_drive/i2c_drive_pico.c b/dev/i2c_drive/i2c_drive_pico.c new file mode 100644 index 0000000..1b89574 --- /dev/null +++ b/dev/i2c_drive/i2c_drive_pico.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include +#include + +// Define constants for I2C communication +#define I2C_PICO_ADDR 0x08 +#define I2C_SDA_PIN 0 +#define I2C_SCL_PIN 1 +#define I2C_PORT i2c0 +#define I2C_BAUDRATE 100 * 1000 + +// Define the length of the data packet +#define I2C_DATA_LENGTH 10 + +#define MESSAGE_START 0xFA +#define MESSAGE_STOP 0xFB + + +// digital low on in# pins indicates direction, both high is no signal +#define turn_in1_pin 4 // 1A, forward direction +#define turn_in2_pin 5 // 1B, backward direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define turn_pwm_pin 9 // 2A, turn motor speed +#define wheel_pwm_pin 8 // 2B, wheel motor speed +#define pwm_slice 4 +#define turn_channel PWM_CHAN_B +#define wheel_channel PWM_CHAN_A + +#define wheel_in1_pin 6 // 3A, forward direction +#define wheel_in2_pin 7 // 3B, backard direction + +// #define freq 500 // note: use clock management frequencies to set frequency +// #define duty_cycle 1 +#define count_max 65535 + + +// Buffer for incoming data +uint8_t incoming_data[I2C_DATA_LENGTH]; + +// Status of the input data +uint8_t input_status = 0; + +// Last event that occurred +int last_event = 0; + +// Index of the current data byte +int data_index = 0; + +// Buffer for the input data +uint8_t input[I2C_DATA_LENGTH - 2]; + +// Handler for I2C events +static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +{ + switch (event) + { + case I2C_SLAVE_RECEIVE:{ + // Read the data + uint8_t tmp = i2c_read_byte_raw(i2c); + // Check if the data is valid + // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff + if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) + { + printf("Invalid data %x\n", tmp); + break; + } + // Store the data + incoming_data[data_index] = tmp; + // printf("Data: %d\n", incoming_data[data_index]); + data_index++; + // set the event status to received + last_event = 1; + break; + } + + + case I2C_SLAVE_REQUEST: // Pi is requesting data + // Write the data into the void + i2c_write_byte_raw(i2c, (uint8_t)input_status); + // set the event status to sent + last_event = 2; + break; + + case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart + // if the last event was a receive event and the data is valid + if (last_event == 1) + if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) + { + // move the data into the input array + for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) + { + input[i] = (int)incoming_data[i + 1]; + } + // set the input status to ready + input_status = 1; + + // Reset incoming_data + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + incoming_data[i] = 0x00; + } + } + data_index = 0; + break; + default: + break; + } +} + + +bool read = false; + +float joyX = 0.0f; +float joyY = 0.0f; + +void setup() +{ // setup pins for pwm functions + stdio_init_all(); + gpio_init(turn_in1_pin); + gpio_init(turn_in2_pin); + gpio_init(wheel_in1_pin); + gpio_init(wheel_in2_pin); + + // check if default output signal is 0, for now put this in + gpio_put(turn_in1_pin, 0); + gpio_put(turn_in2_pin, 0); + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 0); + + gpio_set_dir(turn_in1_pin, GPIO_OUT); + gpio_set_dir(turn_in2_pin, GPIO_OUT); + gpio_set_dir(wheel_in1_pin, GPIO_OUT); + gpio_set_dir(wheel_in2_pin, GPIO_OUT); + + gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); + pwm_set_wrap(pwm_slice, count_max); + + pwm_set_chan_level(pwm_slice, turn_channel, 0); + pwm_set_chan_level(pwm_slice, wheel_channel, 0); + + pwm_set_enabled(pwm_slice, true); +} + +int main() +{ + + stdio_init_all(); + + // Initialize I2C at 100kHz + i2c_init(I2C_PORT, I2C_BAUDRATE); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + + // Set I2C address for Pico + i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); + + setup();//setup for pwm + + while (1) + { + if (input_status ==1){ + uint64_t tmp_float = 0; //use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + for (int i = 7; i >= 0; i--) //bytes are stored in int8 array (64 bits), pico reads backwards + { + tmp_float |= input[i]; //write byte at a time to tmp_float and shift + if(i!=0) + tmp_float<<=8; //preserves order of bits in 64bit int + } + joyX = *(((float*)&tmp_float)); //convert to interprit bits as float (32 bits) + joyY = *(((float*)&tmp_float)+1); + printf("%f ", joyX); + printf("%f\n", joyY); //printing floats in console + tmp_float = 0; //clear float + } + + if (joyX == 0) + { // in1 and in2 are high + // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + } + else if (joyX < 0) + { // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + gpio_put(turn_in1_pin, 0); + } + + // wheel motor + if (joyY == 0) + { // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + } + else if (joyY < 0) + { // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 1); + } + + pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(joyX* count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(joyY * count_max))); + } + + return 0; +} \ No newline at end of file diff --git a/dev/i2c_drive/pico_sdk_import.cmake b/dev/i2c_drive/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/i2c_drive/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/i2c_drive/procon.py b/dev/i2c_drive/procon.py new file mode 100644 index 0000000..8e04765 --- /dev/null +++ b/dev/i2c_drive/procon.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 + +import math +import time + +import hid # pip install hidapi + + +def to_int16(uint16): + return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 + + +class ProCon: + VENDOR_ID = 0x057E + PRODUCT_ID = 0x2009 + PACKET_SIZE = 64 + CALIBRATION_OFFSET = 0x603D + CALIBRATION_LENGTH = 0x12 + COMMAND_RETRIES = 10 + RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) + RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) + DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) + + class OutputReportID: + RUMBLE_SUBCOMMAND = 0x01 + RUMBLE = 0x10 + COMMAND = 0x80 + + class InputReportID: + SUBCOMMAND_REPLY = 0x21 + CONTROLLER_STATE = 0x30 + COMMAND_ACK = 0x81 + + class CommandID: + HANDSHAKE = 0x02 + HIGH_SPEED = 0x03 + FORCE_USB = 0x04 + + class SubcommandID: + SET_INPUT_REPORT_MODE = 0x03 + SPI_FLASH_READ = 0x10 + SET_PLAYER_LIGHTS = 0x30 + SET_HOME_LIGHT = 0x38 + ENABLE_IMU = 0x40 + SET_IMU_SENSITIVITY = 0x41 + ENABLE_VIBRATION = 0x48 + + class Button: + A = "A" + B = "B" + X = "X" + Y = "Y" + UP = "Up" + DOWN = "Down" + LEFT = "Left" + RIGHT = "Right" + MINUS = "-" + PLUS = "+" + SCREENSHOT = "Screenshot" + HOME = "Home" + L = "L" + ZL = "ZL" + R = "R" + ZR = "ZR" + LS = "LS" + RS = "RS" + + def __init__(self): + self.subcommand_counter = 0 + self.dev = hid.device() + self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) + self.handshake() + self.high_speed() + self.handshake() + self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL + self.rumble_expire = 0 + self.load_stick_calibration() + self.enable_vibration(True) + self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) + self.force_usb() + self.set_player_lights(True, False, False, False) + self.enable_imu(True) + self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) + + def start(self, callback): + while True: + state = self.recv() + if state[0] != ProCon.InputReportID.CONTROLLER_STATE: + continue + buttons = { + ProCon.Button.A: state[3] & 0x08 > 0, + ProCon.Button.B: state[3] & 0x04 > 0, + ProCon.Button.X: state[3] & 0x02 > 0, + ProCon.Button.Y: state[3] & 0x01 > 0, + ProCon.Button.UP: state[5] & 0x02 > 0, + ProCon.Button.DOWN: state[5] & 0x01 > 0, + ProCon.Button.LEFT: state[5] & 0x08 > 0, + ProCon.Button.RIGHT: state[5] & 0x04 > 0, + ProCon.Button.MINUS: state[4] & 0x01 > 0, + ProCon.Button.PLUS: state[4] & 0x02 > 0, + ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, + ProCon.Button.HOME: state[4] & 0x10 > 0, + ProCon.Button.L: state[5] & 0x40 > 0, + ProCon.Button.ZL: state[5] & 0x80 > 0, + ProCon.Button.R: state[3] & 0x40 > 0, + ProCon.Button.ZR: state[3] & 0x80 > 0, + ProCon.Button.LS: state[4] & 0x08 > 0, + ProCon.Button.RS: state[4] & 0x04 > 0, + } + l_x = state[6] | ((state[7] & 0xF) << 8) + l_y = (state[7] >> 4) | (state[8] << 4) + r_x = state[9] | ((state[10] & 0xF) << 8) + r_y = (state[10] >> 4) | (state[11] << 4) + l_x = self.apply_stick_calibration(l_x, 0, 0) + l_y = self.apply_stick_calibration(l_y, 0, 1) + r_x = self.apply_stick_calibration(r_x, 1, 0) + r_y = self.apply_stick_calibration(r_y, 1, 1) + l_stick = (l_x, l_y) + r_stick = (r_x, r_y) + accel = ( + state[13] | state[14] << 8, + state[15] | state[16] << 8, + state[17] | state[18] << 8, + ) + gyro = ( + state[19] | state[20] << 8, + state[21] | state[22] << 8, + state[23] | state[24] << 8, + ) + accel = tuple(map(to_int16, accel)) + gyro = tuple(map(to_int16, gyro)) + battery = (state[2] & 0xF0) >> 4 + callback(buttons, l_stick, r_stick, accel, gyro, battery) + if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: + self.send_rumble(False, False, 0) + + def load_stick_calibration(self): + ok, reply = self.spi_flash_read( + ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH + ) + if not ok: + raise RuntimeError("cannot load stick calibration") + self.stick_calibration = [ + [ + [ + ((reply[27] & 0xF) << 8) | reply[26], + ((reply[24] & 0xF) << 8) | reply[23], + ((reply[21] & 0xF) << 8) | reply[20], + ], + [ + (reply[28] << 4) | (reply[27] >> 4), + (reply[25] << 4) | (reply[24] >> 4), + (reply[22] << 4) | (reply[21] >> 4), + ], + ], + [ + [ + ((reply[33] & 0xF) << 8) | reply[32], + ((reply[30] & 0xF) << 8) | reply[29], + ((reply[36] & 0xF) << 8) | reply[35], + ], + [ + (reply[34] << 4) | (reply[33] >> 4), + (reply[31] << 4) | (reply[30] >> 4), + (reply[37] << 4) | (reply[36] >> 4), + ], + ], + ] + for i in range(len(self.stick_calibration)): + for j in range(len(self.stick_calibration[i])): + for k in range(len(self.stick_calibration[i][j])): + if self.stick_calibration[i][j][k] == 0xFFF: + self.stick_calibration[i][j][k] = 0 + self.stick_extends = [ + [ + [ + -int(self.stick_calibration[0][0][0] * 0.7), + int(self.stick_calibration[0][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[0][1][0] * 0.7), + int(self.stick_calibration[0][1][2] * 0.7), + ], + ], + [ + [ + -int(self.stick_calibration[1][0][0] * 0.7), + int(self.stick_calibration[1][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[1][1][0] * 0.7), + int(self.stick_calibration[1][1][2] * 0.7), + ], + ], + ] + + def apply_stick_calibration(self, value, stick, axis): + value -= self.stick_calibration[stick][axis][1] + if value < self.stick_extends[stick][axis][0]: + self.stick_extends[stick][axis][0] = value + if value > self.stick_extends[stick][axis][1]: + self.stick_extends[stick][axis][1] = value + if value > 0: + return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) + return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) + + def send(self, data): + return self.dev.write(data) == len(data) + + def recv(self): + return self.dev.read(ProCon.PACKET_SIZE) + + def send_command(self, id, wait_for_reply=True): + data = (ProCon.OutputReportID.COMMAND, id) + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True + reply = self.recv() + if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: + return True + return False + + def send_subcommand(self, id, param, wait_for_reply=True): + data = ( + (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + + (id,) + + param + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True, [] + reply = self.recv() + if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: + return True, reply + return False, [] + + def send_rumble(self, low, high, duration): + self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL + self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL + self.rumble_expire = ( + int(time.time() * 1000) + duration if (low or high) and duration else 0 + ) + data = ( + (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if self.send(data): + return True + return False + + def handshake(self): + return self.send_command(ProCon.CommandID.HANDSHAKE) + + def high_speed(self): + return self.send_command(ProCon.CommandID.HIGH_SPEED) + + def force_usb(self): + return self.send_command(ProCon.CommandID.FORCE_USB, False) + + def set_input_report_mode(self, mode): + return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) + + def spi_flash_read(self, addr, l): + param = ( + addr & 0x000000FF, + (addr & 0x0000FF00) >> 8, + (addr & 0x00FF0000) >> 16, + (addr & 0xFF000000) >> 24, + l, + ) + return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) + + def set_player_lights(self, one, two, three, four): + param = (one << 0) | (two << 1) | (three << 2) | (four << 3) + return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) + + def set_home_light(self, brightness): + intensity = 0 + if brightness > 0: + if brightness < 65: + intensity = (brightness + 5) // 10 + else: + intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) + intensity = (intensity & 0xF) << 4 + param = (0x01, intensity, intensity, 0x00) + return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) + + def enable_imu(self, enable): + return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) + + def set_imu_sensitivity(self, sensitivity): + return self.send_subcommand( + ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity + ) + + def enable_vibration(self, enable): + return self.send_subcommand( + ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) + ) + + +def print_state(buttons, l_stick, r_stick, accel, gyro, battery): + print("\33[2JButtons:") + for k, v in buttons.items(): + if v: + print("[{}]".format(k), end=" ") + else: + print(" {} ".format(k), end=" ") + print() + print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) + print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) + print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) + print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) + print("Battery: {}/9".format(battery)) + + +if __name__ == "__main__": + try: + ProCon().start(print_state) + except KeyboardInterrupt: + print("\rGoodbye!") diff --git a/dev/motor_test_python/Controller.py b/dev/motor_test_python/Controller.py new file mode 100644 index 0000000..97e20b7 --- /dev/null +++ b/dev/motor_test_python/Controller.py @@ -0,0 +1,258 @@ +from inputs import get_gamepad # pip install inputs +import math +import threading +from procon import ProCon + + +class Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) + self.MAX_JOY_VAL = math.pow(2, 7) + self.THRESHOLD = 0.03 + + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=() + ) + self._monitor_thread.daemon = True + self._monitor_thread.start() + + def read(self): # return the buttons/triggers that you care about in this methode + # x = self.LeftJoystickX + # y = self.LeftJoystickY + # a = self.A + # b = self.X # b=1, x=2 + # rb = self.RightBumper + # return [x, y, a, b, rb] + return [ + self.LeftJoystickY, + self.LeftJoystickX, + self.RightJoystickY, + self.RightJoystickX, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + def read_self(self): + return self + + def threshold(self, val): + return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 + + def _monitor_controller(self): + while True: + events = get_gamepad() + for event in events: + if event.code == "ABS_Y": + self.LeftJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_X": + self.LeftJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RY": + self.RightJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RX": + self.RightJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_Z": + self.LeftTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "ABS_RZ": + self.RightTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "BTN_TL": + self.LeftBumper = event.state + elif event.code == "BTN_TR": + self.RightBumper = event.state + elif event.code == "BTN_SOUTH": + self.A = event.state + elif event.code == "BTN_NORTH": + self.Y = event.state # previously switched with X + elif event.code == "BTN_WEST": + self.X = event.state # previously switched with Y + elif event.code == "BTN_EAST": + self.B = event.state + elif event.code == "BTN_THUMBL": + self.LeftThumb = event.state + elif event.code == "BTN_THUMBR": + self.RightThumb = event.state + elif event.code == "BTN_SELECT": + self.Back = event.state + elif event.code == "BTN_START": + self.Start = event.state + elif event.code == "BTN_TRIGGER_HAPPY1": + self.LeftDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY2": + self.RightDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY3": + self.UpDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY4": + self.DownDPad = event.state + + +class GemXboxController(Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) + self.MAX_JOY_VAL = math.pow(2, 15) + self.THRESHOLD = 0.03 + + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=() + ) + self._monitor_thread.daemon = True + self._monitor_thread.start() + + +class NintendoProController(Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) + self.MAX_JOY_VAL = math.pow(2, 15) + self.THRESHOLD = 0.1 + self.controller = ProCon() + + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + self._monitor_thread = threading.Thread( + target=self.controller.start, args=(self.procon_callback_func,) + ) + self._monitor_thread.daemon = True + self._monitor_thread.start() + + def read(self): + return [ + self.LeftJoystickX, + self.LeftJoystickY, + self.RightJoystickX, + self.RightJoystickY, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + def procon_callback_func(self, buttons, l_stick, r_stick, *_): + self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) + self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) + self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) + self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) + self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) + self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) + self.LeftBumper = buttons[ProCon.Button.L] + self.RightBumper = buttons[ProCon.Button.R] + self.A = buttons[ProCon.Button.A] + self.B = buttons[ProCon.Button.B] + self.X = buttons[ProCon.Button.X] + self.Y = buttons[ProCon.Button.Y] + self.LeftThumb = buttons[ProCon.Button.LS] + self.RightThumb = buttons[ProCon.Button.RS] + self.Back = buttons[ProCon.Button.MINUS] + self.Start = buttons[ProCon.Button.PLUS] + self.LeftDPad = buttons[ProCon.Button.LEFT] + self.RightDPad = buttons[ProCon.Button.RIGHT] + self.UpDPad = buttons[ProCon.Button.UP] + self.DownDPad = buttons[ProCon.Button.DOWN] + + +if __name__ == "__main__": + joy = Controller() + # joy = GemXboxController() + # joy = NintendoProController() + while True: + try: + print(joy.read()) + except Exception as e: + print("error!", e) + break diff --git a/dev/motor_test_python/motor_test.py b/dev/motor_test_python/motor_test.py new file mode 100644 index 0000000..1e1112a --- /dev/null +++ b/dev/motor_test_python/motor_test.py @@ -0,0 +1,107 @@ +# Import the necessary libraries +import lgpio # sudo apt install python3-lgpio +from Controller import Controller + +# Define the GPIO pins for the motor controller +turn_in1_pin = 22 +turn_in2_pin = 27 +turn_pwm_pin = 17 + +wheel_in1_pin = 4 +wheel_in2_pin = 3 +wheel_pwm_pin = 2 + +# Define the frequency for the PWM signal +freq = 500 + +# Initialize the disable flag and the controller +disable = True +joy = Controller() + +# Open the GPIO chip +h = lgpio.gpiochip_open(0) + +# Claim the GPIO pins as output +lgpio.gpio_claim_output(h, turn_in1_pin) +lgpio.gpio_claim_output(h, turn_in2_pin) +lgpio.gpio_claim_output(h, wheel_in1_pin) +lgpio.gpio_claim_output(h, wheel_in2_pin) + +# Initialize the exit counter +exit_count = 0 + +try: + # Main loop + while True: + # Read the controller status + status = joy.read_self() + + # If the X button is pressed, disable the motors + if status.X: + if not disable: + print("disabling!") + disable = True + exit_count += 1 + # If the B button is pressed, enable the motors + elif status.B: + if disable: + print("enabling!") + disable = False + exit_count = 0 + + # If the motors are disabled, write 1 to all GPIO pins + if disable: + lgpio.gpio_write(h, turn_in1_pin, 1) + lgpio.gpio_write(h, turn_in2_pin, 1) + lgpio.gpio_write(h, wheel_in1_pin, 1) + lgpio.gpio_write(h, wheel_in2_pin, 1) + + # If the exit counter exceeds 50000, break the loop + if exit_count > 50000: + break + + continue + + # Read the joystick values + x = status.LeftJoystickX + y = status.RightJoystickY + + # Control the turning of the motors based on the X value of the joystick + if x == 0: + lgpio.gpio_write(h, turn_in1_pin, 1) + lgpio.gpio_write(h, turn_in2_pin, 1) + elif x < 0: + lgpio.gpio_write(h, turn_in1_pin, 1) + lgpio.gpio_write(h, turn_in2_pin, 0) + else: + lgpio.gpio_write(h, turn_in1_pin, 0) + lgpio.gpio_write(h, turn_in2_pin, 1) + + # Control the movement of the motors based on the Y value of the joystick + if y == 0: + lgpio.gpio_write(h, wheel_in1_pin, 1) + lgpio.gpio_write(h, wheel_in2_pin, 1) + elif y < 0: + lgpio.gpio_write(h, wheel_in1_pin, 1) + lgpio.gpio_write(h, wheel_in2_pin, 0) + else: + lgpio.gpio_write(h, wheel_in1_pin, 0) + lgpio.gpio_write(h, wheel_in2_pin, 1) + + # Generate the PWM signal for the motors + lgpio.tx_pwm(h, turn_pwm_pin, freq, round(abs(x) * 100.0, 2)) + lgpio.tx_pwm(h, wheel_pwm_pin, freq, round(abs(y) * 100.0, 2)) +except KeyboardInterrupt: + # If the program is interrupted, pass the exception + pass + +# When the program ends, stop the motors and release the GPIO pins +lgpio.tx_pwm(h, turn_pwm_pin, freq, 0) +lgpio.gpio_write(h, turn_in1_pin, 1) +lgpio.gpio_write(h, turn_in2_pin, 1) + +lgpio.tx_pwm(h, wheel_pwm_pin, freq, 0) +lgpio.gpio_write(h, wheel_in1_pin, 1) +lgpio.gpio_write(h, wheel_in2_pin, 1) + +lgpio.gpiochip_close(h) diff --git a/dev/motor_test_python/procon.py b/dev/motor_test_python/procon.py new file mode 100644 index 0000000..8e04765 --- /dev/null +++ b/dev/motor_test_python/procon.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 + +import math +import time + +import hid # pip install hidapi + + +def to_int16(uint16): + return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 + + +class ProCon: + VENDOR_ID = 0x057E + PRODUCT_ID = 0x2009 + PACKET_SIZE = 64 + CALIBRATION_OFFSET = 0x603D + CALIBRATION_LENGTH = 0x12 + COMMAND_RETRIES = 10 + RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) + RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) + DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) + + class OutputReportID: + RUMBLE_SUBCOMMAND = 0x01 + RUMBLE = 0x10 + COMMAND = 0x80 + + class InputReportID: + SUBCOMMAND_REPLY = 0x21 + CONTROLLER_STATE = 0x30 + COMMAND_ACK = 0x81 + + class CommandID: + HANDSHAKE = 0x02 + HIGH_SPEED = 0x03 + FORCE_USB = 0x04 + + class SubcommandID: + SET_INPUT_REPORT_MODE = 0x03 + SPI_FLASH_READ = 0x10 + SET_PLAYER_LIGHTS = 0x30 + SET_HOME_LIGHT = 0x38 + ENABLE_IMU = 0x40 + SET_IMU_SENSITIVITY = 0x41 + ENABLE_VIBRATION = 0x48 + + class Button: + A = "A" + B = "B" + X = "X" + Y = "Y" + UP = "Up" + DOWN = "Down" + LEFT = "Left" + RIGHT = "Right" + MINUS = "-" + PLUS = "+" + SCREENSHOT = "Screenshot" + HOME = "Home" + L = "L" + ZL = "ZL" + R = "R" + ZR = "ZR" + LS = "LS" + RS = "RS" + + def __init__(self): + self.subcommand_counter = 0 + self.dev = hid.device() + self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) + self.handshake() + self.high_speed() + self.handshake() + self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL + self.rumble_expire = 0 + self.load_stick_calibration() + self.enable_vibration(True) + self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) + self.force_usb() + self.set_player_lights(True, False, False, False) + self.enable_imu(True) + self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) + + def start(self, callback): + while True: + state = self.recv() + if state[0] != ProCon.InputReportID.CONTROLLER_STATE: + continue + buttons = { + ProCon.Button.A: state[3] & 0x08 > 0, + ProCon.Button.B: state[3] & 0x04 > 0, + ProCon.Button.X: state[3] & 0x02 > 0, + ProCon.Button.Y: state[3] & 0x01 > 0, + ProCon.Button.UP: state[5] & 0x02 > 0, + ProCon.Button.DOWN: state[5] & 0x01 > 0, + ProCon.Button.LEFT: state[5] & 0x08 > 0, + ProCon.Button.RIGHT: state[5] & 0x04 > 0, + ProCon.Button.MINUS: state[4] & 0x01 > 0, + ProCon.Button.PLUS: state[4] & 0x02 > 0, + ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, + ProCon.Button.HOME: state[4] & 0x10 > 0, + ProCon.Button.L: state[5] & 0x40 > 0, + ProCon.Button.ZL: state[5] & 0x80 > 0, + ProCon.Button.R: state[3] & 0x40 > 0, + ProCon.Button.ZR: state[3] & 0x80 > 0, + ProCon.Button.LS: state[4] & 0x08 > 0, + ProCon.Button.RS: state[4] & 0x04 > 0, + } + l_x = state[6] | ((state[7] & 0xF) << 8) + l_y = (state[7] >> 4) | (state[8] << 4) + r_x = state[9] | ((state[10] & 0xF) << 8) + r_y = (state[10] >> 4) | (state[11] << 4) + l_x = self.apply_stick_calibration(l_x, 0, 0) + l_y = self.apply_stick_calibration(l_y, 0, 1) + r_x = self.apply_stick_calibration(r_x, 1, 0) + r_y = self.apply_stick_calibration(r_y, 1, 1) + l_stick = (l_x, l_y) + r_stick = (r_x, r_y) + accel = ( + state[13] | state[14] << 8, + state[15] | state[16] << 8, + state[17] | state[18] << 8, + ) + gyro = ( + state[19] | state[20] << 8, + state[21] | state[22] << 8, + state[23] | state[24] << 8, + ) + accel = tuple(map(to_int16, accel)) + gyro = tuple(map(to_int16, gyro)) + battery = (state[2] & 0xF0) >> 4 + callback(buttons, l_stick, r_stick, accel, gyro, battery) + if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: + self.send_rumble(False, False, 0) + + def load_stick_calibration(self): + ok, reply = self.spi_flash_read( + ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH + ) + if not ok: + raise RuntimeError("cannot load stick calibration") + self.stick_calibration = [ + [ + [ + ((reply[27] & 0xF) << 8) | reply[26], + ((reply[24] & 0xF) << 8) | reply[23], + ((reply[21] & 0xF) << 8) | reply[20], + ], + [ + (reply[28] << 4) | (reply[27] >> 4), + (reply[25] << 4) | (reply[24] >> 4), + (reply[22] << 4) | (reply[21] >> 4), + ], + ], + [ + [ + ((reply[33] & 0xF) << 8) | reply[32], + ((reply[30] & 0xF) << 8) | reply[29], + ((reply[36] & 0xF) << 8) | reply[35], + ], + [ + (reply[34] << 4) | (reply[33] >> 4), + (reply[31] << 4) | (reply[30] >> 4), + (reply[37] << 4) | (reply[36] >> 4), + ], + ], + ] + for i in range(len(self.stick_calibration)): + for j in range(len(self.stick_calibration[i])): + for k in range(len(self.stick_calibration[i][j])): + if self.stick_calibration[i][j][k] == 0xFFF: + self.stick_calibration[i][j][k] = 0 + self.stick_extends = [ + [ + [ + -int(self.stick_calibration[0][0][0] * 0.7), + int(self.stick_calibration[0][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[0][1][0] * 0.7), + int(self.stick_calibration[0][1][2] * 0.7), + ], + ], + [ + [ + -int(self.stick_calibration[1][0][0] * 0.7), + int(self.stick_calibration[1][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[1][1][0] * 0.7), + int(self.stick_calibration[1][1][2] * 0.7), + ], + ], + ] + + def apply_stick_calibration(self, value, stick, axis): + value -= self.stick_calibration[stick][axis][1] + if value < self.stick_extends[stick][axis][0]: + self.stick_extends[stick][axis][0] = value + if value > self.stick_extends[stick][axis][1]: + self.stick_extends[stick][axis][1] = value + if value > 0: + return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) + return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) + + def send(self, data): + return self.dev.write(data) == len(data) + + def recv(self): + return self.dev.read(ProCon.PACKET_SIZE) + + def send_command(self, id, wait_for_reply=True): + data = (ProCon.OutputReportID.COMMAND, id) + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True + reply = self.recv() + if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: + return True + return False + + def send_subcommand(self, id, param, wait_for_reply=True): + data = ( + (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + + (id,) + + param + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True, [] + reply = self.recv() + if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: + return True, reply + return False, [] + + def send_rumble(self, low, high, duration): + self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL + self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL + self.rumble_expire = ( + int(time.time() * 1000) + duration if (low or high) and duration else 0 + ) + data = ( + (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if self.send(data): + return True + return False + + def handshake(self): + return self.send_command(ProCon.CommandID.HANDSHAKE) + + def high_speed(self): + return self.send_command(ProCon.CommandID.HIGH_SPEED) + + def force_usb(self): + return self.send_command(ProCon.CommandID.FORCE_USB, False) + + def set_input_report_mode(self, mode): + return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) + + def spi_flash_read(self, addr, l): + param = ( + addr & 0x000000FF, + (addr & 0x0000FF00) >> 8, + (addr & 0x00FF0000) >> 16, + (addr & 0xFF000000) >> 24, + l, + ) + return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) + + def set_player_lights(self, one, two, three, four): + param = (one << 0) | (two << 1) | (three << 2) | (four << 3) + return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) + + def set_home_light(self, brightness): + intensity = 0 + if brightness > 0: + if brightness < 65: + intensity = (brightness + 5) // 10 + else: + intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) + intensity = (intensity & 0xF) << 4 + param = (0x01, intensity, intensity, 0x00) + return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) + + def enable_imu(self, enable): + return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) + + def set_imu_sensitivity(self, sensitivity): + return self.send_subcommand( + ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity + ) + + def enable_vibration(self, enable): + return self.send_subcommand( + ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) + ) + + +def print_state(buttons, l_stick, r_stick, accel, gyro, battery): + print("\33[2JButtons:") + for k, v in buttons.items(): + if v: + print("[{}]".format(k), end=" ") + else: + print(" {} ".format(k), end=" ") + print() + print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) + print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) + print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) + print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) + print("Battery: {}/9".format(battery)) + + +if __name__ == "__main__": + try: + ProCon().start(print_state) + except KeyboardInterrupt: + print("\rGoodbye!") diff --git a/dev/pico_motor_test/CMakeLists.txt b/dev/pico_motor_test/CMakeLists.txt new file mode 100644 index 0000000..95e237d --- /dev/null +++ b/dev/pico_motor_test/CMakeLists.txt @@ -0,0 +1,56 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(cooler_motor_test C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +# add_executable(cooler_motor_test cooler_motor_test.cpp ) +add_executable(cooler_motor_test class_motor_test.cpp ) + +pico_set_program_name(cooler_motor_test "cooler_motor_test") +pico_set_program_version(cooler_motor_test "0.1") + +pico_enable_stdio_uart(cooler_motor_test 0) +pico_enable_stdio_usb(cooler_motor_test 1) + +# Add the standard library to the build +target_link_libraries(cooler_motor_test + pico_stdlib + pico_i2c_slave + hardware_i2c + hardware_pwm + ) + +# Add the standard include files to the build +target_include_directories(cooler_motor_test PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries(cooler_motor_test + hardware_i2c + ) + +pico_add_extra_outputs(cooler_motor_test) + diff --git a/dev/pico_motor_test/class_motor_test.cpp b/dev/pico_motor_test/class_motor_test.cpp new file mode 100644 index 0000000..0af26c5 --- /dev/null +++ b/dev/pico_motor_test/class_motor_test.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +// digital low on in# pins indicates direction, both high is no signal +#define steer_in1_pin 4 // 1A, forward direction +#define steer_in2_pin 5 // 1B, backward direction + +#define drive_in1_pin 6 // 3A, forward direction +#define drive_in2_pin 7 // 3B, backard direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define steer_pwm_pin 9 // 2A, steer motor speed +#define drive_pwm_pin 8 // 2B, drive motor speed +#define pwm_slice 4 +#define steer_channel PWM_CHAN_B +#define drive_channel PWM_CHAN_A + +#define count_max 65535 // number of counts in a cycle --> 1/count_max = freq + +class Motor +{ +public: + Motor(uint pin1, uint pin2, uint pwm_pin, uint slice_num, pwm_chan channel, bool slice = true) + { + this->pwm_pin = pwm_pin; + this->pin1 = pin1; + this->pin2 = pin2; + this->slice_num = slice_num; + this->channel = channel; + + // setup pins for pwm functions + gpio_init(pin1); + gpio_init(pin2); + + // set non-pwm pins to output + gpio_set_dir(pin1, GPIO_OUT); + gpio_set_dir(pin2, GPIO_OUT); + + // setup pwm + gpio_set_function(pwm_pin, GPIO_FUNC_PWM); + + // set pwm slice and channel + if (slice) + pwm_set_wrap(slice_num, count_max); + + pwm_set_chan_level(slice_num, channel, 0); + + if (slice) + pwm_set_enabled(slice_num, true); + } + + void set(float power) + { + power = power > 1 ? 1 : power; + power = power < -1 ? -1 : power; + printf("input power: %f\n", power); + + if (power == 0) + { // in1 and in2 are high + gpio_put(this->pin1, 1); + gpio_put(this->pin2, 1); + } + else if (power < 0) + { // in1 is high and in2 is low + gpio_put(this->pin1, 1); + gpio_put(this->pin2, 0); + } + else + { // in1 is low and in2 is high + gpio_put(this->pin1, 0); + gpio_put(this->pin2, 1); + } + pwm_set_chan_level(this->slice_num, this->channel, abs((int)(power * count_max))); + } + +private: + uint pwm_pin; + uint pin1; + uint pin2; + uint slice_num; + pwm_chan channel; +}; + +int main() +{ + // setup stdio for printing + stdio_init_all(); + + Motor steer = Motor(steer_in1_pin, steer_in2_pin, steer_pwm_pin, pwm_slice, steer_channel, true); + Motor drive = Motor(drive_in1_pin, drive_in2_pin, drive_pwm_pin, pwm_slice, drive_channel, false); + + // step size for oscillation + float step = 0.01; + int a = 0; + + while (1) + { + float power = sin(a * step); + + steer.set(power); + drive.set(power); + + a++; + if (a * step >= 6.28) + a = 0; + + sleep_ms(20); + } + return 0; +} diff --git a/dev/pico_motor_test/cooler_motor_test.cpp b/dev/pico_motor_test/cooler_motor_test.cpp new file mode 100644 index 0000000..3061a73 --- /dev/null +++ b/dev/pico_motor_test/cooler_motor_test.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include + +// digital low on in# pins indicates direction, both high is no signal +#define turn_in1_pin 4 // 1A, forward direction +#define turn_in2_pin 5 // 1B, backward direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define turn_pwm_pin 9 // 2A, turn motor speed +#define wheel_pwm_pin 8 // 2B, wheel motor speed +#define pwm_slice 4 +#define turn_channel PWM_CHAN_B +#define wheel_channel PWM_CHAN_A + +#define wheel_in1_pin 6 // 3A, forward direction +#define wheel_in2_pin 7 // 3B, backard direction + +// #define freq 500 // note: use clock management frequencies to set frequency +// #define duty_cycle 1 +#define count_max 65535 + +void setup() +{ // setup pins for pwm functions + stdio_init_all(); + gpio_init(turn_in1_pin); + gpio_init(turn_in2_pin); + gpio_init(wheel_in1_pin); + gpio_init(wheel_in2_pin); + + // check if default output signal is 0, for now put this in + gpio_put(turn_in1_pin, 0); + gpio_put(turn_in2_pin, 0); + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 0); + + gpio_set_dir(turn_in1_pin, GPIO_OUT); + gpio_set_dir(turn_in2_pin, GPIO_OUT); + gpio_set_dir(wheel_in1_pin, GPIO_OUT); + gpio_set_dir(wheel_in2_pin, GPIO_OUT); + + gpio_set_function(turn_pwm_pin, GPIO_FUNC_PWM); + gpio_set_function(wheel_pwm_pin, GPIO_FUNC_PWM); + pwm_set_wrap(pwm_slice, count_max); + + pwm_set_chan_level(pwm_slice, turn_channel, 0); + pwm_set_chan_level(pwm_slice, wheel_channel, 0); + + pwm_set_enabled(pwm_slice, true); +} + +int main() +{ + setup(); + double x = 0; + double y = 0.5; + + int xflip = 0; + int yflip = 0; + + float step = 0.001; + + while (1) + { + // // turn motor + if (x == 0) + { // in1 and in2 are high + // pwm_set_gpio_level (3, count_max); this method would work, but is iffy, as setting the count value for individual pins update next cycle + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 1); + } + else if (x < 0) + { // in1 is high and in2 is low + gpio_put(turn_in1_pin, 1); + gpio_put(turn_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(turn_in2_pin, 1); + gpio_put(turn_in1_pin, 0); + } + + // wheel motor + if (y == 0) + { // in1 and in2 are high + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 1); + } + else if (y < 0) + { // in1 is high and in2 is low + gpio_put(wheel_in1_pin, 1); + gpio_put(wheel_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(wheel_in1_pin, 0); + gpio_put(wheel_in2_pin, 1); + } + + pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(x * count_max))); + pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(y * count_max))); + // printf("hello world\n"); + if (xflip) + { + x -= step; + } + else + { + x += step; + } + if (yflip) + { + y -= step; + } + else + { + y += step; + } + if (x >= 1 || x <= -1) + xflip = !(xflip); + if (y >= 1 || y <= -1) + yflip = !(yflip); + + printf("x: %f, y: %f\n", x, y); + sleep_ms(20); + } + return 0; +} \ No newline at end of file diff --git a/dev/pico_motor_test/pico_sdk_import.cmake b/dev/pico_motor_test/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/dev/pico_motor_test/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/swerve/motor_test.py b/dev/swerve/motor_test.py new file mode 100644 index 0000000..c5e6860 --- /dev/null +++ b/dev/swerve/motor_test.py @@ -0,0 +1,88 @@ +import lgpio # sudo apt install python3-lgpio +from Controller import Controller + + +turn_in1_pin = 17 +turn_in2_pin = 27 +turn_pwm_pin = 22 + +wheel_in1_pin = 11 +wheel_in2_pin = 9 +wheel_pwm_pin = 10 + +freq = 500 +disable = True +joy = Controller() + +h = lgpio.gpiochip_open(0) + +lgpio.gpio_claim_output(h, turn_in1_pin) +lgpio.gpio_claim_output(h, turn_in2_pin) +lgpio.gpio_claim_output(h, wheel_in1_pin) +lgpio.gpio_claim_output(h, wheel_in2_pin) + +exit_count = 0 + +try: + while True: + status = joy.read_self() + + if status.X: + if not disable: + print("disabling!") + disable = True + exit_count += 1 + elif status.B: + if disable: + print("enabling!") + disable = False + exit_count = 0 + + if disable: + lgpio.gpio_write(h, turn_in1_pin, 1) + lgpio.gpio_write(h, turn_in2_pin, 1) + lgpio.gpio_write(h, wheel_in1_pin, 1) + lgpio.gpio_write(h, wheel_in2_pin, 1) + + if exit_count > 50000: + break + + continue + + x = status.LeftJoystickX + y = status.RightJoystickY + + if x == 0: + lgpio.gpio_write(h, turn_in1_pin, 1) + lgpio.gpio_write(h, turn_in2_pin, 1) + elif x < 0: + lgpio.gpio_write(h, turn_in1_pin, 1) + lgpio.gpio_write(h, turn_in2_pin, 0) + else: + lgpio.gpio_write(h, turn_in1_pin, 0) + lgpio.gpio_write(h, turn_in2_pin, 1) + + if y == 0: + lgpio.gpio_write(h, wheel_in1_pin, 1) + lgpio.gpio_write(h, wheel_in2_pin, 1) + elif y < 0: + lgpio.gpio_write(h, wheel_in1_pin, 1) + lgpio.gpio_write(h, wheel_in2_pin, 0) + else: + lgpio.gpio_write(h, wheel_in1_pin, 0) + lgpio.gpio_write(h, wheel_in2_pin, 1) + + lgpio.tx_pwm(h, turn_pwm_pin, freq, round(abs(x) * 100.0, 2)) + lgpio.tx_pwm(h, wheel_pwm_pin, freq, round(abs(y) * 100.0, 2)) +except KeyboardInterrupt: + pass + +lgpio.tx_pwm(h, turn_pwm_pin, freq, 0) +lgpio.gpio_write(h, turn_in1_pin, 1) +lgpio.gpio_write(h, turn_in2_pin, 1) + +lgpio.tx_pwm(h, wheel_pwm_pin, freq, 0) +lgpio.gpio_write(h, wheel_in1_pin, 1) +lgpio.gpio_write(h, wheel_in2_pin, 1) + +lgpio.gpiochip_close(h) diff --git a/dev/swerve_sim_joystick/Controller.py b/dev/swerve_sim_joystick/Controller.py new file mode 100644 index 0000000..a7e9ace --- /dev/null +++ b/dev/swerve_sim_joystick/Controller.py @@ -0,0 +1,204 @@ +import math +import threading + +from inputs import \ + get_gamepad # Import the get_gamepad function from the inputs module +from procon import ProCon # Import the ProCon class from the procon module + + +# This class represents a PS4 Controller +class PS4_Controller(object): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 7) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + # This method resets all controller variables to their initial state + def reset_vars(self): + # Initialize all controller variables to 0 + self.LeftJoystickY = 0 + self.LeftJoystickX = 0 + self.RightJoystickY = 0 + self.RightJoystickX = 0 + self.LeftTrigger = 0 + self.RightTrigger = 0 + self.LeftBumper = 0 + self.RightBumper = 0 + self.A = 0 + self.X = 0 + self.Y = 0 + self.B = 0 + self.LeftThumb = 0 + self.RightThumb = 0 + self.Back = 0 + self.Start = 0 + self.LeftDPad = 0 + self.RightDPad = 0 + self.UpDPad = 0 + self.DownDPad = 0 + + # This method starts a new thread to monitor the controller + def start_thread(self, thread_args=()): + self._monitor_thread = threading.Thread( + target=self._monitor_controller, args=thread_args + ) + self._monitor_thread.daemon = ( + True # Set the thread as a daemon so it will end when the main program ends + ) + self._monitor_thread.start() # Start the thread + + # This method returns the current state of all buttons/triggers + def read(self): + return [ + self.LeftJoystickY, + self.LeftJoystickX, + self.RightJoystickY, + self.RightJoystickX, + self.LeftTrigger, + self.RightTrigger, + self.LeftBumper, + self.RightBumper, + self.A, + self.B, + self.X, + self.Y, + self.LeftThumb, + self.RightThumb, + self.Back, + self.Start, + self.LeftDPad, + self.RightDPad, + self.UpDPad, + self.DownDPad, + ] + + # This method returns the controller object itself + def read_self(self): + return self + + # This method applies a threshold to a value + def threshold(self, val): + return val - 1.0 if abs(val - 1.0) > self.THRESHOLD else 0 + + def _monitor_controller(self): + while True: + events = get_gamepad() + for event in events: + if event.code == "ABS_Y": + self.LeftJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_X": + self.LeftJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RY": + self.RightJoystickY = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_RX": + self.RightJoystickX = self.threshold( + event.state / self.MAX_JOY_VAL + ) # normalize between -1 and 1 + elif event.code == "ABS_Z": + self.LeftTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "ABS_RZ": + self.RightTrigger = self.threshold( + event.state / self.MAX_TRIG_VAL + ) # normalize between 0 and 1 + elif event.code == "BTN_TL": + self.LeftBumper = event.state + elif event.code == "BTN_TR": + self.RightBumper = event.state + elif event.code == "BTN_SOUTH": + self.A = event.state + elif event.code == "BTN_NORTH": + self.Y = event.state # previously switched with X + elif event.code == "BTN_WEST": + self.X = event.state # previously switched with Y + elif event.code == "BTN_EAST": + self.B = event.state + elif event.code == "BTN_THUMBL": + self.LeftThumb = event.state + elif event.code == "BTN_THUMBR": + self.RightThumb = event.state + elif event.code == "BTN_SELECT": + self.Back = event.state + elif event.code == "BTN_START": + self.Start = event.state + elif event.code == "BTN_TRIGGER_HAPPY1": + self.LeftDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY2": + self.RightDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY3": + self.UpDPad = event.state + elif event.code == "BTN_TRIGGER_HAPPY4": + self.DownDPad = event.state + + +# This class represents the Xbox Controller in WRP used for the CPSRC GEM +class Gem_Xbox_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.03 # Threshold for joystick deadzone + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread(()) # Start a new thread to monitor the controller + + +# This class represents the Nintendo Pro Controller +class Nintendo_Pro_Controller(PS4_Controller): + def __init__(self): + self.MAX_TRIG_VAL = math.pow(2, 8) # Maximum value for trigger input + self.MAX_JOY_VAL = math.pow(2, 15) # Maximum value for joystick input + self.THRESHOLD = 0.1 # Threshold for joystick deadzone + self.controller = ProCon() # Initialize the ProCon controller + + self.reset_vars() # Reset all controller variables to their initial state + self.start_thread( + self.procon_callback_func + ) # Start a new thread to monitor the controller + + # This method is called when the ProCon controller state changes + def procon_callback_func(self, buttons, l_stick, r_stick, *_): + # Update the controller variables based on the new state + # The joystick values are normalized between -1 and 1 + # The threshold method is used to apply a deadband to the joystick values + # The button values are either 0 or 1 + self.LeftJoystickX = self.threshold(l_stick[0] / self.MAX_JOY_VAL) + self.LeftJoystickY = self.threshold(l_stick[1] / self.MAX_JOY_VAL) + self.RightJoystickX = self.threshold(r_stick[0] / self.MAX_JOY_VAL) + self.RightJoystickY = self.threshold(r_stick[1] / self.MAX_JOY_VAL) + self.LeftTrigger = self.threshold(buttons[ProCon.Button.ZL]) + self.RightTrigger = self.threshold(buttons[ProCon.Button.ZR]) + self.LeftBumper = buttons[ProCon.Button.L] + self.RightBumper = buttons[ProCon.Button.R] + self.A = buttons[ProCon.Button.A] + self.B = buttons[ProCon.Button.B] + self.X = buttons[ProCon.Button.X] + self.Y = buttons[ProCon.Button.Y] + self.LeftThumb = buttons[ProCon.Button.LS] + self.RightThumb = buttons[ProCon.Button.RS] + self.Back = buttons[ProCon.Button.MINUS] + self.Start = buttons[ProCon.Button.PLUS] + self.LeftDPad = buttons[ProCon.Button.LEFT] + self.RightDPad = buttons[ProCon.Button.RIGHT] + self.UpDPad = buttons[ProCon.Button.UP] + self.DownDPad = buttons[ProCon.Button.DOWN] + + +if __name__ == "__main__": + joy = PS4_Controller() # Initialize a PS4 controller + # joy = Gem_Xbox_Controller() # Initialize a Gem Xbox controller + # joy = Nintendo_Pro_Controller() # Initialize a Nintendo Pro controller + while True: + try: + print(joy.read()) # Print the current state of the controller + except Exception as e: + print("error!", e) # Print any errors that occur + break # Exit the loop if an error occurs diff --git a/dev/swerve_sim_joystick/joystick_sim.py b/dev/swerve_sim_joystick/joystick_sim.py new file mode 100644 index 0000000..88eb379 --- /dev/null +++ b/dev/swerve_sim_joystick/joystick_sim.py @@ -0,0 +1,195 @@ +import matplotlib.pyplot as plt +import numpy as np +from Controller import (Gem_Xbox_Controller, Nintendo_Pro_Controller, + PS4_Controller) + + +# return the vector perpendicular to the given vector +def perpendicular(vec): + return np.array([-vec[1], vec[0]]) + + +# NOTE: make sure to account for max motor speed when programming real motors, and normalize +# for example, if the swerve math commands one motor to spin higher than it's max speed, +# then it will only spin at the max speed, thus making the ratio of motor powers wrong and the robot will move wrong + + +if __name__ == "__main__": + # joy = Gem_Xbox_Controller() + # joy = Nintendo_Pro_Controller() + joy = PS4_Controller() + + rumble = type(joy) == Nintendo_Pro_Controller + + # robot radius + R = 5 + # dt, the delta time of the "animation" + DT = 0.001 + + # initial robot state + center_pos = np.array([0.0, 0.0]) # center position + module_dirs = ( + np.array([3.0, 7.0, 11.0]) / 6.0 * np.pi + ) # directions of each module, relative to screen + module_pos = np.array( + [ + [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] + for a in module_dirs + ] + ) # absolute positions of each module (as a point) + freeze_pos = ( + center_pos.copy() + ) # position to rotate about when right bumper is pressed + + while True: + try: + # get inputs + joy_input = joy.read_self() + if joy_input.Back: # exit if back button is pressed + print("Exiting") + break + + # TODO: should replace this by standardizing inverts in the Controller.py class + inverts = [False, False, False, False] # Nintendo Pro Controller + # inverts = [False, True, True] # Gem Xbox Controller + + # use joystick inputs to calculate "strafe" movement + left_x = (-1.0 if inverts[0] else 1.0) * round(joy_input.LeftJoystickX, 3) + left_y = (-1.0 if inverts[1] else 1.0) * round(joy_input.LeftJoystickY, 3) + triggers = joy_input.LeftTrigger - joy_input.RightTrigger + + right_x = (-1.0 if inverts[2] else 1.0) * round(joy_input.RightJoystickX, 3) + right_y = (-1.0 if inverts[3] else 1.0) * round(joy_input.RightJoystickY, 3) + + ## LOGIC (begin) + + # get distance between freeze_pos and center_pos + dist = np.hypot( + freeze_pos[0] - center_pos[0], freeze_pos[1] - center_pos[1] + ) + + # if right bumper is not pressed, move robot in direction of joystick & rotate relative to center pos + if not joy_input.RightBumper: + move = np.array([left_x, left_y]) * 1.0 + rotate = 0.1 * triggers + + # if right bumper is pressed and freeze pos is not "inside" robot, rotate robot around freeze pos + elif dist > R: + # calculate vector from freeze to center pos + x = (freeze_pos[0] - center_pos[0]) / dist + y = (freeze_pos[1] - center_pos[1]) / dist + + # calculate new center position, moving robot around freeze pos + # x' = x*cos(theta) - y*sin(theta) + # y' = x*sin(theta) + y*cos(theta) + # where theta is the rotation angle, but we can use left_x and left_y as sin(theta) and cos(theta) + # https://academo.org/demos/rotation-about-point/ + move = np.array( + [-1.0 * y * left_x + x * left_y, x * left_x + y * left_y] + ) + # rotate robot so direction of modules is the same relative to freeze pos, plus some rotation from triggers + rotate = (-1.0 if left_x > 0 else 1.0) * np.hypot( + move[0], move[1] + ) / dist + 0.1 * triggers + + # if left bumper is pressed, make freeze pos the same as center pos + if joy_input.LeftBumper: + freeze_pos = center_pos.copy() + else: # if left bumper is not pressed, move freeze pos in direction of right joystick + freeze_pos += np.array([right_x, right_y]) * 1.0 + + # if right bumper is not pressed, move freeze pos in direction of right joystick (relative to center pos) + if not joy_input.RightBumper: + freeze_pos += move * 1.0 + np.array([right_x, right_y]) * 1.0 + + # update center position + center_pos += move + + # update module directions + module_dirs += rotate + + ## LOGIC (end) + + # update module positions using module directions and center position + module_pos = np.array( + [ + [R * np.cos(a) + center_pos[0], R * np.sin(a) + center_pos[1]] + for a in module_dirs + ] + ) + + # set box size and aspect ratio for matplotlib plot window + box_scale = 10 + plt.xlim(-box_scale * R, box_scale * R) + plt.ylim(-box_scale * R, box_scale * R) + plt.gca().set_aspect("equal", adjustable="box") + + # array to store module controls (direction & speed of each module) + module_controls = [] + + # plot robot + for i, module in enumerate(module_pos): + # plot line from center to module + plt.plot( + [center_pos[0], module[0]], [center_pos[1], module[1]], "black" + ) + + # calculate module direction vector using robot movement vector & rotation + dir_vec = ( + move + + np.array([-np.sin(module_dirs[i]), np.cos(module_dirs[i])]) + * rotate + * 10 + ) + + # add module direction vector to module_controls as degrees & speed + module_controls.append( + ( + round(np.rad2deg(np.arctan2(dir_vec[1], dir_vec[0])), 3), + round(np.hypot(dir_vec[0], dir_vec[1]), 3), + ) + ) + + # plot module direction vectors + plt.quiver( + module[0], + module[1], + dir_vec[0], + dir_vec[1], + color="red", + angles="xy", + scale_units="xy", + scale=0.5, + ) + + print(module_controls) + + # plot center direction vector + plt.quiver( + center_pos[0], + center_pos[1], + move[0], + move[1], + color="green", + angles="xy", + scale_units="xy", + scale=0.5, + ) + + # plot line from center to freeze pos + plt.plot( + [center_pos[0], freeze_pos[0]], [center_pos[1], freeze_pos[1]], "b" + ) + + # rumble if robot is outside of box + if rumble and ( + abs(center_pos[0]) > box_scale * R or abs(center_pos[1]) > box_scale * R + ): + joy.controller.send_rumble(False, True, 1) + + # pause for DT seconds and clear plot + plt.pause(DT) + plt.clf() + + except Exception as e: + print(e) diff --git a/dev/swerve_sim_joystick/procon.py b/dev/swerve_sim_joystick/procon.py new file mode 100644 index 0000000..8e04765 --- /dev/null +++ b/dev/swerve_sim_joystick/procon.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 + +import math +import time + +import hid # pip install hidapi + + +def to_int16(uint16): + return -((uint16 ^ 0xFFFF) + 1) if uint16 & 0x8000 else uint16 + + +class ProCon: + VENDOR_ID = 0x057E + PRODUCT_ID = 0x2009 + PACKET_SIZE = 64 + CALIBRATION_OFFSET = 0x603D + CALIBRATION_LENGTH = 0x12 + COMMAND_RETRIES = 10 + RUMBLE_NEUTRAL = (0x00, 0x01, 0x40, 0x40) + RUMBLE = (0x74, 0xBE, 0xBD, 0x6F) + DEFAULT_IMU_SENSITIVITY = (0x03, 0x00, 0x00, 0x01) + + class OutputReportID: + RUMBLE_SUBCOMMAND = 0x01 + RUMBLE = 0x10 + COMMAND = 0x80 + + class InputReportID: + SUBCOMMAND_REPLY = 0x21 + CONTROLLER_STATE = 0x30 + COMMAND_ACK = 0x81 + + class CommandID: + HANDSHAKE = 0x02 + HIGH_SPEED = 0x03 + FORCE_USB = 0x04 + + class SubcommandID: + SET_INPUT_REPORT_MODE = 0x03 + SPI_FLASH_READ = 0x10 + SET_PLAYER_LIGHTS = 0x30 + SET_HOME_LIGHT = 0x38 + ENABLE_IMU = 0x40 + SET_IMU_SENSITIVITY = 0x41 + ENABLE_VIBRATION = 0x48 + + class Button: + A = "A" + B = "B" + X = "X" + Y = "Y" + UP = "Up" + DOWN = "Down" + LEFT = "Left" + RIGHT = "Right" + MINUS = "-" + PLUS = "+" + SCREENSHOT = "Screenshot" + HOME = "Home" + L = "L" + ZL = "ZL" + R = "R" + ZR = "ZR" + LS = "LS" + RS = "RS" + + def __init__(self): + self.subcommand_counter = 0 + self.dev = hid.device() + self.dev.open(ProCon.VENDOR_ID, ProCon.PRODUCT_ID) + self.handshake() + self.high_speed() + self.handshake() + self.rumble_low = self.rumble_high = ProCon.RUMBLE_NEUTRAL + self.rumble_expire = 0 + self.load_stick_calibration() + self.enable_vibration(True) + self.set_input_report_mode(ProCon.InputReportID.CONTROLLER_STATE) + self.force_usb() + self.set_player_lights(True, False, False, False) + self.enable_imu(True) + self.set_imu_sensitivity(ProCon.DEFAULT_IMU_SENSITIVITY) + + def start(self, callback): + while True: + state = self.recv() + if state[0] != ProCon.InputReportID.CONTROLLER_STATE: + continue + buttons = { + ProCon.Button.A: state[3] & 0x08 > 0, + ProCon.Button.B: state[3] & 0x04 > 0, + ProCon.Button.X: state[3] & 0x02 > 0, + ProCon.Button.Y: state[3] & 0x01 > 0, + ProCon.Button.UP: state[5] & 0x02 > 0, + ProCon.Button.DOWN: state[5] & 0x01 > 0, + ProCon.Button.LEFT: state[5] & 0x08 > 0, + ProCon.Button.RIGHT: state[5] & 0x04 > 0, + ProCon.Button.MINUS: state[4] & 0x01 > 0, + ProCon.Button.PLUS: state[4] & 0x02 > 0, + ProCon.Button.SCREENSHOT: state[4] & 0x20 > 0, + ProCon.Button.HOME: state[4] & 0x10 > 0, + ProCon.Button.L: state[5] & 0x40 > 0, + ProCon.Button.ZL: state[5] & 0x80 > 0, + ProCon.Button.R: state[3] & 0x40 > 0, + ProCon.Button.ZR: state[3] & 0x80 > 0, + ProCon.Button.LS: state[4] & 0x08 > 0, + ProCon.Button.RS: state[4] & 0x04 > 0, + } + l_x = state[6] | ((state[7] & 0xF) << 8) + l_y = (state[7] >> 4) | (state[8] << 4) + r_x = state[9] | ((state[10] & 0xF) << 8) + r_y = (state[10] >> 4) | (state[11] << 4) + l_x = self.apply_stick_calibration(l_x, 0, 0) + l_y = self.apply_stick_calibration(l_y, 0, 1) + r_x = self.apply_stick_calibration(r_x, 1, 0) + r_y = self.apply_stick_calibration(r_y, 1, 1) + l_stick = (l_x, l_y) + r_stick = (r_x, r_y) + accel = ( + state[13] | state[14] << 8, + state[15] | state[16] << 8, + state[17] | state[18] << 8, + ) + gyro = ( + state[19] | state[20] << 8, + state[21] | state[22] << 8, + state[23] | state[24] << 8, + ) + accel = tuple(map(to_int16, accel)) + gyro = tuple(map(to_int16, gyro)) + battery = (state[2] & 0xF0) >> 4 + callback(buttons, l_stick, r_stick, accel, gyro, battery) + if self.rumble_expire and int(time.time() * 1000) >= self.rumble_expire: + self.send_rumble(False, False, 0) + + def load_stick_calibration(self): + ok, reply = self.spi_flash_read( + ProCon.CALIBRATION_OFFSET, ProCon.CALIBRATION_LENGTH + ) + if not ok: + raise RuntimeError("cannot load stick calibration") + self.stick_calibration = [ + [ + [ + ((reply[27] & 0xF) << 8) | reply[26], + ((reply[24] & 0xF) << 8) | reply[23], + ((reply[21] & 0xF) << 8) | reply[20], + ], + [ + (reply[28] << 4) | (reply[27] >> 4), + (reply[25] << 4) | (reply[24] >> 4), + (reply[22] << 4) | (reply[21] >> 4), + ], + ], + [ + [ + ((reply[33] & 0xF) << 8) | reply[32], + ((reply[30] & 0xF) << 8) | reply[29], + ((reply[36] & 0xF) << 8) | reply[35], + ], + [ + (reply[34] << 4) | (reply[33] >> 4), + (reply[31] << 4) | (reply[30] >> 4), + (reply[37] << 4) | (reply[36] >> 4), + ], + ], + ] + for i in range(len(self.stick_calibration)): + for j in range(len(self.stick_calibration[i])): + for k in range(len(self.stick_calibration[i][j])): + if self.stick_calibration[i][j][k] == 0xFFF: + self.stick_calibration[i][j][k] = 0 + self.stick_extends = [ + [ + [ + -int(self.stick_calibration[0][0][0] * 0.7), + int(self.stick_calibration[0][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[0][1][0] * 0.7), + int(self.stick_calibration[0][1][2] * 0.7), + ], + ], + [ + [ + -int(self.stick_calibration[1][0][0] * 0.7), + int(self.stick_calibration[1][0][2] * 0.7), + ], + [ + -int(self.stick_calibration[1][1][0] * 0.7), + int(self.stick_calibration[1][1][2] * 0.7), + ], + ], + ] + + def apply_stick_calibration(self, value, stick, axis): + value -= self.stick_calibration[stick][axis][1] + if value < self.stick_extends[stick][axis][0]: + self.stick_extends[stick][axis][0] = value + if value > self.stick_extends[stick][axis][1]: + self.stick_extends[stick][axis][1] = value + if value > 0: + return int(value * 0x7FFF / self.stick_extends[stick][axis][1]) + return int(value * -0x7FFF / self.stick_extends[stick][axis][0]) + + def send(self, data): + return self.dev.write(data) == len(data) + + def recv(self): + return self.dev.read(ProCon.PACKET_SIZE) + + def send_command(self, id, wait_for_reply=True): + data = (ProCon.OutputReportID.COMMAND, id) + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True + reply = self.recv() + if reply[0] == ProCon.InputReportID.COMMAND_ACK and reply[1] == id: + return True + return False + + def send_subcommand(self, id, param, wait_for_reply=True): + data = ( + (ProCon.OutputReportID.RUMBLE_SUBCOMMAND, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + + (id,) + + param + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if not self.send(data): + continue + if not wait_for_reply: + return True, [] + reply = self.recv() + if reply[0] == ProCon.InputReportID.SUBCOMMAND_REPLY and reply[14] == id: + return True, reply + return False, [] + + def send_rumble(self, low, high, duration): + self.rumble_low = ProCon.RUMBLE if low else ProCon.RUMBLE_NEUTRAL + self.rumble_high = ProCon.RUMBLE if high else ProCon.RUMBLE_NEUTRAL + self.rumble_expire = ( + int(time.time() * 1000) + duration if (low or high) and duration else 0 + ) + data = ( + (ProCon.OutputReportID.RUMBLE, self.subcommand_counter) + + self.rumble_low + + self.rumble_high + ) + self.subcommand_counter = (self.subcommand_counter + 1) & 0xFF + for _ in range(ProCon.COMMAND_RETRIES): + if self.send(data): + return True + return False + + def handshake(self): + return self.send_command(ProCon.CommandID.HANDSHAKE) + + def high_speed(self): + return self.send_command(ProCon.CommandID.HIGH_SPEED) + + def force_usb(self): + return self.send_command(ProCon.CommandID.FORCE_USB, False) + + def set_input_report_mode(self, mode): + return self.send_subcommand(ProCon.SubcommandID.SET_INPUT_REPORT_MODE, (mode,)) + + def spi_flash_read(self, addr, l): + param = ( + addr & 0x000000FF, + (addr & 0x0000FF00) >> 8, + (addr & 0x00FF0000) >> 16, + (addr & 0xFF000000) >> 24, + l, + ) + return self.send_subcommand(ProCon.SubcommandID.SPI_FLASH_READ, param) + + def set_player_lights(self, one, two, three, four): + param = (one << 0) | (two << 1) | (three << 2) | (four << 3) + return self.send_subcommand(ProCon.SubcommandID.SET_PLAYER_LIGHTS, (param,)) + + def set_home_light(self, brightness): + intensity = 0 + if brightness > 0: + if brightness < 65: + intensity = (brightness + 5) // 10 + else: + intensity = math.ceil(0xF * ((brightness / 100) ** 2.13)) + intensity = (intensity & 0xF) << 4 + param = (0x01, intensity, intensity, 0x00) + return self.send_subcommand(ProCon.SubcommandID.SET_HOME_LIGHT, param) + + def enable_imu(self, enable): + return self.send_subcommand(ProCon.SubcommandID.ENABLE_IMU, (int(enable),)) + + def set_imu_sensitivity(self, sensitivity): + return self.send_subcommand( + ProCon.SubcommandID.SET_IMU_SENSITIVITY, sensitivity + ) + + def enable_vibration(self, enable): + return self.send_subcommand( + ProCon.SubcommandID.ENABLE_VIBRATION, (int(enable),) + ) + + +def print_state(buttons, l_stick, r_stick, accel, gyro, battery): + print("\33[2JButtons:") + for k, v in buttons.items(): + if v: + print("[{}]".format(k), end=" ") + else: + print(" {} ".format(k), end=" ") + print() + print("L Stick: ({:6}, {:6})".format(l_stick[0], l_stick[1])) + print("R Stick: ({:6}, {:6})".format(r_stick[0], r_stick[1])) + print("Accelerometer: ({:6}, {:6}, {:6})".format(accel[0], accel[1], accel[2])) + print("Gyroscope: ({:6}, {:6}, {:6})".format(gyro[0], gyro[1], gyro[2])) + print("Battery: {}/9".format(battery)) + + +if __name__ == "__main__": + try: + ProCon().start(print_state) + except KeyboardInterrupt: + print("\rGoodbye!") diff --git a/dev/swerve_sim_keyboard/README.md b/dev/swerve_sim_keyboard/README.md new file mode 100644 index 0000000..86c5360 --- /dev/null +++ b/dev/swerve_sim_keyboard/README.md @@ -0,0 +1,22 @@ +### ModBot - Swerve Algorithm +## Environment setup +For setting up any python project and to avoid "It works on my machine"! conversations.. + +The following commands are for Debian based systems, but should work apporpriatley with WSL (Windows), OSX, and other Linux distros. + +Step 0: Make sure repo is cloned or updated + +Step 1: ```sudo -s``` to enter root user. + +Step 2: Install poetry ```curl -sSL https://install.python-poetry.org | python3 -``` + +Step 3: Add to path ```export PATH="/root/.local/bin:$PATH"``` + +Step 3.5: Check it installed correctly ```poetry --version``` + +Step 4: Navigate to the same directory as *pyproject.toml* and install the local enviornment ```poetry install``` + +Step 5: Run the sim ```poetry run python3 src/main.py``` + +Documentation on poetry, its very popular in industry and in the open source scene: https://python-poetry.org/docs/ + diff --git a/dev/swerve_sim_keyboard/poetry.lock b/dev/swerve_sim_keyboard/poetry.lock new file mode 100644 index 0000000..a80ed45 --- /dev/null +++ b/dev/swerve_sim_keyboard/poetry.lock @@ -0,0 +1,3302 @@ +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. + +[[package]] +name = "contourpy" +version = "1.2.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, + {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, + {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, + {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, + {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, + {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, + {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, + {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, + {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, + {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, +] + +[package.dependencies] +numpy = ">=1.20,<2.0" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "fonttools" +version = "4.44.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1cd1c6bb097e774d68402499ff66185190baaa2629ae2f18515a2c50b93db0c"}, + {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9eab7f9837fdaa2a10a524fbcc2ec24bf60637c044b6e4a59c3f835b90f0fae"}, + {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f412954275e594f7a51c16f3b3edd850acb0d842fefc33856b63a17e18499a5"}, + {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d25893885e80a5955186791eed5579f1e75921751539cc1dc3ffd1160b48cf"}, + {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:22ea8aa7b3712450b42b044702bd3a64fd118006bad09a6f94bd1b227088492e"}, + {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df40daa6c03b98652ffe8110ae014fe695437f6e1cb5a07e16ea37f40e73ac86"}, + {file = "fonttools-4.44.0-cp310-cp310-win32.whl", hash = "sha256:bca49da868e8bde569ef36f0cc1b6de21d56bf9c3be185c503b629c19a185287"}, + {file = "fonttools-4.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:dbac86d83d96099890e731cc2af97976ff2c98f4ba432fccde657c5653a32f1c"}, + {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ff7d19a6804bfd561cfcec9b4200dd1788e28f7de4be70189801530c47c1b3"}, + {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8a1fa9a718de0bc026979c93e1e9b55c5efde60d76f91561fd713387573817d"}, + {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05064f95aacdfc06f21e55096c964b2228d942b8675fa26995a2551f6329d2d"}, + {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b38528f25bc662401e6ffae14b3eb7f1e820892fd80369a37155e3b636a2f4"}, + {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05d7c4d2c95b9490e669f3cb83918799bf1c838619ac6d3bad9ea017cfc63f2e"}, + {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6999e80a125b0cd8e068d0210b63323f17338038c2ecd2e11b9209ec430fe7f2"}, + {file = "fonttools-4.44.0-cp311-cp311-win32.whl", hash = "sha256:a7aec7f5d14dfcd71fb3ebc299b3f000c21fdc4043079101777ed2042ba5b7c5"}, + {file = "fonttools-4.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:518a945dbfe337744bfff31423c1430303b8813c5275dffb0f2577f0734a1189"}, + {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:59b6ad83cce067d10f4790c037a5904424f45bebb5e7be2eb2db90402f288267"}, + {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c2de1fb18198acd400c45ffe2aef5420c8d55fde903e91cba705596099550f3b"}, + {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f308b7a8d28208d54315d11d35f9888d6d607673dd4d42d60b463682ee0400"}, + {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66bc6efd829382f7a7e6cf33c2fb32b13edc8a239eb15f32acbf197dce7a0165"}, + {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a8b99713d3a0d0e876b6aecfaada5e7dc9fe979fcd90ef9fa0ba1d9b9aed03f2"}, + {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b63da598d9cbc52e2381f922da0e94d60c0429f92207bd3fb04d112fc82ea7cb"}, + {file = "fonttools-4.44.0-cp312-cp312-win32.whl", hash = "sha256:f611c97678604e302b725f71626edea113a5745a7fb557c958b39edb6add87d5"}, + {file = "fonttools-4.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:58af428746fa73a2edcbf26aff33ac4ef3c11c8d75bb200eaea2f7e888d2de4e"}, + {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ee8692e23028564c13d924004495f284df8ac016a19f17a87251210e1f1f928"}, + {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dab3d00d27b1a79ae4d4a240e8ceea8af0ff049fd45f05adb4f860d93744110d"}, + {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53526668beccdb3409c6055a4ffe50987a7f05af6436fa55d61f5e7bd450219"}, + {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da036b016c975c2d8c69005bdc4d5d16266f948a7fab950244e0f58301996a"}, + {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b99fe8ef4093f672d00841569d2d05691e50334d79f4d9c15c1265d76d5580d2"}, + {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d16d9634ff1e5cea2cf4a8cbda9026f766e4b5f30b48f8180f0e99133d3abfc"}, + {file = "fonttools-4.44.0-cp38-cp38-win32.whl", hash = "sha256:3d29509f6e05e8d725db59c2d8c076223d793e4e35773040be6632a0349f2f97"}, + {file = "fonttools-4.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4fa4f4bc8fd86579b8cdbe5e948f35d82c0eda0091c399d009b2a5a6b61c040"}, + {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c794de4086f06ae609b71ac944ec7deb09f34ecf73316fddc041087dd24bba39"}, + {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2db63941fee3122e31a21dd0f5b2138ce9906b661a85b63622421d3654a74ae2"}, + {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb01c49c8aa035d5346f46630209923d4927ed15c2493db38d31da9f811eb70d"}, + {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c79af80a835410874683b5779b6c1ec1d5a285e11c45b5193e79dd691eb111"}, + {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6e6aa2d066f8dafd06d8d0799b4944b5d5a1f015dd52ac01bdf2895ebe169a0"}, + {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63a3112f753baef8c6ac2f5f574bb9ac8001b86c8c0c0380039db47a7f512d20"}, + {file = "fonttools-4.44.0-cp39-cp39-win32.whl", hash = "sha256:54efed22b2799a85475e6840e907c402ba49892c614565dc770aa97a53621b2b"}, + {file = "fonttools-4.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e91e19b583961979e2e5a701269d3cfc07418963bee717f8160b0a24332826b"}, + {file = "fonttools-4.44.0-py3-none-any.whl", hash = "sha256:b9beb0fa6ff3ea808ad4a6962d68ac0f140ddab080957b20d9e268e4d67fb335"}, + {file = "fonttools-4.44.0.tar.gz", hash = "sha256:4e90dd81b6e0d97ebfe52c0d12a17a9ef7f305d6bfbb93081265057d6092f252"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "importlib-resources" +version = "6.1.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, + {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] + +[[package]] +name = "keyboard" +version = "0.13.5" +description = "Hook and simulate keyboard events on Windows and Linux" +optional = false +python-versions = "*" +files = [ + {file = "keyboard-0.13.5-py3-none-any.whl", hash = "sha256:8e9c2422f1217e0bd84489b9ecd361027cc78415828f4fe4f88dd4acd587947b"}, + {file = "keyboard-0.13.5.zip", hash = "sha256:63ed83305955939ca5c9a73755e5cc43e8242263f5ad5fd3bb7e0b032f3d308b"}, +] + +[package.dependencies] +pyobjc = {version = "*", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + +[[package]] +name = "matplotlib" +version = "3.8.1" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.8.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e11ab864323fa73ac1b7849688d9671c47a2665242e899785b4db1a375b547e1"}, + {file = "matplotlib-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43a9d40feb63c9e31a0b8b069dcbd74a912f59bdc0095d187126694cd26977e4"}, + {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:608ea2951838d391e45dec2e644888db6899c752d3c29e157af9dcefb3d7d8d5"}, + {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82ec95b02e894561c21e066bd0c716e4b410df141ce9441aa5af6cd937e4ade2"}, + {file = "matplotlib-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e3ad1759ad4a5245172c6d32b8ada603a6020d03211524c39d78d25c9a7dc0d2"}, + {file = "matplotlib-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:20a0fdfd3ee836179047f3782be060057b878ad37f5abe29edf006a1ff3ecd73"}, + {file = "matplotlib-3.8.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7658b7073c1d6a2922ecc0ed41602410fae88586cb8a54f7a2063d537b6beaf7"}, + {file = "matplotlib-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf6889643d4560fcc56f9f0941f078e4df0d72a6c3e4ca548841fc13c5642664"}, + {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff842e27bc6a80de08c40e0bfdce460bd08080e8a94af131162b6a1b8948f2cc"}, + {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f99d07c0e753717775be7be39ab383453b4d8b629c9fa174596b970c6555890"}, + {file = "matplotlib-3.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f34b46dbb1db1f09bfa937cd5853e5f2af232caeeff509c3ab6e43fd33780eae"}, + {file = "matplotlib-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1fcb49b6baf0375281979cbf26695ec10bd1cada1e311893e89533b3b70143e7"}, + {file = "matplotlib-3.8.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e17674ee127f78f26fea237e7f4d5cf910a8be82beb6260fedf358b88075b823"}, + {file = "matplotlib-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d921c0270647ab11c3ef283efaaa3d46fd005ba233bfb3aea75231cdf3656de8"}, + {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2afe7d2f8c9e35e94fbcfcfd9b28f29cb32f0a9068cba469cf907428379c8db9"}, + {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a504ff40f81d6233603475a45497a6dca37a873393fa20ae6f7dd6596ef72b"}, + {file = "matplotlib-3.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cd54bbf089953140905768ed4626d7223e1ad1d7e2a138410a9c4d3b865ccd80"}, + {file = "matplotlib-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:27502d2452208ae784c19504644f09f83742809143bbeae147617640930aa344"}, + {file = "matplotlib-3.8.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f55fb5ff02d999a100be28bf6ffe826e1867a54c7b465409685332c9dd48ffa5"}, + {file = "matplotlib-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:afb72822ae410d62aa1a2920c6563cb5680de9078358f0e9474396c6c3e06be2"}, + {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43cf368a4a1d8cbc426944806e5e183cead746647a64d2cdb786441546235967"}, + {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c54c55457c7f5ea4dfdba0020004fc7667f5c10c8d9b8010d735345acc06c9b8"}, + {file = "matplotlib-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e3bb809b743653b5aab5d72ee45c8c937c28e147b0846b0826a54bece898608c"}, + {file = "matplotlib-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:c1b0ecaa0d1f4fe1e30f625a2347f0034a89a7d17c39efbb502e554d92ee2f61"}, + {file = "matplotlib-3.8.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca84deaa38cb64b7dd160ca2046b45f7b5dbff2b0179642e1339fadc337446c9"}, + {file = "matplotlib-3.8.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed3b29f54f6bbf3eaca4cbd23bc260155153ace63b7f597c474fa6fc6f386530"}, + {file = "matplotlib-3.8.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d24c47a1bb47e392fbcd26fe322e4ff3431653ac1e8718e4e147d450ae97a44"}, + {file = "matplotlib-3.8.1.tar.gz", hash = "sha256:044df81c1f6f3a8e52d70c4cfcb44e77ea9632a10929932870dfaa90de94365d"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.21,<2" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "numpy" +version = "1.26.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = "<3.13,>=3.9" +files = [ + {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, + {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, + {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, + {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, + {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, + {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, + {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, + {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, + {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, + {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, + {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, + {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, + {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, + {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, + {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pillow" +version = "10.1.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, + {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, + {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, + {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, + {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, + {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, + {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "pyobjc" +version = "10.0" +description = "Python<->ObjC Interoperability Module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-10.0-py3-none-any.whl", hash = "sha256:0f30bd3f25425a6c435f37713bc062382c85022d07ddb1f199a9211d859846db"}, + {file = "pyobjc-10.0.tar.gz", hash = "sha256:9987a79e30cdd0de31e58ed41240e2fc4cbf2c085c9fd8988f7de5d0ae06b101"}, +] + +[package.dependencies] +pyobjc-core = "10.0" +pyobjc-framework-Accessibility = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Accounts = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AddressBook = "10.0" +pyobjc-framework-AdServices = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AdSupport = {version = "10.0", markers = "platform_release >= \"18.0\""} +pyobjc-framework-AppleScriptKit = "10.0" +pyobjc-framework-AppleScriptObjC = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-ApplicationServices = "10.0" +pyobjc-framework-AppTrackingTransparency = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AudioVideoBridging = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AuthenticationServices = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-AutomaticAssessmentConfiguration = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Automator = "10.0" +pyobjc-framework-AVFoundation = {version = "10.0", markers = "platform_release >= \"11.0\""} +pyobjc-framework-AVKit = {version = "10.0", markers = "platform_release >= \"13.0\""} +pyobjc-framework-AVRouting = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BackgroundAssets = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BusinessChat = {version = "10.0", markers = "platform_release >= \"18.0\""} +pyobjc-framework-CalendarStore = {version = "10.0", markers = "platform_release >= \"9.0\""} +pyobjc-framework-CallKit = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CFNetwork = "10.0" +pyobjc-framework-Cinematic = {version = "10.0", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ClassKit = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CloudKit = {version = "10.0", markers = "platform_release >= \"14.0\""} +pyobjc-framework-Cocoa = "10.0" +pyobjc-framework-Collaboration = {version = "10.0", markers = "platform_release >= \"9.0\""} +pyobjc-framework-ColorSync = {version = "10.0", markers = "platform_release >= \"17.0\""} +pyobjc-framework-Contacts = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-ContactsUI = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-CoreAudio = "10.0" +pyobjc-framework-CoreAudioKit = "10.0" +pyobjc-framework-CoreBluetooth = {version = "10.0", markers = "platform_release >= \"14.0\""} +pyobjc-framework-CoreData = "10.0" +pyobjc-framework-CoreHaptics = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreLocation = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CoreMedia = {version = "10.0", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMediaIO = {version = "10.0", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMIDI = "10.0" +pyobjc-framework-CoreML = {version = "10.0", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreMotion = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreServices = "10.0" +pyobjc-framework-CoreSpotlight = {version = "10.0", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreText = "10.0" +pyobjc-framework-CoreWLAN = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CryptoTokenKit = {version = "10.0", markers = "platform_release >= \"14.0\""} +pyobjc-framework-DataDetection = {version = "10.0", markers = "platform_release >= \"21.0\""} +pyobjc-framework-DeviceCheck = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-DictionaryServices = {version = "10.0", markers = "platform_release >= \"9.0\""} +pyobjc-framework-DiscRecording = "10.0" +pyobjc-framework-DiscRecordingUI = "10.0" +pyobjc-framework-DiskArbitration = "10.0" +pyobjc-framework-DVDPlayback = "10.0" +pyobjc-framework-EventKit = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-ExceptionHandling = "10.0" +pyobjc-framework-ExecutionPolicy = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ExtensionKit = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ExternalAccessory = {version = "10.0", markers = "platform_release >= \"17.0\""} +pyobjc-framework-FileProvider = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FileProviderUI = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FinderSync = {version = "10.0", markers = "platform_release >= \"14.0\""} +pyobjc-framework-FSEvents = {version = "10.0", markers = "platform_release >= \"9.0\""} +pyobjc-framework-GameCenter = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameController = {version = "10.0", markers = "platform_release >= \"13.0\""} +pyobjc-framework-GameKit = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameplayKit = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-HealthKit = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ImageCaptureCore = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-InputMethodKit = {version = "10.0", markers = "platform_release >= \"9.0\""} +pyobjc-framework-InstallerPlugins = "10.0" +pyobjc-framework-InstantMessage = {version = "10.0", markers = "platform_release >= \"9.0\""} +pyobjc-framework-Intents = {version = "10.0", markers = "platform_release >= \"16.0\""} +pyobjc-framework-IntentsUI = {version = "10.0", markers = "platform_release >= \"21.0\""} +pyobjc-framework-IOBluetooth = "10.0" +pyobjc-framework-IOBluetoothUI = "10.0" +pyobjc-framework-IOSurface = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-iTunesLibrary = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-KernelManagement = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-LatentSemanticMapping = "10.0" +pyobjc-framework-LaunchServices = "10.0" +pyobjc-framework-libdispatch = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-libxpc = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-LinkPresentation = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-LocalAuthentication = {version = "10.0", markers = "platform_release >= \"14.0\""} +pyobjc-framework-LocalAuthenticationEmbeddedUI = {version = "10.0", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MailKit = {version = "10.0", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MapKit = {version = "10.0", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaAccessibility = {version = "10.0", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaLibrary = {version = "10.0", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaPlayer = {version = "10.0", markers = "platform_release >= \"16.0\""} +pyobjc-framework-MediaToolbox = {version = "10.0", markers = "platform_release >= \"13.0\""} +pyobjc-framework-Metal = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalFX = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-MetalKit = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalPerformanceShaders = {version = "10.0", markers = "platform_release >= \"17.0\""} +pyobjc-framework-MetalPerformanceShadersGraph = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-MetricKit = {version = "10.0", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MLCompute = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ModelIO = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MultipeerConnectivity = {version = "10.0", markers = "platform_release >= \"14.0\""} +pyobjc-framework-NaturalLanguage = {version = "10.0", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetFS = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-Network = {version = "10.0", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetworkExtension = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-NotificationCenter = {version = "10.0", markers = "platform_release >= \"14.0\""} +pyobjc-framework-OpenDirectory = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-OSAKit = "10.0" +pyobjc-framework-OSLog = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PassKit = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-PencilKit = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PHASE = {version = "10.0", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Photos = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PhotosUI = {version = "10.0", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PreferencePanes = "10.0" +pyobjc-framework-PubSub = {version = "10.0", markers = "platform_release >= \"9.0\" and platform_release < \"18.0\""} +pyobjc-framework-PushKit = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Quartz = "10.0" +pyobjc-framework-QuickLookThumbnailing = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ReplayKit = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-SafariServices = {version = "10.0", markers = "platform_release >= \"16.0\""} +pyobjc-framework-SafetyKit = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SceneKit = {version = "10.0", markers = "platform_release >= \"11.0\""} +pyobjc-framework-ScreenCaptureKit = {version = "10.0", markers = "platform_release >= \"21.4\""} +pyobjc-framework-ScreenSaver = "10.0" +pyobjc-framework-ScreenTime = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ScriptingBridge = {version = "10.0", markers = "platform_release >= \"9.0\""} +pyobjc-framework-SearchKit = "10.0" +pyobjc-framework-Security = "10.0" +pyobjc-framework-SecurityFoundation = "10.0" +pyobjc-framework-SecurityInterface = "10.0" +pyobjc-framework-SensitiveContentAnalysis = {version = "10.0", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ServiceManagement = {version = "10.0", markers = "platform_release >= \"10.0\""} +pyobjc-framework-SharedWithYou = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SharedWithYouCore = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ShazamKit = {version = "10.0", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Social = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-SoundAnalysis = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Speech = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-SpriteKit = {version = "10.0", markers = "platform_release >= \"13.0\""} +pyobjc-framework-StoreKit = {version = "10.0", markers = "platform_release >= \"11.0\""} +pyobjc-framework-Symbols = {version = "10.0", markers = "platform_release >= \"23.0\""} +pyobjc-framework-SyncServices = "10.0" +pyobjc-framework-SystemConfiguration = "10.0" +pyobjc-framework-SystemExtensions = {version = "10.0", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ThreadNetwork = {version = "10.0", markers = "platform_release >= \"22.0\""} +pyobjc-framework-UniformTypeIdentifiers = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-UserNotifications = {version = "10.0", markers = "platform_release >= \"18.0\""} +pyobjc-framework-UserNotificationsUI = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-VideoSubscriberAccount = {version = "10.0", markers = "platform_release >= \"18.0\""} +pyobjc-framework-VideoToolbox = {version = "10.0", markers = "platform_release >= \"12.0\""} +pyobjc-framework-Virtualization = {version = "10.0", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Vision = {version = "10.0", markers = "platform_release >= \"17.0\""} +pyobjc-framework-WebKit = "10.0" + +[package.extras] +allbindings = ["pyobjc-core (==10.0)", "pyobjc-framework-AVFoundation (==10.0)", "pyobjc-framework-AVKit (==10.0)", "pyobjc-framework-AVRouting (==10.0)", "pyobjc-framework-Accessibility (==10.0)", "pyobjc-framework-Accounts (==10.0)", "pyobjc-framework-AdServices (==10.0)", "pyobjc-framework-AdSupport (==10.0)", "pyobjc-framework-AddressBook (==10.0)", "pyobjc-framework-AppTrackingTransparency (==10.0)", "pyobjc-framework-AppleScriptKit (==10.0)", "pyobjc-framework-AppleScriptObjC (==10.0)", "pyobjc-framework-ApplicationServices (==10.0)", "pyobjc-framework-AudioVideoBridging (==10.0)", "pyobjc-framework-AuthenticationServices (==10.0)", "pyobjc-framework-AutomaticAssessmentConfiguration (==10.0)", "pyobjc-framework-Automator (==10.0)", "pyobjc-framework-BackgroundAssets (==10.0)", "pyobjc-framework-BusinessChat (==10.0)", "pyobjc-framework-CFNetwork (==10.0)", "pyobjc-framework-CalendarStore (==10.0)", "pyobjc-framework-CallKit (==10.0)", "pyobjc-framework-Cinematic (==10.0)", "pyobjc-framework-ClassKit (==10.0)", "pyobjc-framework-CloudKit (==10.0)", "pyobjc-framework-Cocoa (==10.0)", "pyobjc-framework-Collaboration (==10.0)", "pyobjc-framework-ColorSync (==10.0)", "pyobjc-framework-Contacts (==10.0)", "pyobjc-framework-ContactsUI (==10.0)", "pyobjc-framework-CoreAudio (==10.0)", "pyobjc-framework-CoreAudioKit (==10.0)", "pyobjc-framework-CoreBluetooth (==10.0)", "pyobjc-framework-CoreData (==10.0)", "pyobjc-framework-CoreHaptics (==10.0)", "pyobjc-framework-CoreLocation (==10.0)", "pyobjc-framework-CoreMIDI (==10.0)", "pyobjc-framework-CoreML (==10.0)", "pyobjc-framework-CoreMedia (==10.0)", "pyobjc-framework-CoreMediaIO (==10.0)", "pyobjc-framework-CoreMotion (==10.0)", "pyobjc-framework-CoreServices (==10.0)", "pyobjc-framework-CoreSpotlight (==10.0)", "pyobjc-framework-CoreText (==10.0)", "pyobjc-framework-CoreWLAN (==10.0)", "pyobjc-framework-CryptoTokenKit (==10.0)", "pyobjc-framework-DVDPlayback (==10.0)", "pyobjc-framework-DataDetection (==10.0)", "pyobjc-framework-DeviceCheck (==10.0)", "pyobjc-framework-DictionaryServices (==10.0)", "pyobjc-framework-DiscRecording (==10.0)", "pyobjc-framework-DiscRecordingUI (==10.0)", "pyobjc-framework-DiskArbitration (==10.0)", "pyobjc-framework-EventKit (==10.0)", "pyobjc-framework-ExceptionHandling (==10.0)", "pyobjc-framework-ExecutionPolicy (==10.0)", "pyobjc-framework-ExtensionKit (==10.0)", "pyobjc-framework-ExternalAccessory (==10.0)", "pyobjc-framework-FSEvents (==10.0)", "pyobjc-framework-FileProvider (==10.0)", "pyobjc-framework-FileProviderUI (==10.0)", "pyobjc-framework-FinderSync (==10.0)", "pyobjc-framework-GameCenter (==10.0)", "pyobjc-framework-GameController (==10.0)", "pyobjc-framework-GameKit (==10.0)", "pyobjc-framework-GameplayKit (==10.0)", "pyobjc-framework-HealthKit (==10.0)", "pyobjc-framework-IOBluetooth (==10.0)", "pyobjc-framework-IOBluetoothUI (==10.0)", "pyobjc-framework-IOSurface (==10.0)", "pyobjc-framework-ImageCaptureCore (==10.0)", "pyobjc-framework-InputMethodKit (==10.0)", "pyobjc-framework-InstallerPlugins (==10.0)", "pyobjc-framework-InstantMessage (==10.0)", "pyobjc-framework-Intents (==10.0)", "pyobjc-framework-IntentsUI (==10.0)", "pyobjc-framework-KernelManagement (==10.0)", "pyobjc-framework-LatentSemanticMapping (==10.0)", "pyobjc-framework-LaunchServices (==10.0)", "pyobjc-framework-LinkPresentation (==10.0)", "pyobjc-framework-LocalAuthentication (==10.0)", "pyobjc-framework-LocalAuthenticationEmbeddedUI (==10.0)", "pyobjc-framework-MLCompute (==10.0)", "pyobjc-framework-MailKit (==10.0)", "pyobjc-framework-MapKit (==10.0)", "pyobjc-framework-MediaAccessibility (==10.0)", "pyobjc-framework-MediaLibrary (==10.0)", "pyobjc-framework-MediaPlayer (==10.0)", "pyobjc-framework-MediaToolbox (==10.0)", "pyobjc-framework-Metal (==10.0)", "pyobjc-framework-MetalFX (==10.0)", "pyobjc-framework-MetalKit (==10.0)", "pyobjc-framework-MetalPerformanceShaders (==10.0)", "pyobjc-framework-MetalPerformanceShadersGraph (==10.0)", "pyobjc-framework-MetricKit (==10.0)", "pyobjc-framework-ModelIO (==10.0)", "pyobjc-framework-MultipeerConnectivity (==10.0)", "pyobjc-framework-NaturalLanguage (==10.0)", "pyobjc-framework-NetFS (==10.0)", "pyobjc-framework-Network (==10.0)", "pyobjc-framework-NetworkExtension (==10.0)", "pyobjc-framework-NotificationCenter (==10.0)", "pyobjc-framework-OSAKit (==10.0)", "pyobjc-framework-OSLog (==10.0)", "pyobjc-framework-OpenDirectory (==10.0)", "pyobjc-framework-PHASE (==10.0)", "pyobjc-framework-PassKit (==10.0)", "pyobjc-framework-PencilKit (==10.0)", "pyobjc-framework-Photos (==10.0)", "pyobjc-framework-PhotosUI (==10.0)", "pyobjc-framework-PreferencePanes (==10.0)", "pyobjc-framework-PubSub (==10.0)", "pyobjc-framework-PushKit (==10.0)", "pyobjc-framework-Quartz (==10.0)", "pyobjc-framework-QuickLookThumbnailing (==10.0)", "pyobjc-framework-ReplayKit (==10.0)", "pyobjc-framework-SafariServices (==10.0)", "pyobjc-framework-SafetyKit (==10.0)", "pyobjc-framework-SceneKit (==10.0)", "pyobjc-framework-ScreenCaptureKit (==10.0)", "pyobjc-framework-ScreenSaver (==10.0)", "pyobjc-framework-ScreenTime (==10.0)", "pyobjc-framework-ScriptingBridge (==10.0)", "pyobjc-framework-SearchKit (==10.0)", "pyobjc-framework-Security (==10.0)", "pyobjc-framework-SecurityFoundation (==10.0)", "pyobjc-framework-SecurityInterface (==10.0)", "pyobjc-framework-SensitiveContentAnalysis (==10.0)", "pyobjc-framework-ServiceManagement (==10.0)", "pyobjc-framework-SharedWithYou (==10.0)", "pyobjc-framework-SharedWithYouCore (==10.0)", "pyobjc-framework-ShazamKit (==10.0)", "pyobjc-framework-Social (==10.0)", "pyobjc-framework-SoundAnalysis (==10.0)", "pyobjc-framework-Speech (==10.0)", "pyobjc-framework-SpriteKit (==10.0)", "pyobjc-framework-StoreKit (==10.0)", "pyobjc-framework-Symbols (==10.0)", "pyobjc-framework-SyncServices (==10.0)", "pyobjc-framework-SystemConfiguration (==10.0)", "pyobjc-framework-SystemExtensions (==10.0)", "pyobjc-framework-ThreadNetwork (==10.0)", "pyobjc-framework-UniformTypeIdentifiers (==10.0)", "pyobjc-framework-UserNotifications (==10.0)", "pyobjc-framework-UserNotificationsUI (==10.0)", "pyobjc-framework-VideoSubscriberAccount (==10.0)", "pyobjc-framework-VideoToolbox (==10.0)", "pyobjc-framework-Virtualization (==10.0)", "pyobjc-framework-Vision (==10.0)", "pyobjc-framework-WebKit (==10.0)", "pyobjc-framework-iTunesLibrary (==10.0)", "pyobjc-framework-libdispatch (==10.0)", "pyobjc-framework-libxpc (==10.0)"] + +[[package]] +name = "pyobjc-core" +version = "10.0" +description = "Python<->ObjC Interoperability Module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-core-10.0.tar.gz", hash = "sha256:3dd0a7b3acd7e0b8ffd3f5331b29a3aaebe79a03323e61efeece38627a6020b3"}, + {file = "pyobjc_core-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61ea5112a672d21b5b0ed945778707c655b17c400672aef144705674c4b95499"}, + {file = "pyobjc_core-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:99b72cda4593e0c66037b25a178f2bcc6efffb6d5d9dcd477ecca859a1f9ae8e"}, + {file = "pyobjc_core-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2843ca32e86a01ccee67d7ad82a325ddd72d754929d1f2c0d96bc8741dc9af09"}, + {file = "pyobjc_core-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a886b9d2a93210cab4ae72601ab005ca6f627fa2f0cc62c43c03ef1405067a11"}, + {file = "pyobjc_core-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:166666b5c380a49e8aa1ad1dda978c581e29a00703d82203216f3c65a3f397a4"}, + {file = "pyobjc_core-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:198a0360f64e4c0148eed07b42d1de0545f56c498c356d1d5524422bb3352907"}, +] + +[[package]] +name = "pyobjc-framework-accessibility" +version = "10.0" +description = "Wrappers for the framework Accessibility on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Accessibility-10.0.tar.gz", hash = "sha256:5aa152201ccc235a6bbba271b698de42445a4a058b8dceca982d70384c195255"}, + {file = "pyobjc_framework_Accessibility-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e23bcd2e43ab8bc800255ee2b09c1af924b2d6b4602a4ec94719a08d181fdf62"}, + {file = "pyobjc_framework_Accessibility-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:911295caa0ad264787580e2aa8680d3c7c1957e22e9db3ccdc0d9a95a27f3333"}, + {file = "pyobjc_framework_Accessibility-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c2f95c08ae2d7d0c8745d81a68d74aba00a3de026a7831fb67561f85a5886c0b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-accounts" +version = "10.0" +description = "Wrappers for the framework Accounts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Accounts-10.0.tar.gz", hash = "sha256:5679caa87b3bd597f776e154c43dbeb460251798165d55daf79f105a131fdf2c"}, + {file = "pyobjc_framework_Accounts-10.0-py2.py3-none-any.whl", hash = "sha256:72c67d4b1f174d2045558d7b1348d5dce642ea0907ab3dfb79d2f449e601ad42"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-addressbook" +version = "10.0" +description = "Wrappers for the framework AddressBook on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AddressBook-10.0.tar.gz", hash = "sha256:e61dbd593113721ff45bbc706884727dc483502eb4d514fd4c53f56b9a86bef7"}, + {file = "pyobjc_framework_AddressBook-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:71a0667a4d89bf9a50e2fda57705d296ab04ae24063ee67f377226fe6693699d"}, + {file = "pyobjc_framework_AddressBook-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bce60351f7e8db831b9044702e368eee09daacb7bcc51d036f3b95a13b634316"}, + {file = "pyobjc_framework_AddressBook-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:75c7d8dc47dc1f3c01e9bab9af33fb012950b9b0926d8fd0cf3774c6c3a2a2ca"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-adservices" +version = "10.0" +description = "Wrappers for the framework AdServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AdServices-10.0.tar.gz", hash = "sha256:a49b6f57e0bebef2e5484deebd68828106ec3f2e70683f75cb3414a080689983"}, + {file = "pyobjc_framework_AdServices-10.0-py2.py3-none-any.whl", hash = "sha256:d3c9bb0c7da4c98879368ad22c6e558fff73c6d3d971ee5ac8bdd38f723f811b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-adsupport" +version = "10.0" +description = "Wrappers for the framework AdSupport on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AdSupport-10.0.tar.gz", hash = "sha256:d208bcbd4462b2ffa686e9137aa1a028fa2c1cd5dda1d31e409b7e935e843565"}, + {file = "pyobjc_framework_AdSupport-10.0-py2.py3-none-any.whl", hash = "sha256:9fe3295892d2906f46ee36c982cf1b41a94dc9c5a316937174966512d61a7939"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-applescriptkit" +version = "10.0" +description = "Wrappers for the framework AppleScriptKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppleScriptKit-10.0.tar.gz", hash = "sha256:8d6192e4f8a9ca89b2b32e9f6f18e0cb9dd4c026d112d28b18235b1c9f8016fb"}, + {file = "pyobjc_framework_AppleScriptKit-10.0-py2.py3-none-any.whl", hash = "sha256:567123701b86833118236f0177ce7979cd91be5c2d0fe26afb7b73499812f673"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-applescriptobjc" +version = "10.0" +description = "Wrappers for the framework AppleScriptObjC on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppleScriptObjC-10.0.tar.gz", hash = "sha256:3adb92fe129543c5b763e95788b8b408292ce6ff83436fec4182bcbda6493c28"}, + {file = "pyobjc_framework_AppleScriptObjC-10.0-py2.py3-none-any.whl", hash = "sha256:ef800eae5cd54a41f88c8bf83fcc7ab3c902ecaf104e469a6f1ead15da517479"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-applicationservices" +version = "10.0" +description = "Wrappers for the framework ApplicationServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ApplicationServices-10.0.tar.gz", hash = "sha256:8a667da95c09202def58746c42d5093f90be5762a52e6f5ad8beb334b51dca20"}, + {file = "pyobjc_framework_ApplicationServices-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b1688abb30cfd6607ae70dd07d3118a5f2d3593916342ffb842a208e7ff52f2b"}, + {file = "pyobjc_framework_ApplicationServices-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ebbe656c8b1a8fb3880d47ebef5cb7c95b9a646c84bc369cf132347dc4754143"}, + {file = "pyobjc_framework_ApplicationServices-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9cf969781616261ec7a2c427120a9fb4ac861e62bc2854de6cabdd2e4ea47141"}, + {file = "pyobjc_framework_ApplicationServices-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:49c4b3e6399572552ba8167bff16ef6cd2eeba6e417547a67d26316acb80f612"}, + {file = "pyobjc_framework_ApplicationServices-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b26b16077fbd3e32f4216b05798a9fe79b4c00c498581adf15c4f47db13ed270"}, + {file = "pyobjc_framework_ApplicationServices-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b20d97f6aa63c4935363f4d9bad736d92b67f46ccd9c8c8c84d08e03b2583691"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-apptrackingtransparency" +version = "10.0" +description = "Wrappers for the framework AppTrackingTransparency on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppTrackingTransparency-10.0.tar.gz", hash = "sha256:846b148300b0c588cfdfc016cf2ace3f77ee4470ca8112907fb2ef00b1271660"}, + {file = "pyobjc_framework_AppTrackingTransparency-10.0-py2.py3-none-any.whl", hash = "sha256:20d1c8516c2ac568b90f3daf7d93b91a37ea61aa874b4a541d276c7fdac623e4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-audiovideobridging" +version = "10.0" +description = "Wrappers for the framework AudioVideoBridging on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AudioVideoBridging-10.0.tar.gz", hash = "sha256:ca0b947dca87a831328aa8da16232f98699d7a144ed4d088d6b5a388552d85fb"}, + {file = "pyobjc_framework_AudioVideoBridging-10.0-py2.py3-none-any.whl", hash = "sha256:979081558ec3a8cd875515120027448fbe24fa0605b96cf13c7541bffab281bc"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-authenticationservices" +version = "10.0" +description = "Wrappers for the framework AuthenticationServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AuthenticationServices-10.0.tar.gz", hash = "sha256:0ee315ccae58e9821d92052ac937f26d4a033b1fbbda1e213b1752b10653ba5b"}, + {file = "pyobjc_framework_AuthenticationServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b9aafe7b5e6f46ebb1e52f94d562bc4c137ff2cbbcebf7aebce7a0d0e4a2431f"}, + {file = "pyobjc_framework_AuthenticationServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e20a06e250547c3bf29ea209e38f59eb471b3081a160dcb2ef26f05c8ff46412"}, + {file = "pyobjc_framework_AuthenticationServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:019ebeeb191938bc34058cec587b137aee6b7cfcfaa01e23a4073fa633a04cdc"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-automaticassessmentconfiguration" +version = "10.0" +description = "Wrappers for the framework AutomaticAssessmentConfiguration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AutomaticAssessmentConfiguration-10.0.tar.gz", hash = "sha256:008599dc2b2af1175e574ebce2be950c5bb67a2c9eb7391535dac4f514e158a2"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:46dcc05d21ebd6253c266acd40b2392725823455ea730b3dcb62a42764f28bec"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:46a318317123e7a2915bf04f93a11ab11f97f21706aff2152cdcdcc1835252f4"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:520eeb727edd2f9f91dd261b31ed3de2baafb40da59f2f7120b3f39166cbafbf"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-automator" +version = "10.0" +description = "Wrappers for the framework Automator on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Automator-10.0.tar.gz", hash = "sha256:c6d8591650e17a1d9d92f62fd83848d3afbf70b08dfc12a205fb78684ab4b9ac"}, + {file = "pyobjc_framework_Automator-10.0-py2.py3-none-any.whl", hash = "sha256:261e36071f1a662f387bab48f711059e6e468ddd5054c0f2bae7af7e619a7aba"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-avfoundation" +version = "10.0" +description = "Wrappers for the framework AVFoundation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVFoundation-10.0.tar.gz", hash = "sha256:40366a8c6bb964e7b7263e8cf060350f69ad365e6a5356d6ccab9f256a9987f7"}, + {file = "pyobjc_framework_AVFoundation-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b9b2e6731a64425f297bed68c6fc6e31e20965277c96012e62f7fa9059ff544e"}, + {file = "pyobjc_framework_AVFoundation-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:917185ff4e3f262b98cca2789ed68d43b0b111b161b9c8bda0bc7e6ab6def41c"}, + {file = "pyobjc_framework_AVFoundation-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d2bf8c4cfe72a24a4632d4152522c6b1b9b69b1bfadc7d76fd1082e7cc3cec7e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreAudio = ">=10.0" +pyobjc-framework-CoreMedia = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-avkit" +version = "10.0" +description = "Wrappers for the framework AVKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVKit-10.0.tar.gz", hash = "sha256:53f8b74a76c948c0d9a96f331d99e1a6ab7a1ce87af7d9bbfffd267532bea98c"}, + {file = "pyobjc_framework_AVKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:96e153fc74339c5634a8f49614b8039028995cde08b3dd8b024a46ebe4cb4286"}, + {file = "pyobjc_framework_AVKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:44df99e60a3ab5359d92ccc840c0abbed2b8072d27a483fac46ec73800e128bd"}, + {file = "pyobjc_framework_AVKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5bc830421f74c1b78aa85605d2e72c5aa18b6d74b24b82824a1b6e519b66cc64"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-avrouting" +version = "10.0" +description = "Wrappers for the framework AVRouting on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVRouting-10.0.tar.gz", hash = "sha256:41213eb9fdff4ec58dddee240de7100601cef74e458265623763b460a422438c"}, + {file = "pyobjc_framework_AVRouting-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cc38ebfc114467ec14b39e774c93d4f1e4345bc4a723ba555a42bacbb5e45dd3"}, + {file = "pyobjc_framework_AVRouting-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0cd4531d5edd0c6be1ba53db037f04c32ad91caf9572d03666608b8ab93c07ae"}, + {file = "pyobjc_framework_AVRouting-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b9089d3e2b53c10f131d4f23f14ccd2c89dff59b47666ba5383616a2a6026fa2"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-backgroundassets" +version = "10.0" +description = "Wrappers for the framework BackgroundAssets on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-BackgroundAssets-10.0.tar.gz", hash = "sha256:d2b9a7a46a632d0adeaa6ba411ddb829d8efa3273a93d7918cc143dfe9dfb54b"}, + {file = "pyobjc_framework_BackgroundAssets-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9b9c236a3ccaf1e68bfaade6b5c6d843f628bc4d3636b093be78cd7bb7d9c9f6"}, + {file = "pyobjc_framework_BackgroundAssets-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:93736d6fb6bda52cfddf4006ffbdcf6bfe414826c04901d4c5b644cc380ade44"}, + {file = "pyobjc_framework_BackgroundAssets-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a248a110092983f0b19d9c5b21d7027987954ae4d58775411d540139e6972a69"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-businesschat" +version = "10.0" +description = "Wrappers for the framework BusinessChat on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-BusinessChat-10.0.tar.gz", hash = "sha256:fb929e4ab2b8fe618ac8038a12745d32972e0f6d1dd7c3eb41395542e0200207"}, + {file = "pyobjc_framework_BusinessChat-10.0-py2.py3-none-any.whl", hash = "sha256:2eb35f6f3585302c32cab1af13501b13f97badd13c0ed885c4ecd66ed24add15"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-calendarstore" +version = "10.0" +description = "Wrappers for the framework CalendarStore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CalendarStore-10.0.tar.gz", hash = "sha256:bf73fc69e306456a36417b97a56398013637ae551cdfae1ba53e2a86935afa52"}, + {file = "pyobjc_framework_CalendarStore-10.0-py2.py3-none-any.whl", hash = "sha256:1e0da82b7d1a0d1f34991795d1f7dc8e186f79faf9a4b0ef5fe1a74112ac70a4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-callkit" +version = "10.0" +description = "Wrappers for the framework CallKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CallKit-10.0.tar.gz", hash = "sha256:640bc3175b494f6cd0e2a56a453025d2d7d18c97af18800c24dcd8e257209101"}, + {file = "pyobjc_framework_CallKit-10.0-py2.py3-none-any.whl", hash = "sha256:3c431115a3d3c826268a9c4272c0b261e5a15206e9468915a859cde52f32d190"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-cfnetwork" +version = "10.0" +description = "Wrappers for the framework CFNetwork on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CFNetwork-10.0.tar.gz", hash = "sha256:18118d62e05e37692e3cfa5b1ab8c0b82079aad72240bcb6d626740aa4405480"}, + {file = "pyobjc_framework_CFNetwork-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8d9d3badde8adac25c2fef5bf768792a0ee1cbaff9b5d9f416a8a77b0729f2d7"}, + {file = "pyobjc_framework_CFNetwork-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc16d60fdba0aa41ea10353ca4108c464c18eb6caadb457f845bdd32de6a236e"}, + {file = "pyobjc_framework_CFNetwork-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b935f6f43fa42d9f2617e9858e243e0381338aaa4c2a47c59efbefd310d6faa6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-cinematic" +version = "10.0" +description = "Wrappers for the framework Cinematic on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Cinematic-10.0.tar.gz", hash = "sha256:5cc96e10e76dc617f11a327ea351078a44b1a4c918187626d8d7e9e9c3d7bcd7"}, + {file = "pyobjc_framework_Cinematic-10.0-py2.py3-none-any.whl", hash = "sha256:667197227d10add7869dbcfd8396faa251682ff62a702c125ddaf7566469c25b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-AVFoundation = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreMedia = ">=10.0" +pyobjc-framework-Metal = ">=10.0" + +[[package]] +name = "pyobjc-framework-classkit" +version = "10.0" +description = "Wrappers for the framework ClassKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ClassKit-10.0.tar.gz", hash = "sha256:6f0c6dbba20945f515b5a3540c88d91e9c00c1af854c889fb56491179dc064be"}, + {file = "pyobjc_framework_ClassKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:586e5a5d26d4444614cde1be7acc51483ed806057c87c035c3a22bcf6d910d37"}, + {file = "pyobjc_framework_ClassKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea01948a1711a680fb2209adbc72931759b3d96621b10d207d3ec41a3663a3e1"}, + {file = "pyobjc_framework_ClassKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ccd9f14c9ecbd35af3264e017871d26ea51d335d011ebd6ecc6c11b0cd605f3d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-cloudkit" +version = "10.0" +description = "Wrappers for the framework CloudKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CloudKit-10.0.tar.gz", hash = "sha256:05dab3798e9126625a0a72ca1987a768d5bf5c3293b594c9eb8d4e1eb02d26ec"}, + {file = "pyobjc_framework_CloudKit-10.0-py2.py3-none-any.whl", hash = "sha256:cf58196fc29c0fec8f5471172d0fc2f4fe03ded5ccb4d4c1075967283164aad3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Accounts = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreData = ">=10.0" +pyobjc-framework-CoreLocation = ">=10.0" + +[[package]] +name = "pyobjc-framework-cocoa" +version = "10.0" +description = "Wrappers for the Cocoa frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Cocoa-10.0.tar.gz", hash = "sha256:723421eff4f59e4ca9a9bb8ec6dafbc0f778141236fa85a49fdd86732d58a74c"}, + {file = "pyobjc_framework_Cocoa-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:80c22a8fc7f085746d9cd222adeca8fe6790e3e6ad7eed5fc70b32aa87c10adb"}, + {file = "pyobjc_framework_Cocoa-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0187cba228976a45f41116c74aab079b64bacb3ffc3c886a4bd8e472bf9be581"}, + {file = "pyobjc_framework_Cocoa-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a81dabdc40268591e3196087388e680c6570fed1b521df9b04733cb3ece0414e"}, + {file = "pyobjc_framework_Cocoa-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a23db9ab99e338e1d8a268d873cc15408f78cec9946308393ca2241820c18b8"}, + {file = "pyobjc_framework_Cocoa-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a3c66fe56a5156a818fbf056c589f8140a5fdb1dcb1f1075cb34d3755474d900"}, + {file = "pyobjc_framework_Cocoa-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bf9020e85ead569021b15272dcd90207aab6c754093f520b11d4210a2efbdd06"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" + +[[package]] +name = "pyobjc-framework-collaboration" +version = "10.0" +description = "Wrappers for the framework Collaboration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Collaboration-10.0.tar.gz", hash = "sha256:242148c98010f44b4f09d6037b9aa963e54038c1769474643997e7c4618f2c2a"}, + {file = "pyobjc_framework_Collaboration-10.0-py2.py3-none-any.whl", hash = "sha256:971e75adb91bc3f39750ce3f5332e72500f82d04f6e95cb1e8dd1dc468826530"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-colorsync" +version = "10.0" +description = "Wrappers for the framework ColorSync on Mac OS X" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ColorSync-10.0.tar.gz", hash = "sha256:e5722486f02a3c2330996e76207e7e26dde1597122503659259715a7dedf73da"}, + {file = "pyobjc_framework_ColorSync-10.0-py2.py3-none-any.whl", hash = "sha256:5c5d361ebdcf72f4a2665cf0c68adc153b6621ea7ea0df6bbc60a4a69ec1e2b0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-contacts" +version = "10.0" +description = "Wrappers for the framework Contacts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Contacts-10.0.tar.gz", hash = "sha256:7130d83be467c4bb877716a73b2e1a7768f19f2c43bf3bbff2d9ae412008d4a8"}, + {file = "pyobjc_framework_Contacts-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8f3b8cbf337d6a674374d6b90292871bdda0304c58aa7d4e23b96c40816534db"}, + {file = "pyobjc_framework_Contacts-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e344065964de463cc18aaac5d684ae900a3867889a1ad24e0fa1937f03ceac0c"}, + {file = "pyobjc_framework_Contacts-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:03d0a882fef637ebdc709c3915ae437fdc5afe63141fb6c1c3e6041c4831c2b6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-contactsui" +version = "10.0" +description = "Wrappers for the framework ContactsUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ContactsUI-10.0.tar.gz", hash = "sha256:38f885b861af10e5773f4dbbabd4170bcd35610d879763caac47623ff7a410a9"}, + {file = "pyobjc_framework_ContactsUI-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d73a991b76238161d97de16e717e1de0a1359dd5439f7a23277a9cddaf9f2d35"}, + {file = "pyobjc_framework_ContactsUI-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5ea57e3eecc9a8dc451c91a21eb2b03a6a94b23c5c61dbf26d774abedb604313"}, + {file = "pyobjc_framework_ContactsUI-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0fe6ad4f5d1fb15a7c749512458c71da8d5ffe46170e992b1a1d0f07dafd98a3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Contacts = ">=10.0" + +[[package]] +name = "pyobjc-framework-coreaudio" +version = "10.0" +description = "Wrappers for the framework CoreAudio on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreAudio-10.0.tar.gz", hash = "sha256:6042e9fea80bf5c23a8a3a4a2888243b7152316275ab863ed6bc289eabdef9f1"}, + {file = "pyobjc_framework_CoreAudio-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bd83cb720352e59c99d3c60a06670babad27c04ca0266dabf127a4575f3899bf"}, + {file = "pyobjc_framework_CoreAudio-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ae64cfc907b7bc7fb275b6072e6aedf0755f9ec57f862bf74c53ae6fef6e340"}, + {file = "pyobjc_framework_CoreAudio-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:41c75e7a2e17619841c55a0be8c3c0666fad190a7142f1a80f01451184832cf3"}, + {file = "pyobjc_framework_CoreAudio-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0802a45702bf8c2c0cbc5e80863a3c7924d1a6b07ffcd21e3aeac22a590772a1"}, + {file = "pyobjc_framework_CoreAudio-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c7b4af7752e7c99e4b958313a00697b19a3475adb611469909ab5431c0d6ef08"}, + {file = "pyobjc_framework_CoreAudio-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ee779edf528b56bc5dcd2b4d5dda4795516a68d3443453341e485f461e14301"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coreaudiokit" +version = "10.0" +description = "Wrappers for the framework CoreAudioKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreAudioKit-10.0.tar.gz", hash = "sha256:0e604b47fb04303399d6cdeda5e83c0fed53ee61348052d44321ffbe898b08dc"}, + {file = "pyobjc_framework_CoreAudioKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b199359b9140df894a96673bb805f29ef40004167d1bdbea899222844ed3d5e2"}, + {file = "pyobjc_framework_CoreAudioKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:096d04184ed27dc415bcd6c7652f4db6901efbd6df544c1b18f69c1e67c5c03e"}, + {file = "pyobjc_framework_CoreAudioKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4e06731de042dded49b0854c946b6e5cd67b37d3a892609985ffbb2d60169224"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreAudio = ">=10.0" + +[[package]] +name = "pyobjc-framework-corebluetooth" +version = "10.0" +description = "Wrappers for the framework CoreBluetooth on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreBluetooth-10.0.tar.gz", hash = "sha256:dddc9020cd2ca008c7037c61026641fff5d91a608b9e3bda51d4ba6afbb04e3c"}, + {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5dc85f0acea4ec8fb59ed91f89e677133b0e5efab21fe14694328ddcdbdc22c1"}, + {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:155b2f9814970a07c602286ce19d3d3c3a812951a62b9f19aaa80475f9b49f05"}, + {file = "pyobjc_framework_CoreBluetooth-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:119c4747dcc8a1b9980b8bac4f2ffd90a3950b048b9fbf03e70656eaaaefe7d4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coredata" +version = "10.0" +description = "Wrappers for the framework CoreData on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreData-10.0.tar.gz", hash = "sha256:6799c3ab2ad5d609df8d8801d19740abdbe8ea70851abfe8a660bcb91818238d"}, + {file = "pyobjc_framework_CoreData-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:98803ba27319308305cb498947e8edad829ab4564c945217563a0a4d13607702"}, + {file = "pyobjc_framework_CoreData-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ad6c11c9d1fd9c368291546bdaaf9355c1410bce662f782509249863dd8368ef"}, + {file = "pyobjc_framework_CoreData-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1c897103683737761f8d1c248011affbad3f2bc08f0c9f312a051da6134931a2"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-corehaptics" +version = "10.0" +description = "Wrappers for the framework CoreHaptics on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreHaptics-10.0.tar.gz", hash = "sha256:3ea964b452763e27b24373fc61adf65fe6553bd815e8b9b3399f43ee10ab600c"}, + {file = "pyobjc_framework_CoreHaptics-10.0-py2.py3-none-any.whl", hash = "sha256:5c7bbc18db031be82bdbdde8f96045220a0309e200e8779bc7e361eb2d482892"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-corelocation" +version = "10.0" +description = "Wrappers for the framework CoreLocation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreLocation-10.0.tar.gz", hash = "sha256:d84001ab5ef58441514bd92ed9b2fd4225faf0241d2a09ab503592fbc6a6066d"}, + {file = "pyobjc_framework_CoreLocation-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0a9d535f00a0369d493f49bd898e68d5ce7227ce161a3f0df0d9e6668e396a77"}, + {file = "pyobjc_framework_CoreLocation-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cf39e4f092d7a94a8ad516bda2603872fd0952aeac0bb0143e7ec2e2244a172d"}, + {file = "pyobjc_framework_CoreLocation-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4ec1c23b92285f7f33bdc86dc4e6cbccb8788ceca6ea6205f420859ed172abee"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coremedia" +version = "10.0" +description = "Wrappers for the framework CoreMedia on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMedia-10.0.tar.gz", hash = "sha256:27d0755cbd3ae3b487ace5e3233f0598b976905f43357b71fd73489865f7b9e1"}, + {file = "pyobjc_framework_CoreMedia-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0fd980d45d710c54e668e96268cb94dd6c877526a141581d2749bfdce4e6791"}, + {file = "pyobjc_framework_CoreMedia-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:064c35991073fe18f27f321cb33cac1d484052d154cee5222260c7fd179bc3fe"}, + {file = "pyobjc_framework_CoreMedia-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d8bf02036e60c5f47b904a259e0665b7774d915eda95810566ca1b82a1be27e"}, + {file = "pyobjc_framework_CoreMedia-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb6d6dd75febc83d22e986b2894307bd6bac1980535e7f70931bcac034bdbded"}, + {file = "pyobjc_framework_CoreMedia-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:df15b39eec3420b68f878b6de698c8e94fc2a46a2bd5a16928c9cfa2ae24e8ee"}, + {file = "pyobjc_framework_CoreMedia-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b1251c455e593dc0af52eb38c61da9847f349b9dfe6b6449a24f15aea938a31"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coremediaio" +version = "10.0" +description = "Wrappers for the framework CoreMediaIO on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMediaIO-10.0.tar.gz", hash = "sha256:d535c67d287d21e25d739c72ae9f7ce8b0f96eacfd3e19758da69ba355553483"}, + {file = "pyobjc_framework_CoreMediaIO-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2e430da4065cadd8857b1615c5641874484c6d857805f9fd2185de3a8fc4ef53"}, + {file = "pyobjc_framework_CoreMediaIO-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d20f7c39b065f24d12b403b663f1cc09ce794ebcd1a02a199ca335bf4b5fc26"}, + {file = "pyobjc_framework_CoreMediaIO-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ad5fcce52f5f8628b3bad5ddfc9c60f4f74a5509ed59a022cc57d583b279d78d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coremidi" +version = "10.0" +description = "Wrappers for the framework CoreMIDI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMIDI-10.0.tar.gz", hash = "sha256:7e464775fb6bd77148394b5f53caa61c36e3426f61cc621f299bca91931eb3a4"}, + {file = "pyobjc_framework_CoreMIDI-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:7ddb0b6222046b35a93601e3e4562bcbb32e4abe6ffa510e80660d718e45eaf8"}, + {file = "pyobjc_framework_CoreMIDI-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:68d0a9ac864d2ee1b7ba6977e68698d8db853149e64c18279149c1cc6ac39748"}, + {file = "pyobjc_framework_CoreMIDI-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e5e97f794154425def217d041ccf50f4c820b827d27ae48ad5eedb09eaf2f6f6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coreml" +version = "10.0" +description = "Wrappers for the framework CoreML on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreML-10.0.tar.gz", hash = "sha256:11b70aaa34d45b2a325231ddc571686b8e5c6404b74eb647c84c0cb2cf51052a"}, + {file = "pyobjc_framework_CoreML-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c3b6ce1fc80a77909aa1bd0938da7516b3e8a0b04a5800036bdc1456e01c084d"}, + {file = "pyobjc_framework_CoreML-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4bb7b240be5e184a68527db3a4d85a7af1263fca258fb25ee9d5b84a90b6e553"}, + {file = "pyobjc_framework_CoreML-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:beb7eae3e2dce823c2e4d2ceb480884f09271e29784c8b5016b268df8b987378"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coremotion" +version = "10.0" +description = "Wrappers for the framework CoreMotion on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMotion-10.0.tar.gz", hash = "sha256:d735668ffe028450c09499bca57467dbf77e0360b6f0623f1e054b2fe723fffb"}, + {file = "pyobjc_framework_CoreMotion-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8d0fd4ab5f6642f716becd2ba3dfe45d83e3a042ba184bf5056d8d2064bf716"}, + {file = "pyobjc_framework_CoreMotion-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2e0f6657464d5ba212691e5918f3d777a1c36d1694080ad029ef3d987c25b29c"}, + {file = "pyobjc_framework_CoreMotion-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5a2082d1e3acbfde753909680a869c8cc7b11c1a494aa497496ea9c9c98fdf29"}, + {file = "pyobjc_framework_CoreMotion-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd4f635d73a3bdcfe1f6d6b9dc47816d7eda1152e1c9f4f2e2f4de1b4111cf38"}, + {file = "pyobjc_framework_CoreMotion-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d1947bbb8348bc576fcf412781f616a35bc58bc6a8fef58630e5b801ee0e36cc"}, + {file = "pyobjc_framework_CoreMotion-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3bd7725e0f764d861d1ec6a531fa1ae046970ff6d9fcb62fcb804ca86bc28316"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coreservices" +version = "10.0" +description = "Wrappers for the framework CoreServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreServices-10.0.tar.gz", hash = "sha256:a6e80770ead727979e9ffd4ea97c30889e1fdec49873bb5129bf3ef3c5b90005"}, + {file = "pyobjc_framework_CoreServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5846d8fcd2b352c479b5517176a9c6939cb50599041d9f68ddf55804d58f5751"}, + {file = "pyobjc_framework_CoreServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b8638b63f83246f6659624ad20e068ba11fdfe915f7c6318de7e3213cd2b0aac"}, + {file = "pyobjc_framework_CoreServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6ecf120cc89401b410d8f307cd499c6d80f4f52d1d1291150507b85a69bbc12c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-FSEvents = ">=10.0" + +[[package]] +name = "pyobjc-framework-corespotlight" +version = "10.0" +description = "Wrappers for the framework CoreSpotlight on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreSpotlight-10.0.tar.gz", hash = "sha256:393767c63f2513ab4056c688aecdaf1ae67357f8d99fa963d765cfbdc9ccba47"}, + {file = "pyobjc_framework_CoreSpotlight-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:dc07cfa097f79946c15fcc3bb2aea5db17822f2e9f85478c5b07d2f5f194db4f"}, + {file = "pyobjc_framework_CoreSpotlight-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8760252792ce351a97b9bfed9e3f84999b698b63fcf759e8b9df5978c67236ea"}, + {file = "pyobjc_framework_CoreSpotlight-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4370d760d93cfc58691fa7fb585f20067157a18c06cd4c3bfc3dd7c9824eda5a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-coretext" +version = "10.0" +description = "Wrappers for the framework CoreText on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreText-10.0.tar.gz", hash = "sha256:2c157d82373b8128afb9a61df26cbf029896adf86bf86876ce3f8cc3c3f3cb1b"}, + {file = "pyobjc_framework_CoreText-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dfba918655fcd37018f38e6ca9db557c67b63ddd2de93319eb05c07f492cca32"}, + {file = "pyobjc_framework_CoreText-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c8ec3d634b440b53727adf45212bb34639ee404b891be87a62f0f2253b7a92e5"}, + {file = "pyobjc_framework_CoreText-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ab1f76d3f6bf6470217478d2edf62360c5e934dfd66a4d0d35a8bf07086bee65"}, + {file = "pyobjc_framework_CoreText-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:543f37fe792ec75d72bead5616f6dc29ab2d8e26d824080fd7625efc015ecc50"}, + {file = "pyobjc_framework_CoreText-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1c4026c3d15922c7ec372b2e43a5673ba0ed436e59e046afd860a3d6a5676c25"}, + {file = "pyobjc_framework_CoreText-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca269a49bb76fccb6d50eef3c72650bc686ae19a4cc9be6288fd6e758fa67768"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-corewlan" +version = "10.0" +description = "Wrappers for the framework CoreWLAN on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreWLAN-10.0.tar.gz", hash = "sha256:f71594ca1d2741f5979688d6d3880237c469943b49a030de131102357cdccb2a"}, + {file = "pyobjc_framework_CoreWLAN-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f245de3d5d7ff2f49cfe72c0bf499f5fb97ad2e930efd485f8c01ec11d749136"}, + {file = "pyobjc_framework_CoreWLAN-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7d7cb58fba9272a6dd20a84733006e574092128d9d581f346f4c777cb6c353ff"}, + {file = "pyobjc_framework_CoreWLAN-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6211ad500131bf1bd0680847f67a948478551043054a4514f90879067c854bb6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-cryptotokenkit" +version = "10.0" +description = "Wrappers for the framework CryptoTokenKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CryptoTokenKit-10.0.tar.gz", hash = "sha256:314fe7067cecc0901602173a47bcdb3107ddbae6a22052b0e217f79b7d388153"}, + {file = "pyobjc_framework_CryptoTokenKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:94fe71998c6821d4f45de60227ee0541ad71baf758b3051e7a3c84e0abcccaac"}, + {file = "pyobjc_framework_CryptoTokenKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f30f0165b5914da418450cc3f8b870d02052f8eb9b14f27dd61aff7928cd1eb5"}, + {file = "pyobjc_framework_CryptoTokenKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:9d4db0b30f395d0d4e26b48645ee7b219f499c243124fbd6495406620576ca31"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-datadetection" +version = "10.0" +description = "Wrappers for the framework DataDetection on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DataDetection-10.0.tar.gz", hash = "sha256:026b44718fc71750f7ba258be461ecb1463ebeac0ea3bf26e559cd10dfd7bd57"}, + {file = "pyobjc_framework_DataDetection-10.0-py2.py3-none-any.whl", hash = "sha256:6f6420b187475cccf20757577b005bc16b4a606dd8d9d431b59151e571fa6b12"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-devicecheck" +version = "10.0" +description = "Wrappers for the framework DeviceCheck on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DeviceCheck-10.0.tar.gz", hash = "sha256:00a02c603020a9e9369f894d0b6931217ca0c9606f4947c34bfb4f413cc736a7"}, + {file = "pyobjc_framework_DeviceCheck-10.0-py2.py3-none-any.whl", hash = "sha256:e930659cb8cb3096b88f43c237951364dbd1b29d98390e0b55b48aec0442cc92"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-dictionaryservices" +version = "10.0" +description = "Wrappers for the framework DictionaryServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DictionaryServices-10.0.tar.gz", hash = "sha256:a250ead7a3a0504ea860ed4ebefce3bec4b308b91ea760c33bfcc14af5054873"}, + {file = "pyobjc_framework_DictionaryServices-10.0-py2.py3-none-any.whl", hash = "sha256:8bc50b80e8f77e411b707827062609b67695bc5ae619452388eb02bdeea19f05"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-CoreServices = ">=10.0" + +[[package]] +name = "pyobjc-framework-discrecording" +version = "10.0" +description = "Wrappers for the framework DiscRecording on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiscRecording-10.0.tar.gz", hash = "sha256:1b4a9a702f0695ed87392693ab916cc120c179547d6fa7bf3e59708fe218ec22"}, + {file = "pyobjc_framework_DiscRecording-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6d4d737f0536d1d4cd4efc88787d4b20a74b71e2f91e5554346e8b1b993a2f97"}, + {file = "pyobjc_framework_DiscRecording-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:11fecd1d6e464582e0ef0a0a4469e1ed1ea36e45c27d2bbd77cd42dca4f0dadd"}, + {file = "pyobjc_framework_DiscRecording-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d547d69e3ac8f9c735456af7c440c0c318752e1e8b55868f8a2da0aae0bef8ee"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-discrecordingui" +version = "10.0" +description = "Wrappers for the framework DiscRecordingUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiscRecordingUI-10.0.tar.gz", hash = "sha256:9a77cd9fb311ececab84b682ebfbb573a13f6f9f67b39733c1920fcea83dfd31"}, + {file = "pyobjc_framework_DiscRecordingUI-10.0-py2.py3-none-any.whl", hash = "sha256:c80135d65bb25f1d4c3c40af9a50c3b15125c54703d6e65cf4316fe3ed3bd0e7"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-DiscRecording = ">=10.0" + +[[package]] +name = "pyobjc-framework-diskarbitration" +version = "10.0" +description = "Wrappers for the framework DiskArbitration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiskArbitration-10.0.tar.gz", hash = "sha256:cfd8e4c7dbef6b69832fa8e2425c53bf8bbc83b8c78bb5a098a787335df7bf8f"}, + {file = "pyobjc_framework_DiskArbitration-10.0-py2.py3-none-any.whl", hash = "sha256:cf7dadef895980e08dc7dd646c6d819ea3b4b8321abd2af512d9bde5de389895"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-dvdplayback" +version = "10.0" +description = "Wrappers for the framework DVDPlayback on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DVDPlayback-10.0.tar.gz", hash = "sha256:fe0bf9cfbf1be9888685aedd3b25b6793d072e947c6eddcc0ccb2c4a07bee453"}, + {file = "pyobjc_framework_DVDPlayback-10.0-py2.py3-none-any.whl", hash = "sha256:ea31f045edf56abda6e1fc2aa9ff0bee267fd549b7787bbaf7e437e4fa58135e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-eventkit" +version = "10.0" +description = "Wrappers for the framework Accounts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-EventKit-10.0.tar.gz", hash = "sha256:8219a650edf1b6842c92306a077502488f95473d138fd842068d4a1e56621989"}, + {file = "pyobjc_framework_EventKit-10.0-py2.py3-none-any.whl", hash = "sha256:48d65edd47efd0864d93e5bbe3f05121c413d4006b7c0f0a3f0592b58d80a0db"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-exceptionhandling" +version = "10.0" +description = "Wrappers for the framework ExceptionHandling on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExceptionHandling-10.0.tar.gz", hash = "sha256:288b99a86e29999dc0f3f1a6bb90c8dc0b79ed51b265ee4c9e673d660e959cb2"}, + {file = "pyobjc_framework_ExceptionHandling-10.0-py2.py3-none-any.whl", hash = "sha256:3c7669d6e93d4f4d472de8c7b8e3b5ecd42dda16161e24b3bf796713fc20eb1a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-executionpolicy" +version = "10.0" +description = "Wrappers for the framework ExecutionPolicy on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExecutionPolicy-10.0.tar.gz", hash = "sha256:cd6c14970a090f033673e5870804051a2bc41b060d02420eac0e7816b9e2c034"}, + {file = "pyobjc_framework_ExecutionPolicy-10.0-py2.py3-none-any.whl", hash = "sha256:823eda14ad797436101f365cb3a5cd7bc46bb8a8972418851427d478b9274ded"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-extensionkit" +version = "10.0" +description = "Wrappers for the framework ExtensionKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExtensionKit-10.0.tar.gz", hash = "sha256:ed9c596728819a58803841bb36d0a5773929d6bd32279b924dcd004266a901df"}, + {file = "pyobjc_framework_ExtensionKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e8b4bf58985300019f64ed8ce3dc6c9ba73621646dacdc5273d93c5ffdc9238b"}, + {file = "pyobjc_framework_ExtensionKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6613e71395266e948d4bd8917245adf77ca58166d37aac73e340081a4ad6ff8b"}, + {file = "pyobjc_framework_ExtensionKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:144cd81e9e2ff3de8553c236a9fb7b9fbe2023f3886d32328dc324442be7ab07"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-externalaccessory" +version = "10.0" +description = "Wrappers for the framework ExternalAccessory on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExternalAccessory-10.0.tar.gz", hash = "sha256:4b00f07e6ec8e68974d89242789720bfecdc474c26bf0f2b2b2d648e6b6155cc"}, + {file = "pyobjc_framework_ExternalAccessory-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9d51574d483719d646dc4327125ed1038c5bbaae626ae08b178cac3269f0285"}, + {file = "pyobjc_framework_ExternalAccessory-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0d4101c0f3f7a4e0c7cc5c0bc0357dd564b03b5f9c41efa8f347d998806c8df1"}, + {file = "pyobjc_framework_ExternalAccessory-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a2885e163ebbae023bd6990a3864f2ca24aa49fffbff3be54f23225bac64ca6e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-fileprovider" +version = "10.0" +description = "Wrappers for the framework FileProvider on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FileProvider-10.0.tar.gz", hash = "sha256:432165e8ae9e85437bd4b36be4fe1a467f03f5e9d6aca07228ac5385a96b2d44"}, + {file = "pyobjc_framework_FileProvider-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b596718c562d72bfc9dacdc53cef494b8afe9f2c9d715a16c3b209c628edcba"}, + {file = "pyobjc_framework_FileProvider-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:69427a57d2238a3274fe351e39d98918a097bafc54eebba68edc07624086e38a"}, + {file = "pyobjc_framework_FileProvider-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b9820f94be07aaae649d0eae55d7661f828fc12b605c0e61c08aeb33ed0c1747"}, + {file = "pyobjc_framework_FileProvider-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b03250d3e0c5ebfbd71e2134c67577a19c3856ef3c4b849d98a00e22801c7d14"}, + {file = "pyobjc_framework_FileProvider-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7caa8541f36a4842210ac8e82f107cd109d6e835f86d0391d5bbc41433f4b384"}, + {file = "pyobjc_framework_FileProvider-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a453bc7a76ae0bc9a574bfd0b38446166d344c62e59113a19da11ade8413eaf4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-fileproviderui" +version = "10.0" +description = "Wrappers for the framework FileProviderUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FileProviderUI-10.0.tar.gz", hash = "sha256:895c3de1ba34ab011d012906623bcc2d2addc7b32af201ad19d59718e933d2ff"}, + {file = "pyobjc_framework_FileProviderUI-10.0-py2.py3-none-any.whl", hash = "sha256:bca5613525ffb757e033803060d63f592612820fbe7ff024e931a5e3745ec08b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-FileProvider = ">=10.0" + +[[package]] +name = "pyobjc-framework-findersync" +version = "10.0" +description = "Wrappers for the framework FinderSync on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FinderSync-10.0.tar.gz", hash = "sha256:6915d934236efca382926e93ded216b18a23e7dd03bf70b751cb7e86bbf237f6"}, + {file = "pyobjc_framework_FinderSync-10.0-py2.py3-none-any.whl", hash = "sha256:b2d166fa8af4cd7516fc860c896531bcf9921e5251106c99ac6cd726bf41d020"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-fsevents" +version = "10.0" +description = "Wrappers for the framework FSEvents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FSEvents-10.0.tar.gz", hash = "sha256:a462c1ad6d6c93d9542c9780b970915e5e9fa0f70391187f7145b5b1c64e57d5"}, + {file = "pyobjc_framework_FSEvents-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:eb3b590a56c31eee60adddc9657f67e319a80bfe0d91f23cac5c78dd22893404"}, + {file = "pyobjc_framework_FSEvents-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a548e660f2f2e63e9f2cd57db472b2b21056d89748541a167803ba4c9b6287f1"}, + {file = "pyobjc_framework_FSEvents-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:68113dbd448619f1d07e847c6088f8b4371001f2468300638dc78236ffa10c05"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-gamecenter" +version = "10.0" +description = "Wrappers for the framework GameCenter on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameCenter-10.0.tar.gz", hash = "sha256:3157d1389bde7afd6fa7d5d1aa64578e99c5fd50a1400178b1f58443013d6669"}, + {file = "pyobjc_framework_GameCenter-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:9a83d8c13ae716cd3216e288531fb9e3668bf4e8e27fc1c669b1511e801aa02b"}, + {file = "pyobjc_framework_GameCenter-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b333d53bffcbfce4929f7db22dec9f0d8bb47e387ef2e5df4a49250069e76c86"}, + {file = "pyobjc_framework_GameCenter-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:baea3dd2e75ede21c81cc81ecd2f525b2507556bee7bf76fb6a0a292f39f8346"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-gamecontroller" +version = "10.0" +description = "Wrappers for the framework GameController on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameController-10.0.tar.gz", hash = "sha256:c042d6f581a37d39564c6e7b998d6a2775adb4b937aa33e60b8aa99475157c2d"}, + {file = "pyobjc_framework_GameController-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bd8a4e7920df5f6f60d6034af31094fe756210efc75c58455735259e201385a6"}, + {file = "pyobjc_framework_GameController-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1f384ae252be210a32ec1ae6a460a108e004d13d5bac1324b25c9b3932171da9"}, + {file = "pyobjc_framework_GameController-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f7293f4c13ac372f9234d587e5370aac16de562b01c5dcd6e561564adb80211f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-gamekit" +version = "10.0" +description = "Wrappers for the framework GameKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameKit-10.0.tar.gz", hash = "sha256:6febacef9b003b58eeb6ca936cd83825bd22fe55475b965e0deb29b48d5912c5"}, + {file = "pyobjc_framework_GameKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cee6d712b20ef17bea9a92681635e92628ecefd78965e016b1ede0ff9c15ac11"}, + {file = "pyobjc_framework_GameKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d7f7d7a3ed81d02f2191e3d6fc8336840e672f70984b64cefb36165abac371a"}, + {file = "pyobjc_framework_GameKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:53859c370583322acfc9ba03b4b0ba258541836eb16420e8de44ab185caba8de"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-gameplaykit" +version = "10.0" +description = "Wrappers for the framework GameplayKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameplayKit-10.0.tar.gz", hash = "sha256:7e5cf3197a53344638a1957e1827cd86018cf7549a6da73193346cd8c40b1d52"}, + {file = "pyobjc_framework_GameplayKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ee4495804fbb6b1aaef1be5b2dcafb676aabfe9bbdcce319484b4e5e2e9d3526"}, + {file = "pyobjc_framework_GameplayKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7fad056f82b75700139d2479e56a1bdd1e2cabb2367783321b31f4abe240084a"}, + {file = "pyobjc_framework_GameplayKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ea527d8b7ee32f5cc9864afb172bb70bf0a1a02bd29d9d57d9f0278bac56aa0e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-SpriteKit = ">=10.0" + +[[package]] +name = "pyobjc-framework-healthkit" +version = "10.0" +description = "Wrappers for the framework HealthKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-HealthKit-10.0.tar.gz", hash = "sha256:0abe3e003927998728db217d2a023f59d9e8f52072e81cc01469888731b7ebf5"}, + {file = "pyobjc_framework_HealthKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:02e92b6f875bb707516f957d33e94a4ef98b915e2e628356d77d2cf8edbcd4c6"}, + {file = "pyobjc_framework_HealthKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f24e683081c09050667b8d2410ef6ba06c6c314e2d3e42f9d947df87663ff9ef"}, + {file = "pyobjc_framework_HealthKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:888530d05ec6954fb5e6993c8f5089889baabda4527aafbbcca7c7d9c8705f0c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-imagecapturecore" +version = "10.0" +description = "Wrappers for the framework ImageCaptureCore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ImageCaptureCore-10.0.tar.gz", hash = "sha256:9660faa140806dd0f2c50c39062863c23188c6b9596e2946234dd3c35882d3c7"}, + {file = "pyobjc_framework_ImageCaptureCore-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be85524a36f83e753898f93529145b6299760af5891521e4370f09944813942"}, + {file = "pyobjc_framework_ImageCaptureCore-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:43d9fc0dc3f78aac627668aba6627888e95f034b3422a30f3727a0027222fec3"}, + {file = "pyobjc_framework_ImageCaptureCore-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3f0790981bf67416d02004cf83f0f68e8edda4aba48933596317e8653a173403"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-inputmethodkit" +version = "10.0" +description = "Wrappers for the framework InputMethodKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InputMethodKit-10.0.tar.gz", hash = "sha256:dc2f10752ab62e4c7b2306938d617d83bef6d52752862a0998ed57db472e36ae"}, + {file = "pyobjc_framework_InputMethodKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:895771f0a47f588f69ed930868ba48ee380ccecd7ef384ad97c4e78220e12a0a"}, + {file = "pyobjc_framework_InputMethodKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ab33c90206934137b093d71ea8e4b73626e337d7767c16154d4a41b502c9a2a"}, + {file = "pyobjc_framework_InputMethodKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f9d4f8025303549dbcc284bd7bb9ca6e58c06f398eb085ed9ca43e3ca65ab7fe"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-installerplugins" +version = "10.0" +description = "Wrappers for the framework InstallerPlugins on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InstallerPlugins-10.0.tar.gz", hash = "sha256:417f51877cd2ddd95e341d7d4b5db7d152a3e9d4f6537db20758bce0f9235c3d"}, + {file = "pyobjc_framework_InstallerPlugins-10.0-py2.py3-none-any.whl", hash = "sha256:1dfee60017bdf9c2e1566dd26972a288f9f9ace878c25ab5681164b2221d1e70"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-instantmessage" +version = "10.0" +description = "Wrappers for the framework InstantMessage on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InstantMessage-10.0.tar.gz", hash = "sha256:7b5cd6217fb0d867b03ea7c15ab55c66fe63ab0beaef63c1527e734b16780405"}, + {file = "pyobjc_framework_InstantMessage-10.0-py2.py3-none-any.whl", hash = "sha256:c53dd8ddf2b28dd87cdb67c21798b15d432d659abc633fc3c0a27433bc7a241a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-intents" +version = "10.0" +description = "Wrappers for the framework Intents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Intents-10.0.tar.gz", hash = "sha256:228177cd32e63b2b2c76befdb80e520c4db81be7186549753c3dc7b9f74d4a4b"}, + {file = "pyobjc_framework_Intents-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5cc9ec8240b261578cac95b97156883f1ad80ac2771ec5e2fa7fe41e112f169b"}, + {file = "pyobjc_framework_Intents-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c4fb57078f797362169f8c41218248028132e6120b98546e7d22bf6a995c640b"}, + {file = "pyobjc_framework_Intents-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:aea0e15602ffac9ef237fa5bda102372918e8066630d0d4c5e919fb8f647b090"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-intentsui" +version = "10.0" +description = "Wrappers for the framework Intents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IntentsUI-10.0.tar.gz", hash = "sha256:27dbc84df229700c8e187ba9bfc089fe7dea63cfa20ee7e3c3f09c9f8b8c37d0"}, + {file = "pyobjc_framework_IntentsUI-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:930a3b7bde1292a67e2f62b0bbe11778b7d0a77e29a716b6b0ee55f3270515c1"}, + {file = "pyobjc_framework_IntentsUI-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6ad1aaa9fc424605b9800ca848d57058903ad26d1dabaef33a8339051746f1d5"}, + {file = "pyobjc_framework_IntentsUI-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c122eee00aa5bdc471399431088c9359508988e88f57289c25d18cbd95bd190c"}, + {file = "pyobjc_framework_IntentsUI-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28b1d6fc8e0a416f65959a7506672425cff960e152538dc7fbb7fc3f3237871f"}, + {file = "pyobjc_framework_IntentsUI-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:829bd2b67c2d753e43ec6d8d0b3dc544f9a576cbe00cbe3264c1636e367d157d"}, + {file = "pyobjc_framework_IntentsUI-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70335349188f82b5be762b926938fddb01ec24bece963690b6ed6ba50bf480c4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Intents = ">=10.0" + +[[package]] +name = "pyobjc-framework-iobluetooth" +version = "10.0" +description = "Wrappers for the framework IOBluetooth on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOBluetooth-10.0.tar.gz", hash = "sha256:5e6ddcdb8132124fa18c2eb0d0dab9b51e32be14e7ab7a2df12daee3940ec431"}, + {file = "pyobjc_framework_IOBluetooth-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c0dd178608ad97e9c981992f6d9396c2dc05844d24e25095a68aa28d1013f10f"}, + {file = "pyobjc_framework_IOBluetooth-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f73913e8700a1106da8b566160cf1ec5ba7bd69b0983772cc8ca1aa7dc450fa0"}, + {file = "pyobjc_framework_IOBluetooth-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c3645fb7ed7425640eeca0a8e9bb4f1cee337bfa6e9b4b8db3b199b8611b87f6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-iobluetoothui" +version = "10.0" +description = "Wrappers for the framework IOBluetoothUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOBluetoothUI-10.0.tar.gz", hash = "sha256:5cd1e6fb2100020c90af6cfcfdea5089634469d2b29cdba9c749791943274bfd"}, + {file = "pyobjc_framework_IOBluetoothUI-10.0-py2.py3-none-any.whl", hash = "sha256:d8e15a2eb39f9d76613fb6ea241ef5c4bd94ae2f21e0fc15661ae44090bea43f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-IOBluetooth = ">=10.0" + +[[package]] +name = "pyobjc-framework-iosurface" +version = "10.0" +description = "Wrappers for the framework IOSurface on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOSurface-10.0.tar.gz", hash = "sha256:c233b39e4f5f000e0f9014feb9ec54d36d3a11675a6bcfc8d05d058be965940f"}, + {file = "pyobjc_framework_IOSurface-10.0-py2.py3-none-any.whl", hash = "sha256:a3df57071d833c58ca019bf880a54c92aaeb11accc006a1fb4eb7f215cf8a1a1"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-ituneslibrary" +version = "10.0" +description = "Wrappers for the framework iTunesLibrary on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-iTunesLibrary-10.0.tar.gz", hash = "sha256:3891793569bfe5a2dcfbe075dbd9a92b0937ebc47e233d78a2f65ca6bc92d13b"}, + {file = "pyobjc_framework_iTunesLibrary-10.0-py2.py3-none-any.whl", hash = "sha256:2d3d8457f9ba6bf415535263dee6973e468f140b04b3cf436481551a25c8f07f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-kernelmanagement" +version = "10.0" +description = "Wrappers for the framework KernelManagement on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-KernelManagement-10.0.tar.gz", hash = "sha256:52b9546ed192dd9390351fd3c530e658122348c9b6f033f94e5737ce760f9bb2"}, + {file = "pyobjc_framework_KernelManagement-10.0-py2.py3-none-any.whl", hash = "sha256:d3573fb51b0132b6814a0fd56a7fb7d648fd627b459ea3157c3d778a5ea4cdbd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-latentsemanticmapping" +version = "10.0" +description = "Wrappers for the framework LatentSemanticMapping on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LatentSemanticMapping-10.0.tar.gz", hash = "sha256:ad74bb661109cca52543fa9fb5747c6dc3ad352d74771db6c18312b6468098e9"}, + {file = "pyobjc_framework_LatentSemanticMapping-10.0-py2.py3-none-any.whl", hash = "sha256:01dc811aad11914c1e01daa018ef1833da144095f42ca2dfe810e4768a540a86"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-launchservices" +version = "10.0" +description = "Wrappers for the framework LaunchServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LaunchServices-10.0.tar.gz", hash = "sha256:0fb7e8d17db9c6a9c8d9333c88703734ef4325c0d36a319183ac3febccef8a9c"}, + {file = "pyobjc_framework_LaunchServices-10.0-py2.py3-none-any.whl", hash = "sha256:f86c70574c7d7c9586fd1908e15fff9df297ab285d7067759337c8e03955427c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-CoreServices = ">=10.0" + +[[package]] +name = "pyobjc-framework-libdispatch" +version = "10.0" +description = "Wrappers for libdispatch on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-libdispatch-10.0.tar.gz", hash = "sha256:228adf364c895d2a0e8b08bd06f7a23cfbd8e82e9ea6cfdba73bdee0651a4e1f"}, + {file = "pyobjc_framework_libdispatch-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:67fe10a671117fe7e8c35b7eaf8914084fbd0c64f3d635a1147782e3408dbc40"}, + {file = "pyobjc_framework_libdispatch-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:386d53ffd2389c2494a806e3bad2d709ef5d597d1500cb643a817464c20ab8f8"}, + {file = "pyobjc_framework_libdispatch-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d2fbd247ebac8fa57bb0d7a2c4e769118c995bfd81bfc7f189a8ebe297399776"}, + {file = "pyobjc_framework_libdispatch-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:66dc959837b898bdb83a85ed34c2c368d0d38aaed9883c3769d4d1ca1aad23d7"}, + {file = "pyobjc_framework_libdispatch-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c4a1e13172617916b894246b03579814f4e8151f2600403f4a799e4d4c7a032e"}, + {file = "pyobjc_framework_libdispatch-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a55a33fc71e385ff7a8c9f1d29276f44dc0f7ff5b0597d2fd769620438058152"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" + +[[package]] +name = "pyobjc-framework-libxpc" +version = "10.0" +description = "Wrappers for xpc on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-libxpc-10.0.tar.gz", hash = "sha256:ece6fc3158f61c3f33a5ed0d767f2aeb64e4575f367716f3f1642cb80221b02c"}, + {file = "pyobjc_framework_libxpc-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:029157c4af899a835122443d299e2f04030a5c801cd34ab8f5724033ff0e88e5"}, + {file = "pyobjc_framework_libxpc-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2a3c98da602f90bbb0ed0da49193381e439e0716b747b6ecb2b0b07951d0de4"}, + {file = "pyobjc_framework_libxpc-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:918e7defb445ac089736714b2c30665ea566b51fc6dae5c555751f52cced9399"}, + {file = "pyobjc_framework_libxpc-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd6e1ce4e503a45611a9f6fc22b189fa5cc4db0aa779dc09be5e7f321bdd894e"}, + {file = "pyobjc_framework_libxpc-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:862896fb2c4e8c706196ed5f9a8ca8fe88b6bf73a92cc8bc0ec6e00449d6cd20"}, + {file = "pyobjc_framework_libxpc-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d66712e657d3217ef5c8c9181e706ee21370d425ec99aa091c44b92d43dfa341"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" + +[[package]] +name = "pyobjc-framework-linkpresentation" +version = "10.0" +description = "Wrappers for the framework LinkPresentation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LinkPresentation-10.0.tar.gz", hash = "sha256:e48af9dc9a1a33313c85a11d667fcf00461638e8778f8f99ce6b2e967a0a8579"}, + {file = "pyobjc_framework_LinkPresentation-10.0-py2.py3-none-any.whl", hash = "sha256:a3de92916daa214da87afe402feef42536e3896b6ed392e040296d01ddd927f7"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-localauthentication" +version = "10.0" +description = "Wrappers for the framework LocalAuthentication on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LocalAuthentication-10.0.tar.gz", hash = "sha256:c7ca39512babcd08464b12586908d895efe3477289325cd12ab14768a194ed16"}, + {file = "pyobjc_framework_LocalAuthentication-10.0-py2.py3-none-any.whl", hash = "sha256:6d55c6df7a6337903b3a0c61e48c6e1fe7059005024885244ff8c937bf570aae"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Security = ">=10.0" + +[[package]] +name = "pyobjc-framework-localauthenticationembeddedui" +version = "10.0" +description = "Wrappers for the framework LocalAuthenticationEmbeddedUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LocalAuthenticationEmbeddedUI-10.0.tar.gz", hash = "sha256:dd3e53030a2d71c50f5f9dd5e2e7082672ff741a9adfa3da7efe3c9a7691a86a"}, + {file = "pyobjc_framework_LocalAuthenticationEmbeddedUI-10.0-py2.py3-none-any.whl", hash = "sha256:136725e321929fd840905751adf158b4bba561951984ec75a4e534ef0be76c30"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-LocalAuthentication = ">=10.0" + +[[package]] +name = "pyobjc-framework-mailkit" +version = "10.0" +description = "Wrappers for the framework MailKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MailKit-10.0.tar.gz", hash = "sha256:2611649443f3307c8fbfd78d36276fd854373c0dc4516928d3bc51419b34852e"}, + {file = "pyobjc_framework_MailKit-10.0-py2.py3-none-any.whl", hash = "sha256:b95f61745c01d41b2548ff6b6d0efc1476b718874a115fe0f17c06b5b3a1d300"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-mapkit" +version = "10.0" +description = "Wrappers for the framework MapKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MapKit-10.0.tar.gz", hash = "sha256:35a4ac2a9ae3b13699290a6fb592d1914498e4de1b90a2b60394069cd0a02c5b"}, + {file = "pyobjc_framework_MapKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:af80e9f2debe0dfd0f614fc797b4238cc298eb753704c747667f400dc9f57169"}, + {file = "pyobjc_framework_MapKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cded4f7e60ad9595d7cc665d5738c8fcd5eda41d84475b67a3abc0cd493ab808"}, + {file = "pyobjc_framework_MapKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5d33b49430d2d7229121916c57875218c0943caba6faae9caad948839657768e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreLocation = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-mediaaccessibility" +version = "10.0" +description = "Wrappers for the framework MediaAccessibility on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaAccessibility-10.0.tar.gz", hash = "sha256:0f60ed2ac0bdd4c01457619408230d61a0547b16a19ce0d6770a8f4fa7379fbe"}, + {file = "pyobjc_framework_MediaAccessibility-10.0-py2.py3-none-any.whl", hash = "sha256:20b7d0dfd0680e6b19de9683025e35d2cdbdaa76ddb66ae79fea9c0deb5ac3b5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-medialibrary" +version = "10.0" +description = "Wrappers for the framework MediaLibrary on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaLibrary-10.0.tar.gz", hash = "sha256:5a4909257d6c67245b4687327996a4a3f8c038c31e6ea2ea7cc916fd8e44bed5"}, + {file = "pyobjc_framework_MediaLibrary-10.0-py2.py3-none-any.whl", hash = "sha256:e7d0f3353a954abc801bcdb7c02713f38d76835eb8ff4912fab5d005b95d5459"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-mediaplayer" +version = "10.0" +description = "Wrappers for the framework MediaPlayer on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaPlayer-10.0.tar.gz", hash = "sha256:e3c66443fd13e5ddede01f15fdd9b635492edc239c4cd88fa540b866a76c1602"}, + {file = "pyobjc_framework_MediaPlayer-10.0-py2.py3-none-any.whl", hash = "sha256:19afc844bc204e008eac5f59699b93bae84e6235fa030d72651200414b019fc2"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-AVFoundation = ">=10.0" + +[[package]] +name = "pyobjc-framework-mediatoolbox" +version = "10.0" +description = "Wrappers for the framework MediaToolbox on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaToolbox-10.0.tar.gz", hash = "sha256:8bd24724b26a0bdcdec7e078261d8777018c9ec275b553dd8e1372afc60778d1"}, + {file = "pyobjc_framework_MediaToolbox-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:73bb05d629914f1eca277e1e7aa9ca429408121a49874259f190ce7e37dbf646"}, + {file = "pyobjc_framework_MediaToolbox-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65475deb4a9da96504df4ad677ff470afb0f50128bd4a140788db923b6638b12"}, + {file = "pyobjc_framework_MediaToolbox-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:68c6340e33f60a23acc375935abad73b876705ad460cf7f09be0bc000d219d15"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-metal" +version = "10.0" +description = "Wrappers for the framework Metal on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Metal-10.0.tar.gz", hash = "sha256:2e50b1fc34b11654a0ecb0d6ea98f691dc5794c53e18cb70f71d6460f68dbbf3"}, + {file = "pyobjc_framework_Metal-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0336f14c65a7e064d283b785487522c52f894d23348b1cc49114a919bb0db32c"}, + {file = "pyobjc_framework_Metal-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9eade923110db0db7a57c11761c8bac0c780fb786493ca7f504261c749184dfc"}, + {file = "pyobjc_framework_Metal-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:33c35ee2bd8062786c103149949713eb6d7d90618d69a2acb9c0b732824cad70"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-metalfx" +version = "10.0" +description = "Wrappers for the framework MetalFX on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalFX-10.0.tar.gz", hash = "sha256:79edcf90b59276023a143c637d37a1be563a921f5f73f526bb2d970fc08949a3"}, + {file = "pyobjc_framework_MetalFX-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:4ff51b35c29573a5b93b76334d165030055e0cfbf0a8d0b0839f510ca2d9d1ff"}, + {file = "pyobjc_framework_MetalFX-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:eac982bd2e8316e4d6b65d9bac2d62365db65f2f9bf4be4bf1a8111b7b0a08e3"}, + {file = "pyobjc_framework_MetalFX-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:376f78e3edcd67f64ff6c2f682e9e94cdbcddf6bf27a32c73f1b8aefb49fc748"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Metal = ">=10.0" + +[[package]] +name = "pyobjc-framework-metalkit" +version = "10.0" +description = "Wrappers for the framework MetalKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalKit-10.0.tar.gz", hash = "sha256:a29951ae30dae738bb9d1bab5bcc6fa1150815f671923b6e6705a10d7bab2f8c"}, + {file = "pyobjc_framework_MetalKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:31f15e033abaad2a6212bafc39e2f5d6e7d6bc7a6c93c6a24fc64a4b2db01fe9"}, + {file = "pyobjc_framework_MetalKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8cf2cc8f7fa79bb919c4b6b864edce5e1cd789a88af07ad8846dec985808940d"}, + {file = "pyobjc_framework_MetalKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d13c0a8ae8e65b87c56ea63d84a26f6cb06dafe0f34beabcf86b930c39088748"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Metal = ">=10.0" + +[[package]] +name = "pyobjc-framework-metalperformanceshaders" +version = "10.0" +description = "Wrappers for the framework MetalPerformanceShaders on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalPerformanceShaders-10.0.tar.gz", hash = "sha256:eeb3d9e5b44db876ebc93dd3d492dbc4a52b6fee96558d13a66fb283b7757ee4"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1094595da9dd3fbcbaff278538ad88871347d6155fe84d1fe2f49737831bb6d6"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4dbc6e8d902ca9e4ceb3699182006495f9e3da84b1efdbc3821e1ba4c23cf808"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:fa91c8e325c592e2a3db5940109efca9d874a4c19dd238047a4f7bd327015263"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Metal = ">=10.0" + +[[package]] +name = "pyobjc-framework-metalperformanceshadersgraph" +version = "10.0" +description = "Wrappers for the framework MetalPerformanceShadersGraph on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalPerformanceShadersGraph-10.0.tar.gz", hash = "sha256:f9b6be9ef300b82a4d228107122fc153c2763c2a1b3c7e311ded52c565bbcf58"}, + {file = "pyobjc_framework_MetalPerformanceShadersGraph-10.0-py2.py3-none-any.whl", hash = "sha256:824f9721eb724de171c9e4515931a59daacbc743890eef5fe00aa70ad1927f30"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-MetalPerformanceShaders = ">=10.0" + +[[package]] +name = "pyobjc-framework-metrickit" +version = "10.0" +description = "Wrappers for the framework MetricKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetricKit-10.0.tar.gz", hash = "sha256:15f4d384f95ab3656ae183d2fa15e1c59e91b6a5566a4edd105684a70c79401b"}, + {file = "pyobjc_framework_MetricKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:660bfe9654cb21ac450d3be4d4cbc568e503d4d1b04a97c4c90240be632ec1b9"}, + {file = "pyobjc_framework_MetricKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1f83c96d09a6ef03b39da0df5e21235b32145c726455a7e9be40ec5b2d35b5ba"}, + {file = "pyobjc_framework_MetricKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:526ebe3c0d7a4612608668fdfbcb57cdeb9b5324a9e8b481246040013ae5ce6f"}, + {file = "pyobjc_framework_MetricKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:542745e902eeda8cdcb5ca2f0517ae7832142f53a110ad59bd7c8189f31d65d9"}, + {file = "pyobjc_framework_MetricKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b694b1ed197015f14e7f6c35abc855fe790fbbbe17b16ba224c727e185279fc7"}, + {file = "pyobjc_framework_MetricKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5ff9c29a6dda4a19dbdf40e513f06b00bb8f032d2b98655b030fc3a35e71c2d7"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-mlcompute" +version = "10.0" +description = "Wrappers for the framework MLCompute on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MLCompute-10.0.tar.gz", hash = "sha256:1ffbeeb3f4850c1ffada9b253afd2d4fe4448e0e52861701e1c5ab6a56961526"}, + {file = "pyobjc_framework_MLCompute-10.0-py2.py3-none-any.whl", hash = "sha256:8ba3eba33549a22acfdf589818ede36f65031425c6968eb193a9dad143d3cc64"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-modelio" +version = "10.0" +description = "Wrappers for the framework ModelIO on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ModelIO-10.0.tar.gz", hash = "sha256:1629db056d3bebdd79c582637e48c9da5c5aa76a073439dcb3820e00e3f75227"}, + {file = "pyobjc_framework_ModelIO-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:36510ff0567310da48bc5cdd8b8f63e2bf158eb29b598d7b40e26189546c984e"}, + {file = "pyobjc_framework_ModelIO-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:91aa11bc6005b98c73541eebbdb69caf71b5ef4a9d1da032a58ef90b043e4b80"}, + {file = "pyobjc_framework_ModelIO-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:91a80adda076760390ea00cd39d861384455f794673924a923fa7957e4225c52"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-multipeerconnectivity" +version = "10.0" +description = "Wrappers for the framework MultipeerConnectivity on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MultipeerConnectivity-10.0.tar.gz", hash = "sha256:c2641b9c6d2eb2dccd3c69417f5291bd141a23afc3835f7a7822a8cfa45a1153"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:71cbe835d30a81ec7fcdd3706344dcc5351af4eaa9cf17dada28b88023549953"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:feeb828c8005bd9a941ec437ad1c4dc67843a110d9f80e33c6426b0178faef8e"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e3ef039148b7215c3886636a65baad7dbf1083f1c6dc09c782632085f5efeeec"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-naturallanguage" +version = "10.0" +description = "Wrappers for the framework NaturalLanguage on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NaturalLanguage-10.0.tar.gz", hash = "sha256:00b055806a0fe096c8d9e2af0f610951ef0fc892d2f496301f2bda794bca781a"}, + {file = "pyobjc_framework_NaturalLanguage-10.0-py2.py3-none-any.whl", hash = "sha256:8924630ff802486dd16a426d75fddfc7e6cd917fecd5ff3902b84107051130cb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-netfs" +version = "10.0" +description = "Wrappers for the framework NetFS on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NetFS-10.0.tar.gz", hash = "sha256:31becccbbff5cb4336b736e97f61f92d5df0c40b5e3be7d4e5964527e1b1e3b4"}, + {file = "pyobjc_framework_NetFS-10.0-py2.py3-none-any.whl", hash = "sha256:1ad29eb81bd4774259377a716fa3dd8b3e105e5f8021e295f640a8e036847cc0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-network" +version = "10.0" +description = "Wrappers for the framework Network on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Network-10.0.tar.gz", hash = "sha256:4e92b1271f999dea0297a844cc101b5c0c908168428d77caab054d25ca8e4e69"}, + {file = "pyobjc_framework_Network-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2402ddcac58735333420a4e467eb415df59f54cf893cd4401f81cce64449dd77"}, + {file = "pyobjc_framework_Network-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:93327d81b58294065f6d2f6db746e992c218cab94b992f5c778dd15a4ecc6f51"}, + {file = "pyobjc_framework_Network-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:53fa04ba5e0ecdc40dd0139074740d4bc3459c829ef550b89141e4cc71562c5a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-networkextension" +version = "10.0" +description = "Wrappers for the framework NetworkExtension on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NetworkExtension-10.0.tar.gz", hash = "sha256:cd17420c9763c240343fcfedaddff11db8c0f4f1b54c060c24d6f414234d6b5d"}, + {file = "pyobjc_framework_NetworkExtension-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:fa42c5e57247827647fcbc63341f38799403dba28c5e1ebc68fae57b84727f10"}, + {file = "pyobjc_framework_NetworkExtension-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:301bb43a39069af6e28dfd73de4dee48bd87e0c0473d605b58ab92ce7d1cface"}, + {file = "pyobjc_framework_NetworkExtension-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:325b67c507d69f0d97ad5e612bea68426f5523fae2e8f39792a8c46a88d2067d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-notificationcenter" +version = "10.0" +description = "Wrappers for the framework NotificationCenter on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NotificationCenter-10.0.tar.gz", hash = "sha256:64f85eaea8e8811afbfa265e56d3d07ab8b0e57a4a7b5b33a9f72a50a3ede83b"}, + {file = "pyobjc_framework_NotificationCenter-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d06c29f7fd1f0c8b0f7867c80475dfb4d8df491cb92a48932befe47810b6c440"}, + {file = "pyobjc_framework_NotificationCenter-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:95960b70654160083e93cb7c47663a045a68b00ce457e408046062c705b2056e"}, + {file = "pyobjc_framework_NotificationCenter-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1b9534f6a07a6cfeb1a5b5585178a58fb544e2f0f58935afec5ba668b7567170"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-opendirectory" +version = "10.0" +description = "Wrappers for the framework OpenDirectory on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OpenDirectory-10.0.tar.gz", hash = "sha256:94e0313910b343a1e9738a7a006a1651f4d2995125f743576535ecca9cbb141f"}, + {file = "pyobjc_framework_OpenDirectory-10.0-py2.py3-none-any.whl", hash = "sha256:a58211a1cecb4e1d52377dfe60eecdd4579a3dfc44ff50b92cc3bb123a413189"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-osakit" +version = "10.0" +description = "Wrappers for the framework OSAKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OSAKit-10.0.tar.gz", hash = "sha256:eaf442ca46219c19f14d3f12612a37325ab7d2a9b5f67eef64a289877500ea75"}, + {file = "pyobjc_framework_OSAKit-10.0-py2.py3-none-any.whl", hash = "sha256:b87bb4ac330da116c33ffefa2da0b7946ac8a840150da848cafd7fff19f7e674"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-oslog" +version = "10.0" +description = "Wrappers for the framework OSLog on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OSLog-10.0.tar.gz", hash = "sha256:3a169df2fe5fdbd6ca8db28e5c51d89f8759b369636ea7cc2672cde11f4a09fb"}, + {file = "pyobjc_framework_OSLog-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1c2ac445b647edf4e2e925efc4d2471f9a6952dcb2d5929f1e570946941f622d"}, + {file = "pyobjc_framework_OSLog-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:05eaf07e2d4b5c51a9859d0f5e170f51975268ee14782fa626c51b19740d0e68"}, + {file = "pyobjc_framework_OSLog-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:432861f5bd5ad45b119c1327cb17d1feb5e2a5700d753a79d0abdc49a2123496"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreMedia = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-passkit" +version = "10.0" +description = "Wrappers for the framework PassKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PassKit-10.0.tar.gz", hash = "sha256:da2c5b12c341e2e826b5345798854219966c7bef4bfdb8df306816877df22abb"}, + {file = "pyobjc_framework_PassKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bb315a01d67865f06c751ca4f590c7340c27847a51ebdb645bd31dc48a07f478"}, + {file = "pyobjc_framework_PassKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2c7c4b12957ac615759e385d0baf02e82f5218049369e60a3e74cbd97f7730a0"}, + {file = "pyobjc_framework_PassKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:78d82720f05df6447579495de6e1cca17a18ce203b670fb67b8d8b7317fe2b46"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-pencilkit" +version = "10.0" +description = "Wrappers for the framework PencilKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PencilKit-10.0.tar.gz", hash = "sha256:68f19a4d9ebab2d002667dce8820bf200d36cc8e1a2351a47f44f3e1f99bb194"}, + {file = "pyobjc_framework_PencilKit-10.0-py2.py3-none-any.whl", hash = "sha256:162bd4797749247e34414ddfb91336b97ff8c31fa79abe27a2885188cbe3fed8"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-phase" +version = "10.0" +description = "Wrappers for the framework PHASE on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PHASE-10.0.tar.gz", hash = "sha256:9141baca910edc8935a5f22b82444e3b5a001c9275562752f13adce034529377"}, + {file = "pyobjc_framework_PHASE-10.0-py2.py3-none-any.whl", hash = "sha256:d16c38d58065d22c9b688f0fa753fc0a32d9a24bcda23830dab7fd34105c5432"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-AVFoundation = ">=10.0" + +[[package]] +name = "pyobjc-framework-photos" +version = "10.0" +description = "Wrappers for the framework Photos on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Photos-10.0.tar.gz", hash = "sha256:b284e2ede913081570f862fde99fe22c5f254a36b53105fedad4ce66d4dd93af"}, + {file = "pyobjc_framework_Photos-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5809848be530e5f49fbfbe5c6de6255ebe9127da94eab637a8c4f68cef263c80"}, + {file = "pyobjc_framework_Photos-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2058ced6eccf6ffef45f67175d8486d84b07892056338a71aca609f961b807db"}, + {file = "pyobjc_framework_Photos-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:567bbf0287700a32dd3baa0d94da63dd4eece13f622e12ee011a269704880d03"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-photosui" +version = "10.0" +description = "Wrappers for the framework PhotosUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PhotosUI-10.0.tar.gz", hash = "sha256:aa521325e7c86d1c739306cd5a14f3f7f69f5db654dc8884f1630001ad72aa7c"}, + {file = "pyobjc_framework_PhotosUI-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9234c231d6e970fabf1b609933e362bade59d6fd40ebfba0b0bfefec7603308"}, + {file = "pyobjc_framework_PhotosUI-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6812342b1588e4675afacff5e6af1376e1569784081699e7c2e865c206f78b27"}, + {file = "pyobjc_framework_PhotosUI-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:794972421c21f541898a05fb3ffc7efc027c15b5f3b19af9cafd424cb5c29613"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-preferencepanes" +version = "10.0" +description = "Wrappers for the framework PreferencePanes on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PreferencePanes-10.0.tar.gz", hash = "sha256:4e25e9f192252e4d76e9c68fbeae6a7bf832f0d3ab8f18561c65689f344b70c8"}, + {file = "pyobjc_framework_PreferencePanes-10.0-py2.py3-none-any.whl", hash = "sha256:992765158f0cae73957178109338bde94bbac5c91ca6e1ada884c3dc43868e18"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-pubsub" +version = "10.0" +description = "Wrappers for the framework PubSub on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PubSub-10.0.tar.gz", hash = "sha256:b5632265d86bb114444ce56923633c45be930d3ff224fc76390d2711742df0f8"}, + {file = "pyobjc_framework_PubSub-10.0-py2.py3-none-any.whl", hash = "sha256:7d04a4594c232650f4caf3dbb7d3e6e9c7ec1e87847c147bb4f1c5d412efe5ce"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-pushkit" +version = "10.0" +description = "Wrappers for the framework PushKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PushKit-10.0.tar.gz", hash = "sha256:54e0b9f3374ba26bdd2c08993080862e7dfc5ccd5c74ad2d5c1c4f9c4c0caa32"}, + {file = "pyobjc_framework_PushKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8fa158ef13bb6a6e0d5cbb25b60cc7f82ed8f048fccedbe6f38df5d27ae8ff26"}, + {file = "pyobjc_framework_PushKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f50e93124ed0df1c9d3c83f24d877bc286c8a3005fc59b85864c4675cfe078a8"}, + {file = "pyobjc_framework_PushKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:014a293a5d11f4213c222fb10284094eed85a37ce8dc5543a5b5e68ed3fb628d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-quartz" +version = "10.0" +description = "Wrappers for the Quartz frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Quartz-10.0.tar.gz", hash = "sha256:ff7c938d9c8adff87d577d63e58f9be6e4bc75274384715fa7a20032a1ce8b0e"}, + {file = "pyobjc_framework_Quartz-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f06be24fdd2112c9f5b96ede54ec48ad7623e107b85ebbd5b4155d0b1da4d45f"}, + {file = "pyobjc_framework_Quartz-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:52848a5e283a508c6895a73cb0c950bd4dca9b1186b70dd73ddc8f436d64fd42"}, + {file = "pyobjc_framework_Quartz-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0cc89890de411a341e90d2c4148831b6d241fca66e734b5470d27869c04e33c"}, + {file = "pyobjc_framework_Quartz-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc8d6edcdb0bb3dab4dbe6e6a6d420c28aa0caca53715a3e49d7f299601a723f"}, + {file = "pyobjc_framework_Quartz-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:c4b03fade2c4aff2682cd5eae8469f3f15e089c7dd09641e24e5b54d015edfae"}, + {file = "pyobjc_framework_Quartz-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6975be7f776ad4c3a41655b90d67e45a9c7fa2d715b189d8599e8d227f790280"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-quicklookthumbnailing" +version = "10.0" +description = "Wrappers for the framework QuickLookThumbnailing on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-QuickLookThumbnailing-10.0.tar.gz", hash = "sha256:13858592b6cd03c26e0020de47721d74ba992f44e9030ef70f47a99d7660b71c"}, + {file = "pyobjc_framework_QuickLookThumbnailing-10.0-py2.py3-none-any.whl", hash = "sha256:7572750f84477bd9ef4bd6d1fbb88dd3fb3b39567b004307b347b97de861163a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-replaykit" +version = "10.0" +description = "Wrappers for the framework ReplayKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ReplayKit-10.0.tar.gz", hash = "sha256:83a95c5c95d1a1af731fc9fba71e194d13ceded46799422908d8f95376a4a5ac"}, + {file = "pyobjc_framework_ReplayKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:dea16e14c493a6dc976a4d762fd09b5713dac70f49390df07ac0457dc4fdc759"}, + {file = "pyobjc_framework_ReplayKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1b87a5400c05eba3734bfdd5110a9ef186b98aae0c36a1de3a6dacf3802f5c4f"}, + {file = "pyobjc_framework_ReplayKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e2edfcdf3140bf111e21471fbfdc8fc105e3475c342b1ecd240a8d3e8b8ac368"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-safariservices" +version = "10.0" +description = "Wrappers for the framework SafariServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SafariServices-10.0.tar.gz", hash = "sha256:7f7a477b77b17161e22bdddc8a16fb3000eeccc430a730cb144e1a84a5f6e4e3"}, + {file = "pyobjc_framework_SafariServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:60edbbb667e26654a197f3e72528094f63a1aafb20f73bbd4b182e988275edd2"}, + {file = "pyobjc_framework_SafariServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed51d96673f579e3f4379e1130f3e2cde1c427f916e3172332acd948314f3b7f"}, + {file = "pyobjc_framework_SafariServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:91e336dce2622fdf87d5da8de4a43746ca13f3e85410102da067e1bf0dc4eecb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-safetykit" +version = "10.0" +description = "Wrappers for the framework SafetyKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SafetyKit-10.0.tar.gz", hash = "sha256:8f6408bdd4ba749d1840700e1a7f1719a5068ae15a2dfdab9d533333b2adda20"}, + {file = "pyobjc_framework_SafetyKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:18d26db21af00838d907527ec1edfcd59217561cc86ed2cfa4fad4788f58ecf7"}, + {file = "pyobjc_framework_SafetyKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:208285c0460a0ed9459c2c0c636fee941a3f2644d07832533140f22de3cc9f11"}, + {file = "pyobjc_framework_SafetyKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b94d9f17bcaef877dad6024b6990265a9e6d9152bd7f734539bf5da9e95e2a91"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-scenekit" +version = "10.0" +description = "Wrappers for the framework SceneKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SceneKit-10.0.tar.gz", hash = "sha256:205a6706ffe271f3961255f1c55ab60b47d797c7a4154a5c9cc0a3b263c433d6"}, + {file = "pyobjc_framework_SceneKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:aabc098a6870b34af54be2aaf0010f1050d3a30e8e86a478b48f7cc2046a5bee"}, + {file = "pyobjc_framework_SceneKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b4fd50030066bf71e24c793a3d8200b75051f9b6d6daa5b10eb92663e08f0b64"}, + {file = "pyobjc_framework_SceneKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:326a588c89801f96fc10a7629446336f385f5615d03c0cce10bb18824ac5c021"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-screencapturekit" +version = "10.0" +description = "Wrappers for the framework ScreenCaptureKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenCaptureKit-10.0.tar.gz", hash = "sha256:d6abaccf2620d01af9bcb408fc47713f813839a35899caea8fa0a96a147597b9"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4064835492904889290d450b5f4f7b8147235620be0f2b8c455a8ca03e532779"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3b80a295b2545b0da492f4f5b8df5b62dc3e1b69141fe1b8982901839635d6e1"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d963acbc24f1d2e0bcc3d0a4d5515dc680259ef7c3b6e80159c82a05774c2862"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c9aa29cb9628d8d1afdd7c8d650ccf90c228aabded792058ca82ee72682c44f"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:f69c623e1e11068c1af01c7f2e359941e1287b7e840b4cd93a9de2eddcd608aa"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eefc981b906e80b6b51694472d9b573f547d6633c7e9e2f160ad464fbb2b36ab"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-CoreMedia = ">=10.0" + +[[package]] +name = "pyobjc-framework-screensaver" +version = "10.0" +description = "Wrappers for the framework ScreenSaver on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenSaver-10.0.tar.gz", hash = "sha256:84b658c81469305f29aaad61ac29aaad4db27ef9e9b8a13568ddb3a6bfbb902d"}, + {file = "pyobjc_framework_ScreenSaver-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6f0e4a278349997ed1b36ae27ebbbeb18d4a8e766f68c65749e861e4388a5f5"}, + {file = "pyobjc_framework_ScreenSaver-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cd0d3104140be9ef10becae43d222ff0904e4cb655cbe83441f51898e20164e0"}, + {file = "pyobjc_framework_ScreenSaver-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ad0826d5cb11e2615c66e1e0576a5a23f35b6b83c05625efca5ac4ce31e06b33"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-screentime" +version = "10.0" +description = "Wrappers for the framework ScreenTime on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenTime-10.0.tar.gz", hash = "sha256:77d927c7aec657902ef5bdc1cb4be44bc3cd3d1ea51c70f66b8b891b9f97e8ff"}, + {file = "pyobjc_framework_ScreenTime-10.0-py2.py3-none-any.whl", hash = "sha256:800cbb0f5e1bc2ef04e1328e6263b5ec7585538e16989265a3fa8c33957744ed"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-scriptingbridge" +version = "10.0" +description = "Wrappers for the framework ScriptingBridge on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScriptingBridge-10.0.tar.gz", hash = "sha256:dc8ee394c84caabef9512eaf784ba91459b9560556da5fd5762aa7a6ef5e4612"}, + {file = "pyobjc_framework_ScriptingBridge-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:cc1d6d40280b183b34ca24b92d28dbe9ad14e351a53e60262209e44b7da1c98c"}, + {file = "pyobjc_framework_ScriptingBridge-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d3a236e10ef6cdd6a57954950cde0dd4833e0f041b8807da2e14e44645b256bb"}, + {file = "pyobjc_framework_ScriptingBridge-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:632cbf4fd887e3cfc17c7b12ff68879e75f3930d0c54600ab72b41ed2d828901"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-searchkit" +version = "10.0" +description = "Wrappers for the framework SearchKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SearchKit-10.0.tar.gz", hash = "sha256:953ade5f21aed098db366673885cd2e8a3e94574e0fb6e0ccebb063ffc8559ed"}, + {file = "pyobjc_framework_SearchKit-10.0-py2.py3-none-any.whl", hash = "sha256:21921a722f3f1e3868ae38c4582c6d51bad35b13290e90cca62802a477d7f8d1"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-CoreServices = ">=10.0" + +[[package]] +name = "pyobjc-framework-security" +version = "10.0" +description = "Wrappers for the framework Security on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Security-10.0.tar.gz", hash = "sha256:89837b93aaae053d80430da6a3dbd6430ca9d889aa43c3d53ed4ce81afa99462"}, + {file = "pyobjc_framework_Security-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:257abf4821df4a9824f970df7b27acd05c8b7a544c424ca29c63c1bf963b0011"}, + {file = "pyobjc_framework_Security-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e4917cfeca742b790a8f5053b39051be83a132e85f5ad9af2cd3a31527960143"}, + {file = "pyobjc_framework_Security-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a7d9cae84018bcb6ff2967a9cd158b2298e0c5fd95cf6deef12b4b44464e1797"}, + {file = "pyobjc_framework_Security-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71522a2adc3b30c28508156a510b5b8796d5f6ad003bd35b4d86c121bf4f7957"}, + {file = "pyobjc_framework_Security-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:be52243da7a143e898b8e726201140f4be0bd5803b90e56b22d2cc6ad1edde0f"}, + {file = "pyobjc_framework_Security-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ef948582c47593895e27be1a1401d96b19a8edcbed223fa9cf3185345a2bc117"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-securityfoundation" +version = "10.0" +description = "Wrappers for the framework SecurityFoundation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SecurityFoundation-10.0.tar.gz", hash = "sha256:9871cc1cb7e15b694c7c406d8125acbe990b28c6b15d5833df53a38906836342"}, + {file = "pyobjc_framework_SecurityFoundation-10.0-py2.py3-none-any.whl", hash = "sha256:c7c8bc25d3297eb6c4684ef0c9680b619a1966ddc0cfd33a2122a46cd7963f57"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Security = ">=10.0" + +[[package]] +name = "pyobjc-framework-securityinterface" +version = "10.0" +description = "Wrappers for the framework SecurityInterface on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SecurityInterface-10.0.tar.gz", hash = "sha256:fb3e660b7e1e2054597a87237a885ca62212c9889702bd634d34792d84fcc9ab"}, + {file = "pyobjc_framework_SecurityInterface-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:997a018d6f550ccb82e33bd33317bc586e2911ce1645533f7d16f27973d1d439"}, + {file = "pyobjc_framework_SecurityInterface-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:573d31308465ad1842352e982f7a64cfcaf9d599fe0765ce02e66d0c452a172a"}, + {file = "pyobjc_framework_SecurityInterface-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:06567ae02e1757070bdd3dd21bb0ff3b214b6fff4635d7b0b575f304b02386d4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Security = ">=10.0" + +[[package]] +name = "pyobjc-framework-sensitivecontentanalysis" +version = "10.0" +description = "Wrappers for the framework SensitiveContentAnalysis on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SensitiveContentAnalysis-10.0.tar.gz", hash = "sha256:212ebb393b7e8a7d9eebd9025a0cc96e20edd0934e570cd57fd0a8a7e5e6b860"}, + {file = "pyobjc_framework_SensitiveContentAnalysis-10.0-py2.py3-none-any.whl", hash = "sha256:99262f5d8a049973531a44113e9157874bba274ed8541b8b778878c664472042"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-servicemanagement" +version = "10.0" +description = "Wrappers for the framework ServiceManagement on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ServiceManagement-10.0.tar.gz", hash = "sha256:0a578e879adf126b4997feca02b6aebee8fc92ef96e4f1d5d76b53860f8b14fa"}, + {file = "pyobjc_framework_ServiceManagement-10.0-py2.py3-none-any.whl", hash = "sha256:a27685c393c1c91b42c5701e0e18326b58d50f0b0c2a194190bc3078d53b5df1"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-sharedwithyou" +version = "10.0" +description = "Wrappers for the framework SharedWithYou on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SharedWithYou-10.0.tar.gz", hash = "sha256:2d19cd38d54c3c5e85488e6f6264f83638984810d9d1601916abddd0984e6b8d"}, + {file = "pyobjc_framework_SharedWithYou-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:706d0c17ad64c22da16eeb1e10a677ea29712164e7f517ac14d866148f2ba437"}, + {file = "pyobjc_framework_SharedWithYou-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:496c9bd0403e1a9896a3cf21d1ae5f1bbbeaefc94322f1063626d2c489b87b8b"}, + {file = "pyobjc_framework_SharedWithYou-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5fa3394b4cf289c798eebb6ee90295221ef2b02b6bd315ac5bd87d9b0ed8b339"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-SharedWithYouCore = ">=10.0" + +[[package]] +name = "pyobjc-framework-sharedwithyoucore" +version = "10.0" +description = "Wrappers for the framework SharedWithYouCore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SharedWithYouCore-10.0.tar.gz", hash = "sha256:b07e79716e496270a4a84bd2645c1a1dc48b557ff3faaf268c8d5d4c79de9ede"}, + {file = "pyobjc_framework_SharedWithYouCore-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:1e13c7b0c057b2467b9a2417066257266570ae6932032aa0eb1e796790ba85d1"}, + {file = "pyobjc_framework_SharedWithYouCore-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:43e730bd17023c51a0895ec3678856662764eebb13c5a19e9d775fc1ee3e5c6c"}, + {file = "pyobjc_framework_SharedWithYouCore-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a029e5ba6e1123baff788888ffb49a4afd83e95320fdcf377423992415c1f037"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-shazamkit" +version = "10.0" +description = "Wrappers for the framework ShazamKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ShazamKit-10.0.tar.gz", hash = "sha256:f5a84113307bac14460abf522ed2e5fc99c5ac1816e652d2bdb437623ada3429"}, + {file = "pyobjc_framework_ShazamKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:71c2a0927df93276abe299ee49d256a76ce7b32015825085dd7cc572d82cf369"}, + {file = "pyobjc_framework_ShazamKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:428e55cb4115eb38e45994a9f339f7909c2f3d62c40aa37478e772e79e455639"}, + {file = "pyobjc_framework_ShazamKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e8071f802b91d0312cd31cb888bb3de0388f01d7c975111c6cd80e571bd5609d"}, + {file = "pyobjc_framework_ShazamKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:266fdbea9491deb29aa34f08b26a9016d9823585348d4e2714acbb5bf133e4f3"}, + {file = "pyobjc_framework_ShazamKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:ce7a1b842dfe4af9e523ac5635297276810e85fc734be43d5a2ae0237b25acfa"}, + {file = "pyobjc_framework_ShazamKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d2694a3a242844f324a5983fbb8594a7087722308b3777c7cd6d2435387cdbd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-social" +version = "10.0" +description = "Wrappers for the framework Social on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Social-10.0.tar.gz", hash = "sha256:29c4d039b25a73d0499ae37d5eba9c30c12e68209cb85f1bdd94b78274421764"}, + {file = "pyobjc_framework_Social-10.0-py2.py3-none-any.whl", hash = "sha256:05d2cc1b62e2c1ffbe9ed8868e70fb846eb0f4d7157b87c8db77cd13bf0f2a92"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-soundanalysis" +version = "10.0" +description = "Wrappers for the framework SoundAnalysis on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SoundAnalysis-10.0.tar.gz", hash = "sha256:9a2db7edfb506aa296968abfa86f67534b1e02c726aa26c516750c04e7b21d12"}, + {file = "pyobjc_framework_SoundAnalysis-10.0-py2.py3-none-any.whl", hash = "sha256:b2de7dc4ee724fc7940a777ee50aa8b96b836aade84a408737bacf8818b9bde5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-speech" +version = "10.0" +description = "Wrappers for the framework Speech on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Speech-10.0.tar.gz", hash = "sha256:ffcd35855246432f02ebd96e6eb97da319f3ff108d8b62266e83da9c5eec8497"}, + {file = "pyobjc_framework_Speech-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3664cebcb74d48a131fe02bf2716d72378798ed504fad85e72661f2923a8cd9"}, + {file = "pyobjc_framework_Speech-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6755395bb80b28b2ccf4f02e7a1d850c80e5bf8e590f1359daa2b55a7ec3d108"}, + {file = "pyobjc_framework_Speech-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0ab33eecad4a053a281c6f71f9c6b70d0ad19706887cee3e30509df75cfdbe8d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-spritekit" +version = "10.0" +description = "Wrappers for the framework SpriteKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SpriteKit-10.0.tar.gz", hash = "sha256:c9db030232e251426575674bbe61b7bdb1cfc4a587a0a1e0d1a59e704658dc30"}, + {file = "pyobjc_framework_SpriteKit-10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4dfaa8a541f60a3c56b38029e4da154f2672cc231c70ceeb558c18af423f822"}, + {file = "pyobjc_framework_SpriteKit-10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5582ef597d381fb9113099bbd07065c565d9db966193f4807327cd09363043b4"}, + {file = "pyobjc_framework_SpriteKit-10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:71042fd8986d0a6544588b42198b8840fe9afd335a3329d4d1dfa4b4ae432327"}, + {file = "pyobjc_framework_SpriteKit-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c83378276161c3ed043fd8fea6d2d8fcfcb74fb30dbb77a13be6bcdd9914496d"}, + {file = "pyobjc_framework_SpriteKit-10.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:93c19908a3aaf1cbf68192f3f312885a2f829dee349d015162d301b379f8046a"}, + {file = "pyobjc_framework_SpriteKit-10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2884623c261a3ae5f12fcad8c30c15e631e5cd86b7f16581b750b07ee31005f3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-storekit" +version = "10.0" +description = "Wrappers for the framework StoreKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-StoreKit-10.0.tar.gz", hash = "sha256:5835de40067e2ea4374babb41da4ebc0bbe087b770c352bdababfa6871e9590a"}, + {file = "pyobjc_framework_StoreKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e6c607c76edd85444eacf81da5d1823deb5cd1c312f4544d10299f05ae99f87"}, + {file = "pyobjc_framework_StoreKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:df9186ce3ccac15050b3bb90738d9d2abf6617c793d738ac55b95908578c468e"}, + {file = "pyobjc_framework_StoreKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c235266a035b9f3ef9575cd71dda0303e85af90b166cd73466fb668e180c72da"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-symbols" +version = "10.0" +description = "Wrappers for the framework Symbols on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Symbols-10.0.tar.gz", hash = "sha256:16aa2273cb28af944de81039df86bdef04df4b3cf3c776d84d2520fb550a1b6d"}, + {file = "pyobjc_framework_Symbols-10.0-py2.py3-none-any.whl", hash = "sha256:fd1bfc2958d860aef26b15994714abcbb6b3340eda2c67df31c12df0740a661f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-syncservices" +version = "10.0" +description = "Wrappers for the framework SyncServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SyncServices-10.0.tar.gz", hash = "sha256:3060a5b66c42a276b3a5765f7c41fe6a80491685977b0f78b67ef2e8f2325673"}, + {file = "pyobjc_framework_SyncServices-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:398c95d5b306eddc8534fa7411092d39f8d4aeafa68de7349e890ab7f6d8bc3c"}, + {file = "pyobjc_framework_SyncServices-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:05cbef4425859dd372545d238cc868d53edc1265be752ebcb73a311d1a2fd9e4"}, + {file = "pyobjc_framework_SyncServices-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3a89a3b4a627fddcda323c8879e969742a99dc0c312ab4bc04c167ca3d19be5b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreData = ">=10.0" + +[[package]] +name = "pyobjc-framework-systemconfiguration" +version = "10.0" +description = "Wrappers for the framework SystemConfiguration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SystemConfiguration-10.0.tar.gz", hash = "sha256:f9ab1759933c77688615810f8278519158273a658f11fc3d75a1a2446fd0f774"}, + {file = "pyobjc_framework_SystemConfiguration-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e64f7a0011ad4a0f86302bd243ada159dfbc25525cfd48270d230fadd24f7dfa"}, + {file = "pyobjc_framework_SystemConfiguration-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:106c98eff3552611fa9bf456f162a3c578958e6c8bea3cb5cfc9478f3cc09005"}, + {file = "pyobjc_framework_SystemConfiguration-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:df000aee4b8350fa413d00370309d0626e503d2773a9882b64b521105e795d8f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-systemextensions" +version = "10.0" +description = "Wrappers for the framework SystemExtensions on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SystemExtensions-10.0.tar.gz", hash = "sha256:0c71c2d3db048fd55d931137402e9d0550178f65aacc6597538d4c1c9debb729"}, + {file = "pyobjc_framework_SystemExtensions-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b1908397e50d70abe618383dd4b205fd3d5e8ddd3b9b7ff5d2dd6b330530296a"}, + {file = "pyobjc_framework_SystemExtensions-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6dcb3ca8c0598e35a6f7332f4ced3b83560d10e01254f0db76beaee68c1211c8"}, + {file = "pyobjc_framework_SystemExtensions-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4967721a4ba3d8d5ea812d31f0494fe2f876a26e4eae929fcb3681e062349623"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-threadnetwork" +version = "10.0" +description = "Wrappers for the framework ThreadNetwork on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ThreadNetwork-10.0.tar.gz", hash = "sha256:8d014eacbd195367f93c24b1cf08690728f42f20b33d5f1fdc38bd6b114b1f13"}, + {file = "pyobjc_framework_ThreadNetwork-10.0-py2.py3-none-any.whl", hash = "sha256:f4f24ad1457e2a89c80f3aa5133e8015e67cbd0e2654d8f08abe0f4690eb7cb3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-uniformtypeidentifiers" +version = "10.0" +description = "Wrappers for the framework UniformTypeIdentifiers on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UniformTypeIdentifiers-10.0.tar.gz", hash = "sha256:60254b6d3bce2cc79fee6044ebce828a6c7715b218710e0b5cf3a896bba324ea"}, + {file = "pyobjc_framework_UniformTypeIdentifiers-10.0-py2.py3-none-any.whl", hash = "sha256:04ddee19fcac2cb3f56c69a6a70fe889515d2f03cc2fcecfb5e414c5bf588032"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-usernotifications" +version = "10.0" +description = "Wrappers for the framework UserNotifications on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UserNotifications-10.0.tar.gz", hash = "sha256:d2646747d4ddbf9abb8e41937364ae074742449e2fd9d33b3138049ad686d555"}, + {file = "pyobjc_framework_UserNotifications-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:965d38c993efa23e0bb24b63d64bfbbf396172bd9846f397ad3c5b896645648f"}, + {file = "pyobjc_framework_UserNotifications-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0c7a535581b0c72c68b91333dc40c7341a05db666cd57ede812b316ed05534c9"}, + {file = "pyobjc_framework_UserNotifications-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3499b79aa5aa735dfe09a73e72f14313d93675c28769e59847ae98ba31114fc3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-usernotificationsui" +version = "10.0" +description = "Wrappers for the framework UserNotificationsUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UserNotificationsUI-10.0.tar.gz", hash = "sha256:50ee261a50798e8a9dfe5152f66ed18cf584b8fcb3ef9cb6283b61413d376a3a"}, + {file = "pyobjc_framework_UserNotificationsUI-10.0-py2.py3-none-any.whl", hash = "sha256:3732661248a507a61ec551846b5f03d136d719ee402a434f9e77cee044983d75"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-UserNotifications = ">=10.0" + +[[package]] +name = "pyobjc-framework-videosubscriberaccount" +version = "10.0" +description = "Wrappers for the framework VideoSubscriberAccount on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-VideoSubscriberAccount-10.0.tar.gz", hash = "sha256:14f2d6d644df7634d186ae0a64c68317018f3ef191d9ff78658c2bfd9bad394a"}, + {file = "pyobjc_framework_VideoSubscriberAccount-10.0-py2.py3-none-any.whl", hash = "sha256:d7616cc2302372211a415e5afb83f3b52b9582b2f1381ba83b0cf955180ca2ba"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-videotoolbox" +version = "10.0" +description = "Wrappers for the framework VideoToolbox on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-VideoToolbox-10.0.tar.gz", hash = "sha256:3c1d112ca55b56eee913697f044d69f5de6959a7503fd2fcb0822ebad45f7b6c"}, + {file = "pyobjc_framework_VideoToolbox-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:fa1f8442096aa52f6043a02f4ad5566bf3c8783a66e51fcd6f165de700e4244c"}, + {file = "pyobjc_framework_VideoToolbox-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ac6279db7a907d98e2e09ed75aafadfc3dc8662d9e3f0120b48d1e371bc9a9f8"}, + {file = "pyobjc_framework_VideoToolbox-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ff43e2138b5e4ae50ed1373ef436a4d0a9e9cc6b1a296e3687549e519fc73364"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreMedia = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-virtualization" +version = "10.0" +description = "Wrappers for the framework Virtualization on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Virtualization-10.0.tar.gz", hash = "sha256:6387103c8285fe1226f1f35583a11c3aa208d0fea994923cfb405413985cac91"}, + {file = "pyobjc_framework_Virtualization-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ffa8d1a1f588f38bf45b8631f6759ad2d8bb74e4c1c0409f33e99a68bf97b676"}, + {file = "pyobjc_framework_Virtualization-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:62592a97e29d6fb2e999ccfa109b03301e8d7bd91f957b1ddff44dd53afb4b94"}, + {file = "pyobjc_framework_Virtualization-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:95da5017b799c1443edf654cc8d3cbae29f71bb7924976a00c721043d8ccb0a6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyobjc-framework-vision" +version = "10.0" +description = "Wrappers for the framework Vision on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Vision-10.0.tar.gz", hash = "sha256:c78244e68b7601682f0940b3d205ca087db4177e3fcc17ee29ae6f0fa811f492"}, + {file = "pyobjc_framework_Vision-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b688e439129d06d7352924e531d5cc49badf499892272fb9e95c99539f941eb7"}, + {file = "pyobjc_framework_Vision-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76943a64cbb9cbdf06653e801e11b69ce721edccc76b6b86cddb027303b65244"}, + {file = "pyobjc_framework_Vision-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7fbb163e66a65e382123f7dc0056a525c3711e0650186ac4d05b09f21a403ca5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" +pyobjc-framework-CoreML = ">=10.0" +pyobjc-framework-Quartz = ">=10.0" + +[[package]] +name = "pyobjc-framework-webkit" +version = "10.0" +description = "Wrappers for the framework WebKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-WebKit-10.0.tar.gz", hash = "sha256:847a69aeeb2e743c5ff838628f3a0031e538de4e011e29df52272955ed0b11df"}, + {file = "pyobjc_framework_WebKit-10.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:98104c829ecc169fe4ffd0fe499bec21e5fec0aec1974b3edd1ffac1fca0db21"}, + {file = "pyobjc_framework_WebKit-10.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:30850ed65f411bd1d54d15ec4937d36856e1e390ea70878022d45c5a08f33aa0"}, + {file = "pyobjc_framework_WebKit-10.0-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:42936e1af1a4cf328ce05e3dcd56dc937f348e7971642c68d33128550b4cb169"}, +] + +[package.dependencies] +pyobjc-core = ">=10.0" +pyobjc-framework-Cocoa = ">=10.0" + +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyqt5" +version = "5.15.10" +description = "Python bindings for the Qt cross platform application toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyQt5-5.15.10-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:93288d62ebd47b1933d80c27f5d43c7c435307b84d480af689cef2474e87e4c8"}, + {file = "PyQt5-5.15.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:862cea3be95b4b0a2b9678003b3a18edf7bd5eafd673860f58820f246d4bf616"}, + {file = "PyQt5-5.15.10-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:b89478d16d4118664ff58ed609e0a804d002703c9420118de7e4e70fa1cb5486"}, + {file = "PyQt5-5.15.10-cp37-abi3-win32.whl", hash = "sha256:ff99b4f91aa8eb60510d5889faad07116d3340041916e46c07d519f7cad344e1"}, + {file = "PyQt5-5.15.10-cp37-abi3-win_amd64.whl", hash = "sha256:501355f327e9a2c38db0428e1a236d25ebcb99304cd6e668c05d1188d514adec"}, + {file = "PyQt5-5.15.10.tar.gz", hash = "sha256:d46b7804b1b10a4ff91753f8113e5b5580d2b4462f3226288e2d84497334898a"}, +] + +[package.dependencies] +PyQt5-Qt5 = ">=5.15.2" +PyQt5-sip = ">=12.13,<13" + +[[package]] +name = "pyqt5-qt5" +version = "5.15.11" +description = "The subset of a Qt installation needed by PyQt5." +optional = false +python-versions = "*" +files = [ + {file = "PyQt5_Qt5-5.15.11-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:109c418d221538751e4d7755a81c978ee31abbd65facb3f1f361dca74a1b758a"}, + {file = "PyQt5_Qt5-5.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3cccb21942eb49d21fb0193f28e3e8ae3d35395f158e8a9d4d58e23efa3a2ea7"}, +] + +[[package]] +name = "pyqt5-sip" +version = "12.13.0" +description = "The sip module support for PyQt5" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyQt5_sip-12.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-win32.whl", hash = "sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-win32.whl", hash = "sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-win32.whl", hash = "sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-win32.whl", hash = "sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-win32.whl", hash = "sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01"}, + {file = "PyQt5_sip-12.13.0.tar.gz", hash = "sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91"}, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9, <3.13" +content-hash = "bc9c1f3cdf8d32483fcb17c5505fcfd64a68f3b639827511fddddc591ea19047" diff --git a/dev/swerve_sim_keyboard/pyproject.toml b/dev/swerve_sim_keyboard/pyproject.toml new file mode 100644 index 0000000..7225917 --- /dev/null +++ b/dev/swerve_sim_keyboard/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "swerve_sim_keyboard" +version = "0.1.0" +description = "Modbot Swerve Simulation" +authors = ["Autoslug UCSC "] +readme = "README.md" + +[tool.poetry.dependencies] +python = ">=3.9, <3.13" +numpy = "^1.26.0" +matplotlib = "^3.8.0" +keyboard = "^0.13.5" +pyqt5 = "^5.15.9" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/dev/swerve_sim_keyboard/src/main.py b/dev/swerve_sim_keyboard/src/main.py new file mode 100644 index 0000000..8725b5e --- /dev/null +++ b/dev/swerve_sim_keyboard/src/main.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +import numpy as np +import matplotlib +import matplotlib.pyplot as plt + +matplotlib.use("Qt5Agg") +import keyboard + + +################################# +## IMPORTANT: DEPRECATED FILE +## See swerve_sim_joystick +################################# + + + + + +############################################## +### USER DEFINED FUNC ### +############################################## + + +def chassisStateToModuleStates(vel, omega, module_distances) -> list: + states = [] + + for module_distance in module_distances: + states.append(vel + omega * perpendicular(module_distance)) + + return np.array(states) + + +def perpendicular(vector): + return np.array([-vector[1], vector[0]]) + + +def normalizeModules(moduleStates): + max_speed = max([np.linalg.norm(vec) for vec in moduleStates]) + if max_speed <= 1: + return moduleStates + + new_states = [] + for state in moduleStates: + new_states.append(state / max_speed) + + return np.array(new_states) + + +def plot_vec(vector, origin=[0, 0], color="black"): + plt.quiver( + *origin, + vector[0], + vector[1], + color=color, + angles="xy", + scale_units="xy", + scale=1 + ) + + +################################## +### MAIN ### +################################## + +if __name__ == "__main__": + # module radius, i.e distance of modules from center + R = 5 + # dt, the delta time of the "animation" + DT = 0.01 + angle_1 = 3.0 / 6.0 * np.pi + angle_2 = 7.0 / 6.0 * np.pi + angle_3 = 11.0 / 6.0 * np.pi + modules = ( + np.array( + [ + [np.cos(angle_1), np.sin(angle_1)], + [np.cos(angle_2), np.sin(angle_2)], + [np.cos(angle_3), np.sin(angle_3)], + ] + ) + * R + ) + + plot_vec([modules[:, 0], modules[:, 1]], origin=[[0, 0, 0], [0, 0, 0]]) + + plt.pause(DT) + + omega = 0 + + while True: # making a loop + try: + # QUIT BY PRESSING Q ANYTIME + if keyboard.is_pressed("q"): + print("Q pressed, quitting...") + break + + # get keyboard inputs + left = 1 if keyboard.is_pressed("left") else 0 + right = 1 if keyboard.is_pressed("right") else 0 + up = 1 if keyboard.is_pressed("up") else 0 + down = 1 if keyboard.is_pressed("down") else 0 + + # good enough for testing, just very basic 8 directions + dir = ( + np.array([-1, 0]) * left + + np.array([1, 0]) * right + + np.array([0, 1]) * up + + np.array([0, -1]) * down + ) + + # if not zero (avoid divide by zero error) then make it a unit vector + if dir.sum() != 0: + dir = dir / np.linalg.norm(dir) + + # W and E to rotate + if keyboard.is_pressed("w"): + if omega < 6: + omega += 0.75 + elif keyboard.is_pressed("e"): + if omega > -6: + omega -= 0.75 + else: + if omega < 0.75 and omega > -0.75: + omega = 0 + elif omega > 0: + omega -= 0.75 + elif omega < 0: + omega += 0.75 + + # spin the modules by the raidans/sec * sec + angle_1 += omega * DT + angle_2 += omega * DT + angle_3 += omega * DT + modules = ( + np.array( + [ + [np.cos(angle_1), np.sin(angle_1)], + [np.cos(angle_2), np.sin(angle_2)], + [np.cos(angle_3), np.sin(angle_3)], + ] + ) + * R + ) + module_dirs = chassisStateToModuleStates(dir, omega / R, modules) + + module_dirs = normalizeModules(module_dirs) * 2 + + # i spent too much time writing these three lines of code fml + plt.xlim(-2 * R, 2 * R) + plt.ylim(-2 * R, 2 * R) + plt.gca().set_aspect("equal", adjustable="box") + + # [:,0] is fancy way to get all first elements of the lists + plot_vec([modules[:, 0], modules[:, 1]], origin=[[0, 0, 0], [0, 0, 0]]) + plot_vec( + [2 * module_dirs[:, 0], 2 * module_dirs[:, 1]], + origin=[modules[:, 0], modules[:, 1]], + color="r", + ) + + plot_vec(module_dirs[0] + module_dirs[1] + module_dirs[2], color="g") + + plt.pause(DT) + plt.clf() + + except Exception as error: + print("") diff --git a/dev/swerve_sim_keyboard/test/keyboard_test.py b/dev/swerve_sim_keyboard/test/keyboard_test.py new file mode 100644 index 0000000..129be15 --- /dev/null +++ b/dev/swerve_sim_keyboard/test/keyboard_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import keyboard as kb + +while True: # making a loop + try: + # QUIT BY PRESSING Q ANYTIME + if kb.is_pressed('q'): + print('Q pressed, quitting...') + break + + # get keyboard inputs + if kb.is_pressed('left'): + left = 1 + print("Pressed Left") + else: + left = 0 + + if kb.is_pressed('right'): + right = 1 + print("Pressed right") + else: + right = 0 + + if kb.is_pressed('up'): + up = 1 + print("Pressed up") + else: + up = 0 + + if kb.is_pressed('down'): + down = 1 + print("Pressed down") + else: + down = 0 + + except: + pass + + \ No newline at end of file From 6f2534635e3439673d6e72bca3759c25067390c1 Mon Sep 17 00:00:00 2001 From: brendanRose1 <57075608+brendanRose1@users.noreply.github.com> Date: Tue, 21 May 2024 16:21:03 -0700 Subject: [PATCH 56/65] merged encoder branch into swerve-final --- dev/encoder/CMakeLists.txt | 26 ++++++ dev/encoder/pico_sdk_import.cmake | 62 ++++++++++++ dev/encoder/quadrature_encoder.cpp | 145 +++++++++++++++++++++++++++++ dev/encoder/quadrature_encoder.pio | 141 ++++++++++++++++++++++++++++ 4 files changed, 374 insertions(+) create mode 100644 dev/encoder/CMakeLists.txt create mode 100644 dev/encoder/pico_sdk_import.cmake create mode 100644 dev/encoder/quadrature_encoder.cpp create mode 100644 dev/encoder/quadrature_encoder.pio diff --git a/dev/encoder/CMakeLists.txt b/dev/encoder/CMakeLists.txt new file mode 100644 index 0000000..fc8ee89 --- /dev/null +++ b/dev/encoder/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.12) +include(pico_sdk_import.cmake) +project(quadrature_encoder VERSION 1.0.0) + +add_executable(quadrature_encoder quadrature_encoder.cpp) + +set(PICO_CXX_ENABLE_EXCEPTIONS 1) + +pico_sdk_init() + +target_sources(quadrature_encoder PRIVATE quadrature_encoder.cpp) + +target_link_libraries(quadrature_encoder PRIVATE + pico_stdlib + hardware_pio + ) + +pico_generate_pio_header(quadrature_encoder ${CMAKE_CURRENT_LIST_DIR}/quadrature_encoder.pio) + +pico_add_extra_outputs(quadrature_encoder) + +# enable usb output, disable uart output +pico_enable_stdio_usb(quadrature_encoder 1) +pico_enable_stdio_uart(quadrature_encoder 0) + + diff --git a/dev/encoder/pico_sdk_import.cmake b/dev/encoder/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/dev/encoder/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/dev/encoder/quadrature_encoder.cpp b/dev/encoder/quadrature_encoder.cpp new file mode 100644 index 0000000..a1e3079 --- /dev/null +++ b/dev/encoder/quadrature_encoder.cpp @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "hardware/timer.h" + +#include "quadrature_encoder.pio.h" + +// +// ---- quadrature encoder interface example +// +// the PIO program reads phase A/B of a quadrature encoder and increments or +// decrements an internal counter to keep the current absolute step count +// updated. At any point, the main code can query the current count by using +// the quadrature_encoder_*_count functions. The counter is kept in a full +// 32 bit register that just wraps around. Two's complement arithmetic means +// that it can be interpreted as a 32-bit signed or unsigned value, and it will +// work anyway. +// +// As an example, a two wheel robot being controlled at 100Hz, can use two +// state machines to read the two encoders and in the main control loop it can +// simply ask for the current encoder counts to get the absolute step count. It +// can also subtract the values from the last sample to check how many steps +// each wheel as done since the last sample period. +// +// One advantage of this approach is that it requires zero CPU time to keep the +// encoder count updated and because of that it supports very high step rates. +// + +// 374 pulses per revolution (from the description at https://www.dfrobot.com/product-1462.html) +// 4x because the encoder has 2 sensors each w/ rising & falling edges, so each pulse results in +// 4 counts received (https://deltamotion.com/support/webhelp/rmctools/Controller_Features/Transducer_Basics/Quadrature_Fundamentals.htm) +const float ROT_PER_TICK = 1.0 / (4 * 374.0); +const float PULLEY_RATIO = 0.3185 / 1.528; +const float DEG_PER_ROT = 360.0; + +class Encoder +{ +public: + // Create an encoder. Reccommended NOT to use this class, use EncoderFactory::createEncoder() + // @param pinA the pin that encoder A channel is connected to, the B channel should connect to the next pin + // @param sm the state machine to keep track of the encoder, 0-3 + // @param which pio + // @param ratio the ratio by which to multiply encoder ticks + Encoder(uint pinA, uint sm, PIO pio, float ratio = 1.0, bool addProgram = true) + { + this->pio = pio; + this->sm = sm; + this->ratio = ratio; + + uint offset = 0; + + // we don't really need to keep the offset, as this program must be loaded + // at offset 0 + if (addProgram) + uint offset = pio_add_program(pio, &quadrature_encoder_program); + + quadrature_encoder_program_init(pio, sm, offset, pinA, 0); + } + + // updates the pos and velocity, call periodically. + // @param delta_time the time, in miliseconds, since last calling update + void update(int delta_time) + { + pos = quadrature_encoder_get_count(pio, sm) * ratio * DEG_PER_ROT; + velocity = ((prev_pos - pos) / delta_time) * 1000; + prev_pos = pos; + } + + // get position of wheel in ticks, multiplied by any provided ratio. resets on init. + // update() must be called periodically for this to be accurate + float get_pos() + { + return pos; + } + + // get velocity of wheel in ticks per second, multiplied by any provided ratio. + // update() must be called periodically for this to be accurate + float get_velocity() + { + return velocity; + } + +private: + float prev_pos, pos; + float velocity; + float ratio; + PIO pio; + uint sm; +}; + +class EncoderFactory +{ +public: + // Create an encoder, automatically configuring the state machine and pio. + // @param pinA the A encoder channel, the B channel should be connected to the next pin + // @param ratio the ratio by which to multiply encoder outputs. ratio of 1 results in tick / sec + static Encoder createEncoder(uint pinA, float ratio = 1.0) + { + if (encoder_count > 7) + { + throw std::out_of_range("reached encoder limit of 8"); + } + + uint sm = encoder_count % 4; + PIO pio = encoder_count < 4 ? pio0 : pio1; + + encoder_count++; + return Encoder(pinA, sm, pio, ratio, sm == 0); + } + +private: + static uint encoder_count; +}; + +uint EncoderFactory::encoder_count = 0; + +int main() +{ + stdio_init_all(); + + // Base pin to connect the A phase of the encoder (yellow wire). + // The B phase must be connected to the next pin (green wire) + const uint PIN_STEER = 14; + const uint PIN_DRIVE = 16; + + Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); + Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK * DEG_PER_ROT); + + while (1) + { + steer.update(20); + drive.update(20); + + printf("steer position %8f, velocity %6f\n", steer.get_pos(), steer.get_velocity()); + printf("drive position %8f, velocity %6f\n", drive.get_pos(), drive.get_velocity()); + sleep_ms(20); + } +} diff --git a/dev/encoder/quadrature_encoder.pio b/dev/encoder/quadrature_encoder.pio new file mode 100644 index 0000000..8b1e618 --- /dev/null +++ b/dev/encoder/quadrature_encoder.pio @@ -0,0 +1,141 @@ +; +; Copyright (c) 2023 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program quadrature_encoder + +; the code must be loaded at address 0, because it uses computed jumps +.origin 0 + + +; the code works by running a loop that continuously shifts the 2 phase pins into +; ISR and looks at the lower 4 bits to do a computed jump to an instruction that +; does the proper "do nothing" | "increment" | "decrement" action for that pin +; state change (or no change) + +; ISR holds the last state of the 2 pins during most of the code. The Y register +; keeps the current encoder count and is incremented / decremented according to +; the steps sampled + +; the program keeps trying to write the current count to the RX FIFO without +; blocking. To read the current count, the user code must drain the FIFO first +; and wait for a fresh sample (takes ~4 SM cycles on average). The worst case +; sampling loop takes 10 cycles, so this program is able to read step rates up +; to sysclk / 10 (e.g., sysclk 125MHz, max step rate = 12.5 Msteps/sec) + +; 00 state + JMP update ; read 00 + JMP decrement ; read 01 + JMP increment ; read 10 + JMP update ; read 11 + +; 01 state + JMP increment ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP decrement ; read 11 + +; 10 state + JMP decrement ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP increment ; read 11 + +; to reduce code size, the last 2 states are implemented in place and become the +; target for the other jumps + +; 11 state + JMP update ; read 00 + JMP increment ; read 01 +decrement: + ; note: the target of this instruction must be the next address, so that + ; the effect of the instruction does not depend on the value of Y. The + ; same is true for the "JMP X--" below. Basically "JMP Y--, " + ; is just a pure "decrement Y" instruction, with no other side effects + JMP Y--, update ; read 10 + + ; this is where the main loop starts +.wrap_target +update: + MOV ISR, Y ; read 11 + PUSH noblock + +sample_pins: + ; we shift into ISR the last state of the 2 input pins (now in OSR) and + ; the new state of the 2 pins, thus producing the 4 bit target for the + ; computed jump into the correct action for this state. Both the PUSH + ; above and the OUT below zero out the other bits in ISR + OUT ISR, 2 + IN PINS, 2 + + ; save the state in the OSR, so that we can use ISR for other purposes + MOV OSR, ISR + ; jump to the correct state machine action + MOV PC, ISR + + ; the PIO does not have a increment instruction, so to do that we do a + ; negate, decrement, negate sequence +increment: + MOV Y, ~Y + JMP Y--, increment_cont +increment_cont: + MOV Y, ~Y +.wrap ; the .wrap here avoids one jump instruction and saves a cycle too + + + +% c-sdk { + +#include "hardware/clocks.h" +#include "hardware/gpio.h" + +// max_step_rate is used to lower the clock of the state machine to save power +// if the application doesn't require a very high sampling rate. Passing zero +// will set the clock to the maximum + +static inline void quadrature_encoder_program_init(PIO pio, uint sm, uint offset, uint pin, int max_step_rate) +{ + pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false); + gpio_pull_up(pin); + gpio_pull_up(pin + 1); + + pio_sm_config c = quadrature_encoder_program_get_default_config(offset); + + sm_config_set_in_pins(&c, pin); // for WAIT, IN + sm_config_set_jmp_pin(&c, pin); // for JMP + // shift to left, autopull disabled + sm_config_set_in_shift(&c, false, false, 32); + // don't join FIFO's + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); + + // passing "0" as the sample frequency, + if (max_step_rate == 0) { + sm_config_set_clkdiv(&c, 1.0); + } else { + // one state machine loop takes at most 10 cycles + float div = (float)clock_get_hz(clk_sys) / (10 * max_step_rate); + sm_config_set_clkdiv(&c, div); + } + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +static inline int32_t quadrature_encoder_get_count(PIO pio, uint sm) +{ + uint ret; + int n; + + // if the FIFO has N entries, we fetch them to drain the FIFO, + // plus one entry which will be guaranteed to not be stale + n = pio_sm_get_rx_fifo_level(pio, sm) + 1; + while (n > 0) { + ret = pio_sm_get_blocking(pio, sm); + n--; + } + return ret; +} + +%} From 0e51f040ce8c6f692cd313de498a48b0fc613e1e Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Sat, 6 Jul 2024 22:44:03 -0700 Subject: [PATCH 57/65] Added message-library encode/decode Added message library that encodes/decodes messages in specified format for modbot in c & python --- .vscode/settings.json | 6 + .vscode/tasks.json | 28 +++ dev/messaging-library/messaging-library.h | 181 +++++++++++++++++++ dev/messaging-library/messaging-test.c | 172 ++++++++++++++++++ dev/messaging-library/messaging_library.py | 193 +++++++++++++++++++++ dev/messaging-library/messaging_test.py | 166 ++++++++++++++++++ 6 files changed, 746 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 dev/messaging-library/messaging-library.h create mode 100644 dev/messaging-library/messaging-test.c create mode 100644 dev/messaging-library/messaging_library.py create mode 100644 dev/messaging-library/messaging_test.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..85ecbdb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "messaging-library.h": "c", + "stdio.h": "c" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..76d916e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc.exe build active file", + "command": "C:\\TDM-GCC-64\\bin\\gcc.exe", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}\\${fileBasenameNoExtension}.exe" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/dev/messaging-library/messaging-library.h b/dev/messaging-library/messaging-library.h new file mode 100644 index 0000000..258a521 --- /dev/null +++ b/dev/messaging-library/messaging-library.h @@ -0,0 +1,181 @@ + +#define PAYLOAD_MAX_LEN 127 +#define HEAD_ID 0xCC +#define TAIL_ID 0xB9 + +enum messageParseResult { + MESSAGE_BUFFER_OVERFLOW = -2, + MESSAGE_PARSE_FAILURE, + MESSAGE_PARSE_NONE, + MESSAGE_PARSE_SUCCESS +}; + +enum messageReadState { + MESSAGE_ERROR = -1, + MESSAGE_HEAD, + MESSAGE_LENGTH, + MESSAGE_PAYLOAD, + MESSAGE_TAIL, + MESSAGE_CHECKSUM, + MESSAGE_END +}; + +/** + * BSDChecksum: Calculates checksum for messaging, using BSDChecksum algorithm. The max length is 128 bytes. The first byte is the message ID, the next 127 bytes is the payload + * + * @param payloadString string of payload and id, 128 bytes max + * @param payloadLen length of payload with id + * + * @return calculated checksum + */ +char MessagingBSDChecksum(char* payloadString, unsigned char payloadLen) { + unsigned char checksum = 0; + for(short i = 0; i < payloadLen; i++) { + checksum = (checksum >> 1) | (checksum << 7); + checksum += payloadString[i]; + } + return checksum; +} + +/** + * ReadBuffer: Uses state machine to take the buffer parameter and processes the message into the result string; + * Returns true/false based on if conversion was successful. + * + * \nPacket Structure: + * HEAD LENGTH PAYLOAD TAIL CHECKSUM END + * 1 1 (1)<128 1 1 \r\n + * + * Head: 1 byte 0xCC + * Length: 1 byte, length of payload in bytes + * Payload: Variable length < 128 bytes, always proceeded by a 1 byte ID + * Tail: 1 byte 0xB9 + * Checksum: 1 byte checksum calculated over payload using BSDChecksum algorithm + * End: 2 bytes \r\n + * + * @param bufferString string that will be processed + * @param resultString string that the result is stored in + * @param bufferLength length of buffer function parses through + * + * @return true or false based on success of function + */ +char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLength) { // turn result to struct later + short idx = 0; + char state = MESSAGE_HEAD; + char currentChar = bufferString[idx]; + char payloadLen = 0; + char payloadIdx = 0; + char messageID = 0; + char calculatedChecksum = 0; + char messageParseResult = MESSAGE_PARSE_NONE; + + while (idx < bufferLength) { + if(state == MESSAGE_HEAD) { + if(currentChar == (char)HEAD_ID) { + state = MESSAGE_LENGTH; + } + }else if(state == MESSAGE_LENGTH) { + payloadLen = currentChar; + + // expected payload range check + if(payloadLen < 2) { + state = MESSAGE_ERROR; + } + + payloadIdx = 0; + char* payload = bufferString + idx + 1; + calculatedChecksum = MessagingBSDChecksum(payload, payloadLen); + state = MESSAGE_PAYLOAD; + + }else if(state == MESSAGE_PAYLOAD) { + + // id valid check + if(payloadIdx == 0) { + messageID = currentChar; + + // check against list of IDS + // + // + } + + // process payload based on ID + if(messageID == 0) { // example 'processing' + resultString = "TEST"; + } + + // actual payload length check + if(payloadIdx < payloadLen - 1) { + if(currentChar == (char)TAIL_ID) { + state = MESSAGE_ERROR; + }else { + payloadIdx++; + } + }else { + if(currentChar == (char)TAIL_ID) { + state = MESSAGE_ERROR; + }else { + state = MESSAGE_TAIL; + } + } + + }else if(state == MESSAGE_TAIL) { + if(currentChar == (char)TAIL_ID) { + state = MESSAGE_CHECKSUM; + }else{ + state = MESSAGE_ERROR; + } + + }else if(state == MESSAGE_CHECKSUM) { + if(calculatedChecksum != currentChar) { + state = MESSAGE_ERROR; + }else { + state = MESSAGE_END; + } + + }else if(state == MESSAGE_END){ + char nextChar = bufferString[idx + 1]; + if(currentChar == '\r' && nextChar == '\n') { + messageParseResult = MESSAGE_PARSE_SUCCESS; + state = MESSAGE_HEAD; + break; + }else { + state = MESSAGE_ERROR; + } + + }else { + messageParseResult = MESSAGE_PARSE_FAILURE; + break; + } + + idx++; + currentChar = bufferString[idx]; + } + + if(state != MESSAGE_HEAD && state != MESSAGE_ERROR) { + messageParseResult = MESSAGE_BUFFER_OVERFLOW; + } + + return messageParseResult; +} + +/** + * WriteMessage: converts parameter payload into a valid message string and writes it to parameter message. + * Payload corresponds to PAYLOAD in the packet structure: [HEAD, LENGTH, PAYLOAD, TAIL, CHECKSUM, END] + * + * @param payload string that will be converted to a valid message format + * @param message string that the message will be written to + * + * @return None + */ +void MessagingWriteMessage(unsigned char payload[], char message[]){ + char payloadLen = 0; + for(;payload[payloadLen] != '\0'; payloadLen++){ + message[payloadLen + 2] = payload[payloadLen]; + } + message[0] = (char)HEAD_ID; + message[1] = payloadLen; + message[payloadLen + 2] = (char)TAIL_ID; + message[payloadLen + 3] = MessagingBSDChecksum(payload, payloadLen); + message[payloadLen + 4] = '\r'; + message[payloadLen + 5] = '\n'; + message[payloadLen + 6] = '\0'; +} diff --git a/dev/messaging-library/messaging-test.c b/dev/messaging-library/messaging-test.c new file mode 100644 index 0000000..a1ae76d --- /dev/null +++ b/dev/messaging-library/messaging-test.c @@ -0,0 +1,172 @@ +#include +#include + +#include "messaging-library.h" + +#define MIN 0x00 +#define MAX 0x7F + +int main() +{ + printf("Testing BSDChecksum"); + char payload[] = {0x01, 0x01}; + char c = MessagingBSDChecksum(payload, 2); + printf("%i\n", c); + char *r = ""; + // correct message + printf("Testing correct message\n"); + char mes[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes, r, 8), r); + + // incorrect given payload length + printf("Testing too short given payload length\n"); + char mes20[] = {0xCC, MIN, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes20, r, 8), r); + + printf("Testing too long given payload length\n"); + char mes2[] = {0xCC, MAX, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes2, r, 8), r); + + printf("Testing too short actual payload length\n"); + char mes3[] = {0xCC, 0x02, 0x01, 0xB9, 0x01, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes3, r, 8), r); + + printf("Testing too long actual payload length\n"); + char mes4[] = {0xCC, 0x02, 0x01, 0x01, 0x01, 0xB9, 0xC1, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes4, r, 8), r); + + printf("Testing limit payload length\n"); + char mes5[] = {0xCC, 0x7F, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0xB9, 0x01, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes5, r, 133), r); + + printf("Testing exceed limit payload length\n"); + char mes14[] = {0xCC, 0x80, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes14, r, 133), r); + + // incorrect checksum + printf("Testing incorrect checksum\n"); + char mes6[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, MIN, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes6, r, 8), r); + + // fixed character test (HEAD, TAIL, DELIMITERS) + printf("Testing incorrect head\n"); + char mes7[] = {MIN, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes7, r, 8), r); + + printf("Testing incorrect tail\n"); + char mes8[] = {0xCC, 0x02, 0x01, 0x01, MIN, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes8, r, 8), r); + + printf("Testing incorrect delimiter 1\n"); + char mes9[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, MIN, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes9, r, 8), r); + + printf("Testing incorrect delimiter 2\n"); + char mes10[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, MIN}; + printf("%i %s\n", MessagingReadBuffer(mes10, r, 8), r); + + // message structure test + printf("Testing incorrect structure\n"); + char mes11[] = {0xCC, MIN, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes11, r, 8), r); + + char mes12[] = {0xCC, 0x02, MIN, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes12, r, 8), r); + + char mes13[] = {0xCC, 0x02, 0x01, 0x01, MIN, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes13, r, 8), r); + + char mes15[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, MIN, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes15, r, 8), r); + + char mes16[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, MIN, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes16, r, 8), r); + + char mes17[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, MIN, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes17, r, 8), r); + + printf("Testing characters before/after message\n"); + char mes18[] = {MIN, 0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes18, r, 8), r); + + char mes19[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A, MIN}; + printf("%i %s\n", MessagingReadBuffer(mes19, r, 8), r); + + printf("Testing buffer overflow\n"); + char mes22[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + printf("%i %s\n", MessagingReadBuffer(mes22, r, 2), r); + + printf("Testing WriteMessage\n"); + // put c encoded message here + char mes23[] = "TEST_TEST_TEST,1,2,34545edrdfgCCV_+{}|\\\\\\\'\b\b67879$^&*^#$54rtyfghvnghn65876878nyghvgnh658766$--+P]"; + char messLen = strlen(mes23); + char message[100] = {'\0'}; + MessagingWriteMessage(mes23,message); + printf("C output: "); + for(char i = 0; i < (messLen + 7); i ++) { + if(i < 2 || i > messLen + 1) { + printf("0x%02hhX ", message[i]); + }else { + printf("%c", message[i]); + if(i == messLen + 1) { + printf(" "); + } + } + } + printf("\n\n"); + + printf("Python Version: "); + for(char i = 0; i < (messLen + 7); i ++) { + printf("\\x%02hhX", message[i]); + } + printf("\n\n"); + + // put python test harness output here + char message2[] = {0xcc,0x61,0x54,0x45,0x53,0x54,0x5f,0x54,0x45, + 0x53,0x54,0x5f,0x54,0x45,0x53,0x54,0x2c,0x31,0x2c,0x32,0x2c,0x33, + 0x34,0x35,0x34,0x35,0x65,0x64,0x72,0x64,0x66,0x67,0x43,0x43,0x56, + 0x5f,0x2b,0x7b,0x7d,0x7c,0x5c,0x5c,0x5c,0x27,0x8,0x8,0x36,0x37, + 0x38,0x37,0x39,0x24,0x5e,0x26,0x2a,0x5e,0x23,0x24,0x35,0x34,0x72, + 0x74,0x79,0x66,0x67,0x68,0x76,0x6e,0x67,0x68,0x6e,0x36,0x35,0x38, + 0x37,0x36,0x38,0x37,0x38,0x6e,0x79,0x67,0x68,0x76,0x67,0x6e,0x68, + 0x36,0x35,0x38,0x37,0x36,0x36,0x24,0x2d,0x2d,0x2b,0x50,0x5d,0xb9, + 0x7b,0xd,0xa}; + printf("Testing encode/decode between libraries \n"); + printf("%i %s\n", MessagingReadBuffer(message2, r, 103), r); + + return 0; +} \ No newline at end of file diff --git a/dev/messaging-library/messaging_library.py b/dev/messaging-library/messaging_library.py new file mode 100644 index 0000000..b2572b3 --- /dev/null +++ b/dev/messaging-library/messaging_library.py @@ -0,0 +1,193 @@ +""" +messaging_library encodes and decodes messages that are sent over i2c between +raspberry pis and raspberry picos for modbot +""" + +from enum import Enum + +HEAD = 0xCC +TAIL = 0xB9 + +# using pep 8 and flake 8 linter for code styling +# DOES NOT have buffer overflow error due to how python handles strings + + +class MessageReadState (Enum): + """enums that describe the state of the state machine + in messaging_readbuffer""" + MESSAGE_ERROR = -1 + MESSAGE_HEAD = 0 + MESSAGE_LENGTH = 1 + MESSAGE_PAYLOAD = 2 + MESSAGE_TAIL = 3 + MESSAGE_CHECKSUM = 4 + MESSAGE_END = 5 + + +class MessageParseResult (Enum): + """enums that describe the success state of messaging_readbuffer""" + MESSAGE_PARSE_FAILURE = -1 + MESSAGE_PARSE_NONE = 0 + MESSAGE_PARSE_SUCCESS = 1 + + +def messaging_bsd_checksum(payload_string: bytes) -> int: + """ + + messaging_checksum: Calculates checksum for messaging, + using BSDChecksum algorithm. The max length is 128 bytes. + The first byte is the message ID, the next 127 bytes is the payload + + Args: + payloadString (bytes): string of payload and id, 128 bytes max + + Returns: + int: calculated checksum + + """ + checksum = 0 + for idx in payload_string: + checksum = (checksum >> 1) | (checksum << 7) + # 8-bit rotation + checksum = checksum & 0b11111111 + # mask int to only include lower 8 bits + checksum = int(bin(idx), 2) + int(bin(checksum), 2) + # add values bitwise + checksum = checksum & 0b11111111 + # mask int to only include lower 8 bits + return checksum + + +def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: + """ + + messaging_readbuffer: Uses state machine to take the buffer parameter + and processes the message into the result string; + Returns true/false based on if conversion was successful. + + Packet Structure: + HEAD LENGTH PAYLOAD TAIL CHECKSUM END + 1 1 (1)<128 1 1 \r\n + + Head: 1 byte 0xCC + Length: 1 byte, length of payload in bytes + Payload: Variable length < 128 bytes, always proceeded by a 1 byte ID + Tail: 1 byte 0xB9 + Checksum: 1 byte checksum calculated over payload + using BSDChecksum algorithm + End: 2 bytes \r\n + + Args: + buffer_string (bytes): string that will be processed + result_string (bytes): string that the result is stored in + + Returns: + enum: true false or none based on success of function + or if buffer contains no message + + DOES NOT HAVE BUFFER OVERFLOW ERROR + """ + state = MessageReadState.MESSAGE_HEAD + payload_idx = 0 + payload_len = 0 + calculated_checksum = 0 + message_id = 0 + message_parse_result = MessageParseResult.MESSAGE_PARSE_NONE + + # note: since I am writing both the library in c and python, + # I wanted to make the logic the same between both, so it could be easier + # to update/change the whole library in the future + for idx, current_char in enumerate(buffer_string): + if state == MessageReadState.MESSAGE_HEAD: + if current_char == HEAD: + state = MessageReadState.MESSAGE_LENGTH + elif state == MessageReadState.MESSAGE_LENGTH: + payload_len = int(current_char) + + # expected payload range check + if payload_len < 2: + state = MessageReadState.MESSAGE_ERROR + + payload_idx = 0 + payload_string = buffer_string[(idx + 1):(idx + 1 + payload_len)] + calculated_checksum = messaging_bsd_checksum(payload_string) + # calculated_checksum = hex(calculated_checksum).encode() + state = MessageReadState.MESSAGE_PAYLOAD + + elif state == MessageReadState.MESSAGE_PAYLOAD: + + # id valid check + if payload_idx == 0: + message_id = current_char + + # process payload based on ID + if message_id == 0: # example 'processing' + result_string = "TEST" + if result_string: # using to suppress error/warning + pass + + # actual payload range check + # (this is implemented slightly differently + # as python for loops work differently) + if payload_idx < payload_len - 1: + if current_char == TAIL: + state = MessageReadState.MESSAGE_ERROR + else: + payload_idx += 1 + else: + if current_char == TAIL: + state = MessageReadState.MESSAGE_ERROR + else: + state = MessageReadState.MESSAGE_TAIL + + elif state == MessageReadState.MESSAGE_TAIL: + if current_char == TAIL: + state = MessageReadState.MESSAGE_CHECKSUM + else: + state = MessageReadState.MESSAGE_ERROR + + elif state == MessageReadState.MESSAGE_CHECKSUM: + if calculated_checksum != current_char: + state = MessageReadState.MESSAGE_ERROR + else: + state = MessageReadState.MESSAGE_END + + elif state == MessageReadState.MESSAGE_END: + next_char = buffer_string[idx + 1] + if current_char == 0x0D and next_char == 0x0A: + message_parse_result = MessageParseResult.MESSAGE_PARSE_SUCCESS + state = MessageReadState.MESSAGE_HEAD + break + else: + state = MessageReadState.MESSAGE_ERROR + + else: + message_parse_result = MessageParseResult.MESSAGE_PARSE_FAILURE + break + + return message_parse_result + + +def messaging_write_message(payload: str) -> bytes: + """ + + WriteMessage: + converts parameter payload into a valid message + string and writes to parameter message. + Payload corresponds to PAYLOAD in the packet structure: + [HEAD, LENGTH, PAYLOAD, TAIL, CHECKSUM, END] + + Args: + payload (str): string that will be converted to a valid message format + + Returns: + message (str): string that the message will be written to + """ + payload_len = len(payload) + payload_checksum = messaging_bsd_checksum(payload.encode()) + converted_payload = [] + for char in payload: + converted_payload.append(ord(char)) + bytes_return = bytes([HEAD, payload_len] + converted_payload + + [TAIL, payload_checksum, 13, 10]) + return bytes_return diff --git a/dev/messaging-library/messaging_test.py b/dev/messaging-library/messaging_test.py new file mode 100644 index 0000000..3df1dc1 --- /dev/null +++ b/dev/messaging-library/messaging_test.py @@ -0,0 +1,166 @@ +"""testing file for messaging library in python""" +import messaging_library + +MIN = b"\x00" +MAX = b"\x7F" + +print("Testing bsd checksum") +PAYLOAD = b"\x01\x01" +c = messaging_library.messaging_bsd_checksum(PAYLOAD) +print(c) +R = "" +# correct message +print("Testing correct message") +MES = b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +# incorrect given payload length +print("Testing too short given payload length") +MES = b"\xCC\x02" + MIN + b"\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing too long given payload length") +MES = b"\xCC\x02" + MAX + b"\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing too short actual payload length") +MES = b"\xCC\x02\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing too long actual payload length") +MES = b"\xCC\x02\x01\x01\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +# newlines are included in multi-line strings, must concat strings +print("Testing limit payload length") +MES = b"\xCC\x7F" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01" + \ + b"\xB9\x01\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing exceed limit payload length") +MES = b"\xCC\x80" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ + b"\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +# incorrect checksum +print("Testing incorrect checksum") +MES = b"\xCC\x02\x01\x01\x01\xB9" + MIN + b"\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +# fixed character test (HEAD, TAIL, DELIMITERS) +print("Testing incorrect head") +MES = MIN + b"\x02\x01\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing incorrect tail") +MES = b"\xCC\x02\x01\x01" + MIN + b"\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing incorrect delimiter 1\n") +MES = b"\xCC\x02\x01\x01\xB9\x81" + MIN + b"\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing incorrect delimiter 2\n") +MES = b"\xCC\x02\x01\x01\xB9\x81\x0D" + MIN +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +# message structure test +print("Testing incorrect structure") +MES = b"\xCC" + MIN + b"\x02\x01\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +MES = b"\xCC\x02" + MIN + b"\x01\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +MES = b"\xCC\x02\x01\x01" + MIN + b"\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +MES = b"\xCC\x02\x01\x01\xB9" + MIN + b"\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +MES = b"\xCC\x02\x01\x01\xB9\x81" + MIN + b"\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +MES = b"\xCC\x02\x01\x01\xB9\x81\x0D" + MIN + b"\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing characters before/after message") +MES = MIN + b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +MES = b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" + MIN +print(messaging_library.messaging_readbuffer(MES, R)) +print(R) + +print("Testing WriteMessage") +# put python encoded message here +MES = "TEST_TEST_TEST,1,2,34545edrdfgCCV_+{}|\\\\\\\'\b\b67879$^&" +\ + "*^#$54rtyfghvnghn65876878nyghvgnh658766$--+P]" +R = messaging_library.messaging_write_message(MES) +print("Python output: ", end="") +print(R) +print("C version: ", end="") +print("{", end="") +for char in list(R): + if char == 10: + print(hex(char), end="") + else: + print(hex(char), end=",") +print("};", end="\n\n") + +print("Testing encode/decode between libraries") +# put c test harness output message here +MES = b"\xCC\x1B\x54\x45\x53\x54\x5F\x54\x45\x53\x54\x5F\x54\x45\x53\x54" +\ + b"\x2C\x31\x2C\x32\x2C\x33\x24\x5E\x26\x2A\x5E\x23\x24\xB9\xAD\x0D\x0A\x00" +print(messaging_library.messaging_readbuffer(MES, R)) From 07680194c953bea52aa88fa5735347a3f3fac7e0 Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Tue, 9 Jul 2024 03:25:22 -0700 Subject: [PATCH 58/65] Added payload processing Added processing for specific message ids in compute_module_id.py and swerve-module-id.h Added decoding of payload of 24 bytes -> 6 floats, changes state of robot to read converted floats --- .vscode/settings.json | 7 +- dev/messaging-library/compute_module_id.py | 28 ++ dev/messaging-library/messaging-library.h | 21 +- dev/messaging-library/messaging-test.c | 314 +++++++++++---------- dev/messaging-library/messaging_library.py | 28 +- dev/messaging-library/messaging_test.py | 310 ++++++++++---------- dev/messaging-library/swerve-module-id.h | 45 +++ 7 files changed, 419 insertions(+), 334 deletions(-) create mode 100644 dev/messaging-library/compute_module_id.py create mode 100644 dev/messaging-library/swerve-module-id.h diff --git a/.vscode/settings.json b/.vscode/settings.json index 85ecbdb..6a693a1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,11 @@ { "files.associations": { "messaging-library.h": "c", - "stdio.h": "c" + "stdio.h": "c", + "messaging-library-ids.h": "c", + "initializer_list": "c", + "type_traits": "c", + "xstring": "c", + "xutility": "c" } } \ No newline at end of file diff --git a/dev/messaging-library/compute_module_id.py b/dev/messaging-library/compute_module_id.py new file mode 100644 index 0000000..104db85 --- /dev/null +++ b/dev/messaging-library/compute_module_id.py @@ -0,0 +1,28 @@ +"""Defined list of valid message ids and helper function to +check and process messages into actions""" + +from enum import Enum + + +class MessageProcessResult (Enum): + """state of robot after message has been fully processed""" + PAYLOAD_ID_NOT_FOUND = 0 + + +result_array = {} + + +def messaging_process_payload(payload, id) -> MessageProcessResult: + """ + process_payload: reads parameter of specified length + and performs actions based on the id + + @param payload string + @param id id of payload + + @returns success/failure to process payload, + next state of main state machine + """ + print(payload) + print(id) + return MessageProcessResult.PAYLOAD_ID_NOT_FOUND diff --git a/dev/messaging-library/messaging-library.h b/dev/messaging-library/messaging-library.h index 258a521..0cc3e05 100644 --- a/dev/messaging-library/messaging-library.h +++ b/dev/messaging-library/messaging-library.h @@ -1,3 +1,4 @@ +#include "swerve-module-id.h" #define PAYLOAD_MAX_LEN 127 #define HEAD_ID 0xCC @@ -39,7 +40,7 @@ char MessagingBSDChecksum(char* payloadString, unsigned char payloadLen) { /** * ReadBuffer: Uses state machine to take the buffer parameter and processes the message into the result string; - * Returns true/false based on if conversion was successful. + * Returns true/false based on if conversion was successful. Changes value of robotState if message is valid. * * \nPacket Structure: * HEAD LENGTH PAYLOAD TAIL CHECKSUM END @@ -55,10 +56,12 @@ char MessagingBSDChecksum(char* payloadString, unsigned char payloadLen) { * @param bufferString string that will be processed * @param resultString string that the result is stored in * @param bufferLength length of buffer function parses through + * @param robotState pointer to variable containing state of robot that is modified + * when a message is fully processed * * @return true or false based on success of function */ -char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLength) { // turn result to struct later +char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLength, char* robotState) { // turn result to struct later short idx = 0; char state = MESSAGE_HEAD; char currentChar = bufferString[idx]; @@ -67,6 +70,7 @@ char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLengt char messageID = 0; char calculatedChecksum = 0; char messageParseResult = MESSAGE_PARSE_NONE; + char robotResultState = 0; while (idx < bufferLength) { if(state == MESSAGE_HEAD) { @@ -88,18 +92,9 @@ char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLengt }else if(state == MESSAGE_PAYLOAD) { - // id valid check + // assign/store id if(payloadIdx == 0) { messageID = currentChar; - - // check against list of IDS - // - // - } - - // process payload based on ID - if(messageID == 0) { // example 'processing' - resultString = "TEST"; } // actual payload length check @@ -113,6 +108,7 @@ char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLengt if(currentChar == (char)TAIL_ID) { state = MESSAGE_ERROR; }else { + robotResultState = MessagingProcessPayload(bufferString + idx - payloadLen + 2, messageID, payloadLen - 1); state = MESSAGE_TAIL; } } @@ -136,6 +132,7 @@ char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLengt if(currentChar == '\r' && nextChar == '\n') { messageParseResult = MESSAGE_PARSE_SUCCESS; state = MESSAGE_HEAD; + *robotState = robotResultState; break; }else { state = MESSAGE_ERROR; diff --git a/dev/messaging-library/messaging-test.c b/dev/messaging-library/messaging-test.c index a1ae76d..f6cd6a9 100644 --- a/dev/messaging-library/messaging-test.c +++ b/dev/messaging-library/messaging-test.c @@ -8,165 +8,167 @@ int main() { - printf("Testing BSDChecksum"); - char payload[] = {0x01, 0x01}; - char c = MessagingBSDChecksum(payload, 2); - printf("%i\n", c); + // printf("\nTesting BSDChecksum\n"); + // char payload[] = {0x80,0x40,0x13,0x33,0x33,0x40,0x13,0x33,0x33,0x40,0x13,0x33,0x33,0x40,0x13,0x33,0x33,0x40,0x13,0x33,0x33,0x40,0x13,0x33,0x33}; + // char c = MessagingBSDChecksum(payload, 25); + // printf("%i\n", c); char *r = ""; - // correct message - printf("Testing correct message\n"); - char mes[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes, r, 8), r); - - // incorrect given payload length - printf("Testing too short given payload length\n"); - char mes20[] = {0xCC, MIN, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes20, r, 8), r); - - printf("Testing too long given payload length\n"); - char mes2[] = {0xCC, MAX, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes2, r, 8), r); - - printf("Testing too short actual payload length\n"); - char mes3[] = {0xCC, 0x02, 0x01, 0xB9, 0x01, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes3, r, 8), r); - - printf("Testing too long actual payload length\n"); - char mes4[] = {0xCC, 0x02, 0x01, 0x01, 0x01, 0xB9, 0xC1, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes4, r, 8), r); - - printf("Testing limit payload length\n"); - char mes5[] = {0xCC, 0x7F, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0xB9, 0x01, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes5, r, 133), r); - - printf("Testing exceed limit payload length\n"); - char mes14[] = {0xCC, 0x80, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes14, r, 133), r); - - // incorrect checksum - printf("Testing incorrect checksum\n"); - char mes6[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, MIN, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes6, r, 8), r); - - // fixed character test (HEAD, TAIL, DELIMITERS) - printf("Testing incorrect head\n"); - char mes7[] = {MIN, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes7, r, 8), r); - - printf("Testing incorrect tail\n"); - char mes8[] = {0xCC, 0x02, 0x01, 0x01, MIN, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes8, r, 8), r); - - printf("Testing incorrect delimiter 1\n"); - char mes9[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, MIN, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes9, r, 8), r); - - printf("Testing incorrect delimiter 2\n"); - char mes10[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, MIN}; - printf("%i %s\n", MessagingReadBuffer(mes10, r, 8), r); - - // message structure test - printf("Testing incorrect structure\n"); - char mes11[] = {0xCC, MIN, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes11, r, 8), r); - - char mes12[] = {0xCC, 0x02, MIN, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes12, r, 8), r); - - char mes13[] = {0xCC, 0x02, 0x01, 0x01, MIN, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes13, r, 8), r); - - char mes15[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, MIN, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes15, r, 8), r); - - char mes16[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, MIN, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes16, r, 8), r); - - char mes17[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, MIN, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes17, r, 8), r); - - printf("Testing characters before/after message\n"); - char mes18[] = {MIN, 0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes18, r, 8), r); - - char mes19[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A, MIN}; - printf("%i %s\n", MessagingReadBuffer(mes19, r, 8), r); - - printf("Testing buffer overflow\n"); - char mes22[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; - printf("%i %s\n", MessagingReadBuffer(mes22, r, 2), r); - - printf("Testing WriteMessage\n"); - // put c encoded message here - char mes23[] = "TEST_TEST_TEST,1,2,34545edrdfgCCV_+{}|\\\\\\\'\b\b67879$^&*^#$54rtyfghvnghn65876878nyghvgnh658766$--+P]"; - char messLen = strlen(mes23); - char message[100] = {'\0'}; - MessagingWriteMessage(mes23,message); - printf("C output: "); - for(char i = 0; i < (messLen + 7); i ++) { - if(i < 2 || i > messLen + 1) { - printf("0x%02hhX ", message[i]); - }else { - printf("%c", message[i]); - if(i == messLen + 1) { - printf(" "); - } - } - } - printf("\n\n"); - - printf("Python Version: "); - for(char i = 0; i < (messLen + 7); i ++) { - printf("\\x%02hhX", message[i]); - } - printf("\n\n"); + char state = 0; + // // correct message + // printf("Testing correct message\n"); + // char mes[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes, r, 8, &state), r, state); + + // // incorrect given payload length + // printf("Testing too short given payload length\n"); + // char mes20[] = {0xCC, MIN, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes20, r, 8, &state), r, state); + + // printf("Testing too long given payload length\n"); + // char mes2[] = {0xCC, MAX, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes2, r, 8, &state), r, state); + + // printf("Testing too short actual payload length\n"); + // char mes3[] = {0xCC, 0x02, 0x01, 0xB9, 0x01, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes3, r, 8, &state), r, state); + + // printf("Testing too long actual payload length\n"); + // char mes4[] = {0xCC, 0x02, 0x01, 0x01, 0x01, 0xB9, 0xC1, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes4, r, 8, &state), r, state); + + // printf("Testing limit payload length\n"); + // char mes5[] = {0xCC, 0x7F, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0xB9, 0x01, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes5, r, 133, &state), r, state); + + // printf("Testing exceed limit payload length\n"); + // char mes14[] = {0xCC, 0x80, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + // 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes14, r, 133, &state), r, state); + + // // incorrect checksum + // printf("Testing incorrect checksum\n"); + // char mes6[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, MIN, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes6, r, 8, &state), r, state); + + // // fixed character test (HEAD, TAIL, DELIMITERS) + // printf("Testing incorrect head\n"); + // char mes7[] = {MIN, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes7, r, 8, &state), r, state); + + // printf("Testing incorrect tail\n"); + // char mes8[] = {0xCC, 0x02, 0x01, 0x01, MIN, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes8, r, 8, &state), r, state); + + // printf("Testing incorrect delimiter 1\n"); + // char mes9[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, MIN, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes9, r, 8, &state), r, state); + + // printf("Testing incorrect delimiter 2\n"); + // char mes10[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, MIN}; + // printf("%i %s %i\n", MessagingReadBuffer(mes10, r, 8, &state), r, state); + + // // message structure test + // printf("Testing incorrect structure\n"); + // char mes11[] = {0xCC, MIN, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes11, r, 8, &state), r, state); + + // char mes12[] = {0xCC, 0x02, MIN, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes12, r, 8, &state), r, state); + + // char mes13[] = {0xCC, 0x02, 0x01, 0x01, MIN, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes13, r, 8, &state), r, state); + + // char mes15[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, MIN, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes15, r, 8, &state), r, state); + + // char mes16[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, MIN, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes16, r, 8, &state), r, state); + + // char mes17[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, MIN, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes17, r, 8, &state), r, state); + + // printf("Testing characters before/after message\n"); + // char mes18[] = {MIN, 0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes18, r, 8, &state), r, state); + + // char mes19[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A, MIN}; + // printf("%i %s %i\n", MessagingReadBuffer(mes19, r, 8, &state), r, state); + + // printf("Testing buffer overflow\n"); + // char mes22[] = {0xCC, 0x02, 0x01, 0x01, 0xB9, 0x81, 0x0D, 0x0A}; + // printf("%i %s %i\n", MessagingReadBuffer(mes22, r, 2, &state), r, state); + + // printf("Testing WriteMessage\n"); + // // put c encoded message here + // char mes23[] = "TEST_TEST_TEST,1,2,34545edrdfgCCV_+{}|\\\\\\\'\b\b67879$^&*^#$54rtyfghvnghn65876878nyghvgnh658766$--+P]"; + // char messLen = strlen(mes23); + // char message[100] = {'\0'}; + // MessagingWriteMessage(mes23,message); + // printf("C output: "); + // for(char i = 0; i < (messLen + 7); i ++) { + // if(i < 2 || i > messLen + 1) { + // printf("0x%02hhX ", message[i]); + // }else { + // printf("%c", message[i]); + // if(i == messLen + 1) { + // printf(" "); + // } + // } + // } + // printf("\n\n"); + + // printf("Python Version: "); + // for(char i = 0; i < (messLen + 7); i ++) { + // printf("\\x%02hhX", message[i]); + // } + // printf("\n\n"); // put python test harness output here - char message2[] = {0xcc,0x61,0x54,0x45,0x53,0x54,0x5f,0x54,0x45, - 0x53,0x54,0x5f,0x54,0x45,0x53,0x54,0x2c,0x31,0x2c,0x32,0x2c,0x33, - 0x34,0x35,0x34,0x35,0x65,0x64,0x72,0x64,0x66,0x67,0x43,0x43,0x56, - 0x5f,0x2b,0x7b,0x7d,0x7c,0x5c,0x5c,0x5c,0x27,0x8,0x8,0x36,0x37, - 0x38,0x37,0x39,0x24,0x5e,0x26,0x2a,0x5e,0x23,0x24,0x35,0x34,0x72, - 0x74,0x79,0x66,0x67,0x68,0x76,0x6e,0x67,0x68,0x6e,0x36,0x35,0x38, - 0x37,0x36,0x38,0x37,0x38,0x6e,0x79,0x67,0x68,0x76,0x67,0x6e,0x68, - 0x36,0x35,0x38,0x37,0x36,0x36,0x24,0x2d,0x2d,0x2b,0x50,0x5d,0xb9, - 0x7b,0xd,0xa}; + char message2[] = {0xcc,0x19,0x80, + 0x40,0x13,0x33,0x33, + 0x40,0x13,0x33,0x33, + 0x40,0x13,0x33,0x33, + 0x40,0x13,0x33,0x33, + 0x40,0x13,0x33,0x33, + 0x40,0x13,0x33,0x33, + 0xb9,0x57,0xd,0xa}; printf("Testing encode/decode between libraries \n"); - printf("%i %s\n", MessagingReadBuffer(message2, r, 103), r); - + printf("%i ", MessagingReadBuffer(message2, r, 31, &state)); + printf("%i\n",state); + for(int i=0;i<6;i++) + printf(" %f",resultArray[i]); return 0; } \ No newline at end of file diff --git a/dev/messaging-library/messaging_library.py b/dev/messaging-library/messaging_library.py index b2572b3..b1a23db 100644 --- a/dev/messaging-library/messaging_library.py +++ b/dev/messaging-library/messaging_library.py @@ -4,6 +4,7 @@ """ from enum import Enum +import compute_module_id HEAD = 0xCC TAIL = 0xB9 @@ -58,7 +59,8 @@ def messaging_bsd_checksum(payload_string: bytes) -> int: return checksum -def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: +def messaging_readbuffer(buffer_string: bytes, result_string: bytes, + robot_state: Enum) -> bool: """ messaging_readbuffer: Uses state machine to take the buffer parameter @@ -80,6 +82,8 @@ def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: Args: buffer_string (bytes): string that will be processed result_string (bytes): string that the result is stored in + robot_state (enum): variable containing state of robot that is modified + when a message is fully processed Returns: enum: true false or none based on success of function @@ -93,6 +97,7 @@ def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: calculated_checksum = 0 message_id = 0 message_parse_result = MessageParseResult.MESSAGE_PARSE_NONE + robot_result_state = 0 # note: since I am writing both the library in c and python, # I wanted to make the logic the same between both, so it could be easier @@ -116,16 +121,12 @@ def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: elif state == MessageReadState.MESSAGE_PAYLOAD: - # id valid check + # assign/store id if payload_idx == 0: message_id = current_char - # process payload based on ID - if message_id == 0: # example 'processing' - result_string = "TEST" - if result_string: # using to suppress error/warning - pass - + if result_string: + pass # actual payload range check # (this is implemented slightly differently # as python for loops work differently) @@ -138,6 +139,9 @@ def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: if current_char == TAIL: state = MessageReadState.MESSAGE_ERROR else: + robot_result_state = compute_module_id.\ + messaging_process_payload(payload_string[1:], + message_id) state = MessageReadState.MESSAGE_TAIL elif state == MessageReadState.MESSAGE_TAIL: @@ -157,6 +161,8 @@ def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: if current_char == 0x0D and next_char == 0x0A: message_parse_result = MessageParseResult.MESSAGE_PARSE_SUCCESS state = MessageReadState.MESSAGE_HEAD + robot_state = robot_result_state + print(str(robot_state)[0:0]) # silence error break else: state = MessageReadState.MESSAGE_ERROR @@ -168,7 +174,7 @@ def messaging_readbuffer(buffer_string: bytes, result_string: bytes) -> bool: return message_parse_result -def messaging_write_message(payload: str) -> bytes: +def messaging_write_message(payload: bytes) -> bytes: """ WriteMessage: @@ -184,10 +190,10 @@ def messaging_write_message(payload: str) -> bytes: message (str): string that the message will be written to """ payload_len = len(payload) - payload_checksum = messaging_bsd_checksum(payload.encode()) + payload_checksum = messaging_bsd_checksum(payload) converted_payload = [] for char in payload: - converted_payload.append(ord(char)) + converted_payload.append(char) bytes_return = bytes([HEAD, payload_len] + converted_payload + [TAIL, payload_checksum, 13, 10]) return bytes_return diff --git a/dev/messaging-library/messaging_test.py b/dev/messaging-library/messaging_test.py index 3df1dc1..45b2c5f 100644 --- a/dev/messaging-library/messaging_test.py +++ b/dev/messaging-library/messaging_test.py @@ -4,163 +4,165 @@ MIN = b"\x00" MAX = b"\x7F" -print("Testing bsd checksum") -PAYLOAD = b"\x01\x01" -c = messaging_library.messaging_bsd_checksum(PAYLOAD) -print(c) +# print("Testing bsd checksum") +# PAYLOAD = b"\x80\x40\x13\x33\x33\x40\x13\x33\x33\x40\x13\x33\x33\x40\x13"+\ +# b"\x33\x33\x40\x13\x33\x33\x40\x13\x33\x33" +# c = messaging_library.messaging_bsd_checksum(PAYLOAD) +# print(c) R = "" -# correct message -print("Testing correct message") -MES = b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -# incorrect given payload length -print("Testing too short given payload length") -MES = b"\xCC\x02" + MIN + b"\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing too long given payload length") -MES = b"\xCC\x02" + MAX + b"\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing too short actual payload length") -MES = b"\xCC\x02\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing too long actual payload length") -MES = b"\xCC\x02\x01\x01\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -# newlines are included in multi-line strings, must concat strings -print("Testing limit payload length") -MES = b"\xCC\x7F" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01" + \ - b"\xB9\x01\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing exceed limit payload length") -MES = b"\xCC\x80" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ - b"\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -# incorrect checksum -print("Testing incorrect checksum") -MES = b"\xCC\x02\x01\x01\x01\xB9" + MIN + b"\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -# fixed character test (HEAD, TAIL, DELIMITERS) -print("Testing incorrect head") -MES = MIN + b"\x02\x01\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing incorrect tail") -MES = b"\xCC\x02\x01\x01" + MIN + b"\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing incorrect delimiter 1\n") -MES = b"\xCC\x02\x01\x01\xB9\x81" + MIN + b"\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing incorrect delimiter 2\n") -MES = b"\xCC\x02\x01\x01\xB9\x81\x0D" + MIN -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -# message structure test -print("Testing incorrect structure") -MES = b"\xCC" + MIN + b"\x02\x01\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -MES = b"\xCC\x02" + MIN + b"\x01\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -MES = b"\xCC\x02\x01\x01" + MIN + b"\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -MES = b"\xCC\x02\x01\x01\xB9" + MIN + b"\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -MES = b"\xCC\x02\x01\x01\xB9\x81" + MIN + b"\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -MES = b"\xCC\x02\x01\x01\xB9\x81\x0D" + MIN + b"\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing characters before/after message") -MES = MIN + b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -MES = b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" + MIN -print(messaging_library.messaging_readbuffer(MES, R)) -print(R) - -print("Testing WriteMessage") -# put python encoded message here -MES = "TEST_TEST_TEST,1,2,34545edrdfgCCV_+{}|\\\\\\\'\b\b67879$^&" +\ - "*^#$54rtyfghvnghn65876878nyghvgnh658766$--+P]" -R = messaging_library.messaging_write_message(MES) -print("Python output: ", end="") -print(R) -print("C version: ", end="") -print("{", end="") -for char in list(R): - if char == 10: - print(hex(char), end="") - else: - print(hex(char), end=",") -print("};", end="\n\n") +robot_state = 0 +# # correct message +# print("Testing correct message") +# MES = b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# # incorrect given payload length +# print("Testing too short given payload length") +# MES = b"\xCC\x02" + MIN + b"\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing too long given payload length") +# MES = b"\xCC\x02" + MAX + b"\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing too short actual payload length") +# MES = b"\xCC\x02\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing too long actual payload length") +# MES = b"\xCC\x02\x01\x01\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# # newlines are included in multi-line strings, must concat strings +# print("Testing limit payload length") +# MES = b"\xCC\x7F" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\xB9\x01\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing exceed limit payload length") +# MES = b"\xCC\x80" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\x01\x01\x01\x01\x01\x01\x01\x01" + \ +# b"\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# # incorrect checksum +# print("Testing incorrect checksum") +# MES = b"\xCC\x02\x01\x01\x01\xB9" + MIN + b"\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# # fixed character test (HEAD, TAIL, DELIMITERS) +# print("Testing incorrect head") +# MES = MIN + b"\x02\x01\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing incorrect tail") +# MES = b"\xCC\x02\x01\x01" + MIN + b"\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing incorrect delimiter 1\n") +# MES = b"\xCC\x02\x01\x01\xB9\x81" + MIN + b"\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing incorrect delimiter 2\n") +# MES = b"\xCC\x02\x01\x01\xB9\x81\x0D" + MIN +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# # message structure test +# print("Testing incorrect structure") +# MES = b"\xCC" + MIN + b"\x02\x01\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# MES = b"\xCC\x02" + MIN + b"\x01\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# MES = b"\xCC\x02\x01\x01" + MIN + b"\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# MES = b"\xCC\x02\x01\x01\xB9" + MIN + b"\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# MES = b"\xCC\x02\x01\x01\xB9\x81" + MIN + b"\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# MES = b"\xCC\x02\x01\x01\xB9\x81\x0D" + MIN + b"\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing characters before/after message") +# MES = MIN + b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# MES = b"\xCC\x02\x01\x01\xB9\x81\x0D\x0A" + MIN +# print(messaging_library.messaging_readbuffer(MES, R, robot_state)) +# print(R) + +# print("Testing WriteMessage") +# # put python encoded message here +# MES = b"\x80\x40\x13\x33\x33\x40\x13\x33\x33\x40\x13\x33\x33\x40\x13\x33"+\ +# b"\x33\x40\x13\x33\x33\x40\x13\x33\x33" +# R = messaging_library.messaging_write_message(MES) +# print("Python output: ", end="") +# print(R) +# print("C version: ", end="") +# print("{", end="") +# for char in list(R): +# if char == 10: +# print(hex(char), end="") +# else: +# print(hex(char), end=",") +# print("};", end="\n\n") print("Testing encode/decode between libraries") # put c test harness output message here MES = b"\xCC\x1B\x54\x45\x53\x54\x5F\x54\x45\x53\x54\x5F\x54\x45\x53\x54" +\ b"\x2C\x31\x2C\x32\x2C\x33\x24\x5E\x26\x2A\x5E\x23\x24\xB9\xAD\x0D\x0A\x00" -print(messaging_library.messaging_readbuffer(MES, R)) +print(messaging_library.messaging_readbuffer(MES, R, robot_state)) diff --git a/dev/messaging-library/swerve-module-id.h b/dev/messaging-library/swerve-module-id.h new file mode 100644 index 0000000..e9537df --- /dev/null +++ b/dev/messaging-library/swerve-module-id.h @@ -0,0 +1,45 @@ +/** + * Defined list of valid message ids and helper function to check and process messages into actions + */ +#ifndef _MESSAGING_IDS + #define _MESSAGING_IDS + + #define MOVE_IN_DIRECTION 0x80 + + #define resultArraySize 6 + + enum messageProcessResult { + PAYLOAD_ID_NOT_FOUND, + STATE_READ_RESULT_ARRAY + }; + + float resultArray[resultArraySize] = {0}; + char flippedBinaryArray[sizeof(float) * resultArraySize] = {0}; + /** + * ProcessPayload: reads parameter of specified length and performs actions based on the id + * + * @param payload pointer to beginning of payload + * @param id id of payload + * @param len length of payload + * + * @returns success/failure to process payload, next state of main state machine + */ + char MessagingProcessPayload(char* payload, unsigned char id, unsigned char len){ + switch(id){ + case MOVE_IN_DIRECTION: + // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + // store buffer array of uint8 (or char) to n floats + // bytes are stored in the wrong direction, flip and rewrite + + for(char i=0;i Date: Tue, 9 Jul 2024 03:28:52 -0700 Subject: [PATCH 59/65] moved old swerve-decoder code moved old swerve-decoder folder into _OLD --- swerve_pico/{ => _OLD}/swerve_decoder/CMakeLists.txt | 0 swerve_pico/{ => _OLD}/swerve_decoder/pico_sdk_import.cmake | 0 swerve_pico/{ => _OLD}/swerve_decoder/swerve_decoder.cpp | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename swerve_pico/{ => _OLD}/swerve_decoder/CMakeLists.txt (100%) rename swerve_pico/{ => _OLD}/swerve_decoder/pico_sdk_import.cmake (100%) rename swerve_pico/{ => _OLD}/swerve_decoder/swerve_decoder.cpp (100%) diff --git a/swerve_pico/swerve_decoder/CMakeLists.txt b/swerve_pico/_OLD/swerve_decoder/CMakeLists.txt similarity index 100% rename from swerve_pico/swerve_decoder/CMakeLists.txt rename to swerve_pico/_OLD/swerve_decoder/CMakeLists.txt diff --git a/swerve_pico/swerve_decoder/pico_sdk_import.cmake b/swerve_pico/_OLD/swerve_decoder/pico_sdk_import.cmake similarity index 100% rename from swerve_pico/swerve_decoder/pico_sdk_import.cmake rename to swerve_pico/_OLD/swerve_decoder/pico_sdk_import.cmake diff --git a/swerve_pico/swerve_decoder/swerve_decoder.cpp b/swerve_pico/_OLD/swerve_decoder/swerve_decoder.cpp similarity index 100% rename from swerve_pico/swerve_decoder/swerve_decoder.cpp rename to swerve_pico/_OLD/swerve_decoder/swerve_decoder.cpp From 181c70cc84cd7a25eaf8fb8a5cccb33ab4dffcac Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Tue, 9 Jul 2024 03:35:38 -0700 Subject: [PATCH 60/65] made zero() public in zeroing_script --- swerve_pico/zeroing_script/zeroing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swerve_pico/zeroing_script/zeroing.cpp b/swerve_pico/zeroing_script/zeroing.cpp index 60deafd..6397f9f 100644 --- a/swerve_pico/zeroing_script/zeroing.cpp +++ b/swerve_pico/zeroing_script/zeroing.cpp @@ -25,7 +25,6 @@ class Zeroing { //store value of sensor return this->zerod; } - private: void zero() { //put in here for now while (!(this->Read())) { this->motor1.update(1); @@ -33,6 +32,7 @@ class Zeroing { //store value of sensor this->motor3.update(1); } } + private: int readPin; int zerod = false; PID motor1; From 86976a38ba0480d5ae8ff22658e39613e506e7e2 Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Tue, 9 Jul 2024 04:33:43 -0700 Subject: [PATCH 61/65] Added main function Added top level code that might get the swerve drive working (with enough editing to everything else...) --- .vscode/settings.json | 3 +- swerve_pico/main.c | 201 ++++++++++++++++++ .../messaging-library/messaging-library.h | 178 ++++++++++++++++ .../messaging-library/swerve-module-id.h | 46 ++++ swerve_pico/pid_control/pid.cpp | 2 +- swerve_pico/zeroing_script/zeroing.cpp | 28 ++- 6 files changed, 450 insertions(+), 8 deletions(-) create mode 100644 swerve_pico/main.c create mode 100644 swerve_pico/messaging-library/messaging-library.h create mode 100644 swerve_pico/messaging-library/swerve-module-id.h diff --git a/.vscode/settings.json b/.vscode/settings.json index 6a693a1..4b0fd24 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "initializer_list": "c", "type_traits": "c", "xstring": "c", - "xutility": "c" + "xutility": "c", + "zeroing.cpp": "c" } } \ No newline at end of file diff --git a/swerve_pico/main.c b/swerve_pico/main.c new file mode 100644 index 0000000..b45744d --- /dev/null +++ b/swerve_pico/main.c @@ -0,0 +1,201 @@ +#include + +#include "quadrature_encoder.cpp" +#include "SwerveModule.cpp" +#include "messaging-library.h" +#include "zeroing.cpp" + +char robotState = 0; + +uint EncoderFactory::encoder_count = 0; + + + +// Define constants for I2C communication +#define I2C_PICO_ADDR 0x08 +#define I2C_SDA_PIN 0 +#define I2C_SCL_PIN 1 +#define I2C_PORT i2c0 +#define I2C_BAUDRATE 100 * 1000 + +// Define the length of the data packet +#define I2C_DATA_LENGTH 10 + +#define MESSAGE_START 0xFA +#define MESSAGE_STOP 0xFB + + +// digital low on in# pins indicates direction, both high is no signal +#define turn_in1_pin 4 // 1A, forward direction +#define turn_in2_pin 5 // 1B, backward direction + +// #define motor_pwm_pin 9 // 2A, 2B take up by motor speed +#define turn_pwm_pin 9 // 2A, turn motor speed +#define wheel_pwm_pin 8 // 2B, wheel motor speed +#define pwm_slice 4 +#define turn_channel PWM_CHAN_B +#define wheel_channel PWM_CHAN_A + +#define wheel_in1_pin 6 // 3A, forward direction +#define wheel_in2_pin 7 // 3B, backard direction + +// #define freq 500 // note: use clock management frequencies to set frequency +// #define duty_cycle 1 +#define count_max 65535 + + +// Buffer for incoming data +uint8_t incoming_data[I2C_DATA_LENGTH]; + +// Status of the input data +uint8_t input_status = 0; + +// Last event that occurred +int last_event = 0; + +// Index of the current data byte +int data_index = 0; + +// Buffer for the input data +uint8_t input[I2C_DATA_LENGTH - 2]; + +// Handler for I2C events +static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) +{ + switch (event) + { + case I2C_SLAVE_RECEIVE:{ + // Read the data + uint8_t tmp = i2c_read_byte_raw(i2c); + // Check if the data is valid + // TODO: probably revert this back to the original, we don't really need the MESSAGE_START stuff + if ((incoming_data[0] == 0x00 && tmp != MESSAGE_START) || data_index >= I2C_DATA_LENGTH) + { + printf("Invalid data %x\n", tmp); + break; + } + // Store the data + incoming_data[data_index] = tmp; + // printf("Data: %d\n", incoming_data[data_index]); + data_index++; + // set the event status to received + last_event = 1; + break; + } + + + case I2C_SLAVE_REQUEST: // Pi is requesting data + // Write the data into the void + i2c_write_byte_raw(i2c, (uint8_t)input_status); + // set the event status to sent + last_event = 2; + break; + + case I2C_SLAVE_FINISH: // Pi has signalled Stop / Restart + // if the last event was a receive event and the data is valid + if (last_event == 1) + if (incoming_data[0] == MESSAGE_START && incoming_data[I2C_DATA_LENGTH - 1] == MESSAGE_STOP) + { + // move the data into the input array + for (int i = 0; i < I2C_DATA_LENGTH - 2; i++) + { + input[i] = (int)incoming_data[i + 1]; + } + // set the input status to ready + input_status = 1; + + // Reset incoming_data + for (int i = 0; i < I2C_DATA_LENGTH; i++) + { + incoming_data[i] = 0x00; + } + } + data_index = 0; + break; + default: + break; + } +} + + + +int main(){ + /** + * DEFINE ENCODERS + * DEFINE PID OBJ + * DEFINE PWM_H_BRIDGE + * DEFINE ZEROING + */ + + Encoder steer1 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); + Encoder drive1 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT); + + Encoder steer2 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); + Encoder drive2 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT); + + Encoder steer3 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); + Encoder drive3 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT); + + PID steer1PID = PID(0,0,0,steer1); + PID drive1PID = PID(0,0,0,drive1); + + PID steer2PID = PID(0,0,0,steer2); + PID drive2PID = PID(0,0,0,drive2); + + PID steer3PID = PID(0,0,0,steer3); + PID drive3PID = PID(0,0,0,drive3); + + SwerveModule module1 = SwerveModule(0,0,0,steer1PID,drive1PID); + SwerveModule module2 = SwerveModule(0,0,0,steer2PID,drive2PID); + SwerveModule module3 = SwerveModule(0,0,0,steer3PID,drive3PID); + + Zeroing zeroing = Zeroing(0,0,0,steer1,steer2,steer3); + /** + * SETUP EVERYTHING + */ + steer1PID.setup(); + drive1PID.setup(); + + steer2PID.setup(); + drive2PID.setup(); + + steer3PID.setup(); + drive3PID.setup(); + + module1.Setup(); + module2.Setup(); + module3.Setup(); + + + //I2C Setup + + stdio_init_all(); + + // Initialize I2C at 100kHz + i2c_init(I2C_PORT, I2C_BAUDRATE); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + + // Set I2C address for Pico + i2c_slave_init(I2C_PORT, I2C_PICO_ADDR, &i2c_handler); + + + while(1){ + if(robotState == STATE_READ_RESULT_ARRAY){ + module1.updatePID(resultArray[0],resultArray[1],PID_BOTH_SELECTION); + module2.updatePID(resultArray[2],resultArray[3],PID_BOTH_SELECTION); + module3.updatePID(resultArray[4],resultArray[5],PID_BOTH_SELECTION); + }else if(robotState == STATE_ZERO_MOTORS){ + zeroing.Zero(); + }else if(robotState == STATE_RETURN_DATA){ + char returnMessage[I2C_DATA_LENGTH] = {0}; + MessagingWriteMessage("DATAAAAAAAA", returnMessage); + // SEND DATAA + } + if(input_stats == 1){ + char* resultString; + MessagingReadBuffer(incoming_data,resultString,I2C_DATA_LENGTH,&robotState); + } + } + return 0; +} \ No newline at end of file diff --git a/swerve_pico/messaging-library/messaging-library.h b/swerve_pico/messaging-library/messaging-library.h new file mode 100644 index 0000000..0cc3e05 --- /dev/null +++ b/swerve_pico/messaging-library/messaging-library.h @@ -0,0 +1,178 @@ +#include "swerve-module-id.h" + +#define PAYLOAD_MAX_LEN 127 +#define HEAD_ID 0xCC +#define TAIL_ID 0xB9 + +enum messageParseResult { + MESSAGE_BUFFER_OVERFLOW = -2, + MESSAGE_PARSE_FAILURE, + MESSAGE_PARSE_NONE, + MESSAGE_PARSE_SUCCESS +}; + +enum messageReadState { + MESSAGE_ERROR = -1, + MESSAGE_HEAD, + MESSAGE_LENGTH, + MESSAGE_PAYLOAD, + MESSAGE_TAIL, + MESSAGE_CHECKSUM, + MESSAGE_END +}; + +/** + * BSDChecksum: Calculates checksum for messaging, using BSDChecksum algorithm. The max length is 128 bytes. The first byte is the message ID, the next 127 bytes is the payload + * + * @param payloadString string of payload and id, 128 bytes max + * @param payloadLen length of payload with id + * + * @return calculated checksum + */ +char MessagingBSDChecksum(char* payloadString, unsigned char payloadLen) { + unsigned char checksum = 0; + for(short i = 0; i < payloadLen; i++) { + checksum = (checksum >> 1) | (checksum << 7); + checksum += payloadString[i]; + } + return checksum; +} + +/** + * ReadBuffer: Uses state machine to take the buffer parameter and processes the message into the result string; + * Returns true/false based on if conversion was successful. Changes value of robotState if message is valid. + * + * \nPacket Structure: + * HEAD LENGTH PAYLOAD TAIL CHECKSUM END + * 1 1 (1)<128 1 1 \r\n + * + * Head: 1 byte 0xCC + * Length: 1 byte, length of payload in bytes + * Payload: Variable length < 128 bytes, always proceeded by a 1 byte ID + * Tail: 1 byte 0xB9 + * Checksum: 1 byte checksum calculated over payload using BSDChecksum algorithm + * End: 2 bytes \r\n + * + * @param bufferString string that will be processed + * @param resultString string that the result is stored in + * @param bufferLength length of buffer function parses through + * @param robotState pointer to variable containing state of robot that is modified + * when a message is fully processed + * + * @return true or false based on success of function + */ +char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLength, char* robotState) { // turn result to struct later + short idx = 0; + char state = MESSAGE_HEAD; + char currentChar = bufferString[idx]; + char payloadLen = 0; + char payloadIdx = 0; + char messageID = 0; + char calculatedChecksum = 0; + char messageParseResult = MESSAGE_PARSE_NONE; + char robotResultState = 0; + + while (idx < bufferLength) { + if(state == MESSAGE_HEAD) { + if(currentChar == (char)HEAD_ID) { + state = MESSAGE_LENGTH; + } + }else if(state == MESSAGE_LENGTH) { + payloadLen = currentChar; + + // expected payload range check + if(payloadLen < 2) { + state = MESSAGE_ERROR; + } + + payloadIdx = 0; + char* payload = bufferString + idx + 1; + calculatedChecksum = MessagingBSDChecksum(payload, payloadLen); + state = MESSAGE_PAYLOAD; + + }else if(state == MESSAGE_PAYLOAD) { + + // assign/store id + if(payloadIdx == 0) { + messageID = currentChar; + } + + // actual payload length check + if(payloadIdx < payloadLen - 1) { + if(currentChar == (char)TAIL_ID) { + state = MESSAGE_ERROR; + }else { + payloadIdx++; + } + }else { + if(currentChar == (char)TAIL_ID) { + state = MESSAGE_ERROR; + }else { + robotResultState = MessagingProcessPayload(bufferString + idx - payloadLen + 2, messageID, payloadLen - 1); + state = MESSAGE_TAIL; + } + } + + }else if(state == MESSAGE_TAIL) { + if(currentChar == (char)TAIL_ID) { + state = MESSAGE_CHECKSUM; + }else{ + state = MESSAGE_ERROR; + } + + }else if(state == MESSAGE_CHECKSUM) { + if(calculatedChecksum != currentChar) { + state = MESSAGE_ERROR; + }else { + state = MESSAGE_END; + } + + }else if(state == MESSAGE_END){ + char nextChar = bufferString[idx + 1]; + if(currentChar == '\r' && nextChar == '\n') { + messageParseResult = MESSAGE_PARSE_SUCCESS; + state = MESSAGE_HEAD; + *robotState = robotResultState; + break; + }else { + state = MESSAGE_ERROR; + } + + }else { + messageParseResult = MESSAGE_PARSE_FAILURE; + break; + } + + idx++; + currentChar = bufferString[idx]; + } + + if(state != MESSAGE_HEAD && state != MESSAGE_ERROR) { + messageParseResult = MESSAGE_BUFFER_OVERFLOW; + } + + return messageParseResult; +} + +/** + * WriteMessage: converts parameter payload into a valid message string and writes it to parameter message. + * Payload corresponds to PAYLOAD in the packet structure: [HEAD, LENGTH, PAYLOAD, TAIL, CHECKSUM, END] + * + * @param payload string that will be converted to a valid message format + * @param message string that the message will be written to + * + * @return None + */ +void MessagingWriteMessage(unsigned char payload[], char message[]){ + char payloadLen = 0; + for(;payload[payloadLen] != '\0'; payloadLen++){ + message[payloadLen + 2] = payload[payloadLen]; + } + message[0] = (char)HEAD_ID; + message[1] = payloadLen; + message[payloadLen + 2] = (char)TAIL_ID; + message[payloadLen + 3] = MessagingBSDChecksum(payload, payloadLen); + message[payloadLen + 4] = '\r'; + message[payloadLen + 5] = '\n'; + message[payloadLen + 6] = '\0'; +} diff --git a/swerve_pico/messaging-library/swerve-module-id.h b/swerve_pico/messaging-library/swerve-module-id.h new file mode 100644 index 0000000..4a32e05 --- /dev/null +++ b/swerve_pico/messaging-library/swerve-module-id.h @@ -0,0 +1,46 @@ +/** + * Defined list of valid message ids and helper function to check and process messages into actions + */ +#ifndef _MESSAGING_IDS + #define _MESSAGING_IDS + + #define MOVE_IN_DIRECTION 0x80 + + #define resultArraySize 6 + + enum messageProcessResult { + PAYLOAD_ID_NOT_FOUND, + STATE_READ_RESULT_ARRAY, + STATE_ZERO_MOTORS + }; + + float resultArray[resultArraySize] = {0}; + char flippedBinaryArray[sizeof(float) * resultArraySize] = {0}; + /** + * ProcessPayload: reads parameter of specified length and performs actions based on the id + * + * @param payload pointer to beginning of payload + * @param id id of payload + * @param len length of payload + * + * @returns success/failure to process payload, next state of main state machine + */ + char MessagingProcessPayload(char* payload, unsigned char id, unsigned char len){ + switch(id){ + case MOVE_IN_DIRECTION: + // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + // store buffer array of uint8 (or char) to n floats + // bytes are stored in the wrong direction, flip and rewrite + + for(char i=0;iencoder = encoder; this->timestep = timestep //timestep for updating output this->substep = this->timestep / (float)substep // how many times to update integral/derivative per timestep - this->swerveModule = swerveModule; + // this->swerveModule = swerveModule; //WHY IS THIS HERE? } void setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks diff --git a/swerve_pico/zeroing_script/zeroing.cpp b/swerve_pico/zeroing_script/zeroing.cpp index 6397f9f..586562c 100644 --- a/swerve_pico/zeroing_script/zeroing.cpp +++ b/swerve_pico/zeroing_script/zeroing.cpp @@ -9,8 +9,10 @@ class Zeroing { //store value of sensor public: - Zeroing(int pin, PID motor1, PID motor2, PID motor3) { - this->readPin = pin; + Zeroing(int pin1, int pin2, int pin3, PID motor1, PID motor2, PID motor3) { + this->readPin1 = pin1; + this->readPin2 = pin2; + this->readPin3 = pin3; this->motor1 = motor1; this->motor2 = motor2; this->motor3 = motor3; @@ -20,20 +22,34 @@ class Zeroing { //store value of sensor gpio_init(this->readPin); gpio_set_dir(this->readPin, GPIO_IN); } - int Read() { - this->zerod = gpio_get(this->readPin); + int Read1() { + this->zerod = gpio_get(this->readPin1); + return this->zerod; + } + int Read2() { + this->zerod = gpio_get(this->readPin2); + return this->zerod; + } + int Read3() { + this->zerod = gpio_get(this->readPin3); return this->zerod; } void zero() { //put in here for now - while (!(this->Read())) { + while (!(this->Read1())) { this->motor1.update(1); + } + while (!(this->Read2())) { this->motor2.update(1); + } + while (!(this->Read3())) { this->motor3.update(1); } } private: - int readPin; + int readPin1; + int readPin2; + int readPin3; int zerod = false; PID motor1; PID motor2; From 0f2ced2869d101012734cd1bc96fe15805d45cf1 Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Fri, 12 Jul 2024 01:27:22 -0700 Subject: [PATCH 62/65] reorganized swerve_pico code restructured order of PID loop, libraries, etc (add readme of changes) --- .vscode/settings.json | 3 +- .../zeroing_script}/CMakeLists.txt | 0 .../zeroing_script}/pico_sdk_import.cmake | 0 .../{ => _OLD}/zeroing_script/zeroing.cpp | 0 .../control-library/control-library.cpp | 36 +++++ swerve_pico/control-library/pid.cpp | 100 ++++++++++++ swerve_pico/encoder_script/CMakeLists.txt | 26 ---- .../encoder_script/pico_sdk_import.cmake | 62 -------- swerve_pico/main.c | 105 ++++++------- swerve_pico/motor-library/PWMControl.cpp | 88 +++++++++++ swerve_pico/motor-library/motor-library.cpp | 40 +++++ .../quadrature_encoder.cpp | 0 .../quadrature_encoder.pio | 0 swerve_pico/pid_control/pid.cpp | 67 -------- .../pwm_h_bridge_control/CMakeLists.txt | 55 ------- .../pwm_h_bridge_control/SwerveModule.cpp | 146 ------------------ .../pico_sdk_import.cmake | 73 --------- swerve_pico/sensor-library/sensor-library.cpp | 22 +++ swerve_pico/zeroing_script/CMakeLists.txt | 55 ------- .../zeroing_script/pico_sdk_import.cmake | 73 --------- 20 files changed, 339 insertions(+), 612 deletions(-) rename swerve_pico/{pid_control => _OLD/zeroing_script}/CMakeLists.txt (100%) rename swerve_pico/{pid_control => _OLD/zeroing_script}/pico_sdk_import.cmake (100%) rename swerve_pico/{ => _OLD}/zeroing_script/zeroing.cpp (100%) create mode 100644 swerve_pico/control-library/control-library.cpp create mode 100644 swerve_pico/control-library/pid.cpp delete mode 100644 swerve_pico/encoder_script/CMakeLists.txt delete mode 100644 swerve_pico/encoder_script/pico_sdk_import.cmake create mode 100644 swerve_pico/motor-library/PWMControl.cpp create mode 100644 swerve_pico/motor-library/motor-library.cpp rename swerve_pico/{encoder_script => motor-library}/quadrature_encoder.cpp (100%) rename swerve_pico/{encoder_script => motor-library}/quadrature_encoder.pio (100%) delete mode 100644 swerve_pico/pid_control/pid.cpp delete mode 100644 swerve_pico/pwm_h_bridge_control/CMakeLists.txt delete mode 100644 swerve_pico/pwm_h_bridge_control/SwerveModule.cpp delete mode 100644 swerve_pico/pwm_h_bridge_control/pico_sdk_import.cmake create mode 100644 swerve_pico/sensor-library/sensor-library.cpp delete mode 100644 swerve_pico/zeroing_script/CMakeLists.txt delete mode 100644 swerve_pico/zeroing_script/pico_sdk_import.cmake diff --git a/.vscode/settings.json b/.vscode/settings.json index 4b0fd24..a95eb5d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "type_traits": "c", "xstring": "c", "xutility": "c", - "zeroing.cpp": "c" + "zeroing.cpp": "c", + "stdlib.h": "c" } } \ No newline at end of file diff --git a/swerve_pico/pid_control/CMakeLists.txt b/swerve_pico/_OLD/zeroing_script/CMakeLists.txt similarity index 100% rename from swerve_pico/pid_control/CMakeLists.txt rename to swerve_pico/_OLD/zeroing_script/CMakeLists.txt diff --git a/swerve_pico/pid_control/pico_sdk_import.cmake b/swerve_pico/_OLD/zeroing_script/pico_sdk_import.cmake similarity index 100% rename from swerve_pico/pid_control/pico_sdk_import.cmake rename to swerve_pico/_OLD/zeroing_script/pico_sdk_import.cmake diff --git a/swerve_pico/zeroing_script/zeroing.cpp b/swerve_pico/_OLD/zeroing_script/zeroing.cpp similarity index 100% rename from swerve_pico/zeroing_script/zeroing.cpp rename to swerve_pico/_OLD/zeroing_script/zeroing.cpp diff --git a/swerve_pico/control-library/control-library.cpp b/swerve_pico/control-library/control-library.cpp new file mode 100644 index 0000000..d6a3eb8 --- /dev/null +++ b/swerve_pico/control-library/control-library.cpp @@ -0,0 +1,36 @@ + +#include "pid.cpp" + +/** + * Class to hold two pids, represents one swerve module (one turn wheel and one drive wheel) + */ +class SwerveDrive{ + SwerveDrive(PID turnPID, PID drivePID){ + this->turnPID = turnPID; + this->drivePID = drivePID; + } + + void Setup(){ + this->turnPID.Setup(); + this->drivePID.Setup(); + } + + void SetPidTargets(float turnMotorPercentage, float wheelMotorPercentage){ + this->turnPID.updateTarget(turnMotorPercentage); + this->drivePID.updateTarget(wheelMotorPercentage); + } + + void TogglePids(bool value){ + this->turnPID.setActive(value); + this->drivePID.setActive(value); + } + + void ResetPids(){ + this->turnPID.reset(); + this->drivePID.reset(); + } + + private: + PID turnPID; + PID drivePID; +}; \ No newline at end of file diff --git a/swerve_pico/control-library/pid.cpp b/swerve_pico/control-library/pid.cpp new file mode 100644 index 0000000..c4f9b3c --- /dev/null +++ b/swerve_pico/control-library/pid.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include + +#include "motor-library.cpp" + +#define MS_IN_SEC 1000 //DOUBLE CHECK VALUE OF CONVERSION + +void calculate(); +void updateErrorIntegralDerivative(); + +enum PIDTuningMode{ + PID_TUNING_MODE_POSITION, + PID_TUNING_MODE_VELOCITY +}; + +class PID { //pid loop + public: + PID(float P, float I, float D, Motor motor, char tuningMode = PID_TUNING_MODE_POSITION, int timestep = 100, int substep = 10) { + this->P = P; + this->I = I; + this->D = D; + this->motor = motor; + this->timestep = timestep //timestep for updating output + this->substep = this->timestep / (float)substep // how many times to update integral/derivative per timestep + this->tuningMode = tuningMode; + } + + void setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks + this->timer = repeating_timer_t timer; + add_repeating_timer_ms(this->timestep, calculate, NULL, timer); + add_repeating_timer_ms(this->substep, updateErrorIntegralDerivative, NULL, timer); + this->motor.Setup(); + this->active = true; + } + + void updateTarget(float target){ + //Idea; pid loop is autonomous and will automatically do whatever due to repeating timers + //if we just update the target with a function call we might be okay + this->target = target; + } + + void reset(){ + this->output = 0; + this->target = 0; + this->lastError = 0; + } + + /*** + * setActive: sets flag to toggle function of pid callbacks + */ + void setActive(bool active){ + this->active = active; + } + private: + //privated functions for pid function + void calculate(){ + if(this->active){ + this->output = this->P * this->target + this->I * this->Integral + this->D * this->Derivative; + this->motor.SetPwmPercentage(output); + } + } + + void updateErrorIntegralDerivative(){ + if(this->active){ + float error; //error term + if(this->tuningMode = PID_TUNING_MODE_POSITION){ + error = this->motor.getPosition() - this->target; + }else{ + error = this->motor.GetVelocity() - this->target; + } + + //substep is in miliseconds + this->Integral += error * (this->substep / MS_IN_SEC); //left riemann sum at fixed substep + this->Derivative = (error - this->lastError) / (this->substep / MS_IN_SEC); //estimate of derivatvie at fixed substep + this->lastError = error; + } + } + + float P; + float I; + float D; + + float target = 0; + float output = 0; + float input = 0; + float lastError = 0; + + float Integral = 0; + float Derivative = 0; + repeating_timer_t timer = {}; + int timestep; + int substep; + Motor motor; + char tuningMode; + bool active = false; +}; \ No newline at end of file diff --git a/swerve_pico/encoder_script/CMakeLists.txt b/swerve_pico/encoder_script/CMakeLists.txt deleted file mode 100644 index fc8ee89..0000000 --- a/swerve_pico/encoder_script/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.12) -include(pico_sdk_import.cmake) -project(quadrature_encoder VERSION 1.0.0) - -add_executable(quadrature_encoder quadrature_encoder.cpp) - -set(PICO_CXX_ENABLE_EXCEPTIONS 1) - -pico_sdk_init() - -target_sources(quadrature_encoder PRIVATE quadrature_encoder.cpp) - -target_link_libraries(quadrature_encoder PRIVATE - pico_stdlib - hardware_pio - ) - -pico_generate_pio_header(quadrature_encoder ${CMAKE_CURRENT_LIST_DIR}/quadrature_encoder.pio) - -pico_add_extra_outputs(quadrature_encoder) - -# enable usb output, disable uart output -pico_enable_stdio_usb(quadrature_encoder 1) -pico_enable_stdio_uart(quadrature_encoder 0) - - diff --git a/swerve_pico/encoder_script/pico_sdk_import.cmake b/swerve_pico/encoder_script/pico_sdk_import.cmake deleted file mode 100644 index 28efe9e..0000000 --- a/swerve_pico/encoder_script/pico_sdk_import.cmake +++ /dev/null @@ -1,62 +0,0 @@ -# This is a copy of /external/pico_sdk_import.cmake - -# This can be dropped into an external project to help locate this SDK -# It should be include()ed prior to project() - -if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) - set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) - message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) - set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) - message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) - set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) - message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") -endif () - -set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") -set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") -set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") - -if (NOT PICO_SDK_PATH) - if (PICO_SDK_FETCH_FROM_GIT) - include(FetchContent) - set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) - if (PICO_SDK_FETCH_FROM_GIT_PATH) - get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") - endif () - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - ) - if (NOT pico_sdk) - message("Downloading Raspberry Pi Pico SDK") - FetchContent_Populate(pico_sdk) - set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) - endif () - set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) - else () - message(FATAL_ERROR - "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." - ) - endif () -endif () - -get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") -if (NOT EXISTS ${PICO_SDK_PATH}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") -endif () - -set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) -if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") -endif () - -set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) - -include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/swerve_pico/main.c b/swerve_pico/main.c index b45744d..902e21e 100644 --- a/swerve_pico/main.c +++ b/swerve_pico/main.c @@ -1,15 +1,12 @@ #include -#include "quadrature_encoder.cpp" -#include "SwerveModule.cpp" +#include "motor-library.cpp" #include "messaging-library.h" -#include "zeroing.cpp" +#include "control-library.cpp" +#include "sensor-library.cpp" char robotState = 0; -uint EncoderFactory::encoder_count = 0; - - // Define constants for I2C communication #define I2C_PICO_ADDR 0x08 @@ -121,51 +118,29 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) int main(){ /** - * DEFINE ENCODERS - * DEFINE PID OBJ - * DEFINE PWM_H_BRIDGE - * DEFINE ZEROING + * DEFINE AND SETUP MOTORS */ - - Encoder steer1 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); - Encoder drive1 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT); - - Encoder steer2 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); - Encoder drive2 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT); - - Encoder steer3 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); - Encoder drive3 = EncoderFactory::createEncoder(0, ROT_PER_TICK * DEG_PER_ROT); - - PID steer1PID = PID(0,0,0,steer1); - PID drive1PID = PID(0,0,0,drive1); - - PID steer2PID = PID(0,0,0,steer2); - PID drive2PID = PID(0,0,0,drive2); - - PID steer3PID = PID(0,0,0,steer3); - PID drive3PID = PID(0,0,0,drive3); - - SwerveModule module1 = SwerveModule(0,0,0,steer1PID,drive1PID); - SwerveModule module2 = SwerveModule(0,0,0,steer2PID,drive2PID); - SwerveModule module3 = SwerveModule(0,0,0,steer3PID,drive3PID); - - Zeroing zeroing = Zeroing(0,0,0,steer1,steer2,steer3); - /** - * SETUP EVERYTHING - */ - steer1PID.setup(); - drive1PID.setup(); - - steer2PID.setup(); - drive2PID.setup(); - - steer3PID.setup(); - drive3PID.setup(); - - module1.Setup(); - module2.Setup(); - module3.Setup(); - + Motor turn_motor1 = new Motor(0,0,0); + Motor drive_motor1 = new Motor(0,0,0); + Motor turn_motor2 = new Motor(0,0,0); + Motor drive_motor2 = new Motor(0,0,0); + Motor turn_motor3 = new Motor(0,0,0); + Motor drive_motor3 = new Motor(0,0,0); + + PID pid1 = new PID(0,0,0, turn_motor1); + PID pid2 = new PID(0,0,0, drive_motor1); + PID pid3 = new PID(0,0,0, turn_motor2); + PID pid4 = new PID(0,0,0, drive_motor2); + PID pid5 = new PID(0,0,0, turn_motor3); + PID pid6 = new PID(0,0,0, drive_motor3); + + SwerveModule module1 = new SwerveModule(pid1,pid2); + SwerveModule module2 = new SwerveModule(pid3,pid4); + SwerveModule module3 = new SwerveModule(pid5,pid6); + + ZeroingSensor sensor1 = new ZeroingSensor(0); + ZeroingSensor sensor2 = new ZeroingSensor(0); + ZeroingSensor sensor3 = new ZeroingSensor(0); //I2C Setup @@ -182,11 +157,33 @@ int main(){ while(1){ if(robotState == STATE_READ_RESULT_ARRAY){ - module1.updatePID(resultArray[0],resultArray[1],PID_BOTH_SELECTION); - module2.updatePID(resultArray[2],resultArray[3],PID_BOTH_SELECTION); - module3.updatePID(resultArray[4],resultArray[5],PID_BOTH_SELECTION); + module1.SetPidTargets(resultArray[0],resultArray[1]); + module2.SetPidTargets(resultArray[2],resultArray[3]); + module3.SetPidTargets(resultArray[4],resultArray[5]); }else if(robotState == STATE_ZERO_MOTORS){ - zeroing.Zero(); + module1.TogglePids(false); + module2.TogglePids(false); + module3.TogglePids(false); + while(!sensor1.Read()){ + turn_motor1.SetPwmPercentage(0.1); + } + turn_motor1.SetPwmPercentage(0); + while(!sensor2.Read()){ + turn_motor2.SetPwmPercentage(0.1); + } + turn_motor2.SetPwmPercentage(0); + while(!sensor3.Read()){ + turn_motor3.SetPwmPercentage(0.1); + } + turn_motor3.SetPwmPercentage(0); + + module1.ResetPids(); + module2.ResetPids(); + module3.ResetPids(); + + module1.TogglePids(true); + module2.TogglePids(true); + module3.TogglePids(true); }else if(robotState == STATE_RETURN_DATA){ char returnMessage[I2C_DATA_LENGTH] = {0}; MessagingWriteMessage("DATAAAAAAAA", returnMessage); diff --git a/swerve_pico/motor-library/PWMControl.cpp b/swerve_pico/motor-library/PWMControl.cpp new file mode 100644 index 0000000..d712d94 --- /dev/null +++ b/swerve_pico/motor-library/PWMControl.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include + + +#define COUNT_MAX 65535 + +// class to instantiate swerve module +class PWMControl { + public: + // store all pin ids + // swerveModule is controlled by PID loops, module control is abstracted to PID only! + PWMControl(int motorPin, int pwmPin, int chan = PWM_CHAN_A, int countMax = COUNT_MAX) { + // two motors per module, each requiring 3 pins + // two pins to define h-bridge control and one pin for pwm (since there are 2 motors, there are 2 pwm pins) + // constructor assumes that all pins are in sets of 2 and will be next to eachother on an even interval (e.g pin 0 and 1, pin 2 and 4, etc) + this->motor_in1_pin = motorPin; // pin set + this->motor_in2_pin = motorPin + 1; + this->pwm_pin = pwmPin; // pwm pin set + // pwm slices take up two gpio pins, assuming you take up two pins for pwm with the first pin being even, + // the slice is whats activated/outputs a signal and is separate from the pins + // the pwm pins must be configured to let the signal pass through + this->pwm_slice = pwm_gpio_to_slice_num(pwmPin); + this->channel = chan; + //code to check & override default values + if(countMax != COUNT_MAX) + this->countMax = countMax; + } + + // initialize all pins + void Setup() { //PICO SPECIFIC + stdio_init_all(); + // h-bridge requires two binary signals for direction, initialize the in1, in2 pins + gpio_init(this->motor_in1_pin); + gpio_init(this->motor_in2_pin); + + // TODO: check if default output signal is 0, for now put this in + // sets all output signals to 0 + gpio_put(this->motor_in1_pin, 0); + gpio_put(this->motor_in2_pin, 0); + + // define pin output for gpio pins + gpio_set_dir(this->motor_in1_pin, GPIO_OUT); + gpio_set_dir(this->motor_in2_pin, GPIO_OUT); + + // define pin output for pwm pins + gpio_set_function(this->pwm_pin, GPIO_FUNC_PWM); + + // set max cycle count of pwm slice (this does counting) + pwm_set_wrap(this->pwm_slice, count_max); + + // set output signal to 0 + // pwm pins can output anything from 0 to countmax of the pwm_slice (this outputs based on the counter) + pwm_set_chan_level(this->pwm_slice, this->channel, 0); + + // activate pwm slice + pwm_set_enabled(this->pwm_slice, true); + } + + + + //drive function control entirely by PID loops + void Drive(float drivePercentange) { // currently assuming turn & wheel are from -1 to 1, probably need to change later + if (drivePercentange == 0) { // in1 and in2 are high + gpio_put(motor_in1_pin, 1); + gpio_put(motor_in2_pin, 1); + } else if (drivePercentange < 0) { // in1 is high and in2 is low + gpio_put(motor_in1_pin, 1); + gpio_put(motor_in2_pin, 0); + } else { // in1 is low and in2 is high + gpio_put(motor_in2_pin, 1); + gpio_put(motor_in1_pin, 0); + } + + // set pwm pin output as % of slice output + pwm_set_chan_level(pwm_slice, channel, abs((int)(drivePercentange * count_max))); + } + + private: + //private variables for storing pins and constants + int motor_in1_pin; + int motor_in2_pin; + int pwm_pin; + int pwm_slice; + int channel = PWM_CHAN_A; + int countMax = COUNT_MAX; +}; \ No newline at end of file diff --git a/swerve_pico/motor-library/motor-library.cpp b/swerve_pico/motor-library/motor-library.cpp new file mode 100644 index 0000000..9064186 --- /dev/null +++ b/swerve_pico/motor-library/motor-library.cpp @@ -0,0 +1,40 @@ +#include "quadrature_encoder.cpp" +#include "PWMControl.cpp" + +uint EncoderFactory::encoder_count = 0; + +/** + * file contains syntactic sugar to make getting/setting motors easier + * created as a lazy way to not have to combine all 3 already working files :/ + */ + +/** + * Motor class that holds PWMControl and quadrature_encoder class, encapsulates functions to set and read values of 1 motor + */ +class Motor{ + public: + Motor(int motorPin, int pwmPin, uint encoderPin, float ratio = 1.0, int chan = PWM_CHAN_A, int countMax = COUNT_MAX){ + this->pwmControl = new PWMControl(motorPin, pwmPin, chan, countMax); + this->encoder = EncoderFactory::createEncoder(encoderPin, ratio); + } + + void Setup(){ + this->pwmControl.Setup(); + } + void SetPwmPercentage(float percentage){ + this->pwmControl.Drive(percentage); + } + + float GetPosition(){ + return this->encoder.get_pos(); + } + + + float GetVelocity(){ + return this->encoder.get_velocity(); + } + + private: + PWMControl pwmControl; + Encoder encoder; +}; \ No newline at end of file diff --git a/swerve_pico/encoder_script/quadrature_encoder.cpp b/swerve_pico/motor-library/quadrature_encoder.cpp similarity index 100% rename from swerve_pico/encoder_script/quadrature_encoder.cpp rename to swerve_pico/motor-library/quadrature_encoder.cpp diff --git a/swerve_pico/encoder_script/quadrature_encoder.pio b/swerve_pico/motor-library/quadrature_encoder.pio similarity index 100% rename from swerve_pico/encoder_script/quadrature_encoder.pio rename to swerve_pico/motor-library/quadrature_encoder.pio diff --git a/swerve_pico/pid_control/pid.cpp b/swerve_pico/pid_control/pid.cpp deleted file mode 100644 index 2b537d9..0000000 --- a/swerve_pico/pid_control/pid.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "quadrature_encoder.cpp" - -void calculate(); -void updateErrorIntegralDerivative(); -class PID { //pid loop - public: - PID(float P, float I, float D, Encoder encoder, int timestep = 100, int substep = 10) { - this->P = P; - this->I = I; - this->D = D; - this->encoder = encoder; - this->timestep = timestep //timestep for updating output - this->substep = this->timestep / (float)substep // how many times to update integral/derivative per timestep - // this->swerveModule = swerveModule; //WHY IS THIS HERE? - } - - void setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks - this->timer = repeating_timer_t timer; - add_repeating_timer_ms(this->timestep, calculate, NULL, timer); - add_repeating_timer_ms(10, updateErrorIntegralDerivative, NULL, timer); // TODO: why is it 10? - } - - void updateTarget(float target){ - //Idea; pid loop is autonomous and will automatically do whatever due to repeating timers - //if we just update the target with a function call we might be okay - this->target = target; - } - - // float getOutput(){ //random messy function for debugging? :( (TLDR: WHY IS THIS HERE IM JUST GONNA KEEP IT HERE ;-;) - // return this->output; - // } - private: - //privated functions for pid function - void calculate(){ - this->output = this->P * this->target + this->I * this->Integral + this->D * this->Derivative; - } - - void updateErrorIntegralDerivative(){ - error = this->encoder.getPosition()-this->target; //error term - this->Integral += error * (this->substep / 1000); //left riemann sum at fixed substep - this->Derivative = (error-this->lastError)/(this->substep / 1000); //estimate of derivatvie at fixed substep - this->lastError = error; - } - - float P; - float I; - float D; - - float target = 0; - float output = 0; - float input = 0; - float lastError = 0; - - float Integral = 0; - float Derivative = 0; - repeating_timer_t timer = {}; - int timestep; - int substep; - Encoder encoder; -}; \ No newline at end of file diff --git a/swerve_pico/pwm_h_bridge_control/CMakeLists.txt b/swerve_pico/pwm_h_bridge_control/CMakeLists.txt deleted file mode 100644 index 914fc65..0000000 --- a/swerve_pico/pwm_h_bridge_control/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# Generated Cmake Pico project file - -cmake_minimum_required(VERSION 3.5) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) - -# Initialise pico_sdk from installed location -# (note this can come from environment, CMake cache etc) - -set(PICO_BOARD pico CACHE STRING "Board type") - -# Pull in Raspberry Pi Pico SDK (must be before project) -include(pico_sdk_import.cmake) - -if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") - message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") -endif() - -project(i2c_drive_pico C CXX ASM) - -# Initialise the Raspberry Pi Pico SDK -pico_sdk_init() - -# Add executable. Default name is the project name, version 0.1 - -add_executable(i2c_drive_pico i2c_drive_pico.c ) - -pico_set_program_name(i2c_drive_pico "i2c_drive_pico") -pico_set_program_version(i2c_drive_pico "0.1") - -pico_enable_stdio_uart(i2c_drive_pico 0) -pico_enable_stdio_usb(i2c_drive_pico 1) - -# Add the standard library to the build -target_link_libraries(i2c_drive_pico - pico_stdlib - pico_i2c_slave - hardware_i2c - hardware_pwm - ) - -# Add the standard include files to the build -target_include_directories(i2c_drive_pico PRIVATE - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required -) - -# Add any user requested libraries -target_link_libraries(i2c_drive_pico - hardware_i2c - ) - -pico_add_extra_outputs(i2c_drive_pico) - diff --git a/swerve_pico/pwm_h_bridge_control/SwerveModule.cpp b/swerve_pico/pwm_h_bridge_control/SwerveModule.cpp deleted file mode 100644 index c17eeda..0000000 --- a/swerve_pico/pwm_h_bridge_control/SwerveModule.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "../pid_control/pid.cpp" - -#define COUNT_MAX 65535 - -// enum for wheel and turn pid loop selection -typedef enum { - PID_TURN_SELECTION = 0, - PID_WHEEL_SELECTION, - PID_BOTH_SELECTION -}PID_Selection; - -// class to instantiate swerve module -class SwerveModule { - public: - // store all pin ids - // swerveModule is controlled by PID loops, module control is abstracted to PID only! - SwerveModule(int turnPin, int wheelPin, int pwmPin, PID wheelPID, PID turnPID, int countMax = COUNT_MAX, int swapPwmChan = 0) { - // two motors per module, each requiring 3 pins - // two pins to define h-bridge control and one pin for pwm (since there are 2 motors, there are 2 pwm pins) - // constructor assumes that all pins are in sets of 2 and will be next to eachother on an even interval (e.g pin 0 and 1, pin 2 and 4, etc) - this->turn_in1_pin = turnPin; // turn pin set - this->turn_in2_pin = turnPin + 1; - this->wheel_in1_pin = wheelPin; // wheel pin set - this->wheel_in2_pin = wheelPin + 1; - this->turn_pwm_pin = pwmPin; // pwm pin set - this->wheel_pwm_pin = pwmPin + 1; - // pwm slices take up two gpio pins, assuming you take up two pins for pwm with the first pin being even, - // dividing by 2 is an easier way to get the id without having to specify in constructor - // the slice is whats activated/outputs a signal and is separate from the pins - // the pwm pins must be configured to let the signal pass through - this->pwm_slice = pwmPin / 2; - //code to check & override default values - if(countMax != COUNT_MAX) - this->countMax = countMax; - if(swapPwmChan){ - this->wheelChan = PWM_CHAN_B; - this->turnChan = PWM_CHAN_A; - } - //PID loop variable setup - this->wheelPID = wheelPID; - this->turnPID = turnPID; - } - - // initialize all pins - void Setup() { //PICO SPECIFIC - stdio_init_all(); - // h-bridge requires two binary signals for direction, initialize the in1, in2 pins - gpio_init(this->turn_in1_pin); - gpio_init(this->turn_in2_pin); - gpio_init(this->wheel_in1_pin); - gpio_init(this->wheel_in2_pin); - - // TODO: check if default output signal is 0, for now put this in - // sets all output signals to 0 - gpio_put(this->turn_in1_pin, 0); - gpio_put(this->turn_in2_pin, 0); - gpio_put(this->wheel_in1_pin, 0); - gpio_put(this->wheel_in2_pin, 0); - - // define pin output for gpio pins - gpio_set_dir(this->turn_in1_pin, GPIO_OUT); - gpio_set_dir(this->turn_in2_pin, GPIO_OUT); - gpio_set_dir(this->wheel_in1_pin, GPIO_OUT); - gpio_set_dir(this->wheel_in2_pin, GPIO_OUT); - - // define pin output for pwm pins - gpio_set_function(this->turn_pwm_pin, GPIO_FUNC_PWM); - gpio_set_function(this->wheel_pwm_pin, GPIO_FUNC_PWM); - - // set max cycle count of pwm slice (this does counting) - pwm_set_wrap(this->pwm_slice, count_max); - - // set output signal to 0 - // pwm pins can output anything from 0 to countmax of the pwm_slice (this outputs based on the counter) - pwm_set_chan_level(this->pwm_slice, this->turn_channel, 0); - pwm_set_chan_level(this->pwm_slice, this->wheel_channel, 0); - - // activate pwm slice - pwm_set_enabled(this->pwm_slice, true); - } - - void updatePID(float turn, float wheel, int selection){ - if(selection == PID_Selection.PID_TURN_SELECTION){ - this->wheelPID.updateTarget(turn); - }else if(selection == PID_Selection.PID_WHEEL_SELECTION){ - this->turnPID.updateTarget(wheel); - }else { - this->wheelPID.updateTarget(turn); - this->turnPID.updateTarget(wheel); - } - } - - - - private: - //private drive function control entirely by PID loops - void Drive(float turn, float wheel) { // currently assuming turn & wheel are from -1 to 1, probably need to change later - if (turn == 0) { // in1 and in2 are high - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 1); - } else if (turn < 0) { // in1 is high and in2 is low - gpio_put(turn_in1_pin, 1); - gpio_put(turn_in2_pin, 0); - } else { // in1 is low and in2 is high - gpio_put(turn_in2_pin, 1); - gpio_put(turn_in1_pin, 0); - } - - // wheel motor - if (wheel == 0) { // in1 and in2 are high - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 1); - } else if (wheel < 0) { // in1 is high and in2 is low - gpio_put(wheel_in1_pin, 1); - gpio_put(wheel_in2_pin, 0); - } else { // in1 is low and in2 is high - gpio_put(wheel_in1_pin, 0); - gpio_put(wheel_in2_pin, 1); - } - - // set pwm pin output as % of slice output - pwm_set_chan_level(pwm_slice, turn_channel, abs((int)(turn * count_max))); - pwm_set_chan_level(pwm_slice, wheel_channel, abs((int)(wheel * count_max))); - } - - //private variables for storing pins and constants - int turn_in1_pin; - int turn_in2_pin; - int wheel_in1_pin; - int wheel_in2_pin; - int turn_pwm_pin; - int wheel_pwm_pin; - int pwm_slice; - int wheel_channel = PWM_CHAN_A; - int turn_channel = PWM_CHAN_B; - int countMax = COUNT_MAX; - PID wheelPID; - PID turnPID; -}; \ No newline at end of file diff --git a/swerve_pico/pwm_h_bridge_control/pico_sdk_import.cmake b/swerve_pico/pwm_h_bridge_control/pico_sdk_import.cmake deleted file mode 100644 index 65f8a6f..0000000 --- a/swerve_pico/pwm_h_bridge_control/pico_sdk_import.cmake +++ /dev/null @@ -1,73 +0,0 @@ -# This is a copy of /external/pico_sdk_import.cmake - -# This can be dropped into an external project to help locate this SDK -# It should be include()ed prior to project() - -if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) - set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) - message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) - set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) - message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) - set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) - message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") -endif () - -set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") -set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") -set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") - -if (NOT PICO_SDK_PATH) - if (PICO_SDK_FETCH_FROM_GIT) - include(FetchContent) - set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) - if (PICO_SDK_FETCH_FROM_GIT_PATH) - get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") - endif () - # GIT_SUBMODULES_RECURSE was added in 3.17 - if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - GIT_SUBMODULES_RECURSE FALSE - ) - else () - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - ) - endif () - - if (NOT pico_sdk) - message("Downloading Raspberry Pi Pico SDK") - FetchContent_Populate(pico_sdk) - set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) - endif () - set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) - else () - message(FATAL_ERROR - "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." - ) - endif () -endif () - -get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") -if (NOT EXISTS ${PICO_SDK_PATH}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") -endif () - -set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) -if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") -endif () - -set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) - -include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/swerve_pico/sensor-library/sensor-library.cpp b/swerve_pico/sensor-library/sensor-library.cpp new file mode 100644 index 0000000..4ca895b --- /dev/null +++ b/swerve_pico/sensor-library/sensor-library.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +class ZeroingSensor{ + ZeroingSensor(int pin){ + this->readPin = pin; + } + + void Setup() { + stdio_init_all(); + gpio_init(this->readPin); + gpio_set_dir(this->readPin, GPIO_IN); + } + + int Read() { + gpio_get(this->readPin); + } + private: + int readPin; +}; \ No newline at end of file diff --git a/swerve_pico/zeroing_script/CMakeLists.txt b/swerve_pico/zeroing_script/CMakeLists.txt deleted file mode 100644 index 914fc65..0000000 --- a/swerve_pico/zeroing_script/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# Generated Cmake Pico project file - -cmake_minimum_required(VERSION 3.5) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) - -# Initialise pico_sdk from installed location -# (note this can come from environment, CMake cache etc) - -set(PICO_BOARD pico CACHE STRING "Board type") - -# Pull in Raspberry Pi Pico SDK (must be before project) -include(pico_sdk_import.cmake) - -if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") - message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") -endif() - -project(i2c_drive_pico C CXX ASM) - -# Initialise the Raspberry Pi Pico SDK -pico_sdk_init() - -# Add executable. Default name is the project name, version 0.1 - -add_executable(i2c_drive_pico i2c_drive_pico.c ) - -pico_set_program_name(i2c_drive_pico "i2c_drive_pico") -pico_set_program_version(i2c_drive_pico "0.1") - -pico_enable_stdio_uart(i2c_drive_pico 0) -pico_enable_stdio_usb(i2c_drive_pico 1) - -# Add the standard library to the build -target_link_libraries(i2c_drive_pico - pico_stdlib - pico_i2c_slave - hardware_i2c - hardware_pwm - ) - -# Add the standard include files to the build -target_include_directories(i2c_drive_pico PRIVATE - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required -) - -# Add any user requested libraries -target_link_libraries(i2c_drive_pico - hardware_i2c - ) - -pico_add_extra_outputs(i2c_drive_pico) - diff --git a/swerve_pico/zeroing_script/pico_sdk_import.cmake b/swerve_pico/zeroing_script/pico_sdk_import.cmake deleted file mode 100644 index 65f8a6f..0000000 --- a/swerve_pico/zeroing_script/pico_sdk_import.cmake +++ /dev/null @@ -1,73 +0,0 @@ -# This is a copy of /external/pico_sdk_import.cmake - -# This can be dropped into an external project to help locate this SDK -# It should be include()ed prior to project() - -if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) - set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) - message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) - set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) - message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") -endif () - -if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) - set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) - message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") -endif () - -set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") -set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") -set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") - -if (NOT PICO_SDK_PATH) - if (PICO_SDK_FETCH_FROM_GIT) - include(FetchContent) - set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) - if (PICO_SDK_FETCH_FROM_GIT_PATH) - get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") - endif () - # GIT_SUBMODULES_RECURSE was added in 3.17 - if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - GIT_SUBMODULES_RECURSE FALSE - ) - else () - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - ) - endif () - - if (NOT pico_sdk) - message("Downloading Raspberry Pi Pico SDK") - FetchContent_Populate(pico_sdk) - set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) - endif () - set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) - else () - message(FATAL_ERROR - "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." - ) - endif () -endif () - -get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") -if (NOT EXISTS ${PICO_SDK_PATH}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") -endif () - -set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) -if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) - message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") -endif () - -set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) - -include(${PICO_SDK_INIT_CMAKE_FILE}) From 0772caea1b9dd06fa9d9ffcf314c39748fdfd0e4 Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Sun, 28 Jul 2024 20:32:37 -0700 Subject: [PATCH 63/65] change code structure --- .vscode/c_cpp_properties.json | 18 ++ .vscode/cmake-kits.json | 7 + .vscode/extensions.json | 9 + .vscode/launch.json | 58 ++++++ .vscode/settings.json | 41 ++-- .vscode/tasks.json | 55 +++--- swerve_pico/CMakeLists.txt | 58 ++++++ .../control-library/control-library.cpp | 137 ++++++++++--- swerve_pico/control-library/control-library.h | 63 ++++++ swerve_pico/control-library/pid.cpp | 100 ---------- swerve_pico/{main.c => main.cpp} | 66 ++++--- .../messaging-library/messaging-library.cpp | 155 +++++++++++++++ .../messaging-library/messaging-library.h | 146 ++------------ .../messaging-library/swerve-module-id.h | 80 ++++---- swerve_pico/motor-library/PWMControl.cpp | 88 --------- swerve_pico/motor-library/motor-library.cpp | 186 +++++++++++++++--- swerve_pico/motor-library/motor-library.h | 83 ++++++++ .../motor-library/quadrature_encoder.cpp | 150 -------------- swerve_pico/sensor-library/sensor-library.cpp | 35 ++-- swerve_pico/sensor-library/sensor-library.h | 10 + 20 files changed, 902 insertions(+), 643 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/cmake-kits.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 swerve_pico/CMakeLists.txt create mode 100644 swerve_pico/control-library/control-library.h delete mode 100644 swerve_pico/control-library/pid.cpp rename swerve_pico/{main.c => main.cpp} (74%) create mode 100644 swerve_pico/messaging-library/messaging-library.cpp delete mode 100644 swerve_pico/motor-library/PWMControl.cpp create mode 100644 swerve_pico/motor-library/motor-library.h delete mode 100644 swerve_pico/motor-library/quadrature_encoder.cpp create mode 100644 swerve_pico/sensor-library/sensor-library.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..ada839e --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Pico", + "includePath": [ + "${workspaceFolder}/**", + "${env:PICO_SDK_PATH}/**" + ], + "defines": [], + "compilerPath": "${env:PICO_INSTALL_PATH}/gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe", + "cStandard": "c11", + "cppStandard": "c++11", + "intelliSenseMode": "linux-gcc-arm", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} diff --git a/.vscode/cmake-kits.json b/.vscode/cmake-kits.json new file mode 100644 index 0000000..4b24eb7 --- /dev/null +++ b/.vscode/cmake-kits.json @@ -0,0 +1,7 @@ +[ + { + "name": "Pico ARM GCC", + "description": "Pico SDK Toolchain with GCC arm-none-eabi", + "toolchainFile": "${env:PICO_SDK_PATH}/cmake/preload/toolchains/pico_arm_gcc.cmake" + } +] diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..03ba0af --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "marus25.cortex-debug", + "ms-vscode.cmake-tools", + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.vscode-serial-monitor" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c370c53 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,58 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug (Cortex-Debug)", + "cwd": "${workspaceFolder}", + "executable": "${command:cmake.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "gdbPath": "arm-none-eabi-gdb", + "device": "RP2040", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/rp2040.cfg" + ], + "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", + "runToEntryPoint": "main", + "openOCDLaunchCommands": [ + "adapter speed 5000" + ] + }, + { + "name": "Pico Debug (Cortex-Debug with external OpenOCD)", + "cwd": "${workspaceFolder}", + "executable": "${command:cmake.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "gdbPath": "arm-none-eabi-gdb", + "device": "RP2040", + "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", + "runToEntryPoint": "main" + }, + { + "name": "Pico Debug (C++ Debugger)", + "type": "cppdbg", + "request": "launch", + "cwd": "${workspaceFolder}", + "program": "${command:cmake.launchTargetPath}", + "MIMode": "gdb", + "miDebuggerPath": "arm-none-eabi-gdb", + "miDebuggerServerAddress": "localhost:3333", + "debugServerPath": "openocd", + "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 5000\"", + "serverStarted": "Listening on port .* for gdb connections", + "filterStderr": true, + "stopAtEntry": true, + "hardwareBreakpoints": { + "require": true, + "limit": 4 + }, + "preLaunchTask": "Flash", + "svdPath": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index a95eb5d..80c85c5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,32 @@ { - "files.associations": { - "messaging-library.h": "c", - "stdio.h": "c", - "messaging-library-ids.h": "c", - "initializer_list": "c", - "type_traits": "c", - "xstring": "c", - "xutility": "c", - "zeroing.cpp": "c", - "stdlib.h": "c" + // These settings tweaks to the cmake plugin will ensure + // that you debug using cortex-debug instead of trying to launch + // a Pico binary on the host + "cmake.statusbar.advanced": { + "debug": { + "visibility": "hidden" + }, + "launch": { + "visibility": "hidden" + }, + "build": { + "visibility": "hidden" + }, + "buildTarget": { + "visibility": "hidden" } -} \ No newline at end of file + }, + "cmake.buildBeforeRun": true, + "cmake.configureOnOpen": true, + "cmake.configureSettings": { + "CMAKE_MODULE_PATH": "${env:PICO_INSTALL_PATH}/pico-sdk-tools" + }, + "cmake.generator": "Ninja", + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "cmake.sourceDirectory": "C:/Users/arcti/Desktop/githubprojects/modbot/swerve_pico", + "files.associations": { + "iostream": "cpp", + "*.pio": "cpp", + "stdexcept": "cpp" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 76d916e..8de7263 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,28 +1,29 @@ { - "tasks": [ - { - "type": "cppbuild", - "label": "C/C++: gcc.exe build active file", - "command": "C:\\TDM-GCC-64\\bin\\gcc.exe", - "args": [ - "-fdiagnostics-color=always", - "-g", - "${file}", - "-o", - "${fileDirname}\\${fileBasenameNoExtension}.exe" - ], - "options": { - "cwd": "${fileDirname}" - }, - "problemMatcher": [ - "$gcc" - ], - "group": { - "kind": "build", - "isDefault": true - }, - "detail": "Task generated by Debugger." - } - ], - "version": "2.0.0" -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "label": "Flash", + "type": "shell", + "command": "openocd", + "args": [ + "-f", + "interface/cmsis-dap.cfg", + "-f", + "target/rp2040.cfg", + "-c", + "adapter speed 5000; program {${command:cmake.launchTargetPath}} verify reset exit" + ], + "problemMatcher": [] + }, + { + "label": "Build", + "type": "cmake", + "command": "build", + "problemMatcher": "$gcc", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/swerve_pico/CMakeLists.txt b/swerve_pico/CMakeLists.txt new file mode 100644 index 0000000..5cfe0b9 --- /dev/null +++ b/swerve_pico/CMakeLists.txt @@ -0,0 +1,58 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +set(PICO_BOARD pico CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +project(swerve-pico C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(swerve-pico main.cpp ) + +pico_set_program_name(swerve-pico "swerve-pico") +pico_set_program_version(swerve-pico "0.1") + +pico_enable_stdio_uart(swerve-pico 0) +pico_enable_stdio_usb(swerve-pico 1) + +# Add the standard library to the build +target_link_libraries(swerve-pico + pico_stdlib + pico_i2c_slave + hardware_i2c + ) + +# Add the standard include files to the build +target_include_directories(swerve-pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required +) + +# Add any user requested libraries +target_link_libraries( + hardware_gpio + hardware_pwm + hardware_timer + hardware_pio + hardware_clocks + ) + +pico_add_extra_outputs(swerve-pico) + diff --git a/swerve_pico/control-library/control-library.cpp b/swerve_pico/control-library/control-library.cpp index d6a3eb8..d9d3e5e 100644 --- a/swerve_pico/control-library/control-library.cpp +++ b/swerve_pico/control-library/control-library.cpp @@ -1,36 +1,119 @@ +#include "control-library.h" -#include "pid.cpp" +bool calculateCallback(repeating_timer_t *rt); +bool updateIntergralDerivativeCallback(repeating_timer_t *rt); -/** - * Class to hold two pids, represents one swerve module (one turn wheel and one drive wheel) +PID::PID(float P, float I, float D, Motor motor, char tuningMode = PID_TUNING_MODE_POSITION, int timestep = 100, int substep = 10) +{ + this->P = P; + this->I = I; + this->D = D; + this->motor = motor; + this->timestep = timestep; // timestep for updating output + this->substep = this->timestep / (float)substep; // how many times to update integral/derivative per timestep + this->tuningMode = tuningMode; +} + +void PID::Setup() +{ // PICO SPECIFIC: create repeating timer, setup callbacks + repeating_timer_t timer; + this->timer = timer; + add_repeating_timer_ms(this->timestep, calculateCallback, this, &timer); + add_repeating_timer_ms(this->substep, updateIntergralDerivativeCallback, this, &timer); + this->motor.Setup(); + this->active = true; +} + +void PID::updateTarget(float target) +{ + // Idea; pid loop is autonomous and will automatically do whatever due to repeating timers + // if we just update the target with a function call we might be okay + this->target = target; +} + +void PID::reset() +{ + this->output = 0; + this->target = 0; + this->lastError = 0; +} + +/*** + * setActive: sets flag to toggle function of pid callbacks */ -class SwerveDrive{ - SwerveDrive(PID turnPID, PID drivePID){ - this->turnPID = turnPID; - this->drivePID = drivePID; - } +void PID::setActive(bool active) +{ + this->active = active; +} - void Setup(){ - this->turnPID.Setup(); - this->drivePID.Setup(); +void PID::calculate() +{ + if (this->active) + { + this->output = this->P * this->target + this->I * this->Integral + this->D * this->Derivative; + this->motor.SetPwmPercentage(output); } +} - void SetPidTargets(float turnMotorPercentage, float wheelMotorPercentage){ - this->turnPID.updateTarget(turnMotorPercentage); - this->drivePID.updateTarget(wheelMotorPercentage); - } +void PID::updateErrorIntegralDerivative() +{ + if (this->active) + { + float error; // error term + if (this->tuningMode = PID_TUNING_MODE_POSITION) + { + error = this->motor.GetPosition() - this->target; + } + else + { + error = this->motor.GetVelocity() - this->target; + } - void TogglePids(bool value){ - this->turnPID.setActive(value); - this->drivePID.setActive(value); + // substep is in miliseconds + this->Integral += error * (this->substep / MS_IN_SEC); // left riemann sum at fixed substep + this->Derivative = (error - this->lastError) / (this->substep / MS_IN_SEC); // estimate of derivatvie at fixed substep + this->lastError = error; } +} - void ResetPids(){ - this->turnPID.reset(); - this->drivePID.reset(); - } - - private: - PID turnPID; - PID drivePID; -}; \ No newline at end of file +bool calculateCallback(repeating_timer_t *rt) +{ + ((PID *)rt->user_data)->calculate(); + return true; +}; + +bool updateIntergralDerivativeCallback(repeating_timer_t *rt) +{ + ((PID *)rt->user_data)->updateErrorIntegralDerivative(); + return true; +}; + +SwerveDrive::SwerveDrive(PID turnPID, PID drivePID) +{ + this->turnPID = turnPID; + this->drivePID = drivePID; +} + +void SwerveDrive::Setup() +{ + this->turnPID.Setup(); + this->drivePID.Setup(); +} + +void SwerveDrive::SetPidTargets(float turnMotorPercentage, float wheelMotorPercentage) +{ + this->turnPID.updateTarget(turnMotorPercentage); + this->drivePID.updateTarget(wheelMotorPercentage); +} + +void SwerveDrive::TogglePids(bool value) +{ + this->turnPID.setActive(value); + this->drivePID.setActive(value); +} + +void SwerveDrive::ResetPids() +{ + this->turnPID.reset(); + this->drivePID.reset(); +} \ No newline at end of file diff --git a/swerve_pico/control-library/control-library.h b/swerve_pico/control-library/control-library.h new file mode 100644 index 0000000..dacadc7 --- /dev/null +++ b/swerve_pico/control-library/control-library.h @@ -0,0 +1,63 @@ +#include +#include + +#include "motor-library.h" + +#define MS_IN_SEC 1000 // DOUBLE CHECK VALUE OF CONVERSION + +enum PIDTuningMode +{ + PID_TUNING_MODE_POSITION, + PID_TUNING_MODE_VELOCITY +}; + + +class PID +{ // pid loop +public: + PID(); + PID(float P, float I, float D, Motor motor, char tuningMode = PID_TUNING_MODE_POSITION, int timestep = 100, int substep = 10); + void Setup(); + void updateTarget(float target); + void reset(); + void setActive(bool active); + + void calculate(); + void updateErrorIntegralDerivative(); +private: + + float P; + float I; + float D; + + float target = 0; + float output = 0; + float input = 0; + float lastError = 0; + + float Integral = 0; + float Derivative = 0; + repeating_timer_t timer = {}; + int timestep; + int substep; + Motor motor; + char tuningMode; + bool active = false; +}; + +/** + * Class to hold two pids, represents one swerve module (one turn wheel and one drive wheel) + */ +class SwerveDrive +{ +public: + SwerveDrive(PID turnPID, PID drivePID); + void Setup(); + void SetPidTargets(float turnMotorPercentage, float wheelMotorPercentage); + void TogglePids(bool value); + void ResetPids(); + +private: + PID turnPID; + PID drivePID; +}; diff --git a/swerve_pico/control-library/pid.cpp b/swerve_pico/control-library/pid.cpp deleted file mode 100644 index c4f9b3c..0000000 --- a/swerve_pico/control-library/pid.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "motor-library.cpp" - -#define MS_IN_SEC 1000 //DOUBLE CHECK VALUE OF CONVERSION - -void calculate(); -void updateErrorIntegralDerivative(); - -enum PIDTuningMode{ - PID_TUNING_MODE_POSITION, - PID_TUNING_MODE_VELOCITY -}; - -class PID { //pid loop - public: - PID(float P, float I, float D, Motor motor, char tuningMode = PID_TUNING_MODE_POSITION, int timestep = 100, int substep = 10) { - this->P = P; - this->I = I; - this->D = D; - this->motor = motor; - this->timestep = timestep //timestep for updating output - this->substep = this->timestep / (float)substep // how many times to update integral/derivative per timestep - this->tuningMode = tuningMode; - } - - void setup(){ // PICO SPECIFIC: create repeating timer, setup callbacks - this->timer = repeating_timer_t timer; - add_repeating_timer_ms(this->timestep, calculate, NULL, timer); - add_repeating_timer_ms(this->substep, updateErrorIntegralDerivative, NULL, timer); - this->motor.Setup(); - this->active = true; - } - - void updateTarget(float target){ - //Idea; pid loop is autonomous and will automatically do whatever due to repeating timers - //if we just update the target with a function call we might be okay - this->target = target; - } - - void reset(){ - this->output = 0; - this->target = 0; - this->lastError = 0; - } - - /*** - * setActive: sets flag to toggle function of pid callbacks - */ - void setActive(bool active){ - this->active = active; - } - private: - //privated functions for pid function - void calculate(){ - if(this->active){ - this->output = this->P * this->target + this->I * this->Integral + this->D * this->Derivative; - this->motor.SetPwmPercentage(output); - } - } - - void updateErrorIntegralDerivative(){ - if(this->active){ - float error; //error term - if(this->tuningMode = PID_TUNING_MODE_POSITION){ - error = this->motor.getPosition() - this->target; - }else{ - error = this->motor.GetVelocity() - this->target; - } - - //substep is in miliseconds - this->Integral += error * (this->substep / MS_IN_SEC); //left riemann sum at fixed substep - this->Derivative = (error - this->lastError) / (this->substep / MS_IN_SEC); //estimate of derivatvie at fixed substep - this->lastError = error; - } - } - - float P; - float I; - float D; - - float target = 0; - float output = 0; - float input = 0; - float lastError = 0; - - float Integral = 0; - float Derivative = 0; - repeating_timer_t timer = {}; - int timestep; - int substep; - Motor motor; - char tuningMode; - bool active = false; -}; \ No newline at end of file diff --git a/swerve_pico/main.c b/swerve_pico/main.cpp similarity index 74% rename from swerve_pico/main.c rename to swerve_pico/main.cpp index 902e21e..7ec0340 100644 --- a/swerve_pico/main.c +++ b/swerve_pico/main.cpp @@ -1,9 +1,14 @@ #include -#include "motor-library.cpp" +#include +#include +#include +#include + +#include "motor-library.h" #include "messaging-library.h" -#include "control-library.cpp" -#include "sensor-library.cpp" +#include "control-library.h" +#include "sensor-library.h" char robotState = 0; @@ -39,7 +44,8 @@ char robotState = 0; // #define freq 500 // note: use clock management frequencies to set frequency // #define duty_cycle 1 #define count_max 65535 - +//temporary address, make comm protocol to send/recieve messages upon linkage/ on bootup +#define PI_ADDRESS 1 // Buffer for incoming data uint8_t incoming_data[I2C_DATA_LENGTH]; @@ -55,6 +61,7 @@ int data_index = 0; // Buffer for the input data uint8_t input[I2C_DATA_LENGTH - 2]; +uint8_t output[I2C_DATA_LENGTH - 2]; // Handler for I2C events static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) @@ -83,8 +90,9 @@ static void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) case I2C_SLAVE_REQUEST: // Pi is requesting data // Write the data into the void - i2c_write_byte_raw(i2c, (uint8_t)input_status); + // i2c_write_byte_raw(i2c, (uint8_t)input_status); // set the event status to sent + i2c_write_raw_blocking(i2c, output, I2C_DATA_LENGTH - 2); last_event = 2; break; @@ -120,27 +128,27 @@ int main(){ /** * DEFINE AND SETUP MOTORS */ - Motor turn_motor1 = new Motor(0,0,0); - Motor drive_motor1 = new Motor(0,0,0); - Motor turn_motor2 = new Motor(0,0,0); - Motor drive_motor2 = new Motor(0,0,0); - Motor turn_motor3 = new Motor(0,0,0); - Motor drive_motor3 = new Motor(0,0,0); + Motor turn_motor1 = Motor(0,0,0); + Motor drive_motor1 = Motor(0,0,0); + Motor turn_motor2 = Motor(0,0,0); + Motor drive_motor2 = Motor(0,0,0); + Motor turn_motor3 = Motor(0,0,0); + Motor drive_motor3 = Motor(0,0,0); - PID pid1 = new PID(0,0,0, turn_motor1); - PID pid2 = new PID(0,0,0, drive_motor1); - PID pid3 = new PID(0,0,0, turn_motor2); - PID pid4 = new PID(0,0,0, drive_motor2); - PID pid5 = new PID(0,0,0, turn_motor3); - PID pid6 = new PID(0,0,0, drive_motor3); + PID pid1 = PID(0,0,0, turn_motor1); + PID pid2 = PID(0,0,0, drive_motor1); + PID pid3 = PID(0,0,0, turn_motor2); + PID pid4 = PID(0,0,0, drive_motor2); + PID pid5 = PID(0,0,0, turn_motor3); + PID pid6 = PID(0,0,0, drive_motor3); - SwerveModule module1 = new SwerveModule(pid1,pid2); - SwerveModule module2 = new SwerveModule(pid3,pid4); - SwerveModule module3 = new SwerveModule(pid5,pid6); + SwerveDrive module1 = SwerveDrive(pid1,pid2); + SwerveDrive module2 = SwerveDrive(pid3,pid4); + SwerveDrive module3 = SwerveDrive(pid5,pid6); - ZeroingSensor sensor1 = new ZeroingSensor(0); - ZeroingSensor sensor2 = new ZeroingSensor(0); - ZeroingSensor sensor3 = new ZeroingSensor(0); + ZeroingSensor sensor1 = ZeroingSensor(0); + ZeroingSensor sensor2 = ZeroingSensor(0); + ZeroingSensor sensor3 = ZeroingSensor(0); //I2C Setup @@ -185,13 +193,15 @@ int main(){ module2.TogglePids(true); module3.TogglePids(true); }else if(robotState == STATE_RETURN_DATA){ - char returnMessage[I2C_DATA_LENGTH] = {0}; - MessagingWriteMessage("DATAAAAAAAA", returnMessage); - // SEND DATAA + // MessagingWriteMessage("DATAAAAAAAA", output); + // SEND DATA + }else if(robotState == STATE_WRITE_HEARTBEAT){ + // MessagingWriteMessage((unsigned char)0x0, (char*)output); + // SEND DATA } - if(input_stats == 1){ + if(input_status == 1){ char* resultString; - MessagingReadBuffer(incoming_data,resultString,I2C_DATA_LENGTH,&robotState); + MessagingReadBuffer((char*)incoming_data,resultString,I2C_DATA_LENGTH,&robotState); } } return 0; diff --git a/swerve_pico/messaging-library/messaging-library.cpp b/swerve_pico/messaging-library/messaging-library.cpp new file mode 100644 index 0000000..9703a14 --- /dev/null +++ b/swerve_pico/messaging-library/messaging-library.cpp @@ -0,0 +1,155 @@ +#include "messaging-library.h" + + +char MessagingBSDChecksum(char *payloadString, unsigned char payloadLen) +{ + unsigned char checksum = 0; + for (short i = 0; i < payloadLen; i++) + { + checksum = (checksum >> 1) | (checksum << 7); + checksum += payloadString[i]; + } + return checksum; +} + + +char MessagingReadBuffer(char *bufferString, char *resultString, int bufferLength, char *robotState) +{ // turn result to struct later + short idx = 0; + char state = MESSAGE_HEAD; + char currentChar = bufferString[idx]; + char payloadLen = 0; + char payloadIdx = 0; + char messageID = 0; + char calculatedChecksum = 0; + char messageParseResult = MESSAGE_PARSE_NONE; + char robotResultState = 0; + + while (idx < bufferLength) + { + if (state == MESSAGE_HEAD) + { + if (currentChar == (char)HEAD_ID) + { + state = MESSAGE_LENGTH; + } + } + else if (state == MESSAGE_LENGTH) + { + payloadLen = currentChar; + + // expected payload range check + if (payloadLen < 2) + { + state = MESSAGE_ERROR; + } + + payloadIdx = 0; + char *payload = bufferString + idx + 1; + calculatedChecksum = MessagingBSDChecksum(payload, payloadLen); + state = MESSAGE_PAYLOAD; + } + else if (state == MESSAGE_PAYLOAD) + { + + // assign/store id + if (payloadIdx == 0) + { + messageID = currentChar; + } + + // actual payload length check + if (payloadIdx < payloadLen - 1) + { + if (currentChar == (char)TAIL_ID) + { + state = MESSAGE_ERROR; + } + else + { + payloadIdx++; + } + } + else + { + if (currentChar == (char)TAIL_ID) + { + state = MESSAGE_ERROR; + } + else + { + robotResultState = MessagingProcessPayload(bufferString + idx - payloadLen + 2, messageID, payloadLen - 1); + state = MESSAGE_TAIL; + } + } + } + else if (state == MESSAGE_TAIL) + { + if (currentChar == (char)TAIL_ID) + { + state = MESSAGE_CHECKSUM; + } + else + { + state = MESSAGE_ERROR; + } + } + else if (state == MESSAGE_CHECKSUM) + { + if (calculatedChecksum != currentChar) + { + state = MESSAGE_ERROR; + } + else + { + state = MESSAGE_END; + } + } + else if (state == MESSAGE_END) + { + char nextChar = bufferString[idx + 1]; + if (currentChar == '\r' && nextChar == '\n') + { + messageParseResult = MESSAGE_PARSE_SUCCESS; + state = MESSAGE_HEAD; + *robotState = robotResultState; + break; + } + else + { + state = MESSAGE_ERROR; + } + } + else + { + messageParseResult = MESSAGE_PARSE_FAILURE; + break; + } + + idx++; + currentChar = bufferString[idx]; + } + + if (state != MESSAGE_HEAD && state != MESSAGE_ERROR) + { + messageParseResult = MESSAGE_BUFFER_OVERFLOW; + } + + return messageParseResult; +} + +void MessagingWriteMessage(unsigned char payload[], char message[]) +{ + char payloadLen = 0; + for (; payload[payloadLen] != '\0'; payloadLen++) + { + message[payloadLen + 2] = payload[payloadLen]; + } + message[0] = (char)HEAD_ID; + message[1] = payloadLen; + message[payloadLen + 2] = (char)TAIL_ID; + message[payloadLen + 3] = MessagingBSDChecksum((char*)payload, payloadLen); + message[payloadLen + 4] = '\r'; + message[payloadLen + 5] = '\n'; + message[payloadLen + 6] = '\0'; +} diff --git a/swerve_pico/messaging-library/messaging-library.h b/swerve_pico/messaging-library/messaging-library.h index 0cc3e05..9b8b993 100644 --- a/swerve_pico/messaging-library/messaging-library.h +++ b/swerve_pico/messaging-library/messaging-library.h @@ -4,14 +4,16 @@ #define HEAD_ID 0xCC #define TAIL_ID 0xB9 -enum messageParseResult { +enum messageParseResult +{ MESSAGE_BUFFER_OVERFLOW = -2, MESSAGE_PARSE_FAILURE, MESSAGE_PARSE_NONE, MESSAGE_PARSE_SUCCESS }; -enum messageReadState { +enum messageReadState +{ MESSAGE_ERROR = -1, MESSAGE_HEAD, MESSAGE_LENGTH, @@ -23,156 +25,44 @@ enum messageReadState { /** * BSDChecksum: Calculates checksum for messaging, using BSDChecksum algorithm. The max length is 128 bytes. The first byte is the message ID, the next 127 bytes is the payload - * + * * @param payloadString string of payload and id, 128 bytes max * @param payloadLen length of payload with id - * + * * @return calculated checksum */ -char MessagingBSDChecksum(char* payloadString, unsigned char payloadLen) { - unsigned char checksum = 0; - for(short i = 0; i < payloadLen; i++) { - checksum = (checksum >> 1) | (checksum << 7); - checksum += payloadString[i]; - } - return checksum; -} - +char MessagingBSDChecksum(char *payloadString, unsigned char payloadLen); /** * ReadBuffer: Uses state machine to take the buffer parameter and processes the message into the result string; * Returns true/false based on if conversion was successful. Changes value of robotState if message is valid. - * + * * \nPacket Structure: * HEAD LENGTH PAYLOAD TAIL CHECKSUM END * 1 1 (1)<128 1 1 \r\n - * + * * Head: 1 byte 0xCC * Length: 1 byte, length of payload in bytes * Payload: Variable length < 128 bytes, always proceeded by a 1 byte ID * Tail: 1 byte 0xB9 * Checksum: 1 byte checksum calculated over payload using BSDChecksum algorithm * End: 2 bytes \r\n - * + * * @param bufferString string that will be processed * @param resultString string that the result is stored in * @param bufferLength length of buffer function parses through * @param robotState pointer to variable containing state of robot that is modified * when a message is fully processed - * - * @return true or false based on success of function + * + * @return true or false based on success of function */ -char MessagingReadBuffer(char* bufferString, char* resultString, int bufferLength, char* robotState) { // turn result to struct later - short idx = 0; - char state = MESSAGE_HEAD; - char currentChar = bufferString[idx]; - char payloadLen = 0; - char payloadIdx = 0; - char messageID = 0; - char calculatedChecksum = 0; - char messageParseResult = MESSAGE_PARSE_NONE; - char robotResultState = 0; - - while (idx < bufferLength) { - if(state == MESSAGE_HEAD) { - if(currentChar == (char)HEAD_ID) { - state = MESSAGE_LENGTH; - } - }else if(state == MESSAGE_LENGTH) { - payloadLen = currentChar; - - // expected payload range check - if(payloadLen < 2) { - state = MESSAGE_ERROR; - } - - payloadIdx = 0; - char* payload = bufferString + idx + 1; - calculatedChecksum = MessagingBSDChecksum(payload, payloadLen); - state = MESSAGE_PAYLOAD; - - }else if(state == MESSAGE_PAYLOAD) { - - // assign/store id - if(payloadIdx == 0) { - messageID = currentChar; - } - - // actual payload length check - if(payloadIdx < payloadLen - 1) { - if(currentChar == (char)TAIL_ID) { - state = MESSAGE_ERROR; - }else { - payloadIdx++; - } - }else { - if(currentChar == (char)TAIL_ID) { - state = MESSAGE_ERROR; - }else { - robotResultState = MessagingProcessPayload(bufferString + idx - payloadLen + 2, messageID, payloadLen - 1); - state = MESSAGE_TAIL; - } - } - - }else if(state == MESSAGE_TAIL) { - if(currentChar == (char)TAIL_ID) { - state = MESSAGE_CHECKSUM; - }else{ - state = MESSAGE_ERROR; - } - - }else if(state == MESSAGE_CHECKSUM) { - if(calculatedChecksum != currentChar) { - state = MESSAGE_ERROR; - }else { - state = MESSAGE_END; - } - - }else if(state == MESSAGE_END){ - char nextChar = bufferString[idx + 1]; - if(currentChar == '\r' && nextChar == '\n') { - messageParseResult = MESSAGE_PARSE_SUCCESS; - state = MESSAGE_HEAD; - *robotState = robotResultState; - break; - }else { - state = MESSAGE_ERROR; - } - - }else { - messageParseResult = MESSAGE_PARSE_FAILURE; - break; - } - - idx++; - currentChar = bufferString[idx]; - } - - if(state != MESSAGE_HEAD && state != MESSAGE_ERROR) { - messageParseResult = MESSAGE_BUFFER_OVERFLOW; - } - - return messageParseResult; -} - +char MessagingReadBuffer(char *bufferString, char *resultString, int bufferLength, char *robotState); /** - * WriteMessage: converts parameter payload into a valid message string and writes it to parameter message. + * WriteMessage: converts parameter payload into a valid message string and writes it to parameter message. * Payload corresponds to PAYLOAD in the packet structure: [HEAD, LENGTH, PAYLOAD, TAIL, CHECKSUM, END] - * - * @param payload string that will be converted to a valid message format + * + * @param payload string that will be converted to a valid message format * @param message string that the message will be written to - * + * * @return None */ -void MessagingWriteMessage(unsigned char payload[], char message[]){ - char payloadLen = 0; - for(;payload[payloadLen] != '\0'; payloadLen++){ - message[payloadLen + 2] = payload[payloadLen]; - } - message[0] = (char)HEAD_ID; - message[1] = payloadLen; - message[payloadLen + 2] = (char)TAIL_ID; - message[payloadLen + 3] = MessagingBSDChecksum(payload, payloadLen); - message[payloadLen + 4] = '\r'; - message[payloadLen + 5] = '\n'; - message[payloadLen + 6] = '\0'; -} +void MessagingWriteMessage(unsigned char payload[], char message[]); diff --git a/swerve_pico/messaging-library/swerve-module-id.h b/swerve_pico/messaging-library/swerve-module-id.h index 4a32e05..3284e1a 100644 --- a/swerve_pico/messaging-library/swerve-module-id.h +++ b/swerve_pico/messaging-library/swerve-module-id.h @@ -2,45 +2,53 @@ * Defined list of valid message ids and helper function to check and process messages into actions */ #ifndef _MESSAGING_IDS - #define _MESSAGING_IDS +#define _MESSAGING_IDS - #define MOVE_IN_DIRECTION 0x80 +#define MOVE_IN_DIRECTION 0x80 +#define HEARTBEAT_CHECK 0x81 - #define resultArraySize 6 - - enum messageProcessResult { - PAYLOAD_ID_NOT_FOUND, - STATE_READ_RESULT_ARRAY, - STATE_ZERO_MOTORS - }; +#define resultArraySize 6 + +enum messageProcessResult +{ + PAYLOAD_ID_NOT_FOUND, + STATE_READ_RESULT_ARRAY, + STATE_ZERO_MOTORS, + STATE_WRITE_HEARTBEAT, + STATE_RETURN_DATA +}; + +float resultArray[resultArraySize] = {0}; +char flippedBinaryArray[sizeof(float) * resultArraySize] = {0}; +/** + * ProcessPayload: reads parameter of specified length and performs actions based on the id + * + * @param payload pointer to beginning of payload + * @param id id of payload + * @param len length of payload + * + * @returns success/failure to process payload, next state of main state machine + */ +char MessagingProcessPayload(char *payload, unsigned char id, unsigned char len) +{ + switch (id) + { + case MOVE_IN_DIRECTION: + // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex + // store buffer array of uint8 (or char) to n floats + // bytes are stored in the wrong direction, flip and rewrite - float resultArray[resultArraySize] = {0}; - char flippedBinaryArray[sizeof(float) * resultArraySize] = {0}; - /** - * ProcessPayload: reads parameter of specified length and performs actions based on the id - * - * @param payload pointer to beginning of payload - * @param id id of payload - * @param len length of payload - * - * @returns success/failure to process payload, next state of main state machine - */ - char MessagingProcessPayload(char* payload, unsigned char id, unsigned char len){ - switch(id){ - case MOVE_IN_DIRECTION: - // use https://www.h-schmidt.net/FloatConverter/IEEE754.html to convert between float and binary/hex - // store buffer array of uint8 (or char) to n floats - // bytes are stored in the wrong direction, flip and rewrite - - for(char i=0;i -#include -#include -#include - - -#define COUNT_MAX 65535 - -// class to instantiate swerve module -class PWMControl { - public: - // store all pin ids - // swerveModule is controlled by PID loops, module control is abstracted to PID only! - PWMControl(int motorPin, int pwmPin, int chan = PWM_CHAN_A, int countMax = COUNT_MAX) { - // two motors per module, each requiring 3 pins - // two pins to define h-bridge control and one pin for pwm (since there are 2 motors, there are 2 pwm pins) - // constructor assumes that all pins are in sets of 2 and will be next to eachother on an even interval (e.g pin 0 and 1, pin 2 and 4, etc) - this->motor_in1_pin = motorPin; // pin set - this->motor_in2_pin = motorPin + 1; - this->pwm_pin = pwmPin; // pwm pin set - // pwm slices take up two gpio pins, assuming you take up two pins for pwm with the first pin being even, - // the slice is whats activated/outputs a signal and is separate from the pins - // the pwm pins must be configured to let the signal pass through - this->pwm_slice = pwm_gpio_to_slice_num(pwmPin); - this->channel = chan; - //code to check & override default values - if(countMax != COUNT_MAX) - this->countMax = countMax; - } - - // initialize all pins - void Setup() { //PICO SPECIFIC - stdio_init_all(); - // h-bridge requires two binary signals for direction, initialize the in1, in2 pins - gpio_init(this->motor_in1_pin); - gpio_init(this->motor_in2_pin); - - // TODO: check if default output signal is 0, for now put this in - // sets all output signals to 0 - gpio_put(this->motor_in1_pin, 0); - gpio_put(this->motor_in2_pin, 0); - - // define pin output for gpio pins - gpio_set_dir(this->motor_in1_pin, GPIO_OUT); - gpio_set_dir(this->motor_in2_pin, GPIO_OUT); - - // define pin output for pwm pins - gpio_set_function(this->pwm_pin, GPIO_FUNC_PWM); - - // set max cycle count of pwm slice (this does counting) - pwm_set_wrap(this->pwm_slice, count_max); - - // set output signal to 0 - // pwm pins can output anything from 0 to countmax of the pwm_slice (this outputs based on the counter) - pwm_set_chan_level(this->pwm_slice, this->channel, 0); - - // activate pwm slice - pwm_set_enabled(this->pwm_slice, true); - } - - - - //drive function control entirely by PID loops - void Drive(float drivePercentange) { // currently assuming turn & wheel are from -1 to 1, probably need to change later - if (drivePercentange == 0) { // in1 and in2 are high - gpio_put(motor_in1_pin, 1); - gpio_put(motor_in2_pin, 1); - } else if (drivePercentange < 0) { // in1 is high and in2 is low - gpio_put(motor_in1_pin, 1); - gpio_put(motor_in2_pin, 0); - } else { // in1 is low and in2 is high - gpio_put(motor_in2_pin, 1); - gpio_put(motor_in1_pin, 0); - } - - // set pwm pin output as % of slice output - pwm_set_chan_level(pwm_slice, channel, abs((int)(drivePercentange * count_max))); - } - - private: - //private variables for storing pins and constants - int motor_in1_pin; - int motor_in2_pin; - int pwm_pin; - int pwm_slice; - int channel = PWM_CHAN_A; - int countMax = COUNT_MAX; -}; \ No newline at end of file diff --git a/swerve_pico/motor-library/motor-library.cpp b/swerve_pico/motor-library/motor-library.cpp index 9064186..898906e 100644 --- a/swerve_pico/motor-library/motor-library.cpp +++ b/swerve_pico/motor-library/motor-library.cpp @@ -1,7 +1,17 @@ -#include "quadrature_encoder.cpp" -#include "PWMControl.cpp" +#include +#include +#include +#include -uint EncoderFactory::encoder_count = 0; +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "hardware/timer.h" +#include "hardware/pwm.h" + +#include "motor-library.h" +#include "quadrature_encoder.pio.h" + +static EncoderFactory factory; /** * file contains syntactic sugar to make getting/setting motors easier @@ -11,30 +21,146 @@ uint EncoderFactory::encoder_count = 0; /** * Motor class that holds PWMControl and quadrature_encoder class, encapsulates functions to set and read values of 1 motor */ -class Motor{ - public: - Motor(int motorPin, int pwmPin, uint encoderPin, float ratio = 1.0, int chan = PWM_CHAN_A, int countMax = COUNT_MAX){ - this->pwmControl = new PWMControl(motorPin, pwmPin, chan, countMax); - this->encoder = EncoderFactory::createEncoder(encoderPin, ratio); - } - - void Setup(){ - this->pwmControl.Setup(); - } - void SetPwmPercentage(float percentage){ - this->pwmControl.Drive(percentage); - } - - float GetPosition(){ - return this->encoder.get_pos(); - } - - - float GetVelocity(){ - return this->encoder.get_velocity(); - } - - private: - PWMControl pwmControl; - Encoder encoder; -}; \ No newline at end of file +Motor::Motor(int motorPin, int pwmPin, unsigned int encoderPin, float ratio = 1.0, int chan = PWM_CHAN_A, int countMax = COUNT_MAX) +{ + this->pwmControl = PWMControl(motorPin, pwmPin, chan, countMax); + this->encoder = factory.createEncoder(encoderPin, ratio); +} + +void Motor::Setup() +{ + this->pwmControl.Setup(); +} + +void Motor::SetPwmPercentage(float percentage) +{ + this->pwmControl.Drive(percentage); +} + +float Motor::GetPosition() +{ + return this->encoder.get_pos(); +} + +float Motor::GetVelocity() +{ + return this->encoder.get_velocity(); +} + +PWMControl::PWMControl(int motorPin, int pwmPin, int chan = PWM_CHAN_A, int countMax = COUNT_MAX) +{ + // two motors per module, each requiring 3 pins + // two pins to define h-bridge control and one pin for pwm (since there are 2 motors, there are 2 pwm pins) + // constructor assumes that all pins are in sets of 2 and will be next to eachother on an even interval (e.g pin 0 and 1, pin 2 and 4, etc) + this->motor_in1_pin = motorPin; // pin set + this->motor_in2_pin = motorPin + 1; + this->pwm_pin = pwmPin; // pwm pin set + // pwm slices take up two gpio pins, assuming you take up two pins for pwm with the first pin being even, + // the slice is whats activated/outputs a signal and is separate from the pins + // the pwm pins must be configured to let the signal pass through + this->pwm_slice = pwm_gpio_to_slice_num(pwmPin); + this->channel = chan; + // code to check & override default values + if (countMax != COUNT_MAX) + this->countMax = countMax; +} + +// initialize all pins +void PWMControl::Setup() +{ // PICO SPECIFIC + stdio_init_all(); + // h-bridge requires two binary signals for direction, initialize the in1, in2 pins + gpio_init(this->motor_in1_pin); + gpio_init(this->motor_in2_pin); + + // TODO: check if default output signal is 0, for now put this in + // sets all output signals to 0 + gpio_put(this->motor_in1_pin, 0); + gpio_put(this->motor_in2_pin, 0); + + // define pin output for gpio pins + gpio_set_dir(this->motor_in1_pin, GPIO_OUT); + gpio_set_dir(this->motor_in2_pin, GPIO_OUT); + + // define pin output for pwm pins + gpio_set_function(this->pwm_pin, GPIO_FUNC_PWM); + + // set max cycle count of pwm slice (this does counting) + pwm_set_wrap(this->pwm_slice, COUNT_MAX); + + // set output signal to 0 + // pwm pins can output anything from 0 to countmax of the pwm_slice (this outputs based on the counter) + pwm_set_chan_level(this->pwm_slice, this->channel, 0); + + // activate pwm slice + pwm_set_enabled(this->pwm_slice, true); +} + +// drive function control entirely by PID loops +void PWMControl::Drive(float drivePercentange) +{ // currently assuming turn & wheel are from -1 to 1, probably need to change later + if (drivePercentange == 0) + { // in1 and in2 are high + gpio_put(motor_in1_pin, 1); + gpio_put(motor_in2_pin, 1); + } + else if (drivePercentange < 0) + { // in1 is high and in2 is low + gpio_put(motor_in1_pin, 1); + gpio_put(motor_in2_pin, 0); + } + else + { // in1 is low and in2 is high + gpio_put(motor_in2_pin, 1); + gpio_put(motor_in1_pin, 0); + } + + // set pwm pin output as % of slice output + pwm_set_chan_level(pwm_slice, channel, abs((int)(drivePercentange * COUNT_MAX))); +} + + +// Create an encoder. Reccommended NOT to use this class, use EncoderFactory::createEncoder() +// @param pinA the pin that encoder A channel is connected to, the B channel should connect to the next pin +// @param sm the state machine to keep track of the encoder, 0-3 +// @param which pio +// @param ratio the ratio by which to multiply encoder ticks +Encoder::Encoder(uint pinA, uint sm, PIO pio, float ratio = 1.0, bool addProgram = true) +{ + this->pio = pio; + this->sm = sm; + this->ratio = ratio; + + uint offset = 0; + + // we don't really need to keep the offset, as this program must be loaded + // at offset 0 + if (addProgram) + uint offset = pio_add_program(pio, &quadrature_encoder_program); + + quadrature_encoder_program_init(pio, sm, offset, pinA, 0); +} + +// updates the pos and velocity, call periodically. +// @param delta_time the time, in miliseconds, since last calling update +void Encoder::update(int delta_time) +{ + pos = quadrature_encoder_get_count(pio, sm) * ratio * DEG_PER_ROT; + velocity = ((prev_pos - pos) / delta_time) * 1000; + prev_pos = pos; +} + +// get position of wheel in ticks, multiplied by any provided ratio. resets on init. +// update() must be called periodically for this to be accurate +float Encoder::get_pos() +{ + return pos; +} + +// get velocity of wheel in ticks per second, multiplied by any provided ratio. +// update() must be called periodically for this to be accurate +float Encoder::get_velocity() +{ + return velocity; +} + diff --git a/swerve_pico/motor-library/motor-library.h b/swerve_pico/motor-library/motor-library.h new file mode 100644 index 0000000..6009363 --- /dev/null +++ b/swerve_pico/motor-library/motor-library.h @@ -0,0 +1,83 @@ +#include "pio.h" +#include "pwm.h" +#include "quadrature_encoder.pio.h" + +#define COUNT_MAX 65535 + +const float ROT_PER_TICK = 1.0 / (4 * 374.0); +const float PULLEY_RATIO = 0.3185 / 1.528; +const float DEG_PER_ROT = 360.0; + +class Motor +{ +public: + Motor(); + Motor(int motorPin, int pwmPin, unsigned int encoderPin, float ratio = 1.0, int chan = PWM_CHAN_A, int countMax = COUNT_MAX); + void Setup(); + void SetPwmPercentage(float percentage); + float GetPosition(); + float GetVelocity(); + +private: + PWMControl pwmControl; + Encoder encoder; +}; + +class PWMControl +{ +public: + PWMControl(); + PWMControl(int motorPin, int pwmPin, int chan = PWM_CHAN_A, int countMax = COUNT_MAX); + void Setup(); + void Drive(float drivePercentange); + +private: + int motor_in1_pin; + int motor_in2_pin; + int pwm_pin; + int pwm_slice; + int channel = PWM_CHAN_A; + int countMax = COUNT_MAX; +}; + + +class Encoder +{ +public: + Encoder(); + Encoder(unsigned int pinA, unsigned int sm, PIO pio, float ratio = 1.0, bool addProgram = true); + void update(int delta_time); + float get_pos(); + float get_velocity(); + +private: + float prev_pos, pos; + float velocity; + float ratio; + PIO pio; + unsigned int sm; +}; + +class EncoderFactory +{ +public: + // Create an encoder, automatically configuring the state machine and pio. + // @param pinA the A encoder channel, the B channel should be connected to the next pin + // @param ratio the ratio by which to multiply encoder outputs. ratio of 1 results in tick / sec + static Encoder createEncoder(unsigned int pinA, float ratio = 1.0) + { + if (encoder_count > 7) + { + throw std::out_of_range("reached encoder limit of 8"); + } + + unsigned int sm = encoder_count % 4; + PIO pio = encoder_count < 4 ? pio0 : pio1; + + encoder_count++; + return Encoder(pinA, sm, pio, ratio, sm == 0); + } + +private: + static unsigned int encoder_count; +}; diff --git a/swerve_pico/motor-library/quadrature_encoder.cpp b/swerve_pico/motor-library/quadrature_encoder.cpp deleted file mode 100644 index 1e178b1..0000000 --- a/swerve_pico/motor-library/quadrature_encoder.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include "pico/stdlib.h" -#include "hardware/pio.h" -#include "hardware/timer.h" - -#include "quadrature_encoder.pio.h" - -// #define encoder_test - -// -// ---- quadrature encoder interface example -// -// the PIO program reads phase A/B of a quadrature encoder and increments or -// decrements an internal counter to keep the current absolute step count -// updated. At any point, the main code can query the current count by using -// the quadrature_encoder_*_count functions. The counter is kept in a full -// 32 bit register that just wraps around. Two's complement arithmetic means -// that it can be interpreted as a 32-bit signed or unsigned value, and it will -// work anyway. -// -// As an example, a two wheel robot being controlled at 100Hz, can use two -// state machines to read the two encoders and in the main control loop it can -// simply ask for the current encoder counts to get the absolute step count. It -// can also subtract the values from the last sample to check how many steps -// each wheel as done since the last sample period. -// -// One advantage of this approach is that it requires zero CPU time to keep the -// encoder count updated and because of that it supports very high step rates. -// - -// 374 pulses per revolution (from the description at https://www.dfrobot.com/product-1462.html) -// 4x because the encoder has 2 sensors each w/ rising & falling edges, so each pulse results in -// 4 counts received (https://deltamotion.com/support/webhelp/rmctools/Controller_Features/Transducer_Basics/Quadrature_Fundamentals.htm) -const float ROT_PER_TICK = 1.0 / (4 * 374.0); -const float PULLEY_RATIO = 0.3185 / 1.528; -const float DEG_PER_ROT = 360.0; - -class Encoder -{ -public: - // Create an encoder. Reccommended NOT to use this class, use EncoderFactory::createEncoder() - // @param pinA the pin that encoder A channel is connected to, the B channel should connect to the next pin - // @param sm the state machine to keep track of the encoder, 0-3 - // @param which pio - // @param ratio the ratio by which to multiply encoder ticks - Encoder(uint pinA, uint sm, PIO pio, float ratio = 1.0, bool addProgram = true) - { - this->pio = pio; - this->sm = sm; - this->ratio = ratio; - - uint offset = 0; - - // we don't really need to keep the offset, as this program must be loaded - // at offset 0 - if (addProgram) - uint offset = pio_add_program(pio, &quadrature_encoder_program); - - quadrature_encoder_program_init(pio, sm, offset, pinA, 0); - } - - // updates the pos and velocity, call periodically. - // @param delta_time the time, in miliseconds, since last calling update - void update(int delta_time) - { - pos = quadrature_encoder_get_count(pio, sm) * ratio * DEG_PER_ROT; - velocity = ((prev_pos - pos) / delta_time) * 1000; - prev_pos = pos; - } - - // get position of wheel in ticks, multiplied by any provided ratio. resets on init. - // update() must be called periodically for this to be accurate - float get_pos() - { - return pos; - } - - // get velocity of wheel in ticks per second, multiplied by any provided ratio. - // update() must be called periodically for this to be accurate - float get_velocity() - { - return velocity; - } - -private: - float prev_pos, pos; - float velocity; - float ratio; - PIO pio; - uint sm; -}; - -class EncoderFactory -{ -public: - // Create an encoder, automatically configuring the state machine and pio. - // @param pinA the A encoder channel, the B channel should be connected to the next pin - // @param ratio the ratio by which to multiply encoder outputs. ratio of 1 results in tick / sec - static Encoder createEncoder(uint pinA, float ratio = 1.0) - { - if (encoder_count > 7) - { - throw std::out_of_range("reached encoder limit of 8"); - } - - uint sm = encoder_count % 4; - PIO pio = encoder_count < 4 ? pio0 : pio1; - - encoder_count++; - return Encoder(pinA, sm, pio, ratio, sm == 0); - } - -private: - static uint encoder_count; -}; - -#ifdef encoder_test - -uint EncoderFactory::encoder_count = 0; - -int main() -{ - stdio_init_all(); - - // Base pin to connect the A phase of the encoder (yellow wire). - // The B phase must be connected to the next pin (green wire) - const uint PIN_STEER = 14; - const uint PIN_DRIVE = 16; - - Encoder steer = EncoderFactory::createEncoder(PIN_STEER, ROT_PER_TICK * DEG_PER_ROT * PULLEY_RATIO); - Encoder drive = EncoderFactory::createEncoder(PIN_DRIVE, ROT_PER_TICK * DEG_PER_ROT); - - while (1) - { - steer.update(20); - drive.update(20); - - printf("steer position %8f, velocity %6f\n", steer.get_pos(), steer.get_velocity()); - printf("drive position %8f, velocity %6f\n", drive.get_pos(), drive.get_velocity()); - sleep_ms(20); - } -} -#endif \ No newline at end of file diff --git a/swerve_pico/sensor-library/sensor-library.cpp b/swerve_pico/sensor-library/sensor-library.cpp index 4ca895b..0c210c4 100644 --- a/swerve_pico/sensor-library/sensor-library.cpp +++ b/swerve_pico/sensor-library/sensor-library.cpp @@ -1,22 +1,21 @@ -#include -#include -#include #include +#include -class ZeroingSensor{ - ZeroingSensor(int pin){ - this->readPin = pin; - } +#include "sensor-library.h" - void Setup() { - stdio_init_all(); - gpio_init(this->readPin); - gpio_set_dir(this->readPin, GPIO_IN); - } +ZeroingSensor::ZeroingSensor(int pin) +{ + this->readPin = pin; +} - int Read() { - gpio_get(this->readPin); - } - private: - int readPin; -}; \ No newline at end of file +void ZeroingSensor::Setup() +{ + stdio_init_all(); + gpio_init(this->readPin); + gpio_set_dir(this->readPin, GPIO_IN); +} + +int ZeroingSensor::Read() +{ + gpio_get(this->readPin); +} \ No newline at end of file diff --git a/swerve_pico/sensor-library/sensor-library.h b/swerve_pico/sensor-library/sensor-library.h new file mode 100644 index 0000000..7daa008 --- /dev/null +++ b/swerve_pico/sensor-library/sensor-library.h @@ -0,0 +1,10 @@ +class ZeroingSensor +{ +public: + ZeroingSensor(int pin); + void Setup(); + int Read(); + +private: + int readPin; +}; \ No newline at end of file From 42a95a4a95dd5d095156373c7b21253f436d4c0a Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Mon, 29 Jul 2024 02:23:27 -0700 Subject: [PATCH 64/65] edit cmake files added clist files --- swerve_pico/CMakeLists.txt | 32 ++++++-- .../control-library/control-library.cpp | 4 +- swerve_pico/control-library/control-library.h | 2 +- swerve_pico/motor-library/motor-library.cpp | 6 +- swerve_pico/motor-library/motor-library.h | 34 +++++---- swerve_pico/pico_sdk_import.cmake | 73 +++++++++++++++++++ swerve_pico/{main.cpp => swerve-pico.cpp} | 1 - 7 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 swerve_pico/pico_sdk_import.cmake rename swerve_pico/{main.cpp => swerve-pico.cpp} (99%) diff --git a/swerve_pico/CMakeLists.txt b/swerve_pico/CMakeLists.txt index 5cfe0b9..aefb706 100644 --- a/swerve_pico/CMakeLists.txt +++ b/swerve_pico/CMakeLists.txt @@ -19,12 +19,20 @@ endif() project(swerve-pico C CXX ASM) +# enable error handling +set(PICO_CXX_ENABLE_EXCEPTIONS 1) + # Initialise the Raspberry Pi Pico SDK pico_sdk_init() # Add executable. Default name is the project name, version 0.1 +add_executable(swerve-pico swerve-pico.cpp +${CMAKE_CURRENT_LIST_DIR}/motor-library/motor-library.cpp +${CMAKE_CURRENT_LIST_DIR}/control-library/control-library.cpp +${CMAKE_CURRENT_LIST_DIR}/sensor-library/sensor-library.cpp +${CMAKE_CURRENT_LIST_DIR}/messaging-library/messaging-library.cpp +) -add_executable(swerve-pico main.cpp ) pico_set_program_name(swerve-pico "swerve-pico") pico_set_program_version(swerve-pico "0.1") @@ -37,7 +45,8 @@ target_link_libraries(swerve-pico pico_stdlib pico_i2c_slave hardware_i2c - ) + hardware_pio +) # Add the standard include files to the build target_include_directories(swerve-pico PRIVATE @@ -46,13 +55,20 @@ target_include_directories(swerve-pico PRIVATE ) # Add any user requested libraries -target_link_libraries( - hardware_gpio +target_link_libraries(swerve-pico hardware_pwm - hardware_timer hardware_pio - hardware_clocks - ) +) -pico_add_extra_outputs(swerve-pico) +# # add all subdirectories of project + target_include_directories(swerve-pico PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/control-library + ${CMAKE_CURRENT_LIST_DIR}/messaging-library + ${CMAKE_CURRENT_LIST_DIR}/motor-library + ${CMAKE_CURRENT_LIST_DIR}/sensor-library +) +# link pio +pico_generate_pio_header(swerve-pico ${CMAKE_CURRENT_LIST_DIR}/motor-library/quadrature_encoder.pio) + +pico_add_extra_outputs(swerve-pico) diff --git a/swerve_pico/control-library/control-library.cpp b/swerve_pico/control-library/control-library.cpp index d9d3e5e..d113b51 100644 --- a/swerve_pico/control-library/control-library.cpp +++ b/swerve_pico/control-library/control-library.cpp @@ -1,9 +1,11 @@ +#include "pico/stdlib.h" +#include "time.h" #include "control-library.h" bool calculateCallback(repeating_timer_t *rt); bool updateIntergralDerivativeCallback(repeating_timer_t *rt); -PID::PID(float P, float I, float D, Motor motor, char tuningMode = PID_TUNING_MODE_POSITION, int timestep = 100, int substep = 10) +PID::PID(float P, float I, float D, Motor motor, char tuningMode, int timestep, int substep) { this->P = P; this->I = I; diff --git a/swerve_pico/control-library/control-library.h b/swerve_pico/control-library/control-library.h index dacadc7..dbee8f4 100644 --- a/swerve_pico/control-library/control-library.h +++ b/swerve_pico/control-library/control-library.h @@ -1,5 +1,5 @@ #include -#include +#include #include "motor-library.h" diff --git a/swerve_pico/motor-library/motor-library.cpp b/swerve_pico/motor-library/motor-library.cpp index 898906e..310ad10 100644 --- a/swerve_pico/motor-library/motor-library.cpp +++ b/swerve_pico/motor-library/motor-library.cpp @@ -21,7 +21,7 @@ static EncoderFactory factory; /** * Motor class that holds PWMControl and quadrature_encoder class, encapsulates functions to set and read values of 1 motor */ -Motor::Motor(int motorPin, int pwmPin, unsigned int encoderPin, float ratio = 1.0, int chan = PWM_CHAN_A, int countMax = COUNT_MAX) +Motor::Motor(int motorPin, int pwmPin, unsigned int encoderPin, float ratio, int chan, int countMax) { this->pwmControl = PWMControl(motorPin, pwmPin, chan, countMax); this->encoder = factory.createEncoder(encoderPin, ratio); @@ -47,7 +47,7 @@ float Motor::GetVelocity() return this->encoder.get_velocity(); } -PWMControl::PWMControl(int motorPin, int pwmPin, int chan = PWM_CHAN_A, int countMax = COUNT_MAX) +PWMControl::PWMControl(int motorPin, int pwmPin, int chan, int countMax) { // two motors per module, each requiring 3 pins // two pins to define h-bridge control and one pin for pwm (since there are 2 motors, there are 2 pwm pins) @@ -125,7 +125,7 @@ void PWMControl::Drive(float drivePercentange) // @param sm the state machine to keep track of the encoder, 0-3 // @param which pio // @param ratio the ratio by which to multiply encoder ticks -Encoder::Encoder(uint pinA, uint sm, PIO pio, float ratio = 1.0, bool addProgram = true) +Encoder::Encoder(uint pinA, uint sm, PIO pio, float ratio, bool addProgram) { this->pio = pio; this->sm = sm; diff --git a/swerve_pico/motor-library/motor-library.h b/swerve_pico/motor-library/motor-library.h index 6009363..2bdccce 100644 --- a/swerve_pico/motor-library/motor-library.h +++ b/swerve_pico/motor-library/motor-library.h @@ -1,6 +1,8 @@ -#include "pio.h" -#include "pwm.h" +// #pragma once +#include +#include #include "quadrature_encoder.pio.h" +#include #define COUNT_MAX 65535 @@ -8,20 +10,7 @@ const float ROT_PER_TICK = 1.0 / (4 * 374.0); const float PULLEY_RATIO = 0.3185 / 1.528; const float DEG_PER_ROT = 360.0; -class Motor -{ -public: - Motor(); - Motor(int motorPin, int pwmPin, unsigned int encoderPin, float ratio = 1.0, int chan = PWM_CHAN_A, int countMax = COUNT_MAX); - void Setup(); - void SetPwmPercentage(float percentage); - float GetPosition(); - float GetVelocity(); -private: - PWMControl pwmControl; - Encoder encoder; -}; class PWMControl { @@ -81,3 +70,18 @@ class EncoderFactory private: static unsigned int encoder_count; }; + +class Motor +{ +public: + Motor(); + Motor(int motorPin, int pwmPin, unsigned int encoderPin, float ratio = 1.0, int chan = PWM_CHAN_A, int countMax = COUNT_MAX); + void Setup(); + void SetPwmPercentage(float percentage); + float GetPosition(); + float GetVelocity(); + +private: + PWMControl pwmControl; + Encoder encoder; +}; \ No newline at end of file diff --git a/swerve_pico/pico_sdk_import.cmake b/swerve_pico/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/swerve_pico/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/swerve_pico/main.cpp b/swerve_pico/swerve-pico.cpp similarity index 99% rename from swerve_pico/main.cpp rename to swerve_pico/swerve-pico.cpp index 7ec0340..8f8b0b5 100644 --- a/swerve_pico/main.cpp +++ b/swerve_pico/swerve-pico.cpp @@ -5,7 +5,6 @@ #include #include -#include "motor-library.h" #include "messaging-library.h" #include "control-library.h" #include "sensor-library.h" From aff98c4bf5da03a1bf1c68c042f622ed7e23d72c Mon Sep 17 00:00:00 2001 From: brendanRose1 Date: Mon, 29 Jul 2024 02:45:10 -0700 Subject: [PATCH 65/65] dont care --- swerve_pico/CMakeLists.txt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/swerve_pico/CMakeLists.txt b/swerve_pico/CMakeLists.txt index aefb706..7fa9b64 100644 --- a/swerve_pico/CMakeLists.txt +++ b/swerve_pico/CMakeLists.txt @@ -27,10 +27,6 @@ pico_sdk_init() # Add executable. Default name is the project name, version 0.1 add_executable(swerve-pico swerve-pico.cpp -${CMAKE_CURRENT_LIST_DIR}/motor-library/motor-library.cpp -${CMAKE_CURRENT_LIST_DIR}/control-library/control-library.cpp -${CMAKE_CURRENT_LIST_DIR}/sensor-library/sensor-library.cpp -${CMAKE_CURRENT_LIST_DIR}/messaging-library/messaging-library.cpp ) @@ -60,14 +56,6 @@ target_link_libraries(swerve-pico hardware_pio ) -# # add all subdirectories of project - target_include_directories(swerve-pico PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/control-library - ${CMAKE_CURRENT_LIST_DIR}/messaging-library - ${CMAKE_CURRENT_LIST_DIR}/motor-library - ${CMAKE_CURRENT_LIST_DIR}/sensor-library -) - # link pio pico_generate_pio_header(swerve-pico ${CMAKE_CURRENT_LIST_DIR}/motor-library/quadrature_encoder.pio)