@kuriousdesign/machine-sdk 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,3 +12,4 @@ export declare enum DeviceCmds {
12
12
  START_RECORDING_LOGS = 10,// writes to Machine.RecordedLogs
13
13
  STOP_RECORDING_LOGS = 11
14
14
  }
15
+ export declare function deviceCmdToString(cmd: DeviceCmds): string;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DeviceCmds = void 0;
4
+ exports.deviceCmdToString = deviceCmdToString;
4
5
  var DeviceCmds;
5
6
  (function (DeviceCmds) {
6
7
  DeviceCmds[DeviceCmds["NONE"] = 0] = "NONE";
@@ -16,3 +17,33 @@ var DeviceCmds;
16
17
  DeviceCmds[DeviceCmds["START_RECORDING_LOGS"] = 10] = "START_RECORDING_LOGS";
17
18
  DeviceCmds[DeviceCmds["STOP_RECORDING_LOGS"] = 11] = "STOP_RECORDING_LOGS";
18
19
  })(DeviceCmds || (exports.DeviceCmds = DeviceCmds = {}));
20
+ function deviceCmdToString(cmd) {
21
+ switch (cmd) {
22
+ case DeviceCmds.NONE:
23
+ return "None";
24
+ case DeviceCmds.RESET:
25
+ return "Reset";
26
+ case DeviceCmds.STOP:
27
+ return "Stop";
28
+ case DeviceCmds.CLEAR:
29
+ return "Clear";
30
+ case DeviceCmds.KILL:
31
+ return "Kill";
32
+ case DeviceCmds.PAUSE:
33
+ return "Pause";
34
+ case DeviceCmds.UNPAUSE:
35
+ return "Unpause";
36
+ case DeviceCmds.START:
37
+ return "Start";
38
+ case DeviceCmds.TAKE_CONTROL:
39
+ return "Take Control";
40
+ case DeviceCmds.RELEASE_CONTROL:
41
+ return "Release Control";
42
+ case DeviceCmds.START_RECORDING_LOGS:
43
+ return "Start Recording Logs";
44
+ case DeviceCmds.STOP_RECORDING_LOGS:
45
+ return "Stop Recording Logs";
46
+ default:
47
+ return "Unknown Command";
48
+ }
49
+ }
@@ -102,3 +102,23 @@ export interface AxisSts {
102
102
  SkippingPositionCorrectionBeforeGearing: boolean;
103
103
  DualAxisStopType: number;
104
104
  }
105
+ export declare enum AxisMethods {
106
+ NONE = 0,
107
+ JOG_CMD = 1,
108
+ MOVE_ABS_CMD = 2,
109
+ MOVE_OP_MIN_MAX = 3,
110
+ ENFORCE_RAIL_BRAKES = 4,
111
+ START_STREAM = 5,
112
+ STOP = 6
113
+ }
114
+ export declare function axisMethodIdToString(method: AxisMethods): string;
115
+ export declare enum AxisProcesses {
116
+ NONE = 0,
117
+ VERIFY_POSITION = 1,
118
+ PERFORM_MASTERING = 2,
119
+ BACK_AND_FORTH = 3,
120
+ HOME_TO_HARDSTOP = 4,
121
+ ALIGNMENT_ANALYSIS_MOVE = 5,
122
+ MOVE_TO_SHIPPING_POSITION = 6
123
+ }
124
+ export declare function axisProcessIdToString(process: AxisProcesses): string;
@@ -1,8 +1,70 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AxisDriveTypes = void 0;
3
+ exports.AxisProcesses = exports.AxisMethods = exports.AxisDriveTypes = void 0;
4
+ exports.axisMethodIdToString = axisMethodIdToString;
5
+ exports.axisProcessIdToString = axisProcessIdToString;
4
6
  var AxisDriveTypes;
5
7
  (function (AxisDriveTypes) {
6
8
  AxisDriveTypes[AxisDriveTypes["STINGRAY"] = 0] = "STINGRAY";
7
9
  AxisDriveTypes[AxisDriveTypes["BOSCH_FSOE"] = 1] = "BOSCH_FSOE";
8
10
  })(AxisDriveTypes || (exports.AxisDriveTypes = AxisDriveTypes = {}));
11
+ var AxisMethods;
12
+ (function (AxisMethods) {
13
+ AxisMethods[AxisMethods["NONE"] = 0] = "NONE";
14
+ AxisMethods[AxisMethods["JOG_CMD"] = 1] = "JOG_CMD";
15
+ AxisMethods[AxisMethods["MOVE_ABS_CMD"] = 2] = "MOVE_ABS_CMD";
16
+ AxisMethods[AxisMethods["MOVE_OP_MIN_MAX"] = 3] = "MOVE_OP_MIN_MAX";
17
+ AxisMethods[AxisMethods["ENFORCE_RAIL_BRAKES"] = 4] = "ENFORCE_RAIL_BRAKES";
18
+ AxisMethods[AxisMethods["START_STREAM"] = 5] = "START_STREAM";
19
+ AxisMethods[AxisMethods["STOP"] = 6] = "STOP";
20
+ })(AxisMethods || (exports.AxisMethods = AxisMethods = {}));
21
+ function axisMethodIdToString(method) {
22
+ switch (method) {
23
+ case AxisMethods.NONE:
24
+ return "None";
25
+ case AxisMethods.JOG_CMD:
26
+ return "Jog Command";
27
+ case AxisMethods.MOVE_ABS_CMD:
28
+ return "Move Absolute Command";
29
+ case AxisMethods.MOVE_OP_MIN_MAX:
30
+ return "Move Operational Min/Max";
31
+ case AxisMethods.ENFORCE_RAIL_BRAKES:
32
+ return "Enforce Rail Brakes";
33
+ case AxisMethods.START_STREAM:
34
+ return "Start Stream";
35
+ case AxisMethods.STOP:
36
+ return "Stop";
37
+ default:
38
+ return "Unknown";
39
+ }
40
+ }
41
+ var AxisProcesses;
42
+ (function (AxisProcesses) {
43
+ AxisProcesses[AxisProcesses["NONE"] = 0] = "NONE";
44
+ AxisProcesses[AxisProcesses["VERIFY_POSITION"] = 1] = "VERIFY_POSITION";
45
+ AxisProcesses[AxisProcesses["PERFORM_MASTERING"] = 2] = "PERFORM_MASTERING";
46
+ AxisProcesses[AxisProcesses["BACK_AND_FORTH"] = 3] = "BACK_AND_FORTH";
47
+ AxisProcesses[AxisProcesses["HOME_TO_HARDSTOP"] = 4] = "HOME_TO_HARDSTOP";
48
+ AxisProcesses[AxisProcesses["ALIGNMENT_ANALYSIS_MOVE"] = 5] = "ALIGNMENT_ANALYSIS_MOVE";
49
+ AxisProcesses[AxisProcesses["MOVE_TO_SHIPPING_POSITION"] = 6] = "MOVE_TO_SHIPPING_POSITION";
50
+ })(AxisProcesses || (exports.AxisProcesses = AxisProcesses = {}));
51
+ function axisProcessIdToString(process) {
52
+ switch (process) {
53
+ case AxisProcesses.NONE:
54
+ return "None";
55
+ case AxisProcesses.VERIFY_POSITION:
56
+ return "Verify Position";
57
+ case AxisProcesses.PERFORM_MASTERING:
58
+ return "Perform Mastering";
59
+ case AxisProcesses.BACK_AND_FORTH:
60
+ return "Back and Forth";
61
+ case AxisProcesses.HOME_TO_HARDSTOP:
62
+ return "Home to Hardstop";
63
+ case AxisProcesses.ALIGNMENT_ANALYSIS_MOVE:
64
+ return "Alignment Analysis Move";
65
+ case AxisProcesses.MOVE_TO_SHIPPING_POSITION:
66
+ return "Move to Shipping Position";
67
+ default:
68
+ return "Unknown";
69
+ }
70
+ }
@@ -12,3 +12,13 @@ export interface SystemFaultData {
12
12
  list: FaultCodeData[];
13
13
  present: boolean;
14
14
  }
15
+ export interface Machine {
16
+ estopCircuit_OK: boolean;
17
+ estopCircuitDelayed_OK: boolean;
18
+ fenceCircuit_OK: boolean;
19
+ guardDoors_LOCKED: boolean;
20
+ networkHealth_OK: boolean;
21
+ ethercatMaster_OK: boolean;
22
+ ethercatSlaves_OK: boolean;
23
+ supplyAir_OK: boolean;
24
+ }
@@ -1,7 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // export interface Machine {
4
- // user: UserData;
5
- // errors: SystemFaultData;
6
- // warnings: SystemFaultData;
7
- // }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuriousdesign/machine-sdk",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "Shared data types and helpers for machine-related repositories",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,4 +30,32 @@ export const initialApiOpcuaData: ApiOpcuaData = {
30
30
  orchResp: {} as ApiOpcuaReqData,
31
31
  req: {} as ApiOpcuaReqData,
32
32
  resp: {} as ApiOpcuaReqData
33
- };
33
+ };
34
+
35
+ export enum ApiReqRespStates {
36
+ INACTIVE = 0,
37
+ REQUEST_READY = 1,
38
+ REJECTED_INVALID_CHECKSUM = 2,
39
+ REJECTED_ACTION_NOT_ACCEPTED = 3,
40
+ REJECTED_INVALID_SENDERID = 4,
41
+ ACCEPTED = 1000
42
+ }
43
+
44
+ export function apiReqRespStateToString(state: ApiReqRespStates): string {
45
+ switch (state) {
46
+ case ApiReqRespStates.INACTIVE:
47
+ return "INACTIVE";
48
+ case ApiReqRespStates.REQUEST_READY:
49
+ return "REQUEST_READY";
50
+ case ApiReqRespStates.REJECTED_INVALID_CHECKSUM:
51
+ return "REJECTED_INVALID_CHECKSUM";
52
+ case ApiReqRespStates.REJECTED_ACTION_NOT_ACCEPTED:
53
+ return "REJECTED_ACTION_NOT_ACCEPTED";
54
+ case ApiReqRespStates.REJECTED_INVALID_SENDERID:
55
+ return "REJECTED_INVALID_SENDERID";
56
+ case ApiReqRespStates.ACCEPTED:
57
+ return "ACCEPTED";
58
+ default:
59
+ return "UNKNOWN";
60
+ }
61
+ }
@@ -11,4 +11,35 @@ export enum DeviceCmds {
11
11
  RELEASE_CONTROL = 9, // used to release control over device
12
12
  START_RECORDING_LOGS = 10, // writes to Machine.RecordedLogs
13
13
  STOP_RECORDING_LOGS = 11
14
- }
14
+ }
15
+
16
+ export function deviceCmdToString(cmd: DeviceCmds): string {
17
+ switch (cmd) {
18
+ case DeviceCmds.NONE:
19
+ return "None";
20
+ case DeviceCmds.RESET:
21
+ return "Reset";
22
+ case DeviceCmds.STOP:
23
+ return "Stop";
24
+ case DeviceCmds.CLEAR:
25
+ return "Clear";
26
+ case DeviceCmds.KILL:
27
+ return "Kill";
28
+ case DeviceCmds.PAUSE:
29
+ return "Pause";
30
+ case DeviceCmds.UNPAUSE:
31
+ return "Unpause";
32
+ case DeviceCmds.START:
33
+ return "Start";
34
+ case DeviceCmds.TAKE_CONTROL:
35
+ return "Take Control";
36
+ case DeviceCmds.RELEASE_CONTROL:
37
+ return "Release Control";
38
+ case DeviceCmds.START_RECORDING_LOGS:
39
+ return "Start Recording Logs";
40
+ case DeviceCmds.STOP_RECORDING_LOGS:
41
+ return "Stop Recording Logs";
42
+ default:
43
+ return "Unknown Command";
44
+ }
45
+ }
@@ -1,6 +1,6 @@
1
1
  export enum AxisDriveTypes {
2
- STINGRAY = 0,
3
- BOSCH_FSOE = 1
2
+ STINGRAY = 0,
3
+ BOSCH_FSOE = 1
4
4
  }
5
5
 
6
6
  //ignored-
@@ -10,12 +10,12 @@ export interface AxisCfg {
10
10
  Unit: string;
11
11
  AtPosTol: number; // note: this may already be defined in the drive (if using bosch fsoe) difference between data.actualPosition and data.targetPosition below threshold and used during validation to determine if checked position matches the stored mastering position
12
12
 
13
-
13
+
14
14
  IgnoreSoftwareLimits: boolean;
15
15
  IgnoreLimitSwitches: boolean;
16
-
16
+
17
17
  Ignores: boolean[]; // use AxisPermissives ENUM
18
-
18
+
19
19
  SoftwareLimitPositive: number; // this is the limit that will produce an error if exceeded
20
20
  SoftwareLimitNegative: number;
21
21
  OperationalPositionMin: number; // this is the nominal operation minimum
@@ -25,7 +25,7 @@ export interface AxisCfg {
25
25
  SensorPositions: number[];
26
26
  //ignored-MotionProfiles: AxisMotionProfileData[]; // normal use case motion profiles, not e-stopping or error related
27
27
  AxisType: number;
28
-
28
+
29
29
  //ignored-ValidationSensor: AxisSensors; // used for homing or position verification
30
30
  //ignored-PositionReferencingMethod: PositionReferencingMethods; // homing or position verification
31
31
  VerificationSensorOffsetFromZero: number;
@@ -34,18 +34,18 @@ export interface AxisCfg {
34
34
  AutoPositionVerifyDuringReset: boolean; // set to true if you want the axis to automatically try to position verify during reset
35
35
  hasDynamicPositiveLimit: boolean;
36
36
  hasDynamicNegativeLimit: boolean;
37
-
37
+
38
38
  HomingToHardstopDir: number; // use -1.0 for negative and 1.0 for positive
39
39
  MaxCurrentWhileHomingToHardstop: number; // if homing in neg direction, then use a negative value only used if using home_to_hardstop_<dir> as the position verification method.
40
40
  MaxCurrentNormalOperation: number; // max current allowed during normal operation, ignore unless using homing to hardstop
41
-
41
+
42
42
  updateScalingIsNeeded: boolean; // set this to TRUE to enforce scaling changes
43
43
  //ignored-SoftMotionScalingParams: SoftMotionScalingParams;
44
-
44
+
45
45
  // DUAL AXIS ONLY
46
46
  GearingPositionTolerance: number; // mm, (dual Axis only) used for checking differences between geared axes while synced
47
47
  AxesPositionToleranceForSkew: number; // mm, (dual Axis only) used for checking if axes have gone beyond their mechanical limit for position differences before ruining the machine
48
-
48
+
49
49
  ShippingPosition: number; // mm, this should be something that is between the hardstop positions
50
50
  KeepMasteredStatusAtStartup: boolean; // if TRUE, this will maintain the mastered status/homed status of the axis after a power cycle
51
51
  }
@@ -60,7 +60,7 @@ export interface AxisMotionProfileData {
60
60
 
61
61
  export interface AxisSts {
62
62
  Cfg: AxisCfg; // read-only
63
-
63
+
64
64
  actualPosition: number;
65
65
  // actualSetpoint: number; // use the enum associated with setpoints
66
66
  actualVelocity: number;
@@ -68,15 +68,15 @@ export interface AxisSts {
68
68
  ActualTorque: number;
69
69
  ActualCurrent: number;
70
70
  //ignored-ActualControllerMode: SMC_CONTROLLER_MODE;
71
-
71
+
72
72
  ActiveStreamType: number;
73
-
73
+
74
74
  AxisRefState: number; // uses SMC_AXIS_STATE;
75
-
75
+
76
76
  targetPosition: number; // used for MoveAbs or MoveRel, change value before issuing your mc cmd, result should be echoed in setPosition after corresponding MC Cmd
77
77
  targetSetpoint: number;
78
78
  targetVelocity: number; // used for jogging, change value before issuing your mc cmd, result should be echoed in setVelocity after corresponding MC Cmd
79
-
79
+
80
80
  targetDir: number; // used for jogging and moving, 1.0 for fwd and -1.0 for bckwd
81
81
  // targetAccel: number;
82
82
  targetMaxCurrent: number; // only used for homing to hardstop of swing arms
@@ -84,19 +84,19 @@ export interface AxisSts {
84
84
  targetMotionProfileId: number;
85
85
  targetMotionProfileData: AxisMotionProfileData; // used for moving and jogging, change value before issuing your mc cmd, result should be echoed in setVelocity/setAcceleration after corresponding MC Cmd
86
86
  TargetControllerMode: number; // uses SMC_CONTROLLER_MODE;
87
-
87
+
88
88
  setPosition: number; // this value is updated by the drive
89
89
  setVelocity: number; // this value is updated by the drive
90
90
  setAccel: number; // this value is updated by the drive
91
91
  setTorque: number; // this value is updated by the drive
92
92
  setJerk: number; // this value is updated by the drive
93
-
93
+
94
94
  // STATUS BITS
95
95
  isEnabled: boolean;
96
96
  isHoming: boolean; // used to indicate that axis in the process of homing or used to indicate that axis is in the process of verifying its position
97
97
  isHomed: boolean; // used to indicate that axis has homed to a sensor and updated its position accordingly or used to indicate that position has been verified, useful when using absolute encoders that don't require homing (overwriting of known zero position)
98
98
  isMastered: boolean; // used to indicate that axis has been mastered, useful for axes that require position verification as their position referencing method
99
-
99
+
100
100
  mutingSwAndHwLimitsWhileHoming: boolean;
101
101
  //ignored-DriveHwLimitsEnforcement: AxisLimitEnforcements; // this status is set after calling the set software limits
102
102
  //ignored-DriveSwLimitsEnforcement: AxisLimitEnforcements;
@@ -108,48 +108,48 @@ export interface AxisSts {
108
108
  isMoving: boolean; // axis is moving to a position
109
109
  isGeared: boolean; // axis is moving synchronously with another axis
110
110
  isTorquing: boolean; // axis is in torque mode and actively applying torque
111
-
111
+
112
112
  // DRIVE RELATED
113
113
  DriveStatusMsg: string;
114
114
  DriveHasError: boolean;
115
115
  DriveIsStoppingMotor: boolean; // is True when motor faults and starts stopping itself
116
116
  DriveStoppedMotor: boolean; // True after motor finishes stopping itself. clears when you reset the motor
117
-
117
+
118
118
  SoftMotionHasError: boolean;
119
119
  SoftMotionErrorId: number; // uses SMC_ERROR;
120
-
120
+
121
121
  ConfiguredSoftLimitTravelRange: number; // determined by the sw limits;
122
122
  HomeOffsetFromZero: number; // units, this is a read-only value, the retain var HomeOffsetFromZero is used (this might be deprecated)
123
-
123
+
124
124
  CalculatedStoppingPosition: number;
125
125
  TravelLimitNegative: number;
126
126
  TravelLimitPositive: number;
127
127
 
128
128
  //ignored-Permissives: AxisPermissiveStsData[]; // Array of NUM_PERMISSIVES length
129
129
  RestrictedToSlow: boolean; // (TODO: replace this with NOT Permissives.FullSpeed_OK) used to speed limit the axis when it is not homed or has software and hardware limits disabled
130
-
130
+
131
131
  //ignored-RailBrakes: RailBrakeInputs;
132
132
  Sensors: boolean[]; // Array of MAX_NUM_SENSORS length
133
-
133
+
134
134
  targetEncoderSetpoint: number;
135
-
135
+
136
136
  targetMasterAxisDevId: number;
137
137
  targetMasterAxisRefPointer: any; // TypeScript doesn't have direct pointer equivalents
138
138
  actualMasterAxisDevId: number;
139
139
  actualMasterAxisRefPointer: any; // TypeScript doesn't have direct pointer equivalents
140
-
140
+
141
141
  RecordedPosition: number; // use this to save position information, useful for mastering and position verification processes
142
142
  //ignored-Interlocks: AxisInterlocks;
143
-
143
+
144
144
  ignoringRailBrakes: boolean;
145
145
  EtherCatComms_OK: boolean;
146
-
146
+
147
147
  // ROBOT CONTROLLER STATE
148
148
  RcHomingState: number; // uses enumRcAxisHomingState;
149
149
  RcControlMode: number; // uses enumRcAxisMode;
150
150
 
151
151
  isStreaming: boolean;
152
-
152
+
153
153
  // DUAL AXIS ONLY
154
154
  PositionDifference: number; // (Dual Axis Only) difference between the axes: axis1 - axis2
155
155
  AxesPositionDifference_OK: boolean; // (Dual Axis Only)status stating difference in position between two axes while moving or idle is acceptable, based on cfg.GearingPositionTolerance
@@ -157,3 +157,66 @@ export interface AxisSts {
157
157
  SkippingPositionCorrectionBeforeGearing: boolean; // (Dual Axis Only)this is usually TRUE if the axes are first being setup
158
158
  DualAxisStopType: number; // uses DualAxisStopTypes; // (Dual Axis Only)
159
159
  }
160
+
161
+
162
+ export enum AxisMethods {
163
+ NONE = 0,
164
+ JOG_CMD = 1,
165
+ MOVE_ABS_CMD = 2,
166
+ MOVE_OP_MIN_MAX = 3,
167
+ ENFORCE_RAIL_BRAKES = 4,
168
+ START_STREAM = 5,
169
+ STOP = 6
170
+ }
171
+
172
+ export function axisMethodIdToString(method: AxisMethods): string {
173
+ switch (method) {
174
+ case AxisMethods.NONE:
175
+ return "None";
176
+ case AxisMethods.JOG_CMD:
177
+ return "Jog Command";
178
+ case AxisMethods.MOVE_ABS_CMD:
179
+ return "Move Absolute Command";
180
+ case AxisMethods.MOVE_OP_MIN_MAX:
181
+ return "Move Operational Min/Max";
182
+ case AxisMethods.ENFORCE_RAIL_BRAKES:
183
+ return "Enforce Rail Brakes";
184
+ case AxisMethods.START_STREAM:
185
+ return "Start Stream";
186
+ case AxisMethods.STOP:
187
+ return "Stop";
188
+ default:
189
+ return "Unknown";
190
+ }
191
+ }
192
+
193
+ export enum AxisProcesses {
194
+ NONE = 0,
195
+ VERIFY_POSITION = 1,
196
+ PERFORM_MASTERING = 2,
197
+ BACK_AND_FORTH = 3,
198
+ HOME_TO_HARDSTOP = 4,
199
+ ALIGNMENT_ANALYSIS_MOVE = 5,
200
+ MOVE_TO_SHIPPING_POSITION = 6
201
+ }
202
+
203
+ export function axisProcessIdToString(process: AxisProcesses): string {
204
+ switch (process) {
205
+ case AxisProcesses.NONE:
206
+ return "None";
207
+ case AxisProcesses.VERIFY_POSITION:
208
+ return "Verify Position";
209
+ case AxisProcesses.PERFORM_MASTERING:
210
+ return "Perform Mastering";
211
+ case AxisProcesses.BACK_AND_FORTH:
212
+ return "Back and Forth";
213
+ case AxisProcesses.HOME_TO_HARDSTOP:
214
+ return "Home to Hardstop";
215
+ case AxisProcesses.ALIGNMENT_ANALYSIS_MOVE:
216
+ return "Alignment Analysis Move";
217
+ case AxisProcesses.MOVE_TO_SHIPPING_POSITION:
218
+ return "Move to Shipping Position";
219
+ default:
220
+ return "Unknown";
221
+ }
222
+ }
@@ -21,3 +21,14 @@ export interface SystemFaultData {
21
21
  // errors: SystemFaultData;
22
22
  // warnings: SystemFaultData;
23
23
  // }
24
+
25
+ export interface Machine {
26
+ estopCircuit_OK: boolean;
27
+ estopCircuitDelayed_OK: boolean;
28
+ fenceCircuit_OK: boolean;
29
+ guardDoors_LOCKED: boolean;
30
+ networkHealth_OK: boolean;
31
+ ethercatMaster_OK: boolean;
32
+ ethercatSlaves_OK: boolean;
33
+ supplyAir_OK: boolean;
34
+ }