I would like to control the movement of several standard RC servos with a T.16000M joystick. The goal is to be able to use the joystick to move the spools of hydraulic directional control valves and actuate cylindes. My topic is very similar to this one.
My setup:
Arduino Mega
Arduino USB Host Shield
Thustmaster T.16000M joystick with cable
I have installed the USB Host Shield Library 2.0 and I'm able to write to the serial monitor. It outputs hexadecimal code per axis ranging from 0000 to 3FFF.
The code from the included example "File>Examples>USB Host Shield Library 2.0>HID>t16km":
/* Simplified Thrustmaster T.16000M FCS Joystick Report Parser */
#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
// Thrustmaster T.16000M HID report
struct GamePadEventData
{
uint16_t buttons;
uint8_t hat;
uint16_t x;
uint16_t y;
uint8_t twist;
uint8_t slider;
}__attribute__((packed));
class JoystickEvents
{
public:
virtual void OnGamePadChanged(const GamePadEventData *evt);
};
#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)
class JoystickReportParser : public HIDReportParser
{
JoystickEvents *joyEvents;
uint8_t oldPad[RPT_GAMEPAD_LEN];
public:
JoystickReportParser(JoystickEvents *evt);
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt)
{}
void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
// Checking if there are changes in report since the method was last called
bool match = (sizeof(oldPad) == len) && (memcmp(oldPad, buf, len) == 0);
// Calling Game Pad event handler
if (!match && joyEvents) {
joyEvents->OnGamePadChanged((const GamePadEventData*)buf);
memcpy(oldPad, buf, len);
}
}
void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
Serial.print("X: ");
PrintHex<uint16_t>(evt->x, 0x80);
Serial.print(" Y: ");
PrintHex<uint16_t>(evt->y, 0x80);
Serial.print(" Hat Switch: ");
PrintHex<uint8_t>(evt->hat, 0x80);
Serial.print(" Twist: ");
PrintHex<uint8_t>(evt->twist, 0x80);
Serial.print(" Slider: ");
PrintHex<uint8_t>(evt->slider, 0x80);
Serial.print(" Buttons: ");
PrintHex<uint16_t>(evt->buttons, 0x80);
Serial.println();
}
USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);
void setup()
{
Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay( 200 );
if (!Hid.SetReportParser(0, &Joy))
ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1 );
}
void loop()
{
Usb.Task();
}
I have tried to make use of some of the code from the other topic I linked to, but I must admid that I'm lost. Can someone please help to make just one of my servos move forward and reverse in the X-directions of the joystick? I should be able to figure out the other axis' myself, if I have just one working servo.
I do not hope it violate the rules, but I'm willing to pay for some help. Will 50 EUR be encouraging enough for your time spend to help me set up one axis and one button?
This project is solely for my own private amusement. I have bought a Kellfri backhoe and want to convert it to a "real" excavator with rubber tracks and so on, all controlled by joysticks.
BrianPe:
I do not hope it violate the rules, but I'm willing to pay for some help.
If you are able to decode and understand all the data from the joystick, you only need a function to translate the data into movement on a servo / actuator. If you are willing to pay for help, I think you should ask someone in Gigs & Collabs.
IMHO, using a Teensy 3.6 with a built-in USB host controller is a whole lot easier than trying to use a USB host shield. Not sure if you're willing to spend the extra money, but it all comes down to how much you value your own time.
Danois90:
If you are able to decode and understand all the data from the joystick, you only need a function to translate the data into movement on a servo / actuator. If you are willing to pay for help, I think you should ask someone in Gigs & Collabs.
I think I'm able to make it work if I can get a little help to walk through this, so let's put the paycheck on standby for now
I'm basically able to decode and understand the data. It just expressed as hexadecimal values. So, how would I translate these values into something meaningful that the servo understands? As I understand servos, they are normally fed with decimal data ranging from 0 to 1023. Can I approach this by mapping the data?
/* Simplified Thrustmaster T.16000M FCS Joystick Report Parser */
#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>
#include <Servo.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#include <SPI.h>
#endif
// Thrustmaster T.16000M HID report
struct GamePadEventData
{
uint16_t buttons;
uint8_t hat;
uint16_t x;
uint16_t y;
uint8_t twist;
uint8_t slider;
}__attribute__((packed));
class JoystickEvents
{
public:
virtual void OnGamePadChanged(const GamePadEventData *evt);
};
#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)
class JoystickReportParser : public HIDReportParser
{
JoystickEvents *joyEvents;
uint8_t oldPad[RPT_GAMEPAD_LEN];
public:
JoystickReportParser(JoystickEvents *evt);
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt)
{}
void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
// Checking if there are changes in report since the method was last called
bool match = (sizeof(oldPad) == len) && (memcmp(oldPad, buf, len) == 0);
// Calling Game Pad event handler
if (!match && joyEvents) {
joyEvents->OnGamePadChanged((const GamePadEventData*)buf);
memcpy(oldPad, buf, len);
}
}
void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
uint16_t X = evt->x;
//Serial.print(" X: ");
//Serial.println(X);
//Serial.println();
}
USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);
//Defines Servo Pin 8
#define SERVOPIN 8 // Servo Digital Pin 8
//Defines a Joy Pin X
#define JOY_PIN_X JoyX // Joystick Analog Pin X
Servo ServoX; // create servo object to control a servo
int JoyX; // Variables to hold the last reading from the analog pins for the joystick.
// The value will be between 0 and 16383
int ValueXmapped; // The joystick values will be changed (or 'mapped') to new values to be sent to the servo.
int ValueX; //Current positional value being sent to the servo.
void setup()
{
ServoX.attach(8); // attaches the servo on pin 8 to the servo object
Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay( 200 );
if (!Hid.SetReportParser(0, &Joy))
ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1 );
}
void loop()
{
Usb.Task();
//Initialize servos
ServoX.attach(SERVOPIN); // Attaches/activates the servo on pin SERVOPIN
/************** Actuator 1 Positions ******************************/
//Read the values from the joystick
JoyX = analogRead(JOY_PIN_X); // reads the value of the joystick (value between 0 and 16383)
ValueXmapped = map(ValueX, 0, 16383, 0, 179); // scale it to use it with the servo (value between 0 and 180)
ServoX.write(ValueXmapped); // sets the servo position according to the scaled value
Serial.println(ValueXmapped);
delay(50); // waits for the servo to get there
}
I can now get values between 0 and 16383 with:
uint16_t X = evt->x;
Serial.print(" X: ");
Serial.println(X);
Serial.println();
... but when I apply the code in the "void loop()" section, "mapping" the data it is not working. Can you please take a look at the code and see what is wrong.
You have a lot of issues in the code and it seems like variables are used for things they are not ment to be used as, so the code cannot work. Your biggest problem is that you are trying to read the joystick from an analog pin (which is, btw, undefined), but the joystick is connected to an USB port which is handled by other code. Look carefully at the changes made to the code here:
/* Simplified Thrustmaster T.16000M FCS Joystick Report Parser */
#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>
#include <Servo.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#include <SPI.h>
#endif
// Thrustmaster T.16000M HID report
struct GamePadEventData
{
uint16_t buttons;
uint8_t hat;
uint16_t x;
uint16_t y;
uint8_t twist;
uint8_t slider;
}__attribute__((packed));
class JoystickEvents
{
public:
virtual void OnGamePadChanged(const GamePadEventData *evt);
};
#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)
class JoystickReportParser : public HIDReportParser
{
JoystickEvents *joyEvents;
uint8_t oldPad[RPT_GAMEPAD_LEN];
public:
JoystickReportParser(JoystickEvents *evt);
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt)
{}
void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
// Checking if there are changes in report since the method was last called
bool match = (sizeof(oldPad) == len) && (memcmp(oldPad, buf, len) == 0);
// Calling Game Pad event handler
if (!match && joyEvents) {
joyEvents->OnGamePadChanged((const GamePadEventData*)buf);
memcpy(oldPad, buf, len);
}
}
//!!!!! Value used to hold last reading from the joystick
uint16_y LastJoyX = 0;
void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
//!!!!! Store value from joystick to global variable
LastJoyX = evt->x;
//Serial.print(" X: ");
//Serial.println(X);
//Serial.println();
}
USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);
//Defines Servo Pin 8
#define SERVOPIN 8 // Servo Digital Pin 8
//!!!!! JOY_PIN_X refers "JoyX" which is not a pin declaration = removed.
//Defines a Joy Pin X
//#define JOY_PIN_X JoyX // Joystick Analog Pin X
Servo ServoX; // create servo object to control a servo
//!!!!! JoyX is unused = removed.
//int JoyX = 0; // Variables to hold the last reading from the analog pins for the joystick.
// The value will be between 0 and 16383
//!!!!! ValueXMapped & ValueX are not used = removed
//int ValueXmapped; // The joystick values will be changed (or 'mapped') to new values to be sent to the servo.
//int ValueX; //Current positional value being sent to the servo.
void setup()
{
//!!!!! Instead of "8" use SERVOPIN which is defined for the same purpose
ServoX.attach(SERVOPIN); // attaches the servo on pin 8 to the servo object
Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay( 200 );
if (!Hid.SetReportParser(0, &Joy))
ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1 );
}
void loop()
{
Usb.Task();
//!!!!! The servo must only be attached once in setup
//Initialize servos
//ServoX.attach(SERVOPIN); // Attaches/activates the servo on pin SERVOPIN
/************** Actuator 1 Positions ******************************/
//!!!!! There is no joystick attached to JOY_PIN_X
//Read the values from the joystick
//JoyX = analogRead(JOY_PIN_X); // reads the value of the joystick (value between 0 and 16383)
//ValueXmapped = map(ValueX, 0, 16383, 0, 179); // scale it to use it with the servo (value between 0 and 180)
//!!!!! Write LastJoyX to the servo:
byte JoyXMapped = map(LastjoyX, 0, 1638, 0, 179);
ServoX.write(JoyXMapped); // sets the servo position according to the scaled value
Serial.println(JoyXMapped);
delay(50); // waits for the servo to get there
}
The modifications are untested but they should give you a hint of how to do it. I have made some deliberate bugs, you fix those!
Danois90:
You have a lot of issues in the code and it seems like variables are used for things they are not ment to be used as, so the code cannot work. Your biggest problem is that you are trying to read the joystick from an analog pin (which is, btw, undefined), but the joystick is connected to an USB port which is handled by other code. Look carefully at the changes made to the code here:
The modifications are untested but they should give you a hint of how to do it. I have made some deliberate bugs, you fix those!
Thanks a lot! It's working flawlessly and now it's clear to me what caused the faults The implemented bugs were fairly easy to spot, well done haha
Overall this has been a good saturday. Just came home from a nice walk with my lady in the woods, to finally get this thing working.
Here's the working code for others to use:
/* Simplified Thrustmaster T.16000M FCS Joystick Report Parser */
#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>
#include <Servo.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#include <SPI.h>
#endif
// Thrustmaster T.16000M HID report
struct GamePadEventData
{
uint16_t buttons;
uint8_t hat;
uint16_t x;
uint16_t y;
uint8_t twist;
uint8_t slider;
}__attribute__((packed));
class JoystickEvents
{
public:
virtual void OnGamePadChanged(const GamePadEventData *evt);
};
#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)
class JoystickReportParser : public HIDReportParser
{
JoystickEvents *joyEvents;
uint8_t oldPad[RPT_GAMEPAD_LEN];
public:
JoystickReportParser(JoystickEvents *evt);
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt)
{}
void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
// Checking if there are changes in report since the method was last called
bool match = (sizeof(oldPad) == len) && (memcmp(oldPad, buf, len) == 0);
// Calling Game Pad event handler
if (!match && joyEvents) {
joyEvents->OnGamePadChanged((const GamePadEventData*)buf);
memcpy(oldPad, buf, len);
}
}
//!!!!! Value used to hold last reading from the joystick
uint16_t LastJoyX = 0;
void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
//!!!!! Store value from joystick to global variable
LastJoyX = evt->x;
//Serial.print(" X: ");
//Serial.println(X);
//Serial.println();
}
USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);
//Defines Servo Pin 8
#define SERVOPIN 8 // Servo Digital Pin 8
Servo ServoX; // create servo object to control a servo
void setup()
{
ServoX.attach(SERVOPIN); // attaches the servo on pin 8 to the servo object
Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay( 200 );
if (!Hid.SetReportParser(0, &Joy))
ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1 );
}
void loop()
{
Usb.Task();
//!!!!! Write LastJoyX to the servo:
byte JoyXmapped = map(LastJoyX, 0, 16383, 0, 179);
ServoX.write(JoyXmapped); // sets the servo position according to the scaled value
Serial.println(JoyXmapped);
delay(10); // waits for the servo to get there
}
void setup()
{
//Center the position of ServoX
ServoX.write(89);
ServoX.attach(SERVOPIN); // attaches the servo on pin 8 to the servo object
Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay( 200 );
if (!Hid.SetReportParser(0, &Joy))
ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1 );
}
I can't get the servo to center itself initially by: "ServoX.write(89);". The ServoX value is always 0 when the board is repowered or I upload the sketch. As soon as I touch the joystick, it moves to 89 (as it should). I have tried some different stuff, but nothing works out. Can you solve this?
EDIT:
I just figured it out:
//!!!!! Value used to hold last reading from the joystick
uint16_t LastJoyX = 8191;
Even though I also tried to set the value to "89", It didn't occur to me that it was way out of range, given that the max. JoyX value is 16383 and not 179 :
I'm struggling with the map function, trying to use writeMicroseconds:
void loop()
{
Usb.Task();
//!!!!! Write LastJoyX to the servo:
byte JoyXmapped = map(LastJoyX, 0, 16383, 1000, 2000);
ServoX.writeMicroseconds(JoyXmapped); // sets the servo position according to the scaled value
Serial.println(JoyXmapped);
delay(10); // waits for the servo to get there
}
The map function seems to be limited to 8 bits, because it can't output numbers greater than 255, it just rolls over and start from 0 again. Can someone explain what is wrong?
BrianPe:
The map function seems to be limited to 8 bits, because it can't output numbers greater than 255, it just rolls over and start from 0 again. Can someone explain what is wrong?)
map() returns a long int and is not limited to 8 bits. The reason the output doesn't go above 255 is because you're trying to stuff the long int from map() into a byte "JoyXmapped".
void loop()
{
Usb.Task();
//!!!!! Write LastJoyX to the servo:
int JoyXmapped = map(LastJoyX, 0, 16383, 1000, 2000);
ServoX.writeMicroseconds(JoyXmapped); // sets the servo position according to the scaled value
Serial.println(JoyXmapped);
delay(10); // waits for the servo to get there
}
This solved the problem and I learned something that is quite obvious now...
The servo starting position is set to 8191, half of the un-mapped joystick range of 16383.
The servo map range is set from 1000 to 2000 Us (8191 = 1500 Us starting position).
Only the X-axis is coded at the moment. I'll post new revisions with the remaining axis' and buttons in the future.
/* Simplified Thrustmaster T.16000M FCS Joystick Report Parser */
#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>
#include <Servo.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#include <SPI.h>
#endif
// Thrustmaster T.16000M HID report
struct GamePadEventData
{
uint16_t buttons;
uint8_t hat;
uint16_t x;
uint16_t y;
uint8_t twist;
uint8_t slider;
}__attribute__((packed));
class JoystickEvents
{
public:
virtual void OnGamePadChanged(const GamePadEventData *evt);
};
#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)
class JoystickReportParser : public HIDReportParser
{
JoystickEvents *joyEvents;
uint8_t oldPad[RPT_GAMEPAD_LEN];
public:
JoystickReportParser(JoystickEvents *evt);
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt)
{}
void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
// Checking if there are changes in report since the method was last called
bool match = (sizeof(oldPad) == len) && (memcmp(oldPad, buf, len) == 0);
// Calling Game Pad event handler
if (!match && joyEvents) {
joyEvents->OnGamePadChanged((const GamePadEventData*)buf);
memcpy(oldPad, buf, len);
}
}
//!!!!! Value used to hold last reading from the joystick. Also used to set the servo starting position (range 0 to 16383)
uint16_t LastJoyX = 8191;
void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
//!!!!! Store value from joystick to global variable
LastJoyX = evt->x;
//Serial.print(" X: ");
//Serial.println(X);
//Serial.println();
}
USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);
Servo ServoX; // Create servo object to control a servo
void setup()
{
ServoX.attach(8); // Attaches the servo on pin 8 to the servo object
Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay( 200 );
if (!Hid.SetReportParser(0, &Joy))
ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1 );
}
void loop()
{
Usb.Task();
//!!!!! Write LastJoyX to the servo:
int JoyXmapped = map(LastJoyX, 0, 16383, 1000, 2000); // Maps joystick range to servo (microseconds) range
ServoX.writeMicroseconds(JoyXmapped); // sets the servo position according to the scaled value. See "uint16_t LastJoyX =" for servo center position
Serial.print(" X: ");
Serial.println(JoyXmapped);
delay(10); // waits for the servo to get there
}
I'm stuck with setting up the buttons. Can someone please guide me in the right direction on how to set the digital pins high, when the buttons are pressed? There may be a link on Google I have not found, even though I have searched a lot.
Please find the existing code in my previous post.
Danois90:
Which buttons are you referring to and how are they wired?
I’m referring to any of the buttons on the T.16000M. I guess they each would have some sort of address, which can be called and then use pinmode and digitalwrite to set the the pins high or low. My lack of understanding the basics of registers, addresses etc. is not making it easier, I know.
Alright. The path of learning and being able to find the logic behind this is quite interesting I think, but I would like to have some hints just to get started.
I think I see the logic in what I was taught before with the different axis', but the buttons must each have its own address..? All I see is: "uint16_t buttons;" and I really don't know how to go on from there and assign them.
evt->buttons is an unsigned 16 bit integer. The joystick assigns 1 bit per joystick button. For example, press the front trigger and see how the "Buttons:" value changes. In most joysticks, the main trigger button is bit 0.
Assuming there are 16 buttons, the following code will test every bit/button. Press the buttons one at time to and make a table of bit number and joystick button.
for (int i = 0; i < 16; i++ ) {
if (evt->buttons & (1<<i)) {
// Button i down
}
else {
// Button i up
}
}
If you are not familiar with bit shifting, you can also use bitRead() to get the button states:
//Button names, instead of "BUTTON_A" you could use "BUTTON_TRIGGER" or something
#define BUTTON_A 0
#define BUTTON_B 1
#define BUTTON_C 2
//And so on...
if (bitRead(evt->buttons, BUTTON_A))
{
//Button A is pressed...
}
else if (bitRead(evt->buttons, BUTTON_B))
{
//Button B is pressed...
}
//And so on...