Skip to content

Commit 2b29acb

Browse files
committed
add atmega328 support
1 parent e4735cd commit 2b29acb

File tree

8 files changed

+183
-3
lines changed

8 files changed

+183
-3
lines changed

atmega328p.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# RPi PINOUTS
2+
# MOSI -> GPIO10
3+
# MISO -> GPIO9
4+
# SCK -> GPIO11
5+
# CE1 -> GPIO7
6+
# CE1 -> GPIO8
7+
8+
# get the GPIO Library and SPI Library
9+
import spidev
10+
import time
11+
12+
BAUDRATE_MAX = 250000
13+
BAUDRATE = 10000
14+
15+
START = 0xff
16+
CMD_RESET = 0x00
17+
CMD_SET_DATA = 0x01
18+
CMD_GET_DATA = 0x02
19+
20+
ADDR_AI_FIRST = 0x00
21+
ADDR_AI_LAST = 0x01
22+
ADDR_DI_FIRST = 0x02
23+
ADDR_DI_LAST = 0x05
24+
ADDR_DO_FIRST = 0x00
25+
ADDR_DO_LAST = 0x0a
26+
27+
class ATMega328():
28+
29+
_instance = None
30+
31+
@classmethod
32+
def get_instance(cls):
33+
if cls._instance is None:
34+
cls._instance = ATMega328()
35+
return cls._instance
36+
37+
def __init__(self):
38+
#Initialze the SPI
39+
self.spi = spidev.SpiDev()
40+
self.spi.open(0,0)
41+
self.spi.max_speed_hz = BAUDRATE_MAX
42+
43+
def close(self):
44+
self.spi.close()
45+
46+
def digitalWrite(self, addr, value):
47+
resp = self.spi.xfer([START, CMD_SET_DATA, addr, value, 0], BAUDRATE)
48+
49+
def digitalRead(self, addr):
50+
resp = self.spi.xfer([START, CMD_GET_DATA, addr, 0, 0], BAUDRATE)
51+
return resp[3]
52+
53+
def analogRead(self, addr):
54+
resp = self.spi.xfer([START, CMD_GET_DATA, addr, 0, 0], BAUDRATE)
55+
return resp[3]
56+
57+
def get_input(self, addr):
58+
if addr >= ADDR_AI_FIRST and addr <= ADDR_AI_LAST:
59+
return self.analogRead(addr)
60+
elif addr >= ADDR_DI_FIRST and addr <= ADDR_DI_LAST:
61+
return self.digitalRead(addr)
62+
63+
def set_output(self, addr, value):
64+
if addr >= ADDR_DO_FIRST and addr <= ADDR_DO_LAST:
65+
self.digitalWrite(addr, value)

data/program_test_io_ext.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name": "test_io_ext", "dom_code": "<xml xmlns=\"https://developers.google.com/blockly/xml\"><variables><variable id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</variable></variables><block type=\"controls_whileUntil\" id=\"Dl?:_j0SBuhadu{w@UF.\" x=\"228\" y=\"37\"><field name=\"MODE\">WHILE</field><value name=\"BOOL\"><block type=\"logic_boolean\" id=\"pJCxtUhR_b%x:dTqn9md\"><field name=\"BOOL\">TRUE</field></block></value><statement name=\"DO\"><block type=\"variables_set\" id=\"+v#-(`(Qm]QpW/]^kj=k\"><field name=\"VAR\" id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</field><value name=\"VALUE\"><block type=\"coderbot_atmega_get_input\" id=\"ai)*,59_NZc9svsP|JM:\"><field name=\"INPUT\">0</field></block></value><next><block type=\"text_print\" id=\"QtWQNQO[-l[BZ~e`Qmh$\"><value name=\"TEXT\"><block type=\"text_join\" id=\"1Ukw}Lu=x]S?%jKwDJJV\"><mutation items=\"2\"></mutation><value name=\"ADD0\"><block type=\"text\" id=\"Y9wE(=ZJTe,Y=7yA$1+2\"><field name=\"TEXT\">Analog Input 1: </field></block></value><value name=\"ADD1\"><block type=\"variables_get\" id=\"4QrPsC2-~_VL]6GoH_*O\"><field name=\"VAR\" id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</field></block></value></block></value><next><block type=\"controls_if\" id=\"le3Rmn/8;oI#ps$J,7la\"><mutation else=\"1\"></mutation><value name=\"IF0\"><block type=\"logic_compare\" id=\"pm_-a@W30t?pU$DT9!sX\"><field name=\"OP\">GT</field><value name=\"A\"><block type=\"variables_get\" id=\"IrfeoxR,w1!`9%NW+elg\"><field name=\"VAR\" id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</field></block></value><value name=\"B\"><block type=\"math_number\" id=\"R4b=e-`S!g*H6T7B1nel\"><field name=\"NUM\">100</field></block></value></block></value><statement name=\"DO0\"><block type=\"coderbot_atmega_set_output\" id=\"yvJ,i1lZv!^Q~}#Co+-i\"><field name=\"OUTPUT\">0</field><value name=\"VALUE\"><block type=\"logic_boolean\" id=\"Peb+]}4!:tv}p4J_B9]z\"><field name=\"BOOL\">TRUE</field></block></value></block></statement><statement name=\"ELSE\"><block type=\"coderbot_atmega_set_output\" id=\"z/@VGWnrW3D(x,d1SZ^y\"><field name=\"OUTPUT\">0</field><value name=\"VALUE\"><block type=\"logic_boolean\" id=\"$yU!z^ZFMBh!(m7s,9*:\"><field name=\"BOOL\">FALSE</field></block></value></block></statement></block></next></block></next></block></statement></block></xml>", "code": "Analog_Input_1 = None\n\n\nwhile True:\n get_prog_eng().check_end()\n Analog_Input_1 = get_atmega().get_input(0)\n get_cam().set_text('Analog Input 1: ' + str(Analog_Input_1))\n if Analog_Input_1 > 100:\n get_atmega().set_output(0, True)\n else:\n get_atmega().set_output(0, False)\n", "default": false}

data/programs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"_default": {"1": {"name": "test_find_code", "filename": "./data/defaults/programs/program_test_find_code.json", "default": "True"}, "2": {"name": "demo_color_seeker", "filename": "./data/defaults/programs/program_demo_color_seeker.json", "default": "True"}, "3": {"name": "demo_sound_clap_control", "filename": "./data/defaults/programs/program_demo_sound_clap_control.json", "default": "True"}, "4": {"name": "test_find_path_ahead", "filename": "./data/defaults/programs/program_test_find_path_ahead.json", "default": "True"}, "5": {"name": "test_sound_hear", "filename": "./data/defaults/programs/program_test_sound_hear.json", "default": "True"}, "6": {"name": "test_find_color", "filename": "./data/defaults/programs/program_test_find_color.json", "default": "True"}, "7": {"name": "test_cnn_classifier", "filename": "./data/defaults/programs/program_test_cnn_classifier.json", "default": "True"}, "8": {"name": "test_sound_rec", "filename": "./data/defaults/programs/program_test_sound_rec.json", "default": "True"}, "9": {"name": "test_find_face", "filename": "./data/defaults/programs/program_test_find_face.json", "default": "True"}, "10": {"name": "demo_obstacle_avoidance", "filename": "./data/defaults/programs/program_demo_obstacle_avoidance.json", "default": "True"}, "11": {"name": "test_sonars", "filename": "./data/defaults/programs/program_test_sonars.json", "default": "True"}, "12": {"name": "test_img_average", "filename": "./data/defaults/programs/program_test_img_average.json", "default": "True"}, "13": {"name": "demo_ar_tags", "filename": "./data/defaults/programs/program_demo_ar_tags.json", "default": "True"}, "14": {"name": "test_cnn_object_detect", "filename": "./data/defaults/programs/program_test_cnn_object_detect.json", "default": "True"}, "15": {"name": "demo_line_follower", "filename": "./data/defaults/programs/program_demo_line_follower.json", "default": "True"}}}
1+
{"_default": {"1": {"name": "test_find_code", "filename": "./data/defaults/programs/program_test_find_code.json", "default": "True"}, "2": {"name": "demo_color_seeker", "filename": "./data/defaults/programs/program_demo_color_seeker.json", "default": "True"}, "3": {"name": "demo_sound_clap_control", "filename": "./data/defaults/programs/program_demo_sound_clap_control.json", "default": "True"}, "4": {"name": "test_find_path_ahead", "filename": "./data/defaults/programs/program_test_find_path_ahead.json", "default": "True"}, "5": {"name": "test_sound_hear", "filename": "./data/defaults/programs/program_test_sound_hear.json", "default": "True"}, "6": {"name": "test_find_color", "filename": "./data/defaults/programs/program_test_find_color.json", "default": "True"}, "7": {"name": "test_cnn_classifier", "filename": "./data/defaults/programs/program_test_cnn_classifier.json", "default": "True"}, "8": {"name": "test_sound_rec", "filename": "./data/defaults/programs/program_test_sound_rec.json", "default": "True"}, "9": {"name": "test_find_face", "filename": "./data/defaults/programs/program_test_find_face.json", "default": "True"}, "10": {"name": "demo_obstacle_avoidance", "filename": "./data/defaults/programs/program_demo_obstacle_avoidance.json", "default": "True"}, "11": {"name": "test_sonars", "filename": "./data/defaults/programs/program_test_sonars.json", "default": "True"}, "12": {"name": "test_img_average", "filename": "./data/defaults/programs/program_test_img_average.json", "default": "True"}, "13": {"name": "demo_ar_tags", "filename": "./data/defaults/programs/program_demo_ar_tags.json", "default": "True"}, "14": {"name": "test_cnn_object_detect", "filename": "./data/defaults/programs/program_test_cnn_object_detect.json", "default": "True"}, "15": {"name": "demo_line_follower", "filename": "./data/defaults/programs/program_demo_line_follower.json", "default": "True"}, "16": {"name": "test_io_ext", "dom_code": "<xml xmlns=\"https://developers.google.com/blockly/xml\"><variables><variable id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</variable></variables><block type=\"controls_whileUntil\" id=\"Dl?:_j0SBuhadu{w@UF.\" x=\"228\" y=\"37\"><field name=\"MODE\">WHILE</field><value name=\"BOOL\"><block type=\"logic_boolean\" id=\"pJCxtUhR_b%x:dTqn9md\"><field name=\"BOOL\">TRUE</field></block></value><statement name=\"DO\"><block type=\"variables_set\" id=\"+v#-(`(Qm]QpW/]^kj=k\"><field name=\"VAR\" id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</field><value name=\"VALUE\"><block type=\"coderbot_atmega_get_input\" id=\"ai)*,59_NZc9svsP|JM:\"><field name=\"INPUT\">0</field></block></value><next><block type=\"text_print\" id=\"QtWQNQO[-l[BZ~e`Qmh$\"><value name=\"TEXT\"><block type=\"text_join\" id=\"1Ukw}Lu=x]S?%jKwDJJV\"><mutation items=\"2\"></mutation><value name=\"ADD0\"><block type=\"text\" id=\"Y9wE(=ZJTe,Y=7yA$1+2\"><field name=\"TEXT\">Analog Input 1: </field></block></value><value name=\"ADD1\"><block type=\"variables_get\" id=\"4QrPsC2-~_VL]6GoH_*O\"><field name=\"VAR\" id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</field></block></value></block></value><next><block type=\"controls_if\" id=\"le3Rmn/8;oI#ps$J,7la\"><mutation else=\"1\"></mutation><value name=\"IF0\"><block type=\"logic_compare\" id=\"pm_-a@W30t?pU$DT9!sX\"><field name=\"OP\">GT</field><value name=\"A\"><block type=\"variables_get\" id=\"IrfeoxR,w1!`9%NW+elg\"><field name=\"VAR\" id=\"L_e%=^0/~b[gZI1*^ZSd\">Analog_Input_1</field></block></value><value name=\"B\"><block type=\"math_number\" id=\"R4b=e-`S!g*H6T7B1nel\"><field name=\"NUM\">100</field></block></value></block></value><statement name=\"DO0\"><block type=\"coderbot_atmega_set_output\" id=\"yvJ,i1lZv!^Q~}#Co+-i\"><field name=\"OUTPUT\">0</field><value name=\"VALUE\"><block type=\"logic_boolean\" id=\"Peb+]}4!:tv}p4J_B9]z\"><field name=\"BOOL\">TRUE</field></block></value></block></statement><statement name=\"ELSE\"><block type=\"coderbot_atmega_set_output\" id=\"z/@VGWnrW3D(x,d1SZ^y\"><field name=\"OUTPUT\">0</field><value name=\"VALUE\"><block type=\"logic_boolean\" id=\"$yU!z^ZFMBh!(m7s,9*:\"><field name=\"BOOL\">FALSE</field></block></value></block></statement></block></next></block></next></block></statement></block></xml>", "code": "Analog_Input_1 = None\n\n\nwhile True:\n get_prog_eng().check_end()\n Analog_Input_1 = get_atmega().get_input(0)\n get_cam().set_text('Analog Input 1: ' + str(Analog_Input_1))\n if Analog_Input_1 > 100:\n get_atmega().set_output(0, True)\n else:\n get_atmega().set_output(0, False)\n", "default": false, "filename": "./data/program_test_io_ext.json"}}}

program.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@
3131
import config
3232
import audio
3333
import event
34-
35-
34+
import atmega328p
3635

3736
PROGRAM_PATH = "./data/"
3837
PROGRAM_PREFIX = "program_"
@@ -56,6 +55,9 @@ def get_prog_eng():
5655
def get_event():
5756
return event.EventManager.get_instance()
5857

58+
def get_atmega():
59+
return atmega328p.ATMega328.get_instance()
60+
5961
class ProgramEngine:
6062

6163
# pylint: disable=exec-used

static/js/blockly/blocks.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,3 +1206,71 @@ Blockly.Python['coderbot_mpu_get_temp'] = function(block) {
12061206
var code = 'get_bot().get_mpu_temp()';
12071207
return [code, Blockly.Python.ORDER_ATOMIC];
12081208
};
1209+
1210+
Blockly.Blocks['coderbot_atmega_get_input'] = {
1211+
/**
1212+
* Block for get_input function.
1213+
* @this Blockly.Block
1214+
*/
1215+
init: function() {
1216+
this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL);
1217+
this.setColour(240);
1218+
this.appendDummyInput()
1219+
.appendField(Blockly.Msg.CODERBOT_ATMEGA_READ)
1220+
.appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_ATMEGA_AI_1, "0"],
1221+
[Blockly.Msg.CODERBOT_ATMEGA_AI_2, "1"],
1222+
[Blockly.Msg.CODERBOT_ATMEGA_DI_3, "2"],
1223+
[Blockly.Msg.CODERBOT_ATMEGA_DI_4, "3"],
1224+
[Blockly.Msg.CODERBOT_ATMEGA_DI_5, "4"],
1225+
[Blockly.Msg.CODERBOT_ATMEGA_DI_6, "5"],]), 'INPUT');
1226+
this.setOutput(true, 'Number');
1227+
this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP);
1228+
}
1229+
};
1230+
1231+
Blockly.Python['coderbot_atmega_get_input'] = function(block) {
1232+
// input index: 0, 1 are Analogs, 2..5 are Digital
1233+
var input = block.getFieldValue('INPUT');
1234+
var code = 'get_atmega().get_input(' + input + ')';
1235+
return [code, Blockly.Python.ORDER_ATOMIC];
1236+
};
1237+
1238+
Blockly.Blocks['coderbot_atmega_set_output'] = {
1239+
/**
1240+
* Block for set_output function.
1241+
* @this Blockly.Block
1242+
*/
1243+
init: function() {
1244+
this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL);
1245+
this.setColour(240);
1246+
this.appendDummyInput()
1247+
.appendField(Blockly.Msg.CODERBOT_ATMEGA_WRITE)
1248+
.appendField(new Blockly.FieldDropdown([[Blockly.Msg.CODERBOT_ATMEGA_DO_1, "0"],
1249+
[Blockly.Msg.CODERBOT_ATMEGA_DO_2, "1"],
1250+
[Blockly.Msg.CODERBOT_ATMEGA_DO_3, "2"],
1251+
[Blockly.Msg.CODERBOT_ATMEGA_DO_4, "3"],
1252+
[Blockly.Msg.CODERBOT_ATMEGA_DO_5, "4"],
1253+
[Blockly.Msg.CODERBOT_ATMEGA_DO_6, "5"],
1254+
[Blockly.Msg.CODERBOT_ATMEGA_DO_7, "6"],
1255+
[Blockly.Msg.CODERBOT_ATMEGA_DO_8, "7"],
1256+
[Blockly.Msg.CODERBOT_ATMEGA_DO_9, "8"],
1257+
[Blockly.Msg.CODERBOT_ATMEGA_DO_10, "9"],
1258+
[Blockly.Msg.CODERBOT_ATMEGA_DO_11, "10"],]), 'OUTPUT');
1259+
this.appendValueInput('VALUE')
1260+
.setCheck('Boolean')
1261+
.appendField(Blockly.Msg.CODERBOT_ATMEGA_VALUE);
1262+
this.setInputsInline(true);
1263+
this.setPreviousStatement(true);
1264+
this.setNextStatement(true);
1265+
this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP);
1266+
}
1267+
};
1268+
1269+
Blockly.Python['coderbot_atmega_set_output'] = function(block) {
1270+
// input index: 0, 10 are Digital
1271+
var output = block.getFieldValue('OUTPUT');
1272+
var value = Blockly.Python.valueToCode(block, 'VALUE',
1273+
Blockly.Python.ORDER_NONE) || '\'\'';
1274+
var code = 'get_atmega().set_output(' + output + ', ' + value + ')\n';
1275+
return code;
1276+
};

static/js/blockly/bot_en.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,24 @@ Blockly.Msg.CODERBOT_EVENT_PUBLISH = "publish";
9797
Blockly.Msg.CODERBOT_EVENT_ON_TOPIC = "on topic";
9898
Blockly.Msg.CODERBOT_EVENT_GENERATOR = "event generator";
9999
Blockly.Msg.CODERBOT_CONVERSATION_PARSE = "parse";
100+
Blockly.Msg.CODERBOT_ATMEGA_READ = "Read";
101+
Blockly.Msg.CODERBOT_ATMEGA_VALUE = "Value";
102+
Blockly.Msg.CODERBOT_ATMEGA_AI_1 = "Analog Input 1";
103+
Blockly.Msg.CODERBOT_ATMEGA_AI_2 = "Analog Input 2";
104+
Blockly.Msg.CODERBOT_ATMEGA_DI_3 = "Digital Input 1";
105+
Blockly.Msg.CODERBOT_ATMEGA_DI_4 = "Digital Input 2";
106+
Blockly.Msg.CODERBOT_ATMEGA_DI_5 = "Digital Input3";
107+
Blockly.Msg.CODERBOT_ATMEGA_DI_6 = "Digital Input 4";
108+
Blockly.Msg.CODERBOT_ATMEGA_WRITE = "Write";
109+
Blockly.Msg.CODERBOT_ATMEGA_DO_1 = "Digital Output 1";
110+
Blockly.Msg.CODERBOT_ATMEGA_DO_2 = "Digital Output 2";
111+
Blockly.Msg.CODERBOT_ATMEGA_DO_3 = "Digital Output 3";
112+
Blockly.Msg.CODERBOT_ATMEGA_DO_4 = "Digital Output 4";
113+
Blockly.Msg.CODERBOT_ATMEGA_DO_5 = "Digital Output 5";
114+
Blockly.Msg.CODERBOT_ATMEGA_DO_6 = "Digital Output 6";
115+
Blockly.Msg.CODERBOT_ATMEGA_DO_7 = "Digital Output 7";
116+
Blockly.Msg.CODERBOT_ATMEGA_DO_8 = "Digital Output 8";
117+
Blockly.Msg.CODERBOT_ATMEGA_DO_9 = "Digital Output 9";
118+
Blockly.Msg.CODERBOT_ATMEGA_DO_10 = "Digital Output 10";
119+
Blockly.Msg.CODERBOT_ATMEGA_DO_11 = "Digital Output 11";
100120

static/js/blockly/bot_it.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,23 @@ Blockly.Msg.CODERBOT_EVENT_PUBLISH = "pubblica";
9797
Blockly.Msg.CODERBOT_EVENT_ON_TOPIC = "sul topic";
9898
Blockly.Msg.CODERBOT_EVENT_GENERATOR = "genera eventi";
9999
Blockly.Msg.CODERBOT_CONVERSATION_PARSE = "interpreta";
100+
Blockly.Msg.CODERBOT_ATMEGA_READ = "Leggi";
101+
Blockly.Msg.CODERBOT_ATMEGA_VALUE = "Valore";
102+
Blockly.Msg.CODERBOT_ATMEGA_AI_1 = "Analog Input 1";
103+
Blockly.Msg.CODERBOT_ATMEGA_AI_2 = "Analog Input 2";
104+
Blockly.Msg.CODERBOT_ATMEGA_DI_3 = "Digital Input 1";
105+
Blockly.Msg.CODERBOT_ATMEGA_DI_4 = "Digital Input 2";
106+
Blockly.Msg.CODERBOT_ATMEGA_DI_5 = "Digital Input3";
107+
Blockly.Msg.CODERBOT_ATMEGA_DI_6 = "Digital Input 4";
108+
Blockly.Msg.CODERBOT_ATMEGA_WRITE = "Scrivi";
109+
Blockly.Msg.CODERBOT_ATMEGA_DO_1 = "Digital Output 1";
110+
Blockly.Msg.CODERBOT_ATMEGA_DO_2 = "Digital Output 2";
111+
Blockly.Msg.CODERBOT_ATMEGA_DO_3 = "Digital Output 3";
112+
Blockly.Msg.CODERBOT_ATMEGA_DO_4 = "Digital Output 4";
113+
Blockly.Msg.CODERBOT_ATMEGA_DO_5 = "Digital Output 5";
114+
Blockly.Msg.CODERBOT_ATMEGA_DO_6 = "Digital Output 6";
115+
Blockly.Msg.CODERBOT_ATMEGA_DO_7 = "Digital Output 7";
116+
Blockly.Msg.CODERBOT_ATMEGA_DO_8 = "Digital Output 8";
117+
Blockly.Msg.CODERBOT_ATMEGA_DO_9 = "Digital Output 9";
118+
Blockly.Msg.CODERBOT_ATMEGA_DO_10 = "Digital Output 10";
119+
Blockly.Msg.CODERBOT_ATMEGA_DO_11 = "Digital Output 11";

templates/blocks_adv.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,10 @@
331331
<block type="coderbot_mpu_get_heading"></block>
332332
<block type="coderbot_mpu_get_temp"></block>
333333
</category>
334+
<category name="{% trans %}I/O Extensions{% endtrans %}" colour="240">
335+
<block type="coderbot_atmega_get_input"></block>
336+
<block type="coderbot_atmega_set_output"></block>
337+
</category>
334338
<category name="{% trans %}Sound{% endtrans %}" colour="220">
335339
<block type="coderbot_audio_say"></block>
336340
<block type="coderbot_audio_record"></block>

0 commit comments

Comments
 (0)