Skip to content

Commit 1bdefa1

Browse files
authored
Let the user add event handlers to an opmode, for events defined in the robot (#175)
* In common_storage.ts, save the events declared in the module. In mrc_event_handler.ts: Refactored hardcoded fields names into constants FIELD_SENDER and FIELD_EVENT_NAME. Added otherBlockId to EventHandlerExtraState and mrcOtherBlockId to EventHandlerMixin. Added onLoad method that updates event handler blocks if the event definition has been changed. Added functions addRobotEventHandlerBlocks and createRobotEventHandlerBlock to create blocks for the toolbox. In editor.ts, added method getEventsFromRobot. In hardware_category.ts, added function getRobotEventsBlocks and call it in getHardwareCategory when the current module is an opmode. * In extended_python_generator.ts: Added fields hasHardware and hasEventHandler. Set these in mrcWorkspaceToCode before calling super.workspaceToCode. Renamed method defineClassVariables to generateInitStatements. Renamed local variable variableDefinitions to initStatements. Removed setHasHardware and addHardwarePort. In mrc_components.ts, added methods getNewPort and getHardwarePorts. getHardwarePorts collects the hardware ports and is called by extended_python_generator before we start generating python for blocks. (Previous code called addHardwarePort when the mrc_component blocks was generating python, but that is too late.) In mrc_event_handler.ts, added function getHasEventHandler. This is used to determine whether the generated __init__ method needs to call register_events. In generator_context.ts, removed hasHardware, clear(), setHasHardware(), and getHasHardware(). In mrc_call_python_function.ts, use forEach loops where possible. Use BLOCK_NAME instead of 'mrc_call_python_function'. Moved code that iterates through mrc_mechanism_component_holder blocks from editor.ts to mrc_mechanism_component_holder.ts functions getComponents and getEvents. In mrc_mechanism_component_holder.ts added function getHardwarePorts to collect ports needed for define_hardware. Moved code that iterates through mrc_class_method_def blocks from editor.ts to mrc_class_method_def.ts functions getMethodsForWithin, getMethodsForOutside, and getMethodNamesAlreadyOverriddenInWorkspace. In robot_base.py, added register_event and unregister_event methods. In methods_category.ts, don't show blocks for robot methods register_event and unregister_event. * Fixed typescript errors. * In robot_base.py: Rename register_event and unregister_event to register_event_handler and unregister_event_handler. Allow multiple handlers to be registered for an event. Add fire_event function. Changed python code for firing an event to call fire_event. When determining whether a workspace has hardware or events, check whether blocks are enabled.
1 parent ee53dd8 commit 1bdefa1

17 files changed

+573
-202
lines changed

server_python_scripts/blocks_base_classes/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
from .robot_base import RobotBase
66

77
__all__ = ['OpMode', 'Teleop', 'Auto', 'Test', 'Name', 'Group'
8-
'Mechanism', 'RobotBase']
8+
'Mechanism', 'RobotBase']
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,39 @@
11
# This is the class all robots derive from
22

3+
from typing import Callable
4+
35
class RobotBase:
46
def __init__(self):
57
self.hardware = []
8+
# In self.event_handlers, the keys are the event names, the values are a list of handlers.
9+
self.event_handlers = {}
10+
11+
def register_event_handler(self, event_name: str, event_handler: Callable) -> None:
12+
if event_name in self.event_handlers:
13+
self.event_handlers[event_name].append(event_handler)
14+
else:
15+
self.event_handlers[event_name] = [event_handler]
16+
17+
def unregister_event_handler(self, event_name: str, event_handler: Callable) -> None:
18+
if event_name in self.event_handlers:
19+
if event_handler in self.event_handlers[event_name]:
20+
self.event_handlers[event_name].remove(event_handler)
21+
if not self.event_handlers[event_name]:
22+
del self.event_handlers[event_name]
23+
24+
def fire_event(self, event_name: str, *args) -> None:
25+
if event_name in self.event_handlers:
26+
for event_handler in self.event_handlers[event_name]:
27+
event_handler(*args)
28+
629
def start(self) -> None:
730
for hardware in self.hardware:
831
hardware.start()
32+
933
def update(self) -> None:
1034
for hardware in self.hardware:
1135
hardware.update()
36+
1237
def stop(self) -> None:
1338
for hardware in self.hardware:
1439
hardware.stop()

src/blocks/mrc_call_python_function.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,8 @@ export const pythonFromBlock = function(
588588
generator.addImport(callPythonFunctionBlock.mrcImportModule);
589589
}
590590
let code;
591+
let needOpenParen = true;
592+
let delimiterBeforeArgs = '';
591593
let argStartIndex = 0;
592594
switch (callPythonFunctionBlock.mrcFunctionKind) {
593595
case FunctionKind.BUILT_IN: {
@@ -637,9 +639,9 @@ export const pythonFromBlock = function(
637639
}
638640
case FunctionKind.EVENT: {
639641
const eventName = block.getFieldValue(FIELD_EVENT_NAME);
640-
code =
641-
'if self.events.get("' + eventName + '", None):\n' +
642-
generator.INDENT + 'self.events["' + eventName + '"]';
642+
code = 'self.fire_event("' + eventName + '"';
643+
needOpenParen = false;
644+
delimiterBeforeArgs = ', ';
643645
break;
644646
}
645647
case FunctionKind.INSTANCE_COMPONENT: {
@@ -671,7 +673,14 @@ export const pythonFromBlock = function(
671673
default:
672674
throw new Error('mrcFunctionKind has unexpected value: ' + callPythonFunctionBlock.mrcFunctionKind)
673675
}
674-
code += '(' + generateCodeForArguments(callPythonFunctionBlock, generator, argStartIndex) + ')';
676+
if (needOpenParen) {
677+
code += '(';
678+
}
679+
const codeForArgs = generateCodeForArguments(callPythonFunctionBlock, generator, argStartIndex);
680+
if (codeForArgs) {
681+
code += delimiterBeforeArgs + codeForArgs;
682+
}
683+
code += ')';
675684
if (block.outputConnection) {
676685
return [code, Order.FUNCTION_CALL];
677686
} else {
@@ -699,22 +708,22 @@ function generateCodeForArguments(
699708
}
700709

701710
function getMethodCallers(workspace: Blockly.Workspace, otherBlockId: string): Blockly.Block[] {
702-
return workspace.getBlocksByType('mrc_call_python_function').filter((block) => {
711+
return workspace.getBlocksByType(BLOCK_NAME).filter((block) => {
703712
return (block as CallPythonFunctionBlock).mrcOtherBlockId === otherBlockId;
704713
});
705714
}
706715

707716
export function renameMethodCallers(workspace: Blockly.Workspace, otherBlockId: string, newName: string): void {
708-
for (const block of getMethodCallers(workspace, otherBlockId)) {
717+
getMethodCallers(workspace, otherBlockId).forEach(block => {
709718
(block as CallPythonFunctionBlock).renameMethodCaller(newName);
710-
}
719+
});
711720
}
712721

713722
export function mutateMethodCallers(
714723
workspace: Blockly.Workspace, otherBlockId: string, methodOrEvent: commonStorage.Method | commonStorage.Event) {
715724
const oldRecordUndo = Blockly.Events.getRecordUndo();
716725

717-
for (const block of getMethodCallers(workspace, otherBlockId)) {
726+
getMethodCallers(workspace, otherBlockId).forEach(block => {
718727
const callBlock = block as CallPythonFunctionBlock;
719728
// Get the extra state before changing the call block.
720729
const oldExtraState = callBlock.saveExtraState();
@@ -738,7 +747,7 @@ export function mutateMethodCallers(
738747
);
739748
Blockly.Events.setRecordUndo(oldRecordUndo);
740749
}
741-
}
750+
});
742751
}
743752

744753
// Functions used for creating blocks for the toolbox.

src/blocks/mrc_class_method_def.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { MUTATOR_BLOCK_NAME, PARAM_CONTAINER_BLOCK_NAME, MethodMutatorArgBlock }
3636

3737
export const BLOCK_NAME = 'mrc_class_method_def';
3838

39-
const FIELD_METHOD_NAME = 'NAME';
39+
export const FIELD_METHOD_NAME = 'NAME';
4040

4141
type Parameter = {
4242
name: string,
@@ -347,7 +347,7 @@ function findLegalMethodName(name: string, block: ClassMethodDefBlock): string {
347347
function isMethodNameUsed(
348348
name: string, workspace: Blockly.Workspace, opt_exclude?: Blockly.Block): boolean {
349349
const nameLowerCase = name.toLowerCase();
350-
for (const block of workspace.getBlocksByType('mrc_class_method_def')) {
350+
for (const block of workspace.getBlocksByType(BLOCK_NAME)) {
351351
if (block === opt_exclude) {
352352
continue;
353353
}
@@ -409,7 +409,7 @@ export const pythonFromBlock = function (
409409
if (block.mrcPythonMethodName == '__init__') {
410410
let class_specific = generator.getClassSpecificForInit();
411411
branch = generator.INDENT + 'super().__init__(' + class_specific + ')\n' +
412-
generator.defineClassVariables() + branch;
412+
generator.generateInitStatements() + branch;
413413
}
414414
else if (generator.inBaseClassMethod(blocklyName)){
415415
// Special case for methods inherited from the based class: generate the
@@ -510,3 +510,40 @@ function createClassMethodDefBlock(
510510
fields[FIELD_METHOD_NAME] = functionData.functionName;
511511
return new toolboxItems.Block(BLOCK_NAME, extraState, fields, null);
512512
}
513+
514+
// Misc
515+
516+
export function getMethodsForWithin(
517+
workspace: Blockly.Workspace,
518+
methods: commonStorage.Method[]): void {
519+
workspace.getBlocksByType(BLOCK_NAME).forEach(block => {
520+
const method = (block as ClassMethodDefBlock).getMethodForWithin();
521+
if (method) {
522+
methods.push(method);
523+
}
524+
});
525+
}
526+
527+
export function getMethodsForOutside(
528+
workspace: Blockly.Workspace,
529+
methods: commonStorage.Method[]): void {
530+
workspace.getBlocksByType(BLOCK_NAME).forEach(block => {
531+
const method = (block as ClassMethodDefBlock).getMethodForOutside();
532+
if (method) {
533+
methods.push(method);
534+
}
535+
});
536+
}
537+
538+
export function getMethodNamesAlreadyOverriddenInWorkspace(
539+
workspace: Blockly.Workspace,
540+
methodNamesAlreadyOverridden: string[]): void {
541+
workspace.getBlocksByType(BLOCK_NAME).forEach(block => {
542+
const methodDefBlock = block as ClassMethodDefBlock;
543+
// If the user cannot change the signature, it means the block defines a method that overrides a baseclass method.
544+
// That's what we are looking for here.
545+
if (!methodDefBlock.canChangeSignature()) {
546+
methodNamesAlreadyOverridden.push(methodDefBlock.getMethodName());
547+
}
548+
});
549+
}

src/blocks/mrc_component.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const OUTPUT_NAME = 'mrc_component';
3939
export const FIELD_NAME = 'NAME';
4040
export const FIELD_TYPE = 'TYPE';
4141

42-
export type ConstructorArg = {
42+
type ConstructorArg = {
4343
name: string,
4444
type: string,
4545
};
@@ -146,6 +146,23 @@ const COMPONENT = {
146146
className: componentType,
147147
};
148148
},
149+
getNewPort: function (this: ComponentBlock, i: number): string {
150+
let extension = '';
151+
if (i != 0) {
152+
extension = '_' + (i + 1).toString();
153+
}
154+
return this.getFieldValue(FIELD_NAME) + extension + '_port';
155+
},
156+
getHardwarePorts: function (this: ComponentBlock, ports: {[key: string]: string}): void {
157+
// Collect the hardware ports for this component block that are needed to generate
158+
// the define_hardware method. (The key is the port, the value is the type.)
159+
if (this.hideParams) {
160+
for (let i = 0; i < this.mrcArgs.length; i++) {
161+
const newPort = this.getNewPort(i);
162+
ports[newPort] = this.mrcArgs[i].type;
163+
}
164+
}
165+
},
149166
}
150167

151168
export const setup = function () {
@@ -171,13 +188,7 @@ export const pythonFromBlock = function (
171188
code += ', '
172189
}
173190
if (block.hideParams) {
174-
let extension = '';
175-
if (i != 0) {
176-
extension = '_' + (i + 1).toString();
177-
}
178-
const newPort = block.getFieldValue(FIELD_NAME) + extension + '_port';
179-
generator.addHardwarePort(newPort, block.mrcArgs[i].type);
180-
code += block.mrcArgs[i].name + ' = ' + newPort;
191+
code += block.mrcArgs[i].name + ' = ' + block.getNewPort(i);
181192
} else {
182193
code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, fieldName, Order.NONE);
183194
}

src/blocks/mrc_event.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,8 @@ export const setup = function () {
230230
}
231231

232232
export const pythonFromBlock = function (
233-
block: EventBlock,
234-
generator: ExtendedPythonGenerator) {
233+
_block: EventBlock,
234+
_generator: ExtendedPythonGenerator) {
235235
// TODO (Alan): What should this do here??
236236
return '';
237237
}

0 commit comments

Comments
 (0)