Add Code
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Copyright (c) 2015-2017 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
The pymata_aio package that implements a Python asyncio Arduino StandardFirmata client.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Copyright (c) 2015-2019 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
class Constants:
|
||||
"""
|
||||
This class contains a set of constants that may be used by
|
||||
the applications writer.
|
||||
"""
|
||||
# pin modes
|
||||
INPUT = 0x00 # pin set as input
|
||||
OUTPUT = 0x01 # pin set as output
|
||||
ANALOG = 0x02 # analog pin in analogInput mode
|
||||
PWM = 0x03 # digital pin in PWM output mode
|
||||
SERVO = 0x04 # digital pin in Servo output mode
|
||||
I2C = 0x06 # pin included in I2C setup
|
||||
ONEWIRE = 0x07 # possible future feature
|
||||
STEPPER = 0x08 # any pin in stepper mode
|
||||
ENCODER = 0x09 # Any pin in encoder mode
|
||||
SERIAL = 0x0a
|
||||
PULLUP = 0x0b # Any pin in pullup mode
|
||||
SONAR = 0x0c # Any pin in SONAR mode
|
||||
TONE = 0x0d # Any pin in tone mode
|
||||
PIXY = 0x0e # Any pin used by Pixy camera
|
||||
|
||||
IGNORE = 0x7f
|
||||
|
||||
# Tone commands
|
||||
TONE_TONE = 0 # play a tone
|
||||
TONE_NO_TONE = 1 # turn off tone
|
||||
|
||||
# I2C command operation modes
|
||||
I2C_WRITE = 0B00000000
|
||||
I2C_READ = 0B00001000
|
||||
I2C_READ_CONTINUOUSLY = 0B00010000
|
||||
I2C_STOP_READING = 0B00011000
|
||||
I2C_READ_WRITE_MODE_MASK = 0B00011000
|
||||
I2C_10BIT_ADDRESS_MODE_MASK = 0B00100000
|
||||
I2C_END_TX_MASK = 0B01000000
|
||||
I2C_STOP_TX = 1
|
||||
I2C_RESTART_TX = 0
|
||||
|
||||
# callback types
|
||||
CB_TYPE_DIRECT = None
|
||||
CB_TYPE_ASYNCIO = 1
|
||||
|
||||
# latch states
|
||||
LATCH_IGNORE = 0 # this item currently not participating in latching
|
||||
|
||||
# When the next pin value change is received for this pin,
|
||||
# if it matches the latch criteria the data will be latched
|
||||
LATCH_ARMED = 1
|
||||
|
||||
# data has been latched. Read the data to re-arm the latch
|
||||
LATCH_LATCHED = 2
|
||||
|
||||
# latch threshold types
|
||||
LATCH_EQ = 0 # data value is equal to the latch threshold value
|
||||
LATCH_GT = 1 # data value is greater than the latch threshold value
|
||||
LATCH_LT = 2 # data value is less than the latch threshold value
|
||||
LATCH_GTE = 3 # data value is >= to the latch threshold value
|
||||
LATCH_LTE = 4 # data value is <= to the latch threshold value
|
||||
|
||||
# indices into latch table entry for manual read of latch data
|
||||
LATCH_STATE = 0
|
||||
LATCHED_THRESHOLD_TYPE = 1
|
||||
|
||||
LATCH_DATA_TARGET = 2
|
||||
LATCHED_DATA = 3
|
||||
LATCHED_TIME_STAMP = 4
|
||||
LATCH_CALLBACK = 5
|
||||
LATCH_CALLBACK_TYPE = 6
|
||||
|
||||
# indices for data returned for a latch callback
|
||||
LATCH_CALL_BACK_PIN = 0
|
||||
LATCH_CALL_BACK_DATA = 1
|
||||
LATCH_CALLBACK_TIME_STAMP = 2
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Copyright (c) 2015-2019 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
class PinData:
|
||||
"""
|
||||
Each analog and digital input pin is described by an instance of
|
||||
this class. It contains both the last data value received and a potential
|
||||
callback reference and the callback method type.
|
||||
The callback method type default is a non-asyncio call,
|
||||
but can be optionally be set to use yield from when required.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# current data value
|
||||
self._current_value = 0
|
||||
# callback reference
|
||||
self._cb = None
|
||||
# call back to be executed with "await" or direct call
|
||||
# direct call is the default
|
||||
self._cb_type = None
|
||||
|
||||
@property
|
||||
def current_value(self):
|
||||
return self._current_value
|
||||
|
||||
@current_value.setter
|
||||
def current_value(self, value):
|
||||
self._current_value = value
|
||||
|
||||
@property
|
||||
def cb(self):
|
||||
return self._cb
|
||||
|
||||
@cb.setter
|
||||
def cb(self, value):
|
||||
self._cb = value
|
||||
|
||||
@property
|
||||
def cb_type(self):
|
||||
return self._cb_type
|
||||
|
||||
@cb_type.setter
|
||||
def cb_type(self, value):
|
||||
self._cb_type = value
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Copyright (c) 2015-2019 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
class PrivateConstants:
|
||||
"""
|
||||
This class contains a set of constants for PyMata internal use .
|
||||
"""
|
||||
# the following defines are from Firmata.h
|
||||
# message command bytes (128-255/ 0x80- 0xFF)
|
||||
# from this client to firmata
|
||||
MSG_CMD_MIN = 0x80 # minimum value for a message from firmata
|
||||
REPORT_ANALOG = 0xC0 # enable analog input by pin #
|
||||
REPORT_DIGITAL = 0xD0 # enable digital input by port pair
|
||||
SET_PIN_MODE = 0xF4 # set a pin to INPUT/OUTPUT/PWM/etc
|
||||
SET_DIGITAL_PIN_VALUE = 0xF5 # set a single digital pin value instead of entire port
|
||||
START_SYSEX = 0xF0 # start a MIDI Sysex message
|
||||
END_SYSEX = 0xF7 # end a MIDI Sysex message
|
||||
SYSTEM_RESET = 0xFF # reset from MIDI
|
||||
|
||||
# messages from firmata
|
||||
DIGITAL_MESSAGE = 0x90 # send or receive data for a digital pin
|
||||
ANALOG_MESSAGE = 0xE0 # send or receive data for a PWM configured pin
|
||||
REPORT_VERSION = 0xF9 # report protocol version
|
||||
|
||||
# start of FirmataPlus defined SYSEX commands
|
||||
KEEP_ALIVE = 0x50 # keep alive message
|
||||
TONE_DATA = 0x5F # play a tone at a specified frequency and duration
|
||||
ENCODER_CONFIG = 0x60 # create and enable encoder object
|
||||
ENCODER_DATA = 0x61 # current encoder position data
|
||||
SONAR_CONFIG = 0x62 # configure pins to control a sonar distance device
|
||||
SONAR_DATA = 0x63 # distance data returned
|
||||
PIXY_CONFIG = 0x64 # configure the Pixy. Configure has 4 subcommands
|
||||
PIXY_DATA = 0x65 # blocks data returned
|
||||
# end of FirmataPlus defined SYSEX commands
|
||||
|
||||
SERVO_CONFIG = 0x70 # set servo pin and max and min angles
|
||||
STRING_DATA = 0x71 # a string message with 14-bits per char
|
||||
STEPPER_DATA = 0x72 # Stepper motor command
|
||||
I2C_REQUEST = 0x76 # send an I2C read/write request
|
||||
I2C_REPLY = 0x77 # a reply to an I2C read request
|
||||
I2C_CONFIG = 0x78 # config I2C settings such as delay times and power pins
|
||||
REPORT_FIRMWARE = 0x79 # report name and version of the firmware
|
||||
SAMPLING_INTERVAL = 0x7A # modify the sampling interval
|
||||
|
||||
EXTENDED_ANALOG = 0x6F # analog write (PWM, Servo, etc) to any pin
|
||||
PIN_STATE_QUERY = 0x6D # ask for a pin's current mode and value
|
||||
PIN_STATE_RESPONSE = 0x6E # reply with pin's current mode and value
|
||||
CAPABILITY_QUERY = 0x6B # ask for supported modes of all pins
|
||||
CAPABILITY_RESPONSE = 0x6C # reply with supported modes and resolution
|
||||
ANALOG_MAPPING_QUERY = 0x69 # ask for mapping of analog to pin numbers
|
||||
ANALOG_MAPPING_RESPONSE = 0x6A # reply with analog mapping data
|
||||
|
||||
# reserved values
|
||||
SYSEX_NON_REALTIME = 0x7E # MIDI Reserved for non-realtime messages
|
||||
SYSEX_REALTIME = 0x7F # MIDI Reserved for realtime messages
|
||||
|
||||
# reserved for PyMata
|
||||
PYMATA_VERSION = "2.34"
|
||||
|
||||
# each byte represents a digital port
|
||||
# and its value contains the current port settings
|
||||
DIGITAL_OUTPUT_PORT_PINS = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
|
||||
# These values are the index into the data passed by _arduino and
|
||||
# used to reassemble integer values
|
||||
MSB = 2
|
||||
LSB = 1
|
||||
|
||||
# enable reporting for REPORT_ANALOG or REPORT_DIGITAL message
|
||||
# sent to firmata
|
||||
REPORTING_ENABLE = 1
|
||||
# disable reporting for REPORT_ANALOG or REPORT_DIGITAL message
|
||||
# sent to firmata
|
||||
REPORTING_DISABLE = 0
|
||||
|
||||
# Stepper Motor Sub-commands
|
||||
STEPPER_CONFIGURE = 0 # configure a stepper motor for operation
|
||||
STEPPER_STEP = 1 # command a motor to move at the provided speed
|
||||
STEPPER_LIBRARY_VERSION = 2 # used to get stepper library version number
|
||||
|
||||
# Pixy sub commands
|
||||
PIXY_INIT = 0 # Initialize the Pixy object and set the max number of blocks to report
|
||||
PIXY_SET_SERVOS = 1 # directly control the pan and tilt servo motors
|
||||
PIXY_SET_BRIGHTNESS = 2 # adjust the brightness of the Pixy exposure
|
||||
PIXY_SET_LED = 3 # control the color of the Pixy LED
|
||||
|
||||
# Pin used to store Pixy data
|
||||
PIN_PIXY_MOSI = 11
|
||||
|
||||
793
code/.venv/lib/python3.12/site-packages/pymata_aio/pymata3.py
Normal file
793
code/.venv/lib/python3.12/site-packages/pymata_aio/pymata3.py
Normal file
@@ -0,0 +1,793 @@
|
||||
"""
|
||||
Copyright (c) 2015-2019 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
try:
|
||||
from pymata_core import PymataCore
|
||||
except ImportError:
|
||||
from .pymata_core import PymataCore
|
||||
|
||||
|
||||
class PyMata3:
|
||||
"""
|
||||
This class exposes and implements a proxy API for the pymata_core asyncio
|
||||
API, If your application does not use asyncio directly, this is
|
||||
the API that you should use.
|
||||
"""
|
||||
|
||||
def __init__(self, arduino_wait=2, sleep_tune=0.0001, log_output=False, com_port=None,
|
||||
ip_address=None, ip_port=2000, ip_handshake='*HELLO*',
|
||||
port_discovery_exceptions=False):
|
||||
"""
|
||||
Constructor for the PyMata3 API
|
||||
If log_output is set to True, a log file called 'pymata_log'
|
||||
will be created in the current directory and all pymata_aio output
|
||||
will be redirected to the log with no output appearing on the console.
|
||||
|
||||
:param arduino_wait: Amount of time to allow Arduino to finish its
|
||||
reset (2 seconds for Uno, Leonardo can be 0)
|
||||
|
||||
:param sleep_tune: This parameter sets the amount of time PyMata core
|
||||
uses to set asyncio.sleep
|
||||
|
||||
:param log_output: If True, pymata_aio.log is created and all
|
||||
console output is redirected to this file.
|
||||
|
||||
:param com_port: If specified, auto port detection will not be
|
||||
performed and this com port will be used.
|
||||
|
||||
:param ip_address: If using a WiFly module, set its address here
|
||||
|
||||
:param ip_port: Port to used with ip_address
|
||||
|
||||
:param ip_handshake: Connectivity handshake string sent by IP device
|
||||
|
||||
:param port_discovery_exceptions: If True, then RuntimeError is
|
||||
raised instead of exiting.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
self.log_out = log_output
|
||||
# get the event loop
|
||||
# this is for python 3.8
|
||||
if sys.platform == 'win32':
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
self.loop = asyncio.get_event_loop()
|
||||
|
||||
self.sleep_tune = sleep_tune
|
||||
self.core = PymataCore(arduino_wait, self.sleep_tune, log_output,
|
||||
com_port, ip_address, ip_port, ip_handshake,
|
||||
port_discovery_exceptions, self.loop)
|
||||
self.core.start()
|
||||
self.sleep(1)
|
||||
|
||||
def analog_read(self, pin):
|
||||
"""
|
||||
Retrieve the last data update for the specified analog pin.
|
||||
It is intended for a polling application.
|
||||
|
||||
:param pin: Analog pin number (ex. A2 is specified as 2)
|
||||
|
||||
:returns: Last value reported for the analog pin
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.analog_read(pin))
|
||||
value = self.loop.run_until_complete(task)
|
||||
return value
|
||||
|
||||
def analog_write(self, pin, value):
|
||||
"""
|
||||
Set the selected PWM pin to the specified value.
|
||||
|
||||
:param pin: PWM pin number
|
||||
|
||||
:param value: Set the selected pin to the specified
|
||||
value. 0-0x4000 (14 bits)
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.analog_write(pin, value))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def digital_read(self, pin):
|
||||
"""
|
||||
Retrieve the last data update for the specified digital pin.
|
||||
It is intended for a polling application.
|
||||
|
||||
:param pin: Digital pin number
|
||||
|
||||
:returns: Last value reported for the digital pin
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.digital_read(pin))
|
||||
value = self.loop.run_until_complete(task)
|
||||
return value
|
||||
|
||||
def digital_pin_write(self, pin, value=0):
|
||||
"""
|
||||
Set the specified digital input pin to the provided value
|
||||
|
||||
:param pin: Digital pin to be set
|
||||
|
||||
:param value: 0 or 1
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.digital_pin_write(pin, value))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def digital_write(self, pin, value=0):
|
||||
"""
|
||||
Set the specified digital input pin to the provided value
|
||||
|
||||
:param pin: Digital pin to be set
|
||||
|
||||
:param value: 0 or 1
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.digital_write(pin, value))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def disable_analog_reporting(self, pin):
|
||||
"""
|
||||
Disables analog reporting for a single analog pin.
|
||||
|
||||
:param pin: Analog pin number. For example for A0, the number is 0.
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.disable_analog_reporting(pin))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def disable_digital_reporting(self, pin):
|
||||
"""
|
||||
Disables digital reporting. By turning reporting off for this pin,
|
||||
reporting is disabled for all 8 bits in the "port"
|
||||
|
||||
:param pin: Pin and all pins for this port
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.disable_digital_reporting(pin))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def encoder_config(self, pin_a, pin_b, cb=None, cb_type=None,
|
||||
hall_encoder=False):
|
||||
"""
|
||||
This command enables the rotary encoder (2 pin + ground) and will
|
||||
enable encoder reporting.
|
||||
|
||||
This is a FirmataPlus feature.
|
||||
|
||||
Encoder data is retrieved by performing a digital_read from
|
||||
pin a (encoder pin 1)
|
||||
|
||||
:param pin_a: Encoder pin 1.
|
||||
|
||||
:param pin_b: Encoder pin 2.
|
||||
|
||||
:param cb: callback function to report encoder changes
|
||||
|
||||
:param cb_type: Constants.CB_TYPE_DIRECT = direct call
|
||||
or Constants.CB_TYPE_ASYNCIO = asyncio coroutine
|
||||
|
||||
:param hall_encoder: wheel hall_encoder - set to True to select
|
||||
hall encoder support support.
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.encoder_config(pin_a, pin_b,
|
||||
cb, cb_type,
|
||||
hall_encoder))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def encoder_read(self, pin):
|
||||
"""
|
||||
This method retrieves the latest encoder data value.
|
||||
It is a FirmataPlus feature.
|
||||
|
||||
:param pin: Encoder Pin
|
||||
|
||||
:returns: encoder data value
|
||||
"""
|
||||
try:
|
||||
task = asyncio.ensure_future(self.core.encoder_read(pin))
|
||||
value = self.loop.run_until_complete(task)
|
||||
return value
|
||||
except RuntimeError:
|
||||
self.shutdown()
|
||||
|
||||
def enable_analog_reporting(self, pin):
|
||||
"""
|
||||
Enables analog reporting for a single analog pin,
|
||||
|
||||
:param pin: Analog pin number. For example for A0, the number is 0.
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.enable_analog_reporting(pin))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def enable_digital_reporting(self, pin):
|
||||
"""
|
||||
Enables digital reporting. By turning reporting on for all
|
||||
8 bits in the "port".
|
||||
This is part of Firmata's protocol specification.
|
||||
|
||||
:param pin: Pin and all pins for this port
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.enable_digital_reporting(pin))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def extended_analog(self, pin, data):
|
||||
"""
|
||||
This method will send an extended-data analog write command
|
||||
to the selected pin..
|
||||
|
||||
:param pin: 0 - 127
|
||||
|
||||
:param data: 0 - 0-0x4000 (14 bits)
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.extended_analog(pin, data))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def get_analog_latch_data(self, pin):
|
||||
"""
|
||||
A list is returned containing the latch state for the pin, the
|
||||
latched value, and the time stamp
|
||||
[pin_num, latch_state, latched_value, time_stamp]
|
||||
If the the latch state is LATCH_LATCHED, the table is reset
|
||||
(data and timestamp set to zero)
|
||||
It is intended to be used for a polling application.
|
||||
|
||||
:param pin: Pin number.
|
||||
|
||||
:returns: [latched_state, threshold_type, threshold_value,
|
||||
latched_data, time_stamp]
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_analog_latch_data(pin))
|
||||
l_data = self.loop.run_until_complete(task)
|
||||
return l_data
|
||||
|
||||
def get_analog_map(self, cb=None):
|
||||
"""
|
||||
This method requests and returns an analog map.
|
||||
|
||||
:param cb: Optional callback reference
|
||||
|
||||
:returns: An analog map response or None if a timeout occurs
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_analog_map())
|
||||
report = self.loop.run_until_complete(task)
|
||||
if cb:
|
||||
cb(report)
|
||||
else:
|
||||
return report
|
||||
|
||||
def get_capability_report(self, raw=True, cb=None):
|
||||
"""
|
||||
This method retrieves the Firmata capability report
|
||||
|
||||
:param raw: If True, it either stores or provides the callback
|
||||
with a report as list.
|
||||
If False, prints a formatted report to the console
|
||||
|
||||
:param cb: Optional callback reference to receive a raw report
|
||||
|
||||
:returns: capability report
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_capability_report())
|
||||
report = self.loop.run_until_complete(task)
|
||||
if raw:
|
||||
if cb:
|
||||
cb(report)
|
||||
else:
|
||||
return report
|
||||
else:
|
||||
# noinspection PyProtectedMember
|
||||
self.core._format_capability_report(report)
|
||||
|
||||
def get_digital_latch_data(self, pin):
|
||||
"""
|
||||
A list is returned containing the latch state for the pin, the
|
||||
latched value, and the time stamp
|
||||
|
||||
[pin_num, latch_state, latched_value, time_stamp]
|
||||
|
||||
If the the latch state is LATCH_LATCHED, the table is reset
|
||||
(data and timestamp set to zero).
|
||||
It is intended for use by a polling application.
|
||||
|
||||
:param pin: Pin number.
|
||||
|
||||
:returns: [latched_state, threshold_type, threshold_value,
|
||||
latched_data, time_stamp]
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_digital_latch_data(pin))
|
||||
l_data = self.loop.run_until_complete(task)
|
||||
return l_data
|
||||
|
||||
def get_firmware_version(self, cb=None):
|
||||
"""
|
||||
This method retrieves the Firmata firmware version
|
||||
|
||||
:param cb: Reference to a callback function
|
||||
|
||||
:returns:If no callback is specified, the firmware version
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_firmware_version())
|
||||
version = self.loop.run_until_complete(task)
|
||||
if cb:
|
||||
cb(version)
|
||||
else:
|
||||
return version
|
||||
|
||||
def get_protocol_version(self, cb=None):
|
||||
"""
|
||||
This method retrieves the Firmata protocol version
|
||||
|
||||
:param cb: Optional callback reference.
|
||||
|
||||
:returns:If no callback is specified, the firmware version
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_protocol_version())
|
||||
version = self.loop.run_until_complete(task)
|
||||
|
||||
if cb:
|
||||
cb(version)
|
||||
else:
|
||||
return version
|
||||
|
||||
def get_pin_state(self, pin, cb=None):
|
||||
"""
|
||||
This method retrieves a pin state report for the specified pin
|
||||
|
||||
:param pin: Pin of interest
|
||||
|
||||
:param cb: optional callback reference
|
||||
|
||||
:returns: pin state report
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_pin_state(pin))
|
||||
report = self.loop.run_until_complete(task)
|
||||
|
||||
if cb:
|
||||
cb(report)
|
||||
else:
|
||||
return report
|
||||
|
||||
def get_pymata_version(self):
|
||||
"""
|
||||
This method retrieves the PyMata version number
|
||||
|
||||
:returns: PyMata version number.
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.get_pymata_version())
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def i2c_config(self, read_delay_time=0):
|
||||
"""
|
||||
This method configures Arduino i2c with an optional read delay time.
|
||||
|
||||
:param read_delay_time: firmata i2c delay time
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.i2c_config(read_delay_time))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def i2c_read_data(self, address):
|
||||
"""
|
||||
Retrieve result of last data read from i2c device.
|
||||
i2c_read_request should be called before trying to retrieve data.
|
||||
It is intended for use by a polling application.
|
||||
|
||||
:param address: i2c
|
||||
|
||||
:returns: last data read or None if no data is present.
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.i2c_read_data(address))
|
||||
value = self.loop.run_until_complete(task)
|
||||
return value
|
||||
|
||||
def i2c_read_request(self, address, register, number_of_bytes, read_type,
|
||||
cb=None, cb_type=None):
|
||||
"""
|
||||
This method issues an i2c read request for a single read,continuous
|
||||
read or a stop, specified by the read_type.
|
||||
Because different i2c devices return data at different rates,
|
||||
if a callback is not specified, the user must first call this method
|
||||
and then call i2c_read_data after waiting for sufficient time for the
|
||||
i2c device to respond.
|
||||
Some devices require that transmission be restarted
|
||||
(e.g. MMA8452Q accelerometer).
|
||||
Use I2C_READ | I2C_RESTART_TX for those cases.
|
||||
|
||||
:param address: i2c device
|
||||
|
||||
:param register: i2c register number
|
||||
|
||||
:param number_of_bytes: number of bytes to be returned
|
||||
|
||||
:param read_type: Constants.I2C_READ, Constants.I2C_READ_CONTINUOUSLY
|
||||
or Constants.I2C_STOP_READING.
|
||||
|
||||
Constants.I2C_RESTART_TX may be OR'ed when required
|
||||
:param cb: optional callback reference
|
||||
|
||||
:param cb_type: Constants.CB_TYPE_DIRECT = direct call or
|
||||
Constants.CB_TYPE_ASYNCIO = asyncio coroutine
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
|
||||
task = asyncio.ensure_future(self.core.i2c_read_request(address, register,
|
||||
number_of_bytes,
|
||||
read_type,
|
||||
cb,
|
||||
cb_type))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def i2c_write_request(self, address, args):
|
||||
"""
|
||||
Write data to an i2c device.
|
||||
|
||||
:param address: i2c device address
|
||||
|
||||
:param args: A variable number of bytes to be sent to the device
|
||||
passed in as a list.
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.i2c_write_request(address, args))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def keep_alive(self, period=1, margin=.3):
|
||||
"""
|
||||
Periodically send a keep alive message to the Arduino.
|
||||
Frequency of keep alive transmission is calculated as follows:
|
||||
keep_alive_sent = period - (period * margin)
|
||||
|
||||
|
||||
:param period: Time period between keepalives.
|
||||
Range is 0-10 seconds. 0 disables
|
||||
the keepalive mechanism.
|
||||
|
||||
:param margin: Safety margin to assure keepalives
|
||||
are sent before period expires. Range is 0.1 to 0.9
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
asyncio.ensure_future(self.core.keep_alive(period, margin))
|
||||
|
||||
def play_tone(self, pin, tone_command, frequency, duration=None):
|
||||
"""
|
||||
This method will call the Tone library for the selected pin.
|
||||
It requires FirmataPlus to be loaded onto the arduino
|
||||
|
||||
If the tone command is set to TONE_TONE, then the specified
|
||||
tone will be played.
|
||||
|
||||
Else, if the tone command is TONE_NO_TONE, then any currently
|
||||
playing tone will be disabled.
|
||||
|
||||
|
||||
:param pin: Pin number
|
||||
|
||||
:param tone_command: Either TONE_TONE, or TONE_NO_TONE
|
||||
|
||||
:param frequency: Frequency of tone
|
||||
|
||||
:param duration: Duration of tone in milliseconds
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.play_tone(pin, tone_command,
|
||||
frequency, duration))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def send_reset(self):
|
||||
"""
|
||||
Send a Firmata reset command
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.send_reset())
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def servo_config(self, pin, min_pulse=544, max_pulse=2400):
|
||||
"""
|
||||
This method configures the Arduino for servo operation.
|
||||
|
||||
:param pin: Servo control pin
|
||||
|
||||
:param min_pulse: Minimum pulse width
|
||||
|
||||
:param max_pulse: Maximum pulse width
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.servo_config(pin, min_pulse,
|
||||
max_pulse))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def set_analog_latch(self, pin, threshold_type, threshold_value,
|
||||
cb=None, cb_type=None):
|
||||
"""
|
||||
This method "arms" an analog pin for its data to be latched and
|
||||
saved in the latching table.
|
||||
If a callback method is provided, when latching criteria is achieved,
|
||||
the callback function is called with latching data notification.
|
||||
|
||||
:param pin: Analog pin number (value following an 'A' designator,
|
||||
i.e. A5 = 5
|
||||
|
||||
:param threshold_type: Constants.LATCH_GT | Constants.LATCH_LT |
|
||||
Constants.LATCH_GTE | Constants.LATCH_LTE
|
||||
|
||||
:param threshold_value: numerical value - between 0 and 1023
|
||||
|
||||
:param cb: callback method
|
||||
|
||||
:param cb_type: Constants.CB_TYPE_DIRECT = direct call or
|
||||
Constants.CB_TYPE_ASYNCIO = asyncio coroutine
|
||||
|
||||
:returns: True if successful, False if parameter data is invalid
|
||||
"""
|
||||
|
||||
task = asyncio.ensure_future(self.core.set_analog_latch(pin, threshold_type, threshold_value, cb, cb_type))
|
||||
result = self.loop.run_until_complete(task)
|
||||
return result
|
||||
|
||||
def set_digital_latch(self, pin, threshold_value, cb=None, cb_type=None):
|
||||
"""
|
||||
This method "arms" a digital pin for its data to be latched and saved
|
||||
in the latching table.
|
||||
If a callback method is provided, when latching criteria is achieved,
|
||||
the callback function is called
|
||||
with latching data notification.
|
||||
|
||||
:param pin: Digital pin number
|
||||
|
||||
:param threshold_value: 0 or 1
|
||||
|
||||
:param cb: callback function
|
||||
|
||||
:param cb_type: Constants.CB_TYPE_DIRECT = direct call or
|
||||
Constants.CB_TYPE_ASYNCIO = asyncio coroutine
|
||||
|
||||
:returns: True if successful, False if parameter data is invalid
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.set_digital_latch(pin, threshold_value, cb, cb_type))
|
||||
result = self.loop.run_until_complete(task)
|
||||
return result
|
||||
|
||||
def set_pin_mode(self, pin_number, pin_state, callback=None, cb_type=None):
|
||||
"""
|
||||
This method sets the pin mode for the specified pin.
|
||||
|
||||
:param pin_number: Arduino Pin Number
|
||||
|
||||
:param pin_state: INPUT/OUTPUT/ANALOG/PWM/PULLUP - for SERVO use
|
||||
servo_config()
|
||||
|
||||
:param callback: Optional: A reference to a call back function to be
|
||||
called when pin data value changes
|
||||
|
||||
:param cb_type: Constants.CB_TYPE_DIRECT = direct call or
|
||||
Constants.CB_TYPE_ASYNCIO = asyncio coroutine
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.set_pin_mode(pin_number, pin_state, callback, cb_type))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def set_sampling_interval(self, interval):
|
||||
"""
|
||||
This method sets the sampling interval for the Firmata loop method
|
||||
|
||||
:param interval: time in milliseconds
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.set_sampling_interval(interval))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def sleep(self, time):
|
||||
"""
|
||||
Perform an asyncio sleep for the time specified in seconds. T
|
||||
his method should be used in place of time.sleep()
|
||||
|
||||
:param time: time in seconds
|
||||
:returns: No return value
|
||||
"""
|
||||
try:
|
||||
task = asyncio.ensure_future(self.core.sleep(time))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdown the application and exit
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.shutdown())
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def sonar_data_retrieve(self, trigger_pin):
|
||||
"""
|
||||
Retrieve Ping (HC-SR04 type) data. The data is presented as a
|
||||
dictionary.
|
||||
The 'key' is the trigger pin specified in sonar_config() and the
|
||||
'data' is the current measured distance (in centimeters)
|
||||
for that pin. If there is no data, the value is set to None.
|
||||
This is a FirmataPlus feature.
|
||||
|
||||
:param trigger_pin: trigger pin specified in sonar_config
|
||||
|
||||
:returns: active_sonar_map
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.sonar_data_retrieve(trigger_pin))
|
||||
sonar_data = self.loop.run_until_complete(task)
|
||||
return sonar_data
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def sonar_config(self, trigger_pin, echo_pin, cb=None, ping_interval=50,
|
||||
max_distance=200, cb_type=None):
|
||||
"""
|
||||
Configure the pins,ping interval and maximum distance for an HC-SR04
|
||||
type device.
|
||||
Single pin configuration may be used. To do so, set both the trigger
|
||||
and echo pins to the same value.
|
||||
Up to a maximum of 6 SONAR devices is supported
|
||||
If the maximum is exceeded a message is sent to the console and the
|
||||
request is ignored.
|
||||
NOTE: data is measured in centimeters
|
||||
|
||||
This is FirmataPlus feature.
|
||||
|
||||
:param trigger_pin: The pin number of for the trigger (transmitter).
|
||||
|
||||
:param echo_pin: The pin number for the received echo.
|
||||
|
||||
:param cb: optional callback function to report sonar data changes
|
||||
|
||||
:param ping_interval: Minimum interval between pings. Lowest number
|
||||
to use is 33 ms.Max is 127
|
||||
|
||||
:param max_distance: Maximum distance in cm. Max is 200.
|
||||
|
||||
:param cb_type: direct call or asyncio yield from
|
||||
|
||||
:returns: No return value
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.sonar_config(trigger_pin,
|
||||
echo_pin, cb,
|
||||
ping_interval,
|
||||
max_distance, cb_type))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def stepper_config(self, steps_per_revolution, stepper_pins):
|
||||
"""
|
||||
Configure stepper motor prior to operation.
|
||||
This is a FirmataPlus feature.
|
||||
|
||||
:param steps_per_revolution: number of steps per motor revolution
|
||||
|
||||
:param stepper_pins: a list of control pin numbers - either 4 or 2
|
||||
|
||||
:returns: No return value
|
||||
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.stepper_config(steps_per_revolution,
|
||||
stepper_pins))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def stepper_step(self, motor_speed, number_of_steps):
|
||||
"""
|
||||
Move a stepper motor for the number of steps at the specified speed
|
||||
This is a FirmataPlus feature.
|
||||
|
||||
:param motor_speed: 21 bits of data to set motor speed
|
||||
|
||||
:param number_of_steps: 14 bits for number of steps & direction
|
||||
positive is forward, negative is reverse
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.stepper_step(motor_speed,
|
||||
number_of_steps))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def pixy_init(self, max_blocks=5, cb=None, cb_type=None):
|
||||
"""
|
||||
Initialize Pixy and will enable Pixy block reporting.
|
||||
This is a FirmataPlusRB feature.
|
||||
|
||||
:param cb: callback function to report Pixy blocks
|
||||
|
||||
:param cb_type: Constants.CB_TYPE_DIRECT = direct call or
|
||||
Constants.CB_TYPE_ASYNCIO = asyncio coroutine
|
||||
|
||||
:param max_blocks: Maximum number of Pixy blocks to report when many
|
||||
signatures are found.
|
||||
|
||||
:returns: No return value.
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.pixy_init(max_blocks, cb, cb_type))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def pixy_get_blocks(self):
|
||||
"""
|
||||
This method retrieves the latest Pixy data value
|
||||
|
||||
:returns: Pixy data
|
||||
"""
|
||||
return self.core.pixy_blocks
|
||||
|
||||
def pixy_set_servos(self, s0, s1):
|
||||
"""
|
||||
Sends the setServos Pixy command.
|
||||
This method sets the pan/tilt servos that are plugged into Pixy's two servo ports.
|
||||
|
||||
:param s0: value 0 to 1000
|
||||
|
||||
:param s1: value 0 to 1000
|
||||
|
||||
:returns: No return value.
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.pixy_set_servos(s0, s1))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def pixy_set_brightness(self, brightness):
|
||||
"""
|
||||
Sends the setBrightness Pixy command.
|
||||
This method sets the brightness (exposure) of Pixy's camera.
|
||||
|
||||
:param brightness: range between 0 and 255 with 255 being the
|
||||
brightest setting
|
||||
|
||||
:returns: No return value.
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.pixy_set_brightness(brightness))
|
||||
self.loop.run_until_complete(task)
|
||||
|
||||
def pixy_set_led(self, r, g, b):
|
||||
"""
|
||||
Sends the setLed Pixy command.
|
||||
This method sets the RGB LED on front of Pixy.
|
||||
|
||||
:param r: red range between 0 and 255
|
||||
|
||||
:param g: green range between 0 and 255
|
||||
|
||||
:param b: blue range between 0 and 255
|
||||
|
||||
:returns: No return value.
|
||||
"""
|
||||
task = asyncio.ensure_future(self.core.pixy_set_led(r, g, b))
|
||||
self.loop.run_until_complete(task)
|
||||
File diff suppressed because it is too large
Load Diff
2107
code/.venv/lib/python3.12/site-packages/pymata_aio/pymata_core.py
Normal file
2107
code/.venv/lib/python3.12/site-packages/pymata_aio/pymata_core.py
Normal file
File diff suppressed because it is too large
Load Diff
722
code/.venv/lib/python3.12/site-packages/pymata_aio/pymata_iot.py
Normal file
722
code/.venv/lib/python3.12/site-packages/pymata_aio/pymata_iot.py
Normal file
@@ -0,0 +1,722 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Copyright (c) 2015-2019 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
import signal
|
||||
import argparse
|
||||
import websockets
|
||||
from pymata_aio.constants import Constants
|
||||
from pymata_aio.pymata_core import PymataCore
|
||||
|
||||
|
||||
class PymataIOT:
|
||||
def __init__(self, my_core):
|
||||
self.core = my_core
|
||||
|
||||
self.command_map = {
|
||||
"analog_read": self.analog_read,
|
||||
"analog_write": self.analog_write,
|
||||
"digital_read": self.digital_read,
|
||||
"digital_write": self.digital_write,
|
||||
"disable_analog_reporting": self.disable_analog_reporting,
|
||||
"disable_digital_reporting": self.disable_digital_reporting,
|
||||
"enable_analog_reporting": self.enable_analog_reporting,
|
||||
"enable_digital_reporting": self.enable_digital_reporting,
|
||||
"encoder_config": self.encoder_config,
|
||||
"encoder_read": self.encoder_read,
|
||||
"get_analog_latch_data": self.get_analog_latch_data,
|
||||
"get_analog_map": self.get_analog_map,
|
||||
"get_capability_report": self.get_capability_report,
|
||||
"get_digital_latch_data": self.get_digital_latch_data,
|
||||
"get_firmware_version": self.get_firmware_version,
|
||||
"get_pin_state": self.get_pinstate_report,
|
||||
"get_protocol_version": self.get_protocol_version,
|
||||
"get_pymata_version": self.get_pymata_version,
|
||||
"i2c_config": self.i2c_config,
|
||||
"i2c_read_data": self.i2c_read_data,
|
||||
"i2c_read_request": self.i2c_read_request,
|
||||
"i2c_write_request": self.i2c_write_request,
|
||||
"keep_alive": self.keep_alive,
|
||||
"play_tone": self.play_tone,
|
||||
"set_analog_latch": self.set_analog_latch,
|
||||
"set_digital_latch": self.set_digital_latch,
|
||||
"set_pin_mode": self.set_pin_mode,
|
||||
"set_sampling_interval": self.set_sampling_interval,
|
||||
"sonar_config": self.sonar_config,
|
||||
"sonar_read": self.sonar_read,
|
||||
"servo_config": self.servo_config,
|
||||
"stepper_config": self.stepper_config,
|
||||
"stepper_step": self.stepper_step
|
||||
}
|
||||
self.websocket = None
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
async def get_message(self, websocket, path):
|
||||
"""
|
||||
|
||||
:param websocket: websocket
|
||||
:param path: path
|
||||
:return:
|
||||
"""
|
||||
|
||||
self.websocket = websocket
|
||||
try:
|
||||
while True:
|
||||
payload = await self.websocket.recv()
|
||||
|
||||
# cmd_dict = json.loads(payload.decode('utf8'))
|
||||
cmd_dict = json.loads(payload)
|
||||
client_cmd = cmd_dict.get("method")
|
||||
|
||||
if client_cmd in self.command_map:
|
||||
cmd = self.command_map.get(client_cmd)
|
||||
params = cmd_dict.get("params")
|
||||
if params[0] != "null":
|
||||
await cmd(params)
|
||||
else:
|
||||
await cmd()
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
sys.exit()
|
||||
|
||||
async def analog_read(self, command):
|
||||
"""
|
||||
This method reads and returns the last reported value for an analog pin.
|
||||
Normally not used since analog pin updates will be provided automatically
|
||||
as they occur with the analog_message_reply being sent to the client after set_pin_mode is called.
|
||||
(see enable_analog_reporting for message format).
|
||||
|
||||
:param command: {"method": "analog_read", "params": [ANALOG_PIN]}
|
||||
:returns: {"method": "analog_read_reply", "params": [PIN, ANALOG_DATA_VALUE]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
data_val = await self.core.analog_read(pin)
|
||||
reply = json.dumps({"method": "analog_read_reply", "params": [pin, data_val]})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def analog_write(self, command):
|
||||
"""
|
||||
This method writes a value to an analog pin.
|
||||
|
||||
It is used to set the output of a PWM pin or the angle of a Servo.
|
||||
|
||||
:param command: {"method": "analog_write", "params": [PIN, WRITE_VALUE]}
|
||||
:returns: No return message.
|
||||
"""
|
||||
pin = int(command[0])
|
||||
value = int(command[1])
|
||||
await self.core.analog_write(pin, value)
|
||||
|
||||
async def digital_read(self, command):
|
||||
"""
|
||||
This method reads and returns the last reported value for a digital pin.
|
||||
Normally not used since digital pin updates will be provided automatically
|
||||
as they occur with the digital_message_reply being sent to the client after set_pin_mode is called..
|
||||
(see enable_digital_reporting for message format)
|
||||
|
||||
:param command: {"method": "digital_read", "params": [PIN]}
|
||||
:returns: {"method": "digital_read_reply", "params": [PIN, DIGITAL_DATA_VALUE]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
data_val = await self.core.digital_read(pin)
|
||||
reply = json.dumps({"method": "digital_read_reply", "params": [pin, data_val]})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def digital_write(self, command):
|
||||
"""
|
||||
This method writes a zero or one to a digital pin.
|
||||
|
||||
:param command: {"method": "digital_write", "params": [PIN, DIGITAL_DATA_VALUE]}
|
||||
:returns: No return message..
|
||||
"""
|
||||
pin = int(command[0])
|
||||
value = int(command[1])
|
||||
await self.core.digital_write(pin, value)
|
||||
|
||||
async def disable_analog_reporting(self, command):
|
||||
"""
|
||||
Disable Firmata reporting for an analog pin.
|
||||
|
||||
:param command: {"method": "disable_analog_reporting", "params": [PIN]}
|
||||
:returns: No return message..
|
||||
"""
|
||||
pin = int(command[0])
|
||||
await self.core.disable_analog_reporting(pin)
|
||||
|
||||
async def disable_digital_reporting(self, command):
|
||||
"""
|
||||
Disable Firmata reporting for a digital pin.
|
||||
|
||||
:param command: {"method": "disable_digital_reporting", "params": [PIN]}
|
||||
:returns: No return message.
|
||||
"""
|
||||
pin = int(command[0])
|
||||
await self.core.disable_digital_reporting(pin)
|
||||
|
||||
async def enable_analog_reporting(self, command):
|
||||
"""
|
||||
Enable Firmata reporting for an analog pin.
|
||||
|
||||
:param command: {"method": "enable_analog_reporting", "params": [PIN]}
|
||||
:returns: {"method": "analog_message_reply", "params": [PIN, ANALOG_DATA_VALUE]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
await self.core.enable_analog_reporting(pin)
|
||||
|
||||
async def enable_digital_reporting(self, command):
|
||||
"""
|
||||
Enable Firmata reporting for a digital pin.
|
||||
|
||||
:param command: {"method": "enable_digital_reporting", "params": [PIN]}
|
||||
:returns: {"method": "digital_message_reply", "params": [PIN, DIGITAL_DATA_VALUE]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
await self.core.enable_digital_reporting(pin)
|
||||
|
||||
async def encoder_config(self, command):
|
||||
"""
|
||||
Configure 2 pins for FirmataPlus encoder operation.
|
||||
|
||||
:param command: {"method": "encoder_config", "params": [PIN_A, PIN_B]}
|
||||
:returns: {"method": "encoder_data_reply", "params": [ENCODER_DATA]}
|
||||
"""
|
||||
pin_a = int(command[0])
|
||||
pin_b = int(command[1])
|
||||
await self.core.encoder_config(pin_a, pin_b, self.encoder_callback)
|
||||
|
||||
async def encoder_read(self, command):
|
||||
"""
|
||||
This is a polling method to read the last cached FirmataPlus encoder value.
|
||||
Normally not used. See encoder config for the asynchronous report message format.
|
||||
|
||||
:param command: {"method": "encoder_read", "params": [PIN_A]}
|
||||
:returns: {"method": "encoder_read_reply", "params": [PIN_A, ENCODER_VALUE]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
val = await self.core.encoder_read(pin)
|
||||
reply = json.dumps({"method": "encoder_read_reply", "params": [pin, val]})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_analog_latch_data(self, command):
|
||||
"""
|
||||
This method retrieves a latch table entry for an analog pin.
|
||||
|
||||
See constants.py for definition of reply message parameters.
|
||||
|
||||
:param command: {"method": "get_analog_latch_data", "params": [ANALOG_PIN]}
|
||||
:returns: {"method": "get_analog_latch_data_reply", "params": [ANALOG_PIN, LATCHED_STATE, THRESHOLD_TYPE,\
|
||||
THRESHOLD_TARGET, DATA_VALUE, TIME_STAMP ]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
data_val = await self.core.get_analog_latch_data(pin)
|
||||
if data_val:
|
||||
data_val = data_val[0:-1]
|
||||
reply = json.dumps({"method": "get_analog_latch_data_reply", "params": [pin, data_val]})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_analog_map(self):
|
||||
"""
|
||||
This method retrieves the Firmata analog map.
|
||||
|
||||
Refer to: http://firmata.org/wiki/Protocol#Analog_Mapping_Query to interpret the reply
|
||||
|
||||
The command JSON format is: {"method":"get_analog_map","params":["null"]}
|
||||
:returns: {"method": "analog_map_reply", "params": [ANALOG_MAP]}
|
||||
"""
|
||||
value = await self.core.get_analog_map()
|
||||
if value:
|
||||
reply = json.dumps({"method": "analog_map_reply", "params": value})
|
||||
else:
|
||||
reply = json.dumps({"method": "analog_map_reply", "params": "None"})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_capability_report(self):
|
||||
"""
|
||||
This method retrieves the Firmata capability report.
|
||||
|
||||
Refer to http://firmata.org/wiki/Protocol#Capability_Query
|
||||
|
||||
The command format is: {"method":"get_capability_report","params":["null"]}
|
||||
|
||||
:returns: {"method": "capability_report_reply", "params": [RAW_CAPABILITY_REPORT]}
|
||||
"""
|
||||
value = await self.core.get_capability_report()
|
||||
await asyncio.sleep(.1)
|
||||
if value:
|
||||
reply = json.dumps({"method": "capability_report_reply", "params": value})
|
||||
else:
|
||||
reply = json.dumps({"method": "capability_report_reply", "params": "None"})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_digital_latch_data(self, command):
|
||||
"""
|
||||
This method retrieves a latch table entry for a digital pin.
|
||||
|
||||
See constants.py for definition of reply message parameters.
|
||||
|
||||
:param command: {"method": "get_digital_latch_data", "params": [DPIN]}
|
||||
:returns: {"method": "get_digital_latch_data_reply", "params": [DIGITAL_PIN, LATCHED_STATE, THRESHOLD_TYPE,\
|
||||
THRESHOLD_TARGET, DATA_VALUE, TIME_STAMP ]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
data_val = await self.core.get_digital_latch_data(pin)
|
||||
if data_val:
|
||||
data_val = data_val[0:-1]
|
||||
reply = json.dumps({"method": "get_digital_latch_data_reply", "params": [pin, data_val]})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_firmware_version(self):
|
||||
"""
|
||||
This method retrieves the Firmata firmware version.
|
||||
|
||||
See: http://firmata.org/wiki/Protocol#Query_Firmware_Name_and_Version
|
||||
|
||||
|
||||
JSON command: {"method": "get_firmware_version", "params": ["null"]}
|
||||
|
||||
:returns: {"method": "firmware_version_reply", "params": [FIRMWARE_VERSION]}
|
||||
"""
|
||||
value = await self.core.get_firmware_version()
|
||||
if value:
|
||||
reply = json.dumps({"method": "firmware_version_reply", "params": value})
|
||||
else:
|
||||
reply = json.dumps({"method": "firmware_version_reply", "params": "Unknown"})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_pinstate_report(self, command):
|
||||
"""
|
||||
This method retrieves a Firmata pin_state report for a pin..
|
||||
|
||||
See: http://firmata.org/wiki/Protocol#Pin_State_Query
|
||||
|
||||
:param command: {"method": "get_pin_state", "params": [PIN]}
|
||||
:returns: {"method": "get_pin_state_reply", "params": [PIN_NUMBER, PIN_MODE, PIN_STATE]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
value = await self.core.get_pin_state(pin)
|
||||
if value:
|
||||
reply = json.dumps({"method": "pin_state_reply", "params": value})
|
||||
else:
|
||||
reply = json.dumps({"method": "pin_state_reply", "params": "Unknown"})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_protocol_version(self):
|
||||
"""
|
||||
This method retrieves the Firmata protocol version.
|
||||
|
||||
JSON command: {"method": "get_protocol_version", "params": ["null"]}
|
||||
|
||||
:returns: {"method": "protocol_version_reply", "params": [PROTOCOL_VERSION]}
|
||||
"""
|
||||
value = await self.core.get_protocol_version()
|
||||
if value:
|
||||
reply = json.dumps({"method": "protocol_version_reply", "params": value})
|
||||
else:
|
||||
reply = json.dumps({"method": "protocol_version_reply", "params": "Unknown"})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def get_pymata_version(self):
|
||||
"""
|
||||
This method retrieves the PyMata release version number.
|
||||
|
||||
JSON command: {"method": "get_pymata_version", "params": ["null"]}
|
||||
|
||||
:returns: {"method": "pymata_version_reply", "params":[PYMATA_VERSION]}
|
||||
"""
|
||||
value = await self.core.get_pymata_version()
|
||||
if value:
|
||||
reply = json.dumps({"method": "pymata_version_reply", "params": value})
|
||||
else:
|
||||
reply = json.dumps({"method": "pymata_version_reply", "params": "Unknown"})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def i2c_config(self, command):
|
||||
"""
|
||||
This method initializes the I2c and sets the optional read delay (in microseconds).
|
||||
|
||||
It must be called before doing any other i2c operations for a given device.
|
||||
:param command: {"method": "i2c_config", "params": [DELAY]}
|
||||
:returns: No Return message.
|
||||
"""
|
||||
delay = int(command[0])
|
||||
await self.core.i2c_config(delay)
|
||||
|
||||
async def i2c_read_data(self, command):
|
||||
"""
|
||||
This method retrieves the last value read for an i2c device identified by address.
|
||||
This is a polling implementation and i2c_read_request and i2c_read_request_reply may be
|
||||
a better alternative.
|
||||
:param command: {"method": "i2c_read_data", "params": [I2C_ADDRESS ]}
|
||||
:returns:{"method": "i2c_read_data_reply", "params": i2c_data}
|
||||
"""
|
||||
address = int(command[0])
|
||||
i2c_data = await self.core.i2c_read_data(address)
|
||||
reply = json.dumps({"method": "i2c_read_data_reply", "params": i2c_data})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def i2c_read_request(self, command):
|
||||
"""
|
||||
This method sends an I2C read request to Firmata. It is qualified by a single shot, continuous
|
||||
read, or stop reading command.
|
||||
Special Note: for the read type supply one of the following string values:
|
||||
|
||||
"0" = I2C_READ
|
||||
|
||||
"1" = I2C_READ | I2C_END_TX_MASK"
|
||||
|
||||
"2" = I2C_READ_CONTINUOUSLY
|
||||
|
||||
"3" = I2C_READ_CONTINUOUSLY | I2C_END_TX_MASK
|
||||
|
||||
"4" = I2C_STOP_READING
|
||||
|
||||
:param command: {"method": "i2c_read_request", "params": [I2C_ADDRESS, I2C_REGISTER,
|
||||
NUMBER_OF_BYTES, I2C_READ_TYPE ]}
|
||||
:returns: {"method": "i2c_read_request_reply", "params": [DATA]}
|
||||
"""
|
||||
device_address = int(command[0])
|
||||
register = int(command[1])
|
||||
number_of_bytes = int(command[2])
|
||||
|
||||
if command[3] == "0":
|
||||
read_type = Constants.I2C_READ_CONTINUOUSLY
|
||||
elif command[3] == "1":
|
||||
read_type = Constants.I2C_READ
|
||||
elif command[3] == "2":
|
||||
read_type = Constants.I2C_READ | Constants.I2C_END_TX_MASK
|
||||
elif command[3] == "3":
|
||||
read_type = Constants.I2C_READ_CONTINUOUSLY | Constants.I2C_END_TX_MASK
|
||||
else: # the default case stop reading valid request or invalid request
|
||||
read_type = Constants.I2C_STOP_READING
|
||||
|
||||
await self.core.i2c_read_request(device_address, register, number_of_bytes, read_type,
|
||||
self.i2c_read_request_callback)
|
||||
await asyncio.sleep(.1)
|
||||
|
||||
async def i2c_write_request(self, command):
|
||||
"""
|
||||
This method performs an I2C write at a given I2C address,
|
||||
:param command: {"method": "i2c_write_request", "params": [I2C_DEVICE_ADDRESS, [DATA_TO_WRITE]]}
|
||||
:returns:No return message.
|
||||
"""
|
||||
device_address = int(command[0])
|
||||
params = command[1]
|
||||
params = [int(i) for i in params]
|
||||
await self.core.i2c_write_request(device_address, params)
|
||||
|
||||
async def keep_alive(self, command):
|
||||
"""
|
||||
Periodically send a keep alive message to the Arduino.
|
||||
Frequency of keep alive transmission is calculated as follows:
|
||||
keep_alive_sent = period - (period * margin)
|
||||
|
||||
:param command: {"method": "keep_alive", "params": [PERIOD, MARGIN]}
|
||||
Period is time period between keepalives. Range is 0-10 seconds. 0 disables the keepalive mechanism.
|
||||
Margin is a safety margin to assure keepalives are sent before period expires. Range is 0.1 to 0.9
|
||||
:returns: No return value
|
||||
"""
|
||||
period = int(command[0])
|
||||
margin = int(command[1])
|
||||
await self.core.keep_alive(period, margin)
|
||||
|
||||
async def play_tone(self, command):
|
||||
"""
|
||||
This method controls a piezo device to play a tone. It is a FirmataPlus feature.
|
||||
Tone command is TONE_TONE to play, TONE_NO_TONE to stop playing.
|
||||
:param command: {"method": "play_tone", "params": [PIN, TONE_COMMAND, FREQUENCY(Hz), DURATION(MS)]}
|
||||
:returns:No return message.
|
||||
"""
|
||||
pin = int(command[0])
|
||||
if command[1] == "TONE_TONE":
|
||||
tone_command = Constants.TONE_TONE
|
||||
else:
|
||||
tone_command = Constants.TONE_NO_TONE
|
||||
frequency = int(command[2])
|
||||
duration = int(command[3])
|
||||
await self.core.play_tone(pin, tone_command, frequency, duration)
|
||||
|
||||
async def set_analog_latch(self, command):
|
||||
"""
|
||||
This method sets the an analog latch for a given analog pin, providing the threshold type, and
|
||||
latching threshold.
|
||||
:param command: {"method": "set_analog_latch", "params": [PIN, THRESHOLD_TYPE, THRESHOLD_VALUE]}
|
||||
:returns:{"method": "analog_latch_data_reply", "params": [PIN, DATA_VALUE_LATCHED, TIMESTAMP_STRING]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
threshold_type = int(command[1])
|
||||
threshold_value = int(command[2])
|
||||
await self.core.set_analog_latch(pin, threshold_type, threshold_value, self.analog_latch_callback)
|
||||
|
||||
async def set_digital_latch(self, command):
|
||||
"""
|
||||
This method sets the a digital latch for a given digital pin, the threshold type, and latching threshold.
|
||||
:param command:{"method": "set_digital_latch", "params": [PIN, THRESHOLD (0 or 1)]}
|
||||
:returns:{"method": digital_latch_data_reply", "params": [PIN, DATA_VALUE_LATCHED, TIMESTAMP_STRING]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
threshold_value = int(command[1])
|
||||
await self.core.set_digital_latch(pin, threshold_value, self.digital_latch_callback)
|
||||
|
||||
async def set_pin_mode(self, command):
|
||||
"""
|
||||
This method sets the pin mode for the selected pin. It handles: Input, Analog(Input) PWM, and OUTPUT. Servo
|
||||
is handled by servo_config().
|
||||
:param command: {"method": "set_pin_mode", "params": [PIN, MODE]}
|
||||
:returns:No return message.
|
||||
"""
|
||||
pin = int(command[0])
|
||||
mode = int(command[1])
|
||||
if mode == Constants.INPUT:
|
||||
cb = self.digital_callback
|
||||
elif mode == Constants.ANALOG:
|
||||
cb = self.analog_callback
|
||||
else:
|
||||
cb = None
|
||||
|
||||
await self.core.set_pin_mode(pin, mode, cb)
|
||||
|
||||
async def set_sampling_interval(self, command):
|
||||
"""
|
||||
This method sets the Firmata sampling interval in ms.
|
||||
:param command:{"method": "set_sampling_interval", "params": [INTERVAL]}
|
||||
:returns:No return message.
|
||||
"""
|
||||
sample_interval = int(command[0])
|
||||
await self.core.set_sampling_interval(sample_interval)
|
||||
|
||||
async def sonar_config(self, command):
|
||||
"""
|
||||
This method configures 2 pins to support HC-SR04 Ping devices.
|
||||
This is a FirmataPlus feature.
|
||||
:param command: {"method": "sonar_config", "params": [TRIGGER_PIN, ECHO_PIN, PING_INTERVAL(default=50),
|
||||
MAX_DISTANCE(default= 200 cm]}
|
||||
:returns:{"method": "sonar_data_reply", "params": [DISTANCE_IN_CM]}
|
||||
"""
|
||||
trigger = int(command[0])
|
||||
echo = int(command[1])
|
||||
interval = int(command[2])
|
||||
max_dist = int(command[3])
|
||||
await self.core.sonar_config(trigger, echo, self.sonar_callback, interval, max_dist)
|
||||
|
||||
async def sonar_read(self, command):
|
||||
"""
|
||||
This method retrieves the last sonar data value that was cached.
|
||||
This is a polling method. After sonar config, sonar_data_reply messages will be sent automatically.
|
||||
:param command: {"method": "sonar_read", "params": [TRIGGER_PIN]}
|
||||
:returns:{"method": "sonar_read_reply", "params": [TRIGGER_PIN, DATA_VALUE]}
|
||||
"""
|
||||
pin = int(command[0])
|
||||
val = await self.core.sonar_data_retrieve(pin)
|
||||
|
||||
reply = json.dumps({"method": "sonar_read_reply", "params": [pin, val]})
|
||||
await self.websocket.send(reply)
|
||||
|
||||
async def servo_config(self, command):
|
||||
"""
|
||||
This method configures a pin for servo operation. The servo angle is set by using analog_write().
|
||||
:param command: {"method": "servo_config", "params": [PIN, MINIMUM_PULSE(ms), MAXIMUM_PULSE(ms)]}
|
||||
:returns:No message returned.
|
||||
"""
|
||||
pin = int(command[0])
|
||||
min_pulse = int(command[1])
|
||||
max_pulse = int(command[2])
|
||||
await self.core.servo_config(pin, min_pulse, max_pulse)
|
||||
|
||||
async def stepper_config(self, command):
|
||||
"""
|
||||
This method configures 4 pins for stepper motor operation.
|
||||
This is a FirmataPlus feature.
|
||||
:param command: {"method": "stepper_config", "params": [STEPS_PER_REVOLUTION, [PIN1, PIN2, PIN3, PIN4]]}
|
||||
:returns:No message returned.
|
||||
"""
|
||||
steps_per_revs = int(command[0])
|
||||
pins = command[1]
|
||||
pin1 = int(pins[0])
|
||||
pin2 = int(pins[1])
|
||||
pin3 = int(pins[2])
|
||||
pin4 = int(pins[3])
|
||||
await self.core.stepper_config(steps_per_revs, [pin1, pin2, pin3, pin4])
|
||||
|
||||
async def stepper_step(self, command):
|
||||
"""
|
||||
This method activates a stepper motor motion.
|
||||
This is a FirmataPlus feature.
|
||||
:param command: {"method": "stepper_step", "params": [SPEED, NUMBER_OF_STEPS]}
|
||||
:returns:No message returned.
|
||||
"""
|
||||
speed = int(command[0])
|
||||
num_steps = int(command[1])
|
||||
await self.core.stepper_step(speed, num_steps)
|
||||
|
||||
def analog_callback(self, data):
|
||||
"""
|
||||
This method handles the analog message received from pymata_core
|
||||
:param data: analog callback message
|
||||
:returns:{"method": "analog_message_reply", "params": [PIN, DATA_VALUE}
|
||||
"""
|
||||
reply = json.dumps({"method": "analog_message_reply", "params": [data[0], data[1]]})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
def analog_latch_callback(self, data):
|
||||
"""
|
||||
This method handles analog_latch data received from pymata_core
|
||||
:param data: analog latch callback message
|
||||
:returns:{"method": "analog_latch_data_reply", "params": [ANALOG_PIN, VALUE_AT_TRIGGER, TIME_STAMP_STRING]}
|
||||
"""
|
||||
ts = data[2]
|
||||
st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
|
||||
reply = json.dumps({"method": "analog_latch_data_reply", "params": [data[0], data[1], st]})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
def digital_callback(self, data):
|
||||
"""
|
||||
This method handles the digital message received from pymata_core
|
||||
:param data: digital callback message
|
||||
:returns:{"method": "digital_message_reply", "params": [PIN, DATA_VALUE]}
|
||||
"""
|
||||
reply = json.dumps({"method": "digital_message_reply", "params": [data[0], data[1]]})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
def digital_latch_callback(self, data):
|
||||
"""
|
||||
This method handles the digital latch data message received from pymata_core
|
||||
:param data: digital latch callback message
|
||||
:returns:s{"method": "digital_latch_data_reply", "params": [PIN, DATA_VALUE_AT_TRIGGER, TIME_STAMP_STRING]}
|
||||
"""
|
||||
ts = data[2]
|
||||
st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
|
||||
reply = json.dumps({"method": "digital_latch_data_reply", "params": [data[0], data[1], st]})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
def encoder_callback(self, data):
|
||||
"""
|
||||
This method handles the encoder data message received from pymata_core
|
||||
:param data: encoder data callback message
|
||||
:returns:{"method": "encoder_data_reply", "params": [ENCODER VALUE]}
|
||||
"""
|
||||
reply = json.dumps({"method": "encoder_data_reply", "params": data})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
def i2c_read_request_callback(self, data):
|
||||
"""
|
||||
This method handles the i2c read data message received from pymata_core.
|
||||
:param data: i2c read data callback message
|
||||
:returns:{"method": "i2c_read_request_reply", "params": [DATA_VALUE]}
|
||||
"""
|
||||
reply = json.dumps({"method": "i2c_read_request_reply", "params": data})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
def i2c_read_data_callback(self, data):
|
||||
"""
|
||||
This method handles the i2c cached read data received from pymata_core.
|
||||
:param data: i2c read cached data callback message
|
||||
:returns:{"method": "i2c_read_data_reply", "params": [DATA_VALUE]}
|
||||
"""
|
||||
reply = json.dumps({"method": "i2c_read_data_reply", "params": data})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
def sonar_callback(self, data):
|
||||
"""
|
||||
This method handles sonar data received from pymata_core.
|
||||
:param data: sonar data callback message
|
||||
:returns:{"method": "sonar_data_reply", "params": [DATA_VALUE]}
|
||||
"""
|
||||
reply = json.dumps({"method": "sonar_data_reply", "params": data})
|
||||
asyncio.ensure_future(self.websocket.send(reply))
|
||||
|
||||
|
||||
"""
|
||||
|
||||
usage: pymata_iot.py [-h] [-host HOSTNAME] [-port PORT] [-wait WAIT]
|
||||
[-comport COM] [-sleep SLEEP] [-log LOG]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-host HOSTNAME Server name or IP address
|
||||
-port PORT Server port number
|
||||
-wait WAIT Arduino wait time
|
||||
-comport COM Arduino COM port
|
||||
-sleep SLEEP sleep tune in ms.
|
||||
-log LOG True = send output to file, False = send output to console
|
||||
-ardIPAddr ADDR Wireless module ip address (WiFly)
|
||||
-ardPort PORT Wireless module ip port (Wifly)
|
||||
-handshake STR Wireless device handshake string (WiFly)
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-host", dest="hostname", default="localhost", help="Server name or IP address")
|
||||
parser.add_argument("-port", dest="port", default="9000", help="Server port number")
|
||||
parser.add_argument("-wait", dest="wait", default="2", help="Arduino wait time")
|
||||
parser.add_argument("-comport", dest="com", default="None", help="Arduino COM port")
|
||||
parser.add_argument("-sleep", dest="sleep", default=".001", help="sleep tune in ms.")
|
||||
parser.add_argument("-log", dest="log", default="False", help="redirect console output to log file")
|
||||
parser.add_argument("-ardIPAddr", dest="aIPaddr", default="None", help="Arduino IP Address (WiFly")
|
||||
parser.add_argument("-ardPort", dest="aIPport", default="2000", help="Arduino IP port (WiFly")
|
||||
parser.add_argument("-handshake", dest="handshake", default="*HELLO*", help="IP Device Handshake String")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
ip_addr = args.hostname
|
||||
ip_port = args.port
|
||||
|
||||
if args.com == 'None':
|
||||
comport = None
|
||||
else:
|
||||
comport = args.com
|
||||
|
||||
if args.log == 'True':
|
||||
log = True
|
||||
else:
|
||||
log = False
|
||||
|
||||
ard_ip_addr = args.aIPaddr
|
||||
ard_ip_port = args.aIPport
|
||||
ard_handshake = args.handshake
|
||||
|
||||
core = PymataCore(int(args.wait), float(args.sleep), log, comport,
|
||||
ard_ip_addr, ard_ip_port, ard_handshake)
|
||||
|
||||
# core = PymataCore()
|
||||
core.start()
|
||||
|
||||
|
||||
# Signal handler to trap control C
|
||||
# noinspection PyUnusedLocal,PyUnusedLocal
|
||||
def _signal_handler(sig, frame):
|
||||
if core is not None:
|
||||
print('\nYou pressed Ctrl+C')
|
||||
task = asyncio.ensure_future(core.shutdown())
|
||||
asyncio.get_event_loop().run_until_complete(task)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
signal.signal(signal.SIGINT, _signal_handler)
|
||||
signal.signal(signal.SIGTERM, _signal_handler)
|
||||
server = PymataIOT(core)
|
||||
|
||||
try:
|
||||
start_server = websockets.serve(server.get_message, '127.0.0.1', 9000)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
|
||||
asyncio.get_event_loop().run_forever()
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
sys.exit()
|
||||
except RuntimeError:
|
||||
sys.exit()
|
||||
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2015-2019 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import serial
|
||||
|
||||
|
||||
# noinspection PyStatementEffect,PyUnresolvedReferences,PyUnresolvedReferences
|
||||
class PymataSerial:
|
||||
"""
|
||||
This class encapsulates management of the serial port that communicates
|
||||
with the Arduino Firmata
|
||||
It provides a 'futures' interface to make Pyserial compatible with asyncio
|
||||
"""
|
||||
|
||||
def __init__(self, com_port='/dev/ttyACM0', speed=57600, sleep_tune=.001,
|
||||
log_output=False):
|
||||
"""
|
||||
This is the constructor for the aio serial handler
|
||||
|
||||
:param com_port: Com port designator
|
||||
:param speed: baud rate
|
||||
:return: None
|
||||
"""
|
||||
self.log_output = log_output
|
||||
if self.log_output:
|
||||
logging.info('Initializing Arduino - Please wait...')
|
||||
else:
|
||||
print('Initializing Arduino - Please wait...', end=" ")
|
||||
sys.stdout.flush()
|
||||
self.my_serial = serial.Serial(com_port, speed, timeout=1,
|
||||
writeTimeout=1)
|
||||
|
||||
self.com_port = com_port
|
||||
self.sleep_tune = sleep_tune
|
||||
|
||||
def get_serial(self):
|
||||
"""
|
||||
This method returns a reference to the serial port in case the
|
||||
user wants to call pyserial methods directly
|
||||
|
||||
:return: pyserial instance
|
||||
"""
|
||||
return self.my_serial
|
||||
|
||||
async def write(self, data):
|
||||
"""
|
||||
This is an asyncio adapted version of pyserial write. It provides a
|
||||
non-blocking write and returns the number of bytes written upon
|
||||
completion
|
||||
|
||||
:param data: Data to be written
|
||||
:return: Number of bytes written
|
||||
"""
|
||||
# the secret sauce - it is in your future
|
||||
future = asyncio.Future()
|
||||
result = None
|
||||
try:
|
||||
result = self.my_serial.write(bytes([ord(data)]))
|
||||
except serial.SerialException:
|
||||
# self.my_serial.close()
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
await self.close()
|
||||
future.cancel()
|
||||
if self.log_output:
|
||||
logging.exception('Write exception')
|
||||
else:
|
||||
print('Write exception')
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
for t in asyncio.Task.all_tasks(loop):
|
||||
t.cancel()
|
||||
loop.run_until_complete(asyncio.sleep(.1))
|
||||
loop.stop()
|
||||
loop.close()
|
||||
self.my_serial.close()
|
||||
sys.exit(0)
|
||||
except: # swallow any additional exceptions during shutdown
|
||||
pass
|
||||
|
||||
if result:
|
||||
future.set_result(result)
|
||||
while True:
|
||||
if not future.done():
|
||||
# spin our asyncio wheels until future completes
|
||||
await asyncio.sleep(self.sleep_tune)
|
||||
|
||||
else:
|
||||
return future.result()
|
||||
|
||||
async def readline(self):
|
||||
"""
|
||||
This is an asyncio adapted version of pyserial read.
|
||||
It provides a non-blocking read and returns a line of data read.
|
||||
|
||||
:return: A line of data
|
||||
"""
|
||||
future = asyncio.Future()
|
||||
data_available = False
|
||||
while True:
|
||||
if not data_available:
|
||||
if not self.my_serial.inWaiting():
|
||||
await asyncio.sleep(self.sleep_tune)
|
||||
else:
|
||||
data_available = True
|
||||
data = self.my_serial.readline()
|
||||
future.set_result(data)
|
||||
else:
|
||||
if not future.done():
|
||||
await asyncio.sleep(self.sleep_tune)
|
||||
else:
|
||||
return future.result()
|
||||
|
||||
async def read(self):
|
||||
"""
|
||||
This is an asyncio adapted version of pyserial read
|
||||
that provides non-blocking read.
|
||||
|
||||
:return: One character
|
||||
"""
|
||||
|
||||
# create an asyncio Future
|
||||
future = asyncio.Future()
|
||||
|
||||
# create a flag to indicate when data becomes available
|
||||
data_available = False
|
||||
|
||||
# wait for a character to become available and read from
|
||||
# the serial port
|
||||
while True:
|
||||
if not data_available:
|
||||
# test to see if a character is waiting to be read.
|
||||
# if not, relinquish control back to the event loop through the
|
||||
# short sleep
|
||||
if not self.my_serial.inWaiting():
|
||||
await asyncio.sleep(self.sleep_tune)
|
||||
# data is available.
|
||||
# set the flag to true so that the future can "wait" until the
|
||||
# read is completed.
|
||||
else:
|
||||
data_available = True
|
||||
data = self.my_serial.read()
|
||||
# set future result to make the character available
|
||||
future.set_result(ord(data))
|
||||
else:
|
||||
# wait for the future to complete
|
||||
if not future.done():
|
||||
await asyncio.sleep(self.sleep_tune)
|
||||
else:
|
||||
# future is done, so return the character
|
||||
return future.result()
|
||||
|
||||
async def close(self):
|
||||
"""
|
||||
Close the serial port
|
||||
"""
|
||||
# future = asyncio.Future()
|
||||
self.my_serial.close()
|
||||
|
||||
async def open(self):
|
||||
"""
|
||||
Open the serial port
|
||||
"""
|
||||
# future = asyncio.Future()
|
||||
self.my_serial.open()
|
||||
|
||||
async def set_dtr(self, state):
|
||||
"""
|
||||
Set DTR state
|
||||
:param state: DTR state
|
||||
"""
|
||||
self.my_serial.setDTR(state)
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2015-2019 Alan Yorinks All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3 as published by the Free Software Foundation; either
|
||||
or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
||||
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
|
||||
# noinspection PyStatementEffect,PyUnresolvedReferences,PyUnresolvedReferences
|
||||
class PymataSocket:
|
||||
def __init__(self, ip_address, port, loop):
|
||||
self.ip_address = ip_address
|
||||
self.port = port
|
||||
self.loop = loop
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
|
||||
async def start(self):
|
||||
"""
|
||||
This method opens an IP connection on the IP device
|
||||
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.reader, self.writer = await asyncio.open_connection(
|
||||
self.ip_address, self.port, loop=self.loop)
|
||||
except OSError:
|
||||
print("Can't open connection to " + self.ip_address)
|
||||
sys.exit(0)
|
||||
|
||||
async def write(self, data):
|
||||
"""
|
||||
This method writes sends data to the IP device
|
||||
:param data:
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.writer.write((bytes([ord(data)])))
|
||||
await self.writer.drain()
|
||||
|
||||
async def read(self):
|
||||
"""
|
||||
This method reads one byte of data from IP device
|
||||
|
||||
:return: Next byte
|
||||
"""
|
||||
buffer = await self.reader.read(1)
|
||||
return ord(buffer)
|
||||
Reference in New Issue
Block a user