├── PrintClient ├── app │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── assets │ │ │ │ └── fonts │ │ │ │ │ └── Ozone.ttf │ │ │ ├── res │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ ├── add.png │ │ │ │ │ ├── back.png │ │ │ │ │ ├── camera.png │ │ │ │ │ ├── clear.png │ │ │ │ │ ├── delete.png │ │ │ │ │ ├── dummy.jpg │ │ │ │ │ ├── filter.png │ │ │ │ │ ├── invert.png │ │ │ │ │ ├── collections.png │ │ │ │ │ ├── ic_drawer.png │ │ │ │ │ ├── print_error.png │ │ │ │ │ ├── filter_small.png │ │ │ │ │ ├── invert_small.png │ │ │ │ │ ├── print_offline.png │ │ │ │ │ ├── print_online.png │ │ │ │ │ └── drawer_shadow.9.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ │ ├── attrs.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── drawable │ │ │ │ │ ├── slot.xml │ │ │ │ │ ├── control_back.xml │ │ │ │ │ ├── button_back.xml │ │ │ │ │ ├── button.xml │ │ │ │ │ ├── button_back_pressed.xml │ │ │ │ │ ├── thumb_back.xml │ │ │ │ │ └── control_stick.xml │ │ │ │ ├── layout │ │ │ │ │ ├── drawer_item.xml │ │ │ │ │ ├── list_item_filter.xml │ │ │ │ │ ├── list_item_filter_selected.xml │ │ │ │ │ ├── menu_item_offline.xml │ │ │ │ │ ├── menu_item_connected.xml │ │ │ │ │ ├── activity_main.xml │ │ │ │ │ ├── list_item_print.xml │ │ │ │ │ ├── frg_prints_list.xml │ │ │ │ │ ├── frg_printer.xml │ │ │ │ │ ├── activity_print_load.xml │ │ │ │ │ ├── activity_print_process.xml │ │ │ │ │ ├── activity_edit.xml │ │ │ │ │ └── frg_control.xml │ │ │ │ └── menu │ │ │ │ │ └── menu_main.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── tinkerlog │ │ │ │ │ └── printclient │ │ │ │ │ ├── model │ │ │ │ │ ├── Status.java │ │ │ │ │ ├── Image.java │ │ │ │ │ ├── StatusCommand.java │ │ │ │ │ ├── ImageCommand.java │ │ │ │ │ ├── Command.java │ │ │ │ │ ├── Print.java │ │ │ │ │ └── Printer.java │ │ │ │ │ ├── activity │ │ │ │ │ ├── BaseFragment.java │ │ │ │ │ ├── BaseActivity.java │ │ │ │ │ ├── PrinterFragment.java │ │ │ │ │ ├── PrintListFragment.java │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ ├── ProcessActivity.java │ │ │ │ │ ├── ControlFragment.java │ │ │ │ │ └── EditPrintActivity.java │ │ │ │ │ ├── util │ │ │ │ │ ├── FontCache.java │ │ │ │ │ ├── ResponseHandler.java │ │ │ │ │ ├── Prefs.java │ │ │ │ │ └── ImageCache.java │ │ │ │ │ ├── view │ │ │ │ │ ├── CustomTextView.java │ │ │ │ │ ├── CustomToggleButton.java │ │ │ │ │ ├── CustomButton.java │ │ │ │ │ └── CustomEditText.java │ │ │ │ │ ├── network │ │ │ │ │ ├── NetworkScanner.java │ │ │ │ │ ├── NetworkHelper.java │ │ │ │ │ └── ComThread.java │ │ │ │ │ ├── AppContext.java │ │ │ │ │ └── Const.java │ │ │ └── AndroidManifest.xml │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── tinkerlog │ │ │ └── printclient │ │ │ └── ApplicationTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradlew.bat └── gradlew ├── README.md ├── design └── PaintMachine.skp ├── firmware ├── paintmachine │ ├── display.h │ ├── input.h │ ├── spraycan.h │ ├── display.cpp │ ├── motor.h │ ├── input.cpp │ ├── motor.cpp │ ├── spraycan.cpp │ └── paintmachine.ino └── huzzah │ └── huzzah.ino └── LICENSE.md /PrintClient/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /PrintClient/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | More here http://tinkerlog.com/2015/11/17/paint-machine/ 2 | -------------------------------------------------------------------------------- /design/PaintMachine.skp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/design/PaintMachine.skp -------------------------------------------------------------------------------- /PrintClient/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /PrintClient/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/assets/fonts/Ozone.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/assets/fonts/Ozone.ttf -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/add.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/back.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/camera.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/clear.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/delete.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/dummy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/dummy.jpg -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/filter.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/invert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/invert.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/collections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/collections.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/ic_drawer.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/print_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/print_error.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/filter_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/filter_small.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/invert_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/invert_small.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/print_offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/print_offline.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/print_online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/print_online.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerlog/PaintMachine/HEAD/PrintClient/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /firmware/paintmachine/display.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef display_h 3 | #define display_h 4 | 5 | #include "Arduino.h" 6 | 7 | void initDisplay(); 8 | void clearDisplay(); 9 | void display(char* msg); 10 | void updateDisplay(); 11 | 12 | #endif display_h 13 | 14 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable/slot.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable/control_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable/button_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PrintClient/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Aug 07 12:49:23 CEST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 7 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable/button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable/button_back_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable/thumb_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/model/Status.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.model; 2 | 3 | public class Status { 4 | public int leftPosition; 5 | public int rightPosition; 6 | public int headPosition; 7 | public boolean leftEndSwitch; 8 | public boolean rightEndSwitch; 9 | } 10 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/drawable/control_stick.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /firmware/paintmachine/input.h: -------------------------------------------------------------------------------- 1 | #ifndef input_h 2 | #define input_h 3 | 4 | #include "Arduino.h" 5 | 6 | #define MAX_IMAGE_SIZE (8*1024) 7 | 8 | char *readToken(char *str, char *buf, char delimiter); 9 | 10 | byte readLine(UARTClass *cmdSerial, char *line, int size); 11 | 12 | boolean readCmdLine(UARTClass *cmdSerial, char* line); 13 | 14 | void readImage(char *line, byte *image, int* imageWidth, int* imageHeight); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/model/Image.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.model; 2 | 3 | public class Image { 4 | 5 | // 40 pixel height 6 | // => 8 bytes per column 7 | 8 | public int width; 9 | public int height; 10 | 11 | public int[] data; 12 | 13 | public Image(int width, int height) { 14 | this.width = width; 15 | this.height = height; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /PrintClient/app/src/androidTest/java/com/tinkerlog/printclient/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/drawer_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /PrintClient/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/list_item_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FFD0D0D0 5 | #FF303030 6 | #FF202020 7 | #B0000000 8 | #FFE0E0E0 9 | #FF303030 10 | #FF20A080 11 | #FF20A080 12 | 13 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/list_item_filter_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /PrintClient/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.tinkerlog.printclient" 9 | minSdkVersion 17 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:22.1.1' 25 | } 26 | -------------------------------------------------------------------------------- /PrintClient/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /data/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.app.Activity; 4 | import android.app.Fragment; 5 | 6 | import com.tinkerlog.printclient.AppContext; 7 | import com.tinkerlog.printclient.model.Printer; 8 | 9 | /** 10 | * Created by alex on 27.08.15. 11 | */ 12 | public class BaseFragment extends Fragment { 13 | 14 | protected AppContext appContext; 15 | protected Printer printer; 16 | 17 | @Override 18 | public void onAttach(Activity activity) { 19 | super.onAttach(activity); 20 | appContext = (AppContext)activity.getApplicationContext(); 21 | printer = appContext.getPrinter(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/model/StatusCommand.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.model; 2 | 3 | import com.tinkerlog.printclient.util.ResponseHandler; 4 | 5 | public class StatusCommand extends Command { 6 | 7 | public int leftPos; 8 | public int headPos; 9 | public int rightPos; 10 | 11 | public StatusCommand(ResponseHandler handler) { 12 | super("stat", handler); 13 | } 14 | 15 | public void setResponse(String resp) { 16 | this.response = resp.trim(); 17 | String[] tokens = response.split(" "); 18 | leftPos = Integer.parseInt(tokens[0]); 19 | rightPos = Integer.parseInt(tokens[1]); 20 | headPos = Integer.parseInt(tokens[2]); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/model/ImageCommand.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.model; 2 | 3 | import com.tinkerlog.printclient.util.ResponseHandler; 4 | 5 | public class ImageCommand extends Command { 6 | 7 | public ImageCommand(Image image, ResponseHandler handler) { 8 | super(null, handler); 9 | StringBuilder sbuf = new StringBuilder(); 10 | sbuf.append("imag ") 11 | .append(image.width) 12 | .append(" ") 13 | .append(image.height) 14 | .append(" "); 15 | 16 | for (int i = 0; i < image.data.length; i++) { 17 | sbuf.append(image.data[i]).append(" "); 18 | } 19 | request = sbuf.toString(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v4.app.FragmentActivity; 6 | 7 | import com.tinkerlog.printclient.AppContext; 8 | import com.tinkerlog.printclient.model.Printer; 9 | 10 | /** 11 | * Created by alex on 24.08.15. 12 | */ 13 | public class BaseActivity extends FragmentActivity { 14 | 15 | protected AppContext appContext; 16 | protected Printer printer; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | appContext = (AppContext)getApplicationContext(); 22 | printer = appContext.getPrinter(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/util/FontCache.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.util; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by alex on 21.08.15. 11 | */ 12 | public class FontCache { 13 | 14 | private static Map fontMap = new HashMap(); 15 | 16 | public static Typeface getFont(Context context, String fontName){ 17 | if (fontMap.containsKey(fontName)){ 18 | return fontMap.get(fontName); 19 | } 20 | else { 21 | Typeface tf = Typeface.createFromAsset(context.getAssets(), fontName); 22 | fontMap.put(fontName, tf); 23 | return tf; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/menu_item_offline.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 20 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/menu_item_connected.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 20 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/model/Command.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.model; 2 | 3 | import com.tinkerlog.printclient.util.ResponseHandler; 4 | 5 | public class Command { 6 | 7 | protected String request; 8 | protected String response; 9 | public ResponseHandler handler; 10 | 11 | public Command(String request, ResponseHandler handler) { 12 | this.request = request; 13 | this.handler = handler; 14 | } 15 | 16 | public void setResponse(String response) { 17 | this.response = response; 18 | } 19 | 20 | public String getResponse() { 21 | return response; 22 | } 23 | 24 | public String getRequest() { 25 | return request; 26 | } 27 | 28 | public byte[] getPayload() { 29 | return request.getBytes(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PrintClient 3 | 4 | Hello world! 5 | 6 | Settings 7 | Connect 8 | 9 | CONNECTED 10 | CONNECTION LOST 11 | CONNECTION LOST 12 | 13 | 14 | PRINTS 15 | MANUAL CONTROL 16 | PRINTER 17 | 18 | 19 | 20 | red 21 | green 22 | blue 23 | b/w 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PrintClient/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /firmware/paintmachine/spraycan.h: -------------------------------------------------------------------------------- 1 | #ifndef spraycan_h 2 | #define spraycan_h 3 | 4 | #include "Arduino.h" 5 | 6 | #define MAX_X 128 7 | #define MAX_Y 48 8 | 9 | #define Y_REVERSE_OFFSET 375 10 | #define Y_START_OFFSET 375 11 | 12 | 13 | 14 | class SprayCan { 15 | 16 | public: 17 | SprayCan(int sprayPin, int pixelHeigth); 18 | void prepareImage(byte *image, int width, int height); 19 | void startImage(); 20 | void printColumn(int column, int position, boolean isForward); 21 | boolean isEmpty(int column); 22 | int getYMin(int column); 23 | int getYMax(int column); 24 | void finishColumn(); 25 | void finishImage(); 26 | void spray(boolean isOn); 27 | 28 | private: 29 | void toggleSpray(); 30 | 31 | int sprayPin; 32 | int pixelHeight; 33 | int pixelHeight_2; 34 | int width; 35 | int height; 36 | int positions[MAX_X][MAX_Y]; 37 | int endPositions[MAX_X][2]; 38 | int posCount; 39 | 40 | int actColumn; 41 | int actPos; 42 | boolean actIsOn; 43 | 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexander Weber, tinkerlog.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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 20 | 21 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 11 | 12 | 17 | 18 | 25 | 26 | 27 | 30 | 31 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /firmware/paintmachine/display.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "display.h" 3 | 4 | #include 5 | #include "Adafruit_LEDBackpack.h" 6 | #include "Adafruit_GFX.h" 7 | 8 | 9 | Adafruit_AlphaNum4 display0 = Adafruit_AlphaNum4(); 10 | Adafruit_AlphaNum4 display1 = Adafruit_AlphaNum4(); 11 | 12 | char message[80]; 13 | char blanks[] = " "; 14 | int pos; 15 | 16 | void initDisplay() { 17 | display0.begin(0x71); // pass in the address 18 | display1.begin(0x70); // pass in the address 19 | pos = 0; 20 | } 21 | 22 | void clearDisplay() { 23 | display0.clear(); 24 | display1.clear(); 25 | display0.writeDisplay(); 26 | display1.writeDisplay(); 27 | } 28 | 29 | /* 30 | void setMessage(char* msg) { 31 | int msgLen = strlen(msg); 32 | strcpy(message, msg); 33 | char* p = message 34 | } 35 | */ 36 | 37 | void display(char* msg) { 38 | clearDisplay(); 39 | int c; 40 | int pos = 0; 41 | boolean displayDot = false; 42 | while (pos < 8) { 43 | c = *msg++; 44 | if (c == '\0') { 45 | break; 46 | } 47 | displayDot = (c == '.'); 48 | if (pos < 4) { 49 | display0.writeDigitAscii(pos, c, displayDot); 50 | } 51 | else { 52 | display1.writeDigitAscii(pos-4, c, displayDot); 53 | } 54 | pos++; 55 | } 56 | display0.writeDisplay(); 57 | display1.writeDisplay(); 58 | } 59 | 60 | void updateDisplay() { 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/view/CustomTextView.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.util.AttributeSet; 7 | import android.widget.TextView; 8 | 9 | import com.tinkerlog.printclient.R; 10 | import com.tinkerlog.printclient.util.FontCache; 11 | 12 | public class CustomTextView extends TextView { 13 | 14 | public CustomTextView(Context context) { 15 | super(context); 16 | initialize(context, null); 17 | } 18 | 19 | public CustomTextView(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | initialize(context, attrs); 22 | } 23 | 24 | public CustomTextView(Context context, AttributeSet attrs, int defStyle) { 25 | super(context, attrs, defStyle); 26 | initialize(context, attrs); 27 | } 28 | 29 | private void initialize(Context context, AttributeSet attrs) { 30 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView); 31 | String fontName = a.getString(R.styleable.CustomTextView_fontName); 32 | if (fontName != null) { 33 | Typeface tf = FontCache.getFont(context, fontName); 34 | setTypeface(tf); 35 | } 36 | a.recycle(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/view/CustomToggleButton.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.util.AttributeSet; 7 | import android.widget.ToggleButton; 8 | 9 | import com.tinkerlog.printclient.R; 10 | import com.tinkerlog.printclient.util.FontCache; 11 | 12 | public class CustomToggleButton extends ToggleButton { 13 | 14 | public CustomToggleButton(Context context, AttributeSet attrs, int defStyleAttr) { 15 | super(context, attrs, defStyleAttr); 16 | initialize(context, attrs); 17 | } 18 | 19 | public CustomToggleButton(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | initialize(context, attrs); 22 | } 23 | 24 | public CustomToggleButton(Context context) { 25 | super(context); 26 | initialize(context, null); 27 | } 28 | 29 | private void initialize(Context context, AttributeSet attrs) { 30 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView); 31 | String fontName = a.getString(R.styleable.CustomTextView_fontName); 32 | if (fontName != null) { 33 | Typeface tf = FontCache.getFont(context, fontName); 34 | setTypeface(tf); 35 | } 36 | a.recycle(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/view/CustomButton.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.util.AttributeSet; 7 | import android.widget.Button; 8 | 9 | import com.tinkerlog.printclient.R; 10 | import com.tinkerlog.printclient.util.FontCache; 11 | 12 | /** 13 | * Created by alex on 24.08.15. 14 | */ 15 | public class CustomButton extends Button { 16 | 17 | public CustomButton(Context context) { 18 | super(context); 19 | initialize(context, null); 20 | } 21 | 22 | public CustomButton(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | initialize(context, attrs); 25 | } 26 | 27 | public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) { 28 | super(context, attrs, defStyleAttr); 29 | initialize(context, attrs); 30 | } 31 | 32 | private void initialize(Context context, AttributeSet attrs) { 33 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView); 34 | String fontName = a.getString(R.styleable.CustomTextView_fontName); 35 | if (fontName != null) { 36 | Typeface tf = FontCache.getFont(context, fontName); 37 | setTypeface(tf); 38 | } 39 | a.recycle(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/view/CustomEditText.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.util.AttributeSet; 7 | import android.widget.EditText; 8 | 9 | import com.tinkerlog.printclient.R; 10 | import com.tinkerlog.printclient.util.FontCache; 11 | 12 | /** 13 | * Created by alex on 24.08.15. 14 | */ 15 | public class CustomEditText extends EditText { 16 | 17 | public CustomEditText(Context context) { 18 | super(context); 19 | initialize(context, null); 20 | } 21 | 22 | public CustomEditText(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | initialize(context, attrs); 25 | } 26 | 27 | public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) { 28 | super(context, attrs, defStyleAttr); 29 | initialize(context, attrs); 30 | } 31 | 32 | private void initialize(Context context, AttributeSet attrs) { 33 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView); 34 | String fontName = a.getString(R.styleable.CustomTextView_fontName); 35 | if (fontName != null) { 36 | Typeface tf = FontCache.getFont(context, fontName); 37 | setTypeface(tf); 38 | } 39 | a.recycle(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/util/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.util; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.Message; 6 | import android.util.Log; 7 | 8 | import com.tinkerlog.printclient.model.Command; 9 | 10 | public class ResponseHandler extends Handler { 11 | 12 | private static final String TAG = "ResponseHandler"; 13 | 14 | public static final int OK = 1; 15 | public static final int ERROR = 2; 16 | 17 | public ResponseHandler() { 18 | super(); 19 | } 20 | 21 | public ResponseHandler(Callback callback) { 22 | super(callback); 23 | } 24 | 25 | public ResponseHandler(Looper looper) { 26 | super(looper); 27 | } 28 | 29 | public ResponseHandler(Looper looper, Callback callback) { 30 | super(looper, callback); 31 | } 32 | 33 | @Override 34 | public void handleMessage(Message msg) { 35 | switch (msg.what) { 36 | case OK: 37 | handleSuccess((Command)msg.obj); 38 | break; 39 | case ERROR: 40 | handleFailed((Command)msg.obj); 41 | break; 42 | } 43 | } 44 | 45 | public void handleFailed(Command cmd) { 46 | Log.e(TAG, "failed: " + cmd.getRequest()); 47 | } 48 | 49 | public void handleSuccess(Command cmd) { 50 | Log.d(TAG, "success: " + cmd.getResponse()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /firmware/paintmachine/motor.h: -------------------------------------------------------------------------------- 1 | #ifndef motor_h 2 | #define motor_h 3 | 4 | #include "Arduino.h" 5 | 6 | #define ENABLE_PIN 40 7 | 8 | #define MOTOR_STATE_IDLE 0 9 | #define MOTOR_STATE_TARGET 1 10 | #define MOTOR_STATE_SPEED 2 11 | 12 | #define DIR_LEFT 0 13 | #define DIR_RIGHT 1 14 | #define DIR_FORWARD 0 15 | #define DIR_BACKWARD 1 16 | 17 | class Motor { 18 | 19 | public: 20 | Motor(int pwmPin, int dirPin, int encAPin, int encBPin, int maxSpeed, float stepsPerMm); 21 | 22 | void setMaxSpeed(int maxSpeed); 23 | void setInverted(boolean inverted); 24 | void setTarget(int target); 25 | void setTargetMm(int targetInMm); 26 | int getTarget() { return target; } 27 | void setSpeed(int speed); 28 | int getSpeed() { return speed; } 29 | void setDirection(int direction); 30 | int getDirection() { return direction; } 31 | void setPosition(int position); 32 | int getPosition() { return position; } 33 | 34 | void encoderA(); 35 | void encoderB(); 36 | void loop(); 37 | void debug(); 38 | 39 | int getState() { return state; } 40 | boolean isRunning(); 41 | 42 | static void enableMotors(); 43 | static void disableMotors(); 44 | 45 | private: 46 | 47 | int compute(); 48 | 49 | int pwmPin; 50 | int dirPin; 51 | int encAPin; 52 | int encBPin; 53 | int direction; 54 | int position; 55 | int deltaPos; 56 | int target; 57 | int maxSpeed; 58 | int speed; 59 | int state; 60 | float stepsPerMm; 61 | 62 | boolean inverted; 63 | 64 | volatile int errorSum = 0; 65 | volatile double lastError = 0.0; 66 | 67 | }; 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/list_item_print.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 27 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/network/NetworkScanner.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.network; 2 | 3 | import android.content.Context; 4 | import android.net.wifi.ScanResult; 5 | import android.net.wifi.WifiManager; 6 | import android.util.Log; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by alex on 11.08.15. 12 | */ 13 | public class NetworkScanner extends Thread { 14 | 15 | private static final String TAG = "NetworkScanner"; 16 | 17 | private WifiManager wifiManager; 18 | private String ssid; 19 | private Runnable onFound; 20 | private boolean running = true; 21 | 22 | public NetworkScanner(Context context, String ssid, Runnable onFound) { 23 | wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); 24 | this.ssid = ssid; 25 | this.onFound = onFound; 26 | } 27 | 28 | public void shutdown() { 29 | running = false; 30 | } 31 | 32 | public void run() { 33 | while (running) { 34 | try { 35 | Log.d(TAG, "scanning networks ..."); 36 | List scan = wifiManager.getScanResults(); 37 | for (ScanResult result : scan) { 38 | if (result.SSID.equals(ssid)) { 39 | Log.d(TAG, "found " + ssid); 40 | if (onFound != null) { 41 | onFound.run(); 42 | } 43 | running = false; 44 | break; 45 | } 46 | } 47 | if (running) { 48 | Thread.sleep(1000); 49 | } 50 | } 51 | catch (InterruptedException e) { 52 | // ignored 53 | } 54 | } 55 | Log.d(TAG, "scanner going down"); 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/util/Prefs.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.util; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.SharedPreferences.Editor; 6 | import android.util.Log; 7 | 8 | import com.tinkerlog.printclient.model.Print; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | /** 15 | * 16 | */ 17 | public class Prefs { 18 | 19 | private static final String TAG = "Prefs"; 20 | private static final String PREFERENCES = "printclient"; 21 | 22 | private static final String KEY_PRINT_PREFIX = "print_"; 23 | private static final String KEY_ADDRESS = "address"; 24 | 25 | private SharedPreferences prefs; 26 | 27 | public Prefs(Context context) { 28 | this.prefs = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE); 29 | } 30 | 31 | public SharedPreferences getPreferences() { 32 | return prefs; 33 | } 34 | 35 | public void storePrint(Print print) { 36 | Editor e = prefs.edit(); 37 | e.putString(KEY_PRINT_PREFIX + print.creationDate, print.encode()); 38 | e.commit(); 39 | } 40 | 41 | public void deletePrint(Print print) { 42 | Editor e = prefs.edit(); 43 | e.remove(KEY_PRINT_PREFIX + print.creationDate); 44 | e.commit(); 45 | } 46 | 47 | public List loadPrints() { 48 | List prints = new ArrayList(); 49 | for (String key : prefs.getAll().keySet()) { 50 | if (key.startsWith(KEY_PRINT_PREFIX)) { 51 | try { 52 | prints.add(Print.decode(prefs.getString(key, null))); 53 | } 54 | catch (Exception e) { 55 | Log.w(TAG, "parsing failed", e); 56 | } 57 | } 58 | } 59 | return prints; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/frg_prints_list.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 35 | 36 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /firmware/paintmachine/input.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "input.h" 4 | 5 | 6 | char *readToken(char *str, char *buf, char delimiter) { 7 | uint8_t c = 0; 8 | while (true) { 9 | c = *str++; 10 | if ((c == delimiter) || (c == '\0')) { 11 | break; 12 | } 13 | else if (c != ' ') { 14 | *buf++ = c; 15 | } 16 | } 17 | *buf = '\0'; 18 | return str; 19 | } 20 | 21 | byte readLine(UARTClass *cmdSerial, char *line, int size) { 22 | int length = 0; 23 | char c; 24 | while (length < size) { 25 | if (cmdSerial->available()) { 26 | c = cmdSerial->read(); 27 | length++; 28 | if ((c == '\r') || (c == '\n')) { 29 | *line = '\0'; 30 | break; 31 | } 32 | *line++ = c; 33 | } 34 | } 35 | return length; 36 | } 37 | 38 | boolean readCmdLine(UARTClass *cmdSerial, char* line) { 39 | static int linePtr = 0; 40 | int c; 41 | if (cmdSerial->available()) { 42 | c = cmdSerial->read(); 43 | if (c == '\n') { 44 | line[linePtr] = '\0'; 45 | linePtr = 0; 46 | return true; 47 | } 48 | line[linePtr++] = c; 49 | } 50 | return false; 51 | } 52 | 53 | void readImage(char *line, byte *image, int* imageWidth, int* imageHeight) { 54 | int i; 55 | int value; 56 | char buf[16]; 57 | 58 | line = readToken(line, buf, ' '); 59 | *imageWidth = atoi(buf); 60 | line = readToken(line, buf, ' '); 61 | *imageHeight = atoi(buf); 62 | 63 | Serial.print("width: "); Serial.print(*imageWidth); 64 | Serial.print(", height: "); Serial.println(*imageHeight); 65 | 66 | int length = strlen(line); 67 | Serial.print("len: "); Serial.println(length); 68 | 69 | int expected = *imageWidth * *imageHeight; 70 | Serial.print("expected: "); Serial.println(expected); 71 | 72 | for (i = 0; i < expected; i++) { 73 | if (i > MAX_IMAGE_SIZE) { 74 | Serial.println("buffer exceeded"); 75 | return; 76 | } 77 | else if (i > length) { 78 | Serial.println("not enough data"); 79 | return; 80 | } 81 | 82 | line = readToken(line, buf, ' '); 83 | value = atoi(buf); 84 | 85 | // Serial.println(value); 86 | image[i] = value; 87 | } 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /firmware/huzzah/huzzah.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #define SERIAL_DEBUG 0 5 | 6 | #if SERIAL_DEBUG 7 | #define DEBUG_PRINT(c) Serial.print(c) 8 | #define DEBUG_PRINTLN(c) Serial.println(c) 9 | #else 10 | #define DEBUG_PRINT(c) 11 | #define DEBUG_PRINTLN(c) 12 | #endif 13 | 14 | #define SSID "PRINTBOT" 15 | #define PASSWORD "password" 16 | #define LED 0 17 | #define CMD_PING "PING" 18 | #define CMD_STATE "STATE" 19 | 20 | #define MSG_CONNECT "conn" 21 | #define MSG_DISCONNECT "disc" 22 | 23 | WiFiServer server(81); 24 | 25 | boolean alreadyConnected = false; 26 | 27 | WiFiClient client; 28 | char buffer[64]; 29 | 30 | void setup() { 31 | Serial.begin(115200); 32 | 33 | // WiFi.mode(WIFI_AP); 34 | // WiFi.softAP(SSID, PASSWORD); 35 | WiFi.softAP(SSID); 36 | IPAddress myIP = WiFi.softAPIP(); 37 | 38 | pinMode(LED, OUTPUT); 39 | digitalWrite(LED, LOW); 40 | 41 | for (int i = 0; i < 5; i++) { 42 | digitalWrite(LED, LOW); 43 | delay(100); 44 | digitalWrite(LED, HIGH); 45 | delay(100); 46 | } 47 | 48 | server.begin(); 49 | 50 | } 51 | 52 | int emptyCount = 0; 53 | long ledSwitch = 0; 54 | long lastReceivedTimeout = -1; 55 | boolean ledOn = true; 56 | 57 | void loop() { 58 | 59 | client = server.available(); 60 | if (client) { 61 | digitalWrite(LED, LOW); 62 | while (client.connected()) { 63 | 64 | if (lastReceivedTimeout > 0 && millis() > lastReceivedTimeout) { 65 | client.write("TIMEOUT"); 66 | client.flush(); 67 | client.stop(); 68 | break; 69 | } 70 | 71 | while (client.available()) { 72 | lastReceivedTimeout = millis() + 5000; 73 | Serial.write(client.read()); 74 | } 75 | 76 | if (Serial.available()) { 77 | size_t len = Serial.available(); 78 | uint8_t sbuf[len]; 79 | Serial.readBytes(sbuf, len); 80 | client.write(sbuf, len); 81 | // delay(1); 82 | } 83 | } 84 | // client lost 85 | digitalWrite(LED, HIGH); 86 | } 87 | else { 88 | lastReceivedTimeout = -1; 89 | if (millis() > ledSwitch) { 90 | ledSwitch = millis() + 200; 91 | ledOn = !ledOn; 92 | digitalWrite(LED, ledOn); 93 | } 94 | } 95 | delay(5); 96 | 97 | } 98 | 99 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/AppContext.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient; 2 | 3 | import android.app.Application; 4 | import android.os.Environment; 5 | import android.util.Log; 6 | 7 | import com.tinkerlog.printclient.model.Print; 8 | import com.tinkerlog.printclient.model.Printer; 9 | import com.tinkerlog.printclient.util.ImageCache; 10 | import com.tinkerlog.printclient.util.Prefs; 11 | 12 | import java.io.File; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | /** 17 | * Created by alex on 24.08.15. 18 | */ 19 | public class AppContext extends Application { 20 | 21 | private static final String TAG = "AppContext"; 22 | 23 | private Prefs prefs; 24 | private File pictureDir; 25 | private ImageCache imageCache; 26 | 27 | private List prints; 28 | private Printer printer; 29 | 30 | @Override 31 | public void onCreate() { 32 | super.onCreate(); 33 | Log.d(TAG, "----- onCreate -----"); 34 | 35 | printer = new Printer(this); 36 | 37 | prefs = new Prefs(this); 38 | prints = prefs.loadPrints(); 39 | Collections.sort(prints); 40 | imageCache = new ImageCache(); 41 | 42 | pictureDir = new File(Environment.getExternalStoragePublicDirectory( 43 | Environment.DIRECTORY_PICTURES), Const.PUBLIC_IMG_DIR); 44 | if (!pictureDir.mkdirs()) { 45 | Log.e(TAG, "picture dir not created"); 46 | } 47 | Log.d(TAG, "pictureDir: " + pictureDir); 48 | } 49 | 50 | public List getPrints() { 51 | return prints; 52 | } 53 | 54 | public Printer getPrinter() { 55 | return printer; 56 | } 57 | 58 | public void storePrint(Print print) { 59 | if (!prints.contains(print)) { 60 | prints.add(print); 61 | } 62 | Collections.sort(prints); 63 | prefs.storePrint(print); 64 | } 65 | 66 | public Print getPrint(long id) { 67 | for (Print p : prints) { 68 | if (p.creationDate == id) { 69 | return p; 70 | } 71 | } 72 | return null; 73 | } 74 | 75 | public void deletePrint(Print print) { 76 | prints.remove(print); 77 | prefs.deletePrint(print); 78 | } 79 | 80 | public ImageCache getImageCache() { 81 | return imageCache; 82 | } 83 | 84 | public File getPictureDir() { 85 | return pictureDir; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/model/Print.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.model; 2 | 3 | import android.util.Log; 4 | 5 | import java.net.URLDecoder; 6 | import java.net.URLEncoder; 7 | import java.util.Comparator; 8 | import java.util.Scanner; 9 | 10 | /** 11 | * Created by alex on 24.08.15. 12 | */ 13 | public class Print implements Comparable { 14 | 15 | private static final String TAG = "Print"; 16 | 17 | private static final String ENC_STRING = "%d %s %s %s %s"; 18 | private static final String DELIMITER = " "; 19 | 20 | public int id; 21 | public long creationDate; 22 | public String name; 23 | public String pic01FileName; 24 | public String pic02FileName; 25 | public String pic03FileName; 26 | 27 | public Print() { 28 | creationDate = System.currentTimeMillis(); 29 | } 30 | 31 | public void clear() { 32 | pic01FileName = null; 33 | pic02FileName = null; 34 | pic03FileName = null; 35 | } 36 | 37 | public boolean isEmpty() { 38 | return pic01FileName == null; 39 | } 40 | 41 | public static Print decode(String src) { 42 | Log.d(TAG, "decoding: " + src); 43 | Print p = new Print(); 44 | Scanner scanner = new Scanner(src).useDelimiter(DELIMITER); 45 | p.creationDate = scanner.nextLong(); 46 | p.name = URLDecoder.decode(scanner.next()); 47 | p.pic01FileName = scanString(scanner); 48 | p.pic02FileName = scanString(scanner); 49 | p.pic03FileName = scanString(scanner); 50 | return p; 51 | } 52 | 53 | public String encode() { 54 | String s = String.format(ENC_STRING, 55 | creationDate, 56 | URLEncoder.encode(name), 57 | pic01FileName, 58 | pic02FileName, 59 | pic03FileName 60 | ); 61 | Log.d(TAG, "encoded: " + s); 62 | return s; 63 | } 64 | 65 | private static String scanString(Scanner scanner) { 66 | String s = scanner.next(); 67 | if (s == null || s.equals("null")) { 68 | return null; 69 | } 70 | return s; 71 | } 72 | 73 | @Override 74 | public int compareTo(Print another) { 75 | return (creationDate > another.creationDate) ? -1 : 76 | (creationDate == another.creationDate) ? 0 : 1; 77 | } 78 | 79 | public String toString() { 80 | return "Print " + creationDate + ", " + name; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /PrintClient/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/PrinterFragment.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | import com.tinkerlog.printclient.R; 11 | import com.tinkerlog.printclient.model.Printer; 12 | 13 | /** 14 | * Created by alex on 20.08.15. 15 | */ 16 | public class PrinterFragment extends BaseFragment { 17 | 18 | private Button connectBtn; 19 | private View dialogView; 20 | private TextView statusTxt; 21 | private Callback callback = new Callback(); 22 | 23 | public static PrinterFragment newFragment() { 24 | return new PrinterFragment(); 25 | } 26 | 27 | @Override 28 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 29 | View v = inflater.inflate(R.layout.frg_printer, container, false); 30 | dialogView = v.findViewById(R.id.frg_printer_dialog); 31 | 32 | if (printer.isConnected()) { 33 | dialogView.setVisibility(View.GONE); 34 | } 35 | 36 | statusTxt = (TextView)v.findViewById(R.id.frg_printer_status); 37 | connectBtn = (Button)v.findViewById(R.id.frg_printer_connect_btn); 38 | connectBtn.setOnClickListener(new View.OnClickListener() { 39 | @Override 40 | public void onClick(View v) { 41 | onConnect(); 42 | } 43 | }); 44 | return v; 45 | } 46 | 47 | @Override 48 | public void onResume() { 49 | super.onResume(); 50 | printer.addStatusCallback(callback); 51 | } 52 | 53 | @Override 54 | public void onPause() { 55 | super.onPause(); 56 | printer.removeStatusCallback(callback); 57 | } 58 | 59 | private void onConnect() { 60 | connectBtn.setVisibility(View.GONE); 61 | printer.connect(); 62 | } 63 | 64 | private class Callback implements Printer.StatusCallback { 65 | @Override 66 | public void onStatusChanged(final Printer.State state, final String message) { 67 | getActivity().runOnUiThread(new Runnable() { 68 | @Override 69 | public void run() { 70 | statusTxt.setText(message); 71 | if (state == Printer.State.CONNECTED) { 72 | dialogView.setVisibility(View.GONE); 73 | connectBtn.setVisibility(View.VISIBLE); 74 | } 75 | else { 76 | dialogView.setVisibility(View.VISIBLE); 77 | } 78 | } 79 | }); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/frg_printer.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 25 | 26 | 27 | 28 | 34 | 41 | 51 | 52 | 53 | 54 | 60 | 73 | 83 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/util/ImageCache.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.util; 2 | 3 | 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | import java.util.concurrent.BlockingQueue; 7 | import java.util.concurrent.LinkedBlockingQueue; 8 | import java.util.concurrent.ThreadPoolExecutor; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import android.animation.Animator; 12 | import android.animation.AnimatorListenerAdapter; 13 | import android.app.Activity; 14 | import android.graphics.Bitmap; 15 | import android.graphics.BitmapFactory; 16 | import android.util.Log; 17 | import android.widget.ImageView; 18 | 19 | import com.tinkerlog.printclient.Const; 20 | 21 | @SuppressWarnings("serial") 22 | public class ImageCache { 23 | 24 | private static final String TAG = "ImageCache"; 25 | private static final int KEEP_ALIVE_TIME = 1; 26 | private static final int MIN_THREADS = 1; 27 | private static final int MAX_THREADS = 3; 28 | 29 | 30 | private ThreadPoolExecutor executor; 31 | private BlockingQueue workQueue = new LinkedBlockingQueue(); 32 | 33 | private Map imageCache = new LinkedHashMap(Const.MAX_CACHED_IMAGES, .75F, true) { 34 | protected boolean removeEldestEntry(Map.Entry eldest) { 35 | return size() > Const.MAX_CACHED_IMAGES; 36 | } 37 | }; 38 | 39 | public ImageCache() { 40 | executor = new ThreadPoolExecutor(MIN_THREADS, MAX_THREADS, KEEP_ALIVE_TIME, TimeUnit.SECONDS, workQueue); 41 | } 42 | 43 | public void getBitmap(final String filename, final ImageView img, final Activity activity) { 44 | getBitmap(filename, img, activity, null); 45 | } 46 | 47 | public void getBitmap(final String filename, final ImageView img, final Activity activity, final Runnable onDone) { 48 | Bitmap bmp = imageCache.get(filename); 49 | if (bmp != null) { 50 | img.setImageBitmap(bmp); 51 | } 52 | else { 53 | img.setImageBitmap(null); 54 | executor.execute(new Runnable() { 55 | public void run() { 56 | final Bitmap bmp = BitmapFactory.decodeFile(filename); 57 | imageCache.put(filename, bmp); 58 | Log.d(TAG, "image loaded"); 59 | activity.runOnUiThread(new Runnable() { 60 | public void run() { 61 | img.setImageBitmap(bmp); 62 | img.setAlpha(0.0f); 63 | img.animate().alpha(1.0f).setDuration(100).setListener(new AnimatorListenerAdapter() { 64 | @Override 65 | public void onAnimationEnd(Animator animation) { 66 | if (onDone != null) { 67 | onDone.run(); 68 | } 69 | } 70 | }); 71 | } 72 | }); 73 | } 74 | }); 75 | } 76 | } 77 | 78 | public Bitmap getBitmap(String filename) { 79 | Bitmap bmp = imageCache.get(filename); 80 | if (bmp == null) { 81 | bmp = BitmapFactory.decodeFile(filename); 82 | imageCache.put(filename, bmp); 83 | } 84 | return bmp; 85 | } 86 | 87 | 88 | public interface ImageSink { 89 | public void setImageBitmap(Bitmap bmp); 90 | } 91 | 92 | private class ImageViewSink implements ImageSink { 93 | private ImageView imageView; 94 | public ImageViewSink(ImageView imageView) { 95 | this.imageView = imageView; 96 | } 97 | public void setImageBitmap(Bitmap bmp) { 98 | imageView.setImageBitmap(bmp); 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /firmware/paintmachine/motor.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "motor.h" 3 | 4 | Motor::Motor(int pwmPin, int dirPin, int encAPin, int encBPin, int maxSpeed, float stepsPerMm) { 5 | 6 | this->pwmPin = pwmPin; 7 | this->dirPin = dirPin; 8 | this->encAPin = encAPin; 9 | this->encBPin = encBPin; 10 | this->maxSpeed = maxSpeed; 11 | this->stepsPerMm = stepsPerMm; 12 | 13 | pinMode(pwmPin, OUTPUT); 14 | pinMode(dirPin, OUTPUT); 15 | 16 | state = MOTOR_STATE_IDLE; 17 | 18 | target = 0; 19 | speed = 0; 20 | position = 0; 21 | deltaPos = 1; 22 | 23 | inverted = false; 24 | } 25 | 26 | //double aggKp=4, aggKi=0.2, aggKd=1; 27 | //double consKp=1, consKi=0.05, consKd=0.25; 28 | 29 | // 0.2 0.025 0.1 30 | // 0.2 0.04 0.1 31 | 32 | // small steps (1000) 0.1 0.03 0.2 33 | 34 | //double kp = 0.1; 35 | double kp = 0.15; 36 | double ki = 0.03; 37 | //double ki = 0.05; 38 | //double kd = 0.2; 39 | double kd = 0.5; 40 | 41 | double p = 0.0; 42 | double i = 0.0; 43 | double d = 0.0; 44 | 45 | int Motor::compute() { 46 | 47 | //double p = 0.0; 48 | //double i = 0.0; 49 | //double d = 0.0; 50 | 51 | double error = target - position; 52 | errorSum += error; 53 | errorSum = (errorSum > 2000) ? 2000 : (errorSum < -2000) ? -2000 : errorSum; 54 | 55 | p = error * kp; 56 | i = errorSum * ki; 57 | d = (error - lastError) * kd; 58 | lastError = error; 59 | 60 | double out = p + i + d; 61 | out = (out > 0) ? out + 7 : out - 7; 62 | out = constrain(out, -maxSpeed, maxSpeed); 63 | return out; 64 | } 65 | 66 | void Motor::loop() { 67 | if (state != MOTOR_STATE_TARGET) { 68 | return; 69 | } 70 | if (abs(target - position) > 10) { 71 | // ramp up speed 72 | int compSpeed = compute(); 73 | int speedDelta = compSpeed - speed; 74 | if (abs(speedDelta) > 50) { 75 | speed += speedDelta * 0.4; 76 | } 77 | else { 78 | speed = compSpeed; 79 | } 80 | setDirection(inverted ? speed < 0 : speed > 0); 81 | analogWrite(pwmPin, abs(speed)); 82 | } 83 | else { 84 | state = MOTOR_STATE_IDLE; 85 | analogWrite(pwmPin, 0); 86 | } 87 | } 88 | 89 | void Motor::setInverted(boolean inverted) { 90 | this->inverted = inverted; 91 | deltaPos = inverted ? -1 : +1; 92 | } 93 | 94 | void Motor::setTarget(int target) { 95 | state = MOTOR_STATE_TARGET; 96 | this->target = target; 97 | } 98 | 99 | void Motor::setTargetMm(int targetInMm) { 100 | state = MOTOR_STATE_TARGET; 101 | setTarget(targetInMm * stepsPerMm); 102 | } 103 | 104 | void Motor::setSpeed(int speed) { 105 | speed = constrain(speed, -maxSpeed, maxSpeed); 106 | this->speed = speed; 107 | state = (abs(speed) > 0) ? MOTOR_STATE_SPEED : MOTOR_STATE_IDLE; 108 | setDirection(inverted ? speed < 0 : speed > 0); 109 | analogWrite(pwmPin, abs(speed)); 110 | } 111 | 112 | void Motor::setDirection(int direction) { 113 | this->direction = direction; 114 | digitalWrite(dirPin, direction); 115 | } 116 | 117 | void Motor::setPosition(int position) { 118 | this->position = position; 119 | } 120 | 121 | boolean Motor::isRunning() { 122 | return state != MOTOR_STATE_IDLE; 123 | } 124 | 125 | void Motor::debug() { 126 | Serial.print("pos: "); Serial.print(position); 127 | Serial.print(", target: "); Serial.print(target); 128 | Serial.print(", last error: "); Serial.print(lastError); 129 | Serial.print(", errorSum: "); Serial.print(errorSum); 130 | Serial.print(", speed: "); Serial.print(speed); 131 | Serial.print(", pid: "); Serial.print(p); 132 | Serial.print(", "); Serial.print(i); 133 | Serial.print(", "); Serial.println(d); 134 | } 135 | 136 | void Motor::encoderA(void) { 137 | position = digitalRead(encBPin) ? position + deltaPos : position - deltaPos; 138 | } 139 | 140 | void Motor::encoderB(void) { 141 | position = digitalRead(encAPin) ? position - deltaPos : position + deltaPos; 142 | } 143 | 144 | void Motor::enableMotors() { 145 | digitalWrite(ENABLE_PIN, HIGH); 146 | } 147 | 148 | void Motor::disableMotors() { 149 | digitalWrite(ENABLE_PIN, LOW); 150 | } 151 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/network/NetworkHelper.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.network; 2 | 3 | import android.net.wifi.WifiConfiguration; 4 | import android.net.wifi.WifiManager; 5 | import android.os.AsyncTask; 6 | import android.util.Log; 7 | 8 | import com.tinkerlog.printclient.Const; 9 | import com.tinkerlog.printclient.activity.MainActivity; 10 | 11 | import org.apache.http.conn.util.InetAddressUtils; 12 | 13 | import java.net.InetAddress; 14 | import java.net.NetworkInterface; 15 | import java.util.Enumeration; 16 | 17 | public class NetworkHelper { 18 | 19 | private static final String TAG = "NetworkHelper"; 20 | 21 | public static String getLocalIpAddressString() { 22 | try { 23 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { 24 | NetworkInterface intf = en.nextElement(); 25 | if (!intf.isLoopback() && intf.isUp() && intf.getName().equals("wlan0")) { 26 | for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { 27 | InetAddress inetAddress = enumIpAddr.nextElement(); 28 | String sAddr = inetAddress.getHostAddress().toUpperCase(); 29 | boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr); 30 | if (isIPv4) { 31 | String ip = inetAddress.getHostAddress().toString(); 32 | return ip; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | catch (Exception e) { 39 | Log.e(TAG, "failed", e); 40 | } 41 | return null; 42 | } 43 | 44 | public static boolean isOnPrinterWifi(WifiManager wifiManager) { 45 | Log.d(TAG, "isOnPrinterWifi: " + wifiManager.getConnectionInfo().getSSID()); 46 | return wifiManager.getConnectionInfo().getSSID().equals(Const.PRINTER_SSID); 47 | } 48 | 49 | public static void connectToPrinter(final WifiManager wifiManager, final Runnable onEnd) { 50 | boolean found = false; 51 | int netId = 0; 52 | for (WifiConfiguration config : wifiManager.getConfiguredNetworks()) { 53 | Log.d(TAG, "-- SSID: " + config.SSID + ", " + config.networkId); 54 | if (config.SSID.equals(Const.PRINTER_SSID_QUOTED)) { 55 | netId = config.networkId; 56 | Log.d(TAG, "found SSID, netId: " + netId); 57 | found = true; 58 | break; 59 | } 60 | } 61 | 62 | //if (!found) { 63 | Log.d(TAG, "SSID not found, adding config"); 64 | WifiConfiguration config = new WifiConfiguration(); 65 | config.SSID = Const.PRINTER_SSID_QUOTED; 66 | // config.preSharedKey = Const.PRINTER_PASS_QUOTED; 67 | config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 68 | netId = wifiManager.addNetwork(config); 69 | //} 70 | 71 | final int finalNetId = netId; 72 | if (netId == -1) { 73 | Log.w(TAG, "net id -1, failed!"); 74 | return; 75 | } 76 | AsyncTask.execute(new Runnable() { 77 | @Override 78 | public void run() { 79 | try { 80 | Log.d(TAG, "netId: " + finalNetId); 81 | boolean b = wifiManager.disconnect(); 82 | Log.d(TAG, "disconnect: " + b); 83 | Thread.sleep(1000); 84 | b = wifiManager.enableNetwork(finalNetId, true); 85 | Log.d(TAG, "enable: " + b); 86 | if (onEnd != null) { 87 | onEnd.run(); 88 | } 89 | // wifiChangeReceiver.enabled = true; 90 | } 91 | catch (InterruptedException e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | }); 96 | } 97 | 98 | public static void disconnectFromPrinter(WifiManager wifiManager) { 99 | Log.d(TAG, "disconnecting from " + Const.PRINTER_SSID); 100 | boolean b = wifiManager.disconnect(); 101 | Log.d(TAG, "disconnect: " + b); 102 | 103 | for (WifiConfiguration config : wifiManager.getConfiguredNetworks()) { 104 | Log.d(TAG, "-- PRINTER_SSID: " + config.SSID + ", " + config.networkId); 105 | if (config.SSID.equals(Const.PRINTER_SSID_QUOTED)) { 106 | b = wifiManager.disableNetwork(config.networkId); 107 | Log.d(TAG, "disable: " + config.networkId + ", " + b); 108 | } 109 | } 110 | b = wifiManager.reconnect(); 111 | Log.d(TAG, "reconnect: " + b); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /firmware/paintmachine/spraycan.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "spraycan.h" 3 | 4 | 5 | SprayCan::SprayCan(int sprayPin, int pixelHeight) { 6 | this->sprayPin = sprayPin; 7 | this->pixelHeight = pixelHeight; 8 | this->posCount = 0; 9 | 10 | pinMode(sprayPin, OUTPUT); 11 | digitalWrite(sprayPin, LOW); 12 | } 13 | 14 | void SprayCan::prepareImage(byte *image, int width, int height) { 15 | this->width = width; 16 | this->height = height; 17 | 18 | int x, y = 0; 19 | for (x = 0; x < MAX_X; x++) { 20 | for (y = 0; y < MAX_Y; y++) { 21 | positions[x][y] = -1; 22 | } 23 | } 24 | 25 | Serial.println("prepare image"); 26 | 27 | boolean isOn = false; 28 | for (x = 0; x < width; x++) { 29 | posCount = 0; 30 | // Serial.print("x: "); Serial.println(x); 31 | int yMin = -1; 32 | int yMax = -1; 33 | int yPos = 0; 34 | for (y = 0; y < height; y++) { 35 | byte pixel = image[x*height+y]; 36 | yPos = y * pixelHeight + Y_START_OFFSET; 37 | // Serial.print("y: "); Serial.print(y); 38 | // Serial.print(", pixel: "); Serial.print(pixel); 39 | // Serial.print(", pos: "); Serial.print(yPos); 40 | if (pixel > 0 && !isOn) { 41 | positions[x][posCount++] = yPos; 42 | isOn = true; 43 | if (yMin == -1) { 44 | yMin = yPos; 45 | } 46 | // Serial.println(", switch on"); 47 | } 48 | else if (pixel == 0 && isOn) { 49 | positions[x][posCount++] = yPos; 50 | yMax = yPos; 51 | isOn = false; 52 | // Serial.println(", switch off"); 53 | } 54 | else { 55 | // Serial.println(", no change"); 56 | } 57 | } 58 | if (isOn) { 59 | positions[x][posCount++] = yPos; 60 | yMax = yPos; 61 | // Serial.println("end of col, switch off"); 62 | isOn = false; 63 | } 64 | endPositions[x][0] = yMin; 65 | endPositions[x][1] = yMax; 66 | Serial.print("poscount: "); Serial.print(posCount); 67 | Serial.print(", yMin: "); Serial.print(yMin); 68 | Serial.print(", yMax: "); Serial.println(yMax); 69 | } 70 | } 71 | 72 | void SprayCan::spray(boolean isOn) { 73 | actIsOn = isOn; 74 | digitalWrite(sprayPin, isOn); 75 | } 76 | 77 | void SprayCan::startImage() { 78 | actPos = 0; 79 | actIsOn = false; 80 | } 81 | 82 | void SprayCan::finishImage() { 83 | digitalWrite(sprayPin, LOW); 84 | actIsOn = false; 85 | } 86 | 87 | void SprayCan::finishColumn() { 88 | digitalWrite(sprayPin, LOW); 89 | actIsOn = false; 90 | actPos = 0; 91 | } 92 | 93 | void SprayCan::toggleSpray() { 94 | actIsOn = !actIsOn; 95 | digitalWrite(sprayPin, actIsOn); 96 | } 97 | 98 | boolean SprayCan::isEmpty(int column) { 99 | if (column < MAX_X) { 100 | return endPositions[column][0] == -1; 101 | } 102 | return true; 103 | } 104 | 105 | int SprayCan::getYMin(int column) { 106 | if (column < MAX_X) { 107 | return endPositions[column][0]; 108 | } 109 | return -1; 110 | } 111 | 112 | int SprayCan::getYMax(int column) { 113 | if (column < MAX_X) { 114 | return endPositions[column][1]; 115 | } 116 | return -1; 117 | } 118 | 119 | void SprayCan::printColumn(int column, int position, boolean isForward) { 120 | 121 | static int count = 0; 122 | 123 | count++; 124 | 125 | if (column != actColumn) { 126 | count = 0; 127 | // start with a new column 128 | Serial.println("----- new column"); 129 | actColumn = column; 130 | if (isForward) { 131 | actPos = 0; 132 | } 133 | else { 134 | int i; 135 | int pos, lastPos = 0; 136 | for (i = 0; i < MAX_Y; i++) { 137 | lastPos = pos; 138 | int target = positions[column][i]; 139 | // Serial.print("i: "); Serial.print(i); 140 | // Serial.print(", target: "); Serial.println(target); 141 | if (target == -1) { 142 | // we reached the end 143 | actPos = i-1; 144 | break; 145 | } 146 | } 147 | } 148 | } 149 | 150 | if (actPos == -1) { 151 | return; 152 | } 153 | 154 | int targetPos = positions[column][actPos]; 155 | if (targetPos == -1) { 156 | if (count % 10000 == 0) { 157 | Serial.print("print col: "); Serial.print(column); 158 | Serial.print(", position: "); Serial.print(position); 159 | Serial.print(", actPos: "); Serial.print(actPos); 160 | Serial.println(", no target!"); 161 | } 162 | return; 163 | } 164 | if (position > targetPos && isForward) { 165 | toggleSpray(); 166 | Serial.print("print col: "); Serial.print(column); 167 | Serial.print(", position: "); Serial.print(position); 168 | Serial.print(", actPos: "); Serial.print(actPos); 169 | Serial.print(", target: "); Serial.print(targetPos); 170 | Serial.print(", isOn: "); Serial.println(actIsOn); 171 | actPos++; 172 | } 173 | else if (position < (targetPos+Y_REVERSE_OFFSET) && !isForward) { 174 | toggleSpray(); 175 | Serial.print("print col: "); Serial.print(column); 176 | Serial.print(", position: "); Serial.print(position); 177 | Serial.print(", actPos: "); Serial.print(actPos); 178 | Serial.print(", target: "); Serial.print(targetPos); 179 | Serial.print(", isOn: "); Serial.println(actIsOn); 180 | actPos--; 181 | } 182 | else { 183 | if (count % 10000 == 0) { 184 | Serial.print("print col: "); Serial.print(column); 185 | Serial.print(", position: "); Serial.print(position); 186 | Serial.print(", actPos: "); Serial.print(actPos); 187 | Serial.println(", < target"); 188 | } 189 | } 190 | 191 | } 192 | 193 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/activity_print_load.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 23 | 24 | 30 | 37 | 44 | 51 | 52 | 53 | 54 | 60 | 67 | 73 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 100 | 101 | 112 | 113 | 114 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /PrintClient/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/network/ComThread.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.network; 2 | 3 | import android.os.Message; 4 | import android.util.Log; 5 | 6 | import com.tinkerlog.printclient.model.Command; 7 | import com.tinkerlog.printclient.util.ResponseHandler; 8 | 9 | import java.io.BufferedInputStream; 10 | import java.io.IOException; 11 | import java.io.OutputStreamWriter; 12 | import java.io.PrintWriter; 13 | import java.net.InetSocketAddress; 14 | import java.net.Socket; 15 | import java.util.concurrent.BlockingQueue; 16 | import java.util.concurrent.LinkedBlockingQueue; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | public class ComThread extends Thread { 20 | 21 | public interface ComCallback { 22 | void closed(); 23 | void failed(String errorMsg); 24 | } 25 | 26 | private static final String TAG = "ComThread"; 27 | 28 | private static final String SERVER_IP = "192.168.4.1"; 29 | private static final int SERVER_PORT = 81; 30 | private static final String MSG_PING = "ping"; 31 | private static final int MAX_BUFFER = 1024; 32 | private static final int MAX_SOCKET_TIMEOUT = 1000; 33 | 34 | private BlockingQueue outQueue = new LinkedBlockingQueue<>(8); 35 | 36 | private PrintWriter out; 37 | private BufferedInputStream in; 38 | private boolean running = true; 39 | private byte[] inBuffer = new byte[MAX_BUFFER]; 40 | private ComCallback callback; 41 | private long lastActive; 42 | 43 | public ComThread(ComCallback callback) { 44 | this.callback = callback; 45 | } 46 | 47 | public void shutDown() { 48 | running = false; 49 | interrupt(); 50 | } 51 | 52 | @Override 53 | public synchronized void start() { 54 | super.start(); 55 | new Thread(new TimeoutCheck()).start(); 56 | } 57 | 58 | public void send(Command command) { 59 | try { 60 | outQueue.put(command); 61 | } 62 | catch (InterruptedException e) { 63 | Log.w(TAG, "failed", e); 64 | } 65 | } 66 | 67 | public void sendIfEmpty(Command command) { 68 | if (outQueue.isEmpty()) { 69 | Log.d(TAG, "----- isEmpty: " + outQueue.size()); 70 | send(command); 71 | } 72 | else { 73 | Log.d(TAG, "----- not empty: skipping"); 74 | } 75 | } 76 | 77 | public void run() { 78 | Socket socket = null; 79 | try { 80 | Thread.sleep(500); 81 | Log.d(TAG, "connecting ..."); 82 | socket = new Socket(); 83 | socket.setSoTimeout(MAX_SOCKET_TIMEOUT); 84 | socket.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT), 1000); 85 | out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); 86 | in = new BufferedInputStream(socket.getInputStream()); 87 | 88 | Log.d(TAG, "socket timeout: " + socket.getSoTimeout()); 89 | while (running) { 90 | 91 | Command cmd = outQueue.poll(2000, TimeUnit.MILLISECONDS); 92 | if (!running) { 93 | break; 94 | } 95 | 96 | if (cmd == null) { 97 | Log.d(TAG, "ping ..."); 98 | cmd = new Command(MSG_PING, null); 99 | // continue; 100 | } 101 | else { 102 | Log.d(TAG, "sending: " + cmd.getRequest()); 103 | } 104 | out.write(cmd.getRequest()); 105 | out.println(); 106 | out.flush(); 107 | Thread.sleep(1); 108 | 109 | int bytesRead = readBuffer(in, inBuffer); 110 | if (bytesRead == -1) { 111 | Log.d(TAG, "no response, socket closed!"); 112 | if (cmd.handler != null) { 113 | cmd.handler.sendMessage(Message.obtain(cmd.handler, ResponseHandler.ERROR, cmd)); 114 | } 115 | callback.failed("no response!"); 116 | break; 117 | } 118 | else if (bytesRead > 0) { 119 | String result = new String(inBuffer, 0, bytesRead); 120 | Log.d(TAG, " result: " + result); 121 | cmd.setResponse(result); 122 | if (cmd.handler != null) { 123 | cmd.handler.sendMessage(Message.obtain(cmd.handler, ResponseHandler.OK, cmd)); 124 | } 125 | } 126 | 127 | Thread.sleep(1); 128 | } 129 | Log.d(TAG, "going down"); 130 | } 131 | catch (InterruptedException e) { 132 | Log.d(TAG, "interrupted, going down"); 133 | } 134 | catch (Exception e) { 135 | Log.e(TAG, "failed", e); 136 | callback.failed(e.getMessage()); 137 | } 138 | finally { 139 | try { 140 | socket.close(); 141 | } 142 | catch (IOException e) { 143 | // ignore 144 | } 145 | callback.closed(); 146 | running = false; 147 | } 148 | } 149 | 150 | private int readBuffer(BufferedInputStream in, byte[] buf) throws IOException, InterruptedException { 151 | int count = 0; 152 | int b; 153 | 154 | while (true) { 155 | b = in.read(); 156 | if (b != -1) { 157 | lastActive = System.currentTimeMillis(); 158 | buf[count++] = (byte)b; 159 | } 160 | if (b == 0x0A || b == -1) { // end on \n 161 | break; 162 | } 163 | } 164 | return count; 165 | } 166 | 167 | private class TimeoutCheck implements Runnable { 168 | private static final String TAG = "TimeoutCheck"; 169 | public void run() { 170 | while (running) { 171 | long delta = System.currentTimeMillis() - lastActive; 172 | Log.d(TAG, "----- delta: " + delta); 173 | try { 174 | Thread.sleep(1000); 175 | } 176 | catch (InterruptedException e) { 177 | // ignored 178 | } 179 | } 180 | Log.d(TAG, "going down"); 181 | } 182 | } 183 | 184 | } -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/activity_print_process.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 23 | 24 | 33 | 34 | 35 | 36 | 44 | 45 | 49 | 55 | 56 | 57 | 65 | 80 | 95 | 110 | 125 | 140 | 141 | 142 | 148 | 149 | 150 | 151 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/PrintListFragment.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Fragment; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.support.annotation.Nullable; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.AdapterView; 15 | import android.widget.ArrayAdapter; 16 | import android.widget.ImageButton; 17 | import android.widget.ImageView; 18 | import android.widget.ListView; 19 | import android.widget.TextView; 20 | 21 | import com.tinkerlog.printclient.AppContext; 22 | import com.tinkerlog.printclient.Const; 23 | import com.tinkerlog.printclient.R; 24 | import com.tinkerlog.printclient.model.Print; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * Created by alex on 24.08.15. 30 | */ 31 | public class PrintListFragment extends Fragment { 32 | 33 | private static final String TAG = "PrintListFrg"; 34 | 35 | private ImageButton addBtn; 36 | private AppContext appContext; 37 | private TextView emptyTxt; 38 | private ListView printsList; 39 | private List prints; 40 | private PrintsAdapter adapter; 41 | 42 | public static PrintListFragment newFragment() { 43 | return new PrintListFragment(); 44 | } 45 | 46 | @Nullable 47 | @Override 48 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 49 | appContext = (AppContext)getActivity().getApplicationContext(); 50 | prints = appContext.getPrints(); 51 | View rootView = inflater.inflate(R.layout.frg_prints_list, container, false); 52 | emptyTxt = (TextView)rootView.findViewById(R.id.frg_prints_empty); 53 | printsList = (ListView)rootView.findViewById(R.id.frg_prints_list); 54 | printsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { 55 | @Override 56 | public void onItemClick(AdapterView parent, View view, int position, long id) { 57 | clickItem(view, position); 58 | } 59 | }); 60 | adapter = new PrintsAdapter(getActivity(), prints); 61 | printsList.setAdapter(adapter); 62 | addBtn = (ImageButton)rootView.findViewById(R.id.frg_prints_add); 63 | addBtn.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | add(); 67 | } 68 | }); 69 | return rootView; 70 | } 71 | 72 | @Override 73 | public void onResume() { 74 | super.onResume(); 75 | prints = appContext.getPrints(); 76 | updateUI(); 77 | if (adapter != null) { 78 | adapter.notifyDataSetChanged(); 79 | } 80 | } 81 | 82 | private void onShowDelete(int position) { 83 | Log.d(TAG, "delete: " + position); 84 | showDeleteDialog(position); 85 | } 86 | 87 | private void showDeleteDialog(final int position) { 88 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 89 | builder.setMessage("delete?") 90 | .setPositiveButton("YES", new DialogInterface.OnClickListener() { 91 | public void onClick(DialogInterface dialog, int id) { 92 | onDelete(position); 93 | } 94 | }) 95 | .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() { 96 | public void onClick(DialogInterface dialog, int id) { 97 | } 98 | }); 99 | builder.create().show(); 100 | } 101 | 102 | private void onDelete(int position) { 103 | Print toDelete = prints.get(position); 104 | appContext.deletePrint(toDelete); 105 | adapter.notifyDataSetChanged(); 106 | } 107 | 108 | private void add() { 109 | Print p = new Print(); 110 | p.name = "Print " + (prints.size() + 1); 111 | appContext.storePrint(p); 112 | openPrint(p); 113 | } 114 | 115 | private void clickItem(View view, int position) { 116 | Log.d(TAG, "clickItem: " + position); 117 | Print p = prints.get(position); 118 | openPrint(p); 119 | } 120 | 121 | private void openPrint(Print print) { 122 | Intent intent = new Intent(getActivity(), EditPrintActivity.class); 123 | intent.putExtra(Const.KEY_PRINT_ID, print.creationDate); 124 | getActivity().startActivity(intent); 125 | } 126 | 127 | private void updateUI() { 128 | emptyTxt.setVisibility(prints != null && prints.size() > 0 ? View.GONE : View.INVISIBLE); 129 | } 130 | 131 | private class Holder { 132 | TextView name; 133 | ImageView image; 134 | ImageButton deleteBtn; 135 | DeleteListener deleteListener; 136 | int pos; 137 | } 138 | 139 | private class PrintsAdapter extends ArrayAdapter { 140 | 141 | private LayoutInflater inflater; 142 | 143 | public PrintsAdapter(Context context, List prints) { 144 | super(context, R.layout.list_item_print, prints); 145 | inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 146 | } 147 | 148 | @Override 149 | public int getCount() { 150 | return prints.size(); 151 | } 152 | 153 | @Override 154 | public View getView(int position, View convertView, ViewGroup parent) { 155 | Log.d(TAG, "getView: " + position); 156 | Holder holder = null; 157 | if (convertView == null || convertView.getTag() == null) { 158 | View v = inflater.inflate(R.layout.list_item_print, parent, false); 159 | holder = new Holder(); 160 | holder.name = (TextView)v.findViewById(R.id.act_print_image_name); 161 | holder.image = (ImageView)v.findViewById(R.id.list_image); 162 | holder.deleteListener = new DeleteListener(); 163 | holder.deleteBtn = (ImageButton)v.findViewById(R.id.list_delete_btn); 164 | holder.deleteBtn.setFocusable(false); 165 | v.setTag(holder); 166 | convertView = v; 167 | } 168 | else { 169 | holder = (Holder)convertView.getTag(); 170 | } 171 | holder.pos = position; 172 | holder.deleteListener.position = position; 173 | holder.deleteBtn.setOnClickListener(holder.deleteListener); 174 | 175 | Print p = getItem(position); 176 | holder.name.setText(p.name); 177 | if (p.pic01FileName != null) { 178 | appContext.getImageCache().getBitmap(p.pic01FileName, holder.image, getActivity()); 179 | } 180 | else { 181 | holder.image.setImageResource(R.drawable.back); 182 | } 183 | return convertView; 184 | } 185 | } 186 | 187 | private class DeleteListener implements View.OnClickListener { 188 | public int position; 189 | @Override 190 | public void onClick(View v) { 191 | onShowDelete(position); 192 | } 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/Const.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient; 2 | 3 | public class Const { 4 | 5 | public static final String PRINTER_SSID = "PRINTBOT"; 6 | public static final String PRINTER_SSID_QUOTED = "\"PRINTBOT\""; 7 | public static final String PRINTER_PASS_QUOTED = "\"password\""; 8 | 9 | public static final String KEY_PRINT_ID = "print_id"; 10 | 11 | public static final int MAX_CACHED_IMAGES = 16; 12 | 13 | public static final String PUBLIC_IMG_DIR = "print"; 14 | 15 | public static final int MAX_HEIGHT_PIXEL = 46; 16 | 17 | public static final int[] INVADER_DATA_8 = { 18 | 0, 0, 1, 1, 1, 1, 1, 1, 19 | 0, 1, 1, 1, 0, 0, 0, 0, 20 | 1, 1, 0, 1, 1, 0, 0, 0, 21 | 0, 1, 1, 1, 0, 1, 0, 0, 22 | 0, 1, 1, 1, 0, 1, 0, 0, 23 | 1, 1, 0, 1, 1, 0, 0, 0, 24 | 0, 1, 1, 1, 0, 0, 0, 0, 25 | 0, 0, 1, 1, 1, 1, 1, 1 26 | }; 27 | 28 | public static final int[] INVADER_DATA_16 = { 29 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 30 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 31 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 32 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 33 | 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 35 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 36 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 37 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 38 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 39 | 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 40 | 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 41 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 42 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 44 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 45 | }; 46 | 47 | 48 | public static final int[] INVADER1_DATA_16 = { 49 | // 1 2 3 4 5 6 7 8 50 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 51 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 52 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 53 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 54 | 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 55 | 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 56 | 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 57 | 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 58 | 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 59 | 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 60 | 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 61 | 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 62 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 63 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 64 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 65 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 66 | }; 67 | 68 | public static final int[] TEST_DATA_16 = { 69 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70 | 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71 | 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 73 | 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 74 | 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 75 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 77 | }; 78 | 79 | 80 | public static final int[] TEST_DATA_46 = { 81 | // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 82 | /* 1 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 83 | /* 2 */ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 84 | /* 3 */ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 85 | /* 4 */ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 86 | /* 5 */ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 87 | /* 6 */ 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 88 | /* 7 */ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 89 | 90 | /* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91 | 92 | /* 9 */ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93 | /* 10 */ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94 | /* 11 */ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95 | /* 12 */ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96 | /* 13 */ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97 | 98 | /* 14 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99 | 100 | /* 15 */ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101 | /* 16 */ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102 | /* 17 */ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103 | /* 18 */ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104 | /* 19 */ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105 | 106 | /* 20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 107 | }; 108 | 109 | } 110 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.app.Fragment; 6 | import android.app.FragmentManager; 7 | import android.net.NetworkInfo; 8 | import android.os.Handler; 9 | import android.support.v4.widget.DrawerLayout; 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.view.WindowManager; 16 | import android.widget.AdapterView; 17 | import android.widget.ArrayAdapter; 18 | import android.widget.ListView; 19 | import android.widget.Switch; 20 | import android.widget.Toast; 21 | 22 | import com.tinkerlog.printclient.AppContext; 23 | import com.tinkerlog.printclient.R; 24 | import com.tinkerlog.printclient.model.Printer; 25 | 26 | public class MainActivity extends Activity { 27 | 28 | private static final int MENU_LIST = 0; 29 | private static final int MENU_MANUAL = 1; 30 | private static final int MENU_PRINTER = 2; 31 | 32 | private static final String TAG = "MainActivity"; 33 | private Handler handler; 34 | private Switch modeSwitch; 35 | 36 | private Printer printer; 37 | private ControlFragment controlFragment; 38 | private PrinterFragment printFragment; 39 | private PrintListFragment printListFragment; 40 | private ListView drawer; 41 | private DrawerLayout drawerLayout; 42 | private View flash; 43 | private String[] menu; 44 | 45 | private AppContext appContext; 46 | private Callback printerCallback = new Callback(); 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | 52 | handler = new Handler(); 53 | appContext = (AppContext)getApplicationContext(); 54 | 55 | printer = appContext.getPrinter(); 56 | 57 | setContentView(R.layout.activity_main); 58 | 59 | ActionBar actionBar = getActionBar(); 60 | actionBar.setTitle(""); 61 | 62 | menu = getResources().getStringArray(R.array.main_menu); 63 | flash = findViewById(R.id.act_main_flash); 64 | drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout); 65 | drawer = (ListView)findViewById(R.id.left_drawer); 66 | drawer.setAdapter(new ArrayAdapter(this, R.layout.drawer_item, menu)); 67 | drawer.setOnItemClickListener(new DrawerItemClickListener()); 68 | 69 | selectItem(0); 70 | 71 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 72 | } 73 | 74 | @Override 75 | protected void onResume() { 76 | super.onResume(); 77 | Log.d(TAG, "onResume"); 78 | if (!printer.isConnected()) { 79 | printer.addStatusCallback(printerCallback); 80 | printer.connect(); 81 | } 82 | } 83 | 84 | @Override 85 | protected void onPause() { 86 | super.onPause(); 87 | Log.d(TAG, "onPause"); 88 | if (isFinishing()) { 89 | printer.removeStatusCallback(printerCallback); 90 | printer.disconnect(); 91 | } 92 | } 93 | 94 | @Override 95 | public boolean onCreateOptionsMenu(Menu menu) { 96 | getMenuInflater().inflate(R.menu.menu_main, menu); 97 | for (int i = 0; i < menu.size(); i++) { 98 | MenuItem item = menu.getItem(i); 99 | if (item.getItemId() == R.id.menu_connect) { 100 | item.setVisible(printer.getState() == Printer.State.CONNECTED); 101 | } 102 | else if (item.getItemId() == R.id.menu_error) { 103 | item.setVisible(printer.getState() == Printer.State.ERRORED); 104 | } 105 | else if (item.getItemId() == R.id.menu_disconnect) { 106 | item.setVisible(printer.getState() != Printer.State.CONNECTED && printer.getState() != Printer.State.ERRORED); 107 | } 108 | } 109 | return true; 110 | } 111 | 112 | private class DrawerItemClickListener implements ListView.OnItemClickListener { 113 | @Override 114 | public void onItemClick(AdapterView parent, View view, int position, long id) { 115 | Log.d(TAG, "onItemClick: " + position); 116 | selectItem(position); 117 | } 118 | } 119 | 120 | private void selectItem(int position) { 121 | Fragment frg = null; 122 | switch (position) { 123 | case MENU_LIST: 124 | if (printListFragment == null) { 125 | printListFragment = PrintListFragment.newFragment(); 126 | } 127 | frg = printListFragment; 128 | break; 129 | case MENU_MANUAL: 130 | if (controlFragment == null) { 131 | controlFragment = ControlFragment.newFragment(); 132 | } 133 | frg = controlFragment; 134 | break; 135 | case MENU_PRINTER: 136 | if (printFragment == null) { 137 | printFragment = PrinterFragment.newFragment(); 138 | } 139 | frg = printFragment; 140 | } 141 | FragmentManager fragmentManager = getFragmentManager(); 142 | fragmentManager.beginTransaction().replace(R.id.act_main_container, frg).commit(); 143 | 144 | drawer.setItemChecked(position, true); 145 | setTitle(menu[position]); 146 | drawerLayout.closeDrawer(drawer); 147 | } 148 | 149 | // public void doLeft0(View v) { 150 | // comThread.send("g 0 0 0"); 151 | // } 152 | // 153 | // public void doLeft50(View v) { 154 | // comThread.send("g 50 0 0"); 155 | // } 156 | 157 | private class Callback implements Printer.StatusCallback { 158 | @Override 159 | public void onStatusChanged(final Printer.State state, final String message) { 160 | Log.d(TAG, "----- status: " + state.toString()); 161 | final String msg; 162 | switch (state) { 163 | case CONNECTED: 164 | msg = getString(R.string.toast_connected); 165 | break; 166 | case DISCONNECTED: 167 | msg = getString(R.string.toast_disconnected); 168 | break; 169 | case ERRORED: 170 | msg = getString(R.string.toast_error); 171 | handler.postDelayed(new Runnable() { 172 | @Override 173 | public void run() { 174 | Log.d(TAG, "starting re-connect ..."); 175 | printer.connect(); 176 | } 177 | }, 10000); 178 | break; 179 | default: 180 | msg = null; 181 | } 182 | if (msg != null) { 183 | runOnUiThread(new Runnable() { 184 | @Override 185 | public void run() { 186 | Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show(); 187 | flash.setAlpha(1); 188 | flash.animate().alpha(0).setDuration(200).start(); 189 | invalidateOptionsMenu(); 190 | } 191 | }); 192 | } 193 | 194 | } 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/ProcessActivity.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.BitmapDrawable; 6 | import android.os.Bundle; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.CompoundButton; 11 | import android.widget.ImageView; 12 | import android.widget.SeekBar; 13 | import android.widget.ToggleButton; 14 | 15 | import com.tinkerlog.printclient.Const; 16 | import com.tinkerlog.printclient.R; 17 | import com.tinkerlog.printclient.model.Print; 18 | 19 | import java.io.BufferedOutputStream; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | 23 | public class ProcessActivity extends BaseActivity { 24 | 25 | private static final String TAG = "ProcessActivity"; 26 | 27 | private enum Filter { 28 | RED, 29 | GREEN, 30 | BLUE, 31 | BW; 32 | public static Filter getFilter(int value) { 33 | switch (value) { 34 | case 0: return RED; 35 | case 1: return GREEN; 36 | case 2: return BLUE; 37 | case 3: return BW; 38 | } 39 | return null; 40 | } 41 | }; 42 | 43 | private Print currentPrint; 44 | 45 | private Button doneBtn; 46 | private ImageView processImageView; 47 | private ImageView srcImageView; 48 | private SeekBar seekBar; 49 | private ToggleButton[] filterBtns = new ToggleButton[4]; 50 | private ToggleButton invertBtn; 51 | private FilterOnCheckedListener filterListener = new FilterOnCheckedListener(); 52 | private Filter filter; 53 | private int threshold; 54 | private boolean isInverted; 55 | 56 | @Override 57 | protected void onCreate(Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | setContentView(R.layout.activity_print_process); 60 | 61 | processImageView = (ImageView)findViewById(R.id.act_process_image); 62 | srcImageView = (ImageView)findViewById(R.id.act_process_src_image); 63 | seekBar = (SeekBar)findViewById(R.id.act_process_seekbar); 64 | seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 65 | @Override 66 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 67 | onSeek(progress); 68 | } 69 | @Override 70 | public void onStartTrackingTouch(SeekBar seekBar) { 71 | } 72 | @Override 73 | public void onStopTrackingTouch(SeekBar seekBar) { 74 | } 75 | }); 76 | 77 | filterBtns[0] = (ToggleButton)findViewById(R.id.act_process_filter_red); 78 | filterBtns[1] = (ToggleButton)findViewById(R.id.act_process_filter_green); 79 | filterBtns[2] = (ToggleButton)findViewById(R.id.act_process_filter_blue); 80 | filterBtns[3] = (ToggleButton)findViewById(R.id.act_process_filter_bw); 81 | for (ToggleButton t : filterBtns) { 82 | t.setOnCheckedChangeListener(filterListener); 83 | } 84 | 85 | invertBtn = (ToggleButton)findViewById(R.id.act_process_invert); 86 | invertBtn.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 87 | @Override 88 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 89 | isInverted = isChecked; 90 | onProcess(threshold); 91 | } 92 | }); 93 | 94 | doneBtn = (Button)findViewById(R.id.act_process_done_btn); 95 | doneBtn.setOnClickListener(new View.OnClickListener() { 96 | @Override 97 | public void onClick(View v) { 98 | onDone(); 99 | } 100 | }); 101 | 102 | long printId = getIntent().getLongExtra(Const.KEY_PRINT_ID, -1); 103 | currentPrint = appContext.getPrint(printId); 104 | 105 | appContext.getImageCache().getBitmap(currentPrint.pic01FileName, processImageView, this); 106 | appContext.getImageCache().getBitmap(currentPrint.pic01FileName, srcImageView, this); 107 | 108 | filter = Filter.BW; 109 | filterBtns[3].setChecked(true); 110 | threshold = 128; 111 | } 112 | 113 | @Override 114 | protected void onResume() { 115 | super.onResume(); 116 | onProcess(threshold); 117 | } 118 | 119 | private void onDone() { 120 | save(); 121 | finish(); 122 | } 123 | 124 | private void onSeek(int progress) { 125 | threshold = progress; 126 | onProcess(threshold); 127 | } 128 | 129 | private void save() { 130 | Bitmap printBitmap = ((BitmapDrawable)processImageView.getDrawable()).getBitmap(); 131 | try { 132 | String printFilename = appContext.getPictureDir().getAbsolutePath() + "/gallery_" + System.currentTimeMillis() + ".png"; 133 | BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(printFilename)); 134 | printBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); 135 | fos.close(); 136 | Log.d(TAG, "filename: " + printFilename); 137 | currentPrint.pic02FileName = printFilename; 138 | appContext.storePrint(currentPrint); 139 | } 140 | catch (IOException e) { 141 | Log.d(TAG, "failed", e); 142 | } 143 | } 144 | 145 | private void onProcess(int threshold) { 146 | Bitmap srcBitmap = appContext.getImageCache().getBitmap(currentPrint.pic01FileName); 147 | float scale = (float) Const.MAX_HEIGHT_PIXEL / srcBitmap.getHeight(); 148 | int width = (int)(srcBitmap.getWidth() * scale); 149 | int height = (int)(srcBitmap.getHeight() * scale); 150 | Bitmap scaledBitmap = Bitmap.createScaledBitmap(srcBitmap, width, height, true); 151 | if (scaledBitmap == srcBitmap) { 152 | scaledBitmap = srcBitmap.copy(srcBitmap.getConfig(), true); 153 | } 154 | 155 | for (int x = 0; x < width; x++) { 156 | for (int y = 0; y < height; y++) { 157 | int pixel = scaledBitmap.getPixel(x, y); 158 | int newPixel = 0; 159 | switch (filter) { 160 | case RED: 161 | newPixel = (Color.red(pixel) > threshold) ? 0xFFFFFFFF : 0xFF000000; 162 | break; 163 | case GREEN: 164 | newPixel = (Color.green(pixel) > threshold) ? 0xFFFFFFFF : 0xFF000000; 165 | break; 166 | case BLUE: 167 | newPixel = (Color.blue(pixel) > threshold) ? 0xFFFFFFFF : 0xFF000000; 168 | break; 169 | case BW: 170 | int bw = (Color.red(pixel) + Color.green(pixel) + Color.blue(pixel)) / 3; 171 | newPixel = (bw > threshold) ? 0xFFFFFFFF : 0xFF000000; 172 | break; 173 | } 174 | if (isInverted) { 175 | newPixel = (newPixel == 0xFFFFFFFF) ? 0xFF000000 : 0xFFFFFFFF; 176 | } 177 | scaledBitmap.setPixel(x, y, newPixel); 178 | } 179 | } 180 | processImageView.setImageBitmap(scaledBitmap); 181 | } 182 | 183 | private class FilterOnCheckedListener implements CompoundButton.OnCheckedChangeListener { 184 | @Override 185 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 186 | if (isChecked) { 187 | for (int i = 0; i < 4; i++) { 188 | ToggleButton t = filterBtns[i]; 189 | if (buttonView != t) { 190 | t.setChecked(false); 191 | t.setEnabled(true); 192 | } 193 | else { 194 | filter = Filter.getFilter(i); 195 | onProcess(threshold); 196 | t.setEnabled(false); 197 | } 198 | } 199 | } 200 | } 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/activity_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 18 | 19 | 27 | 28 | 41 | 50 | 56 | 57 | 58 | 59 | 60 | 66 | 73 | 79 | 85 | 91 | 92 | 93 | 94 | 95 | 96 | 108 | 118 | 130 | 140 | 141 | 142 | 152 | 153 | 164 | 165 | 166 | 174 | 184 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/res/layout/frg_control.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 31 | 45 | 59 | 60 | 61 | 66 | 67 | 76 | 83 | 90 | 91 | 92 | 98 | 106 | 113 | 120 | 121 | 132 | 133 | 134 | 142 | 149 | 156 | 157 | 158 | 159 | 160 | 170 | 171 | 179 | 180 | 189 | 190 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/model/Printer.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.model; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.net.NetworkInfo; 8 | import android.net.wifi.WifiInfo; 9 | import android.net.wifi.WifiManager; 10 | import android.util.Log; 11 | 12 | import com.tinkerlog.printclient.network.ComThread; 13 | import com.tinkerlog.printclient.network.NetworkHelper; 14 | import com.tinkerlog.printclient.network.NetworkScanner; 15 | import com.tinkerlog.printclient.util.ResponseHandler; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public class Printer { 21 | 22 | private static final String TAG = "Printer"; 23 | 24 | public enum State { 25 | NONE, 26 | SEARCHING_PRINTER, 27 | CONNECTING_PRINTER, 28 | CONNECTED, 29 | DISCONNECTING_PRINTER, 30 | DISCONNECTED, 31 | ERRORED 32 | } 33 | 34 | public enum Action { 35 | START_SEARCH, 36 | FOUND_PRINTER, 37 | CONNECTED_TO_PRINTER, 38 | DROPPED_FROM_NETWORK, 39 | DISCONNECT_PRINTER, 40 | NETWORK_ERROR 41 | } 42 | 43 | public interface StatusCallback { 44 | public void onStatusChanged(State state, String message); 45 | } 46 | 47 | public static final String PRINTER_SSID = "PRINTBOT"; 48 | public static final String PRINTER_SSID_QUOTED = "\"PRINTBOT\""; 49 | 50 | private Context context; 51 | private WifiManager wifiManager; 52 | private String ip; 53 | private State state; 54 | private WifiChangeReceiver wifiChangeReceiver; 55 | private NetworkScanner networkScanner; 56 | private ComThread comThread; 57 | private List callbacks = new ArrayList<>(); 58 | 59 | public Printer(Context context) { 60 | this.context = context; 61 | wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); 62 | state = State.NONE; 63 | } 64 | 65 | public void onResume() { 66 | } 67 | 68 | public void addStatusCallback(StatusCallback callback) { 69 | callbacks.add(callback); 70 | } 71 | 72 | public void removeStatusCallback(StatusCallback callback) { 73 | callbacks.remove(callback); 74 | } 75 | 76 | public State getState() { 77 | return state; 78 | } 79 | 80 | public boolean isConnected() { 81 | return state == State.CONNECTED; 82 | } 83 | 84 | public void connect() { 85 | input(Action.START_SEARCH); 86 | } 87 | 88 | public void disconnect() { 89 | input(Action.DISCONNECT_PRINTER); 90 | } 91 | 92 | public void send(Command command) { 93 | if (!isConnected()) { 94 | Log.w(TAG, "printer not connected!"); 95 | } 96 | else if (comThread != null) { 97 | comThread.send(command); 98 | } 99 | } 100 | 101 | public void sendIfEmpty(Command command) { 102 | if (!isConnected()) { 103 | Log.w(TAG, "printer not connected!"); 104 | } 105 | else if (comThread != null) { 106 | comThread.sendIfEmpty(command); 107 | } 108 | } 109 | 110 | public void requestStatus(ResponseHandler handler) { 111 | Command cmd = new StatusCommand(handler); 112 | send(cmd); 113 | } 114 | 115 | private void input(Action input) { 116 | State oldState = state; 117 | switch (state) { 118 | case DISCONNECTED: 119 | case NONE: 120 | case ERRORED: 121 | if (input == Action.START_SEARCH) { 122 | if (NetworkHelper.isOnPrinterWifi(wifiManager)) { 123 | state = State.CONNECTED; 124 | updateStatus(state, "already on printer wifi."); 125 | startComThread(); 126 | } 127 | else { 128 | state = State.SEARCHING_PRINTER; 129 | startNetworkScanner(); 130 | registerWifiChange(); 131 | updateStatus(state, "searching for printer ..."); 132 | } 133 | } 134 | break; 135 | case SEARCHING_PRINTER: 136 | switch (input) { 137 | case FOUND_PRINTER: 138 | updateStatus(state, "connecting to printer ..."); 139 | NetworkHelper.connectToPrinter(wifiManager, new Runnable() { 140 | @Override 141 | public void run() { 142 | wifiChangeReceiver.enabled = true; 143 | } 144 | }); 145 | state = State.CONNECTING_PRINTER; 146 | break; 147 | case DISCONNECT_PRINTER: 148 | stopNetworkScanner(); 149 | stopComThread(); 150 | context.unregisterReceiver(wifiChangeReceiver); 151 | state = State.DISCONNECTED; 152 | break; 153 | default: 154 | Log.w(TAG, "unhandled: " + input); 155 | } 156 | break; 157 | case CONNECTING_PRINTER: 158 | switch (input) { 159 | case CONNECTED_TO_PRINTER: 160 | state = State.CONNECTED; 161 | updateStatus(state, "connected to printer."); 162 | startComThread(); 163 | break; 164 | case DROPPED_FROM_NETWORK: 165 | state = State.ERRORED; 166 | updateStatus(state, "connection failed"); 167 | break; 168 | default: 169 | Log.w(TAG, "unhandled: " + input); 170 | } 171 | break; 172 | case CONNECTED: 173 | switch (input) { 174 | case DISCONNECT_PRINTER: 175 | stopComThread(); 176 | unregisterWifiChange(); 177 | NetworkHelper.disconnectFromPrinter(wifiManager); 178 | state = State.DISCONNECTED; 179 | updateStatus(state, "disconnected"); 180 | break; 181 | case NETWORK_ERROR: 182 | case DROPPED_FROM_NETWORK: 183 | unregisterWifiChange(); 184 | stopComThread(); 185 | state = State.ERRORED; 186 | updateStatus(state, "ERROR, connection lost"); 187 | break; 188 | default: 189 | Log.w(TAG, "unhandled: " + input); 190 | } 191 | break; 192 | case DISCONNECTING_PRINTER: 193 | break; 194 | } 195 | Log.d(TAG, "----- state: " + input + ": " + oldState + " --> " + state); 196 | } 197 | 198 | private void startNetworkScanner() { 199 | networkScanner = new NetworkScanner(context, PRINTER_SSID, new Runnable() { 200 | public void run() { 201 | input(Action.FOUND_PRINTER); 202 | networkScanner = null; 203 | } 204 | }); 205 | networkScanner.start(); 206 | } 207 | 208 | private void stopNetworkScanner() { 209 | if (networkScanner != null) { 210 | networkScanner.shutdown(); 211 | } 212 | } 213 | 214 | private void registerWifiChange() { 215 | wifiChangeReceiver = new WifiChangeReceiver(); 216 | IntentFilter intentFilter = new IntentFilter(); 217 | intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 218 | context.registerReceiver(wifiChangeReceiver, intentFilter); 219 | } 220 | 221 | private void unregisterWifiChange() { 222 | if (wifiChangeReceiver != null) { 223 | try { 224 | context.unregisterReceiver(wifiChangeReceiver); 225 | } 226 | catch (IllegalArgumentException e) { 227 | // ignore 228 | } 229 | wifiChangeReceiver = null; 230 | } 231 | } 232 | 233 | private void startComThread() { 234 | if (comThread == null) { 235 | comThread = new ComThread(new ComThread.ComCallback() { 236 | @Override 237 | public void closed() { 238 | comThread = null; 239 | Log.d(TAG, "connection closed!"); 240 | } 241 | @Override 242 | public void failed(String errorMsg) { 243 | comThread = null; 244 | Log.w(TAG, "com failed: " + errorMsg); 245 | input(Action.NETWORK_ERROR); 246 | } 247 | }); 248 | comThread.start(); 249 | } 250 | } 251 | 252 | private void stopComThread() { 253 | if (comThread != null) { 254 | comThread.shutDown(); 255 | } 256 | } 257 | 258 | private void updateStatus(State state, String status) { 259 | for (StatusCallback callback : callbacks) { 260 | callback.onStatusChanged(state, status); 261 | } 262 | } 263 | 264 | public class WifiChangeReceiver extends BroadcastReceiver { 265 | public boolean enabled; 266 | @Override 267 | public void onReceive(Context c, Intent intent) { 268 | WifiInfo info = wifiManager.getConnectionInfo(); 269 | NetworkInfo netInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 270 | ip = NetworkHelper.getLocalIpAddressString(); 271 | Log.d(TAG, "wifi: " + (info != null ? info.getSSID() : "null") + ", state: " + netInfo.getState() + ", " + netInfo.getDetailedState() + ", ip: " + ip); 272 | if (ip != null && enabled) { 273 | if (info.getSSID().equals(PRINTER_SSID_QUOTED)) { 274 | if (state == State.CONNECTED) { 275 | Log.d(TAG, " already connected."); 276 | } 277 | else if (netInfo.getState() == NetworkInfo.State.CONNECTED && 278 | netInfo.getDetailedState() == NetworkInfo.DetailedState.CONNECTED) { 279 | Log.d(TAG, " connected!"); 280 | input(Action.CONNECTED_TO_PRINTER); 281 | } 282 | else { 283 | Log.d(TAG, "still waiting to connect ..."); 284 | } 285 | } 286 | else { 287 | Log.d(TAG, " got ip from wrong network!"); 288 | input(Action.DROPPED_FROM_NETWORK); 289 | } 290 | } 291 | else if (ip == null && state == State.CONNECTED) { 292 | Log.d(TAG, "no ip!"); 293 | input(Action.DROPPED_FROM_NETWORK); 294 | } 295 | } 296 | } 297 | 298 | } 299 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/ControlFragment.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.LayoutInflater; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | import android.widget.TextView; 11 | 12 | import com.tinkerlog.printclient.Const; 13 | import com.tinkerlog.printclient.R; 14 | import com.tinkerlog.printclient.model.Command; 15 | import com.tinkerlog.printclient.model.Image; 16 | import com.tinkerlog.printclient.model.ImageCommand; 17 | import com.tinkerlog.printclient.model.StatusCommand; 18 | import com.tinkerlog.printclient.util.ResponseHandler; 19 | 20 | public class ControlFragment extends BaseFragment { 21 | 22 | private static final String TAG = "ControlFragment"; 23 | 24 | private static final int LIMIT_Y = 40; 25 | private static final int LIMIT_X = 40; 26 | 27 | private View leftControl; 28 | private View rightControl; 29 | private View leftStick; 30 | private View rightStick; 31 | private View headControl; 32 | private View headStick; 33 | private TextView leftTxt; 34 | private TextView headTxt; 35 | private TextView rightTxt; 36 | private Button fireBtn; 37 | private Button statusBtn; 38 | private Button imageBtn; 39 | private Button goBtn; 40 | 41 | private int controlHeight; 42 | private int maxHeight; 43 | private int minHeight; 44 | private int rangeHeight; 45 | private int stickHeight; 46 | private int controlWidth; 47 | private int maxWidth; 48 | private int minWidth; 49 | private int rangeWidth; 50 | 51 | private float left; 52 | private float right; 53 | private float head; 54 | private boolean isFire; 55 | 56 | private boolean isGone; 57 | 58 | public static ControlFragment newFragment() { 59 | return new ControlFragment(); 60 | } 61 | 62 | @Override 63 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 64 | View v = inflater.inflate(R.layout.frg_control, container, false); 65 | 66 | leftControl = v.findViewById(R.id.frg_control_left); 67 | leftStick = v.findViewById(R.id.frg_control_left_stick); 68 | rightControl = v.findViewById(R.id.frg_control_right); 69 | rightStick = v.findViewById(R.id.frg_control_right_stick); 70 | headControl = v.findViewById(R.id.frg_control_head); 71 | headStick = v.findViewById(R.id.frg_control_head_stick); 72 | fireBtn = (Button)v.findViewById(R.id.frg_control_fire_btn); 73 | statusBtn = (Button)v.findViewById(R.id.frg_control_state_btn); 74 | imageBtn = (Button)v.findViewById(R.id.frg_control_image_btn); 75 | goBtn = (Button)v.findViewById(R.id.frg_control_go_btn); 76 | leftTxt = (TextView)v.findViewById(R.id.frg_control_status_left); 77 | headTxt = (TextView)v.findViewById(R.id.frg_control_status_head); 78 | rightTxt = (TextView)v.findViewById(R.id.frg_control_status_right); 79 | 80 | leftControl.setOnTouchListener(new VerticalTouchListener(leftStick, new ValueUpdater() { 81 | @Override 82 | public void update(float value, boolean force) { 83 | left = map(value); 84 | onValueUpdate(force); 85 | } 86 | })); 87 | rightControl.setOnTouchListener(new VerticalTouchListener(rightStick, new ValueUpdater() { 88 | @Override 89 | public void update(float value, boolean force) { 90 | right = map(value); 91 | onValueUpdate(force); 92 | } 93 | })); 94 | headControl.setOnTouchListener(new HorizontalTouchListener(headStick, new ValueUpdater() { 95 | @Override 96 | public void update(float value, boolean force) { 97 | head = map(-value); 98 | onValueUpdate(force); 99 | } 100 | })); 101 | 102 | leftControl.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 103 | @Override 104 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 105 | controlHeight = bottom; 106 | minHeight = LIMIT_Y; 107 | maxHeight = controlHeight - LIMIT_Y; 108 | stickHeight = leftStick.getHeight(); 109 | int range = (maxHeight - stickHeight) - minHeight; 110 | rangeHeight = range / 2; 111 | } 112 | }); 113 | 114 | headControl.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 115 | @Override 116 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 117 | controlWidth = right - left; 118 | minWidth = LIMIT_X; 119 | maxWidth = controlWidth - LIMIT_X; 120 | stickHeight = leftStick.getHeight(); 121 | int range = (maxWidth - stickHeight) - minWidth; 122 | rangeWidth = range / 2; 123 | } 124 | }); 125 | 126 | fireBtn.setOnTouchListener(new View.OnTouchListener() { 127 | @Override 128 | public boolean onTouch(View v, MotionEvent event) { 129 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 130 | isFire = true; 131 | onValueUpdate(true); 132 | } 133 | else if (event.getAction() == MotionEvent.ACTION_UP) { 134 | isFire = false; 135 | onValueUpdate(true); 136 | } 137 | return false; 138 | } 139 | }); 140 | 141 | statusBtn.setOnClickListener(new View.OnClickListener() { 142 | @Override 143 | public void onClick(View v) { 144 | printer.requestStatus(new ResponseHandler() { 145 | @Override 146 | public void handleSuccess(Command cmd) { 147 | StatusCommand sc = (StatusCommand) cmd; 148 | leftTxt.setText(Integer.toString(sc.leftPos)); 149 | rightTxt.setText(Integer.toString(sc.rightPos)); 150 | headTxt.setText(Integer.toString(sc.headPos)); 151 | } 152 | }); 153 | } 154 | }); 155 | 156 | imageBtn.setOnClickListener(new View.OnClickListener() { 157 | @Override 158 | public void onClick(View v) { 159 | // final Image img = new Image(16, 20); 160 | // img.data = Const.INVADER1_DATA_16; 161 | // final Image img = new Image(20, 46); 162 | // img.data = Const.TEST_DATA_46; 163 | final Image img = new Image(8, 16); 164 | img.data = Const.TEST_DATA_16; 165 | printer.send(new ImageCommand(img, new ResponseHandler() { 166 | @Override 167 | public void handleSuccess(Command cmd) { 168 | Log.d(TAG, "----- image success: " + cmd.getResponse()); 169 | } 170 | })); 171 | } 172 | }); 173 | 174 | goBtn.setOnClickListener(new View.OnClickListener() { 175 | @Override 176 | public void onClick(View v) { 177 | String c = isGone ? "goto 0 0" : "goto 300 0"; 178 | // String c = isGone ? "goto 0 0" : "goto 0 1500"; 179 | printer.send(new Command(c, new ResponseHandler())); 180 | isGone = !isGone; 181 | } 182 | }); 183 | 184 | return v; 185 | } 186 | 187 | @Override 188 | public void onResume() { 189 | super.onResume(); 190 | } 191 | 192 | public void onPause() { 193 | super.onPause(); 194 | } 195 | 196 | private void onValueUpdate(final boolean force) { 197 | // Log.d(TAG, String.format("left: %.2f right: %.2f head: %.2f", left, right, head)); 198 | final int leftInt = (int) (left * 255); 199 | final int rightInt = (int) (right * 255); 200 | int headInt = (int) (head * 255); 201 | final String msg = String.format("move %d %d %d %d", leftInt, rightInt, headInt, (isFire ? 1 : 0)); 202 | Log.d(TAG, "msg: " + msg); 203 | 204 | Command cmd = new Command(msg, new ResponseHandler() { 205 | @Override 206 | public void handleSuccess(Command command) { 207 | Log.d(TAG, "----- success: " + command.getResponse()); 208 | } 209 | }); 210 | 211 | if (force) { 212 | printer.send(cmd); 213 | } 214 | else { 215 | printer.sendIfEmpty(cmd); 216 | } 217 | } 218 | 219 | private float map(float x) { 220 | if (x > 0) { 221 | return ((float)Math.pow(10, x) - 1) / 9f; 222 | } 223 | else { 224 | return -((float)Math.pow(10, -x) - 1) / 9f; 225 | } 226 | } 227 | 228 | private interface ValueUpdater { 229 | void update(float value, boolean force); 230 | } 231 | 232 | private class VerticalTouchListener implements View.OnTouchListener { 233 | private int lastY; 234 | private int startY; 235 | private View stick; 236 | private ValueUpdater updater; 237 | public VerticalTouchListener(View stick, ValueUpdater updater) { 238 | this.stick = stick; 239 | this.updater = updater; 240 | } 241 | @Override 242 | public boolean onTouch(View v, MotionEvent event) { 243 | int y = (int)event.getY(); 244 | switch (event.getAction()) { 245 | case MotionEvent.ACTION_DOWN: 246 | lastY = y; 247 | startY = (int)stick.getY(); 248 | return true; 249 | case MotionEvent.ACTION_MOVE: 250 | int deltaY = y - lastY; 251 | if (deltaY == 0) { 252 | return false; 253 | } 254 | int yNew = startY + deltaY; 255 | if (yNew < minHeight) { 256 | deltaY = minHeight - startY; 257 | } 258 | else if (yNew > (maxHeight-stickHeight)) { 259 | deltaY = (maxHeight-stickHeight) - startY; 260 | } 261 | stick.setTranslationY(deltaY); 262 | float output = (int)stick.getY() - minHeight; 263 | output = (output - rangeHeight) / (float)rangeHeight; 264 | updater.update(-output, false); 265 | return true; 266 | case MotionEvent.ACTION_UP: 267 | stick.animate().translationY(0).setDuration(100).start(); 268 | updater.update(0f, true); 269 | return true; 270 | } 271 | return false; 272 | } 273 | } 274 | 275 | private class HorizontalTouchListener implements View.OnTouchListener { 276 | private int lastX; 277 | private int startX; 278 | private View stick; 279 | private ValueUpdater updater; 280 | public HorizontalTouchListener(View stick, ValueUpdater updater) { 281 | this.stick = stick; 282 | this.updater = updater; 283 | } 284 | @Override 285 | public boolean onTouch(View v, MotionEvent event) { 286 | int x = (int)event.getX(); 287 | switch (event.getAction()) { 288 | case MotionEvent.ACTION_DOWN: 289 | lastX = x; 290 | startX = (int)stick.getX(); 291 | return true; 292 | case MotionEvent.ACTION_MOVE: 293 | int deltaX = x - lastX; 294 | if (deltaX == 0) { 295 | return false; 296 | } 297 | int xNew = startX + deltaX; 298 | if (xNew < minWidth) { 299 | deltaX = minWidth - startX; 300 | } 301 | else if (xNew > (maxWidth-stickHeight)) { 302 | deltaX = (maxWidth-stickHeight) - startX; 303 | } 304 | stick.setTranslationX(deltaX); 305 | float output = (int)stick.getX() - minWidth; 306 | output = (output - rangeWidth) / (float)rangeWidth; 307 | updater.update(-output, false); 308 | return true; 309 | case MotionEvent.ACTION_UP: 310 | stick.animate().translationX(0).setDuration(100).start(); 311 | updater.update(0f, true); 312 | return true; 313 | } 314 | return false; 315 | } 316 | } 317 | 318 | } 319 | -------------------------------------------------------------------------------- /PrintClient/app/src/main/java/com/tinkerlog/printclient/activity/EditPrintActivity.java: -------------------------------------------------------------------------------- 1 | package com.tinkerlog.printclient.activity; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.drawable.BitmapDrawable; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.util.Log; 11 | import android.view.KeyEvent; 12 | import android.view.View; 13 | import android.view.animation.DecelerateInterpolator; 14 | import android.view.inputmethod.EditorInfo; 15 | import android.widget.Button; 16 | import android.widget.EditText; 17 | import android.widget.ImageButton; 18 | import android.widget.ImageView; 19 | import android.widget.TextView; 20 | import android.widget.Toast; 21 | 22 | import com.tinkerlog.printclient.Const; 23 | import com.tinkerlog.printclient.R; 24 | import com.tinkerlog.printclient.model.Command; 25 | import com.tinkerlog.printclient.model.Image; 26 | import com.tinkerlog.printclient.model.ImageCommand; 27 | import com.tinkerlog.printclient.model.Print; 28 | import com.tinkerlog.printclient.util.ResponseHandler; 29 | 30 | import java.io.BufferedOutputStream; 31 | import java.io.FileOutputStream; 32 | import java.io.InputStream; 33 | 34 | 35 | /** 36 | * Created by alex on 24.08.15. 37 | */ 38 | public class EditPrintActivity extends BaseActivity { 39 | 40 | private static final int ANI_DURATION = 300; 41 | private static final int REQ_SELECT_PICTURE = 1001; 42 | private static final int REQ_PROCESS_PICTURE = 1002; 43 | 44 | private static final String TAG = "EditPrintActivity"; 45 | 46 | private ImageButton cameraBtn; 47 | private ImageButton collectionBtn; 48 | private ImageButton clearBtn; 49 | private View emptyContainer; 50 | private View deleteContainer; 51 | private ImageView pic0Img; 52 | private ImageView pic1Img; 53 | private ImageView pic2Img; 54 | private ImageView pic3Img; 55 | private EditText nameTxt; 56 | private Button processBtn; 57 | private Button printBtn; 58 | private Handler handler; 59 | private DecelerateInterpolator interpolator = new DecelerateInterpolator(1.5f); 60 | private DeleteCheck deleteCheck = new DeleteCheck(); 61 | private Print currentPrint; 62 | private int selectedThumb; 63 | private boolean inPrint; 64 | 65 | @Override 66 | protected void onCreate(Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | setContentView(R.layout.activity_edit); 69 | handler = new Handler(); 70 | 71 | long printId = getIntent().getLongExtra(Const.KEY_PRINT_ID, -1); 72 | currentPrint = appContext.getPrint(printId); 73 | if (currentPrint == null) { 74 | currentPrint = new Print(); 75 | } 76 | 77 | emptyContainer = findViewById(R.id.act_print_empty); 78 | deleteContainer = findViewById(R.id.act_print_delete_container); 79 | pic0Img = (ImageView)findViewById(R.id.act_print_image_0); 80 | pic0Img.setOnClickListener(new View.OnClickListener() { 81 | @Override 82 | public void onClick(View v) { 83 | if (currentPrint.isEmpty() || 84 | selectedThumb == 1 || 85 | (selectedThumb == 2 && currentPrint.pic03FileName == null)) { 86 | return; 87 | } 88 | 89 | if (deleteContainer.getVisibility() == View.INVISIBLE) { 90 | deleteContainer.setVisibility(View.VISIBLE); 91 | deleteContainer.setTranslationY(deleteContainer.getHeight()); 92 | deleteContainer.animate() 93 | .translationY(0F) 94 | .setDuration(ANI_DURATION) 95 | .setInterpolator(interpolator) 96 | .start(); 97 | } 98 | handler.removeCallbacks(deleteCheck); 99 | handler.postDelayed(deleteCheck, 2000); 100 | } 101 | }); 102 | 103 | pic1Img = (ImageView)findViewById(R.id.act_print_image_1); 104 | pic1Img.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | selectedThumb = 0; 108 | updateUI(); 109 | } 110 | }); 111 | 112 | pic2Img = (ImageView)findViewById(R.id.act_print_image_2); 113 | pic2Img.setOnClickListener(new View.OnClickListener() { 114 | @Override 115 | public void onClick(View v) { 116 | selectedThumb = 1; 117 | updateUI(); 118 | } 119 | }); 120 | 121 | pic3Img = (ImageView)findViewById(R.id.act_print_image_3); 122 | pic3Img.setOnClickListener(new View.OnClickListener() { 123 | @Override 124 | public void onClick(View v) { 125 | selectedThumb = 2; 126 | updateUI(); 127 | } 128 | }); 129 | 130 | processBtn = (Button)findViewById(R.id.act_print_process_btn); 131 | processBtn.setOnClickListener(new View.OnClickListener() { 132 | @Override 133 | public void onClick(View v) { 134 | onProcess(); 135 | } 136 | }); 137 | printBtn = (Button)findViewById(R.id.act_print_print_btn); 138 | printBtn.setOnClickListener(new View.OnClickListener() { 139 | @Override 140 | public void onClick(View v) { 141 | onPrint(); 142 | } 143 | }); 144 | clearBtn = (ImageButton)findViewById(R.id.act_print_clear_btn); 145 | clearBtn.setOnClickListener(new View.OnClickListener() { 146 | @Override 147 | public void onClick(View v) { 148 | onClear(); 149 | } 150 | }); 151 | nameTxt = (EditText)findViewById(R.id.act_print_image_name); 152 | nameTxt.setOnEditorActionListener(new EditText.OnEditorActionListener() { 153 | @Override 154 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 155 | Log.d(TAG, "onEditorAction: " + event + ", action: " + actionId); 156 | if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT) { 157 | currentPrint.name = nameTxt.getText().toString(); 158 | appContext.storePrint(currentPrint); 159 | } 160 | return false; 161 | } 162 | }); 163 | cameraBtn = (ImageButton)findViewById(R.id.act_print_camera); 164 | collectionBtn = (ImageButton)findViewById(R.id.act_print_collection); 165 | collectionBtn.setOnClickListener(new View.OnClickListener() { 166 | @Override 167 | public void onClick(View v) { 168 | onGallery(); 169 | } 170 | }); 171 | 172 | selectedThumb = 0; 173 | updateUI(); 174 | } 175 | 176 | private void updateUI() { 177 | 178 | nameTxt.setText(currentPrint.name); 179 | 180 | if (currentPrint.pic01FileName != null) { 181 | appContext.getImageCache().getBitmap(currentPrint.pic01FileName, pic1Img, this); 182 | } 183 | else { 184 | pic1Img.setImageResource(R.drawable.back); 185 | } 186 | if (currentPrint.pic02FileName != null) { 187 | appContext.getImageCache().getBitmap(currentPrint.pic02FileName, pic2Img, this); 188 | } 189 | else { 190 | pic2Img.setImageResource(R.drawable.back); 191 | } 192 | if (currentPrint.pic03FileName != null) { 193 | appContext.getImageCache().getBitmap(currentPrint.pic03FileName, pic3Img, this); 194 | } 195 | else { 196 | pic3Img.setImageResource(R.drawable.back); 197 | } 198 | 199 | pic1Img.setBackgroundColor(0x00000000); 200 | pic2Img.setBackgroundColor(0x00000000); 201 | pic3Img.setBackgroundColor(0x00000000); 202 | 203 | ImageView thumb = null; 204 | String src = null; 205 | switch (selectedThumb) { 206 | case 0: 207 | thumb = pic1Img; 208 | src = currentPrint.pic01FileName; 209 | break; 210 | case 1: 211 | thumb = pic2Img; 212 | src = currentPrint.pic02FileName; 213 | break; 214 | case 2: 215 | thumb = pic3Img; 216 | src = currentPrint.pic03FileName; 217 | break; 218 | } 219 | 220 | thumb.setBackgroundResource(R.drawable.thumb_back); 221 | 222 | if (src != null) { 223 | appContext.getImageCache().getBitmap(src, pic0Img, this); 224 | emptyContainer.setVisibility(View.INVISIBLE); 225 | } 226 | else { 227 | pic0Img.setImageResource(R.drawable.back); 228 | emptyContainer.setVisibility((selectedThumb == 1) ? View.INVISIBLE : View.VISIBLE); 229 | } 230 | 231 | } 232 | 233 | private class DeleteCheck implements Runnable { 234 | public void run() { 235 | deleteContainer.animate() 236 | .translationY(deleteContainer.getHeight()) 237 | .setDuration(ANI_DURATION) 238 | .setInterpolator(interpolator) 239 | .withEndAction(new Runnable() { 240 | @Override 241 | public void run() { 242 | deleteContainer.setVisibility(View.INVISIBLE); 243 | } 244 | }).start(); 245 | } 246 | } 247 | 248 | private void onClear() { 249 | if (selectedThumb == 0) { 250 | currentPrint.clear(); 251 | } 252 | else { 253 | currentPrint.pic03FileName = null; 254 | } 255 | appContext.storePrint(currentPrint); 256 | updateUI(); 257 | } 258 | 259 | private void onProcess() { 260 | if (!currentPrint.isEmpty()) { 261 | Intent intent = new Intent(this, ProcessActivity.class); 262 | intent.putExtra(Const.KEY_PRINT_ID, currentPrint.creationDate); 263 | startActivityForResult(intent, REQ_PROCESS_PICTURE); 264 | } 265 | } 266 | 267 | private void onPrint() { 268 | Log.d(TAG, "----- onPrint"); 269 | if (inPrint) { 270 | return; 271 | } 272 | if (currentPrint.isEmpty() || currentPrint.pic01FileName == null) { 273 | Toast.makeText(this, "Nothing to print ...", Toast.LENGTH_SHORT).show(); 274 | } 275 | else if (!printer.isConnected()) { 276 | Toast.makeText(this, "Not connected ...", Toast.LENGTH_SHORT).show(); 277 | } 278 | else { 279 | inPrint = true; 280 | Bitmap printBitmap = ((BitmapDrawable)pic2Img.getDrawable()).getBitmap(); 281 | int width = printBitmap.getWidth(); 282 | int height = printBitmap.getHeight(); 283 | Log.d(TAG, "width: " + width + ", height: " + height); 284 | Image image = new Image(width, height); 285 | int[] data = new int[width * height]; 286 | for (int x = 0; x < width; x++) { 287 | for (int y = 0; y < height; y++) { 288 | data[x*height+y] = (printBitmap.getPixel(x, y) == 0xFF000000) ? 1 : 0; 289 | } 290 | } 291 | image.data = data; 292 | printer.send(new ImageCommand(image, new ResponseHandler() { 293 | @Override 294 | public void handleFailed(Command cmd) { 295 | inPrint = false; 296 | Toast.makeText(EditPrintActivity.this, "FAILED!", Toast.LENGTH_SHORT).show(); 297 | } 298 | @Override 299 | public void handleSuccess(Command cmd) { 300 | inPrint = false; 301 | Toast.makeText(EditPrintActivity.this, "Print sent!", Toast.LENGTH_SHORT).show(); 302 | } 303 | } )); 304 | } 305 | } 306 | 307 | private void onGallery() { 308 | Intent intent = new Intent(); 309 | intent.setType("image/*"); 310 | intent.setAction(Intent.ACTION_GET_CONTENT); 311 | startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQ_SELECT_PICTURE); 312 | } 313 | 314 | @Override 315 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 316 | Log.d(TAG, "onActivityResult: " + requestCode); 317 | if (requestCode == REQ_SELECT_PICTURE && data != null && data.getData() != null) { 318 | safeImageFromGallery(data.getData()); 319 | } 320 | updateUI(); 321 | } 322 | 323 | private void safeImageFromGallery(Uri uri) { 324 | try { 325 | Log.d(TAG, "safeImageFromGallery: " + uri); 326 | Bitmap newBitmap = null; 327 | InputStream imageStream = getContentResolver().openInputStream(uri); 328 | Bitmap image = BitmapFactory.decodeStream(imageStream); 329 | int width = image.getWidth(); 330 | int height = image.getHeight(); 331 | Log.d(TAG, "orig: " + width + " * " + height); 332 | if (width > height) { // landscape 333 | if (width > 1200) { 334 | float scale = 1200f / width; 335 | height = (int)(height * scale); 336 | width = 1200; 337 | } 338 | } 339 | else { 340 | if (height > 1200) { 341 | float scale = 1200f / height; 342 | width = (int)(width * scale); 343 | height = 1200; 344 | } 345 | } 346 | Log.d(TAG, "scaled: " + width + " * " + height); 347 | newBitmap = Bitmap.createScaledBitmap(image, width, height, true); 348 | 349 | pic0Img.setImageBitmap(newBitmap); 350 | pic0Img.setBackgroundColor(0xFF000000); 351 | 352 | emptyContainer.setVisibility(View.GONE); 353 | 354 | String currentFilename = null; 355 | if (height <= 400 || width <= 400) { 356 | currentFilename = appContext.getPictureDir().getAbsolutePath() + "/gallery_" + System.currentTimeMillis() + ".png"; 357 | BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(currentFilename)); 358 | newBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); 359 | fos.close(); 360 | } 361 | else { 362 | currentFilename = appContext.getPictureDir().getAbsolutePath() + "/gallery_" + System.currentTimeMillis() + ".jpg"; 363 | BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(currentFilename)); 364 | newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); 365 | fos.close(); 366 | } 367 | Log.d(TAG, "filename: " + currentFilename); 368 | 369 | if (selectedThumb == 0) { 370 | currentPrint.pic01FileName = currentFilename; 371 | } 372 | else { 373 | currentPrint.pic03FileName = currentFilename; 374 | } 375 | 376 | appContext.storePrint(currentPrint); 377 | } 378 | catch (Exception e) { 379 | Log.w(TAG, "failed", e); 380 | } 381 | } 382 | 383 | } 384 | -------------------------------------------------------------------------------- /firmware/paintmachine/paintmachine.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | PAINT MACHINE 4 | ============= 5 | 6 | Visit tinkerlog.com for more details. 7 | 8 | 9 | Resolution 10 | ========== 11 | Y0-Ymax 0-25300 ~= 1710 mm ==> 14.8 steps / mm 12 | X 15000 ~= 1000 mm ==> 15 steps / mm 13 | 14 | Y0-Ymax = 1700 - 100 ==> 1600 (save zones) 15 | 16 | Pixel size 35mm x 35mm 17 | MaxY = 1600 / 35 ~= 46 pixel 18 | 16:9 ==> 82 x 46 px 19 | 4:3 ==> 61 x 46 px 20 | 21 | Pixel size 30mm x 30mm 22 | MaxY = 1600 / 30 ~= 53 pixel 23 | 16:9 ==> 94 * 53 px 24 | 4:3 ==> 71 * 53 px 25 | 26 | Problems 27 | ======== 28 | 1. short on one encoder channel 29 | 2. analogWrite on pin 6 30 | 3. noise on encoder lines ==> shielded cables 31 | 4. esp8266 reboot loop ==> add cap & 7805 32 | 33 | 34 | Monitoring Vbat 35 | =============== 36 | 37 | Voltage divider GND --- 10k -+- 100k --- Vbat 38 | sensing on analog pin 3 39 | 1024 ticks @ 3.3V ==> 0.00322 per tick 40 | ==> 0.00322 * 11 (because of 110K) = 0.03542 41 | Example: 42 | 325 ticks 43 | 325 * 3542 = 1151150 44 | 1151150 / 100000 = 11 45 | (1151150 % 100000) / 10000 = 5 ==> 11.5V 46 | 47 | 48 | Pololu DRV8801 motor driver 49 | =========================== 50 | https://www.pololu.com/product/2136 51 | 52 | +----------------+ 53 | VDD 3.3V -----| VDD _FAULT | 54 | | BRAKE CS | 55 | PIN40 -----| _SLEEP VMM |----- motor BAT + 56 | PIN7 -----| DIR OUT- |----- motor 57 | PIN6 -----| PWM OUT+ |----- motor 58 | GND -----| GND GND |----- motor BAT - 59 | +----------------+ 60 | 61 | Pololu 50:1 Metal Gearmotor 62 | =========================== 63 | https://www.pololu.com/product/1440 64 | 65 | Red motor power (connects to one motor terminal) 66 | Black motor power (connects to the other motor terminal) 67 | Green encoder GND 68 | Blue encoder Vcc (3.5 – 20 V) 69 | Yellow encoder A output 70 | White encoder B output 71 | 72 | 73 | Adafruit backpack LEDs 74 | ====================== 75 | http://www.adafruit.com/product/1911 76 | https://learn.adafruit.com/adafruit-led-backpack/0-54-alphanumeric 77 | https://learn.adafruit.com/adafruit-led-backpack/changing-i2c-address 78 | 79 | Display --- Arduino Due 80 | SCL --- 21 81 | SDA --- 20 82 | GND --- GND 83 | VCC --- 5V 84 | Vi2C --- 3V 85 | 86 | Vi2C VCC GND SDA SCL 87 | 88 | 89 | Adafruit Huzzah ESP8266 90 | ======================= 91 | https://www.adafruit.com/products/2471 92 | https://learn.adafruit.com/adafruit-huzzah-esp8266-breakout/using-arduino-ide 93 | 94 | ESP8266 --- Arduino Due 95 | VBat --- VIN 96 | GND --- GND 97 | Rx --- Tx3 14, Serial 3 98 | Tx --- Rx3 15 99 | 100 | 101 | End stops 102 | ========= 103 | right 5V --- 43 104 | right SWT --- 45 105 | right GND --- 47 106 | left 5V --- 49 107 | left SWT --- 51 108 | left GND --- 53 109 | 110 | 111 | IRL2203N 112 | ======== 113 | solenoid --> spray can 114 | pin 8 115 | 116 | 117 | Arduino DUE 118 | =========== 119 | 00-01 Serial 0 120 | 19-18 Serial 1 121 | 17-16 Serial 2 122 | 15-14 Serial 3 123 | 124 | 02-13 PWM 125 | 126 | */ 127 | 128 | #include "motor.h" 129 | #include "input.h" 130 | #include "display.h" 131 | #include "spraycan.h" 132 | #include 133 | #include "Adafruit_LEDBackpack.h" 134 | #include "Adafruit_GFX.h" 135 | #include "DueTimer.h" 136 | 137 | // motor pins 138 | #define RIGHT_DIR 7 139 | #define RIGHT_PWM 6 140 | #define HEAD_DIR 5 141 | // using analogWrite on pin 4 made timer 6 stop. rewired to 9. 142 | //#define HEAD_PWM 4 143 | #define HEAD_PWM 9 144 | #define LEFT_DIR 3 145 | #define LEFT_PWM 2 146 | #define ENABLE 40 147 | 148 | #define RIGHT_ENC_A 24 149 | #define RIGHT_ENC_B 28 150 | #define HEAD_ENC_A 30 151 | #define HEAD_ENC_B 32 152 | #define LEFT_ENC_A 34 153 | #define LEFT_ENC_B 36 154 | 155 | #define LEFT_MAX_SPEED 200 156 | #define RIGHT_MAX_SPEED 200 157 | #define HEAD_MAX_SPEED 220 158 | 159 | // end switches 160 | #define END_SWITCH_RIGHT_PWR 43 161 | #define END_SWITCH_RIGHT_SWT 45 162 | #define END_SWITCH_RIGHT_GND 47 163 | #define END_SWITCH_LEFT_PWR 49 164 | #define END_SWITCH_LEFT_SWT 51 165 | #define END_SWITCH_LEFT_GND 53 166 | 167 | // print pin 168 | #define SPRAY_PIN 8 169 | 170 | // vbat measure pin 171 | #define VBAT_MEASURE_PIN 3 172 | #define VBAT_MUL 3542 173 | 174 | #define LED_PIN 13 175 | #define LED(on) (digitalWrite(LED_PIN, on)) 176 | #define INIT_HEAD_SPEED 50 177 | #define MAX_BUFFER_SIZE (8*1024) 178 | #define MAX_IMAGE_SIZE (8*1024) 179 | 180 | #define X_STEPS_PER_MM 11.7F 181 | #define Y_STEPS_PER_MM 14.5F 182 | #define Y_MIN 0 183 | #define Y_MAX 24650 184 | #define Y_MIN_MM 0 185 | #define Y_MAX_MM 1700 186 | #define Y_OVERSHOOT 750 187 | #define Y_SAFE_MM 30 188 | #define X_FINISH_MM 200 189 | #define PIXEL_WIDTH_MM 35 190 | #define PIXEL_HEIGHT_MM 35 191 | #define PIXEL_HEIGHT (PIXEL_HEIGHT_MM * Y_STEPS_PER_MM) 192 | 193 | // states 194 | #define STATE_INIT 0 195 | #define STATE_INIT_LEFT 1 196 | #define STATE_INIT_RIGHT 2 197 | #define STATE_IDLE 3 198 | #define STATE_DRAW_START 4 199 | #define STATE_DRAW_WAIT_Y0 5 200 | #define STATE_DRAW_LINE_YM 6 201 | #define STATE_DRAW_COL_YM 7 202 | #define STATE_DRAW_LINE_Y0 8 203 | #define STATE_DRAW_COL_Y0 9 204 | #define STATE_DRAW_FINISH 10 205 | #define STATE_DRAW_WAIT_FINISH 11 206 | #define STATE_MOVING 12 207 | #define STATE_GOTO 13 208 | #define STATE_ERROR 14 209 | #define STATE_TEST1 15 210 | #define STATE_TEST2 16 211 | #define STATE_TEST3 17 212 | 213 | char* states[] = { 214 | "INIT", // 0 215 | "INIT LFT", // 1 216 | "INIT RGT", // 2 217 | "IDLE", // 3 218 | "DRAWING", // 4 219 | "WAIT Y0", // 5 220 | "DRAW YM", // 6 221 | "COL YM", // 7 222 | "DRAW Y0", // 8 223 | "COL Y0", // 9 224 | "DRAW FIN", // 10 225 | "WAIT FIN", // 11 226 | "MOVING", // 12 227 | "GOTO", // 13 228 | "ERROR", // 14 229 | "TEST1", // 15 230 | "TEST2", // 16 231 | "TEST3" // 17 232 | }; 233 | 234 | #define CMD_STAT "stat" 235 | #define CMD_IMAG "imag" 236 | #define CMD_PING "ping" 237 | #define CMD_MOVE "move" 238 | #define CMD_GOTO "goto" 239 | 240 | long lastTime; 241 | long nextUpdate = 0; 242 | int supply = 0; 243 | char supplyStr[8]; 244 | int state = STATE_INIT; 245 | char line[MAX_BUFFER_SIZE]; 246 | char buf[32]; 247 | 248 | byte image[MAX_IMAGE_SIZE]; 249 | int imageWidth; 250 | int imageHeight; 251 | int actColumn; 252 | 253 | volatile boolean leftEndSwitch = false; 254 | volatile boolean rightEndSwitch = false; 255 | volatile int loopCount = 0; 256 | 257 | Motor leftMotor( LEFT_PWM, LEFT_DIR, LEFT_ENC_A, LEFT_ENC_B, LEFT_MAX_SPEED, X_STEPS_PER_MM); 258 | Motor rightMotor(RIGHT_PWM, RIGHT_DIR, RIGHT_ENC_A, RIGHT_ENC_B, RIGHT_MAX_SPEED, X_STEPS_PER_MM); 259 | Motor headMotor( HEAD_PWM, HEAD_DIR, HEAD_ENC_A, HEAD_ENC_B, HEAD_MAX_SPEED, Y_STEPS_PER_MM); 260 | 261 | SprayCan sprayCan(SPRAY_PIN, PIXEL_HEIGHT); 262 | 263 | UARTClass *cmdSerial; 264 | 265 | void setup() { 266 | 267 | // setup 268 | pinMode(2, OUTPUT); 269 | digitalWrite(2, LOW); 270 | 271 | Serial.begin(115200); 272 | pinMode(LED_PIN, OUTPUT); 273 | 274 | Serial3.begin(115200); 275 | cmdSerial = &Serial3; 276 | // Serial.begin(115200); 277 | // cmdSerial = &Serial; 278 | 279 | // setup motor decoders 280 | Motor::disableMotors(); 281 | attachInterrupt( LEFT_ENC_A, leftEncoderA, RISING); 282 | attachInterrupt( LEFT_ENC_B, leftEncoderB, RISING); 283 | attachInterrupt(RIGHT_ENC_A, rightEncoderA, RISING); 284 | attachInterrupt(RIGHT_ENC_B, rightEncoderB, RISING); 285 | attachInterrupt( HEAD_ENC_A, headEncoderA, RISING); 286 | attachInterrupt( HEAD_ENC_B, headEncoderB, RISING); 287 | rightMotor.setInverted(true); 288 | 289 | // setup right end switch 290 | pinMode(END_SWITCH_RIGHT_PWR, OUTPUT); 291 | pinMode(END_SWITCH_RIGHT_GND, OUTPUT); 292 | digitalWrite(END_SWITCH_RIGHT_PWR, HIGH); 293 | digitalWrite(END_SWITCH_RIGHT_GND, LOW); 294 | attachInterrupt(END_SWITCH_RIGHT_SWT, rightSwitch, CHANGE); 295 | 296 | // setup left end switch 297 | pinMode(END_SWITCH_LEFT_PWR, OUTPUT); 298 | pinMode(END_SWITCH_LEFT_GND, OUTPUT); 299 | digitalWrite(END_SWITCH_LEFT_PWR, HIGH); 300 | digitalWrite(END_SWITCH_LEFT_GND, LOW); 301 | attachInterrupt(END_SWITCH_LEFT_SWT, leftSwitch, CHANGE); 302 | leftSwitch(); 303 | rightSwitch(); 304 | 305 | // setup display 306 | initDisplay(); 307 | 308 | // wait 5 seconds 309 | int i = 0; 310 | for (i = 5; i > 0; i--) { 311 | itoa(i, buf, 10); 312 | display(buf); 313 | delay(1000); 314 | } 315 | 316 | // clear serial buffer 317 | while (cmdSerial->available()) { 318 | cmdSerial->read(); 319 | } 320 | 321 | Motor::enableMotors(); 322 | 323 | // Timer5.attachInterrupt(updateMotors2).setFrequency(20).start(); 324 | Timer6.attachInterrupt(updateMotors).setFrequency(20).start(); 325 | 326 | // state = STATE_IDLE; 327 | // state = STATE_TEST1; 328 | 329 | Serial.println("READY!"); 330 | display("READY"); 331 | } 332 | 333 | void checkSupply() { 334 | supply = analogRead(VBAT_MEASURE_PIN) * VBAT_MUL; 335 | int vmaj = supply / 100000; 336 | int vmin = (supply % 100000) / 10000; 337 | // Serial.print("battery: "); 338 | // Serial.print(vmaj); Serial.print("."); 339 | // Serial.println(vmin); 340 | char *str = supplyStr; 341 | itoa(vmaj, str, 10); 342 | str = (vmaj >= 10) ? str+2 : str+1; 343 | *str = '.'; 344 | str++; 345 | itoa(vmin, str, 10); 346 | // Serial.println(supplyStr); 347 | display(supplyStr); 348 | } 349 | 350 | void reportStatus() { 351 | char buf[40]; 352 | sprintf(buf, "%d %d %d", leftMotor.getPosition(), rightMotor.getPosition(), headMotor.getPosition()); 353 | Serial.println(buf); 354 | cmdSerial->println(buf); 355 | } 356 | 357 | boolean handlePing(char *line) { 358 | if (strncmp(line, CMD_STAT, 4) == 0) { 359 | Serial.println("request: stat"); 360 | reportStatus(); 361 | return true; 362 | } 363 | else if (strncmp(line, CMD_PING, 4) == 0) { 364 | Serial.println("request: ping"); 365 | cmdSerial->println("OK"); 366 | return true; 367 | } 368 | else { 369 | Serial.print("not allowed: "); 370 | Serial.println(line); 371 | cmdSerial->println("NOTOK"); 372 | return false; 373 | } 374 | } 375 | 376 | int doRequest(char *line) { 377 | 378 | int left = 0, right = 0, head = 0, fire = 0; 379 | int xTarget = 0, headTarget = 0; 380 | char buf[10]; 381 | 382 | if (strncmp(line, CMD_IMAG, 4) == 0) { 383 | Serial.println("image"); 384 | line += 5; // skip command and space 385 | readImage(line, image, &imageWidth, &imageHeight); 386 | sprayCan.prepareImage(image, imageWidth, imageHeight); 387 | cmdSerial->println("OK"); 388 | return STATE_DRAW_START; 389 | } 390 | else if (strncmp(line, CMD_MOVE, 4) == 0) { 391 | line += 5; // skip command and space 392 | line = readToken(line, buf, ' '); 393 | left = atoi(buf); 394 | display(buf); 395 | line = readToken(line, buf, ' '); 396 | right = atoi(buf); 397 | line = readToken(line, buf, ' '); 398 | head = atoi(buf); 399 | line = readToken(line, buf, ' '); 400 | fire = atoi(buf); 401 | Serial.print("move left: "); Serial.print(left); 402 | Serial.print(", right: "); Serial.print(right); 403 | Serial.print(", head: "); Serial.print(head); 404 | Serial.print(", fire: "); Serial.println(fire); 405 | leftMotor.setSpeed(left); 406 | rightMotor.setSpeed(right); 407 | headMotor.setSpeed(head); 408 | sprayCan.spray(fire == 1); 409 | cmdSerial->println("OK"); 410 | return (left == 0 && right == 0 && head == 0 && fire == 0) ? STATE_IDLE : STATE_MOVING; 411 | } 412 | else if (strncmp(line, CMD_GOTO, 4) == 0) { 413 | line += 5; // skip command and space 414 | line = readToken(line, buf, ' '); 415 | xTarget = atoi(buf); 416 | line = readToken(line, buf, ' '); 417 | headTarget = atoi(buf); 418 | Serial.print("goto: "); Serial.print(xTarget); 419 | Serial.print(" "); Serial.println(headTarget); 420 | leftMotor.setTargetMm(xTarget); 421 | rightMotor.setTargetMm(xTarget); 422 | headMotor.setTargetMm(headTarget); 423 | cmdSerial->println("OK"); 424 | return STATE_GOTO; 425 | } 426 | else { 427 | if (handlePing(line)) { 428 | return STATE_IDLE; 429 | } 430 | } 431 | return STATE_ERROR; 432 | } 433 | 434 | /* 435 | * Advances in x direction to the next column, that has data to print. 436 | */ 437 | void advanceColumn(int maxColumn) { 438 | do { 439 | actColumn++; 440 | Serial.print("--- column: "); 441 | Serial.println(actColumn); 442 | } while (sprayCan.isEmpty(actColumn) && actColumn < maxColumn); 443 | int xPos = PIXEL_WIDTH_MM * actColumn; 444 | leftMotor.setTargetMm(xPos); 445 | rightMotor.setTargetMm(xPos); 446 | } 447 | 448 | void finishingMove() { 449 | int finTarget = actColumn * PIXEL_WIDTH_MM + X_FINISH_MM; 450 | leftMotor.setTargetMm(finTarget); 451 | rightMotor.setTargetMm(finTarget); 452 | } 453 | 454 | void resetOrigin() { 455 | leftMotor.setPosition(0); 456 | rightMotor.setPosition(0); 457 | headMotor.setPosition(0); 458 | } 459 | 460 | /* 461 | * Finds the y target position for this column. 462 | * Uses the min/max of the current and the next column to compute, how far 463 | * to move the carriage. Adds some mm to overshoot, to be able to accelerate 464 | * or brake. 465 | */ 466 | int findYTarget(int column, boolean forward) { 467 | int y1 = forward ? sprayCan.getYMax(column) : sprayCan.getYMin(column); 468 | int y2 = forward ? sprayCan.getYMax(column+1) : sprayCan.getYMin(column+1); 469 | int target = forward ? max(y1, y2) : min(y1, y2); 470 | Serial.print("col: "); Serial.print(column); 471 | Serial.print(", forward: "); Serial.print(forward); 472 | Serial.print(", y1: "); Serial.print(y1); 473 | Serial.print(", y2: "); Serial.print(y2); 474 | Serial.print(", result: "); Serial.print(target); 475 | target = target + (forward ? Y_OVERSHOOT : -Y_OVERSHOOT); 476 | target = constrain(target, Y_MIN, Y_MAX); 477 | Serial.print(", overshoot: "); Serial.println(target); 478 | return target; 479 | } 480 | 481 | void loop() { 482 | 483 | static long testTime = 0; 484 | static int nextState = 0; 485 | static int oldCount; 486 | static boolean supplyIsOn = false; 487 | static boolean ledIsOn = 0; 488 | static long ledTime; 489 | static long drawTime; 490 | char buf[8]; 491 | 492 | int oldState = state; 493 | boolean isCmdComplete; 494 | 495 | // blink to show the main loop is working 496 | long now = millis(); 497 | if (now > ledTime) { 498 | ledTime = now + 200; 499 | ledIsOn = !ledIsOn; 500 | LED(ledIsOn); 501 | } 502 | 503 | // read command 504 | isCmdComplete = readCmdLine(cmdSerial, line); 505 | if (isCmdComplete && 506 | (state == STATE_INIT_LEFT || state == STATE_INIT_RIGHT || 507 | state == STATE_GOTO || state == STATE_DRAW_WAIT_Y0 || 508 | state == STATE_DRAW_LINE_YM || state == STATE_DRAW_COL_YM || 509 | state == STATE_DRAW_LINE_Y0 || state == STATE_DRAW_COL_Y0 || 510 | state == STATE_DRAW_FINISH || 511 | state == STATE_TEST1 || state == STATE_TEST2)) { 512 | handlePing(line); 513 | } 514 | 515 | switch (state) { 516 | case STATE_TEST1: 517 | if (testTime < millis()) { 518 | testTime = millis() + 6000; 519 | nextState = STATE_TEST2; 520 | state = STATE_TEST3; 521 | leftMotor.setTargetMm(50); 522 | rightMotor.setTargetMm(50); 523 | } 524 | break; 525 | case STATE_TEST2: 526 | if (testTime < millis()) { 527 | testTime = millis() + 6000; 528 | nextState = STATE_TEST1; 529 | state = STATE_TEST3; 530 | leftMotor.setTargetMm(0); 531 | rightMotor.setTargetMm(0); 532 | } 533 | break; 534 | case STATE_TEST3: 535 | if (!leftMotor.isRunning() && !rightMotor.isRunning()) { 536 | state = nextState; 537 | } 538 | else { 539 | if (oldCount != loopCount) { 540 | oldCount = loopCount; 541 | leftMotor.debug(); 542 | } 543 | } 544 | break; 545 | case STATE_INIT: 546 | if (!leftEndSwitch) { 547 | headMotor.setSpeed(-INIT_HEAD_SPEED); 548 | } 549 | state = STATE_INIT_LEFT; 550 | break; 551 | case STATE_INIT_LEFT: 552 | // goto left end switch 553 | if (leftEndSwitch) { 554 | headMotor.setSpeed(0); 555 | delay(50); 556 | resetOrigin(); 557 | headMotor.setTargetMm(Y_SAFE_MM); 558 | state = STATE_INIT_RIGHT; 559 | } 560 | break; 561 | case STATE_INIT_RIGHT: 562 | // back off a bit 563 | if (!headMotor.isRunning()) { 564 | resetOrigin(); 565 | state = STATE_IDLE; 566 | } 567 | break; 568 | case STATE_MOVING: 569 | case STATE_IDLE: 570 | if (isCmdComplete) { 571 | state = doRequest(line); 572 | } 573 | if (nextUpdate < millis()) { 574 | if (supplyIsOn) { 575 | display(states[state]); 576 | } 577 | else { 578 | checkSupply(); 579 | } 580 | supplyIsOn = !supplyIsOn; 581 | nextUpdate = millis() + 2000; 582 | } 583 | break; 584 | case STATE_GOTO: 585 | if (!leftMotor.isRunning() && !rightMotor.isRunning() && !headMotor.isRunning()) { 586 | state = STATE_IDLE; 587 | } 588 | break; 589 | case STATE_DRAW_START: 590 | actColumn = 0; 591 | headMotor.setTargetMm(Y_MIN_MM); 592 | leftMotor.setPosition(0); 593 | rightMotor.setPosition(0); 594 | state = STATE_DRAW_WAIT_Y0; 595 | break; 596 | case STATE_DRAW_WAIT_Y0: 597 | // before starting print, move to y0 598 | if (!headMotor.isRunning()) { 599 | sprayCan.startImage(); 600 | actColumn = -1; 601 | advanceColumn(imageWidth-1); 602 | if (actColumn == 0) { 603 | state = STATE_DRAW_LINE_YM; 604 | headMotor.setTarget(findYTarget(actColumn, true)); 605 | } 606 | else if (actColumn == imageWidth-1) { 607 | state = STATE_DRAW_FINISH; 608 | } 609 | else { 610 | state = STATE_DRAW_COL_Y0; 611 | } 612 | } 613 | break; 614 | case STATE_DRAW_LINE_YM: 615 | // drawing line Y0 --> Ymax 616 | if (!headMotor.isRunning()) { 617 | if (actColumn == imageWidth-1) { 618 | state = STATE_DRAW_FINISH; 619 | } 620 | else { 621 | state = STATE_DRAW_COL_YM; 622 | sprayCan.finishColumn(); 623 | advanceColumn(imageWidth-1); 624 | if (actColumn == imageWidth-1) { 625 | state = STATE_DRAW_FINISH; 626 | } 627 | } 628 | } 629 | else { 630 | // draw 631 | sprayCan.printColumn(actColumn, headMotor.getPosition(), true); 632 | } 633 | break; 634 | case STATE_DRAW_COL_YM: 635 | // advance X (head at Ymax) 636 | if (!leftMotor.isRunning() && !rightMotor.isRunning()) { 637 | state = STATE_DRAW_LINE_Y0; 638 | headMotor.setTarget(findYTarget(actColumn, false)); 639 | } 640 | break; 641 | case STATE_DRAW_LINE_Y0: 642 | // drawing line Ymax --> Y0 643 | if (!headMotor.isRunning()) { 644 | if (actColumn == imageWidth-1) { 645 | state = STATE_DRAW_FINISH; 646 | } 647 | else { 648 | state = STATE_DRAW_COL_Y0; 649 | sprayCan.finishColumn(); 650 | advanceColumn(imageWidth-1); 651 | if (actColumn == imageWidth-1) { 652 | state = STATE_DRAW_FINISH; 653 | } 654 | } 655 | } 656 | else { 657 | // draw 658 | sprayCan.printColumn(actColumn, headMotor.getPosition(), false); 659 | } 660 | break; 661 | case STATE_DRAW_COL_Y0: 662 | // avance X (head at Y0) 663 | if (!leftMotor.isRunning() && !rightMotor.isRunning()) { 664 | state = STATE_DRAW_LINE_YM; 665 | headMotor.setTarget(findYTarget(actColumn, true)); 666 | } 667 | break; 668 | case STATE_DRAW_FINISH: 669 | state = STATE_DRAW_WAIT_FINISH; 670 | sprayCan.finishImage(); 671 | finishingMove(); 672 | break; 673 | case STATE_DRAW_WAIT_FINISH: 674 | // finishing move 675 | if (!leftMotor.isRunning() && !rightMotor.isRunning()) { 676 | state = STATE_IDLE; 677 | } 678 | break; 679 | case STATE_ERROR: 680 | Serial.println("errored, stopped"); 681 | Serial.println(line); 682 | display("ERROR"); 683 | cmdSerial->print("ERROR "); 684 | cmdSerial->println(line); 685 | delay(5000); 686 | state = STATE_IDLE; 687 | } 688 | 689 | if (state != oldState) { 690 | Serial.print("state: "); 691 | Serial.print(states[oldState]); 692 | Serial.print(" --> "); 693 | Serial.print(states[state]); 694 | Serial.print(", "); 695 | Serial.print(loopCount); 696 | Serial.print(", left: "); Serial.print(leftMotor.getPosition()); 697 | Serial.print(", right: "); Serial.print(rightMotor.getPosition()); 698 | Serial.print(", head: "); Serial.print(headMotor.getPosition()); 699 | Serial.println(); 700 | display(states[state]); 701 | } 702 | 703 | } 704 | 705 | /*---------------------------------------------------------- 706 | * Below are methods that handle interrupts. 707 | * Interrupt handlers have to be void (*)(). 708 | */ 709 | void updateMotors() { 710 | leftMotor.loop(); 711 | rightMotor.loop(); 712 | headMotor.loop(); 713 | loopCount++; 714 | } 715 | 716 | void leftEncoderA() { 717 | leftMotor.encoderA(); 718 | } 719 | 720 | void leftEncoderB() { 721 | leftMotor.encoderB(); 722 | } 723 | 724 | void rightEncoderA() { 725 | rightMotor.encoderA(); 726 | } 727 | 728 | void rightEncoderB() { 729 | rightMotor.encoderB(); 730 | } 731 | 732 | void headEncoderA() { 733 | headMotor.encoderA(); 734 | } 735 | 736 | void headEncoderB() { 737 | headMotor.encoderB(); 738 | } 739 | 740 | void leftSwitch() { 741 | leftEndSwitch = digitalRead(END_SWITCH_LEFT_SWT); 742 | } 743 | 744 | void rightSwitch() { 745 | rightEndSwitch = digitalRead(END_SWITCH_RIGHT_SWT); 746 | } 747 | 748 | --------------------------------------------------------------------------------