mirror of
https://github.com/dandri/PricehaxBT.git
synced 2026-03-30 17:45:38 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc02e6e2b3 | ||
|
|
2a569c728a | ||
|
|
d07a50f3ba | ||
|
|
96baa41467 | ||
|
|
b252ed9fbc | ||
|
|
0e08c3b033 | ||
|
|
20a1f0bcee | ||
|
|
f337b93d25 | ||
|
|
615d7a2909 | ||
|
|
07c5ece3aa | ||
|
|
d33ef3e10a | ||
|
|
cf7ec44431 | ||
|
|
5782df9f38 | ||
|
|
607680fe40 | ||
|
|
b59400eb22 | ||
|
|
05efc0e52b | ||
|
|
22f00ef100 | ||
|
|
d026ecf2c7 | ||
|
|
517a3ef667 | ||
|
|
4fa5487e9c | ||
|
|
d9f2e28c8f |
58
README.md
58
README.md
@@ -1,17 +1,41 @@
|
||||
# Pricehax BT
|
||||
Working prototype of the ESL hack project of furrtek (https://github.com/furrtek/PrecIR) using Pricehax BT app, Arduino Nano board and HC-06 (or HC-05) module for bluetooth communication.
|
||||
Prototype of the ESL hack project done by Furrtek (https://github.com/furrtek/PrecIR) using the Pricehax BT Android app, Arduino Nano board and HC-06 (or HC-05) module for Bluetooth communication.
|
||||
|
||||
Videos about this project are archived in this [playlist](https://www.youtube.com/playlist?list=PLhEz48id1qqD27sRc73mDFfBpu_RcLxfZ) (most in French).
|
||||
**Disclaimer:** For educational purposes and fun only. Both Furrtek and I decline all responsability for any kind of issues related to an illegal use of this repo. Be smart, the prices in the store's database won't change, you have been warned.
|
||||
|
||||
Furrtek's videos of this project are archived in this [playlist](https://www.youtube.com/playlist?list=PLhEz48id1qqD27sRc73mDFfBpu_RcLxfZ) (most in French, auto-translated subtitles are enabled but they are far from perfect).
|
||||
|
||||
<img src="PricehaxBT.jpg" width="640" alt="PricehaxBT">
|
||||
|
||||
## Build
|
||||
- Create the dongle on breadboard following the schematic (or order the pcb and solder the components)
|
||||
- Using AT commands, change the name of the HC-05/HC-06 module to "PRICEHAX TX V3" and its baud rate to 57600bps (115200bps and more will not work because of the SoftwareSerial library usage that produces errors at those rates)
|
||||
- Using the AT commands, change the name of the HC-05/HC-06 module to "PRICEHAX TX V3" and its baudrate to 115200bps
|
||||
- Program the arduino nano with the included sketch
|
||||
- Download and install the [Android app](https://github.com/david4599/PricehaxBT/releases/latest), pair the dongle and enjoy ;-)
|
||||
|
||||
## Android app changelog
|
||||
|
||||
### Version 1.3 (21)
|
||||
|
||||
**Features**
|
||||
- Added PP16 IR Protocol support (not full speed, see [Notes](https://github.com/david4599/PricehaxBT#notes)): [speed comparison video](https://youtu.be/DFfLOQh_ERs)
|
||||
- Updated camera autofocus handling by using continuous focus modes if available
|
||||
- Improved Bluetooth transmission reliability: the whole data (Pricehax header + PPM frame) is sent to the dongle with a basic checksum instead of using the PPM frame CRC that won't verify the header
|
||||
|
||||
**Fixes**
|
||||
- Fixed bug that was sometimes stopping the image transmission just after the wake-up frame
|
||||
- Fixed camera preview rotation in reverse landscape and reverse portrait orientations
|
||||
- Fixed waiting time when sending an image in 2 parts (due to 64kB limit)
|
||||
|
||||
### Version 1.2 (20)
|
||||
|
||||
**Features**
|
||||
- Added 1370 and 1371 ESL types (ST HD L Red and ST HD150 Red black housing versions)
|
||||
- Added checksum verification of the ESL barcode
|
||||
|
||||
**Fixes**
|
||||
- Fixed SmartTag HD T definition in information label
|
||||
|
||||
### Version 1.2 (19)
|
||||
|
||||
**Features**
|
||||
@@ -20,12 +44,12 @@ Videos about this project are archived in this [playlist](https://www.youtube.co
|
||||
### Version 1.1 (18)
|
||||
|
||||
**Features**
|
||||
- Red (and yellow?) ESLs are supported: https://youtu.be/0PFMIiDluDw
|
||||
- Red (yellow not tested) ESLs are supported: https://youtu.be/0PFMIiDluDw
|
||||
- Improved Bluetooth transmission reliability: comparison of received checksum and calculated checksum by the dongle
|
||||
- Added the possibility to stop current image send
|
||||
- Added the possibility to stop sending the current image
|
||||
|
||||
**Fixes**
|
||||
- Fixed ST HD150 and ST HD200 definition
|
||||
- Fixed ST HD150 and ST HD200 definitions
|
||||
|
||||
### Version 1.1 (17)
|
||||
|
||||
@@ -34,25 +58,23 @@ Videos about this project are archived in this [playlist](https://www.youtube.co
|
||||
- Display debug infos for 24h
|
||||
- Hide debug infos feature added
|
||||
- Ability to blink the green LED on ST ESLs (not working yet on some): https://youtu.be/b0Rn40alxQg
|
||||
- Start autofocus by touching the preview screen on "PLID Scan" tab
|
||||
- Start autofocus by touching the preview screen on "PLID Scan" tab (not working sometimes?)
|
||||
- ESL types added (mainly graphic ESLs, not tested on the most of them but it should work)
|
||||
- Automatically choose sending compressed or raw data to graphic ESLs
|
||||
- Ability to force not compressed data sending to graphic ESLs
|
||||
- The number of repeats of frames for graphic ESLs can be chosen (speed transmission vs reliability)
|
||||
- The dongle can be manually connected or disconnected in "Config" page
|
||||
- Ability to force sending uncompressed data to graphic ESLs
|
||||
- The number of frames repeats for graphic ESLs can be chosen (speed transmission vs reliability)
|
||||
- The dongle can be manually connected or disconnected in "Config" tab
|
||||
|
||||
**Fixes**
|
||||
- Fixed some bugs and app crashes
|
||||
|
||||
## Notes
|
||||
- Android app sources are included
|
||||
- I didn't write the app, I only decompiled sources of the apk, imported them on Android Studio and rebuilt the app
|
||||
- The app supports only Bluetooth communication
|
||||
- Furrtek had already code Bluetooth feature in the original app, I just made some changes for that feature works with my dongle
|
||||
- I am not a programming or electronic expert, so maybe I made some mistakes on coding or making the schematic...
|
||||
- The implemented PP4C and PP16 protocols are not transmitting data at their maximum speeds. In theory, they should be around 10kbps for PP4C and 38kbps for PP16 (in reality, they are measured at 9kbps and 31kbps). Pricehax BT seems to do only 6kbps and 11kbps if my calculations are correct
|
||||
- I didn't write the app, I just decompiled the sources from the apk (that's why the code is a bit of a mess). Then, I imported them on Android Studio and fixed the decompilation errors preventing the re-compilation
|
||||
- The app supports Bluetooth communication only, the original communication using audio has been disabled (not the goal of this repo)
|
||||
- Furrtek did code the Bluetooth feature in the original app, I just made some changes so the feature works with my dongle
|
||||
- I am not a programming or electronic expert, so the code and the schematic might be better...
|
||||
|
||||
#
|
||||
|
||||
Both furrtek and me decline all responsability for any kind of issues related by an illegal use of this project.
|
||||
|
||||
Copyright (c) furrtek 2014 - david4599 2019
|
||||
Copyright (c) Furrtek 2014 & david4599 2019 - 2022
|
||||
@@ -6,8 +6,8 @@ android {
|
||||
applicationId "org.furrtek.pricehaxbt"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 28
|
||||
versionCode 19
|
||||
versionName "1.2"
|
||||
versionCode 21
|
||||
versionName "1.3"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package org.furrtek.pricehaxbt;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.hardware.Camera.AutoFocusCallback;
|
||||
import android.hardware.Camera.PreviewCallback;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceHolder.Callback;
|
||||
import android.view.SurfaceView;
|
||||
@@ -18,29 +21,50 @@ public class CameraPreview extends SurfaceView implements Callback {
|
||||
private Camera mCamera;
|
||||
private SurfaceHolder mHolder = getHolder();
|
||||
private PreviewCallback previewCallback;
|
||||
private boolean oldAutoFocusMode;
|
||||
private Context context;
|
||||
private int cameraId;
|
||||
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
checkOrientation(newConfig);
|
||||
}
|
||||
|
||||
private void checkOrientation(Configuration newConfig) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
this.mCamera.setDisplayOrientation(90);
|
||||
} else {
|
||||
this.mCamera.setDisplayOrientation(0);
|
||||
}
|
||||
setCameraDisplayOrientation((Activity) this.context, this.cameraId, this.mCamera);
|
||||
}
|
||||
|
||||
public CameraPreview(Context context, Camera camera, PreviewCallback previewCb, AutoFocusCallback autoFocusCb) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
|
||||
this.mCamera = camera;
|
||||
this.previewCallback = previewCb;
|
||||
this.autoFocusCallback = autoFocusCb;
|
||||
this.mHolder.addCallback(this);
|
||||
this.mHolder.setType(3);
|
||||
|
||||
this.cameraId = getBackCameraId();
|
||||
|
||||
// The old mode uses the onAutoFocus function and makes a timer to try to focus periodically.
|
||||
// The drawback is the hunting of the camera.
|
||||
// This can be "smoothed" by using either FOCUS_MODE_CONTINUOUS_PICTURE or FOCUS_MODE_CONTINUOUS_VIDEO parameters instead (if available).
|
||||
Camera.Parameters params = this.mCamera.getParameters();
|
||||
|
||||
this.oldAutoFocusMode = false;
|
||||
if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
||||
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||
}
|
||||
else if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
|
||||
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
|
||||
}
|
||||
else {
|
||||
this.oldAutoFocusMode = true;
|
||||
}
|
||||
|
||||
this.mCamera.setParameters(params);
|
||||
}
|
||||
|
||||
public boolean isOldAutoFocusMode() {
|
||||
return this.oldAutoFocusMode;
|
||||
}
|
||||
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
@@ -63,16 +87,61 @@ public class CameraPreview extends SurfaceView implements Callback {
|
||||
} catch (Exception e) {
|
||||
}
|
||||
try {
|
||||
if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
|
||||
this.mCamera.setDisplayOrientation(90);
|
||||
}
|
||||
setCameraDisplayOrientation((Activity) this.context, this.cameraId, this.mCamera);
|
||||
|
||||
this.mCamera.setPreviewDisplay(this.mHolder);
|
||||
this.mCamera.setPreviewCallback(this.previewCallback);
|
||||
this.mCamera.startPreview();
|
||||
this.mCamera.autoFocus(this.autoFocusCallback);
|
||||
|
||||
if (this.oldAutoFocusMode) {
|
||||
this.mCamera.autoFocus(this.autoFocusCallback);
|
||||
}
|
||||
|
||||
} catch (Exception e2) {
|
||||
Log.d("DBG", "Error starting camera preview: " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from Android SDK API sample code
|
||||
// https://github.com/Miserlou/Android-SDK-Samples/blob/master/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java
|
||||
private int getBackCameraId() {
|
||||
int numberOfCameras = Camera.getNumberOfCameras();
|
||||
|
||||
CameraInfo cameraInfo = new CameraInfo();
|
||||
for (int i = 0; i < numberOfCameras; i++) {
|
||||
Camera.getCameraInfo(i, cameraInfo);
|
||||
if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Sample code from Android Developer documentation
|
||||
// https://developer.android.com/reference/android/hardware/Camera#setDisplayOrientation%28int%29
|
||||
public static void setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) {
|
||||
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
|
||||
android.hardware.Camera.getCameraInfo(cameraId, info);
|
||||
|
||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
int degrees = 0;
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0: degrees = 0; break;
|
||||
case Surface.ROTATION_90: degrees = 90; break;
|
||||
case Surface.ROTATION_180: degrees = 180; break;
|
||||
case Surface.ROTATION_270: degrees = 270; break;
|
||||
}
|
||||
|
||||
int result;
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
result = (info.orientation + degrees) % 360;
|
||||
result = (360 - result) % 360; // compensate the mirror
|
||||
} else { // back-facing
|
||||
result = (info.orientation - degrees + 360) % 360;
|
||||
}
|
||||
|
||||
camera.setDisplayOrientation(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.media.TransportMediator;
|
||||
import android.support.v4.view.MotionEventCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -42,6 +41,7 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.Spinner;
|
||||
@@ -54,6 +54,7 @@ import android.widget.ToggleButton;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -86,7 +87,8 @@ public class MainActivity extends Activity {
|
||||
Integer PLType;
|
||||
Activity at = this;
|
||||
int donglever;
|
||||
int rawmode;
|
||||
boolean rawmode_forced;
|
||||
boolean rawmode_enabled;
|
||||
Handler handler = new Handler();
|
||||
int hi;
|
||||
InputStream imageStream = null;
|
||||
@@ -134,6 +136,9 @@ public class MainActivity extends Activity {
|
||||
private Camera mCamera;
|
||||
private CameraPreview mPreview;
|
||||
private boolean previewing = true;
|
||||
private boolean pp16Mode = true;
|
||||
private boolean oldAutoFocusMode = false;
|
||||
|
||||
private Runnable doAutoFocus = new Runnable() {
|
||||
public void run() {
|
||||
if (MainActivity.this.previewing && MainActivity.this.tabHost.getCurrentTab() == 1 && MainActivity.this.mCamera != null) {
|
||||
@@ -143,6 +148,8 @@ public class MainActivity extends Activity {
|
||||
};
|
||||
AutoFocusCallback autoFocusCB = new AutoFocusCallback() {
|
||||
public void onAutoFocus(boolean success, Camera camera) {
|
||||
// postDelayed() will call doAutoFocus() and onAutoFocus() will be called again from mCamera.autoFocus().
|
||||
// This will create a timer that will readjust the focus every second.
|
||||
MainActivity.this.autoFocusHandler.postDelayed(MainActivity.this.doAutoFocus, 1000);
|
||||
}
|
||||
};
|
||||
@@ -152,19 +159,40 @@ public class MainActivity extends Activity {
|
||||
Image barcode = new Image(size.width, size.height, "Y800");
|
||||
barcode.setData(data);
|
||||
if (MainActivity.this.scanner.scanImage(barcode) != 0) {
|
||||
MainActivity.this.previewing = false;
|
||||
MainActivity.this.mCamera.setPreviewCallback(null);
|
||||
MainActivity.this.mCamera.stopPreview();
|
||||
|
||||
Iterator it = MainActivity.this.scanner.getResults().iterator();
|
||||
while (it.hasNext()) {
|
||||
String PLBarcode = ((Symbol) it.next()).getData();
|
||||
setESLBarcode(PLBarcode);
|
||||
if (setESLBarcode(PLBarcode)) {
|
||||
MainActivity.this.previewing = false;
|
||||
MainActivity.this.mCamera.setPreviewCallback(null);
|
||||
MainActivity.this.mCamera.stopPreview();
|
||||
MainActivity.this.scanButton.setText("Scan another ESL barcode?");
|
||||
}
|
||||
else {
|
||||
Toast.makeText(MainActivity.this, "The scanned barcode is invalid!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void setESLBarcode(String PLBarcode) {
|
||||
private boolean isBarcodeChecksumValid(String barcode) {
|
||||
byte[] barcodeBytes = barcode.getBytes(Charset.forName("UTF-8"));
|
||||
|
||||
int checksum = 0;
|
||||
for (int i = 0; i < barcodeBytes.length - 1; i++) {
|
||||
checksum += (int) barcodeBytes[i];
|
||||
}
|
||||
|
||||
checksum %= 10;
|
||||
|
||||
return checksum == barcodeBytes[barcodeBytes.length - 1] - '0';
|
||||
}
|
||||
|
||||
private boolean setESLBarcode(String PLBarcode) {
|
||||
if (!isBarcodeChecksumValid(PLBarcode)) return false;
|
||||
|
||||
MainActivity.this.plID = (long) ((Integer.parseInt(PLBarcode.substring(2, 7)) << 16) + Integer.parseInt(PLBarcode.substring(7, 12)));
|
||||
String PLSerial = Long.toHexString(MainActivity.this.plID);
|
||||
MainActivity.this.PLType = Integer.valueOf(Integer.parseInt(PLBarcode.substring(12, 16)));
|
||||
@@ -197,7 +225,10 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.plBitDef = 0;
|
||||
MainActivity.this.ESLType = 1;
|
||||
break;
|
||||
|
||||
case 1243:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (Continuum E4 HCW (18410-00) segments bitmap not done !)");
|
||||
MainActivity.this.ESLType = 1;
|
||||
break;
|
||||
|
||||
case 1217:
|
||||
case 1265:
|
||||
@@ -252,13 +283,6 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.hi = 152;
|
||||
MainActivity.this.ESLType = 2;
|
||||
break;
|
||||
case 1339:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD S Red 152x152) EXPERIMENTAL");
|
||||
MainActivity.this.wi = 152;
|
||||
MainActivity.this.hi = 152;
|
||||
MainActivity.this.ESLType = 2;
|
||||
MainActivity.this.ESLTypeColor = true;
|
||||
break;
|
||||
|
||||
|
||||
case 1318:
|
||||
@@ -283,13 +307,23 @@ public class MainActivity extends Activity {
|
||||
|
||||
|
||||
case 1315:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD L 296x128) EXPERIMENTAL");
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD L 296x128)");
|
||||
MainActivity.this.wi = 296;
|
||||
MainActivity.this.hi = 128;
|
||||
MainActivity.this.ESLType = 2;
|
||||
break;
|
||||
|
||||
case 1370: // 2021 revision of the SmartTag HD L Red 296x128 - black housing
|
||||
case 1328:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD L Red 296x128) EXPERIMENTAL");
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD L Red 296x128)");
|
||||
MainActivity.this.wi = 296;
|
||||
MainActivity.this.hi = 128;
|
||||
MainActivity.this.ESLType = 2;
|
||||
MainActivity.this.ESLTypeColor = true;
|
||||
break;
|
||||
case 1627:
|
||||
case 1628:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD L Red 296x128) ");
|
||||
MainActivity.this.wi = 296;
|
||||
MainActivity.this.hi = 128;
|
||||
MainActivity.this.ESLType = 2;
|
||||
@@ -305,14 +339,14 @@ public class MainActivity extends Activity {
|
||||
|
||||
|
||||
case 1348:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD T Red 296x128) EXPERIMENTAL");
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD T Red 264x176) EXPERIMENTAL");
|
||||
MainActivity.this.wi = 264;
|
||||
MainActivity.this.hi = 176;
|
||||
MainActivity.this.ESLType = 2;
|
||||
MainActivity.this.ESLTypeColor = true;
|
||||
break;
|
||||
case 1349:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD T Yellow 296x128) EXPERIMENTAL");
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD T Yellow 264x176) EXPERIMENTAL");
|
||||
MainActivity.this.wi = 264;
|
||||
MainActivity.this.hi = 176;
|
||||
MainActivity.this.ESLType = 2;
|
||||
@@ -333,7 +367,14 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.ESLType = 2;
|
||||
MainActivity.this.ESLTypeColor = true;
|
||||
break;
|
||||
|
||||
case 1339:
|
||||
case 1639:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD S Red 152x152) ");
|
||||
MainActivity.this.wi = 152;
|
||||
MainActivity.this.hi = 152;
|
||||
MainActivity.this.ESLType = 2;
|
||||
MainActivity.this.ESLTypeColor = true;
|
||||
break;
|
||||
|
||||
case 1351:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD150 648x480) EXPERIMENTAL");
|
||||
@@ -341,6 +382,7 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.hi = 480;
|
||||
MainActivity.this.ESLType = 2;
|
||||
break;
|
||||
case 1371: // 2021 revision of the SmartTag HD150 Red 648x480 - black housing
|
||||
case 1353:
|
||||
case 1354:
|
||||
MainActivity.this.scaneitype.setText("Type: " + MainActivity.this.PLType + " (SmartTag HD150 Red 648x480)");
|
||||
@@ -378,6 +420,7 @@ public class MainActivity extends Activity {
|
||||
break;
|
||||
}
|
||||
MainActivity.this.barcodeScanned = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean repeatMode = false;
|
||||
@@ -738,21 +781,28 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.txtworkh.setText("Waking up ESL...");
|
||||
}
|
||||
});
|
||||
PP4C.sendPP4C(MainActivity.this.at.getApplicationContext(), pingcode, 32, MainActivity.this.donglever, 50, MainActivity.audioTrack, 250);
|
||||
|
||||
int wakeupRepeat = 250;
|
||||
if (MainActivity.this.pp16Mode) wakeupRepeat *= 2;
|
||||
|
||||
PPM.sendPPM(this, pingcode, MainActivity.this.pp16Mode, 32, MainActivity.this.donglever, 50, MainActivity.audioTrack, wakeupRepeat);
|
||||
if (MainActivity.this.donglever == 2) {
|
||||
SystemClock.sleep(2500);
|
||||
} else if (MainActivity.this.donglever == 1) {
|
||||
SystemClock.sleep(1800);
|
||||
PP4C.sendPP4C(MainActivity.this.at.getApplicationContext(), pingcode, 32, MainActivity.this.donglever, 35, MainActivity.audioTrack, 0);
|
||||
PPM.sendPPM(this, pingcode, MainActivity.this.pp16Mode, 32, MainActivity.this.donglever, 35, MainActivity.audioTrack, 0);
|
||||
SystemClock.sleep(1800);
|
||||
PP4C.sendPP4C(MainActivity.this.at.getApplicationContext(), pingcode, 32, MainActivity.this.donglever, 35, MainActivity.audioTrack, 0);
|
||||
PPM.sendPPM(this, pingcode, MainActivity.this.pp16Mode, 32, MainActivity.this.donglever, 35, MainActivity.audioTrack, 0);
|
||||
SystemClock.sleep(1800);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Part:
|
||||
// 0 = entire image
|
||||
// 1 = 1st half of the image (top)
|
||||
// 2 = 2nd half of the image (bottom)
|
||||
private void sendStartCode(int part) {
|
||||
byte[] startcode = new byte[54];
|
||||
startcode[0] = (byte) -123;
|
||||
@@ -766,7 +816,7 @@ public class MainActivity extends Activity {
|
||||
startcode[8] = (byte) 0;
|
||||
startcode[9] = (byte) 5;
|
||||
startcode[10] = (byte) (datalen >> 8);
|
||||
startcode[11] = (byte) (datalen & MotionEventCompat.ACTION_MASK);
|
||||
startcode[11] = (byte) (datalen & 0xFF);
|
||||
byte[] bArr = new byte[20];
|
||||
if (part == 0) {
|
||||
bArr = new byte[]{(byte) 0, (byte) compression_type, (byte) 2, (byte) (MainActivity.this.wi >> 8), (byte) (MainActivity.this.wi & 255), (byte) (MainActivity.this.hi >> 8), (byte) (MainActivity.this.hi & 255), (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) -120, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0};
|
||||
@@ -792,7 +842,7 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.txtworkh.setText("Start frame...");
|
||||
}
|
||||
});
|
||||
PP4C.sendPP4C(MainActivity.this.at.getApplicationContext(), startcode, 34, MainActivity.this.donglever, 6, MainActivity.audioTrack, 10);
|
||||
PPM.sendPPM(this, startcode, MainActivity.this.pp16Mode, 34, MainActivity.this.donglever, 6, MainActivity.audioTrack, 10);
|
||||
if (MainActivity.this.donglever == 2) {
|
||||
SystemClock.sleep(1000);
|
||||
}
|
||||
@@ -806,25 +856,25 @@ public class MainActivity extends Activity {
|
||||
|
||||
|
||||
private void sendFrame(List<Byte> hexlist, int numframe) {
|
||||
byte[] startcode = new byte[54];
|
||||
startcode[0] = (byte) -123;
|
||||
startcode[1] = (byte) ((int) (MainActivity.this.plID & 255));
|
||||
startcode[2] = (byte) ((int) (MainActivity.this.plID >> 8));
|
||||
startcode[3] = (byte) ((int) (MainActivity.this.plID >> 16));
|
||||
startcode[4] = (byte) ((int) (MainActivity.this.plID >> 24));
|
||||
startcode[5] = (byte) 52;
|
||||
startcode[6] = (byte) 0;
|
||||
startcode[7] = (byte) 0;
|
||||
startcode[8] = (byte) 0;
|
||||
startcode[9] = (byte) 32;
|
||||
startcode[10] = (byte) (numframe >> 8);
|
||||
startcode[11] = (byte) (numframe & MotionEventCompat.ACTION_MASK);
|
||||
byte[] framecode = new byte[54];
|
||||
framecode[0] = (byte) -123;
|
||||
framecode[1] = (byte) ((int) (MainActivity.this.plID & 255));
|
||||
framecode[2] = (byte) ((int) (MainActivity.this.plID >> 8));
|
||||
framecode[3] = (byte) ((int) (MainActivity.this.plID >> 16));
|
||||
framecode[4] = (byte) ((int) (MainActivity.this.plID >> 24));
|
||||
framecode[5] = (byte) 52;
|
||||
framecode[6] = (byte) 0;
|
||||
framecode[7] = (byte) 0;
|
||||
framecode[8] = (byte) 0;
|
||||
framecode[9] = (byte) 32;
|
||||
framecode[10] = (byte) (numframe >> 8);
|
||||
framecode[11] = (byte) (numframe & 0xFF);
|
||||
for (int cp = 0; cp < 40; cp++) {
|
||||
startcode[cp + 12] = ((Byte) hexlist.get((numframe * 40) + cp)).byteValue();
|
||||
framecode[cp + 12] = ((Byte) hexlist.get((numframe * 40) + cp)).byteValue();
|
||||
}
|
||||
byte[] FrameCRC = CRCCalc.GetCRC(startcode, 52);
|
||||
startcode[52] = FrameCRC[0];
|
||||
startcode[53] = FrameCRC[1];
|
||||
byte[] FrameCRC = CRCCalc.GetCRC(framecode, 52);
|
||||
framecode[52] = FrameCRC[0];
|
||||
framecode[53] = FrameCRC[1];
|
||||
|
||||
if (!threadRunning) return;
|
||||
MainActivity.this.runOnUiThread(new Runnable() {
|
||||
@@ -832,7 +882,7 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.txtworkh.setText("Data frame " + MainActivity.this.y + "/" + MainActivity.this.ymax);
|
||||
}
|
||||
});
|
||||
PP4C.sendPP4C(MainActivity.this.at.getApplicationContext(), startcode, 54, MainActivity.this.donglever, 1, MainActivity.audioTrack, MainActivity.this.nbRepeatFrame);
|
||||
PPM.sendPPM(this, framecode, MainActivity.this.pp16Mode, 54, MainActivity.this.donglever, 1, MainActivity.audioTrack, MainActivity.this.nbRepeatFrame);
|
||||
if (MainActivity.this.donglever == 2) {
|
||||
SystemClock.sleep(550);
|
||||
}
|
||||
@@ -862,7 +912,7 @@ public class MainActivity extends Activity {
|
||||
MainActivity.this.txtworkh.setText("Verify frame...");
|
||||
}
|
||||
});
|
||||
PP4C.sendPP4C(MainActivity.this.at.getApplicationContext(), vercode, 30, MainActivity.this.donglever, 10, MainActivity.audioTrack, 50);
|
||||
PPM.sendPPM(this, vercode, MainActivity.this.pp16Mode, 30, MainActivity.this.donglever, 10, MainActivity.audioTrack, 50);
|
||||
if (MainActivity.this.donglever != 3) {
|
||||
SystemClock.sleep(2000);
|
||||
}
|
||||
@@ -871,11 +921,12 @@ public class MainActivity extends Activity {
|
||||
|
||||
|
||||
|
||||
private void sendImage(Bitmap image, int size_raw, int imagePart) {
|
||||
MainActivity mainActivity;
|
||||
private List<Byte> convertImage(Bitmap image, int size_raw) {
|
||||
int size_compressed;
|
||||
List<Byte> hexlist;
|
||||
|
||||
MainActivity.this.rawbitstream = new byte[size_raw];
|
||||
|
||||
MainActivity.this.idx = 0;
|
||||
MainActivity.this.compressed = new ArrayList<Integer>();
|
||||
|
||||
@@ -883,27 +934,31 @@ public class MainActivity extends Activity {
|
||||
StringBuilder bstr_compressed = new StringBuilder();
|
||||
|
||||
|
||||
// Add black/white colors first
|
||||
convertMonochrome(image, false);
|
||||
if (!threadRunning) return;
|
||||
if (!threadRunning) return null;
|
||||
|
||||
// Then add red color if compatible ESL
|
||||
if (MainActivity.this.ESLTypeColor) {
|
||||
convertMonochrome(image, true);
|
||||
if (!threadRunning) return;
|
||||
if (!threadRunning) return null;
|
||||
}
|
||||
|
||||
|
||||
RLECompress();
|
||||
if (!threadRunning) return;
|
||||
if (!threadRunning) return null;
|
||||
|
||||
|
||||
Hexadecimalifying(bstr_raw, bstr_compressed);
|
||||
|
||||
size_compressed = bstr_compressed.toString().length();
|
||||
if (size_compressed < size_raw && rawmode == 0) { // Compressed data mode
|
||||
if (size_compressed < size_raw && !rawmode_forced) { // Compressed data mode
|
||||
hexlist = createCompressedHexList(bstr_compressed);
|
||||
rawmode_enabled = false;
|
||||
}
|
||||
else { // raw data mode
|
||||
hexlist = createRawHexList(bstr_raw);
|
||||
rawmode_enabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -918,8 +973,14 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
});
|
||||
|
||||
return hexlist;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void sendImage(List<Byte> hexlist, int imagePart) {
|
||||
MainActivity mainActivity;
|
||||
|
||||
sendPingCode();
|
||||
if (!threadRunning) return;
|
||||
|
||||
@@ -937,20 +998,13 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
|
||||
sendVerifCode();
|
||||
if (!threadRunning) return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void convertImage() {
|
||||
public void convertAndSendImage() {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
MainActivity mainActivity;
|
||||
|
||||
MainActivity.this.txtworkh = (TextView) MainActivity.this.findViewById(R.id.txtwork);
|
||||
MainActivity.this.pgb = (ProgressBar) MainActivity.this.findViewById(R.id.pgb1);
|
||||
MainActivity.this.imgbmp = (ImageView) MainActivity.this.findViewById(R.id.imgvbmp);
|
||||
@@ -980,10 +1034,11 @@ public class MainActivity extends Activity {
|
||||
StringBuilder bstr_compressed = new StringBuilder();
|
||||
|
||||
|
||||
|
||||
// Add black/white colors first
|
||||
convertMonochrome(scaledimage, false);
|
||||
if (!threadRunning) return;
|
||||
|
||||
// Then add red color if compatible ESL
|
||||
if (MainActivity.this.ESLTypeColor) {
|
||||
convertMonochrome(scaledimage, true);
|
||||
if (!threadRunning) return;
|
||||
@@ -998,8 +1053,14 @@ public class MainActivity extends Activity {
|
||||
|
||||
|
||||
size_compressed = bstr_compressed.toString().length();
|
||||
if ((size_compressed/8) <= 65535 && rawmode == 0 || ((size_raw/8) <= 65535 && rawmode == 1)) {
|
||||
if (size_compressed < size_raw && rawmode == 0) {
|
||||
|
||||
// It seems an image cannot be sent if its length (compressed or not) is greater than 64kB.
|
||||
// This is because the "datalen" field (bytes 10 and 11) of the start code is only 2 bytes (unless we can have more?)
|
||||
// A workaround is to split the image e.g. in half and send the parts separately.
|
||||
// So the image has to be converted and compressed a first time to know if we need to split it.
|
||||
// If so, we split it, convert and compress the parts again and send them.
|
||||
if ((size_compressed/8) <= 65535 && !rawmode_forced || ((size_raw/8) <= 65535 && rawmode_forced)) {
|
||||
if (size_compressed < size_raw && !rawmode_forced) {
|
||||
hexlist = createCompressedHexList(bstr_compressed);
|
||||
}
|
||||
else {
|
||||
@@ -1018,26 +1079,7 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
sendPingCode();
|
||||
if (!threadRunning) return;
|
||||
|
||||
sendStartCode(0);
|
||||
if (!threadRunning) return;
|
||||
|
||||
MainActivity.this.ymax = padded_datalen / 40;
|
||||
MainActivity.this.y = 0;
|
||||
while (MainActivity.this.y < MainActivity.this.ymax) {
|
||||
sendFrame(hexlist, MainActivity.this.y);
|
||||
if (!threadRunning) return;
|
||||
|
||||
mainActivity = MainActivity.this;
|
||||
mainActivity.y++;
|
||||
}
|
||||
|
||||
sendVerifCode();
|
||||
if (!threadRunning) return;
|
||||
sendImage(hexlist, 0);
|
||||
}
|
||||
else {
|
||||
MainActivity.this.handler.post(new Runnable() {
|
||||
@@ -1053,15 +1095,23 @@ public class MainActivity extends Activity {
|
||||
e22.printStackTrace();
|
||||
}
|
||||
|
||||
MainActivity.this.scaledimagepart1=Bitmap.createBitmap(MainActivity.this.scaledimage, 0,0, w, h/2);
|
||||
MainActivity.this.scaledimagepart2=Bitmap.createBitmap(MainActivity.this.scaledimage, 0,h/2, w, h/2);
|
||||
MainActivity.this.scaledimagepart1 = Bitmap.createBitmap(MainActivity.this.scaledimage, 0,0, w, h / 2);
|
||||
MainActivity.this.scaledimagepart2 = Bitmap.createBitmap(MainActivity.this.scaledimage, 0,h / 2, w, h / 2);
|
||||
|
||||
|
||||
MainActivity.this.rawbitstream = new byte[size_raw/2];
|
||||
sendImage(scaledimagepart1, (size_raw/2), 1);
|
||||
MainActivity.this.rawbitstream = new byte[size_raw / 2];
|
||||
hexlist = convertImage(scaledimagepart1, (size_raw / 2));
|
||||
sendImage(hexlist, 1);
|
||||
if (!threadRunning) return;
|
||||
|
||||
for (int i = 45; i > 0; i--) {
|
||||
|
||||
// We need to wait because the second part cannot be sent while the ESL is refreshing the first part
|
||||
int timer = 35;
|
||||
|
||||
// If the first part was sent compressed, we have to wait even more since decompression takes a lot of time
|
||||
if (!rawmode_enabled) timer = 50;
|
||||
|
||||
for (int i = timer; i > 0; i--) {
|
||||
final int sec = i;
|
||||
MainActivity.this.handler.post(new Runnable() {
|
||||
public void run() {
|
||||
@@ -1075,11 +1125,12 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
MainActivity.this.rawbitstream = new byte[size_raw/2];
|
||||
sendImage(scaledimagepart2, size_raw/2, 2);
|
||||
if (!threadRunning) return;
|
||||
}
|
||||
|
||||
MainActivity.this.rawbitstream = new byte[size_raw / 2];
|
||||
hexlist = convertImage(scaledimagepart2, size_raw / 2);
|
||||
sendImage(hexlist, 2);
|
||||
}
|
||||
if (!threadRunning) return;
|
||||
|
||||
|
||||
MainActivity.this.handler.post(new Runnable() {
|
||||
@@ -1097,13 +1148,15 @@ public class MainActivity extends Activity {
|
||||
}).start();
|
||||
}
|
||||
|
||||
public boolean isSendImageThreadRunning() {
|
||||
return threadRunning;
|
||||
}
|
||||
|
||||
public void setScanPreview() {
|
||||
this.autoFocusHandler = new Handler();
|
||||
this.mCamera = getCameraInstance();
|
||||
Parameters params = this.mCamera.getParameters();
|
||||
params.setPreviewSize(640, 480);
|
||||
this.mCamera.setParameters(params);
|
||||
this.mPreview = new CameraPreview(this, this.mCamera, this.previewCb, this.autoFocusCB);
|
||||
this.oldAutoFocusMode = this.mPreview.isOldAutoFocusMode();
|
||||
this.preview = (FrameLayout) findViewById(R.id.cameraPreview);
|
||||
this.preview.addView(this.mPreview);
|
||||
this.scanner = new ImageScanner();
|
||||
@@ -1117,25 +1170,31 @@ public class MainActivity extends Activity {
|
||||
this.scanButton = (Button) findViewById(R.id.scan_button);
|
||||
this.scanButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
String scanBarcodeString = "Scan ESL barcode";
|
||||
|
||||
if (MainActivity.this.mCamera == null) {
|
||||
MainActivity.this.previewing = false;
|
||||
|
||||
MainActivity.this.mCamera = MainActivity.getCameraInstance();
|
||||
MainActivity.this.mCamera.setPreviewCallback(MainActivity.this.previewCb);
|
||||
MainActivity.this.mCamera.startPreview();
|
||||
MainActivity.this.previewing = true;
|
||||
MainActivity.this.mCamera.autoFocus(MainActivity.this.autoFocusCB);
|
||||
|
||||
((FrameLayout) MainActivity.this.findViewById(R.id.cameraPreview)).addView(MainActivity.this.mPreview);
|
||||
}
|
||||
if (MainActivity.this.barcodeScanned) {
|
||||
MainActivity.this.barcodeScanned = false;
|
||||
MainActivity.this.scaneibarcode.setText("Scan ESL barcode");
|
||||
MainActivity.this.mCamera.setPreviewCallback(MainActivity.this.previewCb);
|
||||
MainActivity.this.mCamera.startPreview();
|
||||
MainActivity.this.previewing = true;
|
||||
MainActivity.this.mCamera.autoFocus(MainActivity.this.autoFocusCB);
|
||||
MainActivity.this.scaneibarcode.setText(scanBarcodeString);
|
||||
|
||||
MainActivity.this.ESLType = 0;
|
||||
}
|
||||
|
||||
|
||||
MainActivity.this.scanButton.setText(scanBarcodeString);
|
||||
MainActivity.this.mCamera.setPreviewCallback(MainActivity.this.previewCb);
|
||||
MainActivity.this.mCamera.startPreview();
|
||||
MainActivity.this.previewing = true;
|
||||
|
||||
if (MainActivity.this.oldAutoFocusMode) {
|
||||
MainActivity.this.mCamera.autoFocus(MainActivity.this.autoFocusCB);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1224,8 +1283,8 @@ public class MainActivity extends Activity {
|
||||
((TextView) MainActivity.this.findViewById(R.id.tv_nb_repeat)).setText("Frames repeated " + (nbRepeatFrame) + " time" + (p) + " (Lower is faster but less reliable !)");
|
||||
nbRepeatControl.setProgress(nbRepeatFrame - 1);
|
||||
|
||||
this.rawmode = this.settings.getInt("rawmode", 0);
|
||||
if (this.rawmode == 1) {
|
||||
this.rawmode_forced = this.settings.getBoolean("rawmode_forced", false);
|
||||
if (this.rawmode_forced) {
|
||||
((CheckBox) findViewById(R.id.force_raw_sending_mode)).setChecked(true);
|
||||
}
|
||||
|
||||
@@ -1323,6 +1382,9 @@ public class MainActivity extends Activity {
|
||||
public void onClick(View v) {
|
||||
Button btnsend = (Button) v;
|
||||
String buttonText = btnsend.getText().toString();
|
||||
|
||||
String cancelText = "Cancel now";
|
||||
|
||||
if (buttonText.equals("Send image")) {
|
||||
if (!isDonglePaired()) {
|
||||
return;
|
||||
@@ -1338,15 +1400,24 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
|
||||
MainActivity.this.threadRunning = true;
|
||||
btnsend.setText("Stop send");
|
||||
convertImage();
|
||||
btnsend.setText(cancelText);
|
||||
convertAndSendImage();
|
||||
}
|
||||
else if (buttonText.equals("Stop send")) {
|
||||
else if (buttonText.equals(cancelText)) {
|
||||
MainActivity.this.threadRunning = false;
|
||||
//SystemClock.sleep(200);
|
||||
btnsend.setText("Send image");
|
||||
MainActivity.this.txtworkh.setText("Stopped successfully !");
|
||||
MainActivity.this.pgb.setProgress(100);
|
||||
new Timer().schedule(new TimerTask() {
|
||||
public void run() {
|
||||
MainActivity.this.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
MainActivity.this.btnsendimg.setText("Send image");
|
||||
MainActivity.this.txtworkh.setText("Transmission cancelled !");
|
||||
MainActivity.this.pgb.setProgress(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1396,19 +1467,35 @@ public class MainActivity extends Activity {
|
||||
String barcode = barcodeedittext.getText().toString().toUpperCase();
|
||||
|
||||
if (barcode.length() == 17) {
|
||||
if (Character.isLetter(barcode.charAt(0)) && TextUtils.isDigitsOnly(barcode.substring(1, 17))) {
|
||||
setESLBarcode(barcode);
|
||||
if (!setESLBarcode(barcode)) {
|
||||
Toast.makeText(MainActivity.this, "The barcode is invalid!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
else {
|
||||
Toast.makeText(MainActivity.this, "The barcode entered is invalid!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
Toast.makeText(MainActivity.this, "The barcode entered is not the right length!", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(MainActivity.this, "The barcode is not 17 characters long!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RadioGroup radppmversion = findViewById(R.id.ppmversion);
|
||||
radppmversion.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void onCheckedChanged(RadioGroup group, int checkedId) {
|
||||
MainActivity.this.pp16Mode = checkedId == R.id.radpp16;
|
||||
Editor editor = getSharedPreferences("Pricehax", 0).edit();
|
||||
editor.putBoolean("pp16Mode", MainActivity.this.pp16Mode);
|
||||
editor.commit();
|
||||
}
|
||||
});
|
||||
|
||||
radppmversion.check(R.id.radpp16);
|
||||
|
||||
this.pp16Mode = this.settings.getBoolean("pp16Mode", true);
|
||||
if (!this.pp16Mode) {
|
||||
radppmversion.check(R.id.radpp4c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1463,12 +1550,15 @@ public class MainActivity extends Activity {
|
||||
this.autoFocusHandler = new Handler();
|
||||
this.mCamera = getCameraInstance();
|
||||
Parameters params = this.mCamera.getParameters();
|
||||
params.setPreviewSize(640, 480);
|
||||
this.mCamera.setParameters(params);
|
||||
this.mPreview = new CameraPreview(this, this.mCamera, this.previewCb, this.autoFocusCB);
|
||||
this.oldAutoFocusMode = this.mPreview.isOldAutoFocusMode();
|
||||
this.preview = (FrameLayout) findViewById(R.id.cameraPreview);
|
||||
this.preview.addView(this.mPreview);
|
||||
this.mCamera.autoFocus(MainActivity.this.autoFocusCB);
|
||||
|
||||
if (this.oldAutoFocusMode) {
|
||||
this.mCamera.autoFocus(this.autoFocusCB);
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
@@ -1482,9 +1572,9 @@ public class MainActivity extends Activity {
|
||||
this.autoFocusHandler = new Handler();
|
||||
this.mCamera = getCameraInstance();
|
||||
Parameters params = this.mCamera.getParameters();
|
||||
params.setPreviewSize(640, 480);
|
||||
this.mCamera.setParameters(params);
|
||||
this.mPreview = new CameraPreview(this, this.mCamera, this.previewCb, this.autoFocusCB);
|
||||
this.oldAutoFocusMode = this.mPreview.isOldAutoFocusMode();
|
||||
this.preview = (FrameLayout) findViewById(R.id.cameraPreview);
|
||||
this.preview.addView(this.mPreview);
|
||||
}
|
||||
@@ -1501,9 +1591,9 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
public void doAutoFocusOnTouch(View view) {
|
||||
if (this.previewing == true) {
|
||||
this.mCamera.autoFocus(MainActivity.this.autoFocusCB);
|
||||
public void doOldAutoFocusOnTouch(View view) {
|
||||
if (this.previewing && this.oldAutoFocusMode) {
|
||||
this.mCamera.autoFocus(this.autoFocusCB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1522,14 +1612,11 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
|
||||
public void setrawmode(View view) {
|
||||
if (((CheckBox) findViewById(getResources().getIdentifier("force_raw_sending_mode", "id", getPackageName()))).isChecked()) {
|
||||
this.rawmode = 1;
|
||||
} else {
|
||||
this.rawmode = 0;
|
||||
}
|
||||
CheckBox cbForceRawMode = findViewById(getResources().getIdentifier("force_raw_sending_mode", "id", getPackageName()));
|
||||
this.rawmode_forced = cbForceRawMode.isChecked();
|
||||
|
||||
Editor editor = MainActivity.this.getSharedPreferences("Pricehax", 0).edit();
|
||||
editor.putInt("rawmode", this.rawmode);
|
||||
editor.putBoolean("rawmode_forced", this.rawmode_forced);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
@@ -1626,9 +1713,12 @@ public class MainActivity extends Activity {
|
||||
return;
|
||||
}
|
||||
this.repeatModeDM = ((CheckBox) findViewById(R.id.chkrepeatdm)).isChecked();
|
||||
int timerPeriod = 4500;
|
||||
int timerPeriod = 3900;
|
||||
if (MainActivity.this.pp16Mode) timerPeriod = 3600;
|
||||
|
||||
// Shorter timer period for green led flash
|
||||
if (((RadioButton) findViewById(R.id.raddur10)).isChecked() || ((RadioButton) findViewById(R.id.raddur11)).isChecked()) {
|
||||
timerPeriod = 2000;
|
||||
timerPeriod = 1500;
|
||||
}
|
||||
if (this.repeatModeDM) {
|
||||
if (this.timer != null) {
|
||||
@@ -1686,7 +1776,10 @@ public class MainActivity extends Activity {
|
||||
byte[] hcode = new byte[18];
|
||||
int dispDuration = 0;
|
||||
int dispDMDuration = 0;
|
||||
|
||||
int nbrepeat = 400;
|
||||
if (MainActivity.this.pp16Mode) nbrepeat *= 2;
|
||||
|
||||
EditText metxtPage = (EditText) findViewById(R.id.etxtPage);
|
||||
if (metxtPage.getText() == null) {
|
||||
metxtPage.setText("0");
|
||||
@@ -1732,21 +1825,25 @@ public class MainActivity extends Activity {
|
||||
hcode[6] = (byte) 243;
|
||||
dispDMDuration = (dispDMPage[0] & 15);
|
||||
} else if (dispDuration == 10) {
|
||||
hcode[1] = (byte) ((int) (MainActivity.this.plID & 255));
|
||||
hcode[2] = (byte) ((int) (MainActivity.this.plID >> 8));
|
||||
hcode[3] = (byte) ((int) (MainActivity.this.plID >> 16));
|
||||
hcode[4] = (byte) ((int) (MainActivity.this.plID >> 24));
|
||||
//hcode[1] = (byte) ((int) (MainActivity.this.plID & 255));
|
||||
//hcode[2] = (byte) ((int) (MainActivity.this.plID >> 8));
|
||||
//hcode[3] = (byte) ((int) (MainActivity.this.plID >> 16));
|
||||
//hcode[4] = (byte) ((int) (MainActivity.this.plID >> 24));
|
||||
hcode[6] = (byte) 73;
|
||||
dispDMDuration = (dispDMPage[0] & 15);
|
||||
|
||||
nbrepeat = 150;
|
||||
if (MainActivity.this.pp16Mode) nbrepeat *= 2;
|
||||
} else if (dispDuration == 11) {
|
||||
hcode[1] = (byte) ((int) (MainActivity.this.plID & 255));
|
||||
hcode[2] = (byte) ((int) (MainActivity.this.plID >> 8));
|
||||
hcode[3] = (byte) ((int) (MainActivity.this.plID >> 16));
|
||||
hcode[4] = (byte) ((int) (MainActivity.this.plID >> 24));
|
||||
//hcode[1] = (byte) ((int) (MainActivity.this.plID & 255));
|
||||
//hcode[2] = (byte) ((int) (MainActivity.this.plID >> 8));
|
||||
//hcode[3] = (byte) ((int) (MainActivity.this.plID >> 16));
|
||||
//hcode[4] = (byte) ((int) (MainActivity.this.plID >> 24));
|
||||
hcode[6] = (byte) 201;
|
||||
dispDMDuration = (dispDMPage[0] & 15);
|
||||
|
||||
nbrepeat = 150;
|
||||
if (MainActivity.this.pp16Mode) nbrepeat *= 2;
|
||||
} else {
|
||||
hcode[6] = (byte) (((dispDMPage[0] & 15) << 3) | 1);
|
||||
}
|
||||
@@ -1757,7 +1854,7 @@ public class MainActivity extends Activity {
|
||||
byte[] FrameCRC = CRCCalc.GetCRC(hcode, 11);
|
||||
hcode[11] = FrameCRC[0];
|
||||
hcode[12] = FrameCRC[1];
|
||||
PP4C.sendPP4C(getApplicationContext(), hcode, 13, MainActivity.this.donglever, 60, audioTrack, nbrepeat);
|
||||
PPM.sendPPM(MainActivity.this, hcode, MainActivity.this.pp16Mode, 13, MainActivity.this.donglever, 60, audioTrack, nbrepeat);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@@ -1774,7 +1871,7 @@ public class MainActivity extends Activity {
|
||||
byte[] FrameCRC = CRCCalc.GetCRC(hcode, 28);
|
||||
hcode[28] = FrameCRC[0];
|
||||
hcode[29] = FrameCRC[1];
|
||||
PP4C.sendPP4C(getApplicationContext(), hcode, 30, this.donglever, 60, audioTrack, 0);
|
||||
PPM.sendPPM(MainActivity.this, hcode, MainActivity.this.pp16Mode, 30, this.donglever, 60, audioTrack, 0);
|
||||
plHexString = "";
|
||||
for (int cp = 0; cp < 30; cp++) {
|
||||
plHexString = plHexString + String.format("%02X", new Object[]{Byte.valueOf(hcode[cp])});
|
||||
@@ -1814,7 +1911,7 @@ public class MainActivity extends Activity {
|
||||
byte[] FrameCRC = CRCCalc.GetCRC(hcode, 9);
|
||||
hcode[9] = FrameCRC[0];
|
||||
hcode[10] = FrameCRC[1];
|
||||
PP4C.sendPP4C(getApplicationContext(), hcode, 11, MainActivity.this.donglever, 60, audioTrack, 100);
|
||||
PPM.sendPPM(MainActivity.this, hcode, false, 11, MainActivity.this.donglever, 60, audioTrack, 100);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
@@ -1878,7 +1975,7 @@ public class MainActivity extends Activity {
|
||||
((TextView) findViewById(R.id.label_dbgbs)).setText("Segment bitstream: " + plHexString);
|
||||
}
|
||||
});
|
||||
PP4C.sendPP4C(getApplicationContext(), hcode, 43, MainActivity.this.donglever, 15, audioTrack, 100);
|
||||
PPM.sendPPM(MainActivity.this, hcode, false, 43, MainActivity.this.donglever, 15, audioTrack, 100);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@@ -1925,6 +2022,6 @@ public class MainActivity extends Activity {
|
||||
plHexString = plHexString + String.format("%02X", new Object[]{Byte.valueOf(hcode[cp])});
|
||||
}
|
||||
((TextView) findViewById(R.id.label_dbgbs)).setText("Data update: " + plHexString);
|
||||
PP4C.sendPP4C(getApplicationContext(), hcode, 43, this.donglever, 15, audioTrack, 0);
|
||||
PPM.sendPPM(MainActivity.this, hcode, false, 43, this.donglever, 15, audioTrack, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
package org.furrtek.pricehaxbt;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.AudioTrack.OnPlaybackPositionUpdateListener;
|
||||
import android.support.v4.view.MotionEventCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PP4C {
|
||||
public class PPM {
|
||||
static AudioManager mAudioManager;
|
||||
static int origVolume;
|
||||
static double[] sample = new double[48000];
|
||||
|
||||
private static void sendData(byte[] tmp) {
|
||||
Log.d("BT SEND", "SENDING DATA...");
|
||||
private static void sendData(MainActivity mainActivity, byte[] tmp) {
|
||||
//Log.d("BT SEND", "SENDING DATA...");
|
||||
try {
|
||||
byte[] buffer = new byte[128];
|
||||
int timeout = 20;
|
||||
int timeoutRead = 2000;
|
||||
|
||||
do {
|
||||
MainActivity.outStream.write(tmp);
|
||||
do {
|
||||
} while (MainActivity.inStream.available() <= 0);
|
||||
|
||||
int res = MainActivity.inStream.read(buffer);
|
||||
for (int i = timeoutRead; i > 0; i--) {
|
||||
if (MainActivity.inStream.available() > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mainActivity.isSendImageThreadRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e22) {
|
||||
e22.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
MainActivity.inStream.read(buffer);
|
||||
timeout--;
|
||||
} while (buffer[0] == (byte) 49 && timeout >= 0);
|
||||
|
||||
@@ -36,25 +49,40 @@ public class PP4C {
|
||||
}
|
||||
}
|
||||
|
||||
static void sendPP4C(Context context, byte[] hcode, int length, int donglever, int rpt, AudioTrack audioTrack, int nbRepeatFrame) {
|
||||
static void sendPPM(MainActivity mainActivity, byte[] hcode, boolean pp16Mode, int length, int donglever, int rpt, AudioTrack audioTrack, int nbRepeatFrame) {
|
||||
double[] pcode = new double[256];
|
||||
byte[] generatedSnd = new byte[96000];
|
||||
if (donglever == 3) {
|
||||
int cp;
|
||||
byte[] btdata = new byte[58];
|
||||
btdata[0] = (byte) -86;
|
||||
byte[] btdata = new byte[60];
|
||||
btdata[0] = (byte) 170; // The first 4 bytes are only used by the dongle, they are not part of the PPM protocol
|
||||
|
||||
if (pp16Mode) {
|
||||
btdata[0] = (byte) 171;
|
||||
}
|
||||
|
||||
btdata[1] = (byte) (nbRepeatFrame >> 8);
|
||||
btdata[2] = (byte) (nbRepeatFrame & MotionEventCompat.ACTION_MASK);
|
||||
btdata[2] = (byte) (nbRepeatFrame & 0xFF);
|
||||
btdata[3] = (byte) length;
|
||||
for (cp = 0; cp < length; cp++) {
|
||||
btdata[cp + 4] = hcode[cp];
|
||||
}
|
||||
String plHexString = "";
|
||||
for (cp = 0; cp < btdata.length; cp++) {
|
||||
plHexString = plHexString + String.format("%02X", new Object[]{Byte.valueOf(btdata[cp])});
|
||||
|
||||
int basicChecksum = 0;
|
||||
for(byte i = 0; i < 4 + length; i++){
|
||||
basicChecksum += (btdata[i] & 0xFF); // " & 0xFF" is here to convert to unsigned byte because btdata[i] is a signed byte
|
||||
}
|
||||
sendData(btdata);
|
||||
Log.d("DATA", "SENT " + plHexString);
|
||||
|
||||
// Should not overflow since the max possible checksum should be less than 16384 (if all bytes are 255, 255 * 64 = 16320)
|
||||
btdata[4 + length] = (byte) (basicChecksum >> 8);
|
||||
btdata[4 + length + 1] = (byte) (basicChecksum & 0xFF);
|
||||
|
||||
//String plHexString = "";
|
||||
//for (cp = 0; cp < btdata.length; cp++) {
|
||||
// plHexString = plHexString + String.format("%02X", new Object[]{Byte.valueOf(btdata[cp])});
|
||||
//}
|
||||
sendData(mainActivity, btdata);
|
||||
//Log.d("DATA", "SENT " + plHexString);
|
||||
}
|
||||
if (donglever < 3) {
|
||||
int i;
|
||||
@@ -78,7 +106,7 @@ public class PP4C {
|
||||
pcode[(i * 4) + 3] = (double) ((hcode[i] >> 6) & 3);
|
||||
}
|
||||
}
|
||||
mAudioManager = (AudioManager) context.getSystemService("audio");
|
||||
mAudioManager = (AudioManager) mainActivity.at.getApplicationContext().getSystemService("audio");
|
||||
origVolume = mAudioManager.getStreamVolume(3);
|
||||
mAudioManager.setStreamVolume(3, (int) (((double) mAudioManager.getStreamMaxVolume(3)) * (((double) (((float) MainActivity.transmitVolume) / 100.0f)) + 0.6d)), 0);
|
||||
for (int r = 0; r < 48000; r++) {
|
||||
@@ -165,7 +193,7 @@ public class PP4C {
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
PP4C.mAudioManager.setStreamVolume(3, PP4C.origVolume, 0);
|
||||
PPM.mAudioManager.setStreamVolume(3, PPM.origVolume, 0);
|
||||
}
|
||||
|
||||
public void onPeriodicNotification(AudioTrack track) {
|
||||
@@ -112,7 +112,7 @@
|
||||
android:id="@id/raddur7"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="1h30"
|
||||
android:textColor="@color/textcolor" />
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
android:id="@id/raddur8"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Display debug infos"
|
||||
android:text="Show debug infos *"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="12.0sp" />
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:text="Hide debug infos"
|
||||
android:text="Hide debug infos *"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="12.0sp" />
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
android:id="@id/raddur10"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SmartTag LED low flash *"
|
||||
android:text="SmartTag LED weak flash **"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="12.0sp" />
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
android:layout_below="@id/chkrepeatdm"
|
||||
android:layout_alignStart="@id/radgDurations"
|
||||
android:layout_alignLeft="@id/radgDurations"
|
||||
android:text="SmartTag LED high flash *"
|
||||
android:text="SmartTag LED strong flash **"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="12.0sp" />
|
||||
</RadioGroup>
|
||||
@@ -207,14 +207,25 @@
|
||||
android:textColor="@color/textcolor" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/txtvledflash"
|
||||
android:id="@id/txtdebug"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/radgDurations"
|
||||
android:layout_alignLeft="@id/txtvPageDur"
|
||||
android:layout_alignRight="@id/button2"
|
||||
android:layout_marginTop="16.0dip"
|
||||
android:text="* Not working yet on some SmartTag ESLs"
|
||||
android:text="* DotMatrix ESLs only"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="11.0sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/txtvledflash"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtdebug"
|
||||
android:layout_alignLeft="@id/txtvPageDur"
|
||||
android:layout_alignRight="@id/button2"
|
||||
android:text="** Not working yet on some SmartTag ESLs"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="11.0sp" />
|
||||
|
||||
@@ -226,7 +237,7 @@
|
||||
android:layout_alignLeft="@id/txtvPageDur"
|
||||
android:layout_alignRight="@id/button2"
|
||||
android:layout_marginTop="6.0dip"
|
||||
android:text="Use the 'Page number' field to define the number of times the LED will flash (0 = always)"
|
||||
android:text="Use the 'Page number' field to define the number of times the LED will flash (between 1 and 9, 0 = always)"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="11.0sp" />
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
android:layout_height="192.0dip"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="8.0dip"
|
||||
android:onClick="doAutoFocusOnTouch"/>
|
||||
android:onClick="doOldAutoFocusOnTouch"/>
|
||||
|
||||
<Button
|
||||
android:id="@id/scan_button"
|
||||
android:layout_width="200.0dip"
|
||||
android:layout_width="264.0dip"
|
||||
android:layout_height="50.0dip"
|
||||
android:layout_below="@id/cameraPreview"
|
||||
android:layout_centerHorizontal="true"
|
||||
|
||||
@@ -33,14 +33,50 @@
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginLeft="8.0dip"
|
||||
android:layout_marginTop="8.0dip"
|
||||
android:layout_marginBottom="16.0dip"
|
||||
android:text="Segment bitstream: "
|
||||
android:textColor="@color/textcolor" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvppmversion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/label_dbgbs"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:text="PPM mode for DotMatrix ESLs:"
|
||||
android:textColor="@color/textcolor" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/ppmversion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/tvppmversion"
|
||||
android:layout_marginTop="8.0dip"
|
||||
android:layout_marginBottom="32.0dip"
|
||||
android:layout_centerHorizontal="true">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radpp16"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="PP16 (faster)"
|
||||
android:textColor="@color/textcolor" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radpp4c"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="PP4C"
|
||||
android:textColor="@color/textcolor" />
|
||||
</RadioGroup>
|
||||
|
||||
|
||||
<CheckBox
|
||||
android:id="@id/chk_donglever"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/label_dbgbs"
|
||||
android:layout_below="@id/ppmversion"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="25.0dip"
|
||||
android:checked="true"
|
||||
@@ -117,7 +153,7 @@
|
||||
android:id="@+id/btnPairBT"
|
||||
android:layout_below="@id/tvbtt"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:onClick="pairBTTransmitter"
|
||||
android:textColor="@color/textcolor" />
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="48.0dip"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="Version 1.2 BT (19)"
|
||||
android:text="Version 1.3 BT (21)"
|
||||
android:textColor="@color/textcolor"
|
||||
android:textSize="15.0sp" />
|
||||
|
||||
|
||||
@@ -127,4 +127,5 @@
|
||||
<item type="id" name="txtwork" />
|
||||
<item type="id" name="force_raw_sending_mode" />
|
||||
<item type="id" name="action_settings" />
|
||||
<item type="id" name="txtdebug" />
|
||||
</resources>
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Pricehax BT IR dongle (for Pricehax Version 1.1 BT (18.0))
|
||||
* furrtek 2014
|
||||
* david4599 2019
|
||||
*/
|
||||
|
||||
#define F_CPU 16000000L
|
||||
#define NOP __asm__ __volatile__ ("nop\n\t")
|
||||
|
||||
#define BT_RXPIN 10
|
||||
#define BT_TXPIN 12
|
||||
#define LEDPIN 2
|
||||
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
SoftwareSerial BT(BT_RXPIN, BT_TXPIN);
|
||||
|
||||
boolean sendframe = false;
|
||||
uint8_t b, cnt, data[54], datalength;
|
||||
uint16_t s, sym_count, r, repeat = 0, result, poly;
|
||||
|
||||
|
||||
|
||||
void CRCCalc() {
|
||||
result = 0x8408;
|
||||
poly = 0x8408;
|
||||
|
||||
for (int i = 0; i < datalength-2; i++) {
|
||||
result ^= data[i];
|
||||
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (result & 1) {
|
||||
result >>= 1;
|
||||
result ^= poly;
|
||||
}
|
||||
else {
|
||||
result >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void burst() { // Create 40us burst at around 1.25MHz
|
||||
for(int i = 0; i < 50; i++){
|
||||
PORTD ^= (1 << LEDPIN);
|
||||
NOP;
|
||||
NOP;
|
||||
PORTD ^= (1 << LEDPIN);
|
||||
NOP;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void getData() { // Get frames from Pricehax sent over Bluetooth
|
||||
if (BT.read() == 170) { // First byte sent is an ID to avoid unwanted values sent from the bluetooth module itself
|
||||
cnt = 0;
|
||||
|
||||
// Reset data array
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
_delay_ms(1);
|
||||
|
||||
while (BT.available()) {
|
||||
if (cnt == 0) { // Read header data
|
||||
// Get the number of times the frame will be transmitted
|
||||
repeat = (byte) BT.read() << 8;
|
||||
repeat |= (byte) BT.read();
|
||||
|
||||
// Get the data length of the frame
|
||||
datalength = (byte) BT.read();
|
||||
}
|
||||
|
||||
if (cnt == datalength) { // Read unneeded bytes after the end of the frame
|
||||
while (BT.available()) {
|
||||
BT.read();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
data[cnt] = (byte) BT.read(); // Get the next byte of the frame
|
||||
cnt++;
|
||||
}
|
||||
|
||||
sendframe = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void IRSend() { // Send a frame following the IR protocol
|
||||
sym_count = datalength << 2;
|
||||
|
||||
for (r = 0; r < (unsigned int) repeat; r++) {
|
||||
for (s = 0; s < sym_count; s++) {
|
||||
if ((s & 3) == 0) {
|
||||
b = data[s >> 2];
|
||||
}
|
||||
|
||||
burst();
|
||||
|
||||
switch(b & 3) {
|
||||
case 0:
|
||||
_delay_us(56);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
_delay_us(237);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_delay_us(117);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
_delay_us(178);
|
||||
break;
|
||||
}
|
||||
|
||||
b >>= 2;
|
||||
}
|
||||
|
||||
burst();
|
||||
|
||||
_delay_ms(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
DDRD |= (1 << LEDPIN); // Define led pin as output
|
||||
PORTD &= (0 << LEDPIN); // Set led pin to low state
|
||||
|
||||
BT.begin(57600);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void loop() {
|
||||
if (BT.available()) {
|
||||
getData();
|
||||
CRCCalc();
|
||||
|
||||
if (data[datalength-2] != (uint8_t) result || data[datalength-1] != (uint8_t) (result >> 8)) {
|
||||
BT.write("1");
|
||||
sendframe = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sendframe) {
|
||||
IRSend();
|
||||
|
||||
BT.write("0"); // Send an answer to Pricehax once the frame is transmitted
|
||||
sendframe = false;
|
||||
}
|
||||
|
||||
_delay_ms(1);
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* Pricehax BT IR dongle (for Pricehax Version 1.3 BT (21))
|
||||
* furrtek 2014
|
||||
* david4599 2019 - 2022
|
||||
*/
|
||||
|
||||
|
||||
#define F_CPU 16000000UL
|
||||
#define NOP __asm__ __volatile__ ("nop")
|
||||
|
||||
#define BT_RX_PIN 10
|
||||
#define BT_TX_PIN 12
|
||||
#define LED_PIN 2
|
||||
|
||||
#define PULSES_PP4C 50
|
||||
#define PULSES_PP16 26
|
||||
#define FRAME_MAX_LENGTH 58
|
||||
|
||||
// Specific to Pricehax
|
||||
#define PHX_CODE_PP4C 170
|
||||
#define PHX_CODE_PP16 171
|
||||
|
||||
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
|
||||
SoftwareSerial BT(BT_RX_PIN, BT_TX_PIN);
|
||||
|
||||
uint8_t gData[FRAME_MAX_LENGTH], gDataLength;
|
||||
uint16_t gRepeat = 0;
|
||||
boolean gModePP16 = false, gSendFrame = false, gSentOk = false;
|
||||
|
||||
uint8_t gDataTemp[FRAME_MAX_LENGTH], gDataLengthTemp;
|
||||
uint16_t gRepeatTemp = 0;
|
||||
boolean gModePP16Temp = false;
|
||||
|
||||
|
||||
// Mandatory header for PP16 protocol, not included in the frame CRC16
|
||||
const uint8_t gPP16Header[4] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x40
|
||||
};
|
||||
|
||||
// The timings offsets (especially for PP16) may need to be adjusted again if another uC is used
|
||||
const uint8_t gOffPP4C = -5;
|
||||
const uint8_t gOffPP16 = -5;
|
||||
|
||||
// PP4C symbols timings
|
||||
const uint8_t gPP4C[4] = {
|
||||
61 + gOffPP4C,
|
||||
244 + gOffPP4C,
|
||||
122 + gOffPP4C,
|
||||
183 + gOffPP4C
|
||||
};
|
||||
|
||||
// PP16 symbols timings
|
||||
const uint8_t gPP16[16] = {
|
||||
27 + gOffPP16,
|
||||
51 + gOffPP16,
|
||||
35 + gOffPP16,
|
||||
43 + gOffPP16,
|
||||
147 + gOffPP16,
|
||||
123 + gOffPP16,
|
||||
139 + gOffPP16,
|
||||
131 + gOffPP16,
|
||||
83 + gOffPP16,
|
||||
59 + gOffPP16,
|
||||
75 + gOffPP16,
|
||||
67 + gOffPP16,
|
||||
91 + gOffPP16,
|
||||
115 + gOffPP16,
|
||||
99 + gOffPP16,
|
||||
107 + gOffPP16
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Check the whole Bluetooth data with a basic checksum implementation
|
||||
boolean isPhxChecksumValid(uint16_t checksumToVerify) {
|
||||
uint8_t phxCode = PHX_CODE_PP4C, length = gDataLengthTemp, start = 0;
|
||||
uint16_t calcChecksum = 0;
|
||||
|
||||
if (gModePP16Temp) {
|
||||
// Skip the PP16 header
|
||||
phxCode = PHX_CODE_PP16;
|
||||
length = gDataLengthTemp - 4;
|
||||
start = 4;
|
||||
}
|
||||
|
||||
// Add frame parameters to the checksum
|
||||
calcChecksum += phxCode;
|
||||
calcChecksum += (uint8_t) (gRepeatTemp >> 8);
|
||||
calcChecksum += (uint8_t) (gRepeatTemp & 0xFF);
|
||||
calcChecksum += length;
|
||||
|
||||
for (uint8_t i = start; i < 4 + length; i++) {
|
||||
calcChecksum += gDataTemp[i];
|
||||
}
|
||||
|
||||
return calcChecksum == checksumToVerify;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
// Check the PPM frame CRC16 (replaced by isPhxChecksumValid())
|
||||
boolean isCRCValid() {
|
||||
uint16_t result = 0x8408, poly = 0x8408, offset = 0;
|
||||
|
||||
// Skip the PP16 header
|
||||
if (gModePP16Temp)
|
||||
offset = 4;
|
||||
|
||||
for (uint16_t i = offset; i < gDataLengthTemp - 2; i++) {
|
||||
result ^= gDataTemp[i];
|
||||
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
if (result & 1) {
|
||||
result >>= 1;
|
||||
result ^= poly;
|
||||
}
|
||||
else {
|
||||
result >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gDataTemp[gDataLengthTemp - 2] != (uint8_t) result)
|
||||
return false;
|
||||
|
||||
if (gDataTemp[gDataLengthTemp - 1] != (uint8_t) (result >> 8))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// Get a frame from Pricehax sent over Bluetooth
|
||||
boolean getFrame() {
|
||||
uint8_t phxCode, counter;
|
||||
uint16_t checksum;
|
||||
|
||||
// First byte sent is an ID to avoid unwanted values sent from the Bluetooth module itself
|
||||
phxCode = BT.read();
|
||||
if (phxCode == PHX_CODE_PP4C || phxCode == PHX_CODE_PP16) {
|
||||
counter = 0;
|
||||
|
||||
// Reset the temp data array
|
||||
memset(gDataTemp, 0, sizeof(gDataTemp));
|
||||
|
||||
while (BT.available()) {
|
||||
if (counter == 0) { // Read header data
|
||||
gModePP16Temp = false;
|
||||
|
||||
// Get the number of times the frame will be transmitted
|
||||
gRepeatTemp = (uint8_t) BT.read() << 8;
|
||||
gRepeatTemp |= (uint8_t) BT.read();
|
||||
|
||||
// Get the data length of the frame
|
||||
gDataLengthTemp = (uint8_t) BT.read();
|
||||
|
||||
if (phxCode == PHX_CODE_PP16) {
|
||||
// Add the PP16 header
|
||||
gModePP16Temp = true;
|
||||
memcpy(gDataTemp, gPP16Header, sizeof(gPP16Header));
|
||||
gDataLengthTemp += 4;
|
||||
counter += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (counter == gDataLengthTemp) {
|
||||
// Get the basic Pricehax checksum
|
||||
checksum = (uint8_t) BT.read() << 8;
|
||||
checksum |= (uint8_t) BT.read();
|
||||
|
||||
while (BT.available()) { // Read unneeded bytes after the end of the frame
|
||||
BT.read();
|
||||
}
|
||||
|
||||
return isPhxChecksumValid(checksum);
|
||||
}
|
||||
|
||||
gDataTemp[counter] = (uint8_t) BT.read(); // Get the next byte of the frame
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create a burst (40us for PP4C, 21us for PP16) at around 1.25MHz (16MHz/13 = 1.23MHz)
|
||||
// The number of assembly instructions of the whole loop once compiled is critical
|
||||
// If the function is changed, the timings will need to be adjusted by adding or removing NOPs
|
||||
void sendPPMBurst(uint8_t pulses) {
|
||||
for(uint8_t i = 0; i < pulses; i++){
|
||||
PORTD ^= (1 << LED_PIN);
|
||||
NOP;
|
||||
NOP;
|
||||
PORTD ^= (1 << LED_PIN);
|
||||
NOP;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Send a frame using the PP4C protocol
|
||||
void sendPP4CFrame() {
|
||||
uint8_t currentByte;
|
||||
uint16_t repeat, symNumber, symCount;
|
||||
|
||||
// Get the number of PP4C symbols the frame contains
|
||||
symCount = gDataLength << 2;
|
||||
|
||||
for (repeat = 0; repeat < gRepeat; repeat++) {
|
||||
cli(); // Stop all interrupts to avoid added delay that will interfere with the PPM signal
|
||||
for (symNumber = 0; symNumber < symCount; symNumber++) {
|
||||
|
||||
// Switch to the next byte once the 4 2-bit symbols of the current byte are transmitted
|
||||
if ((symNumber & 3) == 0) {
|
||||
currentByte = gData[symNumber >> 2];
|
||||
}
|
||||
|
||||
sendPPMBurst(PULSES_PP4C);
|
||||
|
||||
switch(currentByte & 3) {
|
||||
case 0:
|
||||
_delay_us(gPP4C[0]);
|
||||
break;
|
||||
case 1:
|
||||
_delay_us(gPP4C[1]);
|
||||
break;
|
||||
case 2:
|
||||
_delay_us(gPP4C[2]);
|
||||
break;
|
||||
case 3:
|
||||
_delay_us(gPP4C[3]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Switch to the next symbol
|
||||
currentByte >>= 2;
|
||||
}
|
||||
|
||||
sendPPMBurst(PULSES_PP4C);
|
||||
sei(); // Allow interrupts
|
||||
|
||||
_delay_ms(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Send a frame using the PP16 protocol
|
||||
void sendPP16Frame() {
|
||||
uint8_t currentByte;
|
||||
uint16_t repeat, symNumber, symCount;
|
||||
|
||||
// Get the number of PP16 symbols the frame contains
|
||||
symCount = gDataLength << 1;
|
||||
|
||||
for (repeat = 0; repeat < gRepeat; repeat++) {
|
||||
cli(); // Stop all interrupts to avoid added delay that will interfere with the PPM signal
|
||||
for (symNumber = 0; symNumber < symCount; symNumber++) {
|
||||
|
||||
// Switch to the next byte once the 2 4-bit symbols of the current byte are transmitted
|
||||
if ((symNumber & 1) == 0) {
|
||||
currentByte = gData[symNumber >> 1];
|
||||
}
|
||||
|
||||
sendPPMBurst(PULSES_PP16);
|
||||
|
||||
switch(currentByte & 15) {
|
||||
case 0:
|
||||
_delay_us(gPP16[0]);
|
||||
break;
|
||||
case 1:
|
||||
_delay_us(gPP16[1]);
|
||||
break;
|
||||
case 2:
|
||||
_delay_us(gPP16[2]);
|
||||
break;
|
||||
case 3:
|
||||
_delay_us(gPP16[3]);
|
||||
break;
|
||||
case 4:
|
||||
_delay_us(gPP16[4]);
|
||||
break;
|
||||
case 5:
|
||||
_delay_us(gPP16[5]);
|
||||
break;
|
||||
case 6:
|
||||
_delay_us(gPP16[6]);
|
||||
break;
|
||||
case 7:
|
||||
_delay_us(gPP16[7]);
|
||||
break;
|
||||
case 8:
|
||||
_delay_us(gPP16[8]);
|
||||
break;
|
||||
case 9:
|
||||
_delay_us(gPP16[9]);
|
||||
break;
|
||||
case 10:
|
||||
_delay_us(gPP16[10]);
|
||||
break;
|
||||
case 11:
|
||||
_delay_us(gPP16[11]);
|
||||
break;
|
||||
case 12:
|
||||
_delay_us(gPP16[12]);
|
||||
break;
|
||||
case 13:
|
||||
_delay_us(gPP16[13]);
|
||||
break;
|
||||
case 14:
|
||||
_delay_us(gPP16[14]);
|
||||
break;
|
||||
case 15:
|
||||
_delay_us(gPP16[15]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Switch to the next symbol
|
||||
currentByte >>= 4;
|
||||
}
|
||||
|
||||
sendPPMBurst(PULSES_PP16);
|
||||
sei(); // Allow interrupts
|
||||
|
||||
_delay_ms(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
DDRD |= (1 << LED_PIN); // Define led pin as output
|
||||
PORTD &= (0 << LED_PIN); // Set led pin to low state
|
||||
|
||||
cli(); // Stop all interrupts
|
||||
// Set bit TOIE0 in the TIMSK0 register to zero to disable timer0 interrupt
|
||||
// This was causing randomly and unwanted 5-6us added delay to _delay_us() due to timer overflow
|
||||
TIMSK0 &= ~(1 << TOIE0);
|
||||
sei(); // Allow interrupts
|
||||
|
||||
BT.begin(115200);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void loop() {
|
||||
if (BT.available()) {
|
||||
if (getFrame()) {
|
||||
// Set new PPM frame parameters
|
||||
memcpy(gData, gDataTemp, sizeof(gDataTemp));
|
||||
gDataLength = gDataLengthTemp;
|
||||
gRepeat = gRepeatTemp;
|
||||
gModePP16 = gModePP16Temp;
|
||||
gSendFrame = true;
|
||||
gSentOk = false;
|
||||
}
|
||||
else {
|
||||
// Read all garbage data the Bluetooth module itself may send
|
||||
while (BT.available()) {
|
||||
BT.read();
|
||||
}
|
||||
|
||||
BT.write("1"); // Send an error message to Pricehax if the frame is not properly transmitted
|
||||
|
||||
gSendFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (gSendFrame) {
|
||||
if (gModePP16)
|
||||
sendPP16Frame();
|
||||
else
|
||||
sendPP4CFrame();
|
||||
|
||||
if (!gSentOk) {
|
||||
BT.write("0"); // Send an OK message to Pricehax once the frame is transmitted
|
||||
gSentOk = true;
|
||||
gSendFrame = false;
|
||||
}
|
||||
|
||||
gDataLengthTemp = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user