├── .github ├── FUNDING.yml └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_ru.md ├── SECURITY.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── firmata4j │ │ ├── Consumer.java │ │ ├── Example.java │ │ ├── I2CDevice.java │ │ ├── I2CEvent.java │ │ ├── I2CExample.java │ │ ├── I2CListener.java │ │ ├── IODevice.java │ │ ├── IODeviceEventListener.java │ │ ├── IOEvent.java │ │ ├── OnMessageReceiveListener.java │ │ ├── OnPinChangeListener.java │ │ ├── OnStartListener.java │ │ ├── OnStopListener.java │ │ ├── Parser.java │ │ ├── Pin.java │ │ ├── PinEventListener.java │ │ ├── firmata │ │ ├── DaemonThreadFactory.java │ │ ├── FirmataDevice.java │ │ ├── FirmataI2CDevice.java │ │ ├── FirmataMessageFactory.java │ │ ├── FirmataPin.java │ │ ├── FirmataWatchdog.java │ │ └── parser │ │ │ ├── FirmataEventType.java │ │ │ ├── FirmataParser.java │ │ │ ├── FirmataToken.java │ │ │ ├── ParsingAnalogMappingState.java │ │ │ ├── ParsingAnalogMessageState.java │ │ │ ├── ParsingCapabilityResponseState.java │ │ │ ├── ParsingCustomSysexMessageState.java │ │ │ ├── ParsingDigitalMessageState.java │ │ │ ├── ParsingExtendedAnalogMessageState.java │ │ │ ├── ParsingFirmwareMessageState.java │ │ │ ├── ParsingI2CMessageState.java │ │ │ ├── ParsingStringMessageState.java │ │ │ ├── ParsingSysexMessageState.java │ │ │ ├── ParsingVersionMessageState.java │ │ │ ├── PinStateParsingState.java │ │ │ └── WaitingForMessageState.java │ │ ├── fsm │ │ ├── AbstractState.java │ │ ├── DirectExecutor.java │ │ ├── Event.java │ │ ├── FiniteStateMachine.java │ │ └── State.java │ │ ├── ssd1306 │ │ ├── MonochromeCanvas.java │ │ ├── SSD1306.java │ │ ├── SSD1306MessageFactory.java │ │ └── SSD1306Token.java │ │ ├── transport │ │ ├── JSSCTransport.java │ │ ├── JSerialCommTransport.java │ │ ├── NetworkTransport.java │ │ ├── SerialTransport.java │ │ └── TransportInterface.java │ │ └── ui │ │ ├── JPin.java │ │ └── JPinboard.java └── resources │ ├── img │ ├── blue-off.png │ ├── blue-on.png │ ├── firmata4j.png │ ├── gray-off.png │ ├── gray-on.png │ ├── green-off.png │ └── green-on.png │ └── nondistributable │ └── indicator.svg └── test ├── java └── org │ └── firmata4j │ └── firmata │ ├── FirmataDeviceTest.java │ ├── fsm │ └── FiniteStateMachineTest.java │ └── parser │ └── FirmataParserTest.java └── resources └── simplelogger.properties /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: maven/firmata4j 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.me/OlegKurbatov'] 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 9 * * 6' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['java'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .idea/ 3 | *.iml 4 | .classpath 5 | .project -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/@olegkurbatov?utm_source=github&utm_medium=button&utm_term=olegkurbatov&utm_campaign=github) 2 | 3 | ## firmata4j 4 | **firmata4j** is a client library of [Firmata](https://github.com/firmata/protocol) 5 | written in Java. The library allows controlling Arduino (or another board) which 6 | runs Firmata protocol from your java program. 7 | 8 | ## Capabilities 9 | - Interaction with a board and its pins in object-oriented style 10 | - Communication over serial port, network or custom transport layer 11 | - Abstraction over details of the protocol 12 | - Provides a UI component that visualize the current state of pins and allows 13 | changing their mode and state 14 | - Allows communicating with I2C devices connected to the board 15 | 16 | ## Installation 17 | 18 | ### Maven 19 | Add the following dependency to `pom.xml` of your project: 20 | 21 | ```xml 22 | 23 | com.github.kurbatov 24 | firmata4j 25 | 2.3.9 26 | 27 | ``` 28 | 29 | If you want to connect to the device via serial port (which is the most probable 30 | case), please include one of the following libraries as a dependency into 31 | the `pom.xml`: 32 | 33 | ```xml 34 | 35 | 36 | com.fazecast 37 | jSerialComm 38 | 2.6.2 39 | 40 | 41 | 42 | io.github.java-native 43 | jssc 44 | 2.9.4 45 | 46 | ``` 47 | 48 | **jssc** is an older library that worked just fine until recently. Now it reveals 49 | [issues on **GraalVM** and latest updates of **Windows 10**](https://github.com/kurbatov/firmata4j/issues/42). 50 | **firmata4j** was using **jssc** by default in versions prior to **2.3.9**. 51 | 52 | **jSerialComm** has proven itself to be working on **GraalVM** and latest 53 | updates of **Windows 10**. 54 | 55 | ## Usage 56 | General scenario of usage is following: 57 | ```java 58 | // construct a Firmata device instance 59 | IODevice device = new FirmataDevice("/dev/ttyUSB0"); // using the name of a port 60 | // IODevice device = new FirmataDevice(new NetworkTransport("192.168.1.18:4334")); // using a network address 61 | // subscribe to events using device.addEventListener(...); 62 | // and/or device.getPin(n).addEventListener(...); 63 | device.start(); // initiate communication to the device 64 | device.ensureInitializationIsDone(); // wait for initialization is done 65 | // sending commands to the board 66 | device.stop(); // stop communication to the device 67 | ``` 68 | 69 | Sending commands to the board may cause the device to emit events. 70 | Registered listeners process the events asynchronously. You can add and remove 71 | listeners along the way. 72 | 73 | You can subscribe to events of the device or its pin. 74 | 75 | ```java 76 | device.addEventListener(new IODeviceEventListener() { 77 | @Override 78 | public void onStart(IOEvent event) { 79 | // since this moment we are sure that the device is initialized 80 | // so we can hide initialization spinners and begin doing cool stuff 81 | System.out.println("Device is ready"); 82 | } 83 | 84 | @Override 85 | public void onStop(IOEvent event) { 86 | // since this moment we are sure that the device is properly shut down 87 | System.out.println("Device has been stopped"); 88 | } 89 | 90 | @Override 91 | public void onPinChange(IOEvent event) { 92 | // here we react to changes of pins' state 93 | Pin pin = event.getPin(); 94 | System.out.println( 95 | String.format( 96 | "Pin %d got a value of %d", 97 | pin.getIndex(), 98 | pin.getValue()) 99 | ); 100 | } 101 | 102 | @Override 103 | public void onMessageReceive(IOEvent event, String message) { 104 | // here we react to receiving a text message from the device 105 | System.out.println(message); 106 | } 107 | }); 108 | ``` 109 | 110 | To obtain more fine grained control you can subscribe to events of a particular 111 | pin. 112 | 113 | ```java 114 | Pin pin = device.getPin(2); 115 | pin.addEventListener(new PinEventListener() { 116 | @Override 117 | public void onModeChange(IOEvent event) { 118 | System.out.println("Mode of the pin has been changed"); 119 | } 120 | 121 | @Override 122 | public void onValueChange(IOEvent event) { 123 | System.out.println("Value of the pin has been changed"); 124 | } 125 | }); 126 | ``` 127 | 128 | You can change the mode and value of a pin: 129 | 130 | ```java 131 | pin.setMode(Pin.Mode.OUTPUT); // our listeners will get event about this change 132 | pin.setValue(1); // and then about this change 133 | ``` 134 | 135 | ## I2C 136 | **firmata4j** supports working with I2C devices. You can obtain a reference to 137 | an I2C device in this way: 138 | 139 | ```java 140 | IODevice device = new FirmataDevice(port); 141 | ... 142 | byte i2cAddress = 0x3C; 143 | I2CDevice i2cDevice = device.getI2CDevice(i2cAddress); 144 | ``` 145 | 146 | You may find convenient writing a wrapper for [`I2CDevice` class](https://github.com/kurbatov/firmata4j/blob/master/src/main/java/org/firmata4j/I2CDevice.java) 147 | to facilitate communication with I2C device. Consider [`SSD1306`](https://github.com/kurbatov/firmata4j/blob/master/src/main/java/org/firmata4j/ssd1306/SSD1306.java) 148 | and [`I2CExample`](https://github.com/kurbatov/firmata4j/blob/master/src/main/java/org/firmata4j/I2CExample.java) 149 | classes as an example of that approach. 150 | 151 | ## Low-Level Messages and Events 152 | 153 | **firmata4j** allows sending an arbitrary binary message to the device. For 154 | example, setting sampling intervall using a low-level message: 155 | 156 | ```java 157 | device.sendMessage(FirmataMessageFactory.setSamplingInterval(12)); 158 | ``` 159 | 160 | Low-level event handlers are supported as well. Those may be useful for 161 | debugging or processing custom messages from a device with modified protocol 162 | implementation. 163 | 164 | ```java 165 | device.addProtocolMessageHandler(FirmataEventType.SYSEX_CUSTOM_MESSAGE, new Consumer() { 166 | @Override 167 | public void accept(Event evt) { 168 | byte[] message = (byte[]) evt.getBodyItem(FirmataEventType.SYSEX_CUSTOM_MESSAGE); 169 | byte messageType = message[0]; 170 | // and so on 171 | } 172 | }); 173 | ``` 174 | 175 | ## Watchdog 176 | 177 | Low-level event handlers allow regestering a watchdog: 178 | 179 | ```java 180 | IODevice device = new FirmataDevice(port); 181 | //... 182 | FirmataWatchdog watchdog = new FirmataWatchdog(3000, new Runnable() { 183 | @Override 184 | public void run() { 185 | // do something when there were no low-level events during 3000 milliseconds 186 | } 187 | }); 188 | device.addProtocolMessageHandler(FirmataEventType.ANY, watchdog); 189 | //... 190 | device.start(); 191 | ``` 192 | 193 | This watchdog implementation gets activated by the first received message since 194 | it subscribed. That's why it should be registered before communication starts. 195 | 196 | ## Visualization 197 | 198 | You can get visual representation of device's pins using `JPinboard` Swing component. 199 | 200 | ```java 201 | JPinboard pinboard = new JPinboard(device); 202 | JFrame frame = new JFrame("Pinboard Example"); 203 | frame.add(pinboard); 204 | frame.pack(); 205 | frame.setVisible(true); 206 | ``` 207 | 208 | `JPinboard` allows setting the pin's mode by choosing one from a context menu of 209 | the pin. State of the output pin can be changed by double clicking on it. 210 | 211 | An example of `JPinboard` usage can be found in 212 | [`org.firmata4j.Example` class](https://github.com/kurbatov/firmata4j/blob/master/src/main/java/org/firmata4j/Example.java). 213 | 214 | ## Versions 215 | **firmata4j** sticks to Firmata protocol versions. The first available version 216 | of **firmata4j** is 2.3.1. 217 | 218 | **firmata4j**-2.3.x will work well with Fimata v. 2.3.x. Actually it should work 219 | with Firmata v. 2.x.x but not necessarily support all of the protocol features. 220 | The first digits of versions must be equal because those stand for incompatible 221 | changes of the protocol. 222 | 223 | ## Uploading Firmata To Arduino 224 | Arduino IDE is shipped with an implementation of Firmata protocol. You can 225 | upload it as follows: 226 | 227 | - Plug your Arduino to the computer 228 | - Launch Arduino IDE 229 | - Select `File -> Examples -> Firmata -> StandardFirmata` in IDE's menu 230 | - Select your board in `Tools -> Board` 231 | - Select the port in `Tools -> Port` (it is already selected if you have uploaded something to your Arduino) 232 | - Click on `Upload` button 233 | 234 | Note that **firmata4j** is focused to be client for the `StandardFirmata` firmware. 235 | Although there are several other firmwares that support Firmata protocol, those 236 | may implement only a featured subset of the protocol. A firmware has to respond 237 | to the following requests in order for **firmata4j** to initialize properly: 238 | 239 | - `REPORT_FIRMWARE` 240 | - `CAPABILITY_QUERY` 241 | - `PIN_STATE_QUERY` 242 | - `ANALOG_MAPPING_QUERY` 243 | 244 | ## Cases 245 | 246 | - [Easy Peripherals for the Internet of Things](https://repositorio-aberto.up.pt/bitstream/10216/84433/2/138208.pdf) 247 | - [Modelovanie a Riadenie Hybridných Systémov s Využitím Petriho Sietí Vyšších Úrovní](http://www.fei.stuba.sk/docs/2016/autoreferaty/autoref_Kucera.pdf) 248 | - [Programmazione di Sistemi Embedded con Linguaggi ad Agenti: un Caso di Studio basato su Jason e Arduino](https://amslaurea.unibo.it/9188/1/cozzolino_francesco_tesi.pdf) 249 | - [Using **firmata4j** in Clojure](https://github.com/cowlike/firmata4j-samples-clojure) 250 | 251 | ## Contributing 252 | Contributions are welcome. If you discover a bug or would like to propose a new 253 | feature, please, [open a new issue](https://github.com/kurbatov/firmata4j/issues/new). 254 | 255 | If you have an improvement to share, please, do the following: 256 | 257 | 1. Fork this repository 258 | 2. Clone your own fork to your machine (`git clone https://github.com//firmata4j.git`) 259 | 3. Create a feature branch (`git checkout -b my-new-feature`) 260 | 4. Change the code 261 | 5. Commit the changes (`git commit -am 'Adds some feature'`) 262 | 6. Push to the branch (`git push origin my-new-feature`) 263 | 7. Create new Pull Request 264 | 265 | ## License 266 | **firmata4j** is distributed under the terms of the MIT License. See the 267 | [LICENSE](https://github.com/kurbatov/firmata4j/blob/master/LICENSE) file. 268 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security updates are provided for the latest version only. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please, create an [issue](https://github.com/kurbatov/firmata4j/issues) to report a vulnerability. 10 | 11 | Follow the status of the issue to track the progress. 12 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.github.kurbatov 5 | firmata4j 6 | 2.3.9-SNAPSHOT 7 | jar 8 | firmata4j 9 | firmata4j is a client library of Firmata. 10 | https://github.com/kurbatov/firmata4j 11 | 12 | 13 | MIT License 14 | https://raw.github.com/kurbatov/firmata4j/master/LICENSE 15 | repo 16 | 17 | 18 | 19 | 20 | Oleg Kurbatov 21 | o.v.kurbatov@gmail.com 22 | https://github.com/kurbatov 23 | 24 | 25 | 26 | https://github.com/kurbatov/firmata4j 27 | scm:git:git@github.com:kurbatov/firmata4j.git 28 | scm:git:git@github.com:kurbatov/firmata4j.git 29 | HEAD 30 | 31 | 32 | 33 | UTF-8 34 | src/main/resources 35 | src/test/resources 36 | 2.9.4 37 | 38 | 39 | 40 | 41 | io.github.java-native 42 | jssc 43 | ${jssc.version} 44 | true 45 | 46 | 47 | com.fazecast 48 | jSerialComm 49 | 2.6.2 50 | true 51 | 52 | 53 | 54 | org.slf4j 55 | slf4j-api 56 | 1.7.25 57 | 58 | 59 | 60 | junit 61 | junit 62 | 4.13.1 63 | test 64 | 65 | 66 | org.slf4j 67 | slf4j-simple 68 | test 69 | 1.7.25 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ${project.resource.directory} 78 | 79 | nondistributable/** 80 | 81 | 82 | 83 | 84 | 85 | ${test.resource.directory} 86 | 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-compiler-plugin 92 | 3.6.1 93 | 94 | 1.7 95 | 1.7 96 | -Xlint:unchecked 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-surefire-plugin 102 | 2.21.0 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-release-plugin 107 | 2.5.3 108 | 109 | true 110 | false 111 | release 112 | deploy 113 | 114 | 115 | 116 | org.codehaus.mojo 117 | exec-maven-plugin 118 | 3.0.0 119 | 120 | org.firmata4j.Example 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | release 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-source-plugin 134 | 3.0.1 135 | 136 | 137 | attach-sources 138 | 139 | jar-no-fork 140 | 141 | 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-javadoc-plugin 147 | 2.10.4 148 | 149 | 150 | attach-javadocs 151 | 152 | jar 153 | 154 | 155 | -Xdoclint:none 156 | 157 | 158 | 159 | 160 | 161 | org.apache.maven.plugins 162 | maven-gpg-plugin 163 | 1.6 164 | 165 | 166 | sign-artifacts 167 | verify 168 | 169 | sign 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | ossrh 182 | https://oss.sonatype.org/content/repositories/snapshots 183 | 184 | 185 | ossrh 186 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/Consumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j; 25 | 26 | /** 27 | * Functional interface that represents an operation that accepts a single input 28 | * argument and returns no result. 29 | * It is replacement for Java 8's {@link java.util.function.Consumer} in an 30 | * attempt to stay pure Java 7 implementation. 31 | * 32 | * @param the type of the input to the operation 33 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 34 | */ 35 | public abstract class Consumer { 36 | 37 | /** 38 | * Performs this operation on the given argument. 39 | * 40 | * @param t the input argument 41 | */ 42 | public abstract void accept(T t); 43 | 44 | /** 45 | * Returns a composed {@code Consumer} that performs, in sequence, this 46 | * operation followed by the {@code after} operation. If performing either 47 | * operation throws an exception, it is relayed to the caller of the 48 | * composed operation. If performing this operation throws an exception, 49 | * the {@code after} operation will not be performed. 50 | * 51 | * @param after the operation to perform after this operation 52 | * @return a composed {@code Consumer} that performs in sequence this 53 | * operation followed by the {@code after} operation 54 | * @throws NullPointerException if {@code after} is null 55 | */ 56 | public Consumer andThen(final Consumer after) { 57 | if (after == null) { 58 | throw new NullPointerException(); 59 | } 60 | final Consumer firstly = this; 61 | return new Consumer() { 62 | @Override 63 | public void accept(T t) { 64 | firstly.accept(t); 65 | after.accept(t); 66 | } 67 | }; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/Example.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | import java.awt.Dimension; 28 | import java.awt.GridBagConstraints; 29 | import java.awt.GridBagLayout; 30 | import java.awt.Toolkit; 31 | import java.awt.event.WindowAdapter; 32 | import java.awt.event.WindowEvent; 33 | import org.firmata4j.firmata.FirmataDevice; 34 | import java.io.IOException; 35 | import java.lang.reflect.InvocationTargetException; 36 | import java.util.logging.Level; 37 | import java.util.logging.Logger; 38 | import java.util.regex.Pattern; 39 | import javax.swing.DefaultComboBoxModel; 40 | import javax.swing.JComboBox; 41 | import javax.swing.JFrame; 42 | import javax.swing.JLabel; 43 | import javax.swing.JOptionPane; 44 | import javax.swing.JPanel; 45 | import javax.swing.SwingUtilities; 46 | import javax.swing.UIManager; 47 | import javax.swing.UnsupportedLookAndFeelException; 48 | import jssc.SerialNativeInterface; 49 | import jssc.SerialPortList; 50 | import org.firmata4j.ui.JPinboard; 51 | 52 | /** 53 | * Example of usage {@link JPinboard}. 54 | * 55 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 56 | */ 57 | public class Example { 58 | 59 | private static final JFrame INITIALIZATION_FRAME = new JFrame(); 60 | 61 | public static void main(String[] args) throws IOException { 62 | try { // set look and feel 63 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 64 | } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { 65 | Logger.getLogger(Example.class.getName()).log(Level.SEVERE, "Cannot load system look and feel.", ex); 66 | } 67 | // requesting a user to define the port name 68 | String port = requestPort(); 69 | final IODevice device = new FirmataDevice(port); 70 | showInitializationMessage(); 71 | device.start(); 72 | try { 73 | device.ensureInitializationIsDone(); 74 | } catch (InterruptedException e) { 75 | JOptionPane.showMessageDialog(INITIALIZATION_FRAME, e.getMessage(), "Connection error", JOptionPane.ERROR_MESSAGE); 76 | System.exit(1); 77 | } 78 | hideInitializationWindow(); 79 | SwingUtilities.invokeLater(new Runnable() { 80 | @Override 81 | public void run() { 82 | JFrame mainFrame = new JFrame("Pinboard Example"); 83 | GridBagLayout layout = new GridBagLayout(); 84 | GridBagConstraints constraints = new GridBagConstraints(); 85 | mainFrame.setLayout(layout); 86 | constraints.gridy = 0; 87 | constraints.fill = GridBagConstraints.BOTH; 88 | constraints.weightx = 1; 89 | constraints.weighty = 1; 90 | JPinboard pinboard = new JPinboard(device); 91 | layout.setConstraints(pinboard, constraints); 92 | mainFrame.add(pinboard); 93 | mainFrame.pack(); 94 | mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 95 | mainFrame.addWindowListener(new WindowAdapter() { 96 | @Override 97 | public void windowClosing(WindowEvent e) { 98 | try { 99 | device.stop(); 100 | } catch (IOException ex) { 101 | throw new RuntimeException(ex); 102 | } 103 | super.windowClosing(e); 104 | } 105 | }); 106 | mainFrame.setVisible(true); 107 | } 108 | }); 109 | } 110 | 111 | @SuppressWarnings("unchecked") 112 | private static String requestPort() { 113 | JComboBox portNameSelector = new JComboBox<>(); 114 | portNameSelector.setModel(new DefaultComboBoxModel()); 115 | String[] portNames; 116 | if (SerialNativeInterface.getOsType() == SerialNativeInterface.OS_MAC_OS_X) { 117 | // for MAC OS default pattern of jssc library is too restrictive 118 | portNames = SerialPortList.getPortNames("/dev/", Pattern.compile("tty\\..*")); 119 | } else { 120 | portNames = SerialPortList.getPortNames(); 121 | } 122 | for (String portName : portNames) { 123 | portNameSelector.addItem(portName); 124 | } 125 | if (portNameSelector.getItemCount() == 0) { 126 | JOptionPane.showMessageDialog(null, "Cannot find any serial port", "Error", JOptionPane.ERROR_MESSAGE); 127 | System.exit(1); 128 | } 129 | JPanel panel = new JPanel(); 130 | panel.setLayout(new GridBagLayout()); 131 | panel.add(new JLabel("Port ")); 132 | panel.add(portNameSelector); 133 | if (JOptionPane.showConfirmDialog(null, panel, "Select the port", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { 134 | return portNameSelector.getSelectedItem().toString(); 135 | } else { 136 | System.exit(0); 137 | } 138 | return ""; 139 | } 140 | 141 | private static void showInitializationMessage() { 142 | try { 143 | SwingUtilities.invokeAndWait(new Runnable() { 144 | @Override 145 | public void run() { 146 | JFrame frame = INITIALIZATION_FRAME; 147 | frame.setUndecorated(true); 148 | JLabel label = new JLabel("Connecting to device"); 149 | label.setHorizontalAlignment(JLabel.CENTER); 150 | frame.add(label); 151 | frame.pack(); 152 | frame.setSize(frame.getWidth() + 40, frame.getHeight() + 40); 153 | Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize(); 154 | int x = (int) ((dimension.getWidth() - frame.getWidth()) / 2); 155 | int y = (int) ((dimension.getHeight() - frame.getHeight()) / 2); 156 | frame.setLocation(x, y); 157 | frame.setVisible(true); 158 | } 159 | }); 160 | } catch (InterruptedException | InvocationTargetException ex) { 161 | throw new RuntimeException(ex); 162 | } 163 | } 164 | 165 | private static void hideInitializationWindow() { 166 | try { 167 | SwingUtilities.invokeAndWait(new Runnable() { 168 | @Override 169 | public void run() { 170 | INITIALIZATION_FRAME.setVisible(false); 171 | INITIALIZATION_FRAME.dispose(); 172 | } 173 | }); 174 | } catch (InterruptedException | InvocationTargetException ex) { 175 | throw new RuntimeException(ex); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/I2CDevice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | import java.io.IOException; 28 | 29 | /** 30 | * Represents an I2C device and encapsulates logic to communicate to it. 31 | * 32 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 33 | */ 34 | public interface I2CDevice { 35 | 36 | /** 37 | * Returns address of I2C device. 38 | */ 39 | byte getAddress(); 40 | 41 | /** 42 | * Sets delay between writing and reading data to/from I2C device. 43 | * 44 | * @param delay delay between the moment the device is written to and the 45 | * moment when the data can be read from it 46 | * @throws IOException when delay cannot be set due to communication fail 47 | */ 48 | void setDelay(int delay) throws IOException; 49 | 50 | /** 51 | * Sends data to the I2C device. 52 | * 53 | * @param data data to send 54 | * @throws IOException when the data cannot be sent due to communication 55 | * fail 56 | */ 57 | void tell(byte... data) throws IOException; 58 | 59 | /** 60 | * Requests data from I2C device and receives it only once. Specified 61 | * listener is exclusive processor of the received data. 62 | * 63 | * @param responseLength length of expected response 64 | * @param listener processor of the response 65 | * @throws IOException when I2C device cannot be asked for data due to 66 | * communication fail 67 | */ 68 | void ask(byte responseLength, I2CListener listener) throws IOException; 69 | 70 | /** 71 | * Requests data from I2C device and receives it only once. Specified 72 | * listener is exclusive processor of the received data. 73 | * 74 | * @param register Device register to read from. 75 | * @param responseLength length of expected response 76 | * @param listener processor of the response 77 | * @throws IOException when I2C device cannot be asked for data due to 78 | * communication fail 79 | */ 80 | void ask(int register, byte responseLength, I2CListener listener) throws IOException; 81 | 82 | /** 83 | * Registers a listener as a receiver of regular updates from I2C device. 84 | *
85 | * The listener starts receiving updates after 86 | * {@link #startReceivingUpdates} invocation. 87 | * 88 | * @param listener the object that receives updates 89 | */ 90 | void subscribe(I2CListener listener); 91 | 92 | /** 93 | * Unregisters a listener from receiving of regular updates from I2C device. 94 | * 95 | * @param listener the object that used to receive updates 96 | */ 97 | void unsubscribe(I2CListener listener); 98 | 99 | /** 100 | * Tells I2C device to send data continuously from specified register as it 101 | * available. 102 | * 103 | * @param register the device register to read from 104 | * @param messageLength length of expected message 105 | * @throws IOException when I2C device cannot be asked for data due to 106 | * communication fail 107 | * @return true if receiving updates just started or false otherwise (if it 108 | * have been already going for example) 109 | */ 110 | boolean startReceivingUpdates(int register, byte messageLength) throws IOException; 111 | 112 | /** 113 | * Tells I2C device to send data continuously as it available. 114 | * 115 | * @param messageLength length of expected message 116 | * @throws IOException when I2C device cannot be asked for data due to 117 | * communication fail 118 | * @return true if receiving updates just started or false otherwise (if it 119 | * have been already going for example) 120 | */ 121 | boolean startReceivingUpdates(byte messageLength) throws IOException; 122 | 123 | /** 124 | * Tells I2C device to stop sending data continuously. 125 | * 126 | * @throws IOException when I2C device cannot be asked to stop due to 127 | * communication fail 128 | */ 129 | void stopReceivingUpdates() throws IOException; 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/I2CEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | import java.util.Arrays; 28 | 29 | /** 30 | * This event occurs when I2C device transmits some data. {@link I2CListener} 31 | * processes this kind of events. 32 | * 33 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 34 | */ 35 | public class I2CEvent { 36 | 37 | private final I2CDevice device; 38 | private final int register; 39 | private final byte[] data; 40 | 41 | public I2CEvent(I2CDevice device, int register, byte[] data) { 42 | this.device = device; 43 | this.register = register; 44 | this.data = data; 45 | } 46 | 47 | /** 48 | * Returns data received from {@link I2CDevice}. 49 | */ 50 | public byte[] getData() { 51 | return data; 52 | } 53 | 54 | /** 55 | * Returns register address received from {@link I2CDevice}. 56 | */ 57 | public int getRegister() { 58 | return register; 59 | } 60 | 61 | /** 62 | * Returns {@link I2CDevice} which sent a piece of data. 63 | */ 64 | public I2CDevice getDevice() { 65 | return device; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return String.format("I2CEvent [device=%s, register=0x%02X, data=%s]", device, register, Arrays.toString(data)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/I2CListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * Describes processing of data received from {@link I2CDevice}. 29 | * 30 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 31 | */ 32 | public interface I2CListener { 33 | 34 | void onReceive(I2CEvent event); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/IODevice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | import java.io.IOException; 28 | import java.util.Set; 29 | import org.firmata4j.fsm.Event; 30 | 31 | /** 32 | * This interface describes a device which is able to receive and transmit 33 | * signals. It has a set of pins that represent connections of the device to 34 | * external signal receivers and transmitters. 35 | * 36 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 37 | */ 38 | public interface IODevice { 39 | 40 | /** 41 | * Initiates communication with hardware.
42 | * This method initialize the {@link IODevice} instance. Initialization may 43 | * take some time depending on speed of communication to hardware.
44 | * To check whether the device is ready, use {@link #isReady()}.
45 | * If you want to wait until the device is initializing and continue working 46 | * with it, use {@link #ensureInitializationIsDone()}.
47 | * If you develop application in asynchronous style, you may want to 48 | * register an event listener that will receive a message when the device is 49 | * ready. 50 | * 51 | * @throws IOException when communication cannot be established 52 | * @see IODeviceEventListener#onStart(org.firmata4j.IOEvent) 53 | */ 54 | void start() throws IOException; 55 | 56 | /** 57 | * Terminates communication with hardware.
58 | * When communication is terminated, an event is published to 59 | * {@link IODeviceEventListener}s. 60 | * 61 | * @throws IOException when communication cannot be properly terminated 62 | * @see IODeviceEventListener#onStop(org.firmata4j.IOEvent) 63 | */ 64 | void stop() throws IOException; 65 | 66 | /** 67 | * Waits for initialization is done.
68 | * Use this method if you want to be sure that device is ready to 69 | * communicate. 70 | * 71 | * @throws InterruptedException when the device cannot be started or waiting 72 | * for initialization is interrupted or connection has not been established 73 | * during timeout. 74 | */ 75 | void ensureInitializationIsDone() throws InterruptedException; 76 | 77 | /** 78 | * Checks whether the device is fully initialized and ready to use. 79 | * 80 | * @return true if device is ready, false otherwise 81 | */ 82 | boolean isReady(); 83 | 84 | /** 85 | * Returns a set of pins of the device. 86 | * 87 | * @return set of device's pins 88 | * @see Pin 89 | */ 90 | Set getPins(); 91 | 92 | /** 93 | * Returns count of pins of the device. 94 | * 95 | * @return count of pins 96 | */ 97 | int getPinsCount(); 98 | 99 | /** 100 | * Returns a pin by its index on device. The index should be less than 101 | * result returned by {@link #getPinsCount()}. 102 | * 103 | * @param index index of the pin 104 | * @return the pin 105 | */ 106 | Pin getPin(int index); 107 | 108 | /** 109 | * Returns I2C device by its address. 110 | * 111 | * @param address 112 | * @return I2C device 113 | * @throws IOException when communication to the IO device failed 114 | */ 115 | I2CDevice getI2CDevice(byte address) throws IOException; 116 | 117 | /** 118 | * Adds the specified listener to receive events from this device. 119 | * 120 | * @param listener the listener 121 | */ 122 | void addEventListener(IODeviceEventListener listener); 123 | 124 | /** 125 | * Removes the specified listener so that it no longer receives events from 126 | * this device. 127 | * 128 | * @param listener the listener 129 | */ 130 | void removeEventListener(IODeviceEventListener listener); 131 | 132 | /** 133 | * Returns the name of a protocol that the device uses. 134 | * 135 | * @return the name of a protocol 136 | */ 137 | String getProtocol(); 138 | 139 | /** 140 | * Adds handler for low-level events of specified type. 141 | * 142 | * "*" is a special type that matches to any message. The handlers registed 143 | * for that type will receive an even after the hadlers of specific type. 144 | * 145 | * @param messageType type of low-level event 146 | * @param handler handler of the event 147 | */ 148 | void addProtocolMessageHandler(String messageType, Consumer handler); 149 | 150 | /** 151 | * Sends text message to device. 152 | * 153 | * @param message the message 154 | * @throws IOException when sending a message fails 155 | */ 156 | void sendMessage(String message) throws IOException; 157 | 158 | /** 159 | * Sends binary message to device. 160 | * 161 | * @param msg message 162 | * @throws IOException when sending the message fails 163 | */ 164 | void sendMessage(byte... msg) throws IOException; 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/IODeviceEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * This interface describes an object that can receive and handle events from an 29 | * {@link IODevice}. 30 | * 31 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 32 | */ 33 | public interface IODeviceEventListener { 34 | 35 | /** 36 | * Invoked when an {@link IODevice} has been successfully started 37 | * and initialized. 38 | * 39 | * @param event the event 40 | */ 41 | void onStart(IOEvent event); 42 | 43 | /** 44 | * Invoked when communication with {@link IODevice} has been 45 | * successfully terminated. 46 | * 47 | * @param event the event 48 | */ 49 | void onStop(IOEvent event); 50 | 51 | /** 52 | * Invoked when the state of one of device's pins has been changed. 53 | * It can be change of mode or a value. 54 | * 55 | * @param event the event 56 | */ 57 | void onPinChange(IOEvent event); 58 | 59 | /** 60 | * Invoked when a string message has been received from the device. 61 | * 62 | * @param event the event 63 | * @param message the message 64 | */ 65 | void onMessageReceive(IOEvent event, String message); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/IOEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * An event which indicates that the state of an {@link IODevice} changed. 29 | * 30 | * @author @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 31 | */ 32 | public class IOEvent { 33 | 34 | private final IODevice device; 35 | private final Pin pin; 36 | private final long value; 37 | private final long timestamp; 38 | 39 | /** 40 | * Constructs the event is relevant to {@link IODevice} as a whole. 41 | * 42 | * @param device the device that originated the event 43 | */ 44 | public IOEvent(IODevice device) { 45 | this(device, System.currentTimeMillis()); 46 | } 47 | 48 | /** 49 | * Constructs the event is relevant to {@link IODevice} as a whole. 50 | * 51 | * This constructor allows setting the timestamp of event. 52 | * 53 | * @param device the device that originated the event 54 | * @param timestamp the timestamp of event 55 | */ 56 | public IOEvent(IODevice device, long timestamp) { 57 | this.device = device; 58 | this.pin = null; 59 | this.value = 0; 60 | this.timestamp = timestamp; 61 | } 62 | 63 | /** 64 | * Constructs the event is relevant to a particular {@link Pin}. 65 | * 66 | * @param pin the pin that originated the event 67 | */ 68 | public IOEvent(Pin pin) { 69 | this(pin, System.currentTimeMillis()); 70 | } 71 | 72 | /** 73 | * Constructs the event is relevant to a particular {@link Pin}. 74 | * 75 | * This constructor allows setting the timestamp of event. 76 | * 77 | * @param pin the pin that originated the event 78 | * @param timestamp the timestamp of event 79 | */ 80 | public IOEvent(Pin pin, long timestamp) { 81 | this.device = pin.getDevice(); 82 | this.pin = pin; 83 | this.value = pin.getValue(); 84 | this.timestamp = timestamp; 85 | } 86 | 87 | /** 88 | * Returns the device that relates to the event. 89 | * 90 | * @return the device in which the event has occurred 91 | */ 92 | public IODevice getDevice() { 93 | return device; 94 | } 95 | 96 | /** 97 | * Returns the pin that relates to the event. If the event does not involve 98 | * any particular pin, it returns null. 99 | * 100 | * @return the pin that originated the event or null if a pin does not 101 | * involved in the event 102 | */ 103 | public Pin getPin() { 104 | return pin; 105 | } 106 | 107 | /** 108 | * Returns the value the pin received. 109 | * 110 | * If the event is not about pin, always returns 0. 111 | * 112 | * @return the value the pin received 113 | */ 114 | public long getValue() { 115 | return value; 116 | } 117 | 118 | /** 119 | * Returns the timestamp of the event. 120 | * 121 | * @return timestamp of the event 122 | */ 123 | public long getTimestamp() { 124 | return timestamp; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/OnMessageReceiveListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * Handles events of receiving a message. Ignores the other events. 29 | * 30 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 31 | */ 32 | public abstract class OnMessageReceiveListener implements IODeviceEventListener { 33 | 34 | @Override 35 | public void onStart(IOEvent event) { 36 | 37 | } 38 | 39 | @Override 40 | public void onStop(IOEvent event) { 41 | 42 | } 43 | 44 | @Override 45 | public void onPinChange(IOEvent event) { 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/OnPinChangeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * Handles events of a pin change. Ignores the other events. 29 | * 30 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 31 | */ 32 | public abstract class OnPinChangeListener extends Consumer implements IODeviceEventListener { 33 | 34 | @Override 35 | public void onStart(IOEvent event) { 36 | 37 | } 38 | 39 | @Override 40 | public void onStop(IOEvent event) { 41 | 42 | } 43 | 44 | @Override 45 | public void onPinChange(IOEvent event) { 46 | accept(event); 47 | } 48 | 49 | @Override 50 | public void onMessageReceive(IOEvent event, String message) { 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/OnStartListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * Handles events when {@link IODevice} starts. Ignores other events. 29 | * 30 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 31 | */ 32 | public abstract class OnStartListener extends Consumer implements IODeviceEventListener { 33 | 34 | @Override 35 | public void onStart(IOEvent event) { 36 | accept(event); 37 | } 38 | 39 | @Override 40 | public void onStop(IOEvent event) { 41 | 42 | } 43 | 44 | @Override 45 | public void onPinChange(IOEvent event) { 46 | 47 | } 48 | 49 | @Override 50 | public void onMessageReceive(IOEvent event, String message) { 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/OnStopListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * Handles events when {@link IODevice} stops. Ignores other events. 29 | * 30 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 31 | */ 32 | public abstract class OnStopListener extends Consumer implements IODeviceEventListener { 33 | 34 | @Override 35 | public void onStart(IOEvent event) { 36 | 37 | } 38 | 39 | @Override 40 | public void onStop(IOEvent event) { 41 | accept(event); 42 | } 43 | 44 | @Override 45 | public void onPinChange(IOEvent event) { 46 | 47 | } 48 | 49 | @Override 50 | public void onMessageReceive(IOEvent event, String message) { 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/Parser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j; 25 | 26 | /** 27 | * Processes byte-stream by the rules of underlying protocol. 28 | * 29 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 30 | */ 31 | public interface Parser { 32 | 33 | /** 34 | * Starts processing of input data. 35 | */ 36 | void start(); 37 | 38 | /** 39 | * Stops processing of input data. 40 | */ 41 | void stop(); 42 | 43 | /** 44 | * Processes the input data. 45 | * 46 | * @param bytes the data 47 | */ 48 | void parse(byte[] bytes); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/Pin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | import java.io.IOException; 28 | import java.util.Set; 29 | import org.firmata4j.firmata.parser.FirmataToken; 30 | 31 | /** 32 | * A pin is a connector of an {@link IODevice} to external signal receiver or 33 | * transmitter. 34 | * 35 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 36 | */ 37 | public interface Pin { 38 | 39 | /** 40 | * Mode represents particular duty of a pin. 41 | */ 42 | enum Mode { 43 | 44 | /** 45 | * Digital pin in input mode 46 | */ 47 | INPUT, 48 | /** 49 | * Digital pin in output mode 50 | */ 51 | OUTPUT, 52 | /** 53 | * Analog pin in analog input mode 54 | */ 55 | ANALOG, 56 | /** 57 | * Digital pin in PWM output mode 58 | */ 59 | PWM, 60 | /** 61 | * Digital pin in Servo output mode 62 | */ 63 | SERVO, 64 | /** 65 | * shiftIn/shiftOut mode 66 | */ 67 | SHIFT, 68 | /** 69 | * Pin included in I2C setup 70 | */ 71 | I2C, 72 | /** 73 | * Pin configured for 1-wire 74 | */ 75 | ONEWIRE, 76 | /** 77 | * Pin configured for stepper motor 78 | */ 79 | STEPPER, 80 | /** 81 | * Pin configured for rotary encoders 82 | */ 83 | ENCODER, 84 | /** 85 | * Pin configured for serial communication 86 | */ 87 | SERIAL, 88 | /** 89 | * Enable internal pull-up resistor for pin 90 | */ 91 | PULLUP, 92 | 93 | // add new modes here 94 | 95 | /** 96 | * Indicates a mode that this client library doesn't support 97 | */ 98 | UNSUPPORTED, 99 | /** 100 | * Pin configured to be ignored by digitalWrite and capabilityResponse 101 | */ 102 | IGNORED; 103 | 104 | /** 105 | * Resolves a mode-token from firmata message to enum value. 106 | * 107 | * @param modeToken token that stands for mode 108 | * @return mode 109 | */ 110 | public static Mode resolve(byte modeToken) { 111 | if (modeToken == FirmataToken.PIN_MODE_IGNORE) { 112 | return IGNORED; 113 | } 114 | if (modeToken > FirmataToken.TOTAL_PIN_MODES) { 115 | return UNSUPPORTED; 116 | } 117 | return values()[modeToken]; 118 | } 119 | } 120 | 121 | /** 122 | * Return an {@link IODevice} the pin belongs to. 123 | * 124 | * @return the pin's device 125 | */ 126 | IODevice getDevice(); 127 | 128 | /** 129 | * Returns the index of the pin on its device. 130 | * 131 | * @return the index of the pin 132 | */ 133 | byte getIndex(); 134 | 135 | /** 136 | * Returns current mode of the pin. 137 | * 138 | * @return current mode of the pin 139 | */ 140 | Mode getMode(); 141 | 142 | /** 143 | * Assigns new mode to the pin. 144 | * 145 | * @param mode the mode the pin should get into 146 | * @throws IOException when assigning is failed due a communication issue 147 | * @throws IllegalArgumentException when the pin does not support the mode 148 | */ 149 | void setMode(Mode mode) throws IOException, IllegalArgumentException; 150 | 151 | /** 152 | * 153 | * @param minPulse servo moter control pulse with [µs] on setValue(0) 154 | * @param maxPulse servo moter control pulse with [µs] on setValue(180) 155 | * @throws IOException when assigning is failed due a communication issue 156 | * @throws IllegalArgumentException when the pin does not support the mode 157 | */ 158 | void setServoMode(int minPulse, int maxPulse) throws IOException, IllegalArgumentException; 159 | 160 | /** 161 | * Checks if the pin supports the mode 162 | * 163 | * @param mode the mode 164 | * @return true if the pin supports the mode, false otherwise 165 | */ 166 | boolean supports(Mode mode); 167 | 168 | /** 169 | * Returns a set of modes supported by the pin. 170 | * 171 | * @return set of supported modes 172 | */ 173 | Set getSupportedModes(); 174 | 175 | /** 176 | * Returns current value of the pin. 177 | * 178 | * @return current value of the pin 179 | */ 180 | long getValue(); 181 | 182 | /** 183 | * Sets the value to the pin. It is impossible to set a value to a pin in 184 | * input mode such as {@link Mode#INPUT} or {@link Mode#ANALOG}. 185 | * 186 | * @param value the value to be assigned to the pin 187 | * @throws IOException when setting fails due a communication issue 188 | * @throws IllegalStateException when the pin is in input mode such as 189 | * {@link Mode#INPUT} or {@link Mode#ANALOG}. 190 | */ 191 | void setValue(long value) throws IOException, IllegalStateException; 192 | 193 | /** 194 | * Adds the specified listener to receive events from this pin. 195 | * 196 | * @param listener the listener 197 | */ 198 | void addEventListener(PinEventListener listener); 199 | 200 | /** 201 | * Removes the specified listener so that it no longer receives events from 202 | * this pin. 203 | * 204 | * @param listener the listener 205 | */ 206 | void removeEventListener(PinEventListener listener); 207 | 208 | 209 | /** 210 | * Remove all listeners from this pin. 211 | * 212 | */ 213 | void removeAllEventListeners(); 214 | 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/PinEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j; 26 | 27 | /** 28 | * The listener interface for receiving events from {@link Pin}. When pin's mode 29 | * or value changes, the relevant method in the listener object is invoked. 30 | * 31 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 32 | */ 33 | public interface PinEventListener { 34 | 35 | /** 36 | * Invoked when pin's mode changes. 37 | * @param event Information about the event that was triggered 38 | */ 39 | void onModeChange(IOEvent event); 40 | 41 | /** 42 | * Invoked when pin's value changes. 43 | * @param event Information about the event that was triggered 44 | */ 45 | void onValueChange(IOEvent event); 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/DaemonThreadFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata; 26 | 27 | import java.util.concurrent.ThreadFactory; 28 | import java.util.concurrent.atomic.AtomicInteger; 29 | 30 | /** 31 | * Creates daemon threads. 32 | * 33 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 34 | */ 35 | public class DaemonThreadFactory implements ThreadFactory { 36 | 37 | private final AtomicInteger counter = new AtomicInteger(); 38 | 39 | private final String namePrefix; 40 | 41 | /** 42 | * Creates thread factory that spawns threads with specified name prefix. 43 | * 44 | * @param namePrefix prefix of thread's names 45 | */ 46 | public DaemonThreadFactory(String namePrefix) { 47 | this.namePrefix = namePrefix; 48 | } 49 | 50 | @Override 51 | public Thread newThread(Runnable r) { 52 | Thread result = new Thread(r, String.format("%s-%d", namePrefix, counter.incrementAndGet())); 53 | result.setDaemon(true); 54 | return result; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/FirmataI2CDevice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.firmata; 25 | 26 | import java.io.IOException; 27 | import java.util.Map; 28 | import java.util.Set; 29 | import java.util.concurrent.ConcurrentHashMap; 30 | import java.util.concurrent.ConcurrentSkipListSet; 31 | import java.util.concurrent.atomic.AtomicBoolean; 32 | import org.firmata4j.I2CDevice; 33 | import org.firmata4j.I2CEvent; 34 | import org.firmata4j.I2CListener; 35 | 36 | /** 37 | * Represents an I2C device and encapsulates communication logic using Firmata 38 | * protocol. 39 | * 40 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 41 | */ 42 | public class FirmataI2CDevice implements I2CDevice { 43 | 44 | public static final int REGISTER_NOT_SET = -1; 45 | 46 | private final FirmataDevice masterDevice; 47 | 48 | private final byte address; 49 | 50 | private final AtomicBoolean receivingUpdates = new AtomicBoolean(false); 51 | 52 | private final Map callbacks = new ConcurrentHashMap<>(); 53 | 54 | private final Set subscribers = new ConcurrentSkipListSet<>(); 55 | 56 | FirmataI2CDevice(FirmataDevice masterDevice, byte address) { 57 | this.masterDevice = masterDevice; 58 | this.address = address; 59 | } 60 | 61 | @Override 62 | public byte getAddress() { 63 | return address; 64 | } 65 | 66 | @Override 67 | public void setDelay(int delay) throws IOException { 68 | masterDevice.setI2CDelay(delay); 69 | } 70 | 71 | @Override 72 | public void tell(byte... data) throws IOException { 73 | masterDevice.sendMessage(FirmataMessageFactory.i2cWriteRequest(address, data)); 74 | } 75 | 76 | @Override 77 | public void ask(byte responseLength, I2CListener listener) throws IOException { 78 | ask(REGISTER_NOT_SET, responseLength, listener); 79 | } 80 | 81 | @Override 82 | public void ask(int register, byte responseLength, I2CListener listener) throws IOException { 83 | callbacks.put(register, listener); 84 | masterDevice.sendMessage(FirmataMessageFactory.i2cReadRequest(address, register, responseLength, false)); 85 | } 86 | 87 | @Override 88 | public void subscribe(I2CListener listener) { 89 | subscribers.add(listener); 90 | } 91 | 92 | @Override 93 | public void unsubscribe(I2CListener listener) { 94 | subscribers.remove(listener); 95 | } 96 | 97 | @Override 98 | public boolean startReceivingUpdates(int register, byte messageLength) throws IOException { 99 | boolean result = receivingUpdates.compareAndSet(false, true); 100 | if (result) { 101 | masterDevice.sendMessage(FirmataMessageFactory.i2cReadRequest(address, register, messageLength, true)); 102 | } 103 | return result; 104 | } 105 | 106 | @Override 107 | public boolean startReceivingUpdates(byte messageLength) throws IOException { 108 | boolean result = receivingUpdates.compareAndSet(false, true); 109 | if (result) { 110 | masterDevice.sendMessage(FirmataMessageFactory.i2cReadRequest(address, REGISTER_NOT_SET, messageLength, true)); 111 | } 112 | return result; 113 | } 114 | 115 | @Override 116 | public void stopReceivingUpdates() throws IOException { 117 | if (receivingUpdates.compareAndSet(true, false)) { 118 | masterDevice.sendMessage(FirmataMessageFactory.i2cStopContinuousRequest(address)); 119 | } 120 | } 121 | 122 | /** 123 | * {@link FirmataDevice} calls this method when receives a message from I2C 124 | * device. 125 | * 126 | * @param register The device register read from. 127 | * @param message actual data from I2C device 128 | */ 129 | void onReceive(int register, byte[] message) { 130 | I2CEvent evt = new I2CEvent(this, register, message); 131 | I2CListener listener = callbacks.remove(register); 132 | if (listener == null) { 133 | for (I2CListener subscriber : subscribers) { 134 | subscriber.onReceive(evt); 135 | } 136 | } else { 137 | listener.onReceive(evt); 138 | } 139 | } 140 | 141 | @Override 142 | public String toString() { 143 | return String.format("FirmataI2CDevice [address=0x%02X]", address); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/FirmataPin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata; 26 | 27 | import java.io.IOException; 28 | import java.util.Collections; 29 | import java.util.EnumSet; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | 33 | import org.firmata4j.IOEvent; 34 | import org.firmata4j.Pin; 35 | import org.firmata4j.PinEventListener; 36 | 37 | /** 38 | * This class contains implementation of Firmata pin. 39 | * 40 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 41 | */ 42 | public class FirmataPin implements Pin { 43 | 44 | private final FirmataDevice device; 45 | private final byte pinId; 46 | private final Set supportedModes = Collections.synchronizedSet(EnumSet.noneOf(Mode.class)); 47 | private final Set listeners = Collections.synchronizedSet(new HashSet()); 48 | private volatile Mode currentMode; 49 | private volatile long currentValue; 50 | 51 | /** 52 | * Constructs Firmata pin for the specified device. 53 | * 54 | * @param device the device the pin belongs to 55 | * @param index the index of pin on the device 56 | */ 57 | public FirmataPin(FirmataDevice device, byte index) { 58 | this.device = device; 59 | this.pinId = index; 60 | } 61 | 62 | @Override 63 | public FirmataDevice getDevice() { 64 | return device; 65 | } 66 | 67 | @Override 68 | public byte getIndex() { 69 | return pinId; 70 | } 71 | 72 | @Override 73 | public Mode getMode() { 74 | return currentMode; 75 | } 76 | 77 | @Override 78 | public void setMode(Mode mode) throws IOException { 79 | setMode(mode, 544, 2400); // Arduino defaults (https://www.arduino.cc/en/Reference/ServoAttach) 80 | } 81 | 82 | @Override 83 | public void setServoMode(int minPulse, int maxPulse) throws IOException { 84 | setMode(Mode.SERVO, minPulse, maxPulse); 85 | } 86 | 87 | private synchronized void setMode(Mode mode, int minPulse, int maxPulse) throws IOException { 88 | if (supports(mode)) { 89 | if (currentMode != mode) { 90 | if (mode == Mode.SERVO) { 91 | getDevice().sendMessage(FirmataMessageFactory.servoConfig(pinId, minPulse, maxPulse)); 92 | // The currentValue for a servo is unknown as the motor is 93 | // send to the 1.5ms position when pinStateRequest is invoked 94 | currentValue = -1; 95 | } 96 | getDevice().sendMessage(FirmataMessageFactory.setMode(pinId, mode)); 97 | currentMode = mode; 98 | IOEvent evt = new IOEvent(this); 99 | getDevice().pinChanged(evt); 100 | for (PinEventListener listener : listeners) { 101 | listener.onModeChange(evt); 102 | } 103 | getDevice().sendMessage(FirmataMessageFactory.pinStateRequest(pinId)); 104 | } 105 | } else { 106 | throw new IllegalArgumentException(String.format("Pin %d does not support mode %s", pinId, mode)); 107 | } 108 | } 109 | 110 | @Override 111 | public boolean supports(Mode mode) { 112 | return supportedModes.contains(mode); 113 | } 114 | 115 | @Override 116 | @SuppressWarnings("unchecked") 117 | public Set getSupportedModes() { 118 | if (supportedModes.isEmpty()) { 119 | return Collections.EMPTY_SET; 120 | } else { 121 | return EnumSet.copyOf(supportedModes); 122 | } 123 | } 124 | 125 | @Override 126 | public long getValue() { 127 | return currentValue; 128 | } 129 | 130 | @Override 131 | public synchronized void setValue(long value) throws IOException, IllegalStateException { 132 | byte[] message; 133 | long newValue; 134 | if (currentMode == Mode.OUTPUT) { 135 | //have to calculate the value of whole port (8-pin set) the pin sits in 136 | byte portId = (byte) (pinId / 8); 137 | byte pinInPort = (byte) (pinId % 8); 138 | byte portValue = 0; 139 | for (int i = 0; i < 8; i++) { 140 | Pin p = device.getPin(portId * 8 + i); 141 | if (p.getMode() == Mode.OUTPUT && p.getValue() > 0) { 142 | portValue |= 1 << i; 143 | } 144 | } 145 | byte bitmask = (byte) (1 << pinInPort); 146 | boolean val = value > 0; 147 | if (val) { 148 | portValue |= bitmask; 149 | } else { 150 | portValue &= ((byte) ~bitmask); 151 | } 152 | message = FirmataMessageFactory.setDigitalPinValue(portId, portValue); 153 | newValue = val ? 1 : 0; 154 | } else if (currentMode == Mode.ANALOG || currentMode == Mode.PWM || currentMode == Mode.SERVO) { 155 | message = FirmataMessageFactory.setAnalogPinValue(pinId, value); 156 | newValue = value; 157 | } else { 158 | throw new IllegalStateException(String.format("Port %d is in %s mode and its value cannot be set.", pinId, currentMode)); 159 | } 160 | if (currentValue != newValue) { 161 | device.sendMessage(message); 162 | updateValue(value); 163 | } 164 | } 165 | 166 | @Override 167 | public void addEventListener(PinEventListener listener) { 168 | listeners.add(listener); 169 | } 170 | 171 | @Override 172 | public void removeEventListener(PinEventListener listener) { 173 | listeners.remove(listener); 174 | } 175 | 176 | 177 | @Override 178 | public void removeAllEventListeners() { 179 | listeners.clear(); 180 | } 181 | 182 | 183 | /** 184 | * Adds supported mode to the pin. 185 | * 186 | * @param mode the mode 187 | */ 188 | void addSupportedMode(Mode mode) { 189 | supportedModes.add(mode); 190 | } 191 | 192 | /** 193 | * Sets initial mode of a pin. This method bypasses standard 194 | * {@link #setMode(org.firmata4j.Pin.Mode)} to avoid sending a 195 | * message to hardware. 196 | * 197 | * @param mode initial mode 198 | */ 199 | synchronized void initMode(Mode mode) { 200 | currentMode = mode; 201 | } 202 | 203 | /** 204 | * Sets initial value of a pin. This method bypasses standard 205 | * {@link #setValue(long)} to avoid sending a message to hardware. 206 | * 207 | * @param value initial value 208 | */ 209 | synchronized void initValue(long value) { 210 | currentValue = value; 211 | } 212 | 213 | /** 214 | * Permits the {@link FirmataDevice} to update input pin value. 215 | * 216 | * @param value the new value 217 | */ 218 | synchronized void updateValue(long value) { 219 | if (value != currentValue) { 220 | currentValue = value; 221 | IOEvent evt = new IOEvent(this); 222 | getDevice().pinChanged(evt); // the device listeners receive the event first 223 | for (PinEventListener listener : listeners) { // then pin listeners receive the event 224 | listener.onValueChange(evt); 225 | } 226 | } 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/FirmataWatchdog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.firmata; 25 | 26 | import java.util.concurrent.Executors; 27 | import java.util.concurrent.ScheduledExecutorService; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | import org.firmata4j.Consumer; 31 | import org.firmata4j.fsm.Event; 32 | 33 | /** 34 | * Implementation of low-level event hadler that supposed to be used as a 35 | * watchdog. 36 | * 37 | * It gets activated by the first event received since the watchdog subscribed. 38 | * 39 | *

Example: 40 | *

 41 |  * IODevice device = new FirmataDevice(port);
 42 |  * //...
 43 |  * FirmataWatchdog watchdog = new FirmataWatchdog(3000, new Runnable() {
 44 |  *     public void run() {
 45 |  *         // do something when there were no low-level events during 3000 milliseconds
 46 |  *     }
 47 |  * });
 48 |  * device.addProtocolMessageHandler(FirmataEventType.ANY, watchdog);
 49 |  * //...
 50 |  * device.start();
 51 |  * 
52 | *

53 | * 54 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 55 | */ 56 | public class FirmataWatchdog extends Consumer { 57 | 58 | private final long timeout; 59 | 60 | private final Runnable action; 61 | 62 | private final AtomicBoolean active = new AtomicBoolean(false); 63 | 64 | private volatile long lastTimestamp = 0; 65 | 66 | private final static TimeUnit UNIT = TimeUnit.MILLISECONDS; 67 | 68 | private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory("firmata-watchdog")); 69 | 70 | /** 71 | * Creates a watchdog that activates itself on the first received event. 72 | * 73 | * @param timeout timeout after the latest low-level event that triggers the 74 | * action 75 | * @param action the action that is to be triggered when there were no 76 | * low-level events during timeout since the latest event 77 | */ 78 | public FirmataWatchdog(long timeout, Runnable action) { 79 | this.timeout = timeout; 80 | this.action = action; 81 | } 82 | 83 | @Override 84 | public void accept(Event evt) { 85 | if (lastTimestamp == 0) { // got fitst event 86 | enable(); 87 | } 88 | lastTimestamp = evt.getTimestamp(); 89 | } 90 | 91 | public boolean isActive() { 92 | return active.get(); 93 | } 94 | 95 | public void setActive(boolean active) { 96 | if (active) { 97 | enable(); 98 | } else { 99 | disable(); 100 | } 101 | } 102 | 103 | public void enable() { 104 | if (!active.getAndSet(true)) { 105 | EXECUTOR.schedule(watch, timeout, UNIT); 106 | } 107 | } 108 | 109 | public void disable() { 110 | active.set(false); 111 | } 112 | 113 | private final Runnable watch = new Runnable() { 114 | @Override 115 | public void run() { 116 | if (System.currentTimeMillis() - lastTimestamp >= timeout) { 117 | action.run(); 118 | } 119 | if (active.get()) { 120 | EXECUTOR.schedule(watch, timeout, UNIT); 121 | } 122 | } 123 | }; 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/FirmataEventType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.firmata.parser; 25 | 26 | /** 27 | * Contains constants for protocol message types and payload parts. 28 | * 29 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 30 | */ 31 | public interface FirmataEventType { 32 | 33 | String ANY = "*"; 34 | 35 | String PROTOCOL_MESSAGE = "protocolMessage"; 36 | String PROTOCOL_MAJOR = "major"; 37 | String PROTOCOL_MINOR = "minor"; 38 | 39 | String FIRMWARE_MESSAGE = "firmwareMessage"; 40 | String FIRMWARE_MAJOR = "major"; 41 | String FIRMWARE_MINOR = "minor"; 42 | String FIRMWARE_NAME = "firmwareName"; 43 | 44 | String ANALOG_MAPPING_MESSAGE = "analogMapping"; 45 | String ANALOG_MAPPING = "analogMapping"; 46 | 47 | String ANALOG_MESSAGE_RESPONSE = "analogMessage"; 48 | String DIGITAL_MESSAGE_RESPONSE = "digitalMessage"; 49 | String I2C_MESSAGE = "i2cMessage"; 50 | String I2C_ADDRESS = "i2cAddress"; 51 | String I2C_REGISTER = "i2cRegister"; 52 | 53 | String PIN_CAPABILITIES_MESSAGE = "pinCapabilities"; 54 | String PIN_STATE = "pinState"; 55 | String PIN_ID = "pinId"; 56 | String PIN_SUPPORTED_MODES = "supportedModes"; 57 | String PIN_MODE = "pinMode"; 58 | String PIN_VALUE = "pinValue"; 59 | String PIN_CAPABILITIES_FINISHED = "pinCapabilitiesFinished"; 60 | 61 | String SYSTEM_RESET_MESSAGE = "systemReset"; 62 | 63 | String STRING_MESSAGE = "stringMessage"; 64 | 65 | String SYSEX_CUSTOM_MESSAGE = "sysexCustomMessage"; 66 | 67 | String ERROR_MESSAGE = "error"; 68 | String ERROR_DESCRIPTION = "description"; 69 | String ERROR_CAUSE = "cause"; 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/FirmataParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.firmata.parser; 25 | 26 | import java.util.concurrent.ArrayBlockingQueue; 27 | import java.util.concurrent.atomic.AtomicBoolean; 28 | import org.firmata4j.fsm.FiniteStateMachine; 29 | import org.firmata4j.Parser; 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | 33 | /** 34 | * Parses byte-stream of Firmata messages. 35 | * 36 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 37 | * @author Ali Kia 38 | */ 39 | public class FirmataParser implements Parser { 40 | 41 | private Thread parserExecutor; 42 | private final FiniteStateMachine fsm; 43 | private final ArrayBlockingQueue byteQueue = new ArrayBlockingQueue<>(128); 44 | private final AtomicBoolean running = new AtomicBoolean(); 45 | 46 | private static final long WAIT_FOR_TERMINATION_DELAY = 3000; 47 | private static final Logger LOGGER = LoggerFactory.getLogger(FirmataParser.class); 48 | 49 | public FirmataParser(FiniteStateMachine fsm) { 50 | this.fsm = fsm; 51 | } 52 | 53 | @Override 54 | public void start() { 55 | if (!running.getAndSet(true)) { 56 | parserExecutor = new Thread(new JobRunner(), "firmata-parser-thread"); 57 | parserExecutor.setDaemon(true); 58 | parserExecutor.start(); 59 | } 60 | } 61 | 62 | @Override 63 | public void stop() { 64 | if (running.getAndSet(false)) { 65 | byteQueue.clear(); 66 | // interrupt the thread to ensure it falls out of the loop 67 | // and sees the shutdown request 68 | parserExecutor.interrupt(); 69 | try { 70 | parserExecutor.join(WAIT_FOR_TERMINATION_DELAY); 71 | } catch (InterruptedException e) { 72 | Thread.currentThread().interrupt(); 73 | throw new RuntimeException("Parser didn't stop gracefully", e); 74 | } 75 | } 76 | } 77 | 78 | @Override 79 | public void parse(byte[] bytes) { 80 | if (bytes != null && !byteQueue.offer(bytes)) { 81 | LOGGER.warn("Parser reached byte queue limit. Some bytes were skipped."); 82 | } 83 | } 84 | 85 | private class JobRunner implements Runnable { 86 | 87 | @Override 88 | public void run() { 89 | while (running.get()) { 90 | try { 91 | fsm.process(byteQueue.take()); 92 | } catch (InterruptedException e) { 93 | Thread.currentThread().interrupt(); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/FirmataToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | /** 28 | * This class contains set of constants that represent tokens of Firmata 29 | * protocol.
30 | * In addition it contains headers and keys of events that occur when Firmata 31 | * messages are received. 32 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 33 | */ 34 | public interface FirmataToken { 35 | 36 | /** 37 | * Version numbers for the protocol. The protocol is still changing, so these 38 | * version numbers are important. This number can be queried so that host 39 | * software can test whether it will be compatible with the currently 40 | * installed firmware. 41 | */ 42 | byte FIRMATA_MAJOR_VERSION = 2; // for non-compatible changes 43 | byte FIRMATA_MINOR_VERSION = 3; // for backwards compatible changes 44 | byte FIRMATA_BUGFIX_VERSION = 1; // for bugfix releases 45 | 46 | // message command bytes (128-255/0x80-0xFF) 47 | 48 | /** 49 | * send data for a digital pin 50 | */ 51 | byte DIGITAL_MESSAGE = (byte) 0x90; 52 | 53 | /** 54 | * send data for an analog pin (or PWM) 55 | */ 56 | byte ANALOG_MESSAGE = (byte) 0xE0; 57 | 58 | /** 59 | * enable analog input by pin # 60 | */ 61 | byte REPORT_ANALOG = (byte) 0xC0; 62 | 63 | /** 64 | * enable digital input by port pair 65 | */ 66 | byte REPORT_DIGITAL = (byte) 0xD0; 67 | 68 | /** 69 | * set a pin to INPUT/OUTPUT/PWM/etc 70 | */ 71 | byte SET_PIN_MODE = (byte) 0xF4; 72 | 73 | /** 74 | * set value of an individual digital pin 75 | */ 76 | byte SET_DIGITAL_PIN_VALUE = (byte) 0xF5; 77 | 78 | /** 79 | * report protocol version 80 | */ 81 | byte REPORT_VERSION = (byte) 0xF9; 82 | 83 | /** 84 | * reset from MIDI 85 | */ 86 | byte SYSTEM_RESET = (byte) 0xFF; 87 | 88 | /** 89 | * start a MIDI Sysex message 90 | */ 91 | byte START_SYSEX = (byte) 0xF0; 92 | 93 | /** 94 | * end a MIDI Sysex message 95 | */ 96 | byte END_SYSEX = (byte) 0xF7; 97 | 98 | // extended command set using sysex (0-127/0x00-0x7F) 99 | /* 0x00-0x0F reserved for user-defined commands */ 100 | byte RESERVED_COMMAND = 0x00; // 2nd SysEx data byte is a chip-specific command (AVR, PIC, TI, etc). 101 | byte SERIAL_MESSAGE = 0x60; // communicate with serial devices, including other boards 102 | byte ENCODER_DATA = 0x61; // reply with encoders current positions 103 | byte SERVO_CONFIG = 0x70; // set max angle, minPulse, maxPulse, freq 104 | byte STRING_DATA = 0x71; // a string message with 14-bits per byte 105 | byte STEPPER_DATA = 0x72; // control a stepper motor 106 | byte ONEWIRE_DATA = 0x73; // send an OneWire read/write/reset/select/skip/search request 107 | byte SHIFT_DATA = 0x75; // a bitstream to/from a shift register 108 | byte I2C_REQUEST = 0x76; // send an I2C read/write request 109 | byte I2C_REPLY = 0x77; // a reply to an I2C read request 110 | byte I2C_CONFIG = 0x78; // config I2C settings such as delay times and power pins 111 | byte EXTENDED_ANALOG = 0x6F; // analog write (PWM, Servo, etc) to any pin 112 | byte PIN_STATE_QUERY = 0x6D; // ask for a pin's current mode and value 113 | byte PIN_STATE_RESPONSE = 0x6E; // reply with pin's current mode and value 114 | byte CAPABILITY_QUERY = 0x6B; // ask for supported modes and resolution of all pins 115 | byte CAPABILITY_RESPONSE = 0x6C; // reply with supported modes and resolution 116 | byte ANALOG_MAPPING_QUERY = 0x69; // ask for mapping of analog to pin numbers 117 | byte ANALOG_MAPPING_RESPONSE = 0x6A; // reply with mapping info 118 | byte REPORT_FIRMWARE = 0x79; // report name and version of the firmware 119 | byte SAMPLING_INTERVAL = 0x7A; // set the poll rate of the main loop 120 | byte SCHEDULER_DATA = 0x7B; // send a createtask/deletetask/addtotask/schedule/querytasks/querytask request to the scheduler 121 | byte SYSEX_NON_REALTIME = 0x7E; // MIDI Reserved for non-realtime messages 122 | byte SYSEX_REALTIME = 0x7F; // MIDI Reserved for realtime messages 123 | 124 | // pin modes 125 | byte PIN_MODE_INPUT = 0x00; // defined in wiring.h 126 | byte PIN_MODE_OUTPUT = 0x01; // defined in wiring.h 127 | byte PIN_MODE_ANALOG = 0x02; // analog pin in analogInput mode 128 | byte PIN_MODE_PWM = 0x03; // digital pin in PWM output mode 129 | byte PIN_MODE_SERVO = 0x04; // digital pin in Servo output mode 130 | byte PIN_MODE_SHIFT = 0x05; // shiftIn/shiftOut mode 131 | byte PIN_MODE_I2C = 0x06; // pin included in I2C setup 132 | byte PIN_MODE_ONEWIRE = 0x07; // pin configured for 1-wire 133 | byte PIN_MODE_STEPPER = 0x08; // pin configured for stepper motor 134 | byte PIN_MODE_ENCODER = 0x09; // pin configured for rotary encoders 135 | byte PIN_MODE_SERIAL = 0x0A; // pin configured for serial communication 136 | byte PIN_MODE_PULLUP = 0x0B; // enable internal pull-up resistor for pin 137 | byte PIN_MODE_IGNORE = 0x7F; // pin configured to be ignored by digitalWrite and capabilityResponse 138 | byte TOTAL_PIN_MODES = 13; 139 | 140 | byte I2C_WRITE = 0X00; 141 | byte I2C_READ = 0X08; 142 | byte I2C_READ_CONTINUOUS = 0X10; 143 | byte I2C_STOP_READ_CONTINUOUS = 0X18; 144 | 145 | int MIN_SAMPLING_INTERVAL = 10; 146 | int MAX_SAMPLING_INTERVAL = 100; 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingAnalogMappingState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | import java.util.Map; 31 | import java.util.concurrent.ConcurrentHashMap; 32 | 33 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 34 | import static org.firmata4j.firmata.parser.FirmataToken.*; 35 | 36 | /** 37 | * This state parses analog mapping message and fires an event that contains 38 | * information about how the analog ports match to the pin indexes.
39 | * After receiving the last byte, the state transfers FSM to 40 | * {@link WaitingForMessageState}. 41 | * 42 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 43 | */ 44 | public class ParsingAnalogMappingState extends AbstractState { 45 | 46 | private int portId; 47 | private final Map mapping = new ConcurrentHashMap<>(); 48 | 49 | public ParsingAnalogMappingState(FiniteStateMachine fsm) { 50 | super(fsm); 51 | } 52 | 53 | @Override 54 | public void process(byte b) { 55 | if (b == END_SYSEX) { 56 | Event evt = new Event(ANALOG_MAPPING_MESSAGE); 57 | evt.setBodyItem(ANALOG_MAPPING, mapping); 58 | publish(evt); 59 | transitTo(WaitingForMessageState.class); 60 | } else if (b != 127) { // if pin does support analog, corresponding analog id is in the byte 61 | mapping.put((int) b, portId); 62 | } 63 | portId++; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingAnalogMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | 31 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 32 | 33 | /** 34 | * This state parses the analog message that points an analog input has 35 | * changed.
36 | * The analog message consists of three bytes: first contains four bits of a 37 | * command and four bits with port id; second and third contains current value 38 | * of port.
39 | * After receiving the last byte, the state fires an event to FSM and transfers 40 | * FSM to {@link WaitingForMessageState}. 41 | * 42 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 43 | */ 44 | public class ParsingAnalogMessageState extends AbstractState { 45 | 46 | private int portId, counter, value; 47 | 48 | public ParsingAnalogMessageState(FiniteStateMachine fsm, int portId) { 49 | super(fsm); 50 | this.portId = portId; 51 | } 52 | 53 | @Override 54 | public void process(byte b) { 55 | switch (counter) { 56 | case 0: 57 | value = b; 58 | counter++; 59 | break; 60 | case 1: 61 | value |= b << 7; 62 | Event evt = new Event(ANALOG_MESSAGE_RESPONSE); 63 | evt.setBodyItem(PIN_ID, portId); 64 | evt.setBodyItem(PIN_VALUE, value); 65 | publish(evt); 66 | transitTo(WaitingForMessageState.class); 67 | break; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingCapabilityResponseState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | 31 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 32 | import static org.firmata4j.firmata.parser.FirmataToken.*; 33 | 34 | /** 35 | * This state parses capability response and fires an event that contains 36 | * information about pins and their supported modes.
37 | * After receiving the last byte, the state transfers FSM to 38 | * {@link WaitingForMessageState}. 39 | * 40 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 41 | */ 42 | public class ParsingCapabilityResponseState extends AbstractState { 43 | 44 | private byte pinId; 45 | 46 | public ParsingCapabilityResponseState(FiniteStateMachine fsm) { 47 | super(fsm); 48 | } 49 | 50 | @Override 51 | public void process(byte b) { 52 | if (b == END_SYSEX) { 53 | publish(new Event(PIN_CAPABILITIES_FINISHED)); 54 | transitTo(WaitingForMessageState.class); 55 | } else if (b == 127) { 56 | byte[] buffer = getBuffer(); 57 | byte[] supportedModes = new byte[buffer.length / 2]; 58 | for (int i = 0; i < buffer.length; i += 2) { 59 | //every second byte contains mode's resolution of pin 60 | supportedModes[i / 2] = buffer[i]; 61 | } 62 | Event evt = new Event(PIN_CAPABILITIES_MESSAGE); 63 | evt.setBodyItem(PIN_ID, pinId); 64 | evt.setBodyItem(PIN_SUPPORTED_MODES, supportedModes); 65 | publish(evt); 66 | ParsingCapabilityResponseState nextState = new ParsingCapabilityResponseState(getFiniteStateMashine()); 67 | nextState.setPinId(++pinId); 68 | transitTo(nextState); 69 | } else { 70 | bufferize(b); 71 | } 72 | } 73 | 74 | public void setPinId(byte pinId) { 75 | this.pinId = pinId; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingCustomSysexMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.firmata.parser; 25 | 26 | import org.firmata4j.fsm.AbstractState; 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.FiniteStateMachine; 29 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 30 | import static org.firmata4j.firmata.parser.FirmataToken.*; 31 | 32 | /** 33 | * This state collects raw binary data until it receives {@code SYSEX_END} 34 | * message and publishes it as a {@code SYSEX_CUSTOM_MESSAGE} event. 35 | * 36 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 37 | */ 38 | public class ParsingCustomSysexMessageState extends AbstractState { 39 | 40 | public ParsingCustomSysexMessageState(FiniteStateMachine fsm) { 41 | super(fsm); 42 | } 43 | 44 | @Override 45 | public void process(byte b) { 46 | if (b == END_SYSEX) { 47 | Event event = new Event(SYSEX_CUSTOM_MESSAGE); 48 | event.setBodyItem(SYSEX_CUSTOM_MESSAGE, getBuffer()); 49 | transitTo(WaitingForMessageState.class); 50 | publish(event); 51 | } else { 52 | bufferize(b); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingDigitalMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | 31 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 32 | 33 | /** 34 | * This state parses digital message and fires an event that contains 35 | * information about the state of each pin of the port where state of at least 36 | * one pin has changed.
37 | * After receiving the last byte, the state transfers FSM to 38 | * {@link WaitingForMessageState}.
39 | * When digital input of particular pin has been changed, Firmata transmits 40 | * state of whole port the pin is contained in.
41 | * A port is a set of 8 pins. State of every pin is represented inside one byte 42 | * by bit (0 - low, 1 - high). 43 | * 44 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 45 | */ 46 | public class ParsingDigitalMessageState extends AbstractState { 47 | 48 | private int portId, counter, value; 49 | 50 | public ParsingDigitalMessageState(FiniteStateMachine fsm, int portId) { 51 | super(fsm); 52 | this.portId = portId; 53 | } 54 | 55 | @Override 56 | public void process(byte b) { 57 | switch (counter) { 58 | case 0: 59 | value = b; 60 | counter++; 61 | break; 62 | case 1: 63 | value |= b << 7; 64 | int pinId = portId * 8; 65 | for (int i = 0; i < 8; i++) { 66 | Event evt = new Event(DIGITAL_MESSAGE_RESPONSE); 67 | evt.setBodyItem(PIN_ID, pinId + i); 68 | evt.setBodyItem(PIN_VALUE, (value >>> i) & 0x01); 69 | publish(evt); 70 | } 71 | transitTo(WaitingForMessageState.class); 72 | break; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingExtendedAnalogMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | 31 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 32 | import static org.firmata4j.firmata.parser.FirmataToken.*; 33 | 34 | /** 35 | * This state parses extended analog state message and fires an event that 36 | * contains information about the state of an analog input where the state has 37 | * changed.
38 | * This kind of message is received when pin id of the analog input is greater 39 | * than 15 and/or value on the input is greater than 16383.
40 | * After receiving the last byte of the message, the state transfers FSM to 41 | * {@link WaitingForMessageState}. 42 | * 43 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 44 | */ 45 | public class ParsingExtendedAnalogMessageState extends AbstractState { 46 | 47 | public ParsingExtendedAnalogMessageState(FiniteStateMachine fsm) { 48 | super(fsm); 49 | } 50 | 51 | @Override 52 | public void process(byte b) { 53 | if (b == END_SYSEX) { 54 | byte[] buffer = getBuffer(); 55 | int pinId = buffer[0]; 56 | long value = buffer[1]; 57 | for (int i = 2; i < buffer.length; i++) { 58 | value |= buffer[i] << 7 * (i - 1); 59 | } 60 | Event evt = new Event(ANALOG_MESSAGE_RESPONSE); 61 | evt.setBodyItem(PIN_ID, pinId); 62 | evt.setBodyItem(PIN_VALUE, value); 63 | publish(evt); 64 | transitTo(WaitingForMessageState.class); 65 | } else { 66 | bufferize(b); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingFirmwareMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | 31 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 32 | import static org.firmata4j.firmata.parser.FirmataToken.*; 33 | 34 | /** 35 | * This state parses firmware message and fires an event that contains the name 36 | * and version of firmware.
37 | * After receiving the last byte of the message, the state transfers FSM to 38 | * {@link WaitingForMessageState}. 39 | * 40 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 41 | */ 42 | public class ParsingFirmwareMessageState extends AbstractState { 43 | 44 | public ParsingFirmwareMessageState(FiniteStateMachine fsm) { 45 | super(fsm); 46 | } 47 | 48 | @Override 49 | public void process(byte b) { 50 | if (b == END_SYSEX) { 51 | byte[] buffer = getBuffer(); 52 | int major = buffer[0]; 53 | int minor = buffer[1]; 54 | String name = decode(buffer, 2, buffer.length - 2); 55 | Event event = new Event(FIRMWARE_MESSAGE); 56 | event.setBodyItem(FIRMWARE_MAJOR, major); 57 | event.setBodyItem(FIRMWARE_MINOR, minor); 58 | event.setBodyItem(FIRMWARE_NAME, name); 59 | transitTo(WaitingForMessageState.class); 60 | publish(event); 61 | } else { 62 | bufferize(b); 63 | } 64 | } 65 | 66 | private String decode(byte[] buffer, int offset, int length) { 67 | StringBuilder result = new StringBuilder(); 68 | length = length >>> 1; // divide by two 69 | for (int i = 0; i < length; i++) { 70 | result.append((char) (buffer[offset++] + (buffer[offset++] << 7))); 71 | } 72 | return result.toString(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingI2CMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.firmata.parser; 25 | 26 | import org.firmata4j.fsm.AbstractState; 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.FiniteStateMachine; 29 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 30 | import static org.firmata4j.firmata.parser.FirmataToken.*; 31 | 32 | /** 33 | * This class parses inbound I2C messages and publishes them when they are 34 | * complete. 35 | * 36 | * @author William Reichardt 37 | */ 38 | public class ParsingI2CMessageState extends AbstractState { 39 | 40 | public ParsingI2CMessageState(FiniteStateMachine fsm) { 41 | super(fsm); 42 | } 43 | 44 | /* 45 | * /* I2C reply 46 | * ------------------------------- 47 | * 0 START_SYSEX (0xF0) (MIDI System Exclusive) 48 | * 1 I2C_REPLY (0x77) 49 | * 2 slave address (LSB) 50 | * ... Lotsa Bytes 51 | * n END_SYSEX (0xF7) 52 | */ 53 | /** 54 | * Collects I2C message from individual bytes. 55 | * 56 | * @param b the input byte 57 | */ 58 | @Override 59 | public void process(byte b) { 60 | if (b == END_SYSEX) { 61 | byte[] buffer = convertI2CBuffer(getBuffer()); 62 | byte address = buffer[0]; 63 | byte register = buffer[1]; 64 | byte[] message = new byte[buffer.length - 2]; 65 | System.arraycopy(buffer, 2, message, 0, buffer.length - 2); 66 | Event event = new Event(I2C_MESSAGE); 67 | event.setBodyItem(I2C_ADDRESS, address); 68 | event.setBodyItem(I2C_REGISTER, register); 69 | event.setBodyItem(I2C_MESSAGE, message); 70 | transitTo(WaitingForMessageState.class); 71 | publish(event); 72 | } else { 73 | bufferize(b); 74 | } 75 | } 76 | 77 | private static byte[] convertI2CBuffer(byte[] byteBuffer) { 78 | int outSize = new Double(Math.floor(byteBuffer.length / 2)).intValue(); 79 | byte[] outBuffer = new byte[outSize]; 80 | int outIndex = 0; 81 | for (int index = 0; index < byteBuffer.length; index = index + 2) { 82 | outBuffer[outIndex] = (byte) (((byteBuffer[index + 1] & 0x01) << 7) | (byteBuffer[index] & 0x7F)); 83 | outIndex++; 84 | } 85 | return outBuffer; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingStringMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.AbstractState; 28 | import org.firmata4j.fsm.Event; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 31 | import static org.firmata4j.firmata.parser.FirmataToken.*; 32 | 33 | /** 34 | * 35 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 36 | */ 37 | public class ParsingStringMessageState extends AbstractState { 38 | 39 | public ParsingStringMessageState(FiniteStateMachine fsm) { 40 | super(fsm); 41 | } 42 | 43 | @Override 44 | public void process(byte b) { 45 | if (b == END_SYSEX) { 46 | byte[] buffer = getBuffer(); 47 | String value = decode(buffer, 0, buffer.length); 48 | Event event = new Event(STRING_MESSAGE); 49 | event.setBodyItem(STRING_MESSAGE, value); 50 | transitTo(WaitingForMessageState.class); 51 | publish(event); 52 | } else { 53 | bufferize(b); 54 | } 55 | } 56 | 57 | private String decode(byte[] buffer, int offset, int length) { 58 | StringBuilder result = new StringBuilder(); 59 | length = length >>> 1; // divide by two 60 | for (int i = 0; i < length; i++) { 61 | result.append((char) (buffer[offset++] + (buffer[offset++] << 7))); 62 | } 63 | return result.toString(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingSysexMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.AbstractState; 28 | import org.firmata4j.fsm.FiniteStateMachine; 29 | import org.firmata4j.fsm.State; 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | 33 | import java.util.HashMap; 34 | import java.util.Map; 35 | 36 | import static org.firmata4j.firmata.parser.FirmataToken.*; 37 | 38 | /** 39 | * This state parses type of sysex message and transfers FSM to the state which 40 | * is able to handle the message.
41 | * If the state receives unknown type of sysex message, it transfers FSM to 42 | * {@link WaitingForMessageState}. 43 | * 44 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 45 | */ 46 | public class ParsingSysexMessageState extends AbstractState { 47 | 48 | private static final Logger LOGGER = LoggerFactory.getLogger(ParsingSysexMessageState.class); 49 | private static final Map> STATES; 50 | 51 | static { 52 | STATES = new HashMap<>(); 53 | STATES.put(REPORT_FIRMWARE, ParsingFirmwareMessageState.class); 54 | STATES.put(EXTENDED_ANALOG, ParsingExtendedAnalogMessageState.class); 55 | STATES.put(CAPABILITY_RESPONSE, ParsingCapabilityResponseState.class); 56 | STATES.put(ANALOG_MAPPING_RESPONSE, ParsingAnalogMappingState.class); 57 | STATES.put(PIN_STATE_RESPONSE, PinStateParsingState.class); 58 | STATES.put(STRING_DATA, ParsingStringMessageState.class); 59 | STATES.put(I2C_REPLY,ParsingI2CMessageState.class); 60 | } 61 | 62 | public ParsingSysexMessageState(FiniteStateMachine fsm) { 63 | super(fsm); 64 | } 65 | 66 | @Override 67 | public void process(byte b) { 68 | Class nextState = STATES.get(b); 69 | if (nextState == null) { 70 | State newState = new ParsingCustomSysexMessageState(getFiniteStateMashine()); 71 | newState.process(b); 72 | transitTo(newState); 73 | } else { 74 | transitTo(nextState); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/ParsingVersionMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | 31 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 32 | 33 | /** 34 | * This state parses version report message that contains the version of the 35 | * protocol the hardware supports. The version is handed with the event and the 36 | * FSM is transfered to {@link WaitingForMessageState}. 37 | * 38 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 39 | */ 40 | public class ParsingVersionMessageState extends AbstractState { 41 | 42 | private int counter, major; 43 | 44 | public ParsingVersionMessageState(FiniteStateMachine fsm) { 45 | super(fsm); 46 | } 47 | 48 | @Override 49 | public void process(byte b) { 50 | if (counter == 0) { 51 | major = b; 52 | counter++; 53 | } else { 54 | int minor = b; 55 | Event event = new Event(PROTOCOL_MESSAGE); 56 | event.setBodyItem(PROTOCOL_MAJOR, major); 57 | event.setBodyItem(PROTOCOL_MINOR, minor); 58 | transitTo(WaitingForMessageState.class); 59 | publish(event); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/PinStateParsingState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.AbstractState; 29 | import org.firmata4j.fsm.FiniteStateMachine; 30 | 31 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 32 | import static org.firmata4j.firmata.parser.FirmataToken.*; 33 | 34 | /** 35 | * This state parses the pin state message.
36 | * After receiving the last byte of the message, the state transfers FSM to 37 | * {@link WaitingForMessageState}. 38 | * 39 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 40 | */ 41 | public class PinStateParsingState extends AbstractState { 42 | 43 | public PinStateParsingState(FiniteStateMachine fsm) { 44 | super(fsm); 45 | } 46 | 47 | @Override 48 | public void process(byte b) { 49 | if (b == END_SYSEX) { 50 | byte[] buffer = getBuffer(); 51 | Event evt = new Event(PIN_STATE); 52 | evt.setBodyItem(PIN_ID, buffer[0]); 53 | evt.setBodyItem(PIN_MODE, buffer[1]); 54 | long value = 0; 55 | for (int i = 2; i < buffer.length; i++) { 56 | value |= (buffer[i] << ((i - 2) * 7)); 57 | } 58 | evt.setBodyItem(PIN_VALUE, value); 59 | publish(evt); 60 | transitTo(WaitingForMessageState.class); 61 | } else { 62 | bufferize(b); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/firmata/parser/WaitingForMessageState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.parser; 26 | 27 | import org.firmata4j.firmata.FirmataDevice; 28 | import org.firmata4j.fsm.Event; 29 | import org.firmata4j.fsm.AbstractState; 30 | import org.firmata4j.fsm.FiniteStateMachine; 31 | 32 | import static org.firmata4j.firmata.parser.FirmataEventType.*; 33 | import static org.firmata4j.firmata.parser.FirmataToken.*; 34 | 35 | /** 36 | * This is initial default state of {@link FirmataDevice}.
37 | * The state is waiting for command and determines to which state transfers to 38 | * parse further data. It extracts additional data from a command byte, when the 39 | * command contains that, and hands it to the next state.
40 | * The state skips unknown command bytes throwing events with error messages. 41 | * 42 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 43 | */ 44 | public class WaitingForMessageState extends AbstractState { 45 | 46 | public WaitingForMessageState(FiniteStateMachine fsm) { 47 | super(fsm); 48 | } 49 | 50 | @Override 51 | public void process(byte b) { 52 | // first byte may contain not only command but additional information as well 53 | byte command = b < (byte) 0xF0 ? (byte) (b & 0xF0) : b; 54 | FiniteStateMachine fsm = getFiniteStateMashine(); 55 | switch (command) { 56 | case DIGITAL_MESSAGE: 57 | transitTo(new ParsingDigitalMessageState(fsm, b & 0x0F)); 58 | break; 59 | case ANALOG_MESSAGE: 60 | transitTo(new ParsingAnalogMessageState(fsm, b & 0x0F)); 61 | break; 62 | case REPORT_VERSION: 63 | transitTo(ParsingVersionMessageState.class); 64 | break; 65 | case START_SYSEX: 66 | transitTo(ParsingSysexMessageState.class); 67 | break; 68 | case SYSTEM_RESET: 69 | publish(new Event(SYSTEM_RESET_MESSAGE)); 70 | break; 71 | default: 72 | //skip non control token 73 | Event evt = new Event(ERROR_MESSAGE); 74 | evt.setBodyItem(ERROR_DESCRIPTION, String.format("Unknown control token has been received. Skipping. 0x%2X", b)); 75 | publish(evt); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/fsm/AbstractState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.fsm; 26 | 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | /** 31 | * This state stores FSM it belongs to. It facilitates transitions between 32 | * states and buffering of the input data. 33 | * 34 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 35 | */ 36 | public abstract class AbstractState implements State { 37 | 38 | private final FiniteStateMachine fsm; 39 | private byte[] buffer = new byte[128]; 40 | private int index; 41 | private static final Logger LOGGER = LoggerFactory.getLogger(AbstractState.class); 42 | 43 | public AbstractState(FiniteStateMachine fsm) { 44 | this.fsm = fsm; 45 | } 46 | 47 | @Override 48 | public FiniteStateMachine getFiniteStateMashine() { 49 | return fsm; 50 | } 51 | 52 | /** 53 | * Transfers the FSM to the state of specified class. This method takes only 54 | * classes that provide a constructor taking a {@link FiniteStateMachine} 55 | * instance as a parameter. The {@link IllegalArgumentException} is thrown 56 | * otherwise. 57 | * 58 | * @param stateClass the state class 59 | */ 60 | protected void transitTo(Class stateClass) { 61 | fsm.transitTo(stateClass); 62 | } 63 | 64 | /** 65 | * Transfers the FSM to the specified state. 66 | * 67 | * @param state the state 68 | */ 69 | protected void transitTo(State state) { 70 | fsm.transitTo(state); 71 | } 72 | 73 | /** 74 | * Publishes the event to the FSM. 75 | * 76 | * @param event the event 77 | */ 78 | protected void publish(Event event) { 79 | fsm.handle(event); 80 | } 81 | 82 | /** 83 | * Stores the byte to the internal buffer in order to get a chunk of data 84 | * latter. 85 | * 86 | * @param b the byte 87 | */ 88 | protected void bufferize(byte b) { 89 | if (index == buffer.length) { 90 | byte[] newBuffer = new byte[buffer.length * 2]; 91 | System.arraycopy(buffer, 0, newBuffer, 0, index); 92 | buffer = newBuffer; 93 | } 94 | buffer[index++] = b; 95 | } 96 | 97 | /** 98 | * Returns the data collected so far. 99 | * 100 | * @return the buffered bytes 101 | */ 102 | protected byte[] getBuffer() { 103 | byte[] result = new byte[index]; 104 | System.arraycopy(buffer, 0, result, 0, index); 105 | return result; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/fsm/DirectExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.fsm; 26 | 27 | import java.util.concurrent.Executor; 28 | 29 | /** 30 | * Executes tasks in the current thread. 31 | * 32 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 33 | */ 34 | public class DirectExecutor implements Executor { 35 | 36 | public static final DirectExecutor INSTANCE = new DirectExecutor(); 37 | 38 | @Override 39 | public void execute(Runnable command) { 40 | command.run(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/fsm/Event.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.fsm; 26 | 27 | import java.util.HashMap; 28 | import java.util.Map; 29 | 30 | /** 31 | * The event of very loose structure. It provides possibility to build an event 32 | * of structure that meets the needs of a particular FSM application. 33 | * 34 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 35 | */ 36 | public class Event { 37 | 38 | private final long timestamp; 39 | private final String type; 40 | private final Map body; 41 | 42 | /** 43 | * Constructs the event of unspecified type. 44 | */ 45 | public Event() { 46 | timestamp = System.currentTimeMillis(); 47 | type = "unspecified"; 48 | body = new HashMap<>(); 49 | } 50 | 51 | /** 52 | * Constructs the event of specified type. 53 | * 54 | * @param type the type of the event 55 | */ 56 | public Event(String type) { 57 | timestamp = System.currentTimeMillis(); 58 | this.type = type; 59 | body = new HashMap<>(); 60 | } 61 | 62 | /** 63 | * Constructs the event of specified type with specified type. This 64 | * constructor allows to set the body of event at once. 65 | * 66 | * @param type the type of the event 67 | * @param body the event's body 68 | */ 69 | public Event(String type, Map body) { 70 | timestamp = System.currentTimeMillis(); 71 | this.type = type; 72 | this.body = new HashMap<>(body); 73 | } 74 | 75 | /** 76 | * Returns the type of the event. 77 | */ 78 | public String getType() { 79 | return type; 80 | } 81 | 82 | /** 83 | * Returns the timestamp of the event. 84 | */ 85 | public long getTimestamp() { 86 | return timestamp; 87 | } 88 | 89 | /** 90 | * Returns the body of the event. 91 | */ 92 | public Map getBody() { 93 | return new HashMap<>(body); 94 | } 95 | 96 | /** 97 | * Returns the item of the event's body. 98 | * @param key the key of event item 99 | * @return the event item 100 | */ 101 | public Object getBodyItem(String key) { 102 | return body.get(key); 103 | } 104 | 105 | /** 106 | * Sets the item of the event's body. 107 | * @param key the key of event item 108 | * @param value the event item 109 | */ 110 | public void setBodyItem(String key, Object value) { 111 | body.put(key, value); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/fsm/FiniteStateMachine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.fsm; 26 | 27 | import java.util.Map; 28 | import java.util.concurrent.ConcurrentHashMap; 29 | import java.util.concurrent.Executor; 30 | import org.firmata4j.Consumer; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | /** 35 | * Finite State Machine base implementation.
36 | * The finite state machine is not thread-safe by its nature. This 37 | * implementation does not cope with simultaneously received bytes. The bytes 38 | * have to be fed to the FSM one by one in a single thread that should define 39 | * the order the bytes go in. 40 | * 41 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 42 | */ 43 | public class FiniteStateMachine { 44 | 45 | public static final String FSM_IS_IN_TERMINAL_STATE = "fsm is in terminal state"; 46 | private static final Logger LOGGER = LoggerFactory.getLogger(FiniteStateMachine.class); 47 | private final Map> handlers; 48 | private Executor eventHandlingExecutor = DirectExecutor.INSTANCE; 49 | private State currentState; 50 | 51 | /** 52 | * Constructs the finite state machine in the terminal state, i.e. without 53 | * any particular current state. So that it will do noting on 54 | * {@link #process(byte)} method invocation until a state will be set using 55 | * {@link #transitTo(org.firmata4j.fsm.State)}. 56 | */ 57 | public FiniteStateMachine() { 58 | handlers = new ConcurrentHashMap<>(); 59 | handlers.put("*", new Consumer() { 60 | @Override 61 | public void accept(Event t) { 62 | // default wildcard handler does nothing 63 | } 64 | }); 65 | } 66 | 67 | /** 68 | * Constructs the finite state machine in the state of the specified class. 69 | * 70 | * @param stateClass the class of initial state of the FSM 71 | * @throws IllegalArgumentException when creating of the state instance is 72 | * impossible (likely because of lack of the constructor taking the FSM as a 73 | * parameter) 74 | */ 75 | @SuppressWarnings("LeakingThisInConstructor") 76 | public FiniteStateMachine(Class stateClass) { 77 | this(); 78 | try { 79 | // LeakingThisInConstructor is suppresed since the state instance doesn't run its own thread 80 | currentState = stateClass.getConstructor(FiniteStateMachine.class).newInstance(this); 81 | } catch (ReflectiveOperationException ex) { 82 | throw new IllegalArgumentException("Cannot instantiate the initial state", ex); 83 | } 84 | } 85 | 86 | /** 87 | * Assigns an executor responsible for performing event hanling. 88 | * 89 | * @param executor the executor that will perform event handling 90 | */ 91 | public void setEventHandlingExecutor(Executor executor) { 92 | this.eventHandlingExecutor = executor; 93 | } 94 | 95 | /** 96 | * Transfers the FSM to the new state. 97 | * 98 | * @param state the new state 99 | */ 100 | public void transitTo(State state) { 101 | currentState = state; 102 | } 103 | 104 | /** 105 | * Transfers the FSM to the new state of the specified class.
106 | * This method takes only classes that provide a constructor taking a 107 | * {@link FiniteStateMachine} instance as a parameter. The 108 | * {@link IllegalArgumentException} is thrown otherwise. 109 | * 110 | * @param stateClass the state class 111 | * @throws IllegalArgumentException when the state class does not provide a 112 | * constructor taking {@link FiniteStateMachine} instance as a single 113 | * parameter. 114 | */ 115 | public void transitTo(Class stateClass) { 116 | try { 117 | State nextState = stateClass.getConstructor(FiniteStateMachine.class).newInstance(this); 118 | transitTo(nextState); 119 | } catch (ReflectiveOperationException ex) { 120 | throw new IllegalArgumentException("Cannot instantiate the new state", ex); 121 | } 122 | } 123 | 124 | /** 125 | * Returns current state of the FSM. 126 | * 127 | * @return the current state 128 | */ 129 | public State getCurrentState() { 130 | return currentState; 131 | } 132 | 133 | /** 134 | * Hands the byte to be processed with the current state. 135 | * 136 | * @param b input byte 137 | */ 138 | public void process(byte b) { 139 | if (currentState == null) { 140 | LOGGER.debug("{} is in terminal state", this); 141 | Event evt = new Event(FSM_IS_IN_TERMINAL_STATE); 142 | evt.setBodyItem("fsm", this); 143 | handle(evt); 144 | } else { 145 | LOGGER.trace("processing of byte {} with {}", b, currentState); 146 | currentState.process(b); 147 | } 148 | } 149 | 150 | /** 151 | * Hands bytes from the buffer to be processed by the current state 152 | * sequentially. 153 | * 154 | * @param buffer the bytes to be processed 155 | */ 156 | public void process(byte[] buffer) { 157 | process(buffer, 0, buffer.length); 158 | } 159 | 160 | /** 161 | * Hands bytes from the buffer to be processed by the current state 162 | * sequentially. 163 | * 164 | * @param buffer the bytes to be processed 165 | * @param offset the index of the first byte to process 166 | * @param length the number of the bytes to be processed 167 | */ 168 | public void process(byte[] buffer, int offset, int length) { 169 | int finalIndex = offset + length; 170 | for (int i = offset; i < finalIndex; i++) { 171 | process(buffer[i]); 172 | } 173 | } 174 | 175 | /** 176 | * Adds a handler for specified event type. 177 | * 178 | * If there already is aanother handler for that event type, this handler 179 | * gets added to the end of handler chain. 180 | * 181 | * @param eventType type of event the handler is supposed to deal with 182 | * @param handler an object that gets an event to process 183 | */ 184 | public synchronized void addHandler(String eventType, Consumer handler) { 185 | if (handlers.containsKey(eventType)) { 186 | handlers.put(eventType, handlers.get(eventType).andThen(handler)); 187 | } else { 188 | handlers.put(eventType, handler); 189 | } 190 | } 191 | 192 | /** 193 | * Handles an event that occurs during processing of the input.
194 | * The method is invoked by the current state of FSM when an event occurs. 195 | * 196 | * @param event the event 197 | */ 198 | public void handle(final Event event) { 199 | final Consumer handler = handlers.get(event.getType()); 200 | if (handler == null) { 201 | LOGGER.warn( 202 | "No specific event handler is registered for {}:{}.", 203 | event.getType(), 204 | event.getBody() 205 | ); 206 | } else { 207 | eventHandlingExecutor.execute(new Runnable() { 208 | @Override 209 | public void run() { 210 | handler.accept(event); 211 | } 212 | }); 213 | } 214 | eventHandlingExecutor.execute(new Runnable() { 215 | @Override 216 | public void run() { 217 | handlers.get("*").accept(event); 218 | } 219 | }); 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/fsm/State.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.fsm; 26 | 27 | /** 28 | * The interface of the state of the {@link FiniteStateMachine}. 29 | * 30 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 31 | */ 32 | public interface State { 33 | 34 | /** 35 | * Performs processing of the input byte.
36 | * In particular circumstances the state may fire an event and/or transit 37 | * the finite state machine to the new state. 38 | * 39 | * @param b the input byte 40 | */ 41 | void process(byte b); 42 | 43 | /** 44 | * Returns the {@link FiniteStateMachine} instance the state belongs to. 45 | * 46 | * @return the finite state machine 47 | */ 48 | FiniteStateMachine getFiniteStateMashine(); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/ssd1306/SSD1306.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.ssd1306; 26 | 27 | import java.io.IOException; 28 | import java.util.Arrays; 29 | import org.firmata4j.I2CDevice; 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | import static org.firmata4j.ssd1306.SSD1306Token.DATA_CONTROL_BYTE; 33 | import static org.firmata4j.ssd1306.SSD1306MessageFactory.*; 34 | 35 | /** 36 | * This class provides facilities to control a monochrome OLED display on 37 | * SSD1306 driver.
38 | *

39 | * Example: 40 | *

 41 |  *   IODevice device = new FirmataDevice(port); // connecting to firmata device
 42 |  *   device.ensureInitializationIsDone(); // waiting for the device is ready
 43 |  *   I2CDevice i2cDevice = device.getI2CDevice((byte) 0x3C); // or 0x3D - standard address for SSD1306
 44 |  *   SSD1306 display = new SSD1306(i2cDevice, SSD1306.Size.SSD1306_128_64); // constructing a display on top of i2c device reference
 45 |  *   display.init(); // initializing display
 46 |  *   display.getCanvas().drawString(3, 3, "firmata4j"); // drawing a string on display's canvas
 47 |  *   // drawing anything else
 48 |  *   display.display(); // displaying current state of display's canvas
 49 |  *   // doing anything else
 50 |  *   display.turnOff(); // switching off the display
 51 |  *   device.stop(); // stopping IODevice
 52 |  * 
53 | *

54 | * 55 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 56 | * 57 | * see https://www.adafruit.com/category/63_98 58 | */ 59 | public class SSD1306 { 60 | 61 | private final I2CDevice device; 62 | 63 | private final Size size; 64 | 65 | private final MonochromeCanvas canvas; 66 | 67 | private final VCC vccState; 68 | 69 | private static final Logger LOGGER = LoggerFactory.getLogger(SSD1306.class); 70 | 71 | public SSD1306(I2CDevice device, Size size) { 72 | this(device, size, VCC.INTERNAL); 73 | } 74 | 75 | public SSD1306(I2CDevice device, Size size, VCC vcc) { 76 | this.device = device; 77 | this.size = size; 78 | canvas = new MonochromeCanvas(size.width, size.height); 79 | vccState = vcc; 80 | } 81 | 82 | /** 83 | * Prepares the device to operation. 84 | */ 85 | public void init() { 86 | turnOff(); 87 | // initialization 88 | command(setDisplayClock((byte) 0, (byte) 8)); 89 | command(setMultiplexRatio(size.height)); 90 | command(setDisplayOffset(0)); // no offset 91 | command(setDisplayStartLine(0)); // line #0 92 | command(setMemoryAddressingMode(MemoryAddressingMode.HORIZONTAL)); 93 | command(setHorizontalFlip(false)); 94 | command(setVerticalFlip(false)); 95 | if (vccState == VCC.EXTERNAL) { 96 | command(setChargePump(false)); 97 | } else { 98 | command(setChargePump(true)); 99 | } 100 | int contrast = 0; 101 | boolean sequentialPins = true; 102 | boolean leftRightPinsRemap = false; 103 | if (size == Size.SSD1306_128_32) { 104 | contrast = 0x8F; 105 | } else if (size == Size.SSD1306_128_64) { 106 | if (vccState == VCC.EXTERNAL) { 107 | contrast = 0x9F; 108 | } else { 109 | contrast = 0xCF; 110 | } 111 | sequentialPins = false; 112 | } else if (size == Size.SSD1306_96_16) { 113 | if (vccState == VCC.EXTERNAL) { 114 | contrast = 0x10; 115 | } else { 116 | contrast = 0xAF; 117 | } 118 | } 119 | command(setCOMPinsConfig(sequentialPins, leftRightPinsRemap)); 120 | command(setContrast((byte) contrast)); 121 | if (vccState == VCC.EXTERNAL) { 122 | command(setPrechargePeriod((byte) 2, (byte) 2)); 123 | } else { 124 | command(setPrechargePeriod((byte) 1, (byte) 15)); 125 | } 126 | command(setVcomhDeselectLevel((byte) 3)); 127 | command(DISPLAY_RESUME); 128 | command(setDisplayInverse(false)); 129 | stopScrolling(); 130 | display(); // synchronize canvas with display's internal buffer 131 | turnOn(); 132 | } 133 | 134 | public void turnOn() { 135 | command(TURN_ON); 136 | } 137 | 138 | public void turnOff() { 139 | command(TURN_OFF); 140 | } 141 | 142 | public void scrollRight() { 143 | command(setHorizontalScroll(ScrollDirection.RIGHT, (byte) 0, (byte) 7, (byte) 0)); 144 | command(ENABLE_SCROLLING); 145 | } 146 | 147 | public void scrollLeft() { 148 | command(setHorizontalScroll(ScrollDirection.LEFT, (byte) 0, (byte) 7, (byte) 0)); 149 | command(ENABLE_SCROLLING); 150 | } 151 | 152 | public void stopScrolling() { 153 | command(DISABLE_SCROLLING); 154 | } 155 | 156 | public void invertDisplay(boolean inverted) { 157 | command(setDisplayInverse(inverted)); 158 | } 159 | 160 | public void clear() { 161 | getCanvas().clear(); 162 | display(); 163 | } 164 | 165 | /** 166 | * Dims the display 167 | * 168 | * @param dim true - display is dimmed, false - display is normal 169 | */ 170 | public void dim(boolean dim) { 171 | byte contrast; 172 | 173 | if (dim) { 174 | contrast = 0; // Dimmed display 175 | } else { 176 | if (vccState == VCC.EXTERNAL) { 177 | contrast = (byte) 0x9F; 178 | } else { 179 | contrast = (byte) 0xCF; 180 | } 181 | } 182 | // the range of contrast to too small to be really useful 183 | // it is useful to dim the display 184 | command(setContrast(contrast)); 185 | } 186 | 187 | public void display() { 188 | command(setColumnAddress((byte) 0, (byte) (size.width - 1))); 189 | byte pageEndAddr = 0; 190 | switch (size) { 191 | case SSD1306_128_64: 192 | pageEndAddr = 7; // Page end address 193 | break; 194 | case SSD1306_128_32: 195 | pageEndAddr = 3; // Page end address 196 | break; 197 | case SSD1306_96_16: 198 | pageEndAddr = 1; // Page end address 199 | break; 200 | } 201 | command(setPageAddress((byte) 0, pageEndAddr)); 202 | //TODO increase 2C bitrate if possible 203 | byte[] buffer = canvas.getBuffer(); 204 | try { 205 | for (int i = 0; i < buffer.length / 16; i++) { 206 | byte[] row = new byte[17]; 207 | row[0] = DATA_CONTROL_BYTE; 208 | System.arraycopy(buffer, i * 16, row, 1, 16); 209 | device.tell(row); 210 | } 211 | } catch (IOException e) { 212 | LOGGER.error("Displaying attempt failed", e); 213 | } 214 | } 215 | 216 | public Size getSize() { 217 | return size; 218 | } 219 | 220 | public MonochromeCanvas getCanvas() { 221 | return canvas; 222 | } 223 | 224 | private void command(byte... command) { 225 | try { 226 | for (int i = 0; i < command.length; i += 2) { 227 | device.tell(Arrays.copyOfRange(command, i, i + 2)); 228 | } 229 | } catch (IOException e) { 230 | throw new RuntimeException(e); 231 | } 232 | } 233 | 234 | public static enum Size { 235 | 236 | SSD1306_128_64(128, 64), 237 | SSD1306_128_32(128, 32), 238 | SSD1306_96_16(96, 16); 239 | 240 | public final int width, height; 241 | 242 | private Size(int w, int h) { 243 | width = w; 244 | height = h; 245 | } 246 | 247 | } 248 | 249 | public static enum VCC { 250 | EXTERNAL, 251 | INTERNAL 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/ssd1306/SSD1306Token.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.ssd1306; 26 | 27 | /** 28 | * This class contains set of constants that represent tokens of SSD1306 device 29 | * protocol (commands and control bytes). 30 | * 31 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 32 | * 33 | * see https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf 34 | */ 35 | public interface SSD1306Token { 36 | 37 | byte COMMAND_CONTROL_BYTE = 0x00; // Co = 0, D/C = 0 38 | byte DATA_CONTROL_BYTE = 0x40; // Co = 0, D/C = 1 39 | 40 | // fundamental commands 41 | byte SET_CONTRAST = (byte) 0x81; 42 | byte DISPLAY_ON_RESUME = (byte) 0xA4; // resume to RAM content display 43 | byte DISPLAY_ON_RESET = (byte) 0xA5; // ignore RAM content 44 | byte NORMAL_DISPLAY = (byte) 0xA6; 45 | byte INVERSE_DISPLAY = (byte) 0xA7; 46 | byte LED_OFF = (byte) 0xAE; 47 | byte LED_ON = (byte) 0xAF; 48 | 49 | // scrolling commands 50 | byte SET_HORIZONTAL_SCROLL = 0x26; 51 | byte VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29; 52 | byte VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A; 53 | byte SET_VERTICAL_SCROLL_AREA = (byte) 0xA3; 54 | byte DEACTIVATE_SCROLL = 0x2E; 55 | byte ACTIVATE_SCROLL = 0x2F; 56 | 57 | // addressing comands 58 | byte SET_MEMORY_ADDR_MODE = 0x20; 59 | byte SET_COLUMN_ADDR = 0x21; 60 | byte SET_PAGE_ADDR = 0x22; 61 | byte PAGE_ADDR_SET_LOW_COLUMN = 0x00; 62 | byte PAGE_ADDR_SET_HIGH_COLUMN = 0x10; 63 | byte RAM_PAGE_START_ADDRESS = (byte) 0xB0; 64 | 65 | // hardvare configuration commands 66 | byte SET_START_LINE = 0x40; 67 | byte SEG_REMAP = (byte) 0xA0; 68 | byte SET_MULTIPLEX = (byte) 0xA8; 69 | byte COM_SCAN_INC = (byte) 0xC0; 70 | byte COM_SCAN_DEC = (byte) 0xC8; 71 | byte SET_DISPLAY_OFFSET = (byte) 0xD3; 72 | byte SET_COM_PINS = (byte) 0xDA; 73 | 74 | // timing configuration commands 75 | byte SET_DISPLAY_CLOCK = (byte) 0xD5; 76 | byte SET_PRECHARGE_PERIOD = (byte) 0xD9; 77 | byte SET_VCOMH_DESELECT = (byte) 0xDB; 78 | byte NOP = (byte) 0xE3; 79 | 80 | byte SET_CHARGE_PUMP = (byte) 0x8D; 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/transport/JSSCTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.transport; 25 | 26 | import java.io.IOException; 27 | import jssc.SerialPort; 28 | import jssc.SerialPortEvent; 29 | import jssc.SerialPortEventListener; 30 | import jssc.SerialPortException; 31 | import org.firmata4j.Parser; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | /** 36 | * Transfers data via serial port using jssc library. 37 | * 38 | * @author Ali Kia 39 | */ 40 | public class JSSCTransport implements TransportInterface, SerialPortEventListener { 41 | private Parser parser; 42 | 43 | private final SerialPort port; 44 | 45 | private static final Logger LOGGER = LoggerFactory.getLogger(JSSCTransport.class); 46 | 47 | public JSSCTransport(String portName) { 48 | this.port = new SerialPort(portName); 49 | } 50 | 51 | @Override 52 | public void start() throws IOException { 53 | if (!port.isOpened()) { 54 | try { 55 | port.openPort(); 56 | port.setParams( 57 | SerialPort.BAUDRATE_57600, 58 | SerialPort.DATABITS_8, 59 | SerialPort.STOPBITS_1, 60 | SerialPort.PARITY_NONE); 61 | port.setEventsMask(SerialPort.MASK_RXCHAR); 62 | port.addEventListener(this); 63 | } catch (SerialPortException ex) { 64 | throw new IOException("Cannot start firmata device", ex); 65 | } 66 | } 67 | } 68 | 69 | @Override 70 | public void stop() throws IOException { 71 | try { 72 | if (port.isOpened()) { 73 | port.purgePort(SerialPort.PURGE_RXCLEAR | SerialPort.PURGE_TXCLEAR); 74 | port.closePort(); 75 | } 76 | } catch (SerialPortException ex) { 77 | throw new IOException("Cannot properly stop firmata device", ex); 78 | } 79 | } 80 | 81 | @Override 82 | public void write(byte[] bytes) throws IOException { 83 | try { 84 | port.writeBytes(bytes); 85 | } catch (SerialPortException ex) { 86 | throw new IOException("Cannot send message to device", ex); 87 | } 88 | } 89 | 90 | @Override 91 | public void setParser(Parser parser) { 92 | this.parser = parser; 93 | } 94 | 95 | @Override 96 | public void serialEvent(SerialPortEvent event) { 97 | // queueing data from input buffer to processing by FSM logic 98 | if (event.isRXCHAR() && event.getEventValue() > 0) { 99 | try { 100 | parser.parse(port.readBytes()); 101 | } catch (SerialPortException ex) { 102 | LOGGER.error("Cannot read from device", ex); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/transport/JSerialCommTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.transport; 25 | 26 | import java.io.IOException; 27 | 28 | import org.firmata4j.Parser; 29 | 30 | import com.fazecast.jSerialComm.SerialPort; 31 | import com.fazecast.jSerialComm.SerialPortDataListener; 32 | import com.fazecast.jSerialComm.SerialPortEvent; 33 | 34 | /** 35 | * Implementation of {@link TransportInterface} based on {@link SerialPort}. 36 | * 37 | * @author Norbert Sándor 38 | */ 39 | public class JSerialCommTransport implements TransportInterface { 40 | 41 | private final SerialPort serialPort; 42 | 43 | private Parser parser; 44 | 45 | public JSerialCommTransport(String portDescriptor) { 46 | serialPort = SerialPort.getCommPort(portDescriptor); 47 | } 48 | 49 | @Override 50 | public void start() throws IOException { 51 | if (!serialPort.isOpen()) { 52 | if (serialPort.openPort()) { 53 | serialPort.setComPortParameters(57600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); 54 | serialPort.addDataListener(new SerialPortDataListener() { 55 | @Override 56 | public void serialEvent(SerialPortEvent event) { 57 | if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_RECEIVED) { 58 | parser.parse(event.getReceivedData()); 59 | } 60 | } 61 | 62 | @Override 63 | public int getListeningEvents() { 64 | return SerialPort.LISTENING_EVENT_DATA_RECEIVED; 65 | } 66 | }); 67 | } else { 68 | throw new IOException("Cannot start firmata device: port=" + serialPort); 69 | } 70 | } 71 | } 72 | 73 | @Override 74 | public void stop() throws IOException { 75 | if (serialPort.isOpen() && !serialPort.closePort()) { 76 | throw new IOException("Cannot properly stop firmata device: port=" + serialPort); 77 | } 78 | } 79 | 80 | @Override 81 | public void write(byte[] bytes) throws IOException { 82 | serialPort.writeBytes(bytes, bytes.length); 83 | } 84 | 85 | @Override 86 | public void setParser(Parser parser) { 87 | this.parser = parser; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/transport/NetworkTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.transport; 25 | 26 | import java.io.DataInputStream; 27 | import java.io.DataOutputStream; 28 | import java.io.IOException; 29 | import java.net.InetAddress; 30 | import java.net.Socket; 31 | import java.net.SocketTimeoutException; 32 | import java.net.UnknownHostException; 33 | import org.firmata4j.Parser; 34 | 35 | /** 36 | * Allows connections over the network. 37 | * 38 | * @author Thomas Welsch <ttww@gmx.de> 39 | */ 40 | public class NetworkTransport implements TransportInterface { 41 | 42 | private Parser parser; 43 | private Thread readerThread; 44 | private Socket socket; 45 | private DataOutputStream out; 46 | private DataInputStream in; 47 | private final InetAddress ip; 48 | private final int port; 49 | 50 | /** 51 | * Creates network transport using a sting as address. 52 | * 53 | * Address should specify host and port. Examples: 54 | *
    55 | *
  • "192.168.1.10:36363"
  • 56 | *
  • "explorer-bot.local:72727"
  • 57 | *
58 | * @param address host and port 59 | */ 60 | public NetworkTransport(String address) { 61 | String[] parts = address.split(":"); 62 | if (parts.length != 2) { 63 | throw new IllegalArgumentException("Address must specify host and port but received " + address); 64 | } 65 | try { 66 | this.ip = InetAddress.getByName(parts[0]); 67 | } catch (UnknownHostException e) { 68 | throw new IllegalArgumentException(e); 69 | } 70 | this.port = Integer.valueOf(parts[1]); 71 | } 72 | 73 | public NetworkTransport(InetAddress ip, int port) { 74 | this.ip = ip; 75 | this.port = port; 76 | } 77 | 78 | @Override 79 | public void start() throws IOException { 80 | socket = new Socket(ip, port); 81 | socket.setReuseAddress(true); 82 | socket.setSoTimeout(1500); 83 | socket.setSoLinger(true, 1500); 84 | socket.setSoTimeout(1500); 85 | out = new DataOutputStream(socket.getOutputStream()); 86 | in = new DataInputStream(socket.getInputStream()); 87 | readerThread = new Thread(new Reader(), "firmata-network-transport"); 88 | readerThread.start(); 89 | } 90 | 91 | @Override 92 | public void stop() throws IOException { 93 | try { 94 | readerThread.interrupt(); 95 | readerThread.join(); 96 | } catch (InterruptedException e) { 97 | Thread.currentThread().interrupt(); 98 | } 99 | try { 100 | if (out != null) { 101 | out.close(); 102 | } 103 | } finally { 104 | try { 105 | if (in != null) { 106 | in.close(); 107 | } 108 | } finally { 109 | if (socket != null) { 110 | socket.close(); 111 | } 112 | } 113 | } 114 | out = null; 115 | in = null; 116 | socket = null; 117 | 118 | } 119 | 120 | @Override 121 | public void write(byte[] bytes) throws IOException { 122 | out.write(bytes); 123 | } 124 | 125 | @Override 126 | public void setParser(Parser parser) { 127 | this.parser = parser; 128 | } 129 | 130 | private class Reader implements Runnable { 131 | 132 | @Override 133 | public void run() { 134 | byte[] buf = new byte[100]; 135 | int readIn; 136 | while (!Thread.currentThread().isInterrupted()) { 137 | try { 138 | readIn = in.read(buf); 139 | } catch (SocketTimeoutException e) { 140 | break; // We try to reconnect, hearthbeats (1*second) missing 141 | } catch (IOException e) { 142 | break; 143 | } 144 | if (readIn == -1) { 145 | break; // Connection closed 146 | } 147 | byte[] data = new byte[readIn]; 148 | System.arraycopy(buf, 0, data, 0, readIn); 149 | parser.parse(data); 150 | } 151 | } 152 | 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/transport/SerialTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2021 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.transport; 25 | 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | import java.io.IOException; 29 | import org.firmata4j.Parser; 30 | 31 | /** 32 | * Transfers data over the serial interface. 33 | * 34 | * Using an instance of this transport requires one of the following libraries 35 | * present in the classpath: 36 | * 37 | *
38 |  *  <dependency>
39 |         <groupId>com.fazecast</groupId>
40 |         <artifactId>jSerialComm</artifactId>
41 |         <version>2.6.2</version>
42 |     </dependency>
43 |  *  <dependency>
44 |         <groupId>org.scream3r</groupId>
45 |         <artifactId>jssc</artifactId>
46 |         <version>2.8.0</version>
47 |     </dependency>
48 |  * 
49 | * 50 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 51 | */ 52 | public class SerialTransport implements TransportInterface { 53 | 54 | private TransportInterface delegate; 55 | 56 | private static final Logger LOGGER = LoggerFactory.getLogger(SerialTransport.class); 57 | 58 | public SerialTransport(String portName) { 59 | try { 60 | Class.forName("com.fazecast.jSerialComm.SerialPort", false, this.getClass().getClassLoader()); 61 | delegate = new JSerialCommTransport(portName); 62 | LOGGER.debug("Using jSerialComm transport"); 63 | } catch (ClassNotFoundException e) { 64 | try { 65 | Class.forName("jssc.SerialPort", false, this.getClass().getClassLoader()); 66 | delegate = new JSerialCommTransport(portName); 67 | LOGGER.debug("Using jssc transport"); 68 | } catch (ClassNotFoundException ex) { 69 | throw new IllegalStateException( 70 | "Serial communication library is not found in the classpath. " 71 | + "Please make sure that there is at least one dependency " 72 | + "as described in the javadoc of org.firmata4j.transport.SerialTransport"); 73 | } 74 | } 75 | } 76 | 77 | @Override 78 | public void start() throws IOException { 79 | delegate.start(); 80 | } 81 | 82 | @Override 83 | public void stop() throws IOException { 84 | delegate.stop(); 85 | } 86 | 87 | @Override 88 | public void write(byte[] bytes) throws IOException { 89 | delegate.write(bytes); 90 | } 91 | 92 | @Override 93 | public void setParser(Parser parser) { 94 | delegate.setParser(parser); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/transport/TransportInterface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2018 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.transport; 25 | 26 | import java.io.IOException; 27 | import org.firmata4j.Parser; 28 | 29 | /** 30 | * Interface to abstract the device connection (serial/network) to a device. 31 | * 32 | * @author Ali Kia 33 | */ 34 | public interface TransportInterface { 35 | 36 | /** 37 | * Starts the transport and initializes the connector. 38 | * 39 | * @throws java.io.IOException 40 | */ 41 | void start() throws IOException; 42 | 43 | /** 44 | * Shuts down the connector and stops the transport. 45 | * 46 | * @throws java.io.IOException 47 | */ 48 | void stop() throws IOException; 49 | 50 | /** 51 | * Sends data to the device. 52 | * 53 | * @param bytes data to send 54 | * @throws java.io.IOException 55 | */ 56 | void write(byte[] bytes) throws IOException; 57 | 58 | /** 59 | * Sets the parser. Transport transmits received data to the parser. 60 | * 61 | * @param parser data parser 62 | */ 63 | void setParser(Parser parser); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/ui/JPin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.ui; 26 | 27 | import java.awt.event.ActionEvent; 28 | import java.awt.event.MouseAdapter; 29 | import java.awt.event.MouseEvent; 30 | import java.io.IOException; 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | import javax.swing.AbstractAction; 34 | import javax.swing.Action; 35 | import javax.swing.ButtonGroup; 36 | import javax.swing.Icon; 37 | import javax.swing.ImageIcon; 38 | import javax.swing.JLabel; 39 | import javax.swing.JMenuItem; 40 | import javax.swing.JOptionPane; 41 | import javax.swing.JPopupMenu; 42 | import javax.swing.JRadioButtonMenuItem; 43 | import javax.swing.SwingUtilities; 44 | import org.firmata4j.IOEvent; 45 | import org.firmata4j.Pin; 46 | import org.firmata4j.PinEventListener; 47 | 48 | /** 49 | * Displays representation of a {@link Pin}.
50 | * Currently it supports displaying of pins in 51 | * {@link org.firmata4j.Pin.Mode#INPUT}, 52 | * {@link org.firmata4j.Pin.Mode#OUTPUT} and 53 | * {@link org.firmata4j.Pin.Mode#ANALOG} modes as well as disabled pins, i.e., 54 | * pins without supported modes. 55 | * 56 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 57 | */ 58 | public class JPin extends JLabel implements PinEventListener { 59 | 60 | private Pin model; 61 | private JPopupMenu modesMenu; 62 | private Runnable refreshRoutine = new Runnable() { 63 | @Override 64 | public void run() { 65 | refreshIcon(); 66 | } 67 | }; 68 | private static final Map> ICONS = new HashMap<>(); 69 | private static final Icon DISABLED_OFF; 70 | private static final Icon DISABLED_ON; 71 | 72 | static { 73 | ClassLoader classLoader = JPin.class.getClassLoader(); 74 | Map iconset = new HashMap<>(); 75 | ICONS.put(Pin.Mode.INPUT, iconset); 76 | iconset.put("on", new ImageIcon(classLoader.getResource("img/green-on.png"))); 77 | iconset.put("off", new ImageIcon(classLoader.getResource("img/green-off.png"))); 78 | 79 | ICONS.put(Pin.Mode.PULLUP, iconset); 80 | iconset.put("on", new ImageIcon(classLoader.getResource("img/green-on.png"))); 81 | iconset.put("off", new ImageIcon(classLoader.getResource("img/green-off.png"))); 82 | 83 | iconset = new HashMap<>(); 84 | ICONS.put(Pin.Mode.OUTPUT, iconset); 85 | iconset.put("on", new ImageIcon(classLoader.getResource("img/blue-on.png"))); 86 | iconset.put("off", new ImageIcon(classLoader.getResource("img/blue-off.png"))); 87 | 88 | DISABLED_ON = new ImageIcon(classLoader.getResource("img/gray-on.png")); 89 | DISABLED_OFF = new ImageIcon(classLoader.getResource("img/gray-off.png")); 90 | } 91 | 92 | public JPin(Pin pin) { 93 | setHorizontalAlignment(JLabel.CENTER); 94 | modesMenu = new JPopupMenu(String.valueOf(pin.getIndex())); 95 | setModel(pin); 96 | addMouseListener(new MouseAdapter() { 97 | @Override 98 | public void mouseClicked(MouseEvent e) { 99 | if (e.getClickCount() == 2) { 100 | if (model.getMode() == Pin.Mode.OUTPUT) { 101 | long newValue = (model.getValue() == 0 ? 1 : 0); 102 | try { 103 | model.setValue(newValue); 104 | } catch (IOException ex) { 105 | JOptionPane.showMessageDialog(JPin.this, ex.getLocalizedMessage(), "", JOptionPane.ERROR_MESSAGE); 106 | } 107 | } 108 | } 109 | } 110 | 111 | @Override 112 | public void mousePressed(MouseEvent e) { 113 | if (e.isPopupTrigger()) { 114 | modesMenu.show(e.getComponent(), e.getX(), e.getY()); 115 | } 116 | } 117 | 118 | @Override 119 | public void mouseReleased(MouseEvent e) { 120 | if (e.isPopupTrigger()) { 121 | modesMenu.show(e.getComponent(), e.getX(), e.getY()); 122 | } 123 | } 124 | }); 125 | } 126 | 127 | @Override 128 | public void onModeChange(IOEvent event) { 129 | SwingUtilities.invokeLater(refreshRoutine); 130 | } 131 | 132 | @Override 133 | public void onValueChange(IOEvent event) { 134 | SwingUtilities.invokeLater(refreshRoutine); 135 | } 136 | 137 | public final void setModel(final Pin model) { 138 | if (this.model != null) { 139 | this.model.removeEventListener(this); 140 | modesMenu.removeAll(); 141 | } 142 | this.model = model; 143 | ButtonGroup group = new ButtonGroup(); 144 | for (Pin.Mode mode : model.getSupportedModes()) { 145 | Action action = new AbstractAction(mode.name()) { 146 | @Override 147 | public void actionPerformed(ActionEvent e) { 148 | try { 149 | model.setMode((Pin.Mode) getValue("mode")); 150 | } catch (IOException ex) { 151 | JOptionPane.showMessageDialog(JPin.this, ex.getLocalizedMessage(), "", JOptionPane.ERROR_MESSAGE); 152 | } 153 | } 154 | }; 155 | action.putValue("mode", mode); 156 | JMenuItem item = new JRadioButtonMenuItem(action); 157 | item.setSelected(model.getMode() == mode); 158 | group.add(item); 159 | modesMenu.add(item); 160 | } 161 | model.addEventListener(this); 162 | refreshIcon(); 163 | } 164 | 165 | private void refreshIcon() { 166 | Pin.Mode mode = model.getMode(); 167 | setToolTipText(String.valueOf(mode)); 168 | if (mode == null) { 169 | setIcon(DISABLED_OFF); 170 | setToolTipText("disabled"); 171 | } else if (ICONS.containsKey(mode)) { 172 | String key = (model.getValue() == 0 ? "off" : "on"); 173 | setIcon(ICONS.get(model.getMode()).get(key)); 174 | setText(null); 175 | } else if (mode == Pin.Mode.ANALOG) { 176 | setText(String.valueOf(model.getValue())); 177 | setIcon(null); 178 | } else { 179 | // there were no special icon registered, so show gray icon and mode name 180 | setIcon(model.getValue() == 0 ? DISABLED_OFF : DISABLED_ON); 181 | setText(mode.name()); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/org/firmata4j/ui/JPinboard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.ui; 26 | 27 | import java.awt.GridBagConstraints; 28 | import java.awt.GridBagLayout; 29 | import javax.swing.JLabel; 30 | import javax.swing.JPanel; 31 | import org.firmata4j.IODevice; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | /** 36 | * Displays pins states of {@link IODevice}. 37 | * 38 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 39 | */ 40 | public class JPinboard extends JPanel { 41 | 42 | private static final Logger LOGGER = LoggerFactory.getLogger(JPinboard.class); 43 | 44 | public JPinboard(IODevice model) { 45 | GridBagLayout layout = new GridBagLayout(); 46 | 47 | setLayout(layout); 48 | for (int i = 0; i < model.getPinsCount(); i++) { 49 | JPin pin = new JPin(model.getPin(i)); 50 | GridBagConstraints constraints = new GridBagConstraints(); 51 | constraints.gridy = 0; 52 | constraints.fill = GridBagConstraints.HORIZONTAL; 53 | constraints.weightx = 1; 54 | layout.setConstraints(pin, constraints); 55 | add(pin); 56 | constraints = new GridBagConstraints(); 57 | constraints.gridy = 1; 58 | JLabel label = new JLabel(String.valueOf(i)); 59 | layout.setConstraints(label, constraints); 60 | add(label); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/img/blue-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurbatov/firmata4j/308dba36e3b202b78b4642e30f3ebfa130b70c67/src/main/resources/img/blue-off.png -------------------------------------------------------------------------------- /src/main/resources/img/blue-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurbatov/firmata4j/308dba36e3b202b78b4642e30f3ebfa130b70c67/src/main/resources/img/blue-on.png -------------------------------------------------------------------------------- /src/main/resources/img/firmata4j.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurbatov/firmata4j/308dba36e3b202b78b4642e30f3ebfa130b70c67/src/main/resources/img/firmata4j.png -------------------------------------------------------------------------------- /src/main/resources/img/gray-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurbatov/firmata4j/308dba36e3b202b78b4642e30f3ebfa130b70c67/src/main/resources/img/gray-off.png -------------------------------------------------------------------------------- /src/main/resources/img/gray-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurbatov/firmata4j/308dba36e3b202b78b4642e30f3ebfa130b70c67/src/main/resources/img/gray-on.png -------------------------------------------------------------------------------- /src/main/resources/img/green-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurbatov/firmata4j/308dba36e3b202b78b4642e30f3ebfa130b70c67/src/main/resources/img/green-off.png -------------------------------------------------------------------------------- /src/main/resources/img/green-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurbatov/firmata4j/308dba36e3b202b78b4642e30f3ebfa130b70c67/src/main/resources/img/green-on.png -------------------------------------------------------------------------------- /src/test/java/org/firmata4j/firmata/FirmataDeviceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package org.firmata4j.firmata; 25 | 26 | import java.io.IOException; 27 | import org.firmata4j.Parser; 28 | import org.firmata4j.firmata.parser.FirmataToken; 29 | import org.firmata4j.transport.TransportInterface; 30 | import org.junit.Test; 31 | import static org.junit.Assert.*; 32 | 33 | /** 34 | * 35 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 36 | */ 37 | public class FirmataDeviceTest { 38 | 39 | private Parser p; 40 | 41 | @Test 42 | public void executorShutdownTest() throws IOException, InterruptedException { 43 | FirmataDevice device = new FirmataDevice(new TransportInterface() { 44 | @Override 45 | public void start() throws IOException { 46 | 47 | } 48 | 49 | @Override 50 | public void stop() throws IOException { 51 | 52 | } 53 | 54 | @Override 55 | public void write(byte[] bytes) throws IOException { 56 | 57 | } 58 | 59 | @Override 60 | public void setParser(Parser parser) { 61 | p = parser; 62 | } 63 | }); 64 | device.start(); 65 | p.parse(new byte[] {FirmataToken.SYSTEM_RESET}); 66 | Thread.sleep(100); 67 | Thread t = getThreadByName("firmata-event-handler"); 68 | assertNotNull("firmata-event-handler thread should be started", t); 69 | assertTrue("firmata-event-handler should be alive before stop", t.isAlive()); 70 | device.stop(); 71 | Thread.sleep(100); 72 | assertFalse("firmata-event-handler should not be alive after stop", t.isAlive()); 73 | p = null; 74 | } 75 | 76 | private static Thread getThreadByName(String threadName) { 77 | for (Thread t : Thread.getAllStackTraces().keySet()) { 78 | if (t.getName().startsWith(threadName)) { 79 | return t; 80 | } 81 | } 82 | return null; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/org/firmata4j/firmata/fsm/FiniteStateMachineTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2019 Oleg Kurbatov (o.v.kurbatov@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package org.firmata4j.firmata.fsm; 26 | 27 | import org.firmata4j.fsm.Event; 28 | import org.firmata4j.fsm.FiniteStateMachine; 29 | import org.junit.Before; 30 | import org.junit.Test; 31 | 32 | /** 33 | * 34 | * @author Oleg Kurbatov <o.v.kurbatov@gmail.com> 35 | */ 36 | public class FiniteStateMachineTest { 37 | 38 | private FiniteStateMachine fsm; 39 | 40 | @Before 41 | public void setUp() { 42 | fsm = new FiniteStateMachine(); 43 | } 44 | 45 | @Test 46 | public void testHandle() { 47 | Event evt = new Event("error"); 48 | evt.setBodyItem("test", "test value"); 49 | fsm.handle(evt); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/firmata4j/firmata/parser/FirmataParserTest.java: -------------------------------------------------------------------------------- 1 | package org.firmata4j.firmata.parser; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import org.firmata4j.fsm.Event; 8 | import org.firmata4j.fsm.FiniteStateMachine; 9 | import org.junit.After; 10 | import org.junit.Test; 11 | 12 | public class FirmataParserTest { 13 | @After 14 | public void tearDown() throws InterruptedException { 15 | assertIfThreadStillRunning("firmata"); 16 | assertIfThreadStillRunning("parser"); 17 | } 18 | 19 | @Test 20 | public void testConstruct() { 21 | FiniteStateMachine fsm = new FiniteStateMachine(); 22 | FirmataParser parser = new FirmataParser(fsm); 23 | parser.start(); 24 | parser.stop(); 25 | } 26 | 27 | @Test 28 | public void testStartAndStopTwice() { 29 | FiniteStateMachine fsm = new FiniteStateMachine(); 30 | FirmataParser parser = new FirmataParser(fsm); 31 | parser.start(); 32 | parser.start(); 33 | parser.stop(); 34 | parser.stop(); 35 | } 36 | 37 | @Test 38 | public void testParse() throws InterruptedException { 39 | final AtomicInteger eventCount = new AtomicInteger(0); 40 | FiniteStateMachine fsm = new FiniteStateMachine() { 41 | @Override 42 | public void handle(Event event) { 43 | eventCount.incrementAndGet(); 44 | } 45 | }; 46 | FirmataParser parser = new FirmataParser(fsm); 47 | parser.start(); 48 | try { 49 | parser.parse(new byte[]{1, 2, 3}); 50 | int i = 0; 51 | while (eventCount.get() < 3 && i < 20) { 52 | Thread.sleep(100); 53 | i++; 54 | } 55 | assertEquals( 56 | "Should receive 3 events for each byte", 57 | 3, 58 | eventCount.get() 59 | ); 60 | } finally { 61 | parser.stop(); 62 | } 63 | } 64 | 65 | @Test 66 | public void testParseNull() throws InterruptedException { 67 | final AtomicInteger eventCount = new AtomicInteger(0); 68 | FiniteStateMachine fsm = new FiniteStateMachine() { 69 | @Override 70 | public void handle(Event event) { 71 | eventCount.incrementAndGet(); 72 | } 73 | }; 74 | FirmataParser parser = new FirmataParser(fsm); 75 | parser.start(); 76 | try { 77 | parser.parse(null); 78 | int i = 0; 79 | while (eventCount.get() == 0 && i < 10) { 80 | Thread.sleep(100); 81 | i++; 82 | } 83 | assertEquals( 84 | "Should receive no events at all with null bytes array", 85 | 0, 86 | eventCount.get() 87 | ); 88 | } finally { 89 | parser.stop(); 90 | } 91 | } 92 | 93 | private static void assertIfThreadStillRunning(final String contains) throws InterruptedException { 94 | int count = Thread.currentThread().getThreadGroup().activeCount(); 95 | Thread[] threads = new Thread[count]; 96 | Thread.currentThread().getThreadGroup().enumerate(threads); 97 | for (Thread t : threads) { 98 | if (t != null && t.getName().contains(contains)) { 99 | t.join(); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.defaultLogLevel=debug --------------------------------------------------------------------------------