This commit is contained in:
Jonas Zeunert
2024-08-16 21:57:55 +02:00
parent adeb5c5ec7
commit 4309a2d185
1696 changed files with 279655 additions and 0 deletions

View File

@@ -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.
"""

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

File diff suppressed because it is too large Load Diff

View 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()

View File

@@ -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)

View File

@@ -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)