From 8bc550b619e518d269463bd37a05f59aceddd666 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Tue, 12 Apr 2016 18:46:01 +1000 Subject: [PATCH] oreoled: align with ArduPilot version --- src/drivers/drv_oreoled.h | 72 +- src/drivers/oreoled/oreoled.cpp | 1399 ++++++++++++++++++++++++++++++- 2 files changed, 1440 insertions(+), 31 deletions(-) diff --git a/src/drivers/drv_oreoled.h b/src/drivers/drv_oreoled.h index 00e3683184..8be6eb8d1a 100644 --- a/src/drivers/drv_oreoled.h +++ b/src/drivers/drv_oreoled.h @@ -61,6 +61,33 @@ /** send bytes */ #define OREOLED_SEND_BYTES _OREOLEDIOC(3) +/** send reset */ +#define OREOLED_SEND_RESET _OREOLEDIOC(4) + +/** boot ping */ +#define OREOLED_BL_PING _OREOLEDIOC(5) + +/** boot version */ +#define OREOLED_BL_VER _OREOLEDIOC(6) + +/** boot write flash */ +#define OREOLED_BL_FLASH _OREOLEDIOC(7) + +/** boot application version */ +#define OREOLED_BL_APP_VER _OREOLEDIOC(8) + +/** boot application crc */ +#define OREOLED_BL_APP_CRC _OREOLEDIOC(9) + +/** boot startup colour */ +#define OREOLED_BL_SET_COLOUR _OREOLEDIOC(10) + +/** boot application */ +#define OREOLED_BL_BOOT_APP _OREOLEDIOC(11) + +/** force an i2c gencall */ +#define OREOLED_FORCE_SYNC _OREOLEDIOC(12) + /* Oreo LED driver supports up to 4 leds */ #define OREOLED_NUM_LEDS 4 @@ -68,7 +95,46 @@ #define OREOLED_ALL_INSTANCES 0xff /* maximum command length that can be sent to LEDs */ -#define OREOLED_CMD_LENGTH_MAX 24 +#define OREOLED_CMD_LENGTH_MAX 70 + +/* maximum command length that can be read from LEDs */ +#define OREOLED_CMD_READ_LENGTH_MAX 10 + +/* maximum number of commands retries */ +#define OEROLED_COMMAND_RETRIES 10 + +/* magic number used to verify the software reset is valid */ +#define OEROLED_RESET_NONCE 0x2A + +/* microseconds to hold-off between write and reads */ +#define OREOLED_WRITE_READ_HOLDOFF_US 500 + +/* microseconds to hold-off between retries */ +#define OREOLED_RETRY_HOLDOFF_US 200 + +#define OEROLED_BOOT_COMMAND_RETRIES 25 +#define OREOLED_BOOT_FLASH_WAITMS 10 + +#define OREOLED_BOOT_SUPPORTED_VER 0x01 + +#define OREOLED_BOOT_CMD_PING 0x40 +#define OREOLED_BOOT_CMD_BL_VER 0x41 +#define OREOLED_BOOT_CMD_APP_VER 0x42 +#define OREOLED_BOOT_CMD_APP_CRC 0x43 +#define OREOLED_BOOT_CMD_SET_COLOUR 0x44 + +#define OREOLED_BOOT_CMD_WRITE_FLASH_A 0x50 +#define OREOLED_BOOT_CMD_WRITE_FLASH_B 0x51 +#define OREOLED_BOOT_CMD_FINALISE_FLASH 0x55 + +#define OREOLED_BOOT_CMD_BOOT_APP 0x60 + +#define OREOLED_BOOT_CMD_PING_NONCE 0x2A +#define OREOLED_BOOT_CMD_BOOT_NONCE 0xA2 + +#define OREOLED_FW_FILE_HEADER_LENGTH 2 +#define OREOLED_FW_FILE_SIZE_LIMIT 6144 +#define OREOLED_FW_FILE "/etc/firmware/oreoled.bin" /* enum passed to OREOLED_SET_MODE ioctl() * defined by hardware */ @@ -97,6 +163,8 @@ enum oreoled_param { OREOLED_PARAM_REPEAT = 7, OREOLED_PARAM_PHASEOFFSET = 8, OREOLED_PARAM_MACRO = 9, + OREOLED_PARAM_RESET = 10, + OREOLED_PARAM_APP_CHECKSUM = 11, OREOLED_PARAM_ENUM_COUNT }; @@ -114,6 +182,8 @@ enum oreoled_macro { OREOLED_PARAM_MACRO_BLUE = 8, OREOLED_PARAM_MACRO_YELLOW = 9, OREOLED_PARAM_MACRO_WHITE = 10, + OREOLED_PARAM_MACRO_AUTOMOBILE = 11, + OREOLED_PARAM_MACRO_AVIATION = 12, OREOLED_PARAM_MACRO_ENUM_COUNT }; diff --git a/src/drivers/oreoled/oreoled.cpp b/src/drivers/oreoled/oreoled.cpp index 5ee8dc030b..bbf96b98f4 100644 --- a/src/drivers/oreoled/oreoled.cpp +++ b/src/drivers/oreoled/oreoled.cpp @@ -35,7 +35,7 @@ /** * @file oreoled.cpp * - * Driver for the onboard RGB LED controller (TCA62724FMG) connected via I2C. + * Driver for oreoled ESCs found in solo, connected via I2C. * */ @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -69,7 +70,8 @@ #define OREOLED_NUM_LEDS 4 ///< maximum number of LEDs the oreo led driver can support #define OREOLED_BASE_I2C_ADDR 0x68 ///< base i2c address (7-bit) -#define OREOLED_TIMEOUT_MS 10000000U ///< timeout looking for battery 10seconds after startup +#define OPEOLED_I2C_RETRYCOUNT 2 ///< i2c retry count +#define OREOLED_TIMEOUT_USEC 2000000U ///< timeout looking for oreoleds 2 seconds after startup #define OREOLED_GENERALCALL_US 4000000U ///< general call sent every 4 seconds #define OREOLED_GENERALCALL_CMD 0x00 ///< general call command sent at regular intervals @@ -81,7 +83,7 @@ class OREOLED : public device::I2C { public: - OREOLED(int bus, int i2c_addr); + OREOLED(int bus, int i2c_addr, bool autoupdate, bool alwaysupdate); virtual ~OREOLED(); virtual int init(); @@ -95,6 +97,9 @@ public: /* send cmd to an LEDs (used for testing only) */ int send_cmd(oreoled_cmd_t sb); + /* returns true once the driver finished bootloading and ready for commands */ + bool is_ready(); + private: /** @@ -117,13 +122,40 @@ private: */ void cycle(); + int bootloader_app_reset(int led_num); + int bootloader_app_ping(int led_num); + uint16_t bootloader_inapp_checksum(int led_num); + int bootloader_ping(int led_num); + uint8_t bootloader_version(int led_num); + uint16_t bootloader_app_version(int led_num); + uint16_t bootloader_app_checksum(int led_num); + int bootloader_set_colour(int led_num, uint8_t red, uint8_t green); + int bootloader_flash(int led_num); + int bootloader_boot(int led_num); + uint16_t bootloader_fw_checksum(void); + int bootloader_coerce_healthy(void); + /* internal variables */ work_s _work; ///< work queue for scheduling reads bool _healthy[OREOLED_NUM_LEDS]; ///< health of each LED + bool _in_boot[OREOLED_NUM_LEDS]; ///< true for each LED that is in bootloader mode uint8_t _num_healthy; ///< number of healthy LEDs + uint8_t _num_inboot; ///< number of LEDs in bootloader ringbuffer::RingBuffer *_cmd_queue; ///< buffer of commands to send to LEDs uint64_t _last_gencall; uint64_t _start_time; ///< system time we first attempt to communicate with battery + bool _autoupdate; ///< true if the driver should update all LEDs + bool _alwaysupdate; ///< true if the driver should update all LEDs + bool _is_bootloading; ///< true if a bootloading operation is in progress + bool _is_ready; ///< set to true once the driver has completly initialised + uint16_t _fw_checksum; ///< the current 16bit XOR checksum of the built in oreoled firmware binary + + /* performance checking */ + perf_counter_t _call_perf; + perf_counter_t _gcall_perf; + perf_counter_t _probe_perf; + perf_counter_t _comms_errors; + perf_counter_t _reply_errors; }; /* for now, we only support one OREOLED */ @@ -137,16 +169,30 @@ void oreoled_usage(); extern "C" __EXPORT int oreoled_main(int argc, char *argv[]); /* constructor */ -OREOLED::OREOLED(int bus, int i2c_addr) : +OREOLED::OREOLED(int bus, int i2c_addr, bool autoupdate, bool alwaysupdate) : I2C("oreoled", OREOLED0_DEVICE_PATH, bus, i2c_addr, 100000), _work{}, _num_healthy(0), + _num_inboot(0), _cmd_queue(nullptr), - _last_gencall(0) + _last_gencall(0), + _autoupdate(autoupdate), + _alwaysupdate(alwaysupdate), + _is_bootloading(false), + _is_ready(false), + _fw_checksum(0x0000), + _call_perf(perf_alloc(PC_ELAPSED, "oreoled_call")), + _gcall_perf(perf_alloc(PC_ELAPSED, "oreoled_gcall")), + _probe_perf(perf_alloc(PC_ELAPSED, "oreoled_probe")), + _comms_errors(perf_alloc(PC_COUNT, "oreoled_comms_errors")), + _reply_errors(perf_alloc(PC_COUNT, "oreoled_reply_errors")) { /* initialise to unhealthy */ memset(_healthy, 0, sizeof(_healthy)); + /* initialise to in application */ + memset(_in_boot, 0, sizeof(_in_boot)); + /* capture startup time */ _start_time = hrt_absolute_time(); } @@ -161,6 +207,13 @@ OREOLED::~OREOLED() if (_cmd_queue != nullptr) { delete _cmd_queue; } + + /* free perf counters */ + perf_free(_call_perf); + perf_free(_gcall_perf); + perf_free(_probe_perf); + perf_free(_comms_errors); + perf_free(_reply_errors); } int @@ -192,6 +245,9 @@ OREOLED::init() int OREOLED::probe() { + /* set retry count */ + _retries = OPEOLED_I2C_RETRYCOUNT; + /* always return true */ return OK; } @@ -202,13 +258,20 @@ OREOLED::info() /* print health info on each LED */ for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { if (!_healthy[i]) { - DEVICE_LOG("oreo %u: BAD", (int)i); + DEVICE_LOG("oreo %u: BAD", (unsigned)i); } else { - DEVICE_LOG("oreo %u: OK", (int)i); + DEVICE_LOG("oreo %u: OK", (unsigned)i); } } + /* display perf info */ + perf_print_counter(_call_perf); + perf_print_counter(_gcall_perf); + perf_print_counter(_probe_perf); + perf_print_counter(_comms_errors); + perf_print_counter(_reply_errors); + return OK; } @@ -241,31 +304,67 @@ OREOLED::cycle() { /* check time since startup */ uint64_t now = hrt_absolute_time(); - bool startup_timeout = (now - _start_time > OREOLED_TIMEOUT_MS); + bool startup_timeout = (now - _start_time > OREOLED_TIMEOUT_USEC); - /* if not leds found during start-up period, exit without rescheduling */ - if (startup_timeout && _num_healthy == 0) { - warnx("did not find oreoled"); - return; - } + /* prepare the response buffer */ + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; /* during startup period keep searching for unhealthy LEDs */ if (!startup_timeout && _num_healthy < OREOLED_NUM_LEDS) { - /* prepare command to turn off LED*/ - uint8_t msg[] = {OREOLED_PATTERN_OFF}; + /* prepare command to turn off LED */ + /* add two bytes of pre-amble to for higher signal to noise ratio */ + uint8_t msg[] = {0xAA, 0x55, OREOLED_PATTERN_OFF, 0x00}; /* attempt to contact each unhealthy LED */ for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { if (!_healthy[i]) { + perf_begin(_probe_perf); + /* set I2C address */ set_address(OREOLED_BASE_I2C_ADDR + i); - /* send I2C command and record health*/ - if (transfer(msg, sizeof(msg), nullptr, 0) == OK) { - _healthy[i] = true; - _num_healthy++; - warnx("oreoled %d ok", (unsigned)i); + /* Calculate XOR CRC and append to the i2c write data */ + msg[sizeof(msg) - 1] = OREOLED_BASE_I2C_ADDR + i; + + for (uint8_t j = 0; j < sizeof(msg) - 1; j++) { + msg[sizeof(msg) - 1] ^= msg[j]; } + + /* send I2C command */ + if (transfer(msg, sizeof(msg), reply, 3) == OK) { + if (reply[1] == OREOLED_BASE_I2C_ADDR + i && + reply[2] == msg[sizeof(msg) - 1]) { + DEVICE_LOG("oreoled %u ok - in bootloader", (unsigned)i); + _healthy[i] = true; + _num_healthy++; + + /* If slaves are in application record that so we can reset if we need to bootload */ + /* This additional check is required for LED firmwares below v1.3 and can be + deprecated once all LEDs in the wild have firmware >= v1.3 */ + if (bootloader_ping(i) == OK) { + _in_boot[i] = true; + _num_inboot++; + } + + /* Check for a reply with a checksum offset of 1, + which indicates a response from firmwares >= 1.3 */ + + } else if (reply[1] == OREOLED_BASE_I2C_ADDR + i && + reply[2] == msg[sizeof(msg) - 1] + 1) { + DEVICE_LOG("oreoled %u ok - in application", (unsigned)i); + _healthy[i] = true; + _num_healthy++; + + } else { + DEVICE_LOG("oreo reply errors: %u", (unsigned)_reply_errors); + perf_count(_reply_errors); + } + + } else { + perf_count(_comms_errors); + } + + perf_end(_probe_perf); } } @@ -273,6 +372,121 @@ OREOLED::cycle() work_queue(HPWORK, &_work, (worker_t)&OREOLED::cycle_trampoline, this, USEC2TICK(OREOLED_STARTUP_INTERVAL_US)); return; + + } else if (_alwaysupdate) { + /* reset each healthy LED */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i] && !_in_boot[i]) { + /* reset the LED if it's not in the bootloader */ + /* (this happens during a pixhawk OTA update, since the LEDs stay powered) */ + bootloader_app_reset(i); + } + } + + /* attempt to update each healthy LED */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i] && _in_boot[i]) { + /* flash the new firmware */ + bootloader_flash(i); + } + } + + /* boot each healthy LED */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i] && _in_boot[i]) { + /* boot the application */ + bootloader_boot(i); + } + } + + /* coerce LEDs with startup issues to be healthy again */ + bootloader_coerce_healthy(); + + /* mandatory updating has finished */ + _alwaysupdate = false; + + /* schedule a fresh cycle call when the measurement is done */ + work_queue(HPWORK, &_work, (worker_t)&OREOLED::cycle_trampoline, this, + USEC2TICK(OREOLED_UPDATE_INTERVAL_US)); + return; + + } else if (_autoupdate) { + /* check booted oreoleds to see if the app can report it's checksum (release versions >= v1.2) */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i] && !_in_boot[i]) { + /* put any out of date oreoleds into bootloader mode */ + /* being in bootloader mode signals to be code below that the will likey need updating */ + if (bootloader_inapp_checksum(i) != bootloader_fw_checksum()) { + bootloader_app_reset(i); + } + } + } + + /* reset all healthy oreoleds if the number of outdated oreoled's is > 0 */ + /* this is done for consistency, so if only one oreoled is updating, all LEDs show the same behaviour */ + /* otherwise a single oreoled could appear broken or failed. */ + if (_num_inboot > 0) { + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i] && !_in_boot[i]) { + /* reset the LED if it's not in the bootloader */ + /* (this happens during a pixhawk OTA update, since the LEDs stay powered) */ + bootloader_app_reset(i); + } + } + + /* update each outdated and healthy LED in bootloader mode */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i] && _in_boot[i]) { + /* only flash LEDs with an old version of the applictioon */ + if (bootloader_app_checksum(i) != bootloader_fw_checksum()) { + /* flash the new firmware */ + bootloader_flash(i); + } + } + } + + /* boot each healthy LED */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i] && _in_boot[i]) { + /* boot the application */ + bootloader_boot(i); + } + } + + /* coerce LEDs with startup issues to be healthy again */ + bootloader_coerce_healthy(); + } + + /* auto updating has finished */ + _autoupdate = false; + + /* schedule a fresh cycle call when the measurement is done */ + work_queue(HPWORK, &_work, (worker_t)&OREOLED::cycle_trampoline, this, + USEC2TICK(OREOLED_UPDATE_INTERVAL_US)); + return; + + } else if (_num_inboot > 0) { + /* boot any LEDs which are in still in bootloader mode */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_in_boot[i]) { + bootloader_boot(i); + } + } + + /* coerce LEDs with startup issues to be healthy again */ + bootloader_coerce_healthy(); + + /* ensure we don't get stuck in a loop */ + _num_inboot = 0; + + /* schedule a fresh cycle call when the measurement is done */ + work_queue(HPWORK, &_work, (worker_t)&OREOLED::cycle_trampoline, this, + USEC2TICK(OREOLED_UPDATE_INTERVAL_US)); + return; + + } else if (!_is_ready) { + /* indicate a ready state since startup has finished */ + _is_ready = true; } /* get next command from queue */ @@ -282,23 +496,856 @@ OREOLED::cycle() /* send valid messages to healthy LEDs */ if ((next_cmd.led_num < OREOLED_NUM_LEDS) && _healthy[next_cmd.led_num] && (next_cmd.num_bytes <= OREOLED_CMD_LENGTH_MAX)) { + /* start performance timer */ + perf_begin(_call_perf); + /* set I2C address */ set_address(OREOLED_BASE_I2C_ADDR + next_cmd.led_num); - /* send I2C command */ - transfer(next_cmd.buff, next_cmd.num_bytes, nullptr, 0); + + /* Calculate XOR CRC and append to the i2c write data */ + uint8_t next_cmd_xor = OREOLED_BASE_I2C_ADDR + next_cmd.led_num; + + for (uint8_t i = 0; i < next_cmd.num_bytes; i++) { + next_cmd_xor ^= next_cmd.buff[i]; + } + + next_cmd.buff[next_cmd.num_bytes++] = next_cmd_xor; + + /* send I2C command with a retry limit */ + for (uint8_t retry = OEROLED_COMMAND_RETRIES; retry > 0; retry--) { + if (transfer(next_cmd.buff, next_cmd.num_bytes, reply, 3) == OK) { + if (reply[1] == (OREOLED_BASE_I2C_ADDR + next_cmd.led_num) && reply[2] == next_cmd_xor) { + /* slave returned a valid response */ + break; + + } else { + perf_count(_reply_errors); + } + + } else { + perf_count(_comms_errors); + } + } + + perf_end(_call_perf); } } - /* send general call every 4 seconds*/ - if ((now - _last_gencall) > OREOLED_GENERALCALL_US) { + /* send general call every 4 seconds, if we aren't bootloading*/ + if (!_is_bootloading && ((now - _last_gencall) > OREOLED_GENERALCALL_US)) { + perf_begin(_gcall_perf); send_general_call(); + perf_end(_gcall_perf); } - /* schedule a fresh cycle call when the measurement is done */ + /* schedule a fresh cycle call when the command is sent */ work_queue(HPWORK, &_work, (worker_t)&OREOLED::cycle_trampoline, this, USEC2TICK(OREOLED_UPDATE_INTERVAL_US)); } +int +OREOLED::bootloader_app_reset(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + int ret = -1; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + /* send a reset */ + boot_cmd.buff[0] = OREOLED_PATTERN_PARAMUPDATE; + boot_cmd.buff[1] = OREOLED_PARAM_RESET; + boot_cmd.buff[2] = OEROLED_RESET_NONCE; + boot_cmd.buff[3] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 4; + + for (uint8_t j = 0; j < boot_cmd.num_bytes - 1; j++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[j]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + /* send I2C command with a retry limit */ + for (uint8_t retry = OEROLED_COMMAND_RETRIES; retry > 0; retry--) { + if (transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 3) == OK) { + if (reply[1] == (OREOLED_BASE_I2C_ADDR + boot_cmd.led_num) && + reply[2] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + /* slave returned a valid response */ + ret = OK; + /* set this LED as being in boot mode now */ + _in_boot[led_num] = true; + _num_inboot++; + break; + } + } + } + + /* Allow time for the LED to reboot */ + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + + _is_bootloading = false; + return ret; +} + +int +OREOLED::bootloader_app_ping(int led_num) +{ + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + int ret = -1; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + /* send a pattern off command */ + boot_cmd.buff[0] = 0xAA; + boot_cmd.buff[1] = 0x55; + boot_cmd.buff[2] = OREOLED_PATTERN_OFF; + boot_cmd.buff[3] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 4; + + for (uint8_t j = 0; j < boot_cmd.num_bytes - 1; j++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[j]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + /* send I2C command with a retry limit */ + for (uint8_t retry = OEROLED_COMMAND_RETRIES; retry > 0; retry--) { + if (transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 3) == OK) { + if (reply[1] == (OREOLED_BASE_I2C_ADDR + boot_cmd.led_num) && + reply[2] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + /* slave returned a valid response */ + ret = OK; + break; + } + } + } + + return ret; +} + +uint16_t +OREOLED::bootloader_inapp_checksum(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + uint16_t ret = 0x0000; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + boot_cmd.buff[0] = OREOLED_PATTERN_PARAMUPDATE; + boot_cmd.buff[1] = OREOLED_PARAM_APP_CHECKSUM; + boot_cmd.buff[2] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 3; + + for (uint8_t j = 0; j < boot_cmd.num_bytes - 1; j++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[j]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + for (uint8_t retry = OEROLED_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 6); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_PARAM_APP_CHECKSUM && + reply[5] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl app checksum OK from LED %i", boot_cmd.led_num); + warnx("bl app checksum msb: 0x%x", reply[3]); + warnx("bl app checksum lsb: 0x%x", reply[4]); + ret = ((reply[3] << 8) | reply[4]); + break; + + } else { + warnx("bl app checksum FAIL from LED %i", boot_cmd.led_num); + warnx("bl app checksum response ADDR: 0x%x", reply[1]); + warnx("bl app checksum response CMD: 0x%x", reply[2]); + warnx("bl app checksum response VER H: 0x%x", reply[3]); + warnx("bl app checksum response VER L: 0x%x", reply[4]); + warnx("bl app checksum response XOR: 0x%x", reply[5]); + + if (retry > 1) { + warnx("bl app checksum retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl app checksum failed on LED %i", boot_cmd.led_num); + break; + } + } + } + + _is_bootloading = false; + return ret; +} + +int +OREOLED::bootloader_ping(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + int ret = -1; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + boot_cmd.buff[0] = OREOLED_BOOT_CMD_PING; + boot_cmd.buff[1] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 2; + + for (uint8_t j = 0; j < boot_cmd.num_bytes - 1; j++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[j]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 5); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_PING && + reply[3] == OREOLED_BOOT_CMD_PING_NONCE && + reply[4] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl ping OK from LED %i", boot_cmd.led_num); + ret = OK; + break; + + } else { + warnx("bl ping FAIL from LED %i", boot_cmd.led_num); + warnx("bl ping response ADDR: 0x%x", reply[1]); + warnx("bl ping response CMD: 0x%x", reply[2]); + warnx("bl ping response NONCE: 0x%x", reply[3]); + warnx("bl ping response XOR: 0x%x", reply[4]); + + if (retry > 1) { + warnx("bl ping retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl ping failed on LED %i", boot_cmd.led_num); + break; + } + } + } + + _is_bootloading = false; + return ret; +} + +uint8_t +OREOLED::bootloader_version(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + uint8_t ret = 0x00; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + boot_cmd.buff[0] = OREOLED_BOOT_CMD_BL_VER; + boot_cmd.buff[1] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 2; + + for (uint8_t k = 0; k < boot_cmd.num_bytes - 1; k++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[k]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 5); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_BL_VER && + reply[3] == OREOLED_BOOT_SUPPORTED_VER && + reply[4] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl ver from LED %i = %i", boot_cmd.led_num, reply[3]); + ret = reply[3]; + break; + + } else { + warnx("bl ver response ADDR: 0x%x", reply[1]); + warnx("bl ver response CMD: 0x%x", reply[2]); + warnx("bl ver response VER: 0x%x", reply[3]); + warnx("bl ver response XOR: 0x%x", reply[4]); + + if (retry > 1) { + warnx("bl ver retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl ver failed on LED %i", boot_cmd.led_num); + break; + } + } + } + + _is_bootloading = false; + return ret; +} + +uint16_t +OREOLED::bootloader_app_version(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + uint16_t ret = 0x0000; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + boot_cmd.buff[0] = OREOLED_BOOT_CMD_APP_VER; + boot_cmd.buff[1] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 2; + + for (uint8_t j = 0; j < boot_cmd.num_bytes - 1; j++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[j]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 6); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_APP_VER && + reply[5] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl app version OK from LED %i", boot_cmd.led_num); + warnx("bl app version msb: 0x%x", reply[3]); + warnx("bl app version lsb: 0x%x", reply[4]); + ret = ((reply[3] << 8) | reply[4]); + break; + + } else { + warnx("bl app version FAIL from LED %i", boot_cmd.led_num); + warnx("bl app version response ADDR: 0x%x", reply[1]); + warnx("bl app version response CMD: 0x%x", reply[2]); + warnx("bl app version response VER H: 0x%x", reply[3]); + warnx("bl app version response VER L: 0x%x", reply[4]); + warnx("bl app version response XOR: 0x%x", reply[5]); + + if (retry > 1) { + warnx("bl app version retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl app version failed on LED %i", boot_cmd.led_num); + break; + } + } + } + + _is_bootloading = false; + return ret; +} + +uint16_t +OREOLED::bootloader_app_checksum(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + uint16_t ret = 0x0000; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + boot_cmd.buff[0] = OREOLED_BOOT_CMD_APP_CRC; + boot_cmd.buff[1] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 2; + + for (uint8_t j = 0; j < boot_cmd.num_bytes - 1; j++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[j]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 6); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_APP_CRC && + reply[5] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl app checksum OK from LED %i", boot_cmd.led_num); + warnx("bl app checksum msb: 0x%x", reply[3]); + warnx("bl app checksum lsb: 0x%x", reply[4]); + ret = ((reply[3] << 8) | reply[4]); + break; + + } else { + warnx("bl app checksum FAIL from LED %i", boot_cmd.led_num); + warnx("bl app checksum response ADDR: 0x%x", reply[1]); + warnx("bl app checksum response CMD: 0x%x", reply[2]); + warnx("bl app checksum response VER H: 0x%x", reply[3]); + warnx("bl app checksum response VER L: 0x%x", reply[4]); + warnx("bl app checksum response XOR: 0x%x", reply[5]); + + if (retry > 1) { + warnx("bl app checksum retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl app checksum failed on LED %i", boot_cmd.led_num); + break; + } + } + } + + _is_bootloading = false; + return ret; +} + +int +OREOLED::bootloader_set_colour(int led_num, uint8_t red, uint8_t green) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + int ret = -1; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + boot_cmd.buff[0] = OREOLED_BOOT_CMD_SET_COLOUR; + boot_cmd.buff[1] = red; + boot_cmd.buff[2] = green; + boot_cmd.buff[3] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 4; + + for (uint8_t j = 0; j < boot_cmd.num_bytes - 1; j++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[j]; + } + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 4); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_SET_COLOUR && + reply[3] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl set colour OK from LED %i", boot_cmd.led_num); + ret = OK; + break; + + } else { + warnx("bl set colour FAIL from LED %i", boot_cmd.led_num); + warnx("bl set colour response ADDR: 0x%x", reply[1]); + warnx("bl set colour response CMD: 0x%x", reply[2]); + warnx("bl set colour response XOR: 0x%x", reply[3]); + + if (retry > 1) { + warnx("bl app colour retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl app colour failed on LED %i", boot_cmd.led_num); + break; + } + } + } + + _is_bootloading = false; + return ret; +} + +int +OREOLED::bootloader_flash(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + /* Open the bootloader file */ + int fd = ::open(OREOLED_FW_FILE, O_RDONLY); + + /* check for error opening the file */ + if (fd < 0) { + return -1; + } + + struct stat s; + + /* attempt to stat the file */ + if (stat(OREOLED_FW_FILE, &s) != 0) { + ::close(fd); + return -1; + } + + uint16_t fw_length = s.st_size - OREOLED_FW_FILE_HEADER_LENGTH; + + /* sanity-check file size */ + if (fw_length > OREOLED_FW_FILE_SIZE_LIMIT) { + ::close(fd); + return -1; + } + + uint8_t *buf = new uint8_t[s.st_size]; + + /* check that the buffer has been allocated */ + if (buf == NULL) { + ::close(fd); + return -1; + } + + /* check that the firmware can be read into the buffer */ + if (::read(fd, buf, s.st_size) != s.st_size) { + ::close(fd); + delete[] buf; + return -1; + } + + ::close(fd); + + /* Grab the version bytes from the binary */ + uint8_t version_major = buf[0]; + uint8_t version_minor = buf[1]; + + /* calculate flash pages (rounded up to nearest integer) */ + uint8_t flash_pages = ((fw_length + 64 - 1) / 64); + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + + /* Loop through flash pages */ + for (uint8_t page_idx = 0; page_idx < flash_pages; page_idx++) { + + /* Send the first half of the 64 byte flash page */ + memset(boot_cmd.buff, 0, sizeof(boot_cmd.buff)); + boot_cmd.buff[0] = OREOLED_BOOT_CMD_WRITE_FLASH_A; + boot_cmd.buff[1] = page_idx; + memcpy(boot_cmd.buff + 2, buf + (page_idx * 64) + OREOLED_FW_FILE_HEADER_LENGTH, 32); + boot_cmd.buff[32 + 2] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 32 + 3; + + for (uint8_t k = 0; k < boot_cmd.num_bytes - 1; k++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[k]; + } + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 4); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_WRITE_FLASH_A && + reply[3] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl flash %ia OK for LED %i", page_idx, boot_cmd.led_num); + break; + + } else { + warnx("bl flash %ia FAIL for LED %i", page_idx, boot_cmd.led_num); + warnx("bl flash %ia response ADDR: 0x%x", page_idx, reply[1]); + warnx("bl flash %ia response CMD: 0x%x", page_idx, reply[2]); + warnx("bl flash %ia response XOR: 0x%x", page_idx, reply[3]); + + if (retry > 1) { + warnx("bl flash %ia retrying LED %i", page_idx, boot_cmd.led_num); + + } else { + warnx("bl flash %ia failed on LED %i", page_idx, boot_cmd.led_num); + delete[] buf; + return -1; + } + } + } + + /* Send the second half of the 64 byte flash page */ + memset(boot_cmd.buff, 0, sizeof(boot_cmd.buff)); + boot_cmd.buff[0] = OREOLED_BOOT_CMD_WRITE_FLASH_B; + memcpy(boot_cmd.buff + 1, buf + (page_idx * 64) + 32 + OREOLED_FW_FILE_HEADER_LENGTH, 32); + boot_cmd.buff[32 + 1] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 32 + 2; + + for (uint8_t k = 0; k < boot_cmd.num_bytes - 1; k++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[k]; + } + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write+Read */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 4); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_WRITE_FLASH_B && + reply[3] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl flash %ib OK for LED %i", page_idx, boot_cmd.led_num); + break; + + } else { + warnx("bl flash %ib FAIL for LED %i", page_idx, boot_cmd.led_num); + warnx("bl flash %ib response ADDR: 0x%x", page_idx, reply[1]); + warnx("bl flash %ib response CMD: 0x%x", page_idx, reply[2]); + warnx("bl flash %ib response XOR: 0x%x", page_idx, reply[3]); + + if (retry > 1) { + warnx("bl flash %ib retrying LED %i", page_idx, boot_cmd.led_num); + + } else { + errx(1, "bl flash %ib failed on LED %i", page_idx, boot_cmd.led_num); + delete[] buf; + return -1; + } + } + } + + /* Sleep to allow flash to write */ + /* Wait extra long on the first write, to allow time for EEPROM updates */ + if (page_idx == 0) { + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + + } else { + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000); + } + } + + uint16_t app_checksum = bootloader_fw_checksum(); + + /* Flash writes must have succeeded so finalise the flash */ + boot_cmd.buff[0] = OREOLED_BOOT_CMD_FINALISE_FLASH; + boot_cmd.buff[1] = version_major; + boot_cmd.buff[2] = version_minor; + boot_cmd.buff[3] = (uint8_t)(fw_length >> 8); + boot_cmd.buff[4] = (uint8_t)(fw_length & 0xFF); + boot_cmd.buff[5] = (uint8_t)(app_checksum >> 8); + boot_cmd.buff[6] = (uint8_t)(app_checksum & 0xFF); + boot_cmd.buff[7] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 8; + + for (uint8_t k = 0; k < boot_cmd.num_bytes - 1; k++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[k]; + } + + /* Try to finalise for twice the amount of normal retries */ + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES * 2; retry > 0; retry--) { + /* Send the I2C Write */ + memset(reply, 0, sizeof(reply)); + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 4); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_FINALISE_FLASH && + reply[3] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl finalise OK from LED %i", boot_cmd.led_num); + break; + + } else { + warnx("bl finalise response ADDR: 0x%x", reply[1]); + warnx("bl finalise response CMD: 0x%x", reply[2]); + warnx("bl finalise response XOR: 0x%x", reply[3]); + + if (retry > 1) { + warnx("bl finalise retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl finalise failed on LED %i", boot_cmd.led_num); + delete[] buf; + return -1; + } + } + } + + /* allow time for flash to finalise */ + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + + /* clean up file buffer */ + delete[] buf; + + _is_bootloading = false; + return 1; +} + +int +OREOLED::bootloader_boot(int led_num) +{ + _is_bootloading = true; + oreoled_cmd_t boot_cmd; + boot_cmd.led_num = led_num; + + int ret = -1; + + /* Set the current address */ + set_address(OREOLED_BASE_I2C_ADDR + boot_cmd.led_num); + + boot_cmd.buff[0] = OREOLED_BOOT_CMD_BOOT_APP; + boot_cmd.buff[1] = OREOLED_BOOT_CMD_BOOT_NONCE; + boot_cmd.buff[2] = OREOLED_BASE_I2C_ADDR + boot_cmd.led_num; + boot_cmd.num_bytes = 3; + + for (uint8_t k = 0; k < boot_cmd.num_bytes - 1; k++) { + boot_cmd.buff[boot_cmd.num_bytes - 1] ^= boot_cmd.buff[k]; + } + + for (uint8_t retry = OEROLED_BOOT_COMMAND_RETRIES; retry > 0; retry--) { + /* Send the I2C Write */ + uint8_t reply[OREOLED_CMD_READ_LENGTH_MAX]; + transfer(boot_cmd.buff, boot_cmd.num_bytes, reply, 4); + + /* Check the response */ + if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_BOOT_APP && + reply[3] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl boot OK from LED %i", boot_cmd.led_num); + /* decrement the inboot counter so we don't get confused */ + _in_boot[led_num] = false; + _num_inboot--; + ret = OK; + break; + + } else if (reply[1] == OREOLED_BASE_I2C_ADDR + boot_cmd.led_num && + reply[2] == OREOLED_BOOT_CMD_BOOT_NONCE && + reply[3] == boot_cmd.buff[boot_cmd.num_bytes - 1]) { + warnx("bl boot error from LED %i: no app", boot_cmd.led_num); + break; + + } else { + warnx("bl boot response ADDR: 0x%x", reply[1]); + warnx("bl boot response CMD: 0x%x", reply[2]); + warnx("bl boot response XOR: 0x%x", reply[3]); + + if (retry > 1) { + warnx("bl boot retrying LED %i", boot_cmd.led_num); + + } else { + warnx("bl boot failed on LED %i", boot_cmd.led_num); + break; + } + } + } + + /* allow time for the LEDs to boot */ + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + usleep(OREOLED_BOOT_FLASH_WAITMS * 1000 * 10); + + _is_bootloading = false; + return ret; +} + +uint16_t +OREOLED::bootloader_fw_checksum(void) +{ + /* Calculate the 16 bit XOR checksum of the firmware on the first call of this function */ + if (_fw_checksum == 0x0000) { + /* Open the bootloader file */ + int fd = ::open(OREOLED_FW_FILE, O_RDONLY); + + /* check for error opening the file */ + if (fd < 0) { + return -1; + } + + struct stat s; + + /* attempt to stat the file */ + if (stat(OREOLED_FW_FILE, &s) != 0) { + ::close(fd); + return -1; + } + + uint16_t fw_length = s.st_size - OREOLED_FW_FILE_HEADER_LENGTH; + + /* sanity-check file size */ + if (fw_length > OREOLED_FW_FILE_SIZE_LIMIT) { + ::close(fd); + return -1; + } + + uint8_t *buf = new uint8_t[s.st_size]; + + /* check that the buffer has been allocated */ + if (buf == NULL) { + ::close(fd); + return -1; + } + + /* check that the firmware can be read into the buffer */ + if (::read(fd, buf, s.st_size) != s.st_size) { + ::close(fd); + delete[] buf; + return -1; + } + + ::close(fd); + + /* Calculate a 16 bit XOR checksum of the flash */ + /* Skip the first two bytes which are the version information, plus + the next two bytes which are modified by the bootloader */ + uint16_t app_checksum = 0x0000; + + for (uint16_t j = 2 + OREOLED_FW_FILE_HEADER_LENGTH; j < s.st_size; j += 2) { + app_checksum ^= (buf[j] << 8) | buf[j + 1]; + } + + delete[] buf; + + warnx("fw length = %i", fw_length); + warnx("fw checksum = %i", app_checksum); + + /* Store the checksum so it's only calculated once */ + _fw_checksum = app_checksum; + } + + return _fw_checksum; +} + +int +OREOLED::bootloader_coerce_healthy(void) +{ + int ret = -1; + + /* check each unhealthy LED */ + /* this re-checks "unhealthy" LEDs as they can sometimes power up with the wrong ID, */ + /* but will have the correct ID once they jump to the application and be healthy again */ + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (!_healthy[i] && bootloader_app_ping(i) == OK) { + /* mark as healthy */ + _healthy[i] = true; + _num_healthy++; + ret = OK; + } + } + + return ret; +} + int OREOLED::ioctl(struct file *filp, int cmd, unsigned long arg) { @@ -368,6 +1415,100 @@ OREOLED::ioctl(struct file *filp, int cmd, unsigned long arg) return ret; + case OREOLED_SEND_RESET: + /* send a reset */ + new_cmd.led_num = OREOLED_ALL_INSTANCES; + new_cmd.buff[0] = OREOLED_PATTERN_PARAMUPDATE; + new_cmd.buff[1] = OREOLED_PARAM_RESET; + new_cmd.buff[2] = OEROLED_RESET_NONCE; + new_cmd.num_bytes = 3; + + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + /* add command to queue for all healthy leds */ + if (_healthy[i]) { + warnx("sending a reset... to %i", i); + new_cmd.led_num = i; + _cmd_queue->force(&new_cmd); + ret = OK; + } + } + + return ret; + + case OREOLED_BL_PING: + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i]) { + bootloader_ping(i); + ret = OK; + } + } + + return ret; + + case OREOLED_BL_VER: + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i]) { + bootloader_version(i); + ret = OK; + } + } + + return ret; + + case OREOLED_BL_FLASH: + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i]) { + bootloader_flash(i); + ret = OK; + } + } + + return ret; + + case OREOLED_BL_APP_VER: + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i]) { + bootloader_app_version(i); + ret = OK; + } + } + + return ret; + + case OREOLED_BL_APP_CRC: + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i]) { + bootloader_app_checksum(i); + ret = OK; + } + } + + return ret; + + case OREOLED_BL_SET_COLOUR: + new_cmd.led_num = OREOLED_ALL_INSTANCES; + + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i]) { + bootloader_set_colour(i, ((oreoled_rgbset_t *) arg)->red, ((oreoled_rgbset_t *) arg)->green); + ret = OK; + } + } + + return ret; + + case OREOLED_BL_BOOT_APP: + new_cmd.led_num = OREOLED_ALL_INSTANCES; + + for (uint8_t i = 0; i < OREOLED_NUM_LEDS; i++) { + if (_healthy[i]) { + bootloader_boot(i); + ret = OK; + } + } + + return ret; + case OREOLED_SEND_BYTES: /* send bytes */ new_cmd = *((oreoled_cmd_t *) arg); @@ -393,6 +1534,10 @@ OREOLED::ioctl(struct file *filp, int cmd, unsigned long arg) return ret; + case OREOLED_FORCE_SYNC: + send_general_call(); + break; + default: /* see if the parent class can make any use of it */ ret = CDev::ioctl(filp, cmd, arg); @@ -444,10 +1589,18 @@ OREOLED::send_cmd(oreoled_cmd_t new_cmd) return ret; } +/* return the internal _is_ready flag indicating if initialisation is complete */ +bool +OREOLED::is_ready() +{ + return _is_ready; +} + void oreoled_usage() { - warnx("missing command: try 'start', 'test', 'info', 'off', 'stop', 'rgb 30 40 50' 'macro 4' 'gencall' 'bytes 7 9 6'"); + warnx("missing command: try 'start', 'test', 'info', 'off', 'stop', 'reset', 'rgb 30 40 50', 'macro 4', 'gencall', 'bytes 7 9 6'"); + warnx("bootloader commands: try 'blping', 'blver', 'blappver', 'blappcrc', 'blcolour ', 'blflash', 'blboot'"); warnx("options:"); warnx(" -b i2cbus (%d)", PX4_I2C_BUS_LED); warnx(" -a addr (0x%x)", OREOLED_BASE_I2C_ADDR); @@ -498,8 +1651,21 @@ oreoled_main(int argc, char *argv[]) i2cdevice = PX4_I2C_BUS_LED; } + /* handle update flags */ + bool autoupdate = false; + bool alwaysupdate = false; + + if (argc > 2 && !strcmp(argv[2], "autoupdate")) { + warnx("autoupdate enabled"); + autoupdate = true; + + } else if (argc > 2 && !strcmp(argv[2], "alwaysupdate")) { + warnx("alwaysupdate enabled"); + alwaysupdate = true; + } + /* instantiate driver */ - g_oreoled = new OREOLED(i2cdevice, i2c_addr); + g_oreoled = new OREOLED(i2cdevice, i2c_addr, autoupdate, alwaysupdate); /* check if object was created */ if (g_oreoled == nullptr) { @@ -513,6 +1679,15 @@ oreoled_main(int argc, char *argv[]) errx(1, "failed to start driver"); } + /* wait for up to 20 seconds for the driver become ready */ + for (uint8_t i = 0; i < 20; i++) { + if (g_oreoled != nullptr && g_oreoled->is_ready()) { + break; + } + + sleep(1); + } + exit(0); } @@ -648,6 +1823,170 @@ oreoled_main(int argc, char *argv[]) exit(ret); } + /* send reset request to all LEDS */ + if (!strcmp(verb, "reset")) { + if (argc < 2) { + errx(1, "Usage: oreoled reset"); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + if ((ret = ioctl(fd, OREOLED_SEND_RESET, 0)) != OK) { + errx(1, "failed to run macro"); + } + + close(fd); + exit(ret); + } + + /* attempt to flash all LEDS in bootloader mode*/ + if (!strcmp(verb, "blflash")) { + if (argc < 2) { + errx(1, "Usage: oreoled blflash"); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + if ((ret = ioctl(fd, OREOLED_BL_FLASH, 0)) != OK) { + errx(1, "failed to run flash"); + } + + close(fd); + exit(ret); + } + + /* send bootloader boot request to all LEDS */ + if (!strcmp(verb, "blboot")) { + if (argc < 2) { + errx(1, "Usage: oreoled blboot"); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + if ((ret = ioctl(fd, OREOLED_BL_BOOT_APP, 0)) != OK) { + errx(1, "failed to run boot"); + } + + close(fd); + exit(ret); + } + + /* send bootloader ping all LEDs */ + if (!strcmp(verb, "blping")) { + if (argc < 2) { + errx(1, "Usage: oreoled blping"); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + if ((ret = ioctl(fd, OREOLED_BL_PING, 0)) != OK) { + errx(1, "failed to run blping"); + } + + close(fd); + exit(ret); + } + + /* ask all LEDs for their bootloader version */ + if (!strcmp(verb, "blver")) { + if (argc < 2) { + errx(1, "Usage: oreoled blver"); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + if ((ret = ioctl(fd, OREOLED_BL_VER, 0)) != OK) { + errx(1, "failed to get bootloader version"); + } + + close(fd); + exit(ret); + } + + /* ask all LEDs for their application version */ + if (!strcmp(verb, "blappver")) { + if (argc < 2) { + errx(1, "Usage: oreoled blappver"); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + if ((ret = ioctl(fd, OREOLED_BL_APP_VER, 0)) != OK) { + errx(1, "failed to get boot app version"); + } + + close(fd); + exit(ret); + } + + /* ask all LEDs for their application crc */ + if (!strcmp(verb, "blappcrc")) { + if (argc < 2) { + errx(1, "Usage: oreoled blappcrc"); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + if ((ret = ioctl(fd, OREOLED_BL_APP_CRC, 0)) != OK) { + errx(1, "failed to get boot app crc"); + } + + close(fd); + exit(ret); + } + + /* set the default bootloader LED colour on all LEDs */ + if (!strcmp(verb, "blcolour")) { + if (argc < 4) { + errx(1, "Usage: oreoled blcolour "); + } + + int fd = open(OREOLED0_DEVICE_PATH, 0); + + if (fd == -1) { + errx(1, "Unable to open " OREOLED0_DEVICE_PATH); + } + + uint8_t red = (uint8_t)strtol(argv[2], NULL, 0); + uint8_t green = (uint8_t)strtol(argv[3], NULL, 0); + oreoled_rgbset_t rgb_set = {OREOLED_ALL_INSTANCES, OREOLED_PATTERN_SOLID, red, green, 0}; + + if ((ret = ioctl(fd, OREOLED_BL_SET_COLOUR, (unsigned long)&rgb_set)) != OK) { + errx(1, "failed to set boot startup colours"); + } + + close(fd); + exit(ret); + } + /* send general hardware call to all LEDS */ if (!strcmp(verb, "gencall")) { ret = g_oreoled->send_general_call(); @@ -670,18 +2009,18 @@ oreoled_main(int argc, char *argv[]) } /* check led num */ - sendb.led_num = (uint8_t)strtol(argv[2], NULL, 0); + sendb.led_num = (uint8_t)strtol(argv[optind + 1], NULL, 0); if (sendb.led_num > 3) { errx(1, "led number must be between 0 ~ 3"); } /* get bytes */ - sendb.num_bytes = argc - 3; + sendb.num_bytes = argc - (optind + 2); uint8_t byte_count; for (byte_count = 0; byte_count < sendb.num_bytes; byte_count++) { - sendb.buff[byte_count] = (uint8_t)strtol(argv[byte_count + 3], NULL, 0); + sendb.buff[byte_count] = (uint8_t)strtol(argv[byte_count + optind + 2], NULL, 0); } /* send bytes */