Skip to content

Commit 345d3a7

Browse files
committed
ANDROID: added experimental usb communication support
1 parent 3ad7566 commit 345d3a7

File tree

7 files changed

+358
-221
lines changed

7 files changed

+358
-221
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import android
2+
3+
'
4+
' request usage: endpoint [, data, apiKey]
5+
' data, apikey: uses POST to send data, apiKey applied as bearer authorisation
6+
'
7+
const endPoint = "http://ip-api.com/json"
8+
print android.request(endPoint)
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import android
2+
3+
usb = android.usbConnect()
4+
5+
while 1
6+
usb.send("hello");
7+
print usb.receive()
8+
delay 1000
9+
wend
10+

src/platform/android/app/src/main/java/net/sourceforge/smallbasic/MainActivity.java

+11-4
Original file line numberDiff line numberDiff line change
@@ -651,11 +651,18 @@ public void speak(final byte[] textBytes) {
651651
}
652652
}
653653

654+
public boolean usbClose() {
655+
if (_usbConnection != null) {
656+
_usbConnection.close();
657+
}
658+
return true;
659+
}
660+
654661
public String usbConnect(int vendorId) {
655662
String result;
656663
try {
657664
_usbConnection = new UsbConnection(getApplicationContext(), vendorId);
658-
result = "connected";
665+
result = "[connected]";
659666
} catch (IOException e) {
660667
result = e.toString();
661668
}
@@ -667,14 +674,14 @@ public String usbReceive() {
667674
if (_usbConnection != null) {
668675
result = _usbConnection.receive();
669676
} else {
670-
result = "not connected";
677+
result = "";
671678
}
672679
return result;
673680
}
674681

675-
public void usbSend(final byte[] bytes) {
682+
public void usbSend(final byte[] data) {
676683
if (_usbConnection != null) {
677-
_usbConnection.send(bytes);
684+
_usbConnection.send(data);
678685
}
679686
}
680687

src/platform/android/jni/Android.mk

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ LOCAL_SRC_FILES := main.cpp \
3838
display.cpp \
3939
runtime.cpp \
4040
audio.cpp \
41+
module.cpp \
4142
../../../ui/screen.cpp \
4243
../../../ui/ansiwidget.cpp \
4344
../../../ui/window.cpp \

src/platform/android/jni/module.cpp

+324
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
// This file is part of SmallBASIC
2+
//
3+
// Copyright(C) 2001-2025 Chris Warren-Smith.
4+
//
5+
// This program is distributed under the terms of the GPL v2.0 or later
6+
// Download the GNU Public License (GPL) from www.gnu.org
7+
//
8+
9+
#include "config.h"
10+
11+
#include "platform/android/jni/runtime.h"
12+
#include "languages/messages.en.h"
13+
14+
#define USB_OBJECT_ID 1001
15+
#define USB_CLASS_ID 1002
16+
17+
extern Runtime *runtime;
18+
19+
static bool is_object(var_p_t var) {
20+
return var != nullptr && v_is_type(var, V_MAP) && (var->v.m.id == USB_OBJECT_ID);
21+
}
22+
23+
static bool usb_close() {
24+
return runtime->getBoolean("usbClose");
25+
}
26+
27+
static int cmd_usb_close(var_s *self, int argc, slib_par_t *args, var_s *retval) {
28+
int result;
29+
if (argc != 0 || !is_object(self)) {
30+
v_setstr(retval, ERR_PARAM);
31+
result = 0;
32+
} else {
33+
usb_close();
34+
result = 1;
35+
}
36+
return result;
37+
}
38+
39+
static int cmd_usb_receive(var_s *self, int argc, slib_par_t *args, var_s *retval) {
40+
int result;
41+
if (argc != 0 || !is_object(self)) {
42+
v_setstr(retval, ERR_PARAM);
43+
result = 0;
44+
} else {
45+
v_setstr(retval, runtime->getString("usbReceive"));
46+
result = 1;
47+
}
48+
return result;
49+
}
50+
51+
static int cmd_usb_send(var_s *self, int argc, slib_par_t *args, var_s *retval) {
52+
int result;
53+
if (argc != 1 || !is_object(self) || !v_is_type(args[0].var_p, V_STR)) {
54+
v_setstr(retval, ERR_PARAM);
55+
result = 0;
56+
} else {
57+
runtime->setString("usbSend", v_getstr(args[0].var_p));
58+
result = 1;
59+
}
60+
return result;
61+
}
62+
63+
static int cmd_usb_connect(int argc, slib_par_t *args, var_t *retval) {
64+
int result = 0;
65+
66+
if (argc != 1 || !v_is_type(args[0].var_p, V_INT)) {
67+
v_setstr(retval, "Expected: vendorId");
68+
} else {
69+
runtime->getOutput()->redraw();
70+
android_app *app = runtime->getApp();
71+
72+
JNIEnv *env;
73+
app->activity->vm->AttachCurrentThread(&env, nullptr);
74+
int vendorId = v_getint(args[0].var_p);
75+
jclass clazz = env->GetObjectClass(app->activity->clazz);
76+
const char *signature = "(I)Ljava/lang/String;";
77+
jmethodID methodId = env->GetMethodID(clazz, "usbConnect", signature);
78+
auto jstr = (jstring)env->CallObjectMethod(app->activity->clazz, methodId, vendorId);
79+
const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
80+
81+
if (strncmp(str, "[connected]", 11) == 0) {
82+
map_init(retval);
83+
retval->v.m.id = USB_OBJECT_ID;
84+
retval->v.m.cls_id = USB_CLASS_ID;
85+
v_create_callback(retval, "close", cmd_usb_close);
86+
v_create_callback(retval, "receive", cmd_usb_receive);
87+
v_create_callback(retval, "send", cmd_usb_send);
88+
result = 1;
89+
} else {
90+
v_setstr(retval, str);
91+
result = 0;
92+
}
93+
94+
env->ReleaseStringUTFChars(jstr, str);
95+
env->DeleteLocalRef(jstr);
96+
env->DeleteLocalRef(clazz);
97+
app->activity->vm->DetachCurrentThread();
98+
}
99+
return result;
100+
}
101+
102+
static int cmd_request(int argc, slib_par_t *args, var_t *retval) {
103+
int result = 0;
104+
if (argc != 1 && argc != 3) {
105+
v_setstr(retval, "Expected 1 or 3 arguments");
106+
} else if (!v_is_type(args[0].var_p, V_STR)) {
107+
v_setstr(retval, "Invalid endPoint");
108+
} else if (argc == 3 && !v_is_type(args[1].var_p, V_STR) && !v_is_type(args[1].var_p, V_MAP)) {
109+
v_setstr(retval, "Invalid postData");
110+
} else if (argc == 3 && !v_is_type(args[2].var_p, V_STR)) {
111+
v_setstr(retval, "Invalid apiKey");
112+
} else {
113+
runtime->getOutput()->redraw();
114+
android_app *app = runtime->getApp();
115+
116+
JNIEnv *env;
117+
app->activity->vm->AttachCurrentThread(&env, nullptr);
118+
auto endPoint = env->NewStringUTF(v_getstr(args[0].var_p));
119+
auto data = env->NewStringUTF(argc < 2 ? "" : v_getstr(args[1].var_p));
120+
auto apiKey = env->NewStringUTF(argc < 3 ? "" : v_getstr(args[2].var_p));
121+
122+
jclass clazz = env->GetObjectClass(app->activity->clazz);
123+
const char *signature = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
124+
jmethodID methodId = env->GetMethodID(clazz, "request", signature);
125+
auto jstr = (jstring)env->CallObjectMethod(app->activity->clazz, methodId, endPoint, data, apiKey);
126+
const char *str = env->GetStringUTFChars(jstr, JNI_FALSE);
127+
128+
v_setstr(retval, str);
129+
result = strncmp(str, "error: [", 8) == 0 ? 0 : 1;
130+
131+
env->ReleaseStringUTFChars(jstr, str);
132+
env->DeleteLocalRef(jstr);
133+
env->DeleteLocalRef(clazz);
134+
env->DeleteLocalRef(endPoint);
135+
env->DeleteLocalRef(data);
136+
env->DeleteLocalRef(apiKey);
137+
app->activity->vm->DetachCurrentThread();
138+
}
139+
return result;
140+
}
141+
142+
static int gps_on(int argc, slib_par_t *args, var_t *retval) {
143+
runtime->getBoolean("requestLocationUpdates");
144+
return 1;
145+
}
146+
147+
static int gps_off(int argc, slib_par_t *args, var_t *retval) {
148+
runtime->getBoolean("removeLocationUpdates");
149+
return 1;
150+
}
151+
152+
static int sensor_on(int argc, slib_par_t *args, var_t *retval) {
153+
int result = 0;
154+
if (argc == 1) {
155+
int sensor = v_getint(args[0].var_p);
156+
if (sensor >= 0 && sensor < MAX_SENSORS) {
157+
result = runtime->enableSensor(sensor);
158+
}
159+
}
160+
if (!result) {
161+
v_setstr(retval, "sensor not active");
162+
}
163+
return result;
164+
}
165+
166+
static int sensor_off(int argc, slib_par_t *args, var_t *retval) {
167+
runtime->disableSensor();
168+
return 1;
169+
}
170+
171+
static int tts_speak(int argc, slib_par_t *args, var_t *retval) {
172+
int result;
173+
if (opt_mute_audio) {
174+
result = 1;
175+
} else if (argc == 1 && v_is_type(args[0].var_p, V_STR)) {
176+
runtime->speak(v_getstr(args[0].var_p));
177+
result = 1;
178+
} else {
179+
v_setstr(retval, ERR_PARAM);
180+
result = 0;
181+
}
182+
return result;
183+
}
184+
185+
static int tts_pitch(int argc, slib_par_t *args, var_t *retval) {
186+
int result;
187+
if (argc == 1 && (v_is_type(args[0].var_p, V_NUM) ||
188+
v_is_type(args[0].var_p, V_INT))) {
189+
runtime->setFloat("setTtsPitch", v_getreal(args[0].var_p));
190+
result = 1;
191+
} else {
192+
v_setstr(retval, ERR_PARAM);
193+
result = 0;
194+
}
195+
return result;
196+
}
197+
198+
static int tts_speech_rate(int argc, slib_par_t *args, var_t *retval) {
199+
int result;
200+
if (argc == 1 && (v_is_type(args[0].var_p, V_NUM) ||
201+
v_is_type(args[0].var_p, V_INT))) {
202+
runtime->setFloat("setTtsRate", v_getreal(args[0].var_p));
203+
result = 1;
204+
} else {
205+
v_setstr(retval, ERR_PARAM);
206+
result = 0;
207+
}
208+
return result;
209+
}
210+
211+
static int tts_lang(int argc, slib_par_t *args, var_t *retval) {
212+
int result;
213+
if (argc == 1 && v_is_type(args[0].var_p, V_STR)) {
214+
runtime->setString("setTtsLocale", v_getstr(args[0].var_p));
215+
result = 1;
216+
} else {
217+
v_setstr(retval, ERR_PARAM);
218+
result = 0;
219+
}
220+
return result;
221+
}
222+
223+
static int tts_off(int argc, slib_par_t *args, var_t *retval) {
224+
runtime->getBoolean("setTtsQuiet");
225+
return 1;
226+
}
227+
228+
struct LibProcs {
229+
const char *name;
230+
int (*command)(int, slib_par_t *, var_t *retval);
231+
} lib_procs[] = {
232+
{"GPS_ON", gps_on},
233+
{"GPS_OFF", gps_off},
234+
{"SENSOR_ON", sensor_on},
235+
{"SENSOR_OFF", sensor_off},
236+
{"TTS_PITCH", tts_pitch},
237+
{"TTS_RATE", tts_speech_rate},
238+
{"TTS_LANG", tts_lang},
239+
{"TTS_OFF", tts_off},
240+
{"SPEAK", tts_speak}
241+
};
242+
243+
const char *lib_funcs[] = {
244+
"LOCATION",
245+
"SENSOR",
246+
"REQUEST",
247+
"USBCONNECT"
248+
};
249+
250+
int sblib_proc_count(void) {
251+
return (sizeof(lib_procs) / sizeof(lib_procs[0]));
252+
}
253+
254+
int sblib_proc_getname(int index, char *proc_name) {
255+
int result;
256+
if (index < sblib_proc_count()) {
257+
strcpy(proc_name, lib_procs[index].name);
258+
result = 1;
259+
} else {
260+
result = 0;
261+
}
262+
return result;
263+
}
264+
265+
int sblib_proc_exec(int index, int argc, slib_par_t *args, var_t *retval) {
266+
int result;
267+
if (index < sblib_proc_count()) {
268+
result = lib_procs[index].command(argc, args, retval);
269+
} else {
270+
result = 0;
271+
}
272+
return result;
273+
}
274+
275+
int sblib_func_count(void) {
276+
return (sizeof(lib_funcs) / sizeof(lib_funcs[0]));
277+
}
278+
279+
int sblib_func_getname(int index, char *proc_name) {
280+
int result;
281+
if (index < sblib_func_count()) {
282+
strcpy(proc_name, lib_funcs[index]);
283+
result = 1;
284+
} else {
285+
result = 0;
286+
}
287+
return result;
288+
}
289+
290+
int sblib_func_exec(int index, int argc, slib_par_t *args, var_t *retval) {
291+
int result;
292+
switch (index) {
293+
case 0:
294+
runtime->setLocationData(retval);
295+
result = 1;
296+
break;
297+
case 1:
298+
runtime->setSensorData(retval);
299+
result = 1;
300+
break;
301+
case 2:
302+
result = cmd_request(argc, args, retval);
303+
break;
304+
case 3:
305+
result = cmd_usb_connect(argc, args, retval);
306+
break;
307+
default:
308+
result = 0;
309+
break;
310+
}
311+
return result;
312+
}
313+
314+
void sblib_free(int cls_id, int id) {
315+
if (cls_id == USB_CLASS_ID && id == USB_OBJECT_ID) {
316+
// when the 'usb' variable falls out of scope
317+
usb_close();
318+
}
319+
}
320+
321+
void sblib_close() {
322+
runtime->getBoolean("closeLibHandlers");
323+
runtime->disableSensor();
324+
}

0 commit comments

Comments
 (0)