A library for easier setup and prototyping of IoT devices (pucks), by collecting everything that is common for all pucks in one place.

Dependencies:   BLE_API nRF51822

Dependents:   ir-puck display-puck ir-puck2 BLE_ScoringDevice ... more

/media/uploads/stiaje/header.jpg

Introduction

Raspberry Pi took the maker community by storm when it launched in 2012. With its internet access it allowed small projects to be internet-of-things enabled. We have created a platform to take this one step further.

Our platform, called the Puck platform, is an internet of things platform for mbed. mbed makes it easy to program embedded hardware for people new to embedded systems. Our platform is built upon the first mbed chip with Bluetooth, the nRF51822 created by Nordic Semiconductor. We hope to create a community around these BLE devices where people contribute to the project, and share their designs with each other. Everything is open-source, of course, with lots of supporting materials.

We make it easy to rapidly prototype and develop Bluetooth LE enabled devices - get up and running in under 10 lines of code.

Tutorials and in-depth documentation is available at the project's GitHub page

Pucks

We've developed a handful of awesome examples to demonstrate the platform. These examples are named 'Pucks'. By talking to the internet through your smartphone, the barrier to creating your own Internet of Things device is lower than ever.

Committer:
stiaje
Date:
Fri Aug 01 07:45:38 2014 +0000
Revision:
10:67e4694f2d74
Merge 'Update display puck to use new lib'

Who changed what in which revision?

UserRevisionLine numberNew contents of line
stiaje 10:67e4694f2d74 1 #ifndef __PUCK_HPP__
stiaje 10:67e4694f2d74 2 #define __PUCK_HPP__
stiaje 10:67e4694f2d74 3
stiaje 10:67e4694f2d74 4 #include "BLEDevice.h"
stiaje 10:67e4694f2d74 5 #include <vector>
stiaje 10:67e4694f2d74 6 #include "Log.h"
stiaje 10:67e4694f2d74 7
stiaje 10:67e4694f2d74 8 enum PuckState {
stiaje 10:67e4694f2d74 9 CONNECTING,
stiaje 10:67e4694f2d74 10 CONNECTED,
stiaje 10:67e4694f2d74 11 ADVERTISING,
stiaje 10:67e4694f2d74 12 DISCONNECTED
stiaje 10:67e4694f2d74 13 };
stiaje 10:67e4694f2d74 14
stiaje 10:67e4694f2d74 15 const UUID stringToUUID(const char* str);
stiaje 10:67e4694f2d74 16
stiaje 10:67e4694f2d74 17 typedef void (*CharacteristicWriteCallback)(uint8_t* value);
stiaje 10:67e4694f2d74 18
stiaje 10:67e4694f2d74 19 typedef struct {
stiaje 10:67e4694f2d74 20 const UUID* uuid;
stiaje 10:67e4694f2d74 21 std::vector<CharacteristicWriteCallback>* callbacks;
stiaje 10:67e4694f2d74 22 } CharacteristicWriteCallbacks;
stiaje 10:67e4694f2d74 23
stiaje 10:67e4694f2d74 24
stiaje 10:67e4694f2d74 25 class Puck {
stiaje 10:67e4694f2d74 26 private:
stiaje 10:67e4694f2d74 27 Puck() {}
stiaje 10:67e4694f2d74 28 Puck(const Puck&);
stiaje 10:67e4694f2d74 29 Puck& operator=(const Puck&);
stiaje 10:67e4694f2d74 30
stiaje 10:67e4694f2d74 31 BLEDevice ble;
stiaje 10:67e4694f2d74 32 uint8_t beaconPayload[25];
stiaje 10:67e4694f2d74 33 PuckState state;
stiaje 10:67e4694f2d74 34 std::vector<GattService*> services;
stiaje 10:67e4694f2d74 35 std::vector<GattCharacteristic*> characteristics;
stiaje 10:67e4694f2d74 36 std::vector<CharacteristicWriteCallbacks*> writeCallbacks;
stiaje 10:67e4694f2d74 37 std::vector<CharacteristicWriteCallback> pendingCallbackStack;
stiaje 10:67e4694f2d74 38 std::vector<uint8_t*> pendingCallbackParameterStack;
stiaje 10:67e4694f2d74 39
stiaje 10:67e4694f2d74 40 GattCharacteristic **previousCharacteristics;
stiaje 10:67e4694f2d74 41
stiaje 10:67e4694f2d74 42 public:
stiaje 10:67e4694f2d74 43 static Puck &getPuck();
stiaje 10:67e4694f2d74 44
stiaje 10:67e4694f2d74 45 BLEDevice &getBle() { return ble; }
stiaje 10:67e4694f2d74 46 PuckState getState() { return state; }
stiaje 10:67e4694f2d74 47 void setState(PuckState state);
stiaje 10:67e4694f2d74 48 void init(uint16_t minor);
stiaje 10:67e4694f2d74 49 void startAdvertising();
stiaje 10:67e4694f2d74 50 void stopAdvertising();
stiaje 10:67e4694f2d74 51 bool drive();
stiaje 10:67e4694f2d74 52 void onDataWritten(uint16_t handle);
stiaje 10:67e4694f2d74 53 void addCharacteristic(const UUID serviceUuid, const UUID characteristicUuid, int bytes, int properties = 0xA);
stiaje 10:67e4694f2d74 54 void onCharacteristicWrite(const UUID uuid, CharacteristicWriteCallback callback);
stiaje 10:67e4694f2d74 55 void updateCharacteristicValue(const UUID uuid, uint8_t* value, int length);
stiaje 10:67e4694f2d74 56 uint8_t* getCharacteristicValue(const UUID uuid);
stiaje 10:67e4694f2d74 57 };
stiaje 10:67e4694f2d74 58
stiaje 10:67e4694f2d74 59 Puck &Puck::getPuck() {
stiaje 10:67e4694f2d74 60 static Puck _puckSingletonInstance;
stiaje 10:67e4694f2d74 61 return _puckSingletonInstance;
stiaje 10:67e4694f2d74 62 }
stiaje 10:67e4694f2d74 63
stiaje 10:67e4694f2d74 64
stiaje 10:67e4694f2d74 65 void onDisconnection(void) {
stiaje 10:67e4694f2d74 66 LOG_INFO("Disconnected.\n");
stiaje 10:67e4694f2d74 67 Puck::getPuck().setState(DISCONNECTED);
stiaje 10:67e4694f2d74 68 }
stiaje 10:67e4694f2d74 69
stiaje 10:67e4694f2d74 70 void onConnection(void) {
stiaje 10:67e4694f2d74 71 LOG_INFO("Connected.\n");
stiaje 10:67e4694f2d74 72 Puck::getPuck().setState(CONNECTED);
stiaje 10:67e4694f2d74 73 }
stiaje 10:67e4694f2d74 74
stiaje 10:67e4694f2d74 75 void onDataWrittenCallback(uint16_t handle) {
stiaje 10:67e4694f2d74 76 Puck::getPuck().onDataWritten(handle);
stiaje 10:67e4694f2d74 77 }
stiaje 10:67e4694f2d74 78
stiaje 10:67e4694f2d74 79 bool isEqualUUID(const UUID* uuidA, const UUID uuidB) {
stiaje 10:67e4694f2d74 80 const uint8_t* uuidABase = uuidA->getBaseUUID();
stiaje 10:67e4694f2d74 81 const uint8_t* uuidBBase = uuidB.getBaseUUID();
stiaje 10:67e4694f2d74 82 if(uuidA->getShortUUID() != uuidB.getShortUUID()) {
stiaje 10:67e4694f2d74 83 return false;
stiaje 10:67e4694f2d74 84 }
stiaje 10:67e4694f2d74 85 for(int i = 0; i < 16; i++) {
stiaje 10:67e4694f2d74 86 if(uuidABase[i] != uuidBBase[i]) {
stiaje 10:67e4694f2d74 87 return false;
stiaje 10:67e4694f2d74 88 }
stiaje 10:67e4694f2d74 89 }
stiaje 10:67e4694f2d74 90 return true;
stiaje 10:67e4694f2d74 91 }
stiaje 10:67e4694f2d74 92
stiaje 10:67e4694f2d74 93 const UUID stringToUUID(const char* str) {
stiaje 10:67e4694f2d74 94 uint8_t array[16];
stiaje 10:67e4694f2d74 95 for(int i = 0; i < 16; i++) {
stiaje 10:67e4694f2d74 96 array[i] = str[i];
stiaje 10:67e4694f2d74 97 }
stiaje 10:67e4694f2d74 98 return UUID(array);
stiaje 10:67e4694f2d74 99 }
stiaje 10:67e4694f2d74 100
stiaje 10:67e4694f2d74 101 void Puck::setState(PuckState state) {
stiaje 10:67e4694f2d74 102 LOG_DEBUG("Changed state to %i\n", state);
stiaje 10:67e4694f2d74 103 this->state = state;
stiaje 10:67e4694f2d74 104 }
stiaje 10:67e4694f2d74 105
stiaje 10:67e4694f2d74 106 void Puck::init(uint16_t minor) {
stiaje 10:67e4694f2d74 107 /*
stiaje 10:67e4694f2d74 108 * The Beacon payload (encapsulated within the MSD advertising data structure)
stiaje 10:67e4694f2d74 109 * has the following composition:
stiaje 10:67e4694f2d74 110 * 128-Bit UUID = E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61
stiaje 10:67e4694f2d74 111 * Major/Minor = 1337 / XXXX
stiaje 10:67e4694f2d74 112 * Tx Power = C8
stiaje 10:67e4694f2d74 113 */
stiaje 10:67e4694f2d74 114 uint8_t beaconPayloadTemplate[] = {
stiaje 10:67e4694f2d74 115 0x00, 0x00, // Company identifier code (0x004C == Apple)
stiaje 10:67e4694f2d74 116 0x02, // ID
stiaje 10:67e4694f2d74 117 0x15, // length of the remaining payload
stiaje 10:67e4694f2d74 118 0xE2, 0x0A, 0x39, 0xF4, 0x73, 0xF5, 0x4B, 0xC4, // UUID
stiaje 10:67e4694f2d74 119 0xA1, 0x2F, 0x17, 0xD1, 0xAD, 0x07, 0xA9, 0x61,
stiaje 10:67e4694f2d74 120 0x13, 0x37, // the major value to differenciate a location (Our app requires 1337 as major number)
stiaje 10:67e4694f2d74 121 0x00, 0x00, // the minor value to differenciate a location (Change this to differentiate location pucks)
stiaje 10:67e4694f2d74 122 0xC8 // 2's complement of the Tx power (-56dB)
stiaje 10:67e4694f2d74 123 };
stiaje 10:67e4694f2d74 124 beaconPayloadTemplate[22] = minor >> 8;
stiaje 10:67e4694f2d74 125 beaconPayloadTemplate[23] = minor & 255;
stiaje 10:67e4694f2d74 126
stiaje 10:67e4694f2d74 127 for (int i=0; i < 25; i++) {
stiaje 10:67e4694f2d74 128 beaconPayload[i] = beaconPayloadTemplate[i];
stiaje 10:67e4694f2d74 129 }
stiaje 10:67e4694f2d74 130
stiaje 10:67e4694f2d74 131 ble.init();
stiaje 10:67e4694f2d74 132 LOG_VERBOSE("Inited BLEDevice.\n");
stiaje 10:67e4694f2d74 133 setState(DISCONNECTED);
stiaje 10:67e4694f2d74 134
stiaje 10:67e4694f2d74 135 ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);
stiaje 10:67e4694f2d74 136 LOG_VERBOSE("Accumulate advertising payload: BREDR_NOT_SUPPORTED.\n");
stiaje 10:67e4694f2d74 137
stiaje 10:67e4694f2d74 138 ble.accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, beaconPayload, sizeof(beaconPayload));
stiaje 10:67e4694f2d74 139 LOG_VERBOSE("Accumulate advertising payload: beacon data.\n");
stiaje 10:67e4694f2d74 140
stiaje 10:67e4694f2d74 141 ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
stiaje 10:67e4694f2d74 142 LOG_VERBOSE("Setting advertising type: ADV_CONNECTABLE_UNDIRECTED.\n");
stiaje 10:67e4694f2d74 143
stiaje 10:67e4694f2d74 144 int hundredMillisecondsInAdvertisingIntervalFormat = 160;
stiaje 10:67e4694f2d74 145 ble.setAdvertisingInterval(hundredMillisecondsInAdvertisingIntervalFormat);
stiaje 10:67e4694f2d74 146 LOG_VERBOSE("Set advertising interval: 160 (100 ms).\n");
stiaje 10:67e4694f2d74 147
stiaje 10:67e4694f2d74 148 ble.onDisconnection(onDisconnection);
stiaje 10:67e4694f2d74 149 ble.onConnection(onConnection);
stiaje 10:67e4694f2d74 150 ble.onDataWritten(onDataWrittenCallback);
stiaje 10:67e4694f2d74 151
stiaje 10:67e4694f2d74 152 for(int i = 0; i < services.size(); i++) {
stiaje 10:67e4694f2d74 153 ble.addService(*services[i]);
stiaje 10:67e4694f2d74 154 LOG_VERBOSE("Added service %x to BLEDevice\n", services[i]);
stiaje 10:67e4694f2d74 155 }
stiaje 10:67e4694f2d74 156
stiaje 10:67e4694f2d74 157 LOG_INFO("Inited puck as 0x%X.\n", minor);
stiaje 10:67e4694f2d74 158 }
stiaje 10:67e4694f2d74 159
stiaje 10:67e4694f2d74 160 void Puck::startAdvertising() {
stiaje 10:67e4694f2d74 161 ble.startAdvertising();
stiaje 10:67e4694f2d74 162 LOG_INFO("Starting to advertise.\n");
stiaje 10:67e4694f2d74 163 setState(ADVERTISING);
stiaje 10:67e4694f2d74 164 }
stiaje 10:67e4694f2d74 165
stiaje 10:67e4694f2d74 166 void Puck::stopAdvertising() {
stiaje 10:67e4694f2d74 167 if(state == ADVERTISING) {
stiaje 10:67e4694f2d74 168 ble.stopAdvertising();
stiaje 10:67e4694f2d74 169 LOG_INFO("Stopped advertising.\n");
stiaje 10:67e4694f2d74 170 setState(DISCONNECTED);
stiaje 10:67e4694f2d74 171 } else {
stiaje 10:67e4694f2d74 172 LOG_WARN("Tried to stop advertising, but advertising is already stopped!\n");
stiaje 10:67e4694f2d74 173 }
stiaje 10:67e4694f2d74 174 }
stiaje 10:67e4694f2d74 175
stiaje 10:67e4694f2d74 176
stiaje 10:67e4694f2d74 177 void Puck::addCharacteristic(const UUID serviceUuid, const UUID characteristicUuid, int bytes, int properties) {
stiaje 10:67e4694f2d74 178 MBED_ASSERT(bytes <= 20);
stiaje 10:67e4694f2d74 179
stiaje 10:67e4694f2d74 180 uint16_t size = sizeof(uint8_t) * bytes;
stiaje 10:67e4694f2d74 181 uint8_t* value = (uint8_t*) malloc(size);
stiaje 10:67e4694f2d74 182 GattCharacteristic* characteristic = new GattCharacteristic(characteristicUuid, value, size, size, properties);
stiaje 10:67e4694f2d74 183 characteristics.push_back(characteristic);
stiaje 10:67e4694f2d74 184 GattService* service = NULL;
stiaje 10:67e4694f2d74 185 int removeIndex = -1;
stiaje 10:67e4694f2d74 186 for(int i = 0; i < services.size(); i++) {
stiaje 10:67e4694f2d74 187 if(isEqualUUID(&services[i]->getUUID(), serviceUuid)) {
stiaje 10:67e4694f2d74 188 service = services[i];
stiaje 10:67e4694f2d74 189 removeIndex = i;
stiaje 10:67e4694f2d74 190 break;
stiaje 10:67e4694f2d74 191 }
stiaje 10:67e4694f2d74 192 }
stiaje 10:67e4694f2d74 193 GattCharacteristic** characteristics = NULL;
stiaje 10:67e4694f2d74 194 int characteristicsLength = 0;
stiaje 10:67e4694f2d74 195 if(service != NULL) {
stiaje 10:67e4694f2d74 196 characteristicsLength = service->getCharacteristicCount() + 1;
stiaje 10:67e4694f2d74 197 characteristics = (GattCharacteristic**) malloc(sizeof(GattCharacteristic*) * characteristicsLength);
stiaje 10:67e4694f2d74 198 for(int i = 0; i < characteristicsLength; i++) {
stiaje 10:67e4694f2d74 199 characteristics[i] = service->getCharacteristic(i);
stiaje 10:67e4694f2d74 200 }
stiaje 10:67e4694f2d74 201 services.erase(services.begin() + removeIndex);
stiaje 10:67e4694f2d74 202 delete service;
stiaje 10:67e4694f2d74 203 free(previousCharacteristics);
stiaje 10:67e4694f2d74 204 } else {
stiaje 10:67e4694f2d74 205 characteristicsLength = 1;
stiaje 10:67e4694f2d74 206 characteristics = (GattCharacteristic**) malloc(sizeof(GattCharacteristic*) * characteristicsLength);
stiaje 10:67e4694f2d74 207 }
stiaje 10:67e4694f2d74 208 characteristics[characteristicsLength - 1] = characteristic;
stiaje 10:67e4694f2d74 209 previousCharacteristics = characteristics;
stiaje 10:67e4694f2d74 210 service = new GattService(serviceUuid, characteristics, characteristicsLength);
stiaje 10:67e4694f2d74 211 services.push_back(service);
stiaje 10:67e4694f2d74 212 }
stiaje 10:67e4694f2d74 213
stiaje 10:67e4694f2d74 214
stiaje 10:67e4694f2d74 215 void Puck::updateCharacteristicValue(const UUID uuid, uint8_t* value, int length) {
stiaje 10:67e4694f2d74 216 GattCharacteristic* characteristic = NULL;
stiaje 10:67e4694f2d74 217 for( int i = 0; i < characteristics.size(); i++) {
stiaje 10:67e4694f2d74 218 if(isEqualUUID(&characteristics[i]->getUUID(), uuid)) {
stiaje 10:67e4694f2d74 219 characteristic = characteristics[i];
stiaje 10:67e4694f2d74 220 break;
stiaje 10:67e4694f2d74 221 }
stiaje 10:67e4694f2d74 222 }
stiaje 10:67e4694f2d74 223 if(characteristic != NULL) {
stiaje 10:67e4694f2d74 224 ble.updateCharacteristicValue(characteristic->getHandle(), value, length);
stiaje 10:67e4694f2d74 225 LOG_VERBOSE("Updated characteristic value.\n");
stiaje 10:67e4694f2d74 226 } else {
stiaje 10:67e4694f2d74 227 LOG_WARN("Tried to update an unkown characteristic!\n");
stiaje 10:67e4694f2d74 228 }
stiaje 10:67e4694f2d74 229 }
stiaje 10:67e4694f2d74 230
stiaje 10:67e4694f2d74 231 bool Puck::drive() {
stiaje 10:67e4694f2d74 232 ble.waitForEvent();
stiaje 10:67e4694f2d74 233 if(state == DISCONNECTED) {
stiaje 10:67e4694f2d74 234 startAdvertising();
stiaje 10:67e4694f2d74 235 }
stiaje 10:67e4694f2d74 236 while(pendingCallbackStack.size() > 0) {
stiaje 10:67e4694f2d74 237 LOG_VERBOSE("PendingCallbackStack size: %i\n", pendingCallbackStack.size());
stiaje 10:67e4694f2d74 238 pendingCallbackStack.back()(pendingCallbackParameterStack.back());
stiaje 10:67e4694f2d74 239 pendingCallbackStack.pop_back();
stiaje 10:67e4694f2d74 240 pendingCallbackParameterStack.pop_back();
stiaje 10:67e4694f2d74 241 LOG_VERBOSE("Callback fired\n");
stiaje 10:67e4694f2d74 242 }
stiaje 10:67e4694f2d74 243 return true;
stiaje 10:67e4694f2d74 244 }
stiaje 10:67e4694f2d74 245
stiaje 10:67e4694f2d74 246
stiaje 10:67e4694f2d74 247 void Puck::onCharacteristicWrite(const UUID uuid, CharacteristicWriteCallback callback) {
stiaje 10:67e4694f2d74 248 CharacteristicWriteCallbacks* cb = NULL;
stiaje 10:67e4694f2d74 249 for(int i = 0; i< writeCallbacks.size(); i++) {
stiaje 10:67e4694f2d74 250 if(isEqualUUID(writeCallbacks[i]->uuid, uuid)) {
stiaje 10:67e4694f2d74 251 cb = writeCallbacks[i];
stiaje 10:67e4694f2d74 252 break;
stiaje 10:67e4694f2d74 253 }
stiaje 10:67e4694f2d74 254 }
stiaje 10:67e4694f2d74 255 if(cb == NULL) {
stiaje 10:67e4694f2d74 256 cb = (CharacteristicWriteCallbacks*) malloc(sizeof(CharacteristicWriteCallbacks));
stiaje 10:67e4694f2d74 257 cb->uuid = &uuid;
stiaje 10:67e4694f2d74 258 cb->callbacks = new std::vector<CharacteristicWriteCallback>();
stiaje 10:67e4694f2d74 259 writeCallbacks.push_back(cb);
stiaje 10:67e4694f2d74 260 }
stiaje 10:67e4694f2d74 261 cb->callbacks->push_back(callback);
stiaje 10:67e4694f2d74 262 LOG_VERBOSE("Bound characteristic write callback (uuid: %x, callback: %x)\n", uuid, callback);
stiaje 10:67e4694f2d74 263 }
stiaje 10:67e4694f2d74 264
stiaje 10:67e4694f2d74 265
stiaje 10:67e4694f2d74 266 uint8_t* Puck::getCharacteristicValue(const UUID uuid) {
stiaje 10:67e4694f2d74 267 LOG_VERBOSE("Reading characteristic value for UUID %x\n", uuid);
stiaje 10:67e4694f2d74 268 for(int i = 0; i < characteristics.size(); i++) {
stiaje 10:67e4694f2d74 269 GattCharacteristic* characteristic = characteristics[i];
stiaje 10:67e4694f2d74 270 if(isEqualUUID(&characteristic->getUUID(), uuid)) {
stiaje 10:67e4694f2d74 271 return characteristic->getValuePtr();
stiaje 10:67e4694f2d74 272 }
stiaje 10:67e4694f2d74 273 }
stiaje 10:67e4694f2d74 274 LOG_WARN("Tried to read an unknown characteristic!");
stiaje 10:67e4694f2d74 275 return NULL;
stiaje 10:67e4694f2d74 276 }
stiaje 10:67e4694f2d74 277
stiaje 10:67e4694f2d74 278
stiaje 10:67e4694f2d74 279 void Puck::onDataWritten(uint16_t handle) {
stiaje 10:67e4694f2d74 280 for (int i = 0; i < characteristics.size(); i++) {
stiaje 10:67e4694f2d74 281 GattCharacteristic* characteristic = characteristics[i];
stiaje 10:67e4694f2d74 282 if (characteristic->getHandle() == handle) {
stiaje 10:67e4694f2d74 283 uint16_t maxLength = characteristic->getMaxLength();
stiaje 10:67e4694f2d74 284 ble.readCharacteristicValue(handle, characteristic->getValuePtr(), &maxLength);
stiaje 10:67e4694f2d74 285 for(int j = 0; j < writeCallbacks.size(); j++) {
stiaje 10:67e4694f2d74 286 CharacteristicWriteCallbacks* characteristicWriteCallbacks = writeCallbacks[j];
stiaje 10:67e4694f2d74 287 if(isEqualUUID(characteristicWriteCallbacks->uuid, characteristic->getUUID())) {
stiaje 10:67e4694f2d74 288 for(int k = 0; k < characteristicWriteCallbacks->callbacks->size(); k++) {
stiaje 10:67e4694f2d74 289 pendingCallbackStack.push_back(characteristicWriteCallbacks->callbacks->at(k));
stiaje 10:67e4694f2d74 290 pendingCallbackParameterStack.push_back(characteristic->getValuePtr());
stiaje 10:67e4694f2d74 291 }
stiaje 10:67e4694f2d74 292 }
stiaje 10:67e4694f2d74 293 return;
stiaje 10:67e4694f2d74 294 }
stiaje 10:67e4694f2d74 295 }
stiaje 10:67e4694f2d74 296 }
stiaje 10:67e4694f2d74 297 }
stiaje 10:67e4694f2d74 298
stiaje 10:67e4694f2d74 299
stiaje 10:67e4694f2d74 300
stiaje 10:67e4694f2d74 301 #endif // __PUCK_HPP__