Generic Pelion Device Management example for various U-blox-based boards.

Dependencies:   ublox-at-cellular-interface ublox-cellular-base

DEPRECATED

This example application is not maintained and not recommended. It uses an old version of Mbed OS, Pelion DM, and Arm toolchain. It doesn't work with Mbed Studio.

Please use: https://os.mbed.com/teams/mbed-os-examples/code/mbed-os-example-pelion/

This example is known to work great on the following platforms:

For Odin-W2 please go to Repository link

Follow the Quick-Start instructions: https://cloud.mbed.com/quick-start

UBLOX_C030_U201

UBLOX_C030_R412M

Example functionality

This example showcases the following device functionality:

  • On user button click, increment Pelion LWM2M button resource.
  • Allow the user to change the state of the board LED from Pelion LWM2M led_state resource and PUT request.
  • (currently disabled) Read ADC temperature and ADC vref, and report them as Pelion LWM2M resources.

Use this example with Mbed CLI

1. Import the application into your desktop:

mbed import https://os.mbed.com/teams/ublox/code/pelion-example-common

cd pelion-example-common

2. Install the CLOUD_SDK_API_KEY

mbed config -G CLOUD_SDK_API_KEY <PELION_DM_API_KEY>

For instructions on how to generate your API key, please see the documentation.

3. Initialize firmware credentials (done once per repository). You can use the following command:

mbed dm init -d "<your company name in Pelion DM>" --model-name "<product model identifier>" -q --force

If above command do not work for your Mbed CLI, please consider upgrading Mbed CLI to version 1.8.x or above.

4. Compile and program:

mbed compile -t <toolchain> -m <TARGET_BOARD>

(supported toolchains : GCC_ARM / ARM / IAR)

Committer:
screamer
Date:
Mon Dec 10 21:58:43 2018 +0000
Revision:
0:a076a1bbe630
Initial revision

Who changed what in which revision?

UserRevisionLine numberNew contents of line
screamer 0:a076a1bbe630 1 /* mbed Microcontroller Library
screamer 0:a076a1bbe630 2 * Copyright (c) 2018 ARM Limited
screamer 0:a076a1bbe630 3 *
screamer 0:a076a1bbe630 4 * Licensed under the Apache License, Version 2.0 (the "License");
screamer 0:a076a1bbe630 5 * you may not use this file except in compliance with the License.
screamer 0:a076a1bbe630 6 * You may obtain a copy of the License at
screamer 0:a076a1bbe630 7 *
screamer 0:a076a1bbe630 8 * http://www.apache.org/licenses/LICENSE-2.0
screamer 0:a076a1bbe630 9 *
screamer 0:a076a1bbe630 10 * Unless required by applicable law or agreed to in writing, software
screamer 0:a076a1bbe630 11 * distributed under the License is distributed on an "AS IS" BASIS,
screamer 0:a076a1bbe630 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
screamer 0:a076a1bbe630 13 * See the License for the specific language governing permissions and
screamer 0:a076a1bbe630 14 * limitations under the License.
screamer 0:a076a1bbe630 15 */
screamer 0:a076a1bbe630 16 #include "greentea-client/test_env.h"
screamer 0:a076a1bbe630 17 #include "unity.h"
screamer 0:a076a1bbe630 18 #include "utest.h"
screamer 0:a076a1bbe630 19 #include "QSPIFBlockDevice.h"
screamer 0:a076a1bbe630 20 #include "mbed_trace.h"
screamer 0:a076a1bbe630 21 #include "rtos/Thread.h"
screamer 0:a076a1bbe630 22 #include <stdlib.h>
screamer 0:a076a1bbe630 23
screamer 0:a076a1bbe630 24 using namespace utest::v1;
screamer 0:a076a1bbe630 25
screamer 0:a076a1bbe630 26 #define TEST_BLOCK_COUNT 10
screamer 0:a076a1bbe630 27 #define TEST_ERROR_MASK 16
screamer 0:a076a1bbe630 28 #define QSPIF_TEST_NUM_OF_THREADS 5
screamer 0:a076a1bbe630 29
screamer 0:a076a1bbe630 30 const struct {
screamer 0:a076a1bbe630 31 const char *name;
screamer 0:a076a1bbe630 32 bd_size_t (BlockDevice::*method)() const;
screamer 0:a076a1bbe630 33 } ATTRS[] = {
screamer 0:a076a1bbe630 34 {"read size", &BlockDevice::get_read_size},
screamer 0:a076a1bbe630 35 {"program size", &BlockDevice::get_program_size},
screamer 0:a076a1bbe630 36 {"erase size", &BlockDevice::get_erase_size},
screamer 0:a076a1bbe630 37 {"total size", &BlockDevice::size},
screamer 0:a076a1bbe630 38 };
screamer 0:a076a1bbe630 39
screamer 0:a076a1bbe630 40 static SingletonPtr<PlatformMutex> _mutex;
screamer 0:a076a1bbe630 41
screamer 0:a076a1bbe630 42
screamer 0:a076a1bbe630 43 // Mutex is protecting rand() per srand for buffer writing and verification.
screamer 0:a076a1bbe630 44 // Mutex is also protecting printouts for clear logs.
screamer 0:a076a1bbe630 45 // Mutex is NOT protecting Block Device actions: erase/program/read - which is the purpose of the multithreaded test!
screamer 0:a076a1bbe630 46 void basic_erase_program_read_test(QSPIFBlockDevice &blockD, bd_size_t block_size, uint8_t *write_block,
screamer 0:a076a1bbe630 47 uint8_t *read_block, unsigned addrwidth)
screamer 0:a076a1bbe630 48 {
screamer 0:a076a1bbe630 49 int err = 0;
screamer 0:a076a1bbe630 50 _mutex->lock();
screamer 0:a076a1bbe630 51 // Find a random block
screamer 0:a076a1bbe630 52 bd_addr_t block = (rand() * block_size) % blockD.size();
screamer 0:a076a1bbe630 53
screamer 0:a076a1bbe630 54 // Use next random number as temporary seed to keep
screamer 0:a076a1bbe630 55 // the address progressing in the pseudorandom sequence
screamer 0:a076a1bbe630 56 unsigned seed = rand();
screamer 0:a076a1bbe630 57
screamer 0:a076a1bbe630 58 // Fill with random sequence
screamer 0:a076a1bbe630 59 srand(seed);
screamer 0:a076a1bbe630 60 for (bd_size_t i_ind = 0; i_ind < block_size; i_ind++) {
screamer 0:a076a1bbe630 61 write_block[i_ind] = 0xff & rand();
screamer 0:a076a1bbe630 62 }
screamer 0:a076a1bbe630 63 // Write, sync, and read the block
screamer 0:a076a1bbe630 64 utest_printf("\ntest %0*llx:%llu...", addrwidth, block, block_size);
screamer 0:a076a1bbe630 65 _mutex->unlock();
screamer 0:a076a1bbe630 66
screamer 0:a076a1bbe630 67 err = blockD.erase(block, block_size);
screamer 0:a076a1bbe630 68 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 69
screamer 0:a076a1bbe630 70 err = blockD.program(write_block, block, block_size);
screamer 0:a076a1bbe630 71 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 72
screamer 0:a076a1bbe630 73 err = blockD.read(read_block, block, block_size);
screamer 0:a076a1bbe630 74 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 75
screamer 0:a076a1bbe630 76 _mutex->lock();
screamer 0:a076a1bbe630 77 // Check that the data was unmodified
screamer 0:a076a1bbe630 78 srand(seed);
screamer 0:a076a1bbe630 79 int val_rand;
screamer 0:a076a1bbe630 80 for (bd_size_t i_ind = 0; i_ind < block_size; i_ind++) {
screamer 0:a076a1bbe630 81 val_rand = rand();
screamer 0:a076a1bbe630 82 if ((0xff & val_rand) != read_block[i_ind]) {
screamer 0:a076a1bbe630 83 utest_printf("\n Assert Failed Buf Read - block:size: %llx:%llu \n", block, block_size);
screamer 0:a076a1bbe630 84 utest_printf("\n pos: %llu, exp: %02x, act: %02x, wrt: %02x \n", i_ind, (0xff & val_rand), read_block[i_ind],
screamer 0:a076a1bbe630 85 write_block[i_ind]);
screamer 0:a076a1bbe630 86 }
screamer 0:a076a1bbe630 87 TEST_ASSERT_EQUAL(0xff & val_rand, read_block[i_ind]);
screamer 0:a076a1bbe630 88 }
screamer 0:a076a1bbe630 89 _mutex->unlock();
screamer 0:a076a1bbe630 90 }
screamer 0:a076a1bbe630 91
screamer 0:a076a1bbe630 92 void test_qspif_random_program_read_erase()
screamer 0:a076a1bbe630 93 {
screamer 0:a076a1bbe630 94 utest_printf("\nTest Random Program Read Erase Starts..\n");
screamer 0:a076a1bbe630 95
screamer 0:a076a1bbe630 96 QSPIFBlockDevice blockD(QSPI_FLASH1_IO0, QSPI_FLASH1_IO1, QSPI_FLASH1_IO2, QSPI_FLASH1_IO3,
screamer 0:a076a1bbe630 97 QSPI_FLASH1_SCK, QSPI_FLASH1_CSN, QSPIF_POLARITY_MODE_0, MBED_CONF_QSPIF_QSPI_FREQ);
screamer 0:a076a1bbe630 98
screamer 0:a076a1bbe630 99 int err = blockD.init();
screamer 0:a076a1bbe630 100 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 101
screamer 0:a076a1bbe630 102 for (unsigned atr = 0; atr < sizeof(ATTRS) / sizeof(ATTRS[0]); atr++) {
screamer 0:a076a1bbe630 103 static const char *prefixes[] = {"", "k", "M", "G"};
screamer 0:a076a1bbe630 104 for (int i_ind = 3; i_ind >= 0; i_ind--) {
screamer 0:a076a1bbe630 105 bd_size_t size = (blockD.*ATTRS[atr].method)();
screamer 0:a076a1bbe630 106 if (size >= (1ULL << 10 * i_ind)) {
screamer 0:a076a1bbe630 107 utest_printf("%s: %llu%sbytes (%llubytes)\n",
screamer 0:a076a1bbe630 108 ATTRS[atr].name, size >> 10 * i_ind, prefixes[i_ind], size);
screamer 0:a076a1bbe630 109 break;
screamer 0:a076a1bbe630 110 }
screamer 0:a076a1bbe630 111 }
screamer 0:a076a1bbe630 112 }
screamer 0:a076a1bbe630 113
screamer 0:a076a1bbe630 114 bd_size_t block_size = blockD.get_erase_size();
screamer 0:a076a1bbe630 115 unsigned addrwidth = ceil(log(float(blockD.size() - 1)) / log(float(16))) + 1;
screamer 0:a076a1bbe630 116
screamer 0:a076a1bbe630 117 uint8_t *write_block = new (std::nothrow) uint8_t[block_size];
screamer 0:a076a1bbe630 118 uint8_t *read_block = new (std::nothrow) uint8_t[block_size];
screamer 0:a076a1bbe630 119 if (!write_block || !read_block) {
screamer 0:a076a1bbe630 120 utest_printf("\n Not enough memory for test");
screamer 0:a076a1bbe630 121 goto end;
screamer 0:a076a1bbe630 122 }
screamer 0:a076a1bbe630 123
screamer 0:a076a1bbe630 124 for (int b = 0; b < TEST_BLOCK_COUNT; b++) {
screamer 0:a076a1bbe630 125 basic_erase_program_read_test(blockD, block_size, write_block, read_block, addrwidth);
screamer 0:a076a1bbe630 126 }
screamer 0:a076a1bbe630 127
screamer 0:a076a1bbe630 128 err = blockD.deinit();
screamer 0:a076a1bbe630 129 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 130
screamer 0:a076a1bbe630 131 end:
screamer 0:a076a1bbe630 132 delete[] write_block;
screamer 0:a076a1bbe630 133 delete[] read_block;
screamer 0:a076a1bbe630 134 }
screamer 0:a076a1bbe630 135
screamer 0:a076a1bbe630 136 void test_qspif_unaligned_erase()
screamer 0:a076a1bbe630 137 {
screamer 0:a076a1bbe630 138
screamer 0:a076a1bbe630 139 utest_printf("\nTest Unaligned Erase Starts..\n");
screamer 0:a076a1bbe630 140
screamer 0:a076a1bbe630 141 QSPIFBlockDevice blockD(QSPI_FLASH1_IO0, QSPI_FLASH1_IO1, QSPI_FLASH1_IO2, QSPI_FLASH1_IO3,
screamer 0:a076a1bbe630 142 QSPI_FLASH1_SCK, QSPI_FLASH1_CSN, QSPIF_POLARITY_MODE_0, MBED_CONF_QSPIF_QSPI_FREQ);
screamer 0:a076a1bbe630 143
screamer 0:a076a1bbe630 144 int err = blockD.init();
screamer 0:a076a1bbe630 145 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 146
screamer 0:a076a1bbe630 147 for (unsigned atr = 0; atr < sizeof(ATTRS) / sizeof(ATTRS[0]); atr++) {
screamer 0:a076a1bbe630 148 static const char *prefixes[] = {"", "k", "M", "G"};
screamer 0:a076a1bbe630 149 for (int i_ind = 3; i_ind >= 0; i_ind--) {
screamer 0:a076a1bbe630 150 bd_size_t size = (blockD.*ATTRS[atr].method)();
screamer 0:a076a1bbe630 151 if (size >= (1ULL << 10 * i_ind)) {
screamer 0:a076a1bbe630 152 utest_printf("%s: %llu%sbytes (%llubytes)\n",
screamer 0:a076a1bbe630 153 ATTRS[atr].name, size >> 10 * i_ind, prefixes[i_ind], size);
screamer 0:a076a1bbe630 154 break;
screamer 0:a076a1bbe630 155 }
screamer 0:a076a1bbe630 156 }
screamer 0:a076a1bbe630 157 }
screamer 0:a076a1bbe630 158
screamer 0:a076a1bbe630 159 bd_addr_t addr = 0;
screamer 0:a076a1bbe630 160 bd_size_t sector_erase_size = blockD.get_erase_size(addr);
screamer 0:a076a1bbe630 161 unsigned addrwidth = ceil(log(float(blockD.size() - 1)) / log(float(16))) + 1;
screamer 0:a076a1bbe630 162
screamer 0:a076a1bbe630 163 utest_printf("\ntest %0*llx:%llu...", addrwidth, addr, sector_erase_size);
screamer 0:a076a1bbe630 164
screamer 0:a076a1bbe630 165 //unaligned start address
screamer 0:a076a1bbe630 166 addr += 1;
screamer 0:a076a1bbe630 167 err = blockD.erase(addr, sector_erase_size - 1);
screamer 0:a076a1bbe630 168 TEST_ASSERT_EQUAL(QSPIF_BD_ERROR_INVALID_ERASE_PARAMS, err);
screamer 0:a076a1bbe630 169
screamer 0:a076a1bbe630 170 err = blockD.erase(addr, sector_erase_size);
screamer 0:a076a1bbe630 171 TEST_ASSERT_EQUAL(QSPIF_BD_ERROR_INVALID_ERASE_PARAMS, err);
screamer 0:a076a1bbe630 172
screamer 0:a076a1bbe630 173 err = blockD.erase(addr, 1);
screamer 0:a076a1bbe630 174 TEST_ASSERT_EQUAL(QSPIF_BD_ERROR_INVALID_ERASE_PARAMS, err);
screamer 0:a076a1bbe630 175
screamer 0:a076a1bbe630 176 //unaligned end address
screamer 0:a076a1bbe630 177 addr = 0;
screamer 0:a076a1bbe630 178
screamer 0:a076a1bbe630 179 err = blockD.erase(addr, 1);
screamer 0:a076a1bbe630 180 TEST_ASSERT_EQUAL(QSPIF_BD_ERROR_INVALID_ERASE_PARAMS, err);
screamer 0:a076a1bbe630 181
screamer 0:a076a1bbe630 182 err = blockD.erase(addr, sector_erase_size + 1);
screamer 0:a076a1bbe630 183 TEST_ASSERT_EQUAL(QSPIF_BD_ERROR_INVALID_ERASE_PARAMS, err);
screamer 0:a076a1bbe630 184
screamer 0:a076a1bbe630 185 //erase size exceeds flash device size
screamer 0:a076a1bbe630 186 err = blockD.erase(addr, blockD.size() + 1);
screamer 0:a076a1bbe630 187 TEST_ASSERT_EQUAL(QSPIF_BD_ERROR_INVALID_ERASE_PARAMS, err);
screamer 0:a076a1bbe630 188
screamer 0:a076a1bbe630 189 // Valid erase
screamer 0:a076a1bbe630 190 err = blockD.erase(addr, sector_erase_size);
screamer 0:a076a1bbe630 191 TEST_ASSERT_EQUAL(QSPIF_BD_ERROR_OK, err);
screamer 0:a076a1bbe630 192
screamer 0:a076a1bbe630 193 err = blockD.deinit();
screamer 0:a076a1bbe630 194 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 195 }
screamer 0:a076a1bbe630 196
screamer 0:a076a1bbe630 197
screamer 0:a076a1bbe630 198
screamer 0:a076a1bbe630 199 static void test_qspif_thread_job(void *vBlockD/*, int thread_num*/)
screamer 0:a076a1bbe630 200 {
screamer 0:a076a1bbe630 201 static int thread_num = 0;
screamer 0:a076a1bbe630 202 thread_num++;
screamer 0:a076a1bbe630 203 QSPIFBlockDevice *blockD = (QSPIFBlockDevice *)vBlockD;
screamer 0:a076a1bbe630 204 utest_printf("\n Thread %d Started \n", thread_num);
screamer 0:a076a1bbe630 205
screamer 0:a076a1bbe630 206 bd_size_t block_size = blockD->get_erase_size();
screamer 0:a076a1bbe630 207 unsigned addrwidth = ceil(log(float(blockD->size() - 1)) / log(float(16))) + 1;
screamer 0:a076a1bbe630 208
screamer 0:a076a1bbe630 209 uint8_t *write_block = new (std::nothrow) uint8_t[block_size];
screamer 0:a076a1bbe630 210 uint8_t *read_block = new (std::nothrow) uint8_t[block_size];
screamer 0:a076a1bbe630 211 if (!write_block || !read_block) {
screamer 0:a076a1bbe630 212 utest_printf("\n Not enough memory for test");
screamer 0:a076a1bbe630 213 goto end;
screamer 0:a076a1bbe630 214 }
screamer 0:a076a1bbe630 215
screamer 0:a076a1bbe630 216 for (int b = 0; b < TEST_BLOCK_COUNT; b++) {
screamer 0:a076a1bbe630 217 basic_erase_program_read_test((*blockD), block_size, write_block, read_block, addrwidth);
screamer 0:a076a1bbe630 218 }
screamer 0:a076a1bbe630 219
screamer 0:a076a1bbe630 220 end:
screamer 0:a076a1bbe630 221 delete[] write_block;
screamer 0:a076a1bbe630 222 delete[] read_block;
screamer 0:a076a1bbe630 223 }
screamer 0:a076a1bbe630 224
screamer 0:a076a1bbe630 225 void test_qspif_multi_threads()
screamer 0:a076a1bbe630 226 {
screamer 0:a076a1bbe630 227
screamer 0:a076a1bbe630 228 utest_printf("\nTest Multi Threaded Erase/Program/Read Starts..\n");
screamer 0:a076a1bbe630 229
screamer 0:a076a1bbe630 230 QSPIFBlockDevice blockD(QSPI_FLASH1_IO0, QSPI_FLASH1_IO1, QSPI_FLASH1_IO2, QSPI_FLASH1_IO3,
screamer 0:a076a1bbe630 231 QSPI_FLASH1_SCK, QSPI_FLASH1_CSN, QSPIF_POLARITY_MODE_0, MBED_CONF_QSPIF_QSPI_FREQ);
screamer 0:a076a1bbe630 232
screamer 0:a076a1bbe630 233 int err = blockD.init();
screamer 0:a076a1bbe630 234 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 235
screamer 0:a076a1bbe630 236 for (unsigned atr = 0; atr < sizeof(ATTRS) / sizeof(ATTRS[0]); atr++) {
screamer 0:a076a1bbe630 237 static const char *prefixes[] = {"", "k", "M", "G"};
screamer 0:a076a1bbe630 238 for (int i_ind = 3; i_ind >= 0; i_ind--) {
screamer 0:a076a1bbe630 239 bd_size_t size = (blockD.*ATTRS[atr].method)();
screamer 0:a076a1bbe630 240 if (size >= (1ULL << 10 * i_ind)) {
screamer 0:a076a1bbe630 241 utest_printf("%s: %llu%sbytes (%llubytes)\n",
screamer 0:a076a1bbe630 242 ATTRS[atr].name, size >> 10 * i_ind, prefixes[i_ind], size);
screamer 0:a076a1bbe630 243 break;
screamer 0:a076a1bbe630 244 }
screamer 0:a076a1bbe630 245 }
screamer 0:a076a1bbe630 246 }
screamer 0:a076a1bbe630 247
screamer 0:a076a1bbe630 248 rtos::Thread qspif_bd_thread[QSPIF_TEST_NUM_OF_THREADS];
screamer 0:a076a1bbe630 249
screamer 0:a076a1bbe630 250 osStatus threadStatus;
screamer 0:a076a1bbe630 251 int i_ind;
screamer 0:a076a1bbe630 252
screamer 0:a076a1bbe630 253 for (i_ind = 0; i_ind < QSPIF_TEST_NUM_OF_THREADS; i_ind++) {
screamer 0:a076a1bbe630 254 threadStatus = qspif_bd_thread[i_ind].start(test_qspif_thread_job, (void *)&blockD);
screamer 0:a076a1bbe630 255 if (threadStatus != 0) {
screamer 0:a076a1bbe630 256 utest_printf("\n Thread %d Start Failed!", i_ind + 1);
screamer 0:a076a1bbe630 257 }
screamer 0:a076a1bbe630 258 }
screamer 0:a076a1bbe630 259
screamer 0:a076a1bbe630 260 for (i_ind = 0; i_ind < QSPIF_TEST_NUM_OF_THREADS; i_ind++) {
screamer 0:a076a1bbe630 261 qspif_bd_thread[i_ind].join();
screamer 0:a076a1bbe630 262 }
screamer 0:a076a1bbe630 263
screamer 0:a076a1bbe630 264 err = blockD.deinit();
screamer 0:a076a1bbe630 265 TEST_ASSERT_EQUAL(0, err);
screamer 0:a076a1bbe630 266 }
screamer 0:a076a1bbe630 267
screamer 0:a076a1bbe630 268
screamer 0:a076a1bbe630 269
screamer 0:a076a1bbe630 270
screamer 0:a076a1bbe630 271 // Test setup
screamer 0:a076a1bbe630 272 utest::v1::status_t test_setup(const size_t number_of_cases)
screamer 0:a076a1bbe630 273 {
screamer 0:a076a1bbe630 274 GREENTEA_SETUP(60, "default_auto");
screamer 0:a076a1bbe630 275 return verbose_test_setup_handler(number_of_cases);
screamer 0:a076a1bbe630 276 }
screamer 0:a076a1bbe630 277
screamer 0:a076a1bbe630 278 Case cases[] = {
screamer 0:a076a1bbe630 279 Case("Testing unaligned erase blocks", test_qspif_unaligned_erase),
screamer 0:a076a1bbe630 280 Case("Testing read write random blocks", test_qspif_random_program_read_erase),
screamer 0:a076a1bbe630 281 Case("Testing Multi Threads Erase Program Read", test_qspif_multi_threads)
screamer 0:a076a1bbe630 282 };
screamer 0:a076a1bbe630 283
screamer 0:a076a1bbe630 284 Specification specification(test_setup, cases);
screamer 0:a076a1bbe630 285
screamer 0:a076a1bbe630 286
screamer 0:a076a1bbe630 287 int main()
screamer 0:a076a1bbe630 288 {
screamer 0:a076a1bbe630 289 mbed_trace_init();
screamer 0:a076a1bbe630 290 utest_printf("MAIN STARTS\n");
screamer 0:a076a1bbe630 291 return !Harness::run(specification);
screamer 0:a076a1bbe630 292 }