├── .gitignore ├── LICENSE ├── README.md ├── commanduino ├── __init__.py ├── commanddevices │ ├── __init__.py │ ├── commandaccelstepper.py │ ├── commandaccelstepper.pyi │ ├── commandanalogread.py │ ├── commandanalogread.pyi │ ├── commandanalogwrite.py │ ├── commandanalogwrite.pyi │ ├── commandbme280.py │ ├── commandbme280.pyi │ ├── commanddallas.py │ ├── commanddallas.pyi │ ├── commanddevice.py │ ├── commanddevice.pyi │ ├── commanddigitalread.py │ ├── commanddigitalread.pyi │ ├── commanddigitalwrite.py │ ├── commandlinearaccelstepper.py │ ├── commandlinearaccelstepper.pyi │ ├── commandmax31865.py │ ├── commandmax31865.pyi │ ├── commandmcp9600.py │ ├── commandmcp9600.pyi │ ├── commandpca9548a.py │ ├── commandpca9548a.pyi │ ├── commandservo.py │ ├── commandservo.pyi │ ├── commandsht1x.py │ ├── commandsht1x.pyi │ ├── commandsht31.py │ ├── commandsht31.pyi │ ├── commandtcs34725.py │ ├── commandtcs34725.pyi │ └── register.py ├── commandhandler.py ├── commandmanager.py ├── devices │ ├── __init__.py │ └── axis.py ├── exceptions.py ├── lock.py └── py.typed ├── docs └── commanduino │ ├── Makefile │ ├── _build │ ├── doctrees │ │ ├── commanduino.commanddevices.doctree │ │ ├── commanduino.devices.doctree │ │ ├── commanduino.doctree │ │ ├── environment.pickle │ │ ├── examples.doctree │ │ └── index.doctree │ └── html │ │ └── .buildinfo │ ├── commanduino.commanddevices.rst │ ├── commanduino.devices.rst │ ├── commanduino.rst │ ├── conf.py │ ├── createsphinxproject.py │ ├── examples.rst │ ├── ghPages.py │ ├── index.rst │ ├── make.bat │ └── makehtmldocs.py ├── examples ├── commanddevices │ ├── commandaccelstepper │ │ ├── demo.json │ │ └── demo.py │ ├── commandanalogread │ │ ├── demo.json │ │ └── demo.py │ ├── commandanalogwrite │ │ ├── demo.json │ │ └── demo.py │ ├── commandbme280 │ │ ├── demo.json │ │ └── demo.py │ ├── commanddallas │ │ ├── demo.json │ │ └── demo.py │ ├── commanddigitalread │ │ ├── demo.json │ │ └── demo.py │ ├── commanddigitalwrite │ │ ├── demo.json │ │ └── demo.py │ ├── commandlinearaccelstepper │ │ ├── demo.json │ │ └── demo.py │ ├── commandmax31865 │ │ ├── demo.json │ │ └── demo.py │ ├── commandservo │ │ ├── demo.json │ │ └── demo.py │ ├── commandsht1x │ │ ├── demo.json │ │ └── demo.py │ ├── commandsht31 │ │ ├── demo.json │ │ └── demo.py │ └── commandtcs34725 │ │ ├── demo.json │ │ └── demo.py ├── commandhandler │ └── basics │ │ ├── basics.ino │ │ └── basics.py └── commandmanager │ ├── basics │ ├── basics.ino │ ├── basics.py │ ├── basics_serial.json │ └── basics_tcpip.json │ └── two_boards │ ├── two_boards.json │ └── two_boards.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | commanduino.egg-info 3 | .vscode/* 4 | .idea/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Commanduino Library 2 | 3 | Link to the documentation: [Commanduino](https://croningp.github.io/commanduino/index.html) 4 | 5 | This is the commanduino library for controlling Arduino hardware via a python interface. By using the [Arduino-CommandTools](https://github.com/croningp/Arduino-CommandTools) and [Arduino-CommandHandler](https://github.com/croningp/Arduino-CommandHandler) libraries in conjunction with commanduino, you can essentially control any supported Arduino device through Python. 6 | 7 | ## How This Library Works 8 | The commanduino library is a python library which is used to communicate with Arduino devices via Python, as opposed to hardcoding the desired behaviour onto the Arduino itself. This works by using the [Arduino-CommandTools](https://github.com/croningp/Arduino-CommandTools) and [Arduino-CommandHandler](https://github.com/croningp/Arduino-CommandHandler) libraries. Commanduino acts as a friendlier "front-end" for the devices. 9 | 10 | ### Arduino-CommandTools Library 11 | This library is responsible for the code controlling each Arduino device. A CommandManger within the library controls the initialisation of the devices. To achieve total control of the device, the arduino functionality (the commands you would enter in an Arduino sketch, for example) are wrapped in methods which are called when specific commands are sent (These are specific to each device and are implemented in the library). The actual Arduino sketch will instantiate a CommandManager object which will manage the incoming/outgoing commands. These commands are handled by the Arduino-CommandHandler library which manages the communications between the Arduino device and the controlling computer via serial communication. 12 | To test the devices, the commands can be sent to the device via the Serial Monitor present within the Arduino IDE. 13 | 14 | ``` 15 | Serial Monitor Test 16 | ,,; 17 | 18 | E.g. 19 | stepper1,M,2000; 20 | 21 | This would move a stepper motor with ID "stepper1" 2000 steps. 22 | ``` 23 | 24 | ### Commanduino 25 | Commanduino is a Python interface for this interaction. It uses its own version of the CommandManager and CommandHandler, which initialise and communicate with the Arduino hardware via the Arduino-CommandTools & Arduino-CommandHandler libraries. Each Arduino device is implemented in python with commands and functions that mirror those in the Arduino-CommandTools. This allows python to directly communicate with the hardware. The commanduino implementation of the CommandManager deals with the communications to the hardware. To use the CommandManager, one simply needs to create a config file with the device information and instantiate a CommandManager object which then reads the setup information from the config file. This will give you control of the device in Python. See the [Examples](#Using The Library) section for more information. 26 | 27 | ### Hierarchical Design of Commanduino 28 | The following diagram shows the design of Commanduino, highlighting the layers of communication. 29 | 30 | ![Alt text](https://cloud.githubusercontent.com/assets/13821621/20561408/6886aa92-b176-11e6-8987-4d69ce578e6a.png "Commanduino Hierarchy") 31 | 32 | Links to relevant GitHub Repositories: 33 | * Python: 34 | * [Command Handler/Serial Command Handler](https://github.com/croningp/commanduino/blob/master/commanduino/commandhandler.py) 35 | * [Command Manager](https://github.com/croningp/commanduino/blob/master/commanduino/commandmanager.py) 36 | * [Command Devices](https://github.com/croningp/commanduino/tree/master/commanduino/commanddevices) 37 | 38 | * Arduino: 39 | * [Command Handler](https://github.com/croningp/Arduino-CommandHandler) 40 | * [Command Manager](https://github.com/croningp/Arduino-CommandTools/tree/master/CommandManager) 41 | * [Command Devices](https://github.com/croningp/Arduino-CommandTools) 42 | 43 | ## Tutorial 44 | The following will serve as a tutorial on how to use this library. An example is provided which will demonstrate using the library with a supported device (Servo Motor). 45 | 46 | ### List of Currently Supported Devices 47 | * AccelStepper Motor 48 | * Analog Read capabilities 49 | * Analog Write capabilities 50 | * Digital Read capabilities 51 | * Digital Write capabilities 52 | * Linear Accel Stepper Actuator 53 | * SHT1X Temperature Sensor 54 | * SHT31 Temperature Sensor 55 | * Servo Motor 56 | * BME280 Pressure Sensor 57 | 58 | This list will be continually updated so keep checking back regularly for more supported devices. 59 | 60 | ### Installing the Library 61 | 62 | ``` 63 | git clone https://github.com/croningp/commanduino.git 64 | cd commanduino 65 | python setup.py install # May require the use of sudo 66 | ``` 67 | 68 | Alternatively, if you plan to work on the library itself, use: 69 | 70 | ``` 71 | python setup.py develop 72 | ``` 73 | 74 | This will make a direct link to the folder you are working on. 75 | 76 | ### Dialout Issues 77 | On initial use, there may be an issue with permissions when trying to communicate over the USB connection on Unix-based OS'. This can be solved by adding the main user to the 'dialout' group on the computer: 78 | ``` 79 | sudo adduser dialout 80 | ``` 81 | As this library was develop for Unix-based OS', this issue may not be encountered on Windows. 82 | 83 | ### Using the Library 84 | Using this library is extremely simple! This example will demonstrate the use of a Servo motor. 85 | All Demo files (either Arduino or Python) have an equivalent demo file in their respective counterpart (either Python or Arduino). 86 | 87 | * First, create an Arduino sketch for your device (Demo can be found at `File > Examples > CommandServo > Demo`): 88 | 89 | ```cpp 90 | #include 91 | #include 92 | CommandManager cmdMng; //Create a CommandManager 93 | 94 | //Install this library from Arduino's IDE 95 | #include 96 | 97 | #include 98 | CommandServo cmdServo1(9); //The parameter is the Pin number which you plug the device into 99 | 100 | void setup() 101 | { 102 | Serial.begin(115200); 103 | 104 | cmdServo1.registerToCommandManager(cmdMng, "S1"); //Register the Device to the CommandManager 105 | 106 | 107 | cmdMng.init(); //Initialise the Manager 108 | } 109 | 110 | void loop() 111 | { 112 | cmdMng.update(); 113 | } 114 | 115 | ``` 116 | 117 | * Then, load this sketch onto the Arduino Board 118 | 119 | * Create a json config file containing the information of the device: 120 | * The `"ios"` represents the USB auto-detection of the Arduino Board 121 | * `/dev/tty.usbmodem1411` represents a Mac USB port 122 | * `/dev/ttyACM0/1/2 etc.` represents the Linux USB ports 123 | * `COM[number]` represents the Windows USB ports 124 | * The library will automatically search these ports for connected Arduino devices 125 | 126 | ```json 127 | { 128 | "ios" : [ 129 | { 130 | "port": "/dev/tty.usbmodem1411" 131 | }, 132 | { 133 | "port": "/dev/ttyACM0" 134 | }, 135 | { 136 | "port": "/dev/ttyACM1" 137 | }, 138 | { 139 | "port": "/dev/ttyACM2" 140 | } 141 | ], 142 | "devices": { 143 | "servo1": { 144 | "command_id": "S1" 145 | } 146 | } 147 | } 148 | ``` 149 | 150 | * Next, just import the commanduino library and read the config file. You're now ready to access the methods of the device! 151 | 152 | ```python 153 | 154 | """ 155 | This script reads the information provided by the Config file and creates a CommandManager with this information. This sets up the device on both the Arduino side and Python side which allows you to control it. 156 | """ 157 | import time 158 | 159 | from commanduino import CommandManager 160 | 161 | cmdMng = CommandManager.from_configfile('./demo.json') 162 | 163 | 164 | for i in range(2): 165 | cmdMng.servo1.set_angle(60) 166 | print(cmdMng.servo1.get_angle()) 167 | time.sleep(1) 168 | 169 | cmdMng.servo1.set_angle(120) 170 | print(cmdMng.servo1.get_angle()) 171 | time.sleep(1) 172 | 173 | ``` 174 | 175 | This script in particular will set the angle of the Servo motor to 60 degrees, wait for 1 second then set the angle to 120 degrees. 176 | When the script calls `cmdMng.servo1.set_angle(60)`, it is actually sending the command `S1,SA,60;` to the Arduino device which is then processed by the CommandHandler to obtain the desired movement. 177 | 178 | That's all there is to it! 179 | 180 | ## Authors 181 | 182 | [Jonathan Grizou](http://www.chem.gla.ac.uk/cronin/members/Jonathan/), [Graham Keenan](https://github.com/ShinRa26) and [Dario Cambie](http://www.chem.gla.ac.uk/cronin/members/DCambie/) while working in the [Cronin Group](http://www.chem.gla.ac.uk/cronin/). 183 | 184 | ## License 185 | 186 | [![GPL V3](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl.html) 187 | -------------------------------------------------------------------------------- /commanduino/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom library designed to interface with Arduino hardware, allowing for control of the boards via Python. 3 | 4 | .. moduleauthor:: Jonathan Grizou 5 | 6 | """ 7 | import logging 8 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 9 | 10 | from .lock import Lock 11 | 12 | from .commandhandler import CommandHandler 13 | from .commandhandler import SerialCommandHandler 14 | 15 | from .commandmanager import CommandManager 16 | -------------------------------------------------------------------------------- /commanduino/commanddevices/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom package for managing different Arduino devices to be used in the Commanduino Library. 3 | 4 | .. moduleauthor:: Jonathan Grizou 5 | 6 | """ 7 | 8 | from .register import add_to_bonjour_register 9 | 10 | # default 11 | from .commanddevice import CommandDevice 12 | add_to_bonjour_register('TEMPLATE', CommandDevice) 13 | 14 | # digital 15 | from .commanddigitalread import CommandDigitalRead 16 | add_to_bonjour_register('DIGITALREAD', CommandDigitalRead) 17 | 18 | from .commanddigitalwrite import CommandDigitalWrite 19 | add_to_bonjour_register('DIGITALWRITE', CommandDigitalWrite) 20 | 21 | # analog 22 | from .commandanalogread import CommandAnalogRead 23 | add_to_bonjour_register('ANALOGREAD', CommandAnalogRead) 24 | 25 | from .commandanalogwrite import CommandAnalogWrite 26 | add_to_bonjour_register('ANALOGWRITE', CommandAnalogWrite) 27 | 28 | # servo 29 | from .commandservo import CommandServo 30 | add_to_bonjour_register('SERVO', CommandServo) 31 | 32 | # commandlinearaccelstepper 33 | from .commandlinearaccelstepper import CommandLinearAccelStepper 34 | add_to_bonjour_register('LINEARACCELSTEPPER', CommandLinearAccelStepper) 35 | 36 | # commandaccelstepper 37 | from .commandaccelstepper import CommandAccelStepper 38 | add_to_bonjour_register('ACCELSTEPPER', CommandAccelStepper) 39 | 40 | # Temperature sensors SHT1X 41 | from .commandsht1x import CommandSHT1X 42 | add_to_bonjour_register('SHT1X', CommandSHT1X) 43 | 44 | # Temperature sensors SHT31 45 | from .commandsht31 import CommandSHT31 46 | add_to_bonjour_register('SHT31', CommandSHT31) 47 | 48 | # Dallas temperature sensors 49 | from .commanddallas import CommandDallas 50 | add_to_bonjour_register('DALLAS', CommandDallas) 51 | 52 | # PCA9548A I2C multiplexer 53 | from .commandpca9548a import CommandPCA9548A 54 | add_to_bonjour_register('PCA9548A', CommandPCA9548A) 55 | 56 | # RTD amplifier board MAX31685 57 | from .commandmax31865 import CommandMAX31865 58 | add_to_bonjour_register('MAX31865', CommandMAX31865) 59 | 60 | # RGB sensor TCS34725 61 | from .commandtcs34725 import CommandTCS34725 62 | add_to_bonjour_register('TCS34725', CommandTCS34725) 63 | 64 | # Pressure sensors BME280 65 | from .commandbme280 import CommandBME280 66 | add_to_bonjour_register('BME280', CommandBME280) 67 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandaccelstepper.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandAccelStepper 4 | :platform: Unix 5 | :synopsis: Represents an Accelerator Stepper Arduino device. 6 | 7 | .. moduleauthor:: Graham Keenan <1105045k@student.gla.ac.uk> 8 | 9 | """ 10 | 11 | from .commanddevice import CommandDevice 12 | 13 | import time 14 | import logging 15 | module_logger = logging.getLogger(__name__) 16 | 17 | # Bonjour information 18 | BONJOUR_ID = 'ACCELSTEPPER' 19 | CLASS_NAME = 'CommandAccelStepper' 20 | 21 | # Incoming Commands 22 | COMMANDACCELSTEPPER_SET_POSITION = "SP" 23 | COMMANDACCELSTEPPER_SET_SPEED = "SS" 24 | COMMANDACCELSTEPPER_SET_MAXSPEED = "SMS" 25 | COMMANDACCELSTEPPER_SET_ACC = "SA" 26 | 27 | COMMANDACCELSTEPPER_ENABLE_ACC = "EA" 28 | COMMANDACCELSTEPPER_DISABLE_ACC = "DA" 29 | 30 | COMMANDACCELSTEPPER_MOVE_TO = "MT" 31 | COMMANDACCELSTEPPER_MOVE = "M" 32 | COMMANDACCELSTEPPER_STOP = "S" 33 | 34 | COMMANDACCELSTEPPER_REQUEST_MOVING = "RM" 35 | COMMANDACCELSTEPPER_REQUEST_DIST = "RD" 36 | COMMANDACCELSTEPPER_REQUEST_TARGET = "RT" 37 | COMMANDACCELSTEPPER_REQUEST_POSITION = "RP" 38 | 39 | COMMANDACCELSTEPPER_REQUEST_SPEED = "RIS" 40 | COMMANDACCELSTEPPER_REQUEST_MAXSPEED = "RIMS" 41 | COMMANDACCELSTEPPER_REQUEST_ACCELERATION = "RIA" 42 | 43 | 44 | # Outgoing Commands 45 | COMMANDACCELSTEPPER_MOVING = "M" 46 | COMMANDACCELSTEPPER_DIST = "D" 47 | COMMANDACCELSTEPPER_TARGET = "T" 48 | COMMANDACCELSTEPPER_POSITION = "P" 49 | 50 | COMMANDACCELSTEPPER_SPEED = "IS" 51 | COMMANDACCELSTEPPER_MAXSPEED = "IMS" 52 | COMMANDACCELSTEPPER_ACCELERATION = "IA" 53 | 54 | # Default speed 55 | DEFAULT_SPEED = 5000 56 | 57 | # Default maximum speed 58 | DEFAULT_MAX_SPEED = 5000 59 | 60 | # Default acceleration 61 | DEFAULT_ACCELERATION = 2000 62 | 63 | # Default sleep time 64 | DEFAULT_SLEEP_TIME = 0.1 65 | 66 | 67 | class CommandAccelStepper(CommandDevice): 68 | """ 69 | Accelerator Stepper Arduino Device 70 | 71 | Args: 72 | speed (int): Speed of the device, default set to DEFAULT_SPEED (5000). 73 | 74 | max_speed (int): Maximum speed of the device, default set to DEFAULT_MAX_SPEED (5000). 75 | 76 | acceleration (int): Speed of acceleration, default set to DEFAULT_ACCELERATION (2000). 77 | 78 | enabled_acceleration (bool): Acceleration enabled, default set to True. 79 | 80 | reverted_direction (bool): Direction is reversed, default set to False. 81 | 82 | Base: 83 | CommandDevice 84 | 85 | """ 86 | def __init__(self, speed=DEFAULT_SPEED, max_speed=DEFAULT_MAX_SPEED, acceleration=DEFAULT_ACCELERATION, enabled_acceleration=True, reverted_direction=False): 87 | CommandDevice.__init__(self) 88 | self.register_all_requests() 89 | self.init_speed = speed 90 | self.init_max_speed = max_speed 91 | self.init_acceleration = acceleration 92 | self.enabled_acceleration = enabled_acceleration 93 | self.reverted_direction = reverted_direction 94 | 95 | def init(self): 96 | self.set_all_params() 97 | 98 | def set_all_params(self): 99 | """ 100 | Sets all the parameters of the device automatically. 101 | """ 102 | self.set_running_speed(self.init_speed) 103 | self.set_max_speed(self.init_max_speed) 104 | self.set_acceleration(self.init_acceleration) 105 | 106 | if self.enabled_acceleration: 107 | self.enable_acceleration() 108 | 109 | def apply_reverted_direction(self, value): 110 | """ 111 | Reverses the direction of movement. 112 | 113 | Args: 114 | value (int): The current value. 115 | 116 | Returns: 117 | value (int): The new value (-value) 118 | 119 | """ 120 | if self.reverted_direction: 121 | value = -value 122 | return value 123 | 124 | @property 125 | def is_moving(self): 126 | """ 127 | Check for movement. 128 | 129 | Returns: 130 | bool: The movement state of the stepper. 131 | 132 | """ 133 | return self.get_moving_state() 134 | 135 | def wait_until_idle(self): 136 | """ 137 | Waits until the device is idle (not moving). 138 | """ 139 | while self.is_moving: 140 | time.sleep(DEFAULT_SLEEP_TIME) 141 | 142 | def set_current_position(self, steps): 143 | """ 144 | Sets the current position of the device. 145 | 146 | Args: 147 | steps (int): The current position, in steps 148 | 149 | """ 150 | self.send(COMMANDACCELSTEPPER_SET_POSITION, steps) 151 | 152 | def _set_speed(self, steps_per_sec): 153 | """ 154 | Sets the speed of the device. 155 | 156 | Args: 157 | steps_per_sec (int): The number of steps per second. 158 | 159 | """ 160 | self.send(COMMANDACCELSTEPPER_SET_SPEED, steps_per_sec) 161 | 162 | def set_running_speed(self, steps_per_sec): 163 | """ 164 | Sets the running speed of the device. 165 | 166 | Args: 167 | steps_per_sec (int): The number of steps per second. 168 | 169 | """ 170 | self.running_speed = steps_per_sec 171 | 172 | def set_max_speed(self, steps_per_sec): 173 | """ 174 | Sets the maximum speed of the device. 175 | 176 | Args: 177 | steps_per_sec (int): The number of steps per second. 178 | 179 | """ 180 | self.send(COMMANDACCELSTEPPER_SET_MAXSPEED, steps_per_sec) 181 | 182 | def set_acceleration(self, steps_per_sec_per_sec): 183 | """ 184 | Sets the acceleration speed of the device. 185 | 186 | Args: 187 | steps_per_sec_per_sec (int): The number of steps per second, per second. 188 | 189 | """ 190 | self.send(COMMANDACCELSTEPPER_SET_ACC, steps_per_sec_per_sec) 191 | 192 | def enable_acceleration(self): 193 | """ 194 | Enables acceleration on the device. 195 | """ 196 | self.wait_until_idle() 197 | self.send(COMMANDACCELSTEPPER_ENABLE_ACC) 198 | # Bug in the stepper motor 199 | self.stop() 200 | self.enabled_acceleration = True 201 | 202 | def disable_acceleration(self): 203 | """ 204 | Disables acceleration on the device. 205 | """ 206 | self.wait_until_idle() 207 | self.send(COMMANDACCELSTEPPER_DISABLE_ACC) 208 | # Bug in the stepper motor 209 | self.stop() 210 | self.enabled_acceleration = False 211 | 212 | def move_to(self, steps, wait=True): 213 | """ 214 | Moves the device to a specific point. 215 | 216 | Args: 217 | steps (int): The position to move to. 218 | 219 | wait (bool): Wait until the device is idle, default set to True. 220 | 221 | """ 222 | running_speed = self.apply_reverted_direction(self.running_speed) 223 | self._set_speed(running_speed) 224 | steps = self.apply_reverted_direction(steps) 225 | self.send(COMMANDACCELSTEPPER_MOVE_TO, steps) 226 | if wait: 227 | self.wait_until_idle() 228 | 229 | def move(self, steps, wait=True): 230 | """ 231 | Moves the device a certain number of steps. 232 | 233 | Args: 234 | steps (int): The number of steps to move. 235 | 236 | wait (bool): Wait for the device to be idle, default set to True. 237 | 238 | """ 239 | running_speed = self.apply_reverted_direction(self.running_speed) 240 | self._set_speed(running_speed) 241 | steps = self.apply_reverted_direction(steps) 242 | self.send(COMMANDACCELSTEPPER_MOVE, steps) 243 | if wait: 244 | self.wait_until_idle() 245 | 246 | def stop(self, wait=True): 247 | """ 248 | Stops the device. 249 | 250 | Args: 251 | wait (bool): Wait until the device is idle, default set to True. 252 | 253 | """ 254 | self.send(COMMANDACCELSTEPPER_STOP) 255 | if wait: 256 | self.wait_until_idle() 257 | 258 | def register_all_requests(self): 259 | """ 260 | Registers all requests to the device. 261 | """ 262 | self.register_request( 263 | COMMANDACCELSTEPPER_REQUEST_MOVING, 264 | COMMANDACCELSTEPPER_MOVING, 265 | 'moving_state', 266 | self.handle_moving_state_command) 267 | 268 | self.register_request( 269 | COMMANDACCELSTEPPER_REQUEST_DIST, 270 | COMMANDACCELSTEPPER_DIST, 271 | 'distance_to_go', 272 | self.handle_distance_to_go_command) 273 | 274 | self.register_request( 275 | COMMANDACCELSTEPPER_REQUEST_TARGET, 276 | COMMANDACCELSTEPPER_TARGET, 277 | 'target_position', 278 | self.handle_target_position_command) 279 | 280 | self.register_request( 281 | COMMANDACCELSTEPPER_REQUEST_POSITION, 282 | COMMANDACCELSTEPPER_POSITION, 283 | 'current_position', 284 | self.handle_current_position_command) 285 | 286 | self.register_request( 287 | COMMANDACCELSTEPPER_REQUEST_SPEED, 288 | COMMANDACCELSTEPPER_SPEED, 289 | 'speed', 290 | self.handle_speed_command) 291 | 292 | self.register_request( 293 | COMMANDACCELSTEPPER_REQUEST_MAXSPEED, 294 | COMMANDACCELSTEPPER_MAXSPEED, 295 | 'max_speed', 296 | self.handle_max_speed_command) 297 | 298 | self.register_request( 299 | COMMANDACCELSTEPPER_REQUEST_ACCELERATION, 300 | COMMANDACCELSTEPPER_ACCELERATION, 301 | 'acceleration', 302 | self.handle_acceleration_command) 303 | 304 | def handle_moving_state_command(self, *arg): 305 | """ 306 | Handles the command for the moving state. 307 | 308 | Args: 309 | *arg: Variable command. 310 | 311 | """ 312 | if arg[0]: 313 | self.moving_state = bool(int(arg[0])) 314 | self.moving_state_lock.ensure_released() 315 | 316 | def handle_distance_to_go_command(self, *arg): 317 | """ 318 | Handles the command for distance to go. 319 | 320 | Args: 321 | *arg: Variable command. 322 | 323 | """ 324 | if arg[0]: 325 | self.distance_to_go = self.apply_reverted_direction(int(arg[0])) 326 | self.distance_to_go_lock.ensure_released() 327 | 328 | def handle_target_position_command(self, *arg): 329 | """ 330 | Handles the command for the target position. 331 | 332 | Args: 333 | *arg: Variable command. 334 | 335 | """ 336 | if arg[0]: 337 | self.target_position = self.apply_reverted_direction(int(arg[0])) 338 | self.target_position_lock.ensure_released() 339 | 340 | def handle_current_position_command(self, *arg): 341 | """ 342 | Handles the command for the current position. 343 | 344 | Args: 345 | *arg: Variable command. 346 | 347 | """ 348 | if arg[0]: 349 | self.current_position = self.apply_reverted_direction(int(arg[0])) 350 | self.current_position_lock.ensure_released() 351 | 352 | def handle_speed_command(self, *arg): 353 | """ 354 | Handles the command for the speed. 355 | 356 | Args: 357 | *arg: Variable command. 358 | 359 | """ 360 | if arg[0]: 361 | self.speed = self.apply_reverted_direction(float(arg[0])) 362 | self.speed_lock.ensure_released() 363 | 364 | def handle_max_speed_command(self, *arg): 365 | """ 366 | Handles the command for the maximum speed. 367 | 368 | Args: 369 | *arg: Variable command. 370 | 371 | """ 372 | if arg[0]: 373 | self.max_speed = float(arg[0]) 374 | self.max_speed_lock.ensure_released() 375 | 376 | def handle_acceleration_command(self, *arg): 377 | """ 378 | Handles the command for the acceleration. 379 | 380 | Args: 381 | *arg: Variable command. 382 | 383 | """ 384 | if arg[0]: 385 | self.acceleration = float(arg[0]) 386 | self.acceleration_lock.ensure_released() 387 | 388 | def print_info(self): 389 | """ 390 | Prints the current information for the device. 391 | """ 392 | print('###') 393 | print('moving_state: ', self.get_moving_state()) 394 | print('distance_to_go: ', self.get_distance_to_go()) 395 | print('target_position: ', self.get_target_position()) 396 | print('current_position: ', self.get_current_position()) 397 | print('speed: ', self.get_speed()) 398 | print('max_speed: ', self.get_max_speed()) 399 | print('acceleration: ', self.get_acceleration()) 400 | print('###') 401 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandaccelstepper.pyi: -------------------------------------------------------------------------------- 1 | from . import CommandDevice 2 | 3 | class CommandAccelStepper(CommandDevice): 4 | def __init__(self, speed: float, max_speed: float, acceleration: float, enabled_acceleration: bool = True, 5 | reverted_direction: bool = False): ... 6 | def wait_until_idle(self) -> None: ... 7 | def move_to(self, steps: int, wait: bool = True) -> None: ... 8 | def move(self, steps: int, wait: bool = True) -> None: ... 9 | def stop(self, wait: bool = True) -> None: ... 10 | 11 | # Acceleration 12 | def get_acceleration(self) -> float: ... 13 | def set_acceleration(self, steps_per_second_per_second: float) -> None: ... 14 | 15 | # Current position 16 | def get_current_position(self) -> float: ... 17 | def set_current_position(self, steps: float) -> None: ... 18 | 19 | # Distance to go 20 | def get_distance_to_go(self) -> float: ... 21 | 22 | # Enabled acceleration 23 | enabled_acceleration: bool 24 | def enable_acceleration(self) -> None: ... 25 | def disable_acceleration(self) -> None: ... 26 | 27 | # Moving state 28 | @property 29 | def is_moving(self) -> bool: ... 30 | def get_moving_state(self) -> bool: ... 31 | 32 | # Max speed 33 | def get_max_speed(self) -> float: ... 34 | def set_max_speed(self, steps_per_second: float) -> None: ... 35 | 36 | # Speed 37 | # _set_speed() sets the speed in the Arduino before actually moving the motor [e.g. in move(), move_to()] 38 | # set_running_speed sets the value of the running_speed variable, used for set_speed before normal movements 39 | def get_speed(self) -> float: ... 40 | def _set_speed(self, steps_per_second: float) -> None: ... 41 | 42 | # Running speed 43 | def set_running_speed(self, steps_per_second: int) -> None: ... 44 | 45 | # Target position 46 | def get_target_position(self) -> float: ... 47 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandanalogread.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandAnalogRead 4 | :platform: Unix 5 | :synopsis: Represents an AnalogRead Arduino device. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | 11 | from .commanddevice import CommandDevice 12 | 13 | import logging 14 | module_logger = logging.getLogger(__name__) 15 | 16 | 17 | # Bonjour information 18 | BONJOUR_ID = 'ANALOGREAD' 19 | CLASS_NAME = 'CommandAnalogRead' 20 | 21 | # Incoming (Level) 22 | CMD_ANSWER_LEVEL = 'L' 23 | 24 | # Outgoing (Read) 25 | CMD_REQUEST_LEVEL = 'R' 26 | 27 | 28 | class CommandAnalogRead(CommandDevice): 29 | """ 30 | AnalogRead Arduino device. 31 | 32 | Base: 33 | CommandDevice 34 | 35 | """ 36 | def __init__(self): 37 | CommandDevice.__init__(self) 38 | self.register_all_requests() 39 | 40 | ## 41 | def register_all_requests(self): 42 | """ 43 | Registers all requests to the device. 44 | """ 45 | self.register_request( 46 | CMD_REQUEST_LEVEL, 47 | CMD_ANSWER_LEVEL, 48 | 'level', 49 | self.handle_level_command) 50 | 51 | def handle_level_command(self, *arg): 52 | """ 53 | Handles the level command. 54 | 55 | Args: 56 | *arg: Variable Argument. 57 | """ 58 | if arg[0]: 59 | self.level = int(arg[0]) 60 | self.level_lock.ensure_released() 61 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandanalogread.pyi: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | class CommandAnalogRead(CommandDevice): 4 | def __init__(self): ... 5 | def get_level(self) -> int: ... 6 | def handle_level_command(self, *arg): ... 7 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandanalogwrite.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandAnalogWrite 4 | :platform: Unix 5 | :synopsis: Represents an AnalogWrite Arduino Device. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | from .commanddevice import CommandDevice 11 | 12 | import logging 13 | module_logger = logging.getLogger(__name__) 14 | 15 | # Bonjour Information 16 | BONJOUR_ID = 'ANALOGWRITE' 17 | CLASS_NAME = 'CommandAnalogWrite' 18 | 19 | # Outgoing (Write) 20 | CMD_SET_LEVEL = 'W' 21 | 22 | 23 | class CommandAnalogWrite(CommandDevice): 24 | """ 25 | AnalogWrite Arduino device. 26 | 27 | Base: 28 | CommandDevice 29 | """ 30 | def __init__(self): 31 | CommandDevice.__init__(self) 32 | 33 | ## 34 | def set_pwm_value(self, value): 35 | """ 36 | Sets the pwm value. 37 | 38 | Args: 39 | value: The value to set. 40 | 41 | """ 42 | casted_value = max(min(value, 255), 0) 43 | self.send(CMD_SET_LEVEL, casted_value) 44 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandanalogwrite.pyi: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | class CommandAnalogWrite(CommandDevice): 4 | def __init__(self): ... 5 | def set_pwm_value(self, value: int) -> None: ... 6 | 7 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandbme280.py: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | import logging 4 | module_logger = logging.getLogger(__name__) 5 | 6 | # bonjour info 7 | BONJOUR_ID = 'BME280' 8 | CLASS_NAME = 'CommandBME280' 9 | 10 | # incoming 11 | CMD_ANSWER_PRESSURE = 'P' 12 | CMD_ANSWER_TEMPERATURE = 'T' 13 | CMD_ANSWER_HUMIDITY = 'H' 14 | 15 | # outgoing 16 | CMD_REQUEST_PRESSURE = 'RP' 17 | CMD_REQUEST_TEMPERATURE = 'RT' 18 | CMD_REQUEST_HUMIDITY = 'RH' 19 | 20 | 21 | class CommandBME280(CommandDevice): 22 | 23 | def __init__(self): 24 | CommandDevice.__init__(self) 25 | self.register_all_requests() 26 | 27 | def register_all_requests(self): 28 | self.register_request( 29 | CMD_REQUEST_PRESSURE, 30 | CMD_ANSWER_PRESSURE, 31 | 'pressure', 32 | self.handle_pressure_command) 33 | 34 | self.register_request( 35 | CMD_REQUEST_TEMPERATURE, 36 | CMD_ANSWER_TEMPERATURE, 37 | 'temperature', 38 | self.handle_temperature_command) 39 | 40 | self.register_request( 41 | CMD_REQUEST_HUMIDITY, 42 | CMD_ANSWER_HUMIDITY, 43 | 'humidity', 44 | self.handle_humidity_command) 45 | 46 | def handle_pressure_command(self, *arg): 47 | if arg[0]: 48 | self.pressure = float(arg[0]) 49 | self.pressure_lock.ensure_released() 50 | 51 | def handle_temperature_command(self, *arg): 52 | if arg[0]: 53 | self.temperature = float(arg[0]) 54 | self.temperature_lock.ensure_released() 55 | 56 | def handle_humidity_command(self, *arg): 57 | if arg[0]: 58 | self.humidity = float(arg[0]) 59 | self.humidity_lock.ensure_released() 60 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandbme280.pyi: -------------------------------------------------------------------------------- 1 | from . import CommandDevice 2 | from ..lock import Lock 3 | 4 | 5 | class CommandBME280(CommandDevice): 6 | pressure_lock: Lock 7 | temperature_lock: Lock 8 | humidity_lock: Lock 9 | def get_pressure(self) -> float: ... 10 | def get_temperature(self) -> float: ... 11 | def get_humidity(self) -> float: ... 12 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commanddallas.py: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | import logging 4 | module_logger = logging.getLogger(__name__) 5 | 6 | # bonjour info 7 | BONJOUR_ID = 'DALLAS' 8 | CLASS_NAME = 'CommandDallas' 9 | 10 | # incoming 11 | CMD_ANSWER_CELSIUS = 'C' 12 | 13 | # outgoing 14 | CMD_REQUEST_CELSIUS = 'RC' 15 | 16 | 17 | class CommandDallas(CommandDevice): 18 | 19 | def __init__(self): 20 | CommandDevice.__init__(self) 21 | self.register_all_requests() 22 | 23 | def register_all_requests(self): 24 | self.register_request( 25 | CMD_REQUEST_CELSIUS, 26 | CMD_ANSWER_CELSIUS, 27 | 'celsius', 28 | self.handle_celsius_command) 29 | 30 | def handle_celsius_command(self, *arg): 31 | if arg[0]: 32 | self.celsius = float(arg[0]) 33 | self.celsius_lock.ensure_released() 34 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commanddallas.pyi: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | class CommandDallas(CommandDevice): 4 | def __init__(self): ... 5 | def get_celsius(self) -> float: ... 6 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commanddevice.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: commanddevice 4 | :platform: Unix 5 | :synopsis: Forms the template of the different Arduino devices. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | from ..commandhandler import CommandHandler 11 | from ..lock import Lock 12 | from ..exceptions import CMDeviceReplyTimeout 13 | 14 | import logging 15 | 16 | # Default timeout value 17 | DEFAULT_TIMEOUT = 1 18 | 19 | # Bonjour Information 20 | BONJOUR_ID = 'TEMPLATE' 21 | CLASS_NAME = 'CommandDevice' 22 | 23 | 24 | class CommandDevice(object): 25 | """ 26 | Base class to represent the different Arduino devices. 27 | """ 28 | def __init__(self): 29 | self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) 30 | 31 | self.cmdHdl = CommandHandler() 32 | self.cmdHdl.add_default_handler(self.unrecognized) 33 | 34 | def init(self): 35 | """ 36 | .. note:: This function is called once the write function is set. Device setup (sending commands) goes here 37 | """ 38 | pass 39 | 40 | @classmethod 41 | def from_config(cls, config): 42 | """ 43 | Obtains the device information from a configuration setup. 44 | 45 | Returns: 46 | CommandDevice: A new instance of CommandDevice with details set from the configuration. 47 | 48 | """ 49 | return cls(**config) 50 | 51 | def handle_command(self, cmd): 52 | """ 53 | Handles a command to the device. 54 | 55 | Args: 56 | cmd (str): The command to handle. 57 | 58 | """ 59 | self.cmdHdl.handle(cmd) 60 | 61 | def set_command_header(self, cmdHeader): 62 | """ 63 | Sets the command header. 64 | 65 | Args: 66 | cmdHeader (str): The command header to be set. 67 | 68 | """ 69 | self.cmdHdl.set_command_header(cmdHeader) 70 | 71 | def set_write_function(self, write_func): 72 | """ 73 | Sets the write function for the device. 74 | 75 | Args: 76 | write_func (str): The write function to be set. 77 | 78 | """ 79 | self.write = write_func 80 | 81 | def send(self, command_id, *arg): 82 | """ 83 | Sends a command to/from the device. 84 | 85 | Args: 86 | command_id (str): The ID of the command. 87 | 88 | *arg: Variable argument. 89 | 90 | """ 91 | self.write(self.cmdHdl.forge_command(command_id, *arg)) 92 | 93 | def unrecognized(self, cmd): 94 | """ 95 | The supplied command is unrecognised. 96 | 97 | Args: 98 | cmd (str): The supplied command. 99 | 100 | """ 101 | self.logger.warning('Received unknown command "{}"'.format(cmd)) 102 | 103 | def register_request(self, request_command, answer_command, variable_name, callback_function_for_variable_update, variable_init_value=None, timeout=DEFAULT_TIMEOUT): 104 | """ 105 | Registers a new request to/from the device. 106 | 107 | Args: 108 | request_command (str): The requesting command. 109 | 110 | answer_command (str): The answering command. 111 | 112 | variable_name (str): The name of the variable. 113 | 114 | callback_function_for_variable_update (str): The callback function for updating the variable. 115 | 116 | variable_init_value: Initialisation value for the variable, default set to None. 117 | 118 | timeout (float): Time to wait until timeout, default set to DEFAULT_TIMEOUT (1) 119 | 120 | """ 121 | 122 | setattr(self, variable_name, variable_init_value) 123 | 124 | lock_variable_name = variable_name + '_lock' 125 | setattr(self, lock_variable_name, Lock(timeout)) 126 | 127 | self.cmdHdl.add_command(answer_command, callback_function_for_variable_update) 128 | 129 | request_function_name = 'request_' + variable_name 130 | 131 | def request(): 132 | """ 133 | Sends the request command to/from device. 134 | """ 135 | self.send(request_command) 136 | 137 | setattr(self, request_function_name, request) 138 | 139 | get_function_name = 'get_' + variable_name 140 | 141 | def get(): 142 | """ 143 | Gets the variable name. 144 | 145 | Returns: 146 | variable_name (str): Name of the variable. 147 | 148 | Raises: 149 | CommandTimeOutError: Device did not response to command after X time. 150 | """ 151 | variable_lock = getattr(self, lock_variable_name) 152 | variable_lock.acquire() 153 | getattr(self, request_function_name)() 154 | is_valid, elapsed = variable_lock.wait_until_released() 155 | variable_lock.ensure_released() 156 | 157 | if is_valid: 158 | return getattr(self, variable_name) 159 | else: 160 | raise CMDeviceReplyTimeout(self.cmdHdl.cmd_header, request_command, elapsed) 161 | 162 | setattr(self, get_function_name, get) 163 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commanddevice.pyi: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Dict 3 | from ..commandhandler import GenericCommandHandler 4 | 5 | class CommandDevice: 6 | logger: logging.Logger 7 | cmdHdl: GenericCommandHandler 8 | @classmethod 9 | def from_config(cls, config: Dict) -> CommandDevice: ... 10 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commanddigitalread.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandDigitalRead 4 | :platform: Unix 5 | :synopsis: Represents a DigitalRead Arduino Device. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | 11 | from .commanddevice import CommandDevice 12 | 13 | import logging 14 | module_logger = logging.getLogger(__name__) 15 | 16 | 17 | # Bonjour Information 18 | BONJOUR_ID = 'DIGITALREAD' 19 | CLASS_NAME = 'CommandDigitalRead' 20 | 21 | # Incoming 22 | CMD_ANSWER_STATE = 'S' 23 | 24 | # Outgoing 25 | CMD_REQUEST_STATE = 'R' 26 | 27 | 28 | class CommandDigitalRead(CommandDevice): 29 | """ 30 | DigitalRead Arduino device. 31 | 32 | Base: 33 | CommandDevice 34 | """ 35 | def __init__(self): 36 | CommandDevice.__init__(self) 37 | self.register_all_requests() 38 | 39 | ## 40 | def register_all_requests(self): 41 | """ 42 | Registers all requests. 43 | """ 44 | self.register_request( 45 | CMD_REQUEST_STATE, 46 | CMD_ANSWER_STATE, 47 | 'state', 48 | self.handle_state_command) 49 | 50 | def handle_state_command(self, *arg): 51 | """ 52 | Handles the state setting command. 53 | 54 | Args: 55 | *arg: Variable command. 56 | """ 57 | if arg[0]: 58 | self.state = bool(int(arg[0])) 59 | self.state_lock.ensure_released() 60 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commanddigitalread.pyi: -------------------------------------------------------------------------------- 1 | from . import CommandDevice 2 | 3 | class CommandDigitalRead(CommandDevice): 4 | def __init__(self): ... 5 | def get_state(self) -> bool: ... -------------------------------------------------------------------------------- /commanduino/commanddevices/commanddigitalwrite.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandDigitalWrite 4 | :platform: Unix 5 | :synopsis: Represents a DigitalWrite Arduino device. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | from .commanddevice import CommandDevice 11 | 12 | import logging 13 | module_logger = logging.getLogger(__name__) 14 | 15 | 16 | # Bonjour Information 17 | BONJOUR_ID = 'DIGITALWRITE' 18 | CLASS_NAME = 'CommandDigitalWrite' 19 | 20 | # Outgoing (Write) 21 | CMD_SET_LEVEL = 'W' 22 | 23 | 24 | class CommandDigitalWrite(CommandDevice): 25 | """ 26 | DigitalWrite Arduino device. 27 | 28 | Base: 29 | CommandDevice 30 | """ 31 | def __init__(self): 32 | CommandDevice.__init__(self) 33 | 34 | def set_level(self, level: int) -> None: 35 | """ 36 | Sets the level of the device. 37 | 38 | Args: 39 | level (int): The level setting. 40 | 41 | """ 42 | self.send(CMD_SET_LEVEL, int(bool(level))) 43 | 44 | def low(self): 45 | """ 46 | Sets the level to LOW. 47 | """ 48 | self.set_level(0) 49 | 50 | def high(self): 51 | """ 52 | Sets the level to HIGH. 53 | """ 54 | self.set_level(1) 55 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandlinearaccelstepper.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandLinearAccelStepper 4 | :platform: Unix 5 | :synopsis: Represents a Linear AccelStepper Arduino device. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | 11 | from .commanddevice import CommandDevice 12 | 13 | import time 14 | 15 | import logging 16 | module_logger = logging.getLogger(__name__) 17 | 18 | # bonjour info 19 | BONJOUR_ID = 'LINEARACCELSTEPPER' 20 | CLASS_NAME = 'CommandLinearAccelStepper' 21 | 22 | # Incoming Commands 23 | COMMANDLINEARACCELSTEPPER_SET_POSITION = "SP" 24 | COMMANDLINEARACCELSTEPPER_SET_SPEED = "SS" 25 | COMMANDLINEARACCELSTEPPER_SET_MAX_SPEED = "SMS" 26 | COMMANDLINEARACCELSTEPPER_SET_ACC = "SA" 27 | 28 | COMMANDLINEARACCELSTEPPER_ENABLE_ACC = "EA" 29 | COMMANDLINEARACCELSTEPPER_DISABLE_ACC = "DA" 30 | COMMANDLINEARACCELSTEPPER_ENABLE_SWITCH = "ES" 31 | COMMANDLINEARACCELSTEPPER_DISABLE_SWITCH = "DS" 32 | 33 | COMMANDLINEARACCELSTEPPER_HOME = "H" 34 | COMMANDLINEARACCELSTEPPER_MOVE_TO = "MT" 35 | COMMANDLINEARACCELSTEPPER_MOVE = "M" 36 | COMMANDLINEARACCELSTEPPER_STOP = "S" 37 | 38 | COMMANDLINEARACCELSTEPPER_REQUEST_SWITCH = "RS" 39 | COMMANDLINEARACCELSTEPPER_REQUEST_MOVING = "RM" 40 | COMMANDLINEARACCELSTEPPER_REQUEST_DIST = "RD" 41 | COMMANDLINEARACCELSTEPPER_REQUEST_TARGET = "RT" 42 | COMMANDLINEARACCELSTEPPER_REQUEST_POSITION = "RP" 43 | 44 | COMMANDLINEARACCELSTEPPER_REQUEST_SPEED = "RIS" 45 | COMMANDLINEARACCELSTEPPER_REQUEST_MAXSPEED = "RIMS" 46 | COMMANDLINEARACCELSTEPPER_REQUEST_ACCELERATION = "RIA" 47 | 48 | # Outgoing Commands 49 | COMMANDLINEARACCELSTEPPER_SWITCH = "S" 50 | COMMANDLINEARACCELSTEPPER_MOVING = "M" 51 | COMMANDLINEARACCELSTEPPER_DIST = "D" 52 | COMMANDLINEARACCELSTEPPER_TARGET = "T" 53 | COMMANDLINEARACCELSTEPPER_POSITION = "P" 54 | 55 | COMMANDLINEARACCELSTEPPER_SPEED = "IS" 56 | COMMANDLINEARACCELSTEPPER_MAXSPEED = "IMS" 57 | COMMANDLINEARACCELSTEPPER_ACCELERATION = "IA" 58 | 59 | # Default speed 60 | DEFAULT_SPEED = 5000 61 | 62 | # Default maximum speed 63 | DEFAULT_MAX_SPEED = 5000 64 | 65 | # Default acceleration 66 | DEFAULT_ACCELERATION = 2000 67 | 68 | # Default homing speed 69 | DEFAULT_HOMING_SPEED = 2000 70 | 71 | # Default sleep time 72 | DEFAULT_SLEEP_TIME = 0.1 # let's not make it too low to not make the communication bus too busy 73 | 74 | 75 | class CommandLinearAccelStepper(CommandDevice): 76 | """ 77 | Linear Accelerator Stepper Arduino device. 78 | 79 | Args: 80 | speed (int): Speed of the device, default set to DEFAULT_SPEED (5000) 81 | 82 | max_speed (int): Maximum speed of the device, default set to DEFAULT_MAX_SPEED (5000) 83 | 84 | acceleration (int): Speed of acceleration, default set to DEFAULT_ACCELERATION (2000) 85 | 86 | homing_speed (int): Homing speed of the device, default set to DEFAULT_HOMING_SPEED (2000) 87 | 88 | enabled_acceleration (bool): Acceleration enabled, default set to True. 89 | 90 | reverted_direction (bool): Direction is reverted, default set to False. 91 | 92 | reverted_switch (bool): Switch is reverted, default set to False. 93 | 94 | Base: 95 | CommandDevice 96 | """ 97 | def __init__(self, speed=DEFAULT_SPEED, max_speed=DEFAULT_MAX_SPEED, acceleration=DEFAULT_ACCELERATION, homing_speed=DEFAULT_HOMING_SPEED, enabled_acceleration=True, reverted_direction=False, reverted_switch=False): 98 | CommandDevice.__init__(self) 99 | self.register_all_requests() 100 | 101 | self.init_speed = speed 102 | self.init_max_speed = max_speed 103 | self.init_acceleration = acceleration 104 | self.homing_speed = homing_speed 105 | self.enabled_acceleration = enabled_acceleration 106 | self.reverted_direction = reverted_direction 107 | self.reverted_switch = reverted_switch 108 | 109 | def init(self): 110 | self.set_all_params() 111 | 112 | def set_all_params(self): 113 | """ 114 | Sets all the parameters of the device automatically. 115 | """ 116 | self.set_running_speed(self.init_speed) 117 | self.set_max_speed(self.init_max_speed) 118 | self.set_acceleration(self.init_acceleration) 119 | 120 | if self.enabled_acceleration: 121 | self.enable_acceleration() 122 | else: 123 | self.disable_acceleration() 124 | 125 | if self.reverted_switch: 126 | self.enable_revert_switch() 127 | else: 128 | self.disable_revert_switch() 129 | 130 | def apply_reverted_direction(self, value): 131 | """ 132 | Reverts the direction of movement. 133 | 134 | Args: 135 | value (int): The current value. 136 | 137 | Returns: 138 | value (int): The new value (-value) 139 | 140 | """ 141 | if self.reverted_direction: 142 | value = -value 143 | return value 144 | 145 | @property 146 | def is_moving(self): 147 | """ 148 | Check for movement in device. 149 | 150 | Returns: 151 | self.get_moving_state(): The movement state. 152 | """ 153 | return self.get_moving_state() 154 | 155 | def wait_until_idle(self): 156 | """ 157 | Waits until the device is idle (not moving). 158 | """ 159 | while self.is_moving: 160 | time.sleep(DEFAULT_SLEEP_TIME) 161 | 162 | def set_current_position(self, steps): 163 | """ 164 | Sets the current position of the device. 165 | 166 | Args: 167 | steps (int): The position to move to. 168 | 169 | """ 170 | self.send(COMMANDLINEARACCELSTEPPER_SET_POSITION, steps) 171 | 172 | def _set_speed(self, steps_per_second): 173 | """ 174 | Sets the speed of the device. 175 | 176 | Args: 177 | steps_per_second (int): The number of steps per second to move. 178 | 179 | """ 180 | self.send(COMMANDLINEARACCELSTEPPER_SET_SPEED, steps_per_second) 181 | 182 | def set_running_speed(self, steps_per_second): 183 | """ 184 | Sets the running speed of the device. 185 | 186 | Args: 187 | steps_per_second (int): The number of steps per second. 188 | 189 | """ 190 | self.running_speed = steps_per_second 191 | 192 | def set_homing_speed(self, steps_per_second): 193 | """ 194 | Sets the homing speed of the device. 195 | 196 | Args: 197 | steps_per_second (int): The number of steps per second. 198 | 199 | """ 200 | self.homing_speed = steps_per_second 201 | 202 | def set_max_speed(self, steps_per_second): 203 | """ 204 | Sets the maximum speed of the device. 205 | 206 | Args: 207 | steps_per_second (int): The number of steps per second. 208 | 209 | """ 210 | self.send(COMMANDLINEARACCELSTEPPER_SET_MAX_SPEED, steps_per_second) 211 | 212 | def set_acceleration(self, steps_per_second_per_second): 213 | """ 214 | Sets the acceleration speed of the device. 215 | 216 | Args: 217 | steps_per_second_per_second (int): The number of steps per second per second. 218 | 219 | """ 220 | self.send(COMMANDLINEARACCELSTEPPER_SET_ACC, steps_per_second_per_second) 221 | 222 | def enable_acceleration(self): 223 | """ 224 | Enables acceleration on the device. 225 | """ 226 | self.wait_until_idle() 227 | self.send(COMMANDLINEARACCELSTEPPER_ENABLE_ACC) 228 | # small bug here, better to run stop(), the stepper has a small velocity, seems to be a bug in accel stepper 229 | self.stop() 230 | self.enabled_acceleration = True 231 | 232 | def disable_acceleration(self): 233 | """ 234 | Disables acceleration on the device. 235 | """ 236 | self.wait_until_idle() 237 | self.send(COMMANDLINEARACCELSTEPPER_DISABLE_ACC) 238 | # small bug here, better to run stop(), the stepper has a small velocity, seems to be a bug in accel stepper 239 | self.stop() 240 | self.enabled_acceleration = False 241 | 242 | def enable_revert_switch(self): 243 | """ 244 | Enables switch reversion on the device. 245 | """ 246 | self.reverted_switch = True 247 | self.send(COMMANDLINEARACCELSTEPPER_ENABLE_SWITCH) 248 | 249 | def disable_revert_switch(self): 250 | """ 251 | Disables switch reversion on the device. 252 | """ 253 | self.reverted_switch = False 254 | self.send(COMMANDLINEARACCELSTEPPER_DISABLE_SWITCH) 255 | 256 | def home(self, wait=True): 257 | """ 258 | Homes the device. 259 | 260 | Args: 261 | wait (bool): Wait for the device to be idle, default set to True. 262 | 263 | """ 264 | homing_speed = self.apply_reverted_direction(self.homing_speed) 265 | self._set_speed(-homing_speed) 266 | self.send(COMMANDLINEARACCELSTEPPER_HOME) 267 | if wait: 268 | self.wait_until_idle() 269 | 270 | def move_to(self, steps, wait=True): 271 | """ 272 | Moves the device to a specific point. 273 | 274 | Args: 275 | steps (int): The position to move to. 276 | 277 | wait (bool): Wait until the device is idle, default set to True. 278 | 279 | """ 280 | # if not self.enabled_acceleration: 281 | running_speed = self.apply_reverted_direction(self.running_speed) 282 | self._set_speed(running_speed) 283 | steps = self.apply_reverted_direction(steps) 284 | self.send(COMMANDLINEARACCELSTEPPER_MOVE_TO, steps) 285 | if wait: 286 | self.wait_until_idle() 287 | 288 | def move(self, steps, wait=True): 289 | """ 290 | Moves the device a certain number of steps. 291 | 292 | Args: 293 | steps (int): The number of steps to move. 294 | 295 | wait (bool): Waits for the device to be idle, default set to True. 296 | 297 | """ 298 | # if not self.enabled_acceleration: 299 | running_speed = self.apply_reverted_direction(self.running_speed) 300 | self._set_speed(running_speed) 301 | steps = self.apply_reverted_direction(steps) 302 | self.send(COMMANDLINEARACCELSTEPPER_MOVE, steps) 303 | if wait: 304 | self.wait_until_idle() 305 | 306 | def stop(self, wait=True): 307 | """ 308 | Stops the movement of the device. 309 | 310 | Args: 311 | wait (bool): Wait until the device is idle, default set to True. 312 | 313 | """ 314 | self.send(COMMANDLINEARACCELSTEPPER_STOP) 315 | if wait: 316 | self.wait_until_idle() 317 | 318 | def register_all_requests(self): 319 | """ 320 | Registers all requests to the device for later access. 321 | """ 322 | self.register_request( 323 | COMMANDLINEARACCELSTEPPER_REQUEST_SWITCH, 324 | COMMANDLINEARACCELSTEPPER_SWITCH, 325 | 'switch_state', 326 | self.handle_switch_state_command) 327 | self.register_request( 328 | COMMANDLINEARACCELSTEPPER_REQUEST_MOVING, 329 | COMMANDLINEARACCELSTEPPER_MOVING, 330 | 'moving_state', 331 | self.handle_moving_state_command) 332 | self.register_request( 333 | COMMANDLINEARACCELSTEPPER_REQUEST_DIST, 334 | COMMANDLINEARACCELSTEPPER_DIST, 335 | 'distance_to_go', 336 | self.handle_distance_to_go_command) 337 | self.register_request( 338 | COMMANDLINEARACCELSTEPPER_REQUEST_TARGET, 339 | COMMANDLINEARACCELSTEPPER_TARGET, 340 | 'target_position', 341 | self.handle_target_position_command) 342 | self.register_request( 343 | COMMANDLINEARACCELSTEPPER_REQUEST_POSITION, 344 | COMMANDLINEARACCELSTEPPER_POSITION, 345 | 'current_position', 346 | self.handle_current_position_command) 347 | self.register_request( 348 | COMMANDLINEARACCELSTEPPER_REQUEST_SPEED, 349 | COMMANDLINEARACCELSTEPPER_SPEED, 350 | 'speed', 351 | self.handle_speed_command) 352 | self.register_request( 353 | COMMANDLINEARACCELSTEPPER_REQUEST_MAXSPEED, 354 | COMMANDLINEARACCELSTEPPER_MAXSPEED, 355 | 'max_speed', 356 | self.handle_max_speed_command) 357 | self.register_request( 358 | COMMANDLINEARACCELSTEPPER_REQUEST_ACCELERATION, 359 | COMMANDLINEARACCELSTEPPER_ACCELERATION, 360 | 'acceleration', 361 | self.handle_acceleration_command) 362 | 363 | def handle_switch_state_command(self, *arg): 364 | """ 365 | Handles the command for switch state. 366 | 367 | Args: 368 | arg*: Variable command. 369 | 370 | """ 371 | if arg[0]: 372 | self.switch_state = bool(int(arg[0])) 373 | self.switch_state_lock.ensure_released() 374 | 375 | def handle_moving_state_command(self, *arg): 376 | """ 377 | Handles the command for moving state. 378 | 379 | Args: 380 | *arg: Variable command. 381 | 382 | """ 383 | if arg[0]: 384 | self.moving_state = bool(int(arg[0])) 385 | self.moving_state_lock.ensure_released() 386 | 387 | def handle_distance_to_go_command(self, *arg): 388 | """ 389 | Handles the command for distance to go. 390 | 391 | Args: 392 | *arg: variable command. 393 | 394 | """ 395 | if arg[0]: 396 | self.distance_to_go = self.apply_reverted_direction(int(arg[0])) 397 | self.distance_to_go_lock.ensure_released() 398 | 399 | def handle_target_position_command(self, *arg): 400 | """ 401 | Handles the command for target position. 402 | 403 | Args: 404 | *arg: Variable command. 405 | 406 | """ 407 | if arg[0]: 408 | self.target_position = self.apply_reverted_direction(int(arg[0])) 409 | self.target_position_lock.ensure_released() 410 | 411 | def handle_current_position_command(self, *arg): 412 | """ 413 | Handles the command for current position. 414 | 415 | Args: 416 | *arg: Variable command. 417 | 418 | """ 419 | if arg[0]: 420 | self.current_position = self.apply_reverted_direction(int(arg[0])) 421 | self.current_position_lock.ensure_released() 422 | 423 | def handle_speed_command(self, *arg): 424 | """ 425 | Handles the command for speed. 426 | 427 | Args: 428 | *arg: Variable command. 429 | 430 | """ 431 | if arg[0]: 432 | self.speed = self.apply_reverted_direction(float(arg[0])) 433 | self.speed_lock.ensure_released() 434 | 435 | def handle_max_speed_command(self, *arg): 436 | """ 437 | Handles the command for maximum speed. 438 | 439 | Args: 440 | *arg: Variable argument. 441 | 442 | """ 443 | if arg[0]: 444 | self.max_speed = float(arg[0]) 445 | self.max_speed_lock.ensure_released() 446 | 447 | def handle_acceleration_command(self, *arg): 448 | """ 449 | Handles the command for acceleration. 450 | 451 | Args: 452 | *arg: Variable argument. 453 | """ 454 | if arg[0]: 455 | self.acceleration = float(arg[0]) 456 | self.acceleration_lock.ensure_released() 457 | 458 | def __str__(self): 459 | """ 460 | Prints the current information for the device. 461 | """ 462 | return "###\n" \ 463 | "switch_state: " + str(self.get_switch_state()) + "\n" \ 464 | "moving_state: " + str(self.get_moving_state()) + "\n" \ 465 | "distance_to_go: " + str(self.get_distance_to_go()) + "\n" \ 466 | "target_position: " + str(self.get_target_position()) + "\n" \ 467 | "current_position: " + str(self.get_current_position()) + "\n" \ 468 | "speed: " + str(self.get_speed()) + "\n" \ 469 | "max_speed: " + str(self.get_max_speed()) + "\n" \ 470 | "acceleration: " + str(self.get_acceleration()) + "\n" \ 471 | "###" 472 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandlinearaccelstepper.pyi: -------------------------------------------------------------------------------- 1 | from . import CommandDevice 2 | 3 | class CommandLinearAccelStepper(CommandDevice): 4 | def __init__(self, speed: int, max_speed: int, acceleration: int, homing_speed: int, 5 | enabled_acceleration: bool = True, reverted_direction: bool = False, reverted_switch: bool = False): ... 6 | def wait_until_idle(self) -> None: ... 7 | def home(self, wait: bool = True) -> None: ... 8 | def move_to(self, steps: float, wait: bool = True) -> None: ... 9 | def move(self, steps: float, wait: bool = True): ... 10 | def stop(self, wait: bool = True) -> None: ... 11 | 12 | # Acceleration 13 | def get_acceleration(self) -> float: ... 14 | def set_acceleration(self, steps_per_second_per_second: float) -> None: ... 15 | 16 | # Current position 17 | def get_current_position(self) -> float: ... 18 | def set_current_position(self, steps: float) -> None: ... 19 | 20 | # Distance to go 21 | def get_distance_to_go(self) -> float: ... 22 | 23 | # Enabled acceleration 24 | enabled_acceleration: bool 25 | def enable_acceleration(self) -> None: ... 26 | def disable_acceleration(self) -> None: ... 27 | 28 | # Moving state 29 | @property 30 | def is_moving(self) -> bool: ... 31 | def get_moving_state(self) -> bool: ... 32 | 33 | # Max speed 34 | def get_max_speed(self) -> float: ... 35 | def set_max_speed(self, steps_per_second: float) -> None: ... 36 | 37 | # Speed 38 | # Ok so this might be confusing... 39 | # _set_speed() sets the speed in the Arduino before actually moving the motor [e.g. in move(), move_to() or home()] 40 | # set_running_speed sets the value of the running_speed variable, used for set_speed before normal movements 41 | # set_homing_speed sets the value of the homing_speed variable, used for set_speed before homing movements in home() 42 | def get_speed(self) -> float: ... 43 | def _set_speed(self, steps_per_second: float) -> None: ... 44 | 45 | # Running speed 46 | def set_running_speed(self, steps_per_second: int) -> None: ... 47 | 48 | # Homing speed 49 | def set_homing_speed(self, steps_per_second: int) -> None: ... 50 | 51 | # Homing switch state 52 | def get_switch_state(self) -> bool: ... 53 | """ True means the linear actuator is in home position """ 54 | 55 | # Target position 56 | def get_target_position(self) -> float: ... 57 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandmax31865.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandMAX31865 4 | :platform: Windows 5 | :synopsis: Represents a MAX31865 PT100 RTD Temperature Sensor Amplifier. 6 | 7 | .. moduleauthor:: Alex Hammer <2423935H@student.gla.ac.uk> 8 | 9 | """ 10 | 11 | import time 12 | 13 | from .commanddevice import CommandDevice 14 | 15 | # Bonjour Information 16 | BONJOUR_ID = 'MAX31865' 17 | CLASS_NAME = 'CommandMAX31865' 18 | 19 | # Incoming 20 | CMD_READ_TEMP = 'R' 21 | CMD_READ_ERROR = 'R' 22 | CMD_REPLY_HEADER = 'C' 23 | CMD_INITIALIZE = 'Z' 24 | CMD_INITIALIZE_HEADER = 'E' 25 | 26 | 27 | class CommandMAX31865(CommandDevice): 28 | """ 29 | MAX31865 Arduino device. 30 | 31 | Implementation based on Adafruit RTD amplifier board. 32 | 33 | Base: 34 | CommandDevice 35 | """ 36 | def __init__(self): 37 | CommandDevice.__init__(self) 38 | self.register_all_requests() 39 | 40 | 41 | def register_all_requests(self): 42 | """ Registers all requests. """ 43 | self.register_request( 44 | CMD_READ_TEMP, 45 | CMD_REPLY_HEADER, 46 | 'temp', 47 | self.handle_get_temp 48 | ) 49 | self.register_request( 50 | CMD_READ_ERROR, 51 | CMD_REPLY_HEADER, 52 | 'error_code', 53 | self.handle_get_error_code 54 | ) 55 | self.register_request( 56 | CMD_INITIALIZE, 57 | CMD_INITIALIZE_HEADER, 58 | 'initialization_code', 59 | self.handle_initialize, 60 | timeout=1.2, 61 | ) 62 | 63 | def handle_initialize(self, *arg): 64 | """ Handles the sensor initialization command. """ 65 | if arg[0]: 66 | self.initialization_code = int(arg[0]) 67 | self.initialization_code_lock.ensure_released() 68 | 69 | def init(self): 70 | """ Initializes the sensor. 71 | 72 | Refer to self.initialization_code for checking if the initialization was successful. 73 | 74 | .initialization_code is None - the device wasn't initialized, call get_initialization_code() 75 | .initialization_code == 1 - the device is found and the communication was established successfully 76 | .initialization_code == 0 - the device was not found or the communication cannot be established, please 77 | check the device datasheet for possible reasons 78 | Call get_initialization_code() to try again. 79 | """ 80 | 81 | self.get_initialization_code() 82 | 83 | if self.initialization_code != 1: 84 | self.logger.error("Unable to connect to sensor!") 85 | 86 | # wait for sensor to stabilize 87 | time.sleep(1) 88 | 89 | def handle_get_temp(self, *arg): 90 | """ Handles the temp read command. """ 91 | if arg[0]: 92 | self.temp = float(arg[1]) 93 | self.temp_lock.ensure_released() 94 | 95 | def handle_get_error_code(self, *arg): 96 | """ Handles the fault code read command. """ 97 | if arg[0]: 98 | self.error_code = int(arg[0]) 99 | self.error_code_lock.ensure_released() 100 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandmax31865.pyi: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | 4 | class CommandTCS34725(CommandDevice): 5 | def initialization_code(self) -> None: ... 6 | def get_temp(self) -> float: ... 7 | def get_error_code(self) -> int: ... 8 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandmcp9600.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from .commanddevice import CommandDevice 4 | 5 | module_logger = logging.getLogger(__name__) 6 | 7 | 8 | 9 | 10 | # bonjour info 11 | BONJOUR_ID = "MCP9600" 12 | CLASS_NAME = "CommandMCP9600" 13 | 14 | # incoming 15 | CMD_ANSWER_CELSIUS = "C" 16 | 17 | # outgoing 18 | CMD_REQUEST_CELSIUS = "RC" 19 | 20 | 21 | class CommandMCP9600(CommandDevice): 22 | def __init__(self): 23 | CommandDevice.__init__(self) 24 | self.register_all_requests() 25 | 26 | def register_all_requests(self): 27 | self.register_request( 28 | CMD_REQUEST_CELSIUS, 29 | CMD_ANSWER_CELSIUS, 30 | 'celsius', 31 | self.handle_celsius_command) 32 | 33 | def handle_celsius_command(self, *arg): 34 | if arg[0]: 35 | self.celsius = float(arg[0]) 36 | self.celsius_lock.ensure_released() -------------------------------------------------------------------------------- /commanduino/commanddevices/commandmcp9600.pyi: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | class CommandMCP9600(CommandDevice): 4 | def __init__(self): ... 5 | def get_celsius(self) -> float: ... 6 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandpca9548a.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandPCA9548A 4 | :platform: Unix 5 | :synopsis: Represents a PCA9548A Arduino Device. 6 | 7 | .. moduleauthor:: Sergey Zalesskiy 8 | 9 | """ 10 | 11 | from .commanddevice import CommandDevice 12 | 13 | import logging 14 | module_logger = logging.getLogger(__name__) 15 | 16 | 17 | # Bonjour Information 18 | BONJOUR_ID = 'PCA9548A' 19 | CLASS_NAME = 'CommandPCA9548A' 20 | 21 | # Incoming 22 | CMD_ANSWER_STATE = 'C' 23 | 24 | # Outgoing 25 | CMD_REQUEST_STATE = 'R' 26 | CMD_SET_STATE = 'W' 27 | 28 | # Max channels on the mux 29 | MAX_CHANNELS = 8 30 | 31 | 32 | class CommandPCA9548A(CommandDevice): 33 | """ 34 | PCA9548A Arduino device. 35 | 36 | Base: 37 | CommandDevice 38 | """ 39 | def __init__(self): 40 | CommandDevice.__init__(self) 41 | self.register_all_requests() 42 | 43 | def register_all_requests(self): 44 | """ 45 | Registers all requests. 46 | """ 47 | self.register_request( 48 | CMD_REQUEST_STATE, 49 | CMD_ANSWER_STATE, 50 | 'channels', 51 | self.handle_get_channels) 52 | 53 | def handle_get_channels(self, *arg): 54 | """ 55 | Handles the state setting command. 56 | 57 | Args: 58 | *arg: Variable command. 59 | """ 60 | if arg[0]: 61 | # This would be a one byte representing active channels mask 62 | self.channels = int(arg[0]) 63 | self.channels_lock.ensure_released() 64 | 65 | def set_channels(self, channels): 66 | """Sets the state of all I2C channels on the MUX. 67 | 68 | Args: 69 | channels: An integer representing an 8-bit long mask of channels to switch on/off. 70 | Anything bigger than 2^8=255 is set to zero on Arduino side anyway. 71 | """ 72 | if channels > 255: 73 | channels = 0 74 | self.send(CMD_SET_STATE, channels) 75 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandpca9548a.pyi: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | class CommandPCA9548A(CommandDevice): 4 | def set_channels(self, channels: int) -> None: ... 5 | def get_channels(self) -> int: ... 6 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandservo.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: CommandServo 3 | :platform: Unix 4 | :synopsis: Represents a Servo Arduino device. 5 | 6 | .. moduleauthor:: Jonathan Grizou 7 | 8 | """ 9 | from .commanddevice import CommandDevice 10 | 11 | import logging 12 | module_logger = logging.getLogger(__name__) 13 | 14 | # Bonjour Information 15 | BONJOUR_ID = 'SERVO' 16 | CLASS_NAME = 'CommandServo' 17 | 18 | # Incoming 19 | CMD_ANSWER_ANGLE = 'A' 20 | 21 | # Outgoing 22 | CMD_SET_ANGLE = 'W' 23 | CMD_REQUEST_ANGLE = 'R' 24 | 25 | 26 | class CommandServo(CommandDevice): 27 | """ 28 | Servo Arduino device. 29 | """ 30 | def __init__(self, initial_angle=90, min_limit=0, max_limit=180): 31 | CommandDevice.__init__(self) 32 | self.register_all_requests() 33 | self.min_limit = 0 34 | self.max_limit = 0 35 | self.limit = False 36 | 37 | # From config 38 | self.set_limit(minimum=min_limit, maximum=max_limit) # If limits other than 0-180 are set than self.limit=True 39 | self.initial_angle = initial_angle 40 | 41 | self.clamp = lambda n, minimum, maximum: max(min(maximum, n), minimum) 42 | 43 | def init(self): 44 | self.set_angle(self.initial_angle) 45 | 46 | # Sets the limits of the device 47 | def set_limit(self, minimum, maximum): 48 | if minimum > 0 and maximum < 180: 49 | self.min_limit = minimum 50 | self.max_limit = maximum 51 | self.limit = True 52 | return True 53 | else: 54 | return False 55 | 56 | # Removes limits 57 | def remove_limit(self): 58 | self.limit = False 59 | 60 | ## 61 | def set_angle(self, angle): 62 | """ 63 | Sets the angle of the device. 64 | 65 | Args: 66 | angle (float): Angle to set the device to. 67 | 68 | """ 69 | if self.limit is True: 70 | angle = self.clamp(angle, self.min_limit, self.max_limit) 71 | self.send(CMD_SET_ANGLE, int(angle)) 72 | else: 73 | self.send(CMD_SET_ANGLE, int(angle)) 74 | 75 | def register_all_requests(self): 76 | """ 77 | Registers all requests to the device for later use. 78 | """ 79 | self.register_request( 80 | CMD_REQUEST_ANGLE, 81 | CMD_ANSWER_ANGLE, 82 | 'angle', 83 | self.handle_angle_command) 84 | 85 | def handle_angle_command(self, *arg): 86 | """ 87 | Handles the command for the angle. 88 | 89 | Args: 90 | *arg: Variable argument. 91 | 92 | """ 93 | if arg[0]: 94 | self.angle = int(arg[0]) 95 | self.angle_lock.ensure_released() 96 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandservo.pyi: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | class CommandServo(CommandDevice): 4 | def __init__(self, initial_angle: int, min_limit: int, max_limit: int): 5 | self.initial_angle : int 6 | self.min_limit : int 7 | self.max_limit : int 8 | self.limit : bool 9 | 10 | def set_limit(self, minimum: int, maximum: int) -> bool : ... 11 | def remove_limit(self) -> None : ... 12 | def set_angle(self, angle: int) -> None : ... 13 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandsht1x.py: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | import logging 4 | module_logger = logging.getLogger(__name__) 5 | 6 | # bonjour info 7 | BONJOUR_ID = 'SHT1X' 8 | CLASS_NAME = 'CommandSHT1X' 9 | 10 | # incoming 11 | CMD_ANSWER_FAHRENHEIT = 'F' 12 | CMD_ANSWER_CELSIUS = 'C' 13 | CMD_ANSWER_HUMIDITY = 'H' 14 | 15 | # outgoing 16 | CMD_REQUEST_FAHRENHEIT = 'RF' 17 | CMD_REQUEST_CELSIUS = 'RC' 18 | CMD_REQUEST_HUMIDITY = 'RH' 19 | 20 | 21 | class CommandSHT1X(CommandDevice): 22 | 23 | def __init__(self): 24 | CommandDevice.__init__(self) 25 | self.register_all_requests() 26 | 27 | def register_all_requests(self): 28 | self.register_request( 29 | CMD_REQUEST_FAHRENHEIT, 30 | CMD_ANSWER_FAHRENHEIT, 31 | 'fahrenheit', 32 | self.handle_fahrenheit_command) 33 | 34 | self.register_request( 35 | CMD_REQUEST_CELSIUS, 36 | CMD_ANSWER_CELSIUS, 37 | 'celsius', 38 | self.handle_celsius_command) 39 | 40 | self.register_request( 41 | CMD_REQUEST_HUMIDITY, 42 | CMD_ANSWER_HUMIDITY, 43 | 'humidity', 44 | self.handle_humidity_command) 45 | 46 | def handle_fahrenheit_command(self, *arg): 47 | if arg[0]: 48 | self.fahrenheit = float(arg[0]) 49 | self.fahrenheit_lock.ensure_released() 50 | 51 | def handle_celsius_command(self, *arg): 52 | if arg[0]: 53 | self.celsius = float(arg[0]) 54 | self.celsius_lock.ensure_released() 55 | 56 | def handle_humidity_command(self, *arg): 57 | if arg[0]: 58 | self.humidity = float(arg[0]) 59 | self.humidity_lock.ensure_released() 60 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandsht1x.pyi: -------------------------------------------------------------------------------- 1 | from . import CommandDevice 2 | from ..lock import Lock 3 | 4 | 5 | class CommandSHT1X(CommandDevice): 6 | humidity_lock: Lock 7 | celsius_lock: Lock 8 | fahrenheit_lock: Lock 9 | def get_humidity(self) -> float: ... 10 | def get_celsius(self) -> float: ... 11 | def get_fahrenheit(self) -> float: ... 12 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandsht31.py: -------------------------------------------------------------------------------- 1 | from .commanddevice import CommandDevice 2 | 3 | import logging 4 | module_logger = logging.getLogger(__name__) 5 | 6 | # bonjour info 7 | BONJOUR_ID = 'SHT31' 8 | CLASS_NAME = 'CommandSHT31' 9 | 10 | # incoming 11 | CMD_ANSWER_CELSIUS = 'C' 12 | CMD_ANSWER_HUMIDITY = 'H' 13 | 14 | # outgoing 15 | CMD_REQUEST_CELSIUS = 'RC' 16 | CMD_REQUEST_HUMIDITY = 'RH' 17 | 18 | 19 | class CommandSHT31(CommandDevice): 20 | 21 | def __init__(self): 22 | CommandDevice.__init__(self) 23 | self.register_all_requests() 24 | 25 | def register_all_requests(self): 26 | self.register_request( 27 | CMD_REQUEST_CELSIUS, 28 | CMD_ANSWER_CELSIUS, 29 | 'celsius', 30 | self.handle_celsius_command) 31 | 32 | self.register_request( 33 | CMD_REQUEST_HUMIDITY, 34 | CMD_ANSWER_HUMIDITY, 35 | 'humidity', 36 | self.handle_humidity_command) 37 | 38 | def handle_celsius_command(self, *arg): 39 | if arg[0]: 40 | self.celsius = float(arg[0]) 41 | self.celsius_lock.ensure_released() 42 | 43 | def handle_humidity_command(self, *arg): 44 | if arg[0]: 45 | self.humidity = float(arg[0]) 46 | self.humidity_lock.ensure_released() 47 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandsht31.pyi: -------------------------------------------------------------------------------- 1 | from . import CommandDevice 2 | from ..lock import Lock 3 | 4 | 5 | class CommandSHT31(CommandDevice): 6 | humidity_lock: Lock 7 | celsius_lock: Lock 8 | def get_humidity(self) -> float: ... 9 | def get_celsius(self) -> float: ... -------------------------------------------------------------------------------- /commanduino/commanddevices/commandtcs34725.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: CommandTCS34725 4 | :platform: Unix 5 | :synopsis: Represents a TCS34725 Arduino Device. 6 | 7 | .. moduleauthor:: Artem Leonov 8 | 9 | """ 10 | 11 | import time 12 | 13 | from .commanddevice import CommandDevice 14 | 15 | # Bonjour Information 16 | BONJOUR_ID = 'TCS34725' 17 | CLASS_NAME = 'CommandTCS34725' 18 | 19 | # Incoming 20 | CMD_READ_RGBC = 'R' 21 | CMD_REPLY_HEADER = 'C' 22 | CMD_INITIALIZE = 'Z' 23 | CMD_INITIALIZE_HEADER = 'E' 24 | 25 | # Outgoing 26 | CMD_SET_INTEGRATION_TIME = 'I' 27 | CMD_SET_GAIN = 'G' 28 | 29 | # Accepted values 30 | INTEGRATION_TIMES = [2.4, 24, 50, 101, 154, 700] 31 | GAINS = [1, 4, 16, 60] 32 | 33 | 34 | class CommandTCS34725(CommandDevice): 35 | """ 36 | TCS34725 Arduino device. 37 | 38 | Implementation based on Adafruit #1334 RGB sensor. 39 | 40 | Base: 41 | CommandDevice 42 | """ 43 | def __init__(self): 44 | CommandDevice.__init__(self) 45 | self.register_all_requests() 46 | 47 | # value placeholders 48 | self._integration_time = 0.05 # 50 ms by default 49 | self._gain = 4 # default 50 | 51 | def register_all_requests(self): 52 | """ Registers all requests. """ 53 | self.register_request( 54 | CMD_READ_RGBC, 55 | CMD_REPLY_HEADER, 56 | 'rgbc', 57 | self.handle_get_rgbc 58 | ) 59 | self.register_request( 60 | CMD_INITIALIZE, 61 | CMD_INITIALIZE_HEADER, 62 | 'initialization_code', 63 | self.handle_initialize, 64 | timeout=1.2, 65 | ) 66 | 67 | def handle_initialize(self, *arg): 68 | """ Handles the sensor initialization command. """ 69 | if arg[0]: 70 | self.initialization_code = int(arg[0]) 71 | self.initialization_code_lock.ensure_released() 72 | 73 | def init(self): 74 | """ Initializes the sensor. 75 | 76 | Refer to self.initialization_code for checking if the initialization was successful. 77 | 78 | .initialization_code is None - the device wasn't initialized, call get_initialization_code() 79 | .initialization_code == 1 - the device is found and the communication was established successfully 80 | .initialization_code == 0 - the device was not found or the communication cannot be established, please 81 | check the device datasheet for possible reasons 82 | 83 | Call get_initialization_code() to try again. 84 | """ 85 | 86 | self.get_initialization_code() 87 | 88 | if self.initialization_code != 1: 89 | self.logger.error("Unable to connect to sensor!") 90 | 91 | # wait for sensor to stabilize 92 | time.sleep(self._integration_time * self._gain) 93 | 94 | def handle_get_rgbc(self, *arg): 95 | """ Handles the rgbc read command. """ 96 | if arg[0]: 97 | self.rgbc = (int(arg[0]), int(arg[1]), int(arg[2]), int(arg[3])) 98 | self.rgbc_lock.ensure_released() 99 | 100 | def set_integration_time(self, integration_time): 101 | """ Sets the sensor integration time. 102 | 103 | Delays the following execution by integration time * gain. 104 | 105 | Args: 106 | integration_time (float): Integration time for all channels in ms. 107 | Accepted values - 2.4, 24, 50, 101, 154, 700. If supplied argument is 108 | not an accepted value - sets to 50 ms by default. 109 | """ 110 | 111 | # multiply by 10 to avoid float parsing/calculations 112 | if integration_time in INTEGRATION_TIMES: 113 | integration_time *= 10 114 | 115 | # setting to 50 ms in case of invalid value 116 | else: 117 | self.logger.warning("Invalid integration time given, setting to default 50 ms.") 118 | integration_time = 500 119 | 120 | self.send(CMD_SET_INTEGRATION_TIME, integration_time) 121 | 122 | # updating placeholder 123 | self._integration_time = integration_time / 10 / 1000 # get rid of *10 and convert to seconds 124 | 125 | # delay to correctly set up the value 126 | time.sleep(self._integration_time * self._gain) 127 | 128 | def set_gain(self, gain): 129 | """ Sets the channels gain. 130 | 131 | Delays the following execution by integration time * gain. 132 | 133 | Args: 134 | gain (int): Gain for all channels. 135 | Accepted values: 1, 4, 16, 60. If supplied argument is not an accepted 136 | value - sets to 4 by default. 137 | """ 138 | 139 | # setting to default 4 if incorrect value supplied 140 | if gain not in GAINS: 141 | self.logger.warning("Invalid gain given, setting to default 4X.") 142 | gain = 4 143 | 144 | self.send(CMD_SET_GAIN, gain) 145 | 146 | # updating placeholder 147 | self._gain = gain 148 | 149 | # delay to correctly set up the value 150 | time.sleep(self._integration_time * self._gain) 151 | -------------------------------------------------------------------------------- /commanduino/commanddevices/commandtcs34725.pyi: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from .commanddevice import CommandDevice 4 | 5 | 6 | class CommandTCS34725(CommandDevice): 7 | def set_integration_time(self, integration_time: float) -> None: ... 8 | def set_gain(self, gain: int) -> None: ... 9 | def get_rgbc(self) -> Tuple[int, int, int, int]: ... 10 | def get_initialization_code(self) -> int: ... 11 | -------------------------------------------------------------------------------- /commanduino/commanddevices/register.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: register 4 | :platform: Unix 5 | :synopsis: This module is used to register the different kinds of devices. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | 11 | from . import commanddevice 12 | from ..exceptions import CMDeviceRegisterError 13 | 14 | DEFAULT_REGISTER = commanddevice.BONJOUR_ID 15 | BONJOUR_REGISTER = {} 16 | 17 | 18 | def add_to_bonjour_register(bonjour_id, constructor): 19 | """ 20 | Adds the device to the Bonjour register. 21 | 22 | Args: 23 | bonjour_id (str): The Bonjour ID. 24 | 25 | constructor: The constructor for registration. 26 | 27 | """ 28 | BONJOUR_REGISTER[bonjour_id] = constructor 29 | 30 | 31 | def create_and_setup_device(cmdHdl, command_id, bonjour_id, device_config): 32 | """ 33 | Creates and sets up the Arduino device for usage. 34 | 35 | Args: 36 | cmdHdl (SerialCommandHandler): The Serial Command Handler object for serial communication. 37 | 38 | command_id (str): The ID of the command. 39 | 40 | bonjour_id (str): The Bonjour ID. 41 | 42 | device_config (Dict): Dictionary containing the device configuration. 43 | 44 | Raises: 45 | CMDeviceRegisterError: Bonjour ID is not in the register of the device. 46 | 47 | """ 48 | if bonjour_id in BONJOUR_REGISTER: 49 | device = BONJOUR_REGISTER[bonjour_id].from_config(device_config) 50 | cmdHdl.add_relay(command_id, device.handle_command) 51 | device.set_command_header(command_id) 52 | device.set_write_function(cmdHdl.write) 53 | device.init() 54 | return device 55 | else: 56 | raise CMDeviceRegisterError(bonjour_id) -------------------------------------------------------------------------------- /commanduino/commandhandler.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: commandhandler 4 | :platform: Unix 5 | :synopsis: Handles the communication to/from the Arduino Hardware. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | import time 11 | import serial 12 | import socket 13 | import threading 14 | import logging 15 | from typing import Callable, Dict, List, Union 16 | 17 | from .exceptions import CMHandlerConfigurationError, CMTimeout, CMCommunicationError 18 | 19 | # Default delimiter to separate commands 20 | DEFAULT_DELIM = ',' 21 | 22 | # Default terminal character of a command 23 | DEFAULT_TERM = ';' 24 | 25 | # Default decimal 26 | DEFAULT_CMD_DECIMAL = 2 27 | 28 | # Default baudrate for communication 29 | DEFAULT_BAUDRATE = 115200 30 | 31 | # Default timeout time 32 | DEFAULT_TIMEOUT = 0.01 33 | 34 | 35 | class CommandHandler(object): 36 | """ 37 | Represents the Command Handler which will handle commands to/from the Arduino hardware. 38 | 39 | Args: 40 | delim: Delimiter of the command, default set to DEFAULT_DELIM(',') 41 | 42 | term: Terminal character of the command, set to DEFAULT_TERM(';') 43 | 44 | cmd_decimal: Decimal of the command, default set to DEFAULT_CMD_DECIMAL(2) 45 | 46 | """ 47 | 48 | @classmethod 49 | def from_config(cls, config: Dict) -> 'CommandHandler': 50 | """ 51 | Obtains the details of the handler from a configuration. 52 | 53 | Args: 54 | cls: The instantiating class. 55 | 56 | config: Dictionary containing the configuration details. 57 | 58 | Returns: 59 | CommandHandler: New CommandHandler object with details set from a configuration setup. 60 | 61 | """ 62 | return cls(**config) 63 | 64 | def __init__(self, delim: str = DEFAULT_DELIM, term: str = DEFAULT_TERM, cmd_decimal: int = DEFAULT_CMD_DECIMAL, 65 | **kwargs): 66 | self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) 67 | 68 | # Something descriptive to reference the handler in logs. 69 | self.name = self.__class__.__name__ 70 | 71 | self.delim = delim # character separating args in a message 72 | self.term = term # character ending a message 73 | 74 | self.buffer = '' # string that will hold the received data 75 | 76 | self.handlers: Dict[str, List[Callable]] = {} 77 | self.relays: Dict[str, List[Callable]] = {} 78 | self.default_handlers: List[Callable] = [] 79 | 80 | self.cmd_header = '' 81 | self.cmd_decimal = cmd_decimal 82 | 83 | def process_char(self, a_char: bytes) -> None: 84 | """ 85 | Processes a single character of a command and adds it to the receiving buffer. 86 | If the terminator character is found, process the buffer. 87 | 88 | Args: 89 | a_char: The character to be processed 90 | 91 | """ 92 | if a_char: 93 | decoded_char = a_char.decode(encoding="utf-8", errors="ignore") 94 | if decoded_char == self.term: 95 | self.handle(self.buffer) 96 | self.buffer = '' 97 | else: 98 | self.buffer += decoded_char 99 | 100 | def handle(self, cmd: str): 101 | """ 102 | Handles a full command to/from the Arduino hardware. 103 | 104 | Args: 105 | cmd: The command to be handled. 106 | 107 | """ 108 | cmd = cmd.strip().strip(self.term) 109 | cmd_list = cmd.split(self.delim) 110 | cmd_id = cmd_list[0] 111 | 112 | self.logger.debug('Handling "{}"'.format(cmd), 113 | extra={'cmd_list': cmd_list, 114 | 'cmd_id': cmd_id}) 115 | 116 | if cmd_id in self.handlers: 117 | for clb in self.handlers[cmd_id]: 118 | self.logger.debug('Found callback for "{}"'.format(cmd_id), 119 | extra={'callback_function': clb}) 120 | clb(*cmd_list[1:]) # all args in the command are given to the callback function as arguments 121 | elif cmd_id in self.relays: 122 | for clb in self.relays[cmd_id]: 123 | self.logger.debug('Found relay for "{}"'.format(cmd_id), 124 | extra={'relay_function': clb}) 125 | clb(self.build_remaining(cmd_list)) 126 | else: 127 | self.logger.debug('No callback assigned to "{}", defaulting'.format(cmd_id)) 128 | for clb in self.default_handlers: 129 | clb(cmd) # give back what was received 130 | 131 | def build_remaining(self, cmd_list: List[str]) -> str: 132 | """ 133 | Builds an Arduino command from a list of partial commands. 134 | 135 | Args: 136 | cmd_list: The list of command constituents. 137 | 138 | """ 139 | return self.delim.join(cmd_list[1:]) + self.term 140 | 141 | def add_command(self, command_id: str, callback_function: Callable) -> None: 142 | """ 143 | Adds an Arduino command to the handler. 144 | 145 | Args: 146 | command_id: The ID of the command. 147 | 148 | callback_function: A copy of the command to "callback". 149 | 150 | """ 151 | if command_id not in self.handlers: 152 | self.handlers[command_id] = [] 153 | if callback_function not in self.handlers[command_id]: 154 | self.handlers[command_id].append(callback_function) 155 | 156 | def remove_command(self, command_id: str, callback_function: Callable) -> None: 157 | """ 158 | Removes an Arduino command from the Handler. 159 | 160 | Args: 161 | command_id: The ID of the command. 162 | 163 | callback_function: A copy of the command to "callback". 164 | 165 | """ 166 | if command_id in self.handlers: 167 | if callback_function in self.handlers[command_id]: 168 | self.handlers[command_id].remove(callback_function) 169 | 170 | def add_relay(self, command_id: str, callback_function: Callable) -> None: 171 | """ 172 | Adds a relay to the Handler. 173 | 174 | Args: 175 | command_id: The ID of the command. 176 | 177 | callback_function: A copy of the command to "callback". 178 | 179 | """ 180 | if command_id not in self.relays: 181 | self.relays[command_id] = [] 182 | if callback_function not in self.relays[command_id]: 183 | self.relays[command_id].append(callback_function) 184 | 185 | def remove_relay(self, command_id: str, callback_function: Callable) -> None: 186 | """ 187 | Removes a relay form the Handler. 188 | 189 | Args: 190 | command_id: The ID of the command. 191 | 192 | callback_function: A copy of the command to "callback". 193 | 194 | """ 195 | if command_id in self.relays: 196 | if callback_function in self.relays[command_id]: 197 | self.relays[command_id].remove(callback_function) 198 | 199 | def add_default_handler(self, callback_function: Callable) -> None: 200 | """ 201 | Adds a default handler to the device. 202 | 203 | Args: 204 | callback_function: A copy of the command to "callback". 205 | 206 | """ 207 | if callback_function not in self.default_handlers: 208 | self.default_handlers.append(callback_function) 209 | 210 | def remove_default_handler(self, callback_function: Callable) -> None: 211 | """ 212 | Removes a default handler from the device. 213 | 214 | Args: 215 | callback_function: A copy of the command to "callback". 216 | 217 | """ 218 | if callback_function in self.default_handlers: 219 | self.default_handlers.remove(callback_function) 220 | 221 | # 222 | def set_command_header(self, cmd_header: str, add_delim: bool = True) -> None: 223 | """ 224 | Sets the header of the Arduino command. 225 | 226 | Args: 227 | cmd_header: The header of the command. 228 | 229 | add_delim: Adds a delimiter to the command, default set to True. 230 | 231 | """ 232 | self.cmd_header = cmd_header 233 | if add_delim: 234 | self.cmd_header += self.delim 235 | self.logger.debug('Set command header to "{}"'.format(self.cmd_header)) 236 | 237 | def set_command_decimal(self, cmd_decimal: int) -> None: 238 | """ 239 | Sets the decimal of the Arduino command. 240 | 241 | Args: 242 | cmd_decimal: The decimal of the command. 243 | 244 | """ 245 | self.cmd_decimal = cmd_decimal 246 | self.logger.debug('Set decimal to "{}"'.format(self.cmd_decimal)) 247 | 248 | def forge_command(self, command_id: str, *args) -> str: 249 | """ 250 | Creates a full Arduino command. 251 | 252 | Args: 253 | command_id: The ID of the command. 254 | 255 | *args: Variable length argument list. 256 | 257 | """ 258 | cmd = self.cmd_header 259 | cmd += command_id 260 | for arg in args: 261 | cmd += self.delim 262 | if type(arg) == float: 263 | cmd += str(round(arg, self.cmd_decimal)) 264 | else: 265 | cmd += str(arg) 266 | cmd += self.term 267 | 268 | self.logger.debug('Forged "{}"'.format(cmd), 269 | extra={'command_id': command_id, 270 | 'arguments': args}) 271 | 272 | return cmd 273 | 274 | 275 | class SerialCommandHandler(threading.Thread, CommandHandler): 276 | """ 277 | Represents the Command Handler which will handle commands to/from the Arduino hardware via Serial Communication. 278 | 279 | Args: 280 | port: The port to communicate over. 281 | 282 | baudrate: The baudrate of the serial communication, default set to DEFAULT_BAUDRATE (115200) 283 | 284 | timeout: The time to wait for timeout, default set to DEFAULT_TIMEOUT (0.01) 285 | 286 | delim: The delimiting character of a command, default set to DEFAULT_DELIM (',') 287 | 288 | term: The terminal character of a command, default set to DEFAULT_TERM (';') 289 | 290 | cmd_decimal: The decimal of the command, default set to DEFAULT_CMD_DECIMAL (2) 291 | 292 | """ 293 | def __init__(self, port: str, baudrate: int = DEFAULT_BAUDRATE, timeout: float = DEFAULT_TIMEOUT, 294 | delim: str = DEFAULT_DELIM, term: str = DEFAULT_TERM, cmd_decimal: int = DEFAULT_CMD_DECIMAL): 295 | threading.Thread.__init__(self) 296 | self.daemon = True 297 | self.interrupted = threading.Lock() 298 | 299 | self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) 300 | 301 | CommandHandler.__init__(self, delim, term, cmd_decimal) 302 | self.name = port 303 | self.open(port, baudrate, timeout) 304 | 305 | def open(self, port: str, baudrate: int, timeout: float) -> None: 306 | """ 307 | Opens the serial communication between the PC and Arduino board. 308 | 309 | Args: 310 | port: The port to communicate over. 311 | 312 | baudrate: The baudrate of serial communication. 313 | 314 | timeout: The time to wait for timeout. 315 | 316 | """ 317 | self.logger.debug('Opening port {}'.format(port), 318 | extra={'port': port, 319 | 'baudrate': baudrate, 320 | 'timeout': timeout}) 321 | try: 322 | self._serial = serial.Serial(port, baudrate, timeout=timeout) 323 | except (serial.SerialException, TypeError, ValueError) as e: 324 | raise CMHandlerConfigurationError(str(e)) 325 | 326 | def close(self) -> None: 327 | """ 328 | Closes the serial communication between the PC and Arduino board. 329 | """ 330 | if hasattr(self, "_serial"): 331 | self._serial.close() 332 | self.logger.debug('Closing port "{}"'.format(self._serial.port)) 333 | 334 | def __del__(self): 335 | """ 336 | Closes the serial communication between the PC and Arduino board. 337 | """ 338 | self.close() 339 | 340 | def __exit__(self, exc_type, exc_value, traceback): 341 | """ 342 | Closes the serial communication between the PC and Arduino Board, due to an exception occurrence. 343 | 344 | Args: 345 | exc_type (str): The exception type. 346 | 347 | exc_value: The value of the exception. 348 | 349 | traceback (str): The position in the code where the exception occurred. 350 | 351 | """ 352 | self.close() 353 | 354 | def stop(self) -> None: 355 | """ 356 | Releases the lock when signalled via an interrupt. 357 | """ 358 | self.interrupted.release() 359 | 360 | def run(self) -> None: 361 | """ 362 | Starts the Handler processing commands. 363 | """ 364 | self.interrupted.acquire() 365 | while self.interrupted.locked(): 366 | try: 367 | self.process_serial(self._serial) 368 | except (serial.SerialException, serial.SerialTimeoutException) as e: 369 | raise CMTimeout(f"Error reading from serial port {self._serial.port}! {e}") from None 370 | self.close() 371 | 372 | def send(self, command_id: str, *arg) -> None: 373 | """ 374 | Sends a command over the serial communication. 375 | 376 | Args: 377 | command_id (str): The ID of the command. 378 | 379 | *arg: Variable argument. 380 | 381 | """ 382 | self.write(self.forge_command(command_id, *arg)) 383 | 384 | def write(self, msg: str) -> None: 385 | """ 386 | Writes a message over the serial communication. 387 | 388 | Args: 389 | msg (str): The message to send. 390 | 391 | """ 392 | self.logger.debug('Sending "{}" on port "{}"'.format(msg, self._serial.port)) 393 | try: 394 | self._serial.write(msg.encode()) 395 | except serial.SerialException as e: 396 | raise CMCommunicationError(f"Error writing to serial port {self._serial.port}! {e}") from None 397 | 398 | def process_serial(self, a_serial: serial.Serial) -> None: 399 | """ 400 | Processes the serial communication to obtain data to be processed. 401 | 402 | Args: 403 | a_serial: The serial to read from. 404 | 405 | """ 406 | self.process_char(a_serial.read(1)) 407 | 408 | def wait_until_running(self, sleep_time: float = 0.01) -> None: 409 | """ 410 | Waits until the current thread is running. 411 | 412 | Args: 413 | sleep_time (float): The time to wait for, default set to 0.01 414 | 415 | """ 416 | while not self.interrupted.locked(): 417 | time.sleep(sleep_time) 418 | 419 | 420 | class TCPIPCommandHandler(threading.Thread, CommandHandler): 421 | """ 422 | Represents the Command Handler which will handle commands to/from the Arduino hardware via TCP/IP socket. 423 | 424 | Args: 425 | port: The TCP/UDP port to communicate over. 426 | 427 | address: The IP address of the device. 428 | 429 | protocol: Either tcp or udp, default set to TCP. 430 | 431 | timeout: The time to wait for timeout, default set to DEFAULT_TIMEOUT (0.01) 432 | 433 | delim: The delimiting character of a command, default set to DEFAULT_DELIM (',') 434 | 435 | term: The terminal character of a command, default set to DEFAULT_TERM (';') 436 | 437 | cmd_decimal: The decimal of the command, default set to DEFAULT_CMD_DECIMAL (2) 438 | 439 | """ 440 | def __init__(self, port: str, address: str, protocol: str = "TCP", timeout: float = DEFAULT_TIMEOUT, 441 | delim: str = DEFAULT_DELIM, term: str = DEFAULT_TERM, cmd_decimal: int = DEFAULT_CMD_DECIMAL): 442 | threading.Thread.__init__(self) 443 | self.daemon = True 444 | self.interrupted = threading.Event() 445 | self.interrupted.clear() 446 | 447 | self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) 448 | 449 | CommandHandler.__init__(self, delim, term, cmd_decimal) 450 | 451 | self.name = address + ":" + port 452 | 453 | self._connection: socket.socket = None # type: ignore 454 | 455 | self.open(port, address, protocol.upper(), timeout) 456 | 457 | def open(self, port: str, address: str, protocol: str, timeout: float): 458 | """ 459 | Opens the TCP/IP communication between the PC and Arduino board. 460 | 461 | Args: 462 | port: The port to communicate over. 463 | 464 | address: The IP address of the device. 465 | 466 | protocol: Protocol to use - TCP or UDP 467 | 468 | timeout: The time to wait for timeout. 469 | 470 | """ 471 | self.logger.debug('Opening connection to %s:%s (%s)', address, port, protocol) 472 | 473 | try: 474 | if protocol == "TCP": 475 | self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 476 | elif protocol == "UDP": 477 | self._connection = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 478 | else: 479 | raise CMHandlerConfigurationError(f"Unknown transport layer protocol <{protocol}> provided!") 480 | self._connection.connect((address, int(port))) 481 | # Receive timeout in seconds. 482 | # This has to be done after opening the connection to avoid connect() error on non-blocking socket 483 | self._connection.settimeout(timeout) 484 | except TimeoutError as e: 485 | raise CMHandlerConfigurationError(f"Socket timeout! {e}") 486 | except (OSError, TypeError, ValueError) as e: 487 | raise CMHandlerConfigurationError(f"Can't open socket! {e}") 488 | 489 | def close(self) -> None: 490 | """ 491 | Closes the communication between the PC and Arduino board. 492 | """ 493 | self._connection.close() 494 | self.logger.debug("Connection closed.") 495 | 496 | def __del__(self): 497 | """ 498 | Closes the serial communication between the PC and Arduino board. 499 | """ 500 | self.close() 501 | 502 | def __exit__(self, exc_type, exc_value, traceback): 503 | """ 504 | Closes the serial communication between the PC and Arduino Board, due to an exception occurrence. 505 | 506 | Args: 507 | exc_type (str): The exception type. 508 | 509 | exc_value: The value of the exception. 510 | 511 | traceback (str): The position in the code where the exception occurred. 512 | 513 | """ 514 | self.close() 515 | 516 | def stop(self) -> None: 517 | """ 518 | Releases the lock when signalled via an interrupt. 519 | """ 520 | self.interrupted.set() 521 | 522 | def run(self) -> None: 523 | """ 524 | Starts the Handler processing commands. 525 | """ 526 | while not self.interrupted.is_set(): 527 | self.process_data() 528 | self.close() 529 | 530 | def send(self, command_id: str, *arg) -> None: 531 | """ 532 | Sends a command over the TCP/IP connection. 533 | 534 | Args: 535 | command_id: The ID of the command. 536 | 537 | *arg: Variable argument. 538 | 539 | """ 540 | try: 541 | self.write(self.forge_command(command_id, *arg)) 542 | except OSError as e: 543 | raise CMCommunicationError(f"Error writing to socket! {e}") from None 544 | 545 | def write(self, msg: str) -> None: 546 | """ 547 | Writes raw data into the socket. 548 | 549 | Args: 550 | msg: The message to send. 551 | 552 | """ 553 | self.logger.debug('Sending "%s" to "%s"', msg, self._connection.getpeername()) 554 | self._connection.send(msg.encode()) 555 | 556 | def process_data(self) -> None: 557 | """ 558 | Gets the data from socket to be processed. 559 | """ 560 | try: 561 | self.process_char(self._connection.recv(1)) 562 | except socket.timeout: 563 | pass 564 | except OSError as e: 565 | raise CMCommunicationError(f"Error reading from socket! {e}") 566 | 567 | def wait_until_running(self) -> None: 568 | """ 569 | Waits until the current thread is running. 570 | """ 571 | self.interrupted.wait() 572 | 573 | 574 | # Typing variable for either Serial or TCPIP CommandHandler 575 | GenericCommandHandler = Union[SerialCommandHandler, TCPIPCommandHandler] 576 | -------------------------------------------------------------------------------- /commanduino/commandmanager.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: commandmanager 3 | :platform: Unix 4 | :synopsis: Module to manage various Command Handlers. 5 | 6 | .. moduleauthor:: Jonathan Grizou 7 | 8 | """ 9 | from .commandhandler import SerialCommandHandler 10 | from .commandhandler import TCPIPCommandHandler 11 | 12 | from .commanddevices.register import create_and_setup_device 13 | from .commanddevices.register import DEFAULT_REGISTER 14 | 15 | from .exceptions import (CMManagerConfigurationError, 16 | CMHandlerConfigurationError, 17 | CMHandlerDiscoveryTimeout, 18 | CMDeviceConfigurationError, 19 | CMBonjourTimeout, 20 | CMDeviceDiscoveryTimeout, 21 | CMDeviceRegisterError, 22 | CMCommunicationError) 23 | 24 | from .lock import Lock 25 | 26 | from .commanddevices import CommandDevice 27 | 28 | import time 29 | import json 30 | import logging 31 | from typing import Optional, Any 32 | 33 | from typing import Dict, List, Tuple 34 | from commanduino.commandhandler import GenericCommandHandler 35 | 36 | # Default initialisation timeout 37 | DEFAULT_INIT_TIMEOUT = 1 38 | 39 | # Default amount of times to attempt initialisation 40 | DEFAULT_INIT_N_REPEATS = 5 41 | 42 | # Default timeout value 43 | DEFAULT_BONJOUR_TIMEOUT = 0.1 44 | 45 | COMMAND_BONJOUR = 'BONJOUR' 46 | COMMAND_IS_INIT = 'ISINIT' 47 | COMMAND_INIT = 'INIT' 48 | 49 | 50 | class CommandManager(object): 51 | """ 52 | Manages varying amounts of Command Handler objects. 53 | 54 | Args: 55 | command_configs: Collection of command configurations. 56 | 57 | devices_dict: Dictionary containing the list of devices. 58 | 59 | init_timeout: Initialisation timeout, default set to DEFAULT_INIT_TIMEOUT (1). 60 | 61 | init_n_repeats: Number of times to attempt initialisation, default set to DEFAULT_INIT_N_REPEATS (5). 62 | """ 63 | def __init__(self, command_configs: List[Dict], devices_dict: Dict, init_timeout: float = DEFAULT_INIT_TIMEOUT, 64 | init_n_repeats: int = DEFAULT_INIT_N_REPEATS, simulation: bool = False): 65 | self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) 66 | 67 | self._simulation = simulation 68 | 69 | self.initialised = False 70 | self.init_n_repeats = init_n_repeats 71 | self.init_lock = Lock(init_timeout) 72 | 73 | self.commandhandlers: List[GenericCommandHandler] = [] 74 | if not self._simulation: 75 | for config_entry in command_configs: 76 | # Create handler from config & initialize device 77 | self.add_command_handler(config_entry) 78 | else: 79 | self.logger.info("Simulation mode, skipping handlers creation.") 80 | self.commandhandlers = command_configs # type: ignore 81 | 82 | self.devices: Dict[str, Any] = {} 83 | self.register_all_devices(devices_dict) 84 | self.set_devices_as_attributes() 85 | self.initialised = True 86 | 87 | def add_command_handler(self, handler_config: Dict) -> None: 88 | """Creates command handler from the configuration dictionary, tests connection 89 | and appends instance to self.commandhandlers 90 | 91 | Args: 92 | handler_config: Handler configuration dictionary. 93 | """ 94 | if self._simulation: 95 | self.logger.info("Simulation mode, skipping handlers addition.") 96 | self.commandhandlers.append(handler_config) # type: ignore 97 | return None 98 | # Make a copy not to mutate original dict - might be re-used 99 | # by upper-level code for object re-creation. 100 | handler_config = handler_config.copy() 101 | # Check if type is present, if not - log and fall back to serial 102 | # for backwards compatibility reasons. 103 | if "type" in handler_config: 104 | handler_type = handler_config["type"] 105 | # Remove connection type from config dict copy - not needed any more 106 | handler_config.pop("type") 107 | else: 108 | self.logger.warning("No command handler type provided in configuration. Falling back to serial.") 109 | handler_type = "serial" 110 | # Get full name - for use in logging 111 | device_name = handler_config.get("address", "") + ":" + handler_config.get("port", "") 112 | # Check if handler is required & remove the key from dict 113 | required = handler_config.get("required", False) 114 | handler_config.pop("required", None) 115 | 116 | try: 117 | if handler_type == "serial": 118 | handler = SerialCommandHandler.from_config(handler_config) 119 | self.logger.info("Created serial-based command handler for %s.", device_name) 120 | elif handler_type == "tcpip": 121 | handler = TCPIPCommandHandler.from_config(handler_config) 122 | self.logger.info("Created socket-based command handler for %s.", device_name) 123 | except CMHandlerConfigurationError as e: 124 | if required: 125 | raise CMHandlerConfigurationError(f"Error initializing device {device_name}: {e}") from None 126 | else: 127 | self.logger.warning(f"I/O device {device_name} (type {handler_type}) was not found") 128 | self.logger.warning("Additional error message: %s", e) 129 | else: 130 | # If CommandHandler creation succeeded, add callback, start handler and initialize it 131 | assert isinstance(handler, (SerialCommandHandler, TCPIPCommandHandler)) 132 | handler.add_default_handler(self.unrecognized) 133 | handler.start() 134 | try: 135 | elapsed = self.wait_device_for_init(handler) 136 | self.logger.info(f"Found Arduino CommandManager at {device_name}, init time was {elapsed:.3} seconds") 137 | except CMHandlerDiscoveryTimeout: 138 | self.logger.warning(f"Arduino CommandManager at {device_name} has not initialized") 139 | else: 140 | # Update handlers list 141 | self.commandhandlers.append(handler) 142 | 143 | def remove_command_handler(self, handler_to_remove: GenericCommandHandler) -> None: 144 | """ 145 | Deletes the command handler object & removes a reference to it from 146 | class dictionary. Then deletes the devices bound to the handler being deleted. 147 | 148 | The method to detect which devices depend on the handler being deleted 149 | may seem wanky because it is. 150 | However, it is the only possible method as every device internal 151 | CommandHandler doesn't hold a reference to an actual command handler. 152 | The only thing having this reference is the device's write() function 153 | which gets updated on create_and_setup_device() 154 | """ 155 | if self._simulation: 156 | self.logger.info("Simulation mode, skipping handlers removal.") 157 | return None 158 | if handler_to_remove not in self.commandhandlers: 159 | self.logger.warning("Command handler %s not found!", handler_to_remove) 160 | return 161 | self.logger.info("Removing devices...") 162 | # Find the dependent devices to remove (i.e. the devices using that handler) 163 | for name, dev in self.devices.copy().items(): 164 | # write.__self__ is the CommandHandler instance owning the write function set to CommandDevice.write 165 | if handler_to_remove is dev.write.__self__: # type: ignore 166 | self.logger.info("Removing dependent device %s", name) 167 | self.unregister_device(name) 168 | # Remove handler 169 | self.logger.info("Removing command handler...") 170 | self.commandhandlers.remove(handler_to_remove) 171 | 172 | def set_devices_as_attributes(self) -> None: 173 | """ 174 | Sets the list of devices as attributes. 175 | """ 176 | for device_name, device in list(self.devices.items()): 177 | if hasattr(self, device_name): 178 | self.logger.warning(f"Device named {device_name} is already a reserved attribute! " 179 | f"Please change name or do not use this device in attribute mode, " 180 | f"rather use devices[{device_name}]") 181 | else: 182 | setattr(self, device_name, device) 183 | 184 | def handle_init(self, *arg) -> None: 185 | """ 186 | Handles the initialisation of the Manager, ensuring that the threading locks are released. 187 | 188 | Args: 189 | *arg: Variable argument. 190 | """ 191 | if arg[0] and bool(int(arg[0])): 192 | self.init_lock.ensure_released() 193 | 194 | def request_init(self, handler: GenericCommandHandler) -> None: 195 | """ 196 | Requests initialisation over communication link. 197 | 198 | Args: 199 | handler: Command Handler object for communication. 200 | 201 | """ 202 | handler.send(COMMAND_IS_INIT) 203 | 204 | def request_and_wait_for_init(self, handler: GenericCommandHandler) -> Tuple[bool, float]: 205 | """ 206 | Requests initialisation and waits until it obtains a threading lock. 207 | 208 | Args: 209 | handler: Command Handler object for communication. 210 | 211 | """ 212 | start_time = time.time() 213 | 214 | self.init_lock.acquire() 215 | for _ in range(self.init_n_repeats): 216 | self.request_init(handler) 217 | is_init, _ = self.init_lock.wait_until_released() 218 | if is_init: 219 | break 220 | self.init_lock.ensure_released() 221 | 222 | elapsed = time.time() - start_time 223 | return is_init, elapsed 224 | 225 | def wait_device_for_init(self, handler: GenericCommandHandler) -> float: 226 | """ 227 | Waits for initialisation using communication link. 228 | 229 | Args: 230 | handler: Command Handler object to add/remove commands. 231 | 232 | Returns: 233 | elapsed: Time waited for initialisation. 234 | 235 | Raises: 236 | CMHandlerDiscoveryTimeout: CommandManager on the port was not initialised. 237 | 238 | """ 239 | self.logger.debug('Waiting for device at %s to init...', handler.name) 240 | 241 | handler.add_command(COMMAND_INIT, self.handle_init) 242 | try: 243 | is_init, elapsed = self.request_and_wait_for_init(handler) 244 | except CMCommunicationError: 245 | raise CMHandlerDiscoveryTimeout(handler.name) 246 | handler.remove_command(COMMAND_INIT, self.handle_init) 247 | if is_init: 248 | return elapsed 249 | raise CMHandlerDiscoveryTimeout(handler.name) 250 | 251 | def register_all_devices(self, devices_dict: Dict) -> None: 252 | """ 253 | Registers all available Arduino devices. 254 | 255 | Args: 256 | devices_dict: Dictionary containing all devices. 257 | 258 | """ 259 | for device_name, device_info in devices_dict.items(): 260 | try: 261 | self.register_device(device_name, device_info) 262 | except CMDeviceConfigurationError as e: 263 | self.logger.error(e) 264 | 265 | def register_device(self, device_name: str, device_info: Dict) -> None: 266 | """ 267 | Registers an individual Arduino device. 268 | 269 | Args: 270 | device_name: Name of the device. 271 | 272 | device_info: Dictionary containing the device information. 273 | 274 | Raises: 275 | CMDeviceRegisterError: Device is not in the device register. 276 | 277 | CMBonjourError: Device has not been found. 278 | 279 | """ 280 | 281 | # Command ID must contain a valid string 282 | command_id = device_info.get('command_id', "") 283 | if command_id == "": 284 | raise CMDeviceConfigurationError(f"Invalid or missing 'command_id' in {device_name} configuration!") 285 | 286 | # Configuration is optional 287 | device_config = device_info.get("config", {}) 288 | 289 | if not self._simulation: 290 | # Look for device via bonjour on all handlers 291 | try: 292 | bonjour_service = CommandBonjour(self.commandhandlers) 293 | handler, bonjour_id, elapsed = bonjour_service.detect_device(command_id) 294 | self.logger.debug(f"Device '{device_name}' (ID=<{command_id}> type=<{bonjour_id}>) found on {handler.name}") 295 | except CMBonjourTimeout: 296 | raise CMDeviceDiscoveryTimeout(f"Device '{device_name}' (ID=<{command_id}>) has not been found!") from None 297 | 298 | # Initialise device 299 | try: 300 | device = create_and_setup_device(handler, command_id, bonjour_id, device_config) 301 | self.logger.debug(f"Device '{device_name}' created! (ID=<{command_id}> type=<{bonjour_id}> handler=" 302 | f"<{handler.name}> detection time {elapsed:.3f} s)") 303 | except CMDeviceRegisterError: 304 | device = create_and_setup_device(handler, command_id, DEFAULT_REGISTER, device_config) 305 | self.logger.warning(f"Device '{device_name}' NOT found in the register! Initialized as blank minimal object" 306 | f"! (ID=<{command_id}> type=<{bonjour_id}> handler=<{handler.name}>)") 307 | self.devices[device_name] = device 308 | else: 309 | device = VirtualDevice(device_name, device_config) 310 | self.devices[device_name] = device 311 | 312 | def unregister_device(self, device_name: str) -> None: 313 | """ 314 | Removes device attribute & reference from devices dictionary 315 | """ 316 | # Remove device attribute from self 317 | delattr(self, device_name) 318 | # Remove reference from device list 319 | self.devices.pop(device_name) 320 | 321 | @classmethod 322 | def from_config(cls, config) -> 'CommandManager': 323 | """ 324 | Obtains the necessary information from a configuration setup. 325 | 326 | Args: 327 | cls (CommandManager): The instantiating class. 328 | 329 | config (Dict): Dictionary containing the configuration data. 330 | 331 | """ 332 | try: 333 | command_configs = config["ios"] 334 | devices = config["devices"] 335 | except KeyError as e: 336 | raise CMManagerConfigurationError(f"Invalid configuration provided: missing {e} in dict!") from None 337 | sim = config.get("simulation", False) 338 | return cls(command_configs, devices, simulation=sim) 339 | 340 | @classmethod 341 | def from_configfile(cls, configfile: str, simulation: Optional[bool] = None) -> 'CommandManager': 342 | """ 343 | Obtains the configuration data from a configuration file. 344 | 345 | Args: 346 | cls (CommandManager): The instantiating class. 347 | 348 | configfile (File): The configuration file. 349 | 350 | simulation (bool): True if simulation mode is needed. 351 | 352 | """ 353 | try: 354 | with open(configfile) as f: 355 | config_dict = json.load(f) 356 | except (FileNotFoundError, json.decoder.JSONDecodeError) as e: 357 | # Printing "e" as well as it holds info on the line/column where the error occurred 358 | raise CMManagerConfigurationError(f"The JSON file provided {configfile} is invalid!\n{e}") from None 359 | # Parameter overrides config file only if provided 360 | if simulation is not None: 361 | config_dict["simulation"] = simulation 362 | return cls.from_config(config_dict) 363 | 364 | def unrecognized(self, cmd: str) -> None: 365 | """ 366 | Received command is unrecognised. 367 | 368 | Args: 369 | cmd (str): The received command. 370 | 371 | """ 372 | # Do not print out unrecognized error during init. as those are "normal" when multiple Arduinos are connected 373 | if self.initialised: 374 | self.logger.warning('Received unknown command "{}"'.format(cmd)) 375 | 376 | 377 | class CommandBonjour(object): 378 | """ 379 | Represents a Command Manager for Bonjour devices. 380 | 381 | Args: 382 | commandhandlers: Collection of CommandHandler objects. 383 | 384 | timeout: Time to wait before timeout, default set to DEFAULT_BONJOUR_TIMEOUT (0.1) 385 | 386 | """ 387 | def __init__(self, commandhandlers: List[GenericCommandHandler], 388 | timeout: float = DEFAULT_BONJOUR_TIMEOUT): 389 | self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) 390 | 391 | self.commandhandlers = commandhandlers 392 | self.lock = Lock(timeout) 393 | # Init bonjour info 394 | self.device_bonjour_id = '' 395 | self.device_bonjour_id_valid = False 396 | 397 | def clear_bonjour_info(self) -> None: 398 | """ 399 | Initialises the Bonjour information. 400 | """ 401 | self.device_bonjour_id = '' 402 | self.device_bonjour_id_valid = False 403 | 404 | def handle_bonjour(self, *arg) -> None: 405 | """ 406 | Handles the Bonjour initialisation. 407 | 408 | Args: 409 | *arg: Variable argument. 410 | """ 411 | if arg[0]: 412 | self.device_bonjour_id = arg[0] 413 | self.device_bonjour_id_valid = True 414 | self.lock.ensure_released() 415 | 416 | def send_bonjour(self, handler: GenericCommandHandler, command_id: str) -> None: 417 | """ 418 | Sends a message to the device. 419 | 420 | .. todo:: Fix this up 421 | 422 | Args: 423 | handler: The Command Handler object. 424 | 425 | command_id: The ID of the command. 426 | 427 | """ 428 | handler.send(command_id, COMMAND_BONJOUR) 429 | 430 | def get_bonjour_id(self, handler: GenericCommandHandler, command_id: str) -> Tuple[str, bool, float]: 431 | """ 432 | Obtains the device's bonjour ID. 433 | 434 | Args: 435 | handler: The Command Handler object. 436 | 437 | command_id: The ID of the command. 438 | 439 | Returns: 440 | self.device_bonjour_id: The Bonjour ID. 441 | 442 | is_valid: Validity of the ID. 443 | 444 | elapsed: Time elapsed since request. 445 | 446 | """ 447 | self.lock.acquire() 448 | self.clear_bonjour_info() 449 | self.send_bonjour(handler, command_id) 450 | is_valid, elapsed = self.lock.wait_until_released() 451 | self.lock.ensure_released() 452 | return self.device_bonjour_id, is_valid, elapsed 453 | 454 | def detect_device(self, command_id: str) -> Tuple[GenericCommandHandler, str, float]: 455 | """ 456 | Attempts to detect the Bonjour device. 457 | 458 | Args: 459 | command_id: The ID of the command. 460 | 461 | Returns: 462 | handler: TheCommand Handler object. 463 | 464 | bonjour_id: The Bonjour ID. 465 | 466 | elapsed: Time elapsed since request. 467 | 468 | """ 469 | for handler in self.commandhandlers: 470 | self.logger.debug('Scanning for "%s" at "%s"...', command_id, handler.name) 471 | 472 | handler.add_relay(command_id, handler.handle) 473 | handler.add_command(COMMAND_BONJOUR, self.handle_bonjour) 474 | bonjour_id, is_valid, elapsed = self.get_bonjour_id(handler, command_id) 475 | handler.remove_command(COMMAND_BONJOUR, self.handle_bonjour) 476 | handler.remove_relay(command_id, handler.handle) 477 | if is_valid: 478 | return handler, bonjour_id, elapsed 479 | raise CMBonjourTimeout(command_id) 480 | 481 | 482 | class VirtualAttribute: 483 | """ Callable attribute for virtual device 484 | """ 485 | def __call__(self, *args, **kwargs): 486 | # Make nice arguments string for logging 487 | args = ", ".join([str(arg) for arg in args]) 488 | if args and kwargs: 489 | args += "," 490 | kwargs = ", ".join([str(k)+"="+str(v) for k, v in kwargs.items()]) 491 | self.logger.info("Virtual call %s(%s%s)", self.name, args, kwargs) 492 | 493 | def __init__(self, name, logger): 494 | self.name = name 495 | self.logger = logger 496 | self.logger.info("Created virtual method %s()", name) 497 | 498 | 499 | class VirtualDevice: 500 | """ Virtual device mock to replace normal devices in simulation mode 501 | """ 502 | def __getattr__(self, name): 503 | 504 | # Check if we already have this attribute defined, 505 | # e.g. from device_config during __init__() 506 | # or previous __setattr__() calls 507 | if name in self.__dict__: 508 | return self.__dict__[name] 509 | # Otherwise create a virtual callable 510 | self.__dict__[name] = VirtualAttribute(name, self.__dict__["logger"]) 511 | return self.__dict__[name] 512 | 513 | def __setattr__(self, name, value): 514 | 515 | # Check if we already have this attribute defined, 516 | # e.g. from device_config during __init__() 517 | # or previous __setattr__() calls 518 | if name not in self.__dict__: 519 | self.__dict__["logger"].debug("Creating virtual attribute %s=%s", name, value) 520 | elif callable(self.__dict__[name]): 521 | self.logger.warning("Redefining virtual method %s with virtual attribute", self.__dict__[name]) 522 | 523 | self.__dict__[name] = value 524 | 525 | def __init__(self, name=None, device_info = None): 526 | 527 | # Setup logger 528 | self.__dict__["logger"] = logging.getLogger(__name__).getChild(self.__class__.__name__) 529 | 530 | # Populate device configuration, if present 531 | # These are static non-callable attributes 532 | if device_info is not None: 533 | for k, v in device_info.items(): 534 | self.__dict__[k] = v 535 | self.__dict__["logger"].info("Created virtual device %s", name) 536 | -------------------------------------------------------------------------------- /commanduino/devices/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/commanduino/devices/__init__.py -------------------------------------------------------------------------------- /commanduino/devices/axis.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: Axis 4 | :platform: Unix 5 | :synopsis: Represents an Axis which devices can move along 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | 11 | import logging 12 | 13 | 14 | class Axis(object): 15 | """ 16 | Represents a singular Axis which devices can move along. 17 | 18 | Args: 19 | linear_actuator: The linear actuator which moves along the axis. 20 | 21 | unit_per_step Union(int, float): Amount of units to move per step, default set to 1 22 | 23 | min_position Union(int, float): Minimum position of the axis (in unit), default set 24 | to 0 25 | 26 | max_position Union(int, float, str): The maximum position of the axis (in unit), cast to 27 | (float) 28 | 29 | """ 30 | 31 | def __init__(self, linear_actuator, unit_per_step=1, min_position=0, 32 | max_position=float('inf')): 33 | self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__) 34 | self.linear_actuator = linear_actuator 35 | self.unit_per_step = float(unit_per_step) 36 | self.min_position = float(min_position) 37 | self.max_position = float(max_position) 38 | self.initialized = False 39 | 40 | def initialize(self): 41 | """ 42 | Initialises the axis. 43 | """ 44 | 45 | self.home(wait=True) 46 | 47 | def is_initialized(self): 48 | """ 49 | Check for initialisation. 50 | 51 | Returns: 52 | self.initialized (bool): Initialisation status 53 | 54 | """ 55 | 56 | return self.initialized 57 | 58 | def position_to_step(self, position_in_unit): 59 | """ 60 | Converts position to steps. 61 | 62 | Args: 63 | position_in_unit (int): Position in Units. 64 | 65 | Returns: 66 | n_steps (int): Number of steps. 67 | 68 | """ 69 | 70 | n_steps = position_in_unit / self.unit_per_step 71 | return int(round(n_steps)) 72 | 73 | def step_to_position(self, n_steps): 74 | """ 75 | Converts steps to position. 76 | 77 | Args: 78 | n_steps (int): Number of steps. 79 | 80 | Returns: 81 | n_steps * units_per_step 82 | 83 | """ 84 | 85 | return n_steps * self.unit_per_step 86 | 87 | def cast_position(self, position_in_unit): 88 | """ 89 | Casts the position on the axis. 90 | 91 | Args: 92 | position_in_unit (int): Position in units. 93 | 94 | Returns: 95 | casted_position (int): The casted position. 96 | 97 | """ 98 | 99 | casted_position = min(self.max_position, position_in_unit) 100 | casted_position = max(self.min_position, casted_position) 101 | if casted_position != position_in_unit: 102 | self.logger.warning("The position requested ({}) is outside the axis boundary!".format(position_in_unit)) 103 | return casted_position 104 | 105 | def is_moving(self): 106 | """ 107 | Check for axis movement. 108 | 109 | Returns: 110 | self.linear_actuator.is_moving (bool): The actuator movement 111 | status. 112 | 113 | """ 114 | 115 | return self.linear_actuator.is_moving() 116 | 117 | def wait_until_idle(self): 118 | """ 119 | Waits until the linear actuator is idle. 120 | """ 121 | 122 | self.linear_actuator.wait_until_idle() 123 | 124 | def home(self, wait=True): 125 | """ 126 | Returns the actuator to the home position. 127 | 128 | Args: 129 | wait (bool): Wait until the actuator is idle, default set to True 130 | 131 | """ 132 | 133 | self.linear_actuator.home(wait=wait) 134 | self.initialized = True 135 | 136 | def move_to(self, position_in_unit, wait=True, force=False): 137 | """ 138 | Moves the linear actuator to a set position. 139 | 140 | Args: 141 | position_in_unit (Union(int, list)): Position to move to. 142 | 143 | wait (bool): Wait until the actuator is idle, default set to True. 144 | 145 | force (bool): Force the movement, default set to False. 146 | 147 | """ 148 | 149 | if self.is_initialized() or force: 150 | if type(position_in_unit) == list: 151 | position_in_unit = position_in_unit[0] 152 | position = self.cast_position(position_in_unit) 153 | n_steps = self.position_to_step(position) 154 | self.linear_actuator.move_to(n_steps, wait=wait) 155 | 156 | def move(self, delta_position_in_unit, wait=True, force=False): 157 | """ 158 | Moves the linear actuator. 159 | 160 | Args: 161 | delta_position_in_unit (int): The amount to move. 162 | 163 | wait (bool): Wait until the linear actuator is idle, default set to 164 | True. 165 | 166 | force (bool):Force the movement, default set to False. 167 | 168 | """ 169 | 170 | current_position = self.get_current_position() 171 | self.move_to(current_position + delta_position_in_unit, wait=wait, 172 | force=force) 173 | 174 | def get_current_position(self): 175 | """ 176 | Gets the current position on the axis. 177 | 178 | Returns: 179 | self.step_to_position (int): THe position of the linear actuator. 180 | 181 | """ 182 | 183 | n_steps = self.linear_actuator.get_current_position() 184 | return self.step_to_position(n_steps) 185 | 186 | def get_switch_state(self): 187 | """ 188 | Gets the switch state of the linear actuator. 189 | 190 | Returns: 191 | The switch state of the linear actuator. 192 | 193 | """ 194 | 195 | return self.linear_actuator.get_switch_state() 196 | 197 | def stop(self): 198 | """ 199 | Stops the linear actuator. 200 | """ 201 | self.linear_actuator.stop() 202 | 203 | 204 | class MultiAxis(object): 205 | """ 206 | Represents a collection of Axis objects which device can move along. 207 | 208 | Args: 209 | *args: Variable argument list. 210 | 211 | """ 212 | 213 | def __init__(self, *args): 214 | self.axes = [] 215 | for arg in args: 216 | self.axes.append(arg) 217 | self.initialized = False 218 | 219 | def initialize(self): 220 | """ 221 | Initialises the set of axes. 222 | """ 223 | self.home(wait=True) 224 | 225 | def is_initialized(self): 226 | """ 227 | Gets the initialisation status of the devices. 228 | 229 | Returns: 230 | self.initialised (bool): The initialisation status. 231 | 232 | """ 233 | 234 | return self.initialized 235 | 236 | def is_moving(self): 237 | """ 238 | Check for movement along the axes. 239 | 240 | Returns: 241 | True (bool): The axes are moving. 242 | 243 | False (bool): The axes are not moving. 244 | 245 | """ 246 | 247 | for axis in self.axes: 248 | if axis.is_moving(): 249 | return True 250 | return False 251 | 252 | def wait_until_idle(self): 253 | """ 254 | Waits until the axes are idle. 255 | """ 256 | for axis in self.axes: 257 | axis.wait_until_idle() 258 | 259 | def home(self, wait=True): 260 | """ 261 | Returns the axes to their home position. 262 | 263 | Args: 264 | wait (bool): Wait until the axes are idle, default set to True. 265 | 266 | """ 267 | 268 | for axis in self.axes: 269 | axis.home(wait=False) 270 | if wait: 271 | self.wait_until_idle() 272 | self.initialized = True 273 | 274 | def move_to(self, position_array_in_unit, wait=True, force=False): 275 | """ 276 | Moves the axes to a set position. 277 | 278 | Args: 279 | position_array_in_unit (Union(int, tuple)): The position to move to. 280 | 281 | wait (bool): Wait until the axes are idle, default set to True. 282 | 283 | force (bool): Force the movement of the axes, default se tto False. 284 | 285 | """ 286 | 287 | for i, position_in_unit in enumerate(position_array_in_unit): 288 | self.axes[i].move_to(position_in_unit, wait=False, force=force) 289 | if wait: 290 | self.wait_until_idle() 291 | 292 | def move(self, position_array_in_unit, wait=True, force=False): 293 | """ 294 | Moves the axes. 295 | 296 | Args: 297 | position_array_in_unit (list): The amount to move. 298 | 299 | wait (bool): Wait until the axes are idle, default set to True. 300 | 301 | force (bool): Force the movement of the axes, default set to False. 302 | 303 | """ 304 | 305 | for i, position_in_unit in enumerate(position_array_in_unit): 306 | self.axes[i].move(position_in_unit, wait=False, force=force) 307 | if wait: 308 | self.wait_until_idle() 309 | 310 | def get_current_position(self): 311 | """ 312 | Gets the current position of the axes. 313 | 314 | Returns: 315 | position (List): List of the positions. 316 | 317 | """ 318 | 319 | position = [] 320 | for axis in self.axes: 321 | position.append(axis.get_current_position()) 322 | return position 323 | 324 | def get_switch_state(self): 325 | """ 326 | Gets the switch state of the axes. 327 | 328 | Returns: 329 | switch_state (List): List of the switch states. 330 | 331 | """ 332 | 333 | switch_state = [] 334 | for axis in self.axes: 335 | switch_state.append(axis.get_switch_state()) 336 | return switch_state 337 | 338 | def stop(self): 339 | """ 340 | Stops all movement in the axes. 341 | """ 342 | 343 | for axis in self.axes: 344 | axis.stop() 345 | -------------------------------------------------------------------------------- /commanduino/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: exceptions 3 | :platform: Unix 4 | :synopsis: Holds all the custom exceptions used in Commanduino. 5 | """ 6 | 7 | 8 | class CMError(Exception): 9 | """ Base catch-all""" 10 | 11 | 12 | class CMDeviceRegisterError(CMError): 13 | """ 14 | When the Bonjour ID is not in the register of the device. 15 | """ 16 | def __init__(self, bonjour_id): 17 | self.bonjour_id = bonjour_id 18 | 19 | def __str__(self): 20 | return '{self.bonjour_id} is not in the register of device'.format(self=self) 21 | 22 | 23 | class CMCommunicationError(CMError): 24 | """ 25 | Can't communicate to device - broken connection or device doesn't reply 26 | """ 27 | 28 | 29 | # CONFIGURATION ERRORS 30 | class CMConfigurationError(CMError): 31 | """ Base error for wrong configuration """ 32 | 33 | 34 | class CMDeviceConfigurationError(CMConfigurationError): 35 | """ Bad device config (e.g. missing command_id) """ 36 | 37 | 38 | class CMManagerConfigurationError(CMConfigurationError): 39 | """Manager can't init due to bad config (e.g. invalid JSON file or missing fields)""" 40 | 41 | 42 | class CMHandlerConfigurationError(CMConfigurationError): 43 | """ Handler can't init due to bad config (e.g. specified port cannot be opened) """ 44 | 45 | 46 | class CMTimeout(CMError): 47 | """All timeout related errors""" 48 | 49 | 50 | class CMHandlerDiscoveryTimeout(CMTimeout): 51 | """ 52 | Specified 'io' found but handler not created since no is_init reply was received 53 | """ 54 | def __init__(self, addr): 55 | self.addr = addr 56 | super().__init__() 57 | 58 | def __str__(self): 59 | return f"Manager at {self.addr} did not initialize.\nCheck that the right port has been used and reset Arduino." 60 | 61 | 62 | class CMBonjourTimeout(CMTimeout): 63 | """ 64 | Bonjour device is not available/not found. 65 | This is always catch and re-raised as CMDeviceDiscoveryTimeout including info on the device generating the error. 66 | """ 67 | 68 | def __init__(self, command_id): 69 | self.command_id = command_id 70 | super().__init__() 71 | 72 | def __str__(self): 73 | return f"{self.command_id} seems to not be existing/available!" 74 | 75 | 76 | class CMDeviceDiscoveryTimeout(CMError): 77 | """Specified device not found during bonjour""" 78 | 79 | 80 | class CMDeviceReplyTimeout(CMTimeout): 81 | """ 82 | Raised when the device does not respond to a command after a set time frame (default 1 sec). 83 | 84 | Args: 85 | device_name (str): The name of the device. 86 | command_name (str): The name of the command. 87 | elapsed (float): Time elapsed until the exception was thrown. 88 | """ 89 | def __init__(self, device_name, command_name, elapsed): 90 | self.device_name = device_name 91 | self.elapsed = elapsed 92 | self.command_name = command_name 93 | 94 | def __str__(self): 95 | return f"{self.device_name} did not respond to {self.command_name} within {self.elapsed:.3} s" 96 | -------------------------------------------------------------------------------- /commanduino/lock.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | .. module:: lock 4 | :platform: Unix 5 | :synopsis: Represents the specific threading functions to be carried out in this Library. 6 | 7 | .. moduleauthor:: Jonathan Grizou 8 | 9 | """ 10 | import time 11 | import threading 12 | 13 | 14 | class Lock(object): 15 | """ 16 | Represents the Threading locks to be used throughout this library. 17 | 18 | Args: 19 | timeout (int): Time to wait until timeout, default set to 1. 20 | 21 | sleep_time (float): Time to wait, default set to 0.001. 22 | 23 | """ 24 | def __init__(self, timeout=1, sleep_time=0.001): 25 | self.lock = threading.Lock() 26 | self.timeout = timeout 27 | self.sleep_time = sleep_time 28 | 29 | def acquire(self): 30 | """ 31 | Acquires a lock. 32 | """ 33 | return self.lock.acquire() 34 | 35 | def release(self): 36 | """ 37 | Releases a lock. 38 | """ 39 | self.lock.release() 40 | 41 | def locked(self): 42 | """ 43 | Checks if a thread is locked. 44 | 45 | Returns: 46 | self.lock.locked() (bool): Thread is locked/unlocked. 47 | """ 48 | return self.lock.locked() 49 | 50 | def wait_until_released(self): 51 | """ 52 | Waits until the thread lock is released. 53 | 54 | Returns: 55 | False (bool): Elapsed time > the timeout. 56 | 57 | True (bool): Elapsed time < timeout. 58 | 59 | elapsed (float): Time taken to complete. 60 | 61 | """ 62 | elapsed = 0 63 | start_time = time.time() 64 | while self.locked(): 65 | time.sleep(self.sleep_time) 66 | elapsed = time.time() - start_time 67 | if elapsed > self.timeout: 68 | return False, elapsed 69 | return True, elapsed 70 | 71 | def ensure_released(self): 72 | """ 73 | Forces unlocking of a locked thread. 74 | """ 75 | if self.locked(): 76 | self.release() 77 | -------------------------------------------------------------------------------- /commanduino/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/commanduino/py.typed -------------------------------------------------------------------------------- /docs/commanduino/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | GH_PAGES_SOURCES = source code commandduino Makefile 11 | 12 | # Internal variables. 13 | PAPEROPT_a4 = -D latex_paper_size=a4 14 | PAPEROPT_letter = -D latex_paper_size=letter 15 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | # the i18n builder cannot share the environment and doctrees with the others 17 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 18 | 19 | .PHONY: help 20 | help: 21 | @echo "Please use \`make ' where is one of" 22 | @echo " html to make standalone HTML files" 23 | @echo " dirhtml to make HTML files named index.html in directories" 24 | @echo " singlehtml to make a single large HTML file" 25 | @echo " pickle to make pickle files" 26 | @echo " json to make JSON files" 27 | @echo " htmlhelp to make HTML files and a HTML help project" 28 | @echo " qthelp to make HTML files and a qthelp project" 29 | @echo " applehelp to make an Apple Help Book" 30 | @echo " devhelp to make HTML files and a Devhelp project" 31 | @echo " epub to make an epub" 32 | @echo " epub3 to make an epub3" 33 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 34 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 35 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 36 | @echo " text to make text files" 37 | @echo " man to make manual pages" 38 | @echo " texinfo to make Texinfo files" 39 | @echo " info to make Texinfo files and run them through makeinfo" 40 | @echo " gettext to make PO message catalogs" 41 | @echo " changes to make an overview of all changed/added/deprecated items" 42 | @echo " xml to make Docutils-native XML files" 43 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 44 | @echo " linkcheck to check all external links for integrity" 45 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 46 | @echo " coverage to run coverage check of the documentation (if enabled)" 47 | @echo " dummy to check syntax errors of document sources" 48 | 49 | .PHONY: clean 50 | clean: 51 | rm -rf $(BUILDDIR)/* 52 | 53 | .PHONY: html 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | .PHONY: dirhtml 60 | dirhtml: 61 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 62 | @echo 63 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 64 | 65 | .PHONY: singlehtml 66 | singlehtml: 67 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 68 | @echo 69 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 70 | 71 | .PHONY: pickle 72 | pickle: 73 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 74 | @echo 75 | @echo "Build finished; now you can process the pickle files." 76 | 77 | .PHONY: json 78 | json: 79 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 80 | @echo 81 | @echo "Build finished; now you can process the JSON files." 82 | 83 | .PHONY: htmlhelp 84 | htmlhelp: 85 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 86 | @echo 87 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 88 | ".hhp project file in $(BUILDDIR)/htmlhelp." 89 | 90 | .PHONY: qthelp 91 | qthelp: 92 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 93 | @echo 94 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 95 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 96 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Commanduino.qhcp" 97 | @echo "To view the help file:" 98 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Commanduino.qhc" 99 | 100 | .PHONY: applehelp 101 | applehelp: 102 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 103 | @echo 104 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 105 | @echo "N.B. You won't be able to view it unless you put it in" \ 106 | "~/Library/Documentation/Help or install it in your application" \ 107 | "bundle." 108 | 109 | .PHONY: devhelp 110 | devhelp: 111 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 112 | @echo 113 | @echo "Build finished." 114 | @echo "To view the help file:" 115 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Commanduino" 116 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Commanduino" 117 | @echo "# devhelp" 118 | 119 | .PHONY: epub 120 | epub: 121 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 122 | @echo 123 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 124 | 125 | .PHONY: epub3 126 | epub3: 127 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 128 | @echo 129 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 130 | 131 | .PHONY: latex 132 | latex: 133 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 134 | @echo 135 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 136 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 137 | "(use \`make latexpdf' here to do that automatically)." 138 | 139 | .PHONY: latexpdf 140 | latexpdf: 141 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 142 | @echo "Running LaTeX files through pdflatex..." 143 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 144 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 145 | 146 | .PHONY: latexpdfja 147 | latexpdfja: 148 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 149 | @echo "Running LaTeX files through platex and dvipdfmx..." 150 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 151 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 152 | 153 | .PHONY: text 154 | text: 155 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 156 | @echo 157 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 158 | 159 | .PHONY: man 160 | man: 161 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 162 | @echo 163 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 164 | 165 | .PHONY: texinfo 166 | texinfo: 167 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 168 | @echo 169 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 170 | @echo "Run \`make' in that directory to run these through makeinfo" \ 171 | "(use \`make info' here to do that automatically)." 172 | 173 | .PHONY: info 174 | info: 175 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 176 | @echo "Running Texinfo files through makeinfo..." 177 | make -C $(BUILDDIR)/texinfo info 178 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 179 | 180 | .PHONY: gettext 181 | gettext: 182 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 183 | @echo 184 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 185 | 186 | .PHONY: changes 187 | changes: 188 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 189 | @echo 190 | @echo "The overview file is in $(BUILDDIR)/changes." 191 | 192 | .PHONY: linkcheck 193 | linkcheck: 194 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 195 | @echo 196 | @echo "Link check complete; look for any errors in the above output " \ 197 | "or in $(BUILDDIR)/linkcheck/output.txt." 198 | 199 | .PHONY: doctest 200 | doctest: 201 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 202 | @echo "Testing of doctests in the sources finished, look at the " \ 203 | "results in $(BUILDDIR)/doctest/output.txt." 204 | 205 | .PHONY: coverage 206 | coverage: 207 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 208 | @echo "Testing of coverage in the sources finished, look at the " \ 209 | "results in $(BUILDDIR)/coverage/python.txt." 210 | 211 | .PHONY: xml 212 | xml: 213 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 214 | @echo 215 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 216 | 217 | .PHONY: pseudoxml 218 | pseudoxml: 219 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 220 | @echo 221 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 222 | 223 | .PHONY: dummy 224 | dummy: 225 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 226 | @echo 227 | @echo "Build finished. Dummy builder generates no files." 228 | 229 | gh-pages: 230 | git checkout gh-pages 231 | rm -rf build _sources _static 232 | git checkout develop $(GH_PAGES_SOURCES) 233 | git reset HEAD 234 | make html 235 | mv -fv build/html/* ./ 236 | rm -rf $(GH_PAGES_SOURCES) build 237 | git add -A 238 | git ci -m "Generated gh-pages for commanduino" && git push origin gh-pages ; git checkout develop 239 | -------------------------------------------------------------------------------- /docs/commanduino/_build/doctrees/commanduino.commanddevices.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/docs/commanduino/_build/doctrees/commanduino.commanddevices.doctree -------------------------------------------------------------------------------- /docs/commanduino/_build/doctrees/commanduino.devices.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/docs/commanduino/_build/doctrees/commanduino.devices.doctree -------------------------------------------------------------------------------- /docs/commanduino/_build/doctrees/commanduino.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/docs/commanduino/_build/doctrees/commanduino.doctree -------------------------------------------------------------------------------- /docs/commanduino/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/docs/commanduino/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/commanduino/_build/doctrees/examples.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/docs/commanduino/_build/doctrees/examples.doctree -------------------------------------------------------------------------------- /docs/commanduino/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/croningp/commanduino/2bbebc02487134495e4b0cd4846deaa362c2f4a9/docs/commanduino/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/commanduino/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: bf014b0859a3f28c1706f8a8df555cb6 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/commanduino/commanduino.commanddevices.rst: -------------------------------------------------------------------------------- 1 | .. _command_devices: 2 | 3 | Command Devices Package 4 | ************************ 5 | This is the documentation for the Arduino devices that can be controlled with the Commanduino library. 6 | For more information on Axis control, please see the :ref:`devices`. 7 | Here is a list of all supported devices: 8 | 9 | * :ref:`cmdAR` 10 | * :ref:`cmdAW` 11 | * :ref:`cmdDev` 12 | * :ref:`cmdDR` 13 | * :ref:`cmdDW` 14 | * :ref:`cmdLAS` 15 | * :ref:`cmdServ` 16 | 17 | For registering each device, please see the :ref:`cmdReg` 18 | 19 | Module contents 20 | --------------- 21 | 22 | .. automodule:: commanduino.commanddevices 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | .. _cmdAR: 28 | 29 | Command Analog Read Module 30 | --------------------------------------------------- 31 | 32 | .. automodule:: commanduino.commanddevices.commandanalogread 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | 37 | .. _cmdAW: 38 | 39 | Command Analog Write Module 40 | ---------------------------------------------------- 41 | 42 | .. automodule:: commanduino.commanddevices.commandanalogwrite 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | .. _cmdDev: 48 | 49 | Command Device Module 50 | ----------------------------------------------- 51 | 52 | .. automodule:: commanduino.commanddevices.commanddevice 53 | :members: 54 | :undoc-members: 55 | :show-inheritance: 56 | 57 | .. _cmdDR: 58 | 59 | Command Digital Read Module 60 | ---------------------------------------------------- 61 | 62 | .. automodule:: commanduino.commanddevices.commanddigitalread 63 | :members: 64 | :undoc-members: 65 | :show-inheritance: 66 | 67 | .. _cmdDW: 68 | 69 | Command Digital Write Module 70 | ----------------------------------------------------- 71 | 72 | .. automodule:: commanduino.commanddevices.commanddigitalwrite 73 | :members: 74 | :undoc-members: 75 | :show-inheritance: 76 | 77 | .. _cmdLAS: 78 | 79 | Command Linear Accelerator Stepper Module 80 | ----------------------------------------------------------- 81 | 82 | .. automodule:: commanduino.commanddevices.commandlinearaccelstepper 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | 87 | .. _cmdServ: 88 | 89 | Command Servo Module 90 | ---------------------------------------------- 91 | 92 | .. automodule:: commanduino.commanddevices.commandservo 93 | :members: 94 | :undoc-members: 95 | :show-inheritance: 96 | 97 | .. _cmdReg: 98 | 99 | Register Module 100 | ------------------------------------------ 101 | 102 | .. automodule:: commanduino.commanddevices.register 103 | :members: 104 | :undoc-members: 105 | :show-inheritance: 106 | -------------------------------------------------------------------------------- /docs/commanduino/commanduino.devices.rst: -------------------------------------------------------------------------------- 1 | .. _devices: 2 | 3 | Devices Package 4 | *************** 5 | This is the documentation concerned with controlling an Axis/Axes. 6 | 7 | Axis Module 8 | ------------------------------- 9 | 10 | .. automodule:: commanduino.devices.axis 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | -------------------------------------------------------------------------------- /docs/commanduino/commanduino.rst: -------------------------------------------------------------------------------- 1 | Commanduino Library 2 | =================== 3 | This is the documentation for the Commanduino controller modules. Here, you will find the methods associated with :ref:`command_handler`, :ref:`command_manager`, and :ref:`lock`. 4 | For the documentation associated with the Arduino devices, please see the :ref:`command_devices` page for more details. 5 | 6 | Module contents 7 | --------------- 8 | 9 | .. automodule:: commanduino 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | 14 | Subpackages 15 | ----------- 16 | 17 | .. toctree:: 18 | 19 | commanduino.commanddevices 20 | commanduino.devices 21 | 22 | .. _command_handler: 23 | 24 | Command Handler Module 25 | --------------------------------- 26 | 27 | .. automodule:: commanduino.commandhandler 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | .. _command_manager: 33 | 34 | Command Manager Module 35 | --------------------------------- 36 | 37 | .. automodule:: commanduino.commandmanager 38 | :members: 39 | :undoc-members: 40 | :show-inheritance: 41 | 42 | .. _lock: 43 | 44 | Lock module 45 | ----------------------- 46 | 47 | .. automodule:: commanduino.lock 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | -------------------------------------------------------------------------------- /docs/commanduino/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Commanduino documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Oct 11 11:54:45 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | # 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.todo', 35 | 'sphinx.ext.viewcode', 36 | 'sphinx.ext.intersphinx', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # 45 | # source_suffix = ['.rst', '.md'] 46 | source_suffix = '.rst' 47 | 48 | # The encoding of source files. 49 | # 50 | # source_encoding = 'utf-8-sig' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = u'Commanduino' 57 | copyright = u'2016, Jonathan Grizou' 58 | author = u'Jonathan Grizou' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = u'0.1.2' 66 | # The full version, including alpha/beta/rc tags. 67 | release = u'0.1.2' 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # 72 | # This is also used if you do content translation via gettext catalogs. 73 | # Usually you set "language" from the command line for these cases. 74 | language = 'en' 75 | 76 | # There are two options for replacing |today|: either, you set today to some 77 | # non-false value, then it is used: 78 | # 79 | # today = '' 80 | # 81 | # Else, today_fmt is used as the format for a strftime call. 82 | # 83 | # today_fmt = '%B %d, %Y' 84 | 85 | # List of patterns, relative to source directory, that match files and 86 | # directories to ignore when looking for source files. 87 | # This patterns also effect to html_static_path and html_extra_path 88 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 89 | 90 | # The reST default role (used for this markup: `text`) to use for all 91 | # documents. 92 | # 93 | # default_role = None 94 | 95 | # If true, '()' will be appended to :func: etc. cross-reference text. 96 | # 97 | # add_function_parentheses = True 98 | 99 | # If true, the current module name will be prepended to all description 100 | # unit titles (such as .. function::). 101 | # 102 | # add_module_names = True 103 | 104 | # If true, sectionauthor and moduleauthor directives will be shown in the 105 | # output. They are ignored by default. 106 | # 107 | # show_authors = False 108 | 109 | # The name of the Pygments (syntax highlighting) style to use. 110 | pygments_style = 'sphinx' 111 | 112 | # A list of ignored prefixes for module index sorting. 113 | # modindex_common_prefix = [] 114 | 115 | # If true, keep warnings as "system message" paragraphs in the built documents. 116 | # keep_warnings = False 117 | 118 | # If true, `todo` and `todoList` produce output, else they produce nothing. 119 | todo_include_todos = True 120 | 121 | 122 | # -- Options for HTML output ---------------------------------------------- 123 | 124 | # The theme to use for HTML and HTML Help pages. See the documentation for 125 | # a list of builtin themes. 126 | # 127 | import sphinx_rtd_theme 128 | html_theme = 'sphinx_rtd_theme' 129 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 130 | 131 | # Theme options are theme-specific and customize the look and feel of a theme 132 | # further. For a list of options available for each theme, see the 133 | # documentation. 134 | # 135 | # html_theme_options = {} 136 | 137 | # Add any paths that contain custom themes here, relative to this directory. 138 | # html_theme_path = [] 139 | 140 | # The name for this set of Sphinx documents. 141 | # " v documentation" by default. 142 | # 143 | #html_title = u'Commanduino v0.1.2' 144 | 145 | # A shorter title for the navigation bar. Default is the same as html_title. 146 | # 147 | # html_short_title = None 148 | 149 | # The name of an image file (relative to this directory) to place at the top 150 | # of the sidebar. 151 | # 152 | # html_logo = None 153 | 154 | # The name of an image file (relative to this directory) to use as a favicon of 155 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 156 | # pixels large. 157 | # 158 | # html_favicon = None 159 | 160 | # Add any paths that contain custom static files (such as style sheets) here, 161 | # relative to this directory. They are copied after the builtin static files, 162 | # so a file named "default.css" will overwrite the builtin "default.css". 163 | html_static_path = ['_static'] 164 | 165 | # Add any extra paths that contain custom files (such as robots.txt or 166 | # .htaccess) here, relative to this directory. These files are copied 167 | # directly to the root of the documentation. 168 | # 169 | # html_extra_path = [] 170 | 171 | # If not None, a 'Last updated on:' timestamp is inserted at every page 172 | # bottom, using the given strftime format. 173 | # The empty string is equivalent to '%b %d, %Y'. 174 | # 175 | # html_last_updated_fmt = None 176 | 177 | # If true, SmartyPants will be used to convert quotes and dashes to 178 | # typographically correct entities. 179 | # 180 | # html_use_smartypants = True 181 | 182 | # Custom sidebar templates, maps document names to template names. 183 | # 184 | # html_sidebars = {} 185 | 186 | # Additional templates that should be rendered to pages, maps page names to 187 | # template names. 188 | # 189 | # html_additional_pages = {} 190 | 191 | # If false, no module index is generated. 192 | # 193 | # html_domain_indices = True 194 | 195 | # If false, no index is generated. 196 | # 197 | # html_use_index = True 198 | 199 | # If true, the index is split into individual pages for each letter. 200 | # 201 | # html_split_index = False 202 | 203 | # If true, links to the reST sources are added to the pages. 204 | # 205 | # html_show_sourcelink = True 206 | 207 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 208 | # 209 | # html_show_sphinx = True 210 | 211 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 212 | # 213 | # html_show_copyright = True 214 | 215 | # If true, an OpenSearch description file will be output, and all pages will 216 | # contain a tag referring to it. The value of this option must be the 217 | # base URL from which the finished HTML is served. 218 | # 219 | # html_use_opensearch = '' 220 | 221 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 222 | # html_file_suffix = None 223 | 224 | # Language to be used for generating the HTML full-text search index. 225 | # Sphinx supports the following languages: 226 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 227 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 228 | # 229 | # html_search_language = 'en' 230 | 231 | # A dictionary with options for the search language support, empty by default. 232 | # 'ja' uses this config value. 233 | # 'zh' user can custom change `jieba` dictionary path. 234 | # 235 | # html_search_options = {'type': 'default'} 236 | 237 | # The name of a javascript file (relative to the configuration directory) that 238 | # implements a search results scorer. If empty, the default will be used. 239 | # 240 | # html_search_scorer = 'scorer.js' 241 | 242 | # Output file base name for HTML help builder. 243 | htmlhelp_basename = 'Commanduinodoc' 244 | 245 | # -- Options for LaTeX output --------------------------------------------- 246 | 247 | latex_elements = { 248 | # The paper size ('letterpaper' or 'a4paper'). 249 | # 250 | # 'papersize': 'letterpaper', 251 | 252 | # The font size ('10pt', '11pt' or '12pt'). 253 | # 254 | # 'pointsize': '10pt', 255 | 256 | # Additional stuff for the LaTeX preamble. 257 | # 258 | # 'preamble': '', 259 | 260 | # Latex figure (float) alignment 261 | # 262 | # 'figure_align': 'htbp', 263 | } 264 | 265 | # Grouping the document tree into LaTeX files. List of tuples 266 | # (source start file, target name, title, 267 | # author, documentclass [howto, manual, or own class]). 268 | latex_documents = [ 269 | (master_doc, 'Commanduino.tex', u'Commanduino Documentation', 270 | u'Jonathan Grizou', 'manual'), 271 | ] 272 | 273 | # The name of an image file (relative to this directory) to place at the top of 274 | # the title page. 275 | # 276 | # latex_logo = None 277 | 278 | # For "manual" documents, if this is true, then toplevel headings are parts, 279 | # not chapters. 280 | # 281 | # latex_use_parts = False 282 | 283 | # If true, show page references after internal links. 284 | # 285 | # latex_show_pagerefs = False 286 | 287 | # If true, show URL addresses after external links. 288 | # 289 | # latex_show_urls = False 290 | 291 | # Documents to append as an appendix to all manuals. 292 | # 293 | # latex_appendices = [] 294 | 295 | # It false, will not define \strong, \code, itleref, \crossref ... but only 296 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 297 | # packages. 298 | # 299 | # latex_keep_old_macro_names = True 300 | 301 | # If false, no module index is generated. 302 | # 303 | # latex_domain_indices = True 304 | 305 | 306 | # -- Options for manual page output --------------------------------------- 307 | 308 | # One entry per manual page. List of tuples 309 | # (source start file, name, description, authors, manual section). 310 | man_pages = [ 311 | (master_doc, 'commanduino', u'Commanduino Documentation', 312 | [author], 1) 313 | ] 314 | 315 | # If true, show URL addresses after external links. 316 | # 317 | # man_show_urls = False 318 | 319 | 320 | # -- Options for Texinfo output ------------------------------------------- 321 | 322 | # Grouping the document tree into Texinfo files. List of tuples 323 | # (source start file, target name, title, author, 324 | # dir menu entry, description, category) 325 | texinfo_documents = [ 326 | (master_doc, 'Commanduino', u'Commanduino Documentation', 327 | author, 'Commanduino', 'One line description of project.', 328 | 'Miscellaneous'), 329 | ] 330 | 331 | # Documents to append as an appendix to all manuals. 332 | # 333 | # texinfo_appendices = [] 334 | 335 | # If false, no module index is generated. 336 | # 337 | # texinfo_domain_indices = True 338 | 339 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 340 | # 341 | # texinfo_show_urls = 'footnote' 342 | 343 | # If true, do not generate a @detailmenu in the "Top" node's menu. 344 | # 345 | # texinfo_no_detailmenu = False 346 | 347 | 348 | # -- Options for Epub output ---------------------------------------------- 349 | 350 | # Bibliographic Dublin Core info. 351 | epub_title = project 352 | epub_author = author 353 | epub_publisher = author 354 | epub_copyright = copyright 355 | 356 | # The basename for the epub file. It defaults to the project name. 357 | # epub_basename = project 358 | 359 | # The HTML theme for the epub output. Since the default themes are not 360 | # optimized for small screen space, using the same theme for HTML and epub 361 | # output is usually not wise. This defaults to 'epub', a theme designed to save 362 | # visual space. 363 | # 364 | # epub_theme = 'epub' 365 | 366 | # The language of the text. It defaults to the language option 367 | # or 'en' if the language is not set. 368 | # 369 | # epub_language = '' 370 | 371 | # The scheme of the identifier. Typical schemes are ISBN or URL. 372 | # epub_scheme = '' 373 | 374 | # The unique identifier of the text. This can be a ISBN number 375 | # or the project homepage. 376 | # 377 | # epub_identifier = '' 378 | 379 | # A unique identification for the text. 380 | # 381 | # epub_uid = '' 382 | 383 | # A tuple containing the cover image and cover page html template filenames. 384 | # 385 | # epub_cover = () 386 | 387 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 388 | # 389 | # epub_guide = () 390 | 391 | # HTML files that should be inserted before the pages created by sphinx. 392 | # The format is a list of tuples containing the path and title. 393 | # 394 | # epub_pre_files = [] 395 | 396 | # HTML files that should be inserted after the pages created by sphinx. 397 | # The format is a list of tuples containing the path and title. 398 | # 399 | # epub_post_files = [] 400 | 401 | # A list of files that should not be packed into the epub file. 402 | epub_exclude_files = ['search.html'] 403 | 404 | # The depth of the table of contents in toc.ncx. 405 | # 406 | # epub_tocdepth = 3 407 | 408 | # Allow duplicate toc entries. 409 | # 410 | # epub_tocdup = True 411 | 412 | # Choose between 'default' and 'includehidden'. 413 | # 414 | # epub_tocscope = 'default' 415 | 416 | # Fix unsupported image types using the Pillow. 417 | # 418 | # epub_fix_images = False 419 | 420 | # Scale large images. 421 | # 422 | # epub_max_image_width = 0 423 | 424 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 425 | # 426 | # epub_show_urls = 'inline' 427 | 428 | # If false, no index is generated. 429 | # 430 | # epub_use_index = True 431 | -------------------------------------------------------------------------------- /docs/commanduino/createsphinxproject.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def main(): 5 | print("Please enter the details of the Sphinx project you wish to create.\n") 6 | projectName = input("Project name: ") 7 | author = input("Author: ") 8 | version = input("Project version: ") 9 | outputDir = input("Output directory: ") 10 | sourceDir = input("Source code directory: ") 11 | 12 | try: 13 | print("Creating sphinx project...") 14 | command = "sphinx-apidoc -F -H {0} -A {1} -V {2} -o {3} {4}".format(projectName, author, version, outputDir, sourceDir) 15 | print(command) 16 | # os.system(command) 17 | except Exception as e: 18 | print("Error when creating project.") 19 | 20 | 21 | if __name__=='__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /docs/commanduino/examples.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples 4 | ******** 5 | 6 | Here you will find in-depth examples of how to use the Commanduino library. 7 | 8 | CommandServo Example 9 | ==================== 10 | This example focuses on controlling a servo motor attached to an Arduino board via Python. 11 | 12 | Below are samples of a .json configuration file and the Python script which controls the motor. 13 | 14 | .. note:: This example is for the CommandServo module but other modules can be used this way too, just replace the name! 15 | 16 | JSON Configuration file (demo.json) 17 | +++++++++++++++++++++++++++++++++++ 18 | This is how to lay out a .json configuration file. 19 | 20 | * ios 21 | This are the I/O port(s) on which the Arduino board is attached to. 22 | This is dependent on the the port which is set upon uploading code to the Arduino board. 23 | * devices 24 | These are the names that will be used to communicate with the device in the Python code (e.g. servo1, servo2, etc). 25 | 26 | * command_id 27 | This is the label which the library uses to identify the device. 28 | 29 | Add as many devices as necessary. 30 | 31 | **JSON Configuration file:** 32 | 33 | :: 34 | 35 | { 36 | "ios" : [ 37 | { 38 | "port": "/dev/tty.usbmodem1411" 39 | }, 40 | { 41 | "port": "/dev/ttyACM0" 42 | }, 43 | { 44 | "port": "/dev/ttyACM1" 45 | }, 46 | { 47 | "port": "/dev/ttyACM5" 48 | } 49 | ], 50 | "devices": { 51 | "servo1": { 52 | "command_id": "S1" 53 | }, 54 | "servo2": { 55 | "command_id": "S2" 56 | } 57 | } 58 | } 59 | 60 | Python Script (demo.py) 61 | +++++++++++++++++++++++ 62 | This is an example of a python script which uses the Commanduino library. 63 | 64 | 1. First, import the CommandManager module from the commanduino library. 65 | 2. Create a CommandManager object, using a .json configuration file to set the parameters. 66 | 3. The manager is now initialised and ready for use! 67 | 4. You can gain access to the devices through the command manager methods (e.g. cmdMgr.servo1) 68 | 5. This will allow access to the control methods for the device (e.g.cmdMgr.servo1.set_angle(60)) 69 | 70 | Please see the :ref:`command_devices` page for a list of supported devices. 71 | 72 | **Python Script Example:** 73 | 74 | :: 75 | 76 | import time 77 | import logging 78 | logging.basicConfig(level=logging.INFO) 79 | 80 | 81 | from commanduino import CommandManager 82 | 83 | cmdMng = CommandManager.from_configfile('./demo.json') 84 | 85 | 86 | for i in range(2): 87 | cmdMng.servo1.set_angle(60) 88 | cmdMng.servo2.set_angle(60) 89 | print cmdMng.servo1.get_angle() 90 | print cmdMng.servo2.get_angle() 91 | time.sleep(1) 92 | cmdMng.servo1.set_angle(120) 93 | cmdMng.servo2.set_angle(120) 94 | print cmdMng.servo1.get_angle() 95 | print cmdMng.servo2.get_angle() 96 | time.sleep(1) 97 | 98 | .. note:: Insert Examples here similar to the above format! 99 | -------------------------------------------------------------------------------- /docs/commanduino/ghPages.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | 5 | def main(): 6 | cmd = generateCommandList() 7 | 8 | for i in range(len(cmd)): 9 | if i is 0: 10 | os.chdir('../../') 11 | time.sleep(1) 12 | print(os.getcwd()) 13 | os.system(cmd[i]) 14 | elif i is 4: 15 | os.chdir('docs/commanduino') 16 | time.sleep(1) 17 | os.system(cmd[i]) 18 | elif i is 6: 19 | os.chdir('../../') 20 | time.sleep(1) 21 | os.system(cmd[i]) 22 | else: 23 | print("Executing command: \"{0}\"").format(cmd[i]) 24 | time.sleep(2) 25 | os.system(cmd[i]) 26 | 27 | 28 | def generateCommandList(): 29 | cmd = ['git checkout gh-pages', 30 | 'rm -rf *.html *.js *.inv', 31 | 'rm -rf _sources/ _static/ _modules/ docs/', 32 | 'git checkout develop', 33 | 'make clean && make html', 34 | 'mv -fv _build/html/* ../../', 35 | 'git add -A', 36 | 'git stash', 37 | 'git checkout gh-pages', 38 | 'git stash pop', 39 | 'rm -rf docs/commanduino/_build/* commanduino/* docs/*', 40 | 'git add -A', 41 | 'git commit -m "Updated the GH pages"', 42 | 'git push origin gh-pages', 43 | 'git checkout develop'] 44 | 45 | return cmd 46 | 47 | 48 | if __name__ == '__main__': 49 | main() 50 | -------------------------------------------------------------------------------- /docs/commanduino/index.rst: -------------------------------------------------------------------------------- 1 | .. Commanduino documentation master file, created by 2 | sphinx-quickstart on Tue Oct 11 11:54:45 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Documentation for the Commanduino Library 7 | ========================================= 8 | 9 | Welcome to the Commanduino Documentation pages! Here, you will find all the necessary information relating to using the Commanuino Library. 10 | For examples of using this library, please see the :ref:`examples` page. 11 | 12 | .. note:: Insert more info here! 13 | 14 | Contents: 15 | ----------- 16 | 17 | .. toctree:: 18 | :maxdepth: 4 19 | 20 | commanduino 21 | 22 | examples 23 | 24 | 25 | Indices and tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /docs/commanduino/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Commanduino.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Commanduino.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/commanduino/makehtmldocs.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def main(): 4 | os.system("make clean") 5 | os.system("make html") 6 | 7 | if __name__=='__main__': 8 | main() 9 | -------------------------------------------------------------------------------- /examples/commanddevices/commandaccelstepper/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "stepper": { 18 | "command_id": "CSTP1", 19 | "config": { 20 | "enabled_acceleration": false, 21 | "speed": 1000, 22 | "max_speed": 10000, 23 | "acceleration": 1000 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/commanddevices/commandaccelstepper/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | logging.basicConfig(level=logging.INFO) 4 | 5 | 6 | from commanduino import CommandManager 7 | 8 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandaccelstepper/demo.json') 9 | 10 | 11 | stepper = cmdMng.stepper 12 | 13 | print('Setting current position to 0') 14 | stepper.set_current_position(0) 15 | 16 | stepper.enable_acceleration() 17 | stepper.set_acceleration(1000) 18 | stepper.set_running_speed(5000) 19 | stepper.set_max_speed(10000) 20 | 21 | print('Moving to 20000 with acceleration 1000') 22 | stepper.move_to(20000, wait=True) 23 | print('Moving back to 0 with acceleration 5000') 24 | stepper.set_acceleration(5000) 25 | stepper.move_to(0, wait=True) 26 | 27 | # 28 | print('Disabling acceleration') 29 | stepper.disable_acceleration() 30 | stepper.set_max_speed(10000) 31 | 32 | # 33 | print('Moving to 20000 with default speed 5000') 34 | stepper.move_to(10000, wait=True) 35 | print('Setting speed to 10000') 36 | stepper.set_running_speed(10000) 37 | print('Moving back to 0') 38 | stepper.move_to(0, wait=True) 39 | 40 | print('Current position is {}'.format(stepper.get_current_position())) 41 | -------------------------------------------------------------------------------- /examples/commanddevices/commandanalogread/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "A1": { 18 | "command_id": "A1" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/commanddevices/commandanalogread/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from commanduino import CommandManager 4 | 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandanalogread/demo.json') 8 | 9 | 10 | for i in range(10): 11 | print(cmdMng.A1.get_level()) 12 | time.sleep(1) 13 | -------------------------------------------------------------------------------- /examples/commanddevices/commandanalogwrite/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "A1": { 18 | "command_id": "A1" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/commanddevices/commandanalogwrite/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | logging.basicConfig(level=logging.INFO) 4 | 5 | 6 | from commanduino import CommandManager 7 | 8 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandanalogwrite/demo.json') 9 | 10 | 11 | for i in range(10): 12 | cmdMng.A1.set_pwm_value(255) 13 | time.sleep(1) 14 | cmdMng.A1.set_pwm_value(0) 15 | time.sleep(1) 16 | cmdMng.A1.set_pwm_value(50) 17 | time.sleep(1) 18 | cmdMng.A1.set_pwm_value(0) 19 | time.sleep(1) 20 | -------------------------------------------------------------------------------- /examples/commanddevices/commandbme280/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem14301" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "bme280": { 18 | "command_id": "BME280" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/commanddevices/commandbme280/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from commanduino import CommandManager 4 | 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandbme280/demo.json') 8 | 9 | for i in range(10): 10 | P = cmdMng.bme280.get_pressure() 11 | T = cmdMng.bme280.get_temperature() 12 | H = cmdMng.bme280.get_humidity() 13 | print('###') 14 | print('Pressure = {} Pascals'.format(P)) 15 | print('Temperature = {} ºC'.format(T)) 16 | print('Humidity = {}%'.format(H)) 17 | time.sleep(1) 18 | -------------------------------------------------------------------------------- /examples/commanddevices/commanddallas/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "D1": { 18 | "command_id": "D1" 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /examples/commanddevices/commanddallas/demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding: utf-8 3 | 4 | import time 5 | import logging 6 | from commanduino import CommandManager 7 | 8 | logging.basicConfig(level=logging.INFO) 9 | 10 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commanddallas/demo.json') 11 | 12 | 13 | for i in range(10): 14 | C = cmdMng.D1.get_celsius() 15 | print("Temperature = {}°C".format(C)) 16 | 17 | 18 | if __name__ == '__main__': 19 | pass 20 | -------------------------------------------------------------------------------- /examples/commanddevices/commanddigitalread/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "D1": { 18 | "command_id": "D1" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/commanddevices/commanddigitalread/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from commanduino import CommandManager 4 | 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | 8 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commanddigitalread/demo.json') 9 | 10 | 11 | for i in range(10): 12 | print(cmdMng.D1.get_state()) 13 | time.sleep(1) 14 | -------------------------------------------------------------------------------- /examples/commanddevices/commanddigitalwrite/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "D1": { 18 | "command_id": "D1" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/commanddevices/commanddigitalwrite/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | logging.basicConfig(level=logging.INFO) 4 | 5 | 6 | from commanduino import CommandManager 7 | 8 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commanddigitalwrite/demo.json') 9 | 10 | 11 | for i in range(10): 12 | cmdMng.D1.high() 13 | time.sleep(1) 14 | cmdMng.D1.low() 15 | time.sleep(1) 16 | -------------------------------------------------------------------------------- /examples/commanddevices/commandlinearaccelstepper/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "stepper": { 18 | "command_id": "LS1", 19 | "config": { 20 | "enabled_acceleration": false, 21 | "speed": 1000, 22 | "max_speed": 1000, 23 | "acceleration": 1000 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/commanddevices/commandlinearaccelstepper/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from commanduino import CommandManager 4 | 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | 8 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandlinearaccelstepper/demo.json') 9 | 10 | stepper = cmdMng.stepper 11 | 12 | stepper.set_current_position(0) 13 | 14 | stepper.enable_acceleration() 15 | stepper.set_acceleration(1000) 16 | stepper.set_max_speed(10000) 17 | 18 | print('Moving to 20000 with acceleration 1000') 19 | stepper.move_to(20000, wait=True) 20 | 21 | print('Moving back to 0 with acceleration 5000...') 22 | print('Should turn in the opposite direction no?') 23 | stepper.set_acceleration(5000) 24 | stepper.move_to(0, wait=True) 25 | 26 | 27 | print('Disabling acceleration') 28 | stepper.disable_acceleration() 29 | stepper.set_max_speed(10000) 30 | 31 | 32 | print('Moving to 20000 with default speed 1000') 33 | stepper.move_to(20000, wait=False) 34 | time.sleep(1) 35 | print('Increasing speed to 10000') 36 | stepper.set_running_speed(10000) 37 | stepper.wait_until_idle() 38 | 39 | 40 | print('Moving back to 0') 41 | print('Should turn in the opposite direction no?') 42 | print('Speed should still be 10000 no?') 43 | stepper.set_running_speed(10000) 44 | stepper.move_to(0, wait=True) 45 | -------------------------------------------------------------------------------- /examples/commanddevices/commandmax31865/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios": [ 3 | { 4 | "port": "COM3" 5 | } 6 | ], 7 | "devices": { 8 | "rtd": { 9 | "command_id": "MAX" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/commanddevices/commandmax31865/demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | An example module to work with the MAX31685 RTD amplifier board sensor. 3 | """ 4 | 5 | import time 6 | import logging 7 | import os 8 | from commanduino import CommandManager 9 | 10 | logging.basicConfig(level=logging.INFO) 11 | 12 | HERE = os.path.abspath(os.path.dirname(__file__)) 13 | DEMO = os.path.join(HERE, 'demo.json') 14 | 15 | cmdMng = CommandManager.from_configfile(DEMO) 16 | 17 | # Upon instantiation, initialization code can be used to check if the sensor is actually attached 18 | # .initialization_code is None - the device wasn't initialized, call get_initialization_code() 19 | # .initialization_code == 1 - the device is found and the communication was established successfully 20 | # .initialization_code == 0 - the device was not found or the communication cannot be established, please 21 | # check the device datasheet for possible reasons 22 | # 23 | # Call get_initialization_code() to try again. 24 | 25 | # Below is an example code for a check 26 | class SensorError(Exception): 27 | """ Generic sensor error. """ 28 | 29 | cmdMng.rtd.get_initialization_code() 30 | 31 | if cmdMng.rtd.initialization_code is not None and cmdMng.rtd.initialization_code == 0: 32 | raise SensorError("Sensor is not connected!") 33 | 34 | for i in range(3): 35 | start_time = time.time() 36 | temp = cmdMng.rtd.get_temp() 37 | end_time = time.time() 38 | print("Temperature: ", temp) 39 | -------------------------------------------------------------------------------- /examples/commanddevices/commandservo/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "servo1": { 18 | "command_id": "S1" 19 | }, 20 | "servo2": { 21 | "command_id": "S2" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/commanddevices/commandservo/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from commanduino import CommandManager 4 | 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandservo/demo.json') 8 | 9 | 10 | for i in range(2): 11 | cmdMng.servo1.set_angle(60) 12 | cmdMng.servo2.set_angle(60) 13 | print(cmdMng.servo1.get_angle()) 14 | print(cmdMng.servo2.get_angle()) 15 | time.sleep(1) 16 | cmdMng.servo1.set_angle(120) 17 | cmdMng.servo2.set_angle(120) 18 | print(cmdMng.servo1.get_angle()) 19 | print(cmdMng.servo2.get_angle()) 20 | time.sleep(1) 21 | -------------------------------------------------------------------------------- /examples/commanddevices/commandsht1x/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "sht15": { 18 | "command_id": "SHT15" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/commanddevices/commandsht1x/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from commanduino import CommandManager 4 | 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandsht1x/demo.json') 8 | 9 | for i in range(10): 10 | F = cmdMng.sht15.get_fahrenheit() 11 | C = cmdMng.sht15.get_celsius() 12 | H = cmdMng.sht15.get_humidity() 13 | print('###') 14 | print('Temperature = {} F / {} C'.format(F, C)) 15 | print('Humidity = {}%'.format(H)) 16 | time.sleep(1) 17 | -------------------------------------------------------------------------------- /examples/commanddevices/commandsht31/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "/dev/tty.usbmodem1411" 5 | }, 6 | { 7 | "port": "/dev/ttyACM0" 8 | }, 9 | { 10 | "port": "/dev/ttyACM1" 11 | }, 12 | { 13 | "port": "/dev/ttyACM2" 14 | } 15 | ], 16 | "devices": { 17 | "sht31": { 18 | "command_id": "SHT31" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/commanddevices/commandsht31/demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from commanduino import CommandManager 4 | 5 | logging.basicConfig(level=logging.INFO) 6 | 7 | cmdMng = CommandManager.from_configfile('./examples/commanddevices/commandsht31/demo.json') 8 | 9 | for i in range(10): 10 | C = cmdMng.sht31.get_celsius() 11 | H = cmdMng.sht31.get_humidity() 12 | print('###') 13 | print('Temperature = {} C'.format(C)) 14 | print('Humidity = {}%'.format(H)) 15 | time.sleep(1) 16 | -------------------------------------------------------------------------------- /examples/commanddevices/commandtcs34725/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios": [ 3 | { 4 | "port": "COM4" 5 | } 6 | ], 7 | "devices": { 8 | "demo": { 9 | "command_id": "TCS" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/commanddevices/commandtcs34725/demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | An example module to work with the TCS34725 RGB sensor as Arduino device. 3 | """ 4 | 5 | import time 6 | import logging 7 | import os 8 | from commanduino import CommandManager 9 | 10 | logging.basicConfig(level=logging.INFO) 11 | 12 | HERE = os.path.abspath(os.path.dirname(__file__)) 13 | DEMO = os.path.join(HERE, 'demo.json') 14 | 15 | cmdMng = CommandManager.from_configfile(DEMO) 16 | 17 | # Upon instantiation, initialization code can be used to check if the sensor is actually attached 18 | # .initialization_code is None - the device wasn't initialized, call get_initialization_code() 19 | # .initialization_code == 1 - the device is found and the communication was established successfully 20 | # .initialization_code == 0 - the device was not found or the communication cannot be established, please 21 | # check the device datasheet for possible reasons 22 | # 23 | # Call get_initialization_code() to try again. 24 | 25 | # Below is an example code for a check 26 | class SensorError(Exception): 27 | """ Generic sensor error. """ 28 | if cmdMng.demo.initialization_code is not None and cmdMng.demo.initialization_code == 0: 29 | raise SensorError("Sensor is not connected!") 30 | 31 | for i in range(3): 32 | start_time = time.time() 33 | C = cmdMng.demo.get_rgbc() 34 | end_time = time.time() 35 | print("RGBC = R {0}, G {1}, B {2}, C {3}, time {4}".format(*C, end_time-start_time)) 36 | 37 | print('INTEGRATION TIME 700') 38 | cmdMng.demo.set_integration_time(700) 39 | for i in range(3): 40 | start_time = time.time() 41 | C = cmdMng.demo.get_rgbc() 42 | end_time = time.time() 43 | print("RGBC = R {0}, G {1}, B {2}, C {3}, time {4}".format(*C, end_time-start_time)) 44 | 45 | print('GAIN 16') 46 | cmdMng.demo.set_gain(16) 47 | for i in range(3): 48 | start_time = time.time() 49 | C = cmdMng.demo.get_rgbc() 50 | end_time = time.time() 51 | print("RGBC = R {0}, G {1}, B {2}, C {3}, time {4}".format(*C, end_time-start_time)) 52 | -------------------------------------------------------------------------------- /examples/commandhandler/basics/basics.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | CommandHandler cmdHdl; 4 | 5 | // this constant won't change: 6 | const int buttonPin = 53; 7 | 8 | // Variables will change: 9 | int buttonPushCounter = 0; // counter for the number of button presses 10 | int buttonState = 0; // current state of the button 11 | int lastButtonState = 0; // previous state of the button 12 | 13 | void setup() { 14 | // initialize the button pin as a input: 15 | pinMode(buttonPin, INPUT_PULLUP); 16 | 17 | // initialize serial communication: 18 | Serial.begin(115200); 19 | 20 | cmdHdl.addCommand("CURRENT", sendCurrentState); 21 | cmdHdl.addCommand("NPUSH", sendNumberOfPush); 22 | cmdHdl.setDefaultHandler(unrecognized); 23 | 24 | } 25 | 26 | void loop() { 27 | 28 | cmdHdl.processSerial(Serial); 29 | 30 | // read the pushbutton input pin: 31 | buttonState = digitalRead(buttonPin); 32 | 33 | // compare the buttonState to its previous state 34 | if (buttonState != lastButtonState) { 35 | // if the state has changed, increment the counter 36 | if (buttonState == HIGH) { 37 | buttonPushCounter++; 38 | // 39 | cmdHdl.initCmd(); 40 | cmdHdl.addCmdString("PRESS"); 41 | cmdHdl.addCmdDelim(); 42 | cmdHdl.addCmdInt(buttonState); 43 | cmdHdl.addCmdDelim(); 44 | cmdHdl.addCmdInt(buttonPushCounter); 45 | cmdHdl.addCmdTerm(); 46 | cmdHdl.sendCmdSerial(); 47 | } else { 48 | cmdHdl.initCmd(); 49 | cmdHdl.addCmdString("RELEASE"); 50 | cmdHdl.addCmdDelim(); 51 | cmdHdl.addCmdInt(buttonState); 52 | cmdHdl.addCmdDelim(); 53 | cmdHdl.addCmdInt(buttonPushCounter); 54 | cmdHdl.addCmdTerm(); 55 | cmdHdl.sendCmdSerial(); 56 | } 57 | // Delay a little bit to avoid bouncing 58 | delay(100); 59 | } 60 | lastButtonState = buttonState; 61 | } 62 | 63 | void sendCurrentState() { 64 | cmdHdl.initCmd(); 65 | cmdHdl.addCmdString("STATE"); 66 | cmdHdl.addCmdDelim(); 67 | cmdHdl.addCmdInt(buttonState); 68 | cmdHdl.addCmdTerm(); 69 | cmdHdl.sendCmdSerial(); 70 | } 71 | 72 | void sendNumberOfPush() { 73 | cmdHdl.initCmd(); 74 | cmdHdl.addCmdString("NPUSH"); 75 | cmdHdl.addCmdDelim(); 76 | cmdHdl.addCmdInt(buttonPushCounter); 77 | cmdHdl.addCmdTerm(); 78 | cmdHdl.sendCmdSerial(); 79 | } 80 | 81 | 82 | // This gets set as the default handler, and gets called when no other command matches. 83 | void unrecognized(const char *command) { 84 | cmdHdl.initCmd(); 85 | cmdHdl.addCmdString("What?"); 86 | cmdHdl.addCmdTerm(); 87 | cmdHdl.sendCmdSerial(); 88 | } 89 | -------------------------------------------------------------------------------- /examples/commandhandler/basics/basics.py: -------------------------------------------------------------------------------- 1 | from commanduino.commandhandler import (SerialCommandHandler, TCPIPCommandHandler) 2 | 3 | def defaultPrint(cmd): 4 | print(cmd) 5 | 6 | config_tcpip = {"port":"5000", "address":"192.168.1.80"} 7 | config_serial = {"port":"COM3"} 8 | 9 | # Uncomment and use one of the lines below 10 | 11 | # Create with default constructor 12 | #cmdHdl = SerialCommandHandler(port="COM3") 13 | cmdHdl = TCPIPCommandHandler(port="5000", address="192.168.1.80") 14 | # Create from configuration dictionary 15 | #cmdHdl = SerialCommandHandler.from_config(config_serial) 16 | #cmdHdl = TCPIPCommandHandler.from_config(config_tcpip) 17 | 18 | cmdHdl.start() 19 | 20 | q = False 21 | while not q: 22 | msg = input() 23 | if msg == "QUIT": 24 | isRunning = False 25 | q = True 26 | else: 27 | cmdHdl.write(msg) 28 | 29 | cmdHdl.stop() 30 | cmdHdl.join() 31 | -------------------------------------------------------------------------------- /examples/commandmanager/basics/basics.ino: -------------------------------------------------------------------------------- 1 | #include "CommandHandler.h" 2 | #include "CommandManager.h" 3 | 4 | #include "CommandDigitalWrite.h" 5 | 6 | #define PIN1 4 7 | #define PIN2 5 8 | #define PIN3 6 9 | 10 | CommandManager mgr; 11 | CommandDigitalWrite p1(PIN1); 12 | CommandDigitalWrite p2(PIN2); 13 | CommandDigitalWrite p3(PIN3); 14 | 15 | void setup() { 16 | Serial.begin(115200); 17 | 18 | p1.registerToCommandManager(mgr, "P1"); 19 | p2.registerToCommandManager(mgr, "P2"); 20 | p3.registerToCommandManager(mgr, "P3"); 21 | 22 | mgr.init(); 23 | } 24 | 25 | void loop() { 26 | mgr.update(); 27 | } 28 | -------------------------------------------------------------------------------- /examples/commandmanager/basics/basics.py: -------------------------------------------------------------------------------- 1 | from commanduino import CommandManager 2 | 3 | import logging 4 | logging.basicConfig(level=logging.INFO) 5 | 6 | #cmdMng = CommandManager.from_configfile('./examples/commandmanager/basics/basics_serial.json') 7 | cmdMng = CommandManager.from_configfile('./examples/commandmanager/basics/basics_tcpip.json') 8 | 9 | 10 | p1, p2, p3 = True, True, True 11 | while True: 12 | msg = input() 13 | if msg == "QUIT": 14 | break 15 | elif msg == "P1": 16 | # Devices can be accessed through devices dictionary 17 | cmdMng.devices['pin1'].set_level(int(p1)) 18 | # Invert next state for this pin 19 | p1 = not p1 20 | elif msg == "P2": 21 | # Or directly as a CommandManager attribute 22 | cmdMng.pin2.set_level(int(p2)) 23 | p2 = not p2 24 | elif msg == "P3": 25 | if p3 is True: 26 | cmdMng.pin3.high() 27 | else: 28 | cmdMng.pin3.low() 29 | p3 = not p3 -------------------------------------------------------------------------------- /examples/commandmanager/basics/basics_serial.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "type": "serial", 5 | "port": "COM3" 6 | } 7 | ], 8 | "devices": { 9 | "pin1": { 10 | "command_id": "P1" 11 | }, 12 | "pin2": { 13 | "command_id": "P2" 14 | }, 15 | "pin3": { 16 | "command_id": "P3" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/commandmanager/basics/basics_tcpip.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "type": "tcpip", 5 | "port": "5000", 6 | "address": "192.168.1.80" 7 | } 8 | ], 9 | "devices": { 10 | "pin1": { 11 | "command_id": "P1" 12 | }, 13 | "pin2": { 14 | "command_id": "P2" 15 | }, 16 | "pin3": { 17 | "command_id": "P3" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/commandmanager/two_boards/two_boards.json: -------------------------------------------------------------------------------- 1 | { 2 | "ios" : [ 3 | { 4 | "port": "5000", 5 | "type":"tcpip", 6 | "address":"192.168.1.80" 7 | }, 8 | { 9 | "port": "/dev/ttyACM1" 10 | } 11 | ], 12 | "devices": { 13 | "servo1": { 14 | "command_id": "S1" 15 | }, 16 | "servo2": { 17 | "command_id": "S2" 18 | }, 19 | "stepper1": { 20 | "command_id": "LS1" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/commandmanager/two_boards/two_boards.py: -------------------------------------------------------------------------------- 1 | from commanduino import CommandManager 2 | 3 | import logging 4 | logging.basicConfig(level=logging.INFO) 5 | 6 | # in your two board please load the arduino example OF: 7 | # - CommandServo 8 | # - CommandLinearAccelStepper 9 | cmdMng = CommandManager.from_configfile('./examples/commandmanager/two_boards/two_boards.json') 10 | 11 | s1 = cmdMng.devices['servo1'] 12 | s2 = cmdMng.devices['servo2'] 13 | m1 = cmdMng.devices['stepper1'] 14 | 15 | from commanduino.devices.axis import Axis 16 | 17 | a = Axis(m1, 0.1) 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | VERSION = '0.1.12' 4 | 5 | setup(name="commanduino", 6 | version=VERSION, 7 | description="A library to interact with an arduino board running Arduino-CommandHandler and derivatives", 8 | author="Jonathan Grizou", 9 | author_email='jonathan.grizou@gmail.com', 10 | platforms=["any"], 11 | url="https://github.com/croningp/commanduino", 12 | packages=find_packages(), 13 | package_data={ 14 | "commanduino": ["commanddevices/*.pyi", "py.typed"] 15 | }, 16 | include_package_data=True, 17 | install_requires=['pyserial'] 18 | ) 19 | --------------------------------------------------------------------------------