Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document JSON protocol? #2

Open
pepijndevos opened this issue Apr 22, 2021 · 7 comments
Open

Document JSON protocol? #2

pepijndevos opened this issue Apr 22, 2021 · 7 comments

Comments

@pepijndevos
Copy link

I noticed that you can get a Micropython shell over the USB TTY, but was curious how the app reads the motors and uploads sensors and such. So I went ahead and captured a trace of the USB data. I have not completely figured it out, but it does not seem too complicated, so I thought I'd share. I snipped out all the repeated updates from the hub.

{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 2, 2, 2]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 3, 0]], [7, -878, 427], [2, 1, -2], [-172, 0, 64], "", 0]}
{"i":"XZ2s","m":"get_hub_info","p":{}}
{"i":"XZ2s","r":{"firmware": {"checksum": "b0c335b", "version": [1, 0, 6, 34]}, "runtime": {"version": [2, 1, 4, 13]}, "variant": "1", "extra_files": "641473ba;4a2158b7;adfa1928;271c3044;389d9425;88796775;598a22e8;dd777413;793aadf0;2468ccaf;883c31f8;34cab5cb;9aa3664c;5bf00379;dccbf26c;5639f8c7;cd0d4f0e;542212c6;ecb2ed13;d13ad105;eedee3f7;3ab4af94;2d756977;3c1eb8b7;fab2bbf2;24fa3ac5;93e65f44;1292b727;30f86d13;045bf290;b0e0f641;e8e2d751;220cec0c;127eb15d;125991bf;fb9dc13e;47abd59d;36cc5af6;86d05ed7;e0155d41;fd2fa533;449c00db;62a2614f;4c56fc14;dfc8ad1a;65e6ecf8;ccdf069e;804a7d41;3fc01cf1;3a92a82a;521716f1;97699d1a;0c7a1872;3dc6439e;87718197;d7e51168;0b8f6dce;ba979273;bc6142a7;a4842285"}}
{"m":2,"p":[8.384, 100, true]}
{"i":"rHv7","m":"trigger_current_state","p":{}}
{"m":2,"p":[8.384, 100, true]}
{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 2, 3, 2]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 4, 0]], [10, -876, 428], [2, 2, -2], [-172, 0, 64], "", 0]}
{"i":"DZ0h","m":"program_modechange","p":{"mode":"download"}}
{"m":1,"p":{"storage": {"available": 28464, "total": 31744, "pct": 11.3327, "unit": "kb", "free": 28464}, "slots": {"4": {"name": "VGltZSB0byBjZWxlYnJhdGU=", "id": 33453, "project_id": "i0eGDC42vNhf", "modified": 1619018480572, "type": "scratch", "created": 1619017975508, "size": 8150}, "0": {"name": "UHJvamVjdCA0", "id": 31645, "project_id": "79YYK4zIFeG_", "modified": 1619115802137, "type": "python", "created": 1619115789833, "size": 380}, "2": {"name": "UHJvamVjdCA0", "id": 11624, "project_id": "znqYK8VOs1AX", "modified": 1619116345548, "type": "python", "created": 1619116335998, "size": 380}}}}
{"m":4,"p":"rightside"}
{"m":9,"p":["TEVHTyBIdWI=", "A8:E2:C1:9B:99:CF"]}
{"m":12,"p":[null, false]}
{"i":"rHv7","r":{}}
{"i":"DZ0h","r":{}}
{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 3, 4, 4]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 3, 0]], [8, -877, 424], [1, 1, -2], [-172, 0, 64], "", 0]}
{"m":2,"p":[8.386, 100, true]}
{"i":"HWTe","m":"program_modechange","p":{"mode":"download"}}
{"i":"HWTe","r":{}}
{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 4, 4, 4]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 4, 0]], [7, -877, 426], [2, 2, -2], [-172, 0, 64], "", 0]}
{"m":2,"p":[8.381, 100, true]}
{"i":"7duO","m":"program_modechange","p":{"mode":"download"}}
{"i":"7duO","r":{}}
{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 1, 2, 2]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 3, 0]], [8, -879, 429], [2, 2, -3], [-171, 0, 64], "", 0]}
{"i":"_MaC","m":"start_write_program","p":{"meta":{"created":1619116708630,"modified":1619116717319,"project_id":"G2NKxiLwOFuY","name":"UHJvamVjdCA0","type":"python"},"size":380,"slotid":5}}
{"i":"_MaC","r":{"blocksize": 512, "transferid": "41475"}}
{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 4, 4, 4]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 3, 0]], [6, -875, 427], [2, 0, -2], [-170, 0, 64], "", 0]}
{"m":2,"p":[8.384, 100, true]}
{"i":"grax","m":"write_package","p":{"data":"ZnJvbSBtaW5kc3Rvcm1
zIGltcG9ydCBNU0h1YiwgTW90b3IsIE1vdG9yUGFpciwgQ29sb3JTZW5zb3IsIER
pc3RhbmNlU2Vuc29yLCBBcHAKZnJvbSBtaW5kc3Rvcm1zLmNvbnRyb2wgaW1wb3J
0IHdhaXRfZm9yX3NlY29uZHMsIHdhaXRfdW50aWwsIFRpbWVyCmZyb20gbWluZHN
0b3Jtcy5vcGVyYXRvciBpbXBvcnQgZ3JlYXRlcl90aGFuLCBncmVhdGVyX3RoYW5
fb3JfZXF1YWxfdG8sIGxlc3NfdGhhbiwgbGVzc190aGFuX29yX2VxdWFsX3RvLCB
lcXVhbF90bywgbm90X2VxdWFsX3RvCmltcG9ydCBtYXRoCgoKIyBDcmVhdGUgeW9
1ciBvYmplY3RzIGhlcmUuCmh1YiA9IE1TSHViKCkKCgojIFdyaXRlIHlvdXIgcHJ
vZ3JhbSBoZXJlLgpodWIuc3BlYWtlci5iZWVwKCk=","transferid":"41475"}
}
{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 4, 4, 4]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 3, 0]], [8, -875, 427], [2, 2, -2], [-170, 0, 64], "", 0]}
{"i":"grax","r":{"next_ptr": null}}
{"i":"BfCl","m":"program_execute","p":{"slotid":5}}
{"m":1,"p":{"storage": {"available": 28460, "total": 31744, "pct": 11.3453, "unit": "kb", "free": 28460}, "slots": {"5": {"name": "UHJvamVjdCA0", "id": 57859, "project_id": "G2NKxiLwOFuY", "modified": 1619116717319, "type": "python", "created": 1619116708630, "size": 380}, "4": {"name": "VGltZSB0byBjZWxlYnJhdGU=", "id": 33453, "project_id": "i0eGDC42vNhf", "modified": 1619018480572, "type": "scratch", "created": 1619017975508, "size": 8150}, "0": {"name": "UHJvamVjdCA0", "id": 31645, "project_id": "79YYK4zIFeG_", "modified": 1619115802137, "type": "python", "created": 1619115789833, "size": 380}, "2": {"name": "UHJvamVjdCA0", "id": 11624, "project_id": "znqYK8VOs1AX", "modified": 1619116345548, "type": "python", "created": 1619116335998, "size": 380}}}}
{"m":12,"p":[null, false]}
{"i":"BfCl","r":null}
{"m":0,"p":[[75, [0, 2, -152, 0]], [75, [0, 6, -13, 0]], [61, [0, null, 2, 3, 2]], [0, []], [75, [0, 2, -50, 0]], [75, [0, 0, 4, 0]], [7, -876, 429], [2, 6, -3], [-170, 0, 64], "", 0]}
{"i":"9zLI","m":"program_terminate","p":{}}
{"i":"9zLI","r":{}}

My understanding is the following:

The hub spams JSON updates where "m" specifies the message and "p" contains data. The most frequent m=0 one seems to contain the motor rotation an sensor values seen in the hub. Wild guess is m=2 is battery levels? voltage, percent, plugged? Obviously m=1 is the device status triggered by trigger_current_state. m=4 seems to be hub orientation. m=9 is hostname (LEGO Hub in base64) and mac address.

The commands from the host seem to be of the form {"i":"random ID","m":"method","p":{parameters}}, to which the hub replies with {"i":"matching ID","r":{return data}}.

Upon starting the software, it executes

{"i":"XZ2s","m":"get_hub_info","p":{}}
{"i":"rHv7","m":"trigger_current_state","p":{}}

Then upon changing the programming slot, it sets the mode to download. Presumably, the other mode is "streaming".

{"i":"HWTe","m":"program_modechange","p":{"mode":"download"}}

Now here comes the interesting part. Uploading a Python program. First it starts by sending the start_write_program command, to which the hub responds with the blocksize and transferid.

{"i":"_MaC","m":"start_write_program","p":{"meta":{"created":1619116708630,"modified":1619116717319,"project_id":"G2NKxiLwOFuY","name":"UHJvamVjdCA0","type":"python"},"size":380,"slotid":5}}
{"i":"_MaC","r":{"blocksize": 512, "transferid": "41475"}}

In this case the program is only 380 bytes long, so only one block follows in the write_package command, which contains a base64 encoded version of my program and the transferid. I will have to take another trace to see what happens with longer programs. I bet you have to do something with the next_ptr in the next block.

{"i":"grax","m":"write_package","p":{"data":"ZnJvbSBtaW5kc3Rvcm1
zIGltcG9ydCBNU0h1YiwgTW90b3IsIE1vdG9yUGFpciwgQ29sb3JTZW5zb3IsIER
pc3RhbmNlU2Vuc29yLCBBcHAKZnJvbSBtaW5kc3Rvcm1zLmNvbnRyb2wgaW1wb3J
0IHdhaXRfZm9yX3NlY29uZHMsIHdhaXRfdW50aWwsIFRpbWVyCmZyb20gbWluZHN
0b3Jtcy5vcGVyYXRvciBpbXBvcnQgZ3JlYXRlcl90aGFuLCBncmVhdGVyX3RoYW5
fb3JfZXF1YWxfdG8sIGxlc3NfdGhhbiwgbGVzc190aGFuX29yX2VxdWFsX3RvLCB
lcXVhbF90bywgbm90X2VxdWFsX3RvCmltcG9ydCBtYXRoCgoKIyBDcmVhdGUgeW9
1ciBvYmplY3RzIGhlcmUuCmh1YiA9IE1TSHViKCkKCgojIFdyaXRlIHlvdXIgcHJ
vZ3JhbSBoZXJlLgpodWIuc3BlYWtlci5iZWVwKCk=","transferid":"41475"}
}
{"i":"grax","r":{"next_ptr": null}}

Finally the code is executed, and later terminated.

{"i":"BfCl","m":"program_execute","p":{"slotid":5}}
{"i":"BfCl","r":null}
{"i":"9zLI","m":"program_terminate","p":{}}
{"i":"9zLI","r":{}}

I'd like to experiment more with this, and write a small utility for uploading code and monitoring sensors without having to boot up a Windows VM.

@pepijndevos
Copy link
Author

I did a larger program and the only small puzzle is that next_ptr is 574 which is not a logical number as far as I can tell. The chunk is just 512 bytes as expected, and the next on starts right after.

{"i":"LmwE","m":"start_write_program","p":{"meta":{"created":161
9873636262,"modified":1619873712807,"project_id":"PAYWrK2bJXll",
"name":"UHJvamVjdCA0","type":"python"},"size":732,"slotid":0}}
{"i":"LmwE","r":{"blocksize": 512, "transferid": "2054"}}
{"m":{"i":"LwpP","m":"write_package","p":{"data":"ZnJvbSBtaW5kc3Rvcm1
zIGltcG9ydCBNU0h1YiwgTW90b3IsIE1vdG9yUGFpciwgQ29sb3JTZW5zb3IsIER
pc3RhbmNlU2Vuc29yLCBBcHAKZnJvbSBtaW5kc3Rvcm1zLmNvbnRyb2wgaW1wb3J
0IHdhaXRfZm9yX3NlY29uZHMsIHdhaXRfdW50aWwsIFRpbWVyCmZyb20gbWluZHN
0b3Jtcy5vcGVyYXRvciBpbXBvcnQgZ3JlYXRlcl90aGFuLCBncmVhdGVyX3RoYW5
fb3JfZXF1YWxfdG8sIGxlc3NfdGhhbiwgbGVzc190aGFuX29yX2VxdWFsX3RvLCB
lcXVhbF90bywgbm90X2VxdWFsX3RvCmltcG9ydCBtYXRoCgoKIyBDcmVhdGUgeW9
1ciBvYmplY3RzIGhlcmUuCmh1YiA9IE1TSHViKCkKCgojIFdyaXRlIHlvdXIgcHJ
vZ3JhbSBoZXJlLgpodWIuc3BlYWtlci5iZWVwKCkKCmRhdGEgPSAiIiIKb3RlbmV
vdG5haG9ldG5lb3NoYXRlb3NuYXRvZW5zdG9lYXN0b2Vhbm9uYW9lbmFzaHRvbmF
ubwpvdGVuZW90bmFob2V0bmVvc2hhdGVvc25hdG9lbnN0b2Vhc3RvZWFub25hb2V
uYXNodG9uYW5vCm90ZW5lb3Q=","transferid":"2054"}}
{"i":"LwpP","r":{"next_ptr": 574}}
{"i":"B8tN","m":"write_package","p":{"data":"bmFob2V0bmVvc2hhdGV
vc25hdG9lbnN0b2Vhc3RvZWFub25hb2VuYXNodG9uYW5vCm90ZW5lb3RuYWhvZXR
uZW9zaGF0ZW9zbmF0b2Vuc3RvZWFzdG9lYW5vbmFvZW5hc2h0b25hbm8Kb3RlbmV
vdG5haG9ldG5lb3NoYXRlb3NuYXRvZW5zdG9lYXN0b2Vhbm9uYW9lbmFzaHRvbmF
ubwpvdGVuZW90bmFob2V0bmVvc2hhdGVvc25hdG9lbnN0b2Vhc3RvZWFub25hb2V
uYXNodG9uYW5vCiIiIg==","transferid":"2054"}}
{"i":"B8tN","r":{"next_ptr": null}}
{"i":"OvGy","m":"program_execute","p":{"slotid":0}}
{"m":1,"p":{"storage": {"available": 28456, "total": 31744, "pct": 11.3579, "unit": "kb", "free": 28456}, "slots": {"5": {"name": "UHJvamVjdCA0", "id": 57859, "project_id": "G2NKxiLwOFuY", "modified": 1619116717319, "type": "python", "created": 1619116708630, "size": 380}, "4": {"name": "VGltZSB0byBjZWxlYnJhdGU=", "id": 22622, "project_id": "EaB0l5vx2R6c", "modified": 1619251021264, "type": "scratch", "created": 1619017505200, "size": 8150}, "0": {"name": "UHJvamVjdCA0", "id": 31645, "project_id": "PAYWrK2bJXll", "modified": 1619873712807, "type": "python", "created": 1619873636262, "size": 732}, "2": {"name": "UHJvamVjdCA0", "id": 11624, "project_id": "znqYK8VOs1AX", "modified": 1619116345548, "type": "python", "created": 1619116335998, "size": 380}}}}
{"m":0,"p":[[75, [0, 0, -87, 0]], [75, [0, 0, -30, 0]], [0, []], [0, []], [75, [0, 0, 64, 0]], [75, [0, 0, 10, 0]], [-67, -832, 507], [1, 2, -2], [1, 3, 58], "", 0]}
{"m":12,"p":[null, false]}
{"i":"OvGy","r":null}

For good measure I also did a scratch program, from which it becomes apparent that there might just be a 62 byte header before the program to store the name and data, which are also shown in the UI.

As expected the type is different

{"i":"3b4u","m":"start_write_program","p":{"meta":{"created":161
9017975508,"modified":1619874814350,"project_id":"i0eGDC42vNhf",
"name":"VGltZSB0byBjZWxlYnJhdGU=","type":"scratch"},"size":8150,
"slotid":4}}

The program itself is just Python, as your guide pretty much explains.

import hub
from runtime import MultiMotor, VirtualMachine
from util.rotation import rotate_hub_display_to_value
from util.scratch import clamp, compare, convert_animation_frame, number_color_to_rgb, pitch_to_freq
from util.sensors import get_sensor_value

g_animation = ["7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000000007707700000", "7707700000000000000000000", "7707700000000008808800000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000", "7707700000990999909900000"]

async def playBotCalibrate(vm):
    yield
    vm.reset_time()
    vm.system.motors.on_port("B").pwm(70, stall=vm.store.motor_stall("B"))
    vm.system.motors.on_port("F").pwm(-70, stall=vm.store.motor_stall("F"))
    while True:
        if ((compare(get_sensor_value("B", 1, 0, (49, 48, 76, 75)), "15") < 0) and (compare(vm.get_time() / 1000, "0.25") > 0)) or (compare(vm.get_time() / 1000, "1") > 0):
            break
        yield 0
    vm.system.motors.on_port("B").stop(vm.store.motor_stop("B"))
    while True:
        if ((compare(get_sensor_value("F", 1, 0, (49, 48, 76, 75)), "-15") > 0) and (compare(vm.get_time() / 1000, "0.25") > 0)) or (compare(vm.get_time() / 1000, "1") > 0):
            break
        yield 0
    vm.system.motors.on_port("F").stop(vm.store.motor_stop("F"))
    yield 200
    position = get_sensor_value("B", 3, 0, (49, 48, 76, 75))
    if position < 0:
        position = position + 360
    vm.system.motors.on_port("B").preset(round(clamp(position, -3600000, 3600000)))
    position_1 = get_sensor_value("F", 3, 0, (49, 48, 76, 75))
    if position_1 < 0:
        position_1 = position_1 + 360
    vm.system.motors.on_port("F").preset(round(clamp(position_1 - 360, -3600000, 3600000)))
    multi = MultiMotor(vm)
    for port in "BF":
        multi.run(port, vm.system.motors.on_port(port).run_to_relative_position, 0, 30, stall=vm.store.motor_stall(port), stop=vm.store.motor_stop(port))
    await multi.await_all()

async def stack_1(vm, stack):
    hub.led(*number_color_to_rgb(0))
    rotate_hub_display_to_value("3")
    global g_animation
    brightness = vm.store.display_brightness()
    frames = [hub.Image(convert_animation_frame(frame, brightness)) for frame in g_animation]
    vm.system.display.show(frames, clear=False, delay=125, loop=True, fade=2)
    await playBotCalibrate(vm)
    vm.store.move_pair(("A", "E"))
    vm.store.move_speed(80)
    yield 3000
    vm.system.sound.play("/extra_files/Humming", freq=pitch_to_freq(vm.store.sound_pitch(), 12000, 16000, 20000))
    for _ in range(2):
        (acceleration, deceleration) = vm.store.motor_acceleration("F")
        vm.store.motor_last_status("F", await vm.system.motors.on_port("F").run_for_degrees_async(100, -vm.store.motor_speed("F"), stall=vm.store.motor_stall("F"), stop=vm.store.motor_stop("F"), acceleration=acceleration, deceleration=deceleration))
        (acceleration_1, deceleration_1) = vm.store.motor_acceleration("F")
        vm.store.motor_last_status("F", await vm.system.motors.on_port("F").run_for_degrees_async(100, vm.store.motor_speed("F"), stall=vm.store.motor_stall("F"), stop=vm.store.motor_stop("F"), acceleration=acceleration_1, deceleration=deceleration_1))
        (acceleration_2, deceleration_2) = vm.store.motor_acceleration("B")
        vm.store.motor_last_status("B", await vm.system.motors.on_port("B").run_for_degrees_async(100, vm.store.motor_speed("B"), stall=vm.store.motor_stall("B"), stop=vm.store.motor_stop("B"), acceleration=acceleration_2, deceleration=deceleration_2))
        (acceleration_3, deceleration_3) = vm.store.motor_acceleration("B")
        vm.store.motor_last_status("B", await vm.system.motors.on_port("B").run_for_degrees_async(100, -vm.store.motor_speed("B"), stall=vm.store.motor_stall("B"), stop=vm.store.motor_stop("B"), acceleration=acceleration_3, deceleration=deceleration_3))
        yield
    vm.system.sound.play("/extra_files/Humming", freq=pitch_to_freq(vm.store.sound_pitch(), 12000, 16000, 20000))
    for _ in range(2):
        multi = MultiMotor(vm)
        for port in "AF":
            (acceleration_4, deceleration_4) = vm.store.motor_acceleration(port)
            multi.run(port, vm.system.motors.on_port(port).run_for_degrees, 100, -vm.store.motor_speed(port), stall=vm.store.motor_stall(port), stop=vm.store.motor_stop(port), acceleration=acceleration_4, deceleration=deceleration_4)
        await multi.await_all()
        multi_1 = MultiMotor(vm)
        for port_1 in "AF":
            (acceleration_5, deceleration_5) = vm.store.motor_acceleration(port_1)
            multi_1.run(port_1, vm.system.motors.on_port(port_1).run_for_degrees, 100, vm.store.motor_speed(port_1), stall=vm.store.motor_stall(port_1), stop=vm.store.motor_stop(port_1), acceleration=acceleration_5, deceleration=deceleration_5)
        await multi_1.await_all()
        multi_2 = MultiMotor(vm)
        for port_2 in "BE":
            (acceleration_6, deceleration_6) = vm.store.motor_acceleration(port_2)
            multi_2.run(port_2, vm.system.motors.on_port(port_2).run_for_degrees, 100, vm.store.motor_speed(port_2), stall=vm.store.motor_stall(port_2), stop=vm.store.motor_stop(port_2), acceleration=acceleration_6, deceleration=deceleration_6)
        await multi_2.await_all()
        multi_3 = MultiMotor(vm)
        for port_3 in "BE":
            (acceleration_7, deceleration_7) = vm.store.motor_acceleration(port_3)
            multi_3.run(port_3, vm.system.motors.on_port(port_3).run_for_degrees, 100, -vm.store.motor_speed(port_3), stall=vm.store.motor_stall(port_3), stop=vm.store.motor_stop(port_3), acceleration=acceleration_7, deceleration=deceleration_7)
        await multi_3.await_all()
    pair = vm.system.move.on_pair(*vm.store.move_pair())
    speeds = pair.from_direction("clockwise", vm.store.move_speed())
    (acceleration_8, deceleration_8) = vm.store.move_acceleration()
    vm.store.move_last_status(await pair.move_differential_speed_async(round(clamp((40 / vm.store.move_calibration()) * 360, -3600000, 3600000)), speeds[0], speeds[1], stop=vm.store.move_stop(), acceleration=acceleration_8, deceleration=deceleration_8))
    pair = vm.system.move.on_pair(*vm.store.move_pair())
    speeds = pair.from_direction("counterclockwise", vm.store.move_speed())
    (acceleration_9, deceleration_9) = vm.store.move_acceleration()
    vm.store.move_last_status(await pair.move_differential_speed_async(round(clamp((40 / vm.store.move_calibration()) * 360, -3600000, 3600000)), speeds[0], speeds[1], stop=vm.store.move_stop(), acceleration=acceleration_9, deceleration=deceleration_9))
    (acceleration_10, deceleration_10) = vm.store.motor_acceleration("F")
    vm.store.motor_last_status("F", await vm.system.motors.on_port("F").run_to_position_async(270, abs(vm.store.motor_speed("F")), "counterclockwise", stall=vm.store.motor_stall("F"), stop=vm.store.motor_stop("F"), acceleration=acceleration_10, deceleration=deceleration_10))
    yield 1000
    (acceleration_11, deceleration_11) = vm.store.motor_acceleration("F")
    vm.store.motor_last_status("F", await vm.system.motors.on_port("F").run_for_time_async(1000, vm.store.motor_speed("F"), stall=vm.store.motor_stall("F"), stop=vm.store.motor_stop("F"), acceleration=acceleration_11, deceleration=deceleration_11))
    await vm.system.sound.play_async("/extra_files/Tadaa", freq=pitch_to_freq(vm.store.sound_pitch(), 12000, 16000, 20000))
    (acceleration_12, deceleration_12) = vm.store.motor_acceleration("F")
    vm.store.motor_last_status("F", await vm.system.motors.on_port("F").run_to_position_async(0, abs(vm.store.motor_speed("F")), "counterclockwise", stall=vm.store.motor_stall("F"), stop=vm.store.motor_stop("F"), acceleration=acceleration_12, deceleration=deceleration_12))

def setup(rpc, system, stop):
    vm = VirtualMachine(rpc, system, stop, "OwN7h6f8YHOw5LM41dlD")

    vm.register_on_start("sbHaSJJPT3US4yoiFGAs", stack_1)

    return vm

@pepijndevos
Copy link
Author

Yay, I completed my upload script: https://gist.github.com/pepijndevos/9d55dd4b53f630186545fa6361fc229a

@cscovino
Copy link

Hi, @pepijndevos are there docs with the methods of VM? I would like to know what methods are available and what parameters receive each one.

@pepijndevos
Copy link
Author

Not really any complete ones. A few people have some scattered notes.

@cscovino
Copy link

Ok. I have the problem that when I send the code using VirtualMachine to register events, It works the first time but when I send another code the last VirtualMachine It still running, is there a way to shut down the VM when I try to send another code? or is there a method to get that VM and then shut down? Or how could I overwrite the VM so only the last events registered will work?

@cscovino
Copy link

cscovino commented Jun 16, 2021

Well, I finally did what I need. I had to use the config attribute of the hub to save the reference of the created VM, then I could get that VM from another script and send a broadcast that shuts down the VM.

from hub import config

...

try:
  prevVM = config["prevVM"]
  prevVM.broadcast("killAllPrevVMs")
except:
  pass
newVM = setup(RPC(), system, exit)
config['prevVM'] = newVM
newVM.start()

@azzieg
Copy link
Owner

azzieg commented Oct 1, 2022

We should revive this thread as interesting things are happening lately.

  • uasyncio is now shipped with the firmware. Does it mean that a new async programming model is coming?
  • Programs are now precompiled in the app and only bytecode is sent to the hub. I quickly created https://github.com/azzieg/mindstorms-inventor/tree/main/word_blocks so that we have reference material :-)
  • The emit method in the RPC fake seems no longer necessary, to my understanding the caller now subscribes to some form of VM tracing.
  • The JSON protocol program uploads now include a filename. Can we finally have multi-file programs?

There is indeed a gap in program termination caused by the fact that we (ab)use the external coroutine executor, but do not return a handle to our VM to the outside program, so it cannot be properly shut down. So we have two options. Either we run our own coroutine executor, or somehow connect to program termination.

Running your own coroutine executor technically works, but interrupting blocking programs is ugly and slow as it most likely involves some resetting. Run a blocking infinite loop, even using wait_for_seconds if you want, and try to stop the program. You'll immediately see what I mean.

Interestingly, connecting to the program termination is easier than I expected, as json_rpc is a global object. We have to use a trick similar to this of Carlos' to keep the state across program runs, but it works great. Try the following:

import sys
import system
from protocol.ujsonrpc import json_rpc

...

if "program_selector" not in json_rpc.methods:
    json_rpc.add_method("program_selector", json_rpc.methods["program_terminate"])
vm = setup(None, system.system, sys.exit)
def terminate(p, i):
    vm.shutdown()
    json_rpc.methods["program_selector"](p, i)
json_rpc.add_method("program_terminate", terminate)
vm.start()

This also shows how to connect our programs to the RPC stream, I think it can open up interesting new avenues :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants