Add custom HID struct for serial transmission
This commit is contained in:
parent
c356b98801
commit
e94f2eb8d7
6 changed files with 335 additions and 81 deletions
|
@ -16,3 +16,5 @@ usbd-hid = "0.6.1"
|
||||||
critical-section = "1.1.1"
|
critical-section = "1.1.1"
|
||||||
embedded-hal = "0.2.7"
|
embedded-hal = "0.2.7"
|
||||||
fugit = "0.3.6"
|
fugit = "0.3.6"
|
||||||
|
numtoa = "0.2.4"
|
||||||
|
usbd-serial = "0.1.1"
|
||||||
|
|
90
src/custom_hid.rs
Normal file
90
src/custom_hid.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
use crate::serial::Serial;
|
||||||
|
|
||||||
|
use rp_pico as bsp;
|
||||||
|
|
||||||
|
use bsp::{
|
||||||
|
hal::{
|
||||||
|
clocks::ClocksManager,
|
||||||
|
pac,
|
||||||
|
pac::{interrupt, Interrupt},
|
||||||
|
usb::UsbBus,
|
||||||
|
Clock,
|
||||||
|
},
|
||||||
|
pac::{RESETS, USBCTRL_DPRAM, USBCTRL_REGS},
|
||||||
|
};
|
||||||
|
|
||||||
|
use cortex_m::{delay::Delay, Peripherals as CortexPeripherals};
|
||||||
|
|
||||||
|
use usb_device::{class_prelude::*, prelude::*};
|
||||||
|
use usbd_hid::{
|
||||||
|
descriptor::{MouseReport, SerializedDescriptor},
|
||||||
|
hid_class::HIDClass,
|
||||||
|
};
|
||||||
|
|
||||||
|
static mut USB_BUS: Option<UsbBusAllocator<UsbBus>> = None;
|
||||||
|
static mut USB_SERIAL: Option<Serial> = None;
|
||||||
|
static mut USB_DEVICE: Option<UsbDevice<UsbBus>> = None;
|
||||||
|
|
||||||
|
pub struct CustomHID<'a> {
|
||||||
|
out_ep: EndpointOut<'a, UsbBus>,
|
||||||
|
in_ep: EndpointIn<'a, UsbBus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CustomHID<'a> {
|
||||||
|
pub fn new(
|
||||||
|
usbctrl_reg: USBCTRL_REGS,
|
||||||
|
usbctrl_dpram: USBCTRL_DPRAM,
|
||||||
|
resets: &mut RESETS,
|
||||||
|
core: CortexPeripherals,
|
||||||
|
clocks: ClocksManager,
|
||||||
|
poll_ms: u8,
|
||||||
|
) -> Self {
|
||||||
|
// Set up the USB driver
|
||||||
|
unsafe {
|
||||||
|
USB_BUS = Some(UsbBusAllocator::new(UsbBus::new(
|
||||||
|
usbctrl_reg,
|
||||||
|
usbctrl_dpram,
|
||||||
|
clocks.usb_clock,
|
||||||
|
true,
|
||||||
|
resets,
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
USB_SERIAL = Some(Serial::new(USB_BUS.as_ref().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
USB_DEVICE = Some(
|
||||||
|
UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x16c0, 0x27dd))
|
||||||
|
.manufacturer("Fake company")
|
||||||
|
.product("Serial port")
|
||||||
|
.serial_number("TEST")
|
||||||
|
.device_class(2) // from: https://www.usb.org/defined-class-codes
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (in_ep, out_ep) = unsafe {
|
||||||
|
pac::NVIC::unmask(Interrupt::USBCTRL_IRQ);
|
||||||
|
|
||||||
|
(
|
||||||
|
USB_BUS.as_ref().unwrap().interrupt(64, poll_ms),
|
||||||
|
USB_BUS.as_ref().unwrap().interrupt(64, poll_ms),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { in_ep, out_ep }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[interrupt]
|
||||||
|
unsafe fn USBCTRL_IRQ() {
|
||||||
|
// Handle USB request
|
||||||
|
let usb_dev = USB_DEVICE.as_mut().unwrap();
|
||||||
|
let usb_hid = USB_SERIAL.as_mut().unwrap();
|
||||||
|
usb_dev.poll(&mut [usb_hid]);
|
||||||
|
}
|
93
src/main.rs
93
src/main.rs
|
@ -1,8 +1,12 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
|
mod custom_hid;
|
||||||
|
mod mouse_hid;
|
||||||
mod mouse_sensor;
|
mod mouse_sensor;
|
||||||
|
mod serial;
|
||||||
|
|
||||||
|
use mouse_hid::MouseHID;
|
||||||
// Ensure we halt the program on panic (if we don't mention this crate it won't
|
// Ensure we halt the program on panic (if we don't mention this crate it won't
|
||||||
// be linked)
|
// be linked)
|
||||||
use panic_halt as _;
|
use panic_halt as _;
|
||||||
|
@ -30,15 +34,6 @@ use usbd_hid::{
|
||||||
|
|
||||||
use crate::mouse_sensor::MouseSensor;
|
use crate::mouse_sensor::MouseSensor;
|
||||||
|
|
||||||
/// The USB Device Driver (shared with the interrupt).
|
|
||||||
static mut USB_DEVICE: Option<UsbDevice<UsbBus>> = None;
|
|
||||||
|
|
||||||
/// The USB Bus Driver (shared with the interrupt).
|
|
||||||
static mut USB_BUS: Option<UsbBusAllocator<UsbBus>> = None;
|
|
||||||
|
|
||||||
/// The USB Human Interface Device Driver (shared with the interrupt).
|
|
||||||
static mut USB_HID: Option<HIDClass<UsbBus>> = None;
|
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
// Grab our singleton objects
|
// Grab our singleton objects
|
||||||
|
@ -61,6 +56,16 @@ fn main() -> ! {
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let peripheral_clock = clocks.peripheral_clock.freq();
|
||||||
|
|
||||||
|
let mut mouse_hid = MouseHID::new(
|
||||||
|
pac.USBCTRL_REGS,
|
||||||
|
pac.USBCTRL_DPRAM,
|
||||||
|
&mut pac.RESETS,
|
||||||
|
core,
|
||||||
|
clocks,
|
||||||
|
);
|
||||||
|
|
||||||
// The single-cycle I/O block controls our GPIO pins
|
// The single-cycle I/O block controls our GPIO pins
|
||||||
let sio = hal::Sio::new(pac.SIO);
|
let sio = hal::Sio::new(pac.SIO);
|
||||||
|
|
||||||
|
@ -72,75 +77,9 @@ fn main() -> ! {
|
||||||
&mut pac.RESETS,
|
&mut pac.RESETS,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mouse_sensor = MouseSensor::new(pins, &mut pac.RESETS, pac.SPI0, &clocks);
|
let mouse_sensor = MouseSensor::new(pins, &mut pac.RESETS, pac.SPI0, peripheral_clock);
|
||||||
|
|
||||||
unsafe {
|
|
||||||
USB_BUS = Some(UsbBusAllocator::new(UsbBus::new(
|
|
||||||
pac.USBCTRL_REGS,
|
|
||||||
pac.USBCTRL_DPRAM,
|
|
||||||
clocks.usb_clock,
|
|
||||||
true,
|
|
||||||
&mut pac.RESETS,
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
USB_HID = Some(HIDClass::new(
|
|
||||||
USB_BUS.as_ref().unwrap(),
|
|
||||||
MouseReport::desc(),
|
|
||||||
60,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
USB_DEVICE = Some(
|
|
||||||
UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x046d, 0x101b))
|
|
||||||
.manufacturer("Logitech")
|
|
||||||
.product("Marathon Mouse/Performance Plus M705")
|
|
||||||
.serial_number("B14D65DA")
|
|
||||||
.device_class(0)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
pac::NVIC::unmask(Interrupt::USBCTRL_IRQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
|
||||||
const PIXEL: i8 = 2;
|
|
||||||
const WAIT_TIME: u32 = 10000;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
mouse_move(&mut delay, WAIT_TIME, PIXEL);
|
mouse_hid.run();
|
||||||
mouse_move(&mut delay, 50, -PIXEL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_move(delay: &mut Delay, wait_time: u32, v: i8) {
|
|
||||||
delay.delay_ms(wait_time);
|
|
||||||
|
|
||||||
let rep = MouseReport {
|
|
||||||
x: 0,
|
|
||||||
y: v,
|
|
||||||
buttons: 0,
|
|
||||||
wheel: 0,
|
|
||||||
pan: 0,
|
|
||||||
};
|
|
||||||
push_mouse_movement(rep).unwrap_or(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// with interrupts disabled, to avoid a race hazard with the USB IRQ.
|
|
||||||
fn push_mouse_movement(report: MouseReport) -> Result<usize, usb_device::UsbError> {
|
|
||||||
critical_section::with(|_| unsafe { USB_HID.as_mut().map(|hid| hid.push_input(&report)) })
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#[interrupt]
|
|
||||||
unsafe fn USBCTRL_IRQ() {
|
|
||||||
// Handle USB request
|
|
||||||
let usb_dev = USB_DEVICE.as_mut().unwrap();
|
|
||||||
let usb_hid = USB_HID.as_mut().unwrap();
|
|
||||||
usb_dev.poll(&mut [usb_hid]);
|
|
||||||
}
|
|
||||||
|
|
116
src/mouse_hid.rs
Normal file
116
src/mouse_hid.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use rp_pico as bsp;
|
||||||
|
|
||||||
|
use bsp::{
|
||||||
|
hal::{
|
||||||
|
clocks::ClocksManager,
|
||||||
|
pac,
|
||||||
|
pac::{interrupt, Interrupt},
|
||||||
|
usb::UsbBus,
|
||||||
|
Clock,
|
||||||
|
},
|
||||||
|
pac::{RESETS, USBCTRL_DPRAM, USBCTRL_REGS},
|
||||||
|
};
|
||||||
|
|
||||||
|
use cortex_m::{delay::Delay, Peripherals as CortexPeripherals};
|
||||||
|
|
||||||
|
use usb_device::{class_prelude::*, prelude::*};
|
||||||
|
use usbd_hid::{
|
||||||
|
descriptor::{MouseReport, SerializedDescriptor},
|
||||||
|
hid_class::HIDClass,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The USB Device Driver (shared with the interrupt).
|
||||||
|
static mut USB_DEVICE: Option<UsbDevice<UsbBus>> = None;
|
||||||
|
|
||||||
|
/// The USB Bus Driver (shared with the interrupt).
|
||||||
|
static mut USB_BUS: Option<UsbBusAllocator<UsbBus>> = None;
|
||||||
|
|
||||||
|
/// The USB Human Interface Device Driver (shared with the interrupt).
|
||||||
|
static mut USB_HID: Option<HIDClass<UsbBus>> = None;
|
||||||
|
|
||||||
|
pub struct MouseHID {
|
||||||
|
delay: Delay,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MouseHID {
|
||||||
|
const PIXEL: i8 = 2;
|
||||||
|
const WAIT_TIME: u32 = 10000;
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
usbctrl_reg: USBCTRL_REGS,
|
||||||
|
usbctrl_dpram: USBCTRL_DPRAM,
|
||||||
|
resets: &mut RESETS,
|
||||||
|
core: CortexPeripherals,
|
||||||
|
clocks: ClocksManager,
|
||||||
|
) -> Self {
|
||||||
|
unsafe {
|
||||||
|
USB_BUS = Some(UsbBusAllocator::new(UsbBus::new(
|
||||||
|
usbctrl_reg,
|
||||||
|
usbctrl_dpram,
|
||||||
|
clocks.usb_clock,
|
||||||
|
true,
|
||||||
|
resets,
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
USB_HID = Some(HIDClass::new(
|
||||||
|
USB_BUS.as_ref().unwrap(),
|
||||||
|
MouseReport::desc(),
|
||||||
|
60,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
USB_DEVICE = Some(
|
||||||
|
UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x046d, 0x101b))
|
||||||
|
.manufacturer("Logitech")
|
||||||
|
.product("Marathon Mouse/Performance Plus M705")
|
||||||
|
.serial_number("B14D65DA")
|
||||||
|
.device_class(0)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
pac::NVIC::unmask(Interrupt::USBCTRL_IRQ);
|
||||||
|
}
|
||||||
|
|
||||||
|
let delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
||||||
|
|
||||||
|
Self { delay }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
self.mouse_move(Self::WAIT_TIME, Self::PIXEL);
|
||||||
|
self.mouse_move(50, -Self::PIXEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_move(&mut self, wait_time: u32, v: i8) {
|
||||||
|
self.delay.delay_ms(wait_time);
|
||||||
|
|
||||||
|
let rep = MouseReport {
|
||||||
|
x: 0,
|
||||||
|
y: v,
|
||||||
|
buttons: 0,
|
||||||
|
wheel: 0,
|
||||||
|
pan: 0,
|
||||||
|
};
|
||||||
|
push_mouse_movement(rep).unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// with interrupts disabled, to avoid a race hazard with the USB IRQ.
|
||||||
|
fn push_mouse_movement(report: MouseReport) -> Result<usize, usb_device::UsbError> {
|
||||||
|
critical_section::with(|_| unsafe { USB_HID.as_mut().map(|hid| hid.push_input(&report)) })
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[interrupt]
|
||||||
|
unsafe fn USBCTRL_IRQ() {
|
||||||
|
// Handle USB request
|
||||||
|
let usb_dev = USB_DEVICE.as_mut().unwrap();
|
||||||
|
let usb_hid = USB_HID.as_mut().unwrap();
|
||||||
|
usb_dev.poll(&mut [usb_hid]);
|
||||||
|
}
|
|
@ -3,14 +3,12 @@ use embedded_hal::digital::v2::OutputPin;
|
||||||
use fugit::HertzU32;
|
use fugit::HertzU32;
|
||||||
use rp_pico::{
|
use rp_pico::{
|
||||||
hal::{
|
hal::{
|
||||||
clocks::ClocksManager,
|
|
||||||
gpio::{
|
gpio::{
|
||||||
self,
|
self,
|
||||||
bank0::{Gpio2, Gpio3, Gpio4, Gpio5},
|
bank0::{Gpio2, Gpio3, Gpio4, Gpio5},
|
||||||
Function, Output, Pin, PushPull, Spi,
|
Function, Output, Pin, PushPull, Spi,
|
||||||
},
|
},
|
||||||
spi::{self, Enabled, Spi as SpiDevice},
|
spi::{self, Enabled, Spi as SpiDevice},
|
||||||
Clock,
|
|
||||||
},
|
},
|
||||||
pac::{RESETS, SPI0},
|
pac::{RESETS, SPI0},
|
||||||
Pins,
|
Pins,
|
||||||
|
@ -29,7 +27,12 @@ impl MouseSensor {
|
||||||
const READ: u8 = 0x7F;
|
const READ: u8 = 0x7F;
|
||||||
const WRITE: u8 = 0x80;
|
const WRITE: u8 = 0x80;
|
||||||
|
|
||||||
pub fn new(pins: Pins, resets: &mut RESETS, spio: SPI0, clocks: &ClocksManager) -> Self {
|
pub fn new(
|
||||||
|
pins: Pins,
|
||||||
|
resets: &mut RESETS,
|
||||||
|
spio: SPI0,
|
||||||
|
peripheral_clock: impl Into<HertzU32>,
|
||||||
|
) -> Self {
|
||||||
// These are implicitly used by the spi driver if they are in the correct mode
|
// These are implicitly used by the spi driver if they are in the correct mode
|
||||||
let _sclk = pins.gpio2.into_mode::<gpio::FunctionSpi>();
|
let _sclk = pins.gpio2.into_mode::<gpio::FunctionSpi>();
|
||||||
let _mosi = pins.gpio3.into_mode::<gpio::FunctionSpi>();
|
let _mosi = pins.gpio3.into_mode::<gpio::FunctionSpi>();
|
||||||
|
@ -42,7 +45,7 @@ impl MouseSensor {
|
||||||
// Exchange the uninitialised SPI driver for an initialised one
|
// Exchange the uninitialised SPI driver for an initialised one
|
||||||
let spi = spi.init(
|
let spi = spi.init(
|
||||||
resets,
|
resets,
|
||||||
clocks.peripheral_clock.freq(),
|
peripheral_clock,
|
||||||
HertzU32::from_raw(16_000_000u32),
|
HertzU32::from_raw(16_000_000u32),
|
||||||
&embedded_hal::spi::MODE_0,
|
&embedded_hal::spi::MODE_0,
|
||||||
);
|
);
|
||||||
|
|
104
src/serial.rs
Normal file
104
src/serial.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use rp_pico as bsp;
|
||||||
|
|
||||||
|
use bsp::hal::usb::UsbBus;
|
||||||
|
|
||||||
|
use usb_device::class_prelude::*;
|
||||||
|
use usbd_serial::SerialPort;
|
||||||
|
|
||||||
|
use numtoa::NumToA;
|
||||||
|
|
||||||
|
pub enum Commands {
|
||||||
|
Reset,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Serial<'a> {
|
||||||
|
serial: SerialPort<'a, UsbBus>,
|
||||||
|
buf: [u8; 256],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Serial<'a> {
|
||||||
|
pub fn new(usb_bus: &'a UsbBusAllocator<UsbBus>) -> Self {
|
||||||
|
Self {
|
||||||
|
serial: SerialPort::new(&usb_bus),
|
||||||
|
buf: [0u8; 256],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&mut self, i: impl NumToA<u16>) {
|
||||||
|
let s = i.numtoa_str(10, &mut self.buf);
|
||||||
|
|
||||||
|
// Send back to the host
|
||||||
|
let mut wr_ptr = s.as_bytes();
|
||||||
|
|
||||||
|
while !wr_ptr.is_empty() {
|
||||||
|
match self.serial.write(wr_ptr) {
|
||||||
|
Ok(len) => wr_ptr = &wr_ptr[len..],
|
||||||
|
// On error, just drop unwritten data.
|
||||||
|
// One possible error is Err(WouldBlock), meaning the USB
|
||||||
|
// write buffer is full.
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self) -> Option<Commands> {
|
||||||
|
match self.serial.read(&mut self.buf) {
|
||||||
|
Err(_e) => None,
|
||||||
|
Ok(0) => None,
|
||||||
|
Ok(count) => {
|
||||||
|
let c = count.min(self.buf.len());
|
||||||
|
let msg = &self.buf[..c];
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
b"reset" => Some(Commands::Reset),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> usb_device::class::UsbClass<rp_pico::hal::usb::UsbBus> for Serial<'a> {
|
||||||
|
fn get_configuration_descriptors(
|
||||||
|
&self,
|
||||||
|
writer: &mut DescriptorWriter,
|
||||||
|
) -> usb_device::Result<()> {
|
||||||
|
self.serial.get_configuration_descriptors(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_bos_descriptors(&self, writer: &mut BosWriter) -> usb_device::Result<()> {
|
||||||
|
self.serial.get_bos_descriptors(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_string(&self, index: StringIndex, lang_id: u16) -> Option<&str> {
|
||||||
|
self.serial.get_string(index, lang_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.serial.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self) {
|
||||||
|
self.serial.poll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_out(&mut self, xfer: ControlOut<rp_pico::hal::usb::UsbBus>) {
|
||||||
|
self.serial.control_out(xfer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_in(&mut self, xfer: ControlIn<rp_pico::hal::usb::UsbBus>) {
|
||||||
|
self.serial.control_in(xfer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint_setup(&mut self, addr: EndpointAddress) {
|
||||||
|
self.serial.endpoint_setup(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint_out(&mut self, addr: EndpointAddress) {
|
||||||
|
self.serial.endpoint_out(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
|
||||||
|
self.serial.endpoint_in_complete(addr)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue