Alcatel-Lucent IoT Development / LMiC

Dependents:   LoRaWAN-lmic-app

Fork of LMiC by Pascal Nysten

Files at this revision

API Documentation at this revision

Comitter:
mluis
Date:
Thu Jan 22 12:50:49 2015 +0000
Child:
1:d3b7bde3995c
Commit message:
Porting of IBM LoRa MAC in C (LMiC) to the mbed platform.

Changed in this revision

aes.cpp Show annotated file Show diff for this revision Revisions of this file
hal.h Show annotated file Show diff for this revision Revisions of this file
hal/debug.h Show annotated file Show diff for this revision Revisions of this file
hal/hal.cpp Show annotated file Show diff for this revision Revisions of this file
lmic.cpp Show annotated file Show diff for this revision Revisions of this file
lmic.h Show annotated file Show diff for this revision Revisions of this file
lorabase.h Show annotated file Show diff for this revision Revisions of this file
oslmic.cpp Show annotated file Show diff for this revision Revisions of this file
oslmic.h Show annotated file Show diff for this revision Revisions of this file
radio.cpp Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/aes.cpp	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,367 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#include "oslmic.h"
+
+#define AES_MICSUB 0x30 // internal use only
+
+static const u4_t AES_RCON[10] = { 
+    0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 
+    0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000
+};
+
+static const u1_t AES_S[256] = {
+  0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 
+  0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 
+  0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 
+  0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 
+  0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 
+  0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 
+  0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 
+  0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 
+  0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 
+  0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 
+  0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 
+  0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 
+  0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 
+  0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 
+  0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 
+  0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
+};
+
+static const u4_t AES_E1[256] = {
+  0xC66363A5, 0xF87C7C84, 0xEE777799, 0xF67B7B8D, 0xFFF2F20D, 0xD66B6BBD, 0xDE6F6FB1, 0x91C5C554, 
+  0x60303050, 0x02010103, 0xCE6767A9, 0x562B2B7D, 0xE7FEFE19, 0xB5D7D762, 0x4DABABE6, 0xEC76769A, 
+  0x8FCACA45, 0x1F82829D, 0x89C9C940, 0xFA7D7D87, 0xEFFAFA15, 0xB25959EB, 0x8E4747C9, 0xFBF0F00B, 
+  0x41ADADEC, 0xB3D4D467, 0x5FA2A2FD, 0x45AFAFEA, 0x239C9CBF, 0x53A4A4F7, 0xE4727296, 0x9BC0C05B, 
+  0x75B7B7C2, 0xE1FDFD1C, 0x3D9393AE, 0x4C26266A, 0x6C36365A, 0x7E3F3F41, 0xF5F7F702, 0x83CCCC4F, 
+  0x6834345C, 0x51A5A5F4, 0xD1E5E534, 0xF9F1F108, 0xE2717193, 0xABD8D873, 0x62313153, 0x2A15153F, 
+  0x0804040C, 0x95C7C752, 0x46232365, 0x9DC3C35E, 0x30181828, 0x379696A1, 0x0A05050F, 0x2F9A9AB5, 
+  0x0E070709, 0x24121236, 0x1B80809B, 0xDFE2E23D, 0xCDEBEB26, 0x4E272769, 0x7FB2B2CD, 0xEA75759F, 
+  0x1209091B, 0x1D83839E, 0x582C2C74, 0x341A1A2E, 0x361B1B2D, 0xDC6E6EB2, 0xB45A5AEE, 0x5BA0A0FB, 
+  0xA45252F6, 0x763B3B4D, 0xB7D6D661, 0x7DB3B3CE, 0x5229297B, 0xDDE3E33E, 0x5E2F2F71, 0x13848497, 
+  0xA65353F5, 0xB9D1D168, 0x00000000, 0xC1EDED2C, 0x40202060, 0xE3FCFC1F, 0x79B1B1C8, 0xB65B5BED, 
+  0xD46A6ABE, 0x8DCBCB46, 0x67BEBED9, 0x7239394B, 0x944A4ADE, 0x984C4CD4, 0xB05858E8, 0x85CFCF4A, 
+  0xBBD0D06B, 0xC5EFEF2A, 0x4FAAAAE5, 0xEDFBFB16, 0x864343C5, 0x9A4D4DD7, 0x66333355, 0x11858594, 
+  0x8A4545CF, 0xE9F9F910, 0x04020206, 0xFE7F7F81, 0xA05050F0, 0x783C3C44, 0x259F9FBA, 0x4BA8A8E3, 
+  0xA25151F3, 0x5DA3A3FE, 0x804040C0, 0x058F8F8A, 0x3F9292AD, 0x219D9DBC, 0x70383848, 0xF1F5F504, 
+  0x63BCBCDF, 0x77B6B6C1, 0xAFDADA75, 0x42212163, 0x20101030, 0xE5FFFF1A, 0xFDF3F30E, 0xBFD2D26D, 
+  0x81CDCD4C, 0x180C0C14, 0x26131335, 0xC3ECEC2F, 0xBE5F5FE1, 0x359797A2, 0x884444CC, 0x2E171739, 
+  0x93C4C457, 0x55A7A7F2, 0xFC7E7E82, 0x7A3D3D47, 0xC86464AC, 0xBA5D5DE7, 0x3219192B, 0xE6737395, 
+  0xC06060A0, 0x19818198, 0x9E4F4FD1, 0xA3DCDC7F, 0x44222266, 0x542A2A7E, 0x3B9090AB, 0x0B888883, 
+  0x8C4646CA, 0xC7EEEE29, 0x6BB8B8D3, 0x2814143C, 0xA7DEDE79, 0xBC5E5EE2, 0x160B0B1D, 0xADDBDB76, 
+  0xDBE0E03B, 0x64323256, 0x743A3A4E, 0x140A0A1E, 0x924949DB, 0x0C06060A, 0x4824246C, 0xB85C5CE4, 
+  0x9FC2C25D, 0xBDD3D36E, 0x43ACACEF, 0xC46262A6, 0x399191A8, 0x319595A4, 0xD3E4E437, 0xF279798B, 
+  0xD5E7E732, 0x8BC8C843, 0x6E373759, 0xDA6D6DB7, 0x018D8D8C, 0xB1D5D564, 0x9C4E4ED2, 0x49A9A9E0, 
+  0xD86C6CB4, 0xAC5656FA, 0xF3F4F407, 0xCFEAEA25, 0xCA6565AF, 0xF47A7A8E, 0x47AEAEE9, 0x10080818, 
+  0x6FBABAD5, 0xF0787888, 0x4A25256F, 0x5C2E2E72, 0x381C1C24, 0x57A6A6F1, 0x73B4B4C7, 0x97C6C651, 
+  0xCBE8E823, 0xA1DDDD7C, 0xE874749C, 0x3E1F1F21, 0x964B4BDD, 0x61BDBDDC, 0x0D8B8B86, 0x0F8A8A85, 
+  0xE0707090, 0x7C3E3E42, 0x71B5B5C4, 0xCC6666AA, 0x904848D8, 0x06030305, 0xF7F6F601, 0x1C0E0E12, 
+  0xC26161A3, 0x6A35355F, 0xAE5757F9, 0x69B9B9D0, 0x17868691, 0x99C1C158, 0x3A1D1D27, 0x279E9EB9, 
+  0xD9E1E138, 0xEBF8F813, 0x2B9898B3, 0x22111133, 0xD26969BB, 0xA9D9D970, 0x078E8E89, 0x339494A7, 
+  0x2D9B9BB6, 0x3C1E1E22, 0x15878792, 0xC9E9E920, 0x87CECE49, 0xAA5555FF, 0x50282878, 0xA5DFDF7A, 
+  0x038C8C8F, 0x59A1A1F8, 0x09898980, 0x1A0D0D17, 0x65BFBFDA, 0xD7E6E631, 0x844242C6, 0xD06868B8, 
+  0x824141C3, 0x299999B0, 0x5A2D2D77, 0x1E0F0F11, 0x7BB0B0CB, 0xA85454FC, 0x6DBBBBD6, 0x2C16163A, 
+};
+
+static const u4_t AES_E2[256] = {
+  0xA5C66363, 0x84F87C7C, 0x99EE7777, 0x8DF67B7B, 0x0DFFF2F2, 0xBDD66B6B, 0xB1DE6F6F, 0x5491C5C5, 
+  0x50603030, 0x03020101, 0xA9CE6767, 0x7D562B2B, 0x19E7FEFE, 0x62B5D7D7, 0xE64DABAB, 0x9AEC7676, 
+  0x458FCACA, 0x9D1F8282, 0x4089C9C9, 0x87FA7D7D, 0x15EFFAFA, 0xEBB25959, 0xC98E4747, 0x0BFBF0F0, 
+  0xEC41ADAD, 0x67B3D4D4, 0xFD5FA2A2, 0xEA45AFAF, 0xBF239C9C, 0xF753A4A4, 0x96E47272, 0x5B9BC0C0, 
+  0xC275B7B7, 0x1CE1FDFD, 0xAE3D9393, 0x6A4C2626, 0x5A6C3636, 0x417E3F3F, 0x02F5F7F7, 0x4F83CCCC, 
+  0x5C683434, 0xF451A5A5, 0x34D1E5E5, 0x08F9F1F1, 0x93E27171, 0x73ABD8D8, 0x53623131, 0x3F2A1515, 
+  0x0C080404, 0x5295C7C7, 0x65462323, 0x5E9DC3C3, 0x28301818, 0xA1379696, 0x0F0A0505, 0xB52F9A9A, 
+  0x090E0707, 0x36241212, 0x9B1B8080, 0x3DDFE2E2, 0x26CDEBEB, 0x694E2727, 0xCD7FB2B2, 0x9FEA7575, 
+  0x1B120909, 0x9E1D8383, 0x74582C2C, 0x2E341A1A, 0x2D361B1B, 0xB2DC6E6E, 0xEEB45A5A, 0xFB5BA0A0, 
+  0xF6A45252, 0x4D763B3B, 0x61B7D6D6, 0xCE7DB3B3, 0x7B522929, 0x3EDDE3E3, 0x715E2F2F, 0x97138484, 
+  0xF5A65353, 0x68B9D1D1, 0x00000000, 0x2CC1EDED, 0x60402020, 0x1FE3FCFC, 0xC879B1B1, 0xEDB65B5B, 
+  0xBED46A6A, 0x468DCBCB, 0xD967BEBE, 0x4B723939, 0xDE944A4A, 0xD4984C4C, 0xE8B05858, 0x4A85CFCF, 
+  0x6BBBD0D0, 0x2AC5EFEF, 0xE54FAAAA, 0x16EDFBFB, 0xC5864343, 0xD79A4D4D, 0x55663333, 0x94118585, 
+  0xCF8A4545, 0x10E9F9F9, 0x06040202, 0x81FE7F7F, 0xF0A05050, 0x44783C3C, 0xBA259F9F, 0xE34BA8A8, 
+  0xF3A25151, 0xFE5DA3A3, 0xC0804040, 0x8A058F8F, 0xAD3F9292, 0xBC219D9D, 0x48703838, 0x04F1F5F5, 
+  0xDF63BCBC, 0xC177B6B6, 0x75AFDADA, 0x63422121, 0x30201010, 0x1AE5FFFF, 0x0EFDF3F3, 0x6DBFD2D2, 
+  0x4C81CDCD, 0x14180C0C, 0x35261313, 0x2FC3ECEC, 0xE1BE5F5F, 0xA2359797, 0xCC884444, 0x392E1717, 
+  0x5793C4C4, 0xF255A7A7, 0x82FC7E7E, 0x477A3D3D, 0xACC86464, 0xE7BA5D5D, 0x2B321919, 0x95E67373, 
+  0xA0C06060, 0x98198181, 0xD19E4F4F, 0x7FA3DCDC, 0x66442222, 0x7E542A2A, 0xAB3B9090, 0x830B8888, 
+  0xCA8C4646, 0x29C7EEEE, 0xD36BB8B8, 0x3C281414, 0x79A7DEDE, 0xE2BC5E5E, 0x1D160B0B, 0x76ADDBDB, 
+  0x3BDBE0E0, 0x56643232, 0x4E743A3A, 0x1E140A0A, 0xDB924949, 0x0A0C0606, 0x6C482424, 0xE4B85C5C, 
+  0x5D9FC2C2, 0x6EBDD3D3, 0xEF43ACAC, 0xA6C46262, 0xA8399191, 0xA4319595, 0x37D3E4E4, 0x8BF27979, 
+  0x32D5E7E7, 0x438BC8C8, 0x596E3737, 0xB7DA6D6D, 0x8C018D8D, 0x64B1D5D5, 0xD29C4E4E, 0xE049A9A9, 
+  0xB4D86C6C, 0xFAAC5656, 0x07F3F4F4, 0x25CFEAEA, 0xAFCA6565, 0x8EF47A7A, 0xE947AEAE, 0x18100808, 
+  0xD56FBABA, 0x88F07878, 0x6F4A2525, 0x725C2E2E, 0x24381C1C, 0xF157A6A6, 0xC773B4B4, 0x5197C6C6, 
+  0x23CBE8E8, 0x7CA1DDDD, 0x9CE87474, 0x213E1F1F, 0xDD964B4B, 0xDC61BDBD, 0x860D8B8B, 0x850F8A8A, 
+  0x90E07070, 0x427C3E3E, 0xC471B5B5, 0xAACC6666, 0xD8904848, 0x05060303, 0x01F7F6F6, 0x121C0E0E, 
+  0xA3C26161, 0x5F6A3535, 0xF9AE5757, 0xD069B9B9, 0x91178686, 0x5899C1C1, 0x273A1D1D, 0xB9279E9E, 
+  0x38D9E1E1, 0x13EBF8F8, 0xB32B9898, 0x33221111, 0xBBD26969, 0x70A9D9D9, 0x89078E8E, 0xA7339494, 
+  0xB62D9B9B, 0x223C1E1E, 0x92158787, 0x20C9E9E9, 0x4987CECE, 0xFFAA5555, 0x78502828, 0x7AA5DFDF, 
+  0x8F038C8C, 0xF859A1A1, 0x80098989, 0x171A0D0D, 0xDA65BFBF, 0x31D7E6E6, 0xC6844242, 0xB8D06868, 
+  0xC3824141, 0xB0299999, 0x775A2D2D, 0x111E0F0F, 0xCB7BB0B0, 0xFCA85454, 0xD66DBBBB, 0x3A2C1616, 
+};
+
+static const u4_t AES_E3[256] = {
+  0x63A5C663, 0x7C84F87C, 0x7799EE77, 0x7B8DF67B, 0xF20DFFF2, 0x6BBDD66B, 0x6FB1DE6F, 0xC55491C5, 
+  0x30506030, 0x01030201, 0x67A9CE67, 0x2B7D562B, 0xFE19E7FE, 0xD762B5D7, 0xABE64DAB, 0x769AEC76, 
+  0xCA458FCA, 0x829D1F82, 0xC94089C9, 0x7D87FA7D, 0xFA15EFFA, 0x59EBB259, 0x47C98E47, 0xF00BFBF0, 
+  0xADEC41AD, 0xD467B3D4, 0xA2FD5FA2, 0xAFEA45AF, 0x9CBF239C, 0xA4F753A4, 0x7296E472, 0xC05B9BC0, 
+  0xB7C275B7, 0xFD1CE1FD, 0x93AE3D93, 0x266A4C26, 0x365A6C36, 0x3F417E3F, 0xF702F5F7, 0xCC4F83CC, 
+  0x345C6834, 0xA5F451A5, 0xE534D1E5, 0xF108F9F1, 0x7193E271, 0xD873ABD8, 0x31536231, 0x153F2A15, 
+  0x040C0804, 0xC75295C7, 0x23654623, 0xC35E9DC3, 0x18283018, 0x96A13796, 0x050F0A05, 0x9AB52F9A, 
+  0x07090E07, 0x12362412, 0x809B1B80, 0xE23DDFE2, 0xEB26CDEB, 0x27694E27, 0xB2CD7FB2, 0x759FEA75, 
+  0x091B1209, 0x839E1D83, 0x2C74582C, 0x1A2E341A, 0x1B2D361B, 0x6EB2DC6E, 0x5AEEB45A, 0xA0FB5BA0, 
+  0x52F6A452, 0x3B4D763B, 0xD661B7D6, 0xB3CE7DB3, 0x297B5229, 0xE33EDDE3, 0x2F715E2F, 0x84971384, 
+  0x53F5A653, 0xD168B9D1, 0x00000000, 0xED2CC1ED, 0x20604020, 0xFC1FE3FC, 0xB1C879B1, 0x5BEDB65B, 
+  0x6ABED46A, 0xCB468DCB, 0xBED967BE, 0x394B7239, 0x4ADE944A, 0x4CD4984C, 0x58E8B058, 0xCF4A85CF, 
+  0xD06BBBD0, 0xEF2AC5EF, 0xAAE54FAA, 0xFB16EDFB, 0x43C58643, 0x4DD79A4D, 0x33556633, 0x85941185, 
+  0x45CF8A45, 0xF910E9F9, 0x02060402, 0x7F81FE7F, 0x50F0A050, 0x3C44783C, 0x9FBA259F, 0xA8E34BA8, 
+  0x51F3A251, 0xA3FE5DA3, 0x40C08040, 0x8F8A058F, 0x92AD3F92, 0x9DBC219D, 0x38487038, 0xF504F1F5, 
+  0xBCDF63BC, 0xB6C177B6, 0xDA75AFDA, 0x21634221, 0x10302010, 0xFF1AE5FF, 0xF30EFDF3, 0xD26DBFD2, 
+  0xCD4C81CD, 0x0C14180C, 0x13352613, 0xEC2FC3EC, 0x5FE1BE5F, 0x97A23597, 0x44CC8844, 0x17392E17, 
+  0xC45793C4, 0xA7F255A7, 0x7E82FC7E, 0x3D477A3D, 0x64ACC864, 0x5DE7BA5D, 0x192B3219, 0x7395E673, 
+  0x60A0C060, 0x81981981, 0x4FD19E4F, 0xDC7FA3DC, 0x22664422, 0x2A7E542A, 0x90AB3B90, 0x88830B88, 
+  0x46CA8C46, 0xEE29C7EE, 0xB8D36BB8, 0x143C2814, 0xDE79A7DE, 0x5EE2BC5E, 0x0B1D160B, 0xDB76ADDB, 
+  0xE03BDBE0, 0x32566432, 0x3A4E743A, 0x0A1E140A, 0x49DB9249, 0x060A0C06, 0x246C4824, 0x5CE4B85C, 
+  0xC25D9FC2, 0xD36EBDD3, 0xACEF43AC, 0x62A6C462, 0x91A83991, 0x95A43195, 0xE437D3E4, 0x798BF279, 
+  0xE732D5E7, 0xC8438BC8, 0x37596E37, 0x6DB7DA6D, 0x8D8C018D, 0xD564B1D5, 0x4ED29C4E, 0xA9E049A9, 
+  0x6CB4D86C, 0x56FAAC56, 0xF407F3F4, 0xEA25CFEA, 0x65AFCA65, 0x7A8EF47A, 0xAEE947AE, 0x08181008, 
+  0xBAD56FBA, 0x7888F078, 0x256F4A25, 0x2E725C2E, 0x1C24381C, 0xA6F157A6, 0xB4C773B4, 0xC65197C6, 
+  0xE823CBE8, 0xDD7CA1DD, 0x749CE874, 0x1F213E1F, 0x4BDD964B, 0xBDDC61BD, 0x8B860D8B, 0x8A850F8A, 
+  0x7090E070, 0x3E427C3E, 0xB5C471B5, 0x66AACC66, 0x48D89048, 0x03050603, 0xF601F7F6, 0x0E121C0E, 
+  0x61A3C261, 0x355F6A35, 0x57F9AE57, 0xB9D069B9, 0x86911786, 0xC15899C1, 0x1D273A1D, 0x9EB9279E, 
+  0xE138D9E1, 0xF813EBF8, 0x98B32B98, 0x11332211, 0x69BBD269, 0xD970A9D9, 0x8E89078E, 0x94A73394, 
+  0x9BB62D9B, 0x1E223C1E, 0x87921587, 0xE920C9E9, 0xCE4987CE, 0x55FFAA55, 0x28785028, 0xDF7AA5DF, 
+  0x8C8F038C, 0xA1F859A1, 0x89800989, 0x0D171A0D, 0xBFDA65BF, 0xE631D7E6, 0x42C68442, 0x68B8D068, 
+  0x41C38241, 0x99B02999, 0x2D775A2D, 0x0F111E0F, 0xB0CB7BB0, 0x54FCA854, 0xBBD66DBB, 0x163A2C16, 
+};
+
+static const u4_t AES_E4[256] = {
+  0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, 
+  0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, 
+  0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, 
+  0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, 
+  0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, 
+  0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, 
+  0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, 
+  0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, 
+  0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, 
+  0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, 
+  0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, 
+  0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, 
+  0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, 
+  0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, 
+  0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, 
+  0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, 
+  0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, 
+  0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, 
+  0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, 
+  0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, 
+  0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, 
+  0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, 
+  0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, 
+  0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, 
+  0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, 
+  0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, 
+  0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, 
+  0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, 
+  0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, 
+  0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, 
+  0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, 
+  0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C, 
+};
+
+#define msbf4_read(p)    ((p)[0]<<24 | (p)[1]<<16 | (p)[2]<<8 | (p)[3])
+#define msbf4_write(p,v) (p)[0]=(v)>>24,(p)[1]=(v)>>16,(p)[2]=(v)>>8,(p)[3]=(v)
+#define swapmsbf(x)      ( (x&0xFF)<<24 | (x&0xFF00)<<8 | (x&0xFF0000)>>8 | (x>>24) )
+
+#define u1(v)		            ((u1_t)(v))
+
+#define AES_key4(r1,r2,r3,r0,i)    r1 = ki[i+1]; \
+                                   r2 = ki[i+2]; \
+                                   r3 = ki[i+3]; \
+                                   r0 = ki[i]
+
+#define AES_expr4(r1,r2,r3,r0,i)   r1 ^= AES_E4[u1(i)];	    \
+			           r2 ^= AES_E3[u1(i>>8)];  \
+			           r3 ^= AES_E2[u1(i>>16)]; \
+			           r0 ^= AES_E1[  (i>>24)]
+
+#define AES_expr(a,r0,r1,r2,r3,i)  a = ki[i];                    \
+				   a ^= (AES_S[   r0>>24 ]<<24); \
+				   a ^= (AES_S[u1(r1>>16)]<<16); \
+				   a ^= (AES_S[u1(r2>> 8)]<< 8); \
+				   a ^=  AES_S[u1(r3)    ]
+
+// global area for passing parameters (aux, key) and for storing round keys
+u4_t AESAUX[16/sizeof(u4_t)];
+u4_t AESKEY[11*16/sizeof(u4_t)];
+
+// generate 1+10 roundkeys for encryption with 128-bit key
+// read 128-bit key from AESKEY in MSBF, generate roundkey words in place
+static void aesroundkeys (void) {
+    int i;
+    u4_t b;
+
+    for( i=0; i<4; i++) {
+	AESKEY[i] = swapmsbf(AESKEY[i]);
+    }
+    
+    b = AESKEY[3];
+    for( ; i<44; i++ ) {
+	if( i%4==0 ) {
+            // b = SubWord(RotWord(b)) xor Rcon[i/4]
+	    b = (AES_S[u1(b >> 16)] << 24) ^
+		(AES_S[u1(b >>  8)] << 16) ^
+		(AES_S[u1(b)      ] <<  8) ^
+		(AES_S[   b >> 24 ]      ) ^
+                 AES_RCON[(i-4)/4];
+	}
+	AESKEY[i] = b ^= AESKEY[i-4];
+    }
+}
+
+u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) {
+        
+	aesroundkeys();
+
+	if( mode & AES_MICNOAUX ) {
+	    AESAUX[0] = AESAUX[1] = AESAUX[2] = AESAUX[3] = 0;
+	} else {
+	    AESAUX[0] = swapmsbf(AESAUX[0]);
+	    AESAUX[1] = swapmsbf(AESAUX[1]);
+	    AESAUX[2] = swapmsbf(AESAUX[2]);
+	    AESAUX[3] = swapmsbf(AESAUX[3]);
+	}
+
+	while( (signed char)len > 0 ) {
+	    u4_t a0, a1, a2, a3;
+	    u4_t t0, t1, t2, t3;
+	    u4_t *ki, *ke;
+
+	    // load input block
+	    if( (mode & AES_CTR) || ((mode & AES_MIC) && (mode & AES_MICNOAUX)==0) ) { // load CTR block or first MIC block
+		a0 = AESAUX[0];
+		a1 = AESAUX[1];
+		a2 = AESAUX[2];
+		a3 = AESAUX[3];
+            }
+            else if( (mode & AES_MIC) && len <= 16 ) { // last MIC block
+                a0 = a1 = a2 = a3 = 0; // load null block
+                mode |= ((len == 16) ? 1 : 2) << 4; // set MICSUB: CMAC subkey K1 or K2
+            } else
+        LOADDATA: { // load data block (partially)
+		for(t0=0; t0<16; t0++) {
+                    t1 = (t1<<8) | ((t0<len) ? buf[t0] : (t0==len) ? 0x80 : 0x00);
+                    if((t0&3)==3) {
+                        a0 = a1;
+                        a1 = a2;
+                        a2 = a3;
+                        a3 = t1;
+                    }
+                } 
+		if( mode & AES_MIC ) {
+		    a0 ^= AESAUX[0];
+		    a1 ^= AESAUX[1];
+		    a2 ^= AESAUX[2];
+		    a3 ^= AESAUX[3];
+		}
+            }
+
+	    // perform AES encryption on block in a0-a3
+	    ki = AESKEY;
+	    ke = ki + 8*4;
+	    a0 ^= ki[0];
+	    a1 ^= ki[1];
+	    a2 ^= ki[2];
+	    a3 ^= ki[3];
+	    do {
+		AES_key4 (t1,t2,t3,t0,4);
+		AES_expr4(t1,t2,t3,t0,a0);
+		AES_expr4(t2,t3,t0,t1,a1);
+		AES_expr4(t3,t0,t1,t2,a2);
+		AES_expr4(t0,t1,t2,t3,a3);
+
+		AES_key4 (a1,a2,a3,a0,8);
+		AES_expr4(a1,a2,a3,a0,t0);
+		AES_expr4(a2,a3,a0,a1,t1);
+		AES_expr4(a3,a0,a1,a2,t2);
+		AES_expr4(a0,a1,a2,a3,t3);
+	    } while( (ki+=8) < ke );
+
+	    AES_key4 (t1,t2,t3,t0,4);
+	    AES_expr4(t1,t2,t3,t0,a0);
+	    AES_expr4(t2,t3,t0,t1,a1);
+	    AES_expr4(t3,t0,t1,t2,a2);
+	    AES_expr4(t0,t1,t2,t3,a3);
+
+	    AES_expr(a0,t0,t1,t2,t3,8);
+	    AES_expr(a1,t1,t2,t3,t0,9);
+	    AES_expr(a2,t2,t3,t0,t1,10);
+	    AES_expr(a3,t3,t0,t1,t2,11);
+	    // result of AES encryption in a0-a3
+
+	    if( mode & AES_MIC ) {
+		if( (t1 = ((mode & AES_MICSUB) >> 4)) != 0 ) { // last block
+		    do {
+			// compute CMAC subkey K1 and K2
+			t0 = a0 >> 31; // save MSB
+			a0 = (a0 << 1) | (a1 >> 31);
+			a1 = (a1 << 1) | (a2 >> 31);
+			a2 = (a2 << 1) | (a3 >> 31);
+			a3 = (a3 << 1);
+			if( t0 ) a3 ^= 0x87;
+		    } while( --t1 );
+
+		    AESAUX[0] ^= a0;
+		    AESAUX[1] ^= a1;
+		    AESAUX[2] ^= a2;
+		    AESAUX[3] ^= a3;
+                    mode &= ~AES_MICSUB;
+		    goto LOADDATA;
+		} else {
+                    // save cipher block as new iv
+                    AESAUX[0] = a0;
+                    AESAUX[1] = a1;
+                    AESAUX[2] = a2;
+                    AESAUX[3] = a3;
+                }
+	    } else { // CIPHER
+		if( mode & AES_CTR ) { // xor block (partially)
+                    t0 = (len > 16) ? 16: len;
+                    for(t1=0; t1<t0; t1++) {
+                        buf[t1] ^= (a0>>24);
+                        a0 <<= 8;
+                        if((t1&3)==3) {
+                            a0 = a1;
+                            a1 = a2;
+                            a2 = a3;
+                        }
+                    }
+		    // update counter
+		    AESAUX[3]++;
+		} else { // ECB
+                    // store block
+                    msbf4_write(buf+0,  a0);
+		    msbf4_write(buf+4,  a1);
+		    msbf4_write(buf+8,  a2);
+		    msbf4_write(buf+12, a3);
+		}
+	    }
+
+            // update block state
+            if( (mode & AES_MIC)==0 || (mode & AES_MICNOAUX) ) {
+                buf += 16;
+                len -= 16;
+            }
+            mode |= AES_MICNOAUX;
+	}
+	return AESAUX[0];
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hal.h	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#ifndef _hal_hpp_
+#define _hal_hpp_
+
+/*
+ * initialize hardware (IO, SPI, TIMER, IRQ).
+ */
+void hal_init (void);
+
+/*
+ * drive radio NSS pin (0=low, 1=high).
+ */
+void hal_pin_nss (u1_t val);
+
+/*
+ * drive radio RX/TX pins (0=rx, 1=tx).
+ */
+void hal_pin_rxtx (u1_t val);
+
+/*
+ * control radio RST pin (0=low, 1=high, 2=floating)
+ */
+void hal_pin_rst (u1_t val);
+
+/*
+ * perform 8-bit SPI transaction with radio.
+ *   - write given byte 'outval'
+ *   - read byte and return value
+ */
+u1_t hal_spi (u1_t outval);
+
+/*
+ * disable all CPU interrupts.
+ *   - might be invoked nested 
+ *   - will be followed by matching call to hal_enableIRQs()
+ */
+void hal_disableIRQs (void);
+
+/*
+ * enable CPU interrupts.
+ */
+void hal_enableIRQs (void);
+
+/*
+ * put system and CPU in low-power mode, sleep until interrupt.
+ */
+void hal_sleep (void);
+
+/*
+ * return 32-bit system time in ticks.
+ */
+u4_t hal_ticks (void);
+
+/*
+ * busy-wait until specified timestamp (in ticks) is reached.
+ */
+void hal_waitUntil (u4_t time);
+
+/*
+ * check and rewind timer for target time.
+ *   - return 1 if target time is close
+ *   - otherwise rewind timer for target time or full period and return 0
+ */
+u1_t hal_checkTimer (u4_t targettime);
+
+/*
+ * perform fatal failure action.
+ *   - called by assertions
+ *   - action could be HALT or reboot
+ */
+void hal_failed (void);
+
+//////////////////////////////////////////////////////////////////////
+#ifdef CFG_DEBUG
+void debug_init (void);
+void debug_led (u1_t val);
+void debug_char (u1_t c);
+void debug_hex (u1_t b);
+void debug_buf (const u1_t* buf, u2_t len);
+void debug_uint (u4_t v);
+void debug_str (const u1_t* str);
+void debug_event (int ev);
+void debug_val (const u1_t* label, u4_t val);
+#define DEBUG_INIT()   debug_init()
+#define DEBUG_LED(v)   debug_led(v)
+#define DEBUG_CHAR(c)  debug_char(c)
+#define DEBUG_HEX(b)   debug_hex(b)
+#define DEBUG_BUF(b,l) debug_buf(b,l)
+#define DEBUG_UINT(v)  debug_uint(v)
+#define DEBUG_STR(s)   debug_str(s)
+#define DEBUG_EVENT(e) debug_event(e)
+#define DEBUG_VAL(l,v) debug_val(l, v)
+#else // CFG_DEBUG
+#define DEBUG_INIT()
+#define DEBUG_LED(v)
+#define DEBUG_CHAR(c)
+#define DEBUG_HEX(b)
+#define DEBUG_BUF(b,l)
+#define DEBUG_UINT(v)
+#define DEBUG_STR(s)
+#define DEBUG_EVENT(e)
+#define DEBUG_VAL(l,v)
+#endif // CFG_DEBUG
+
+#endif // _hal_hpp_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hal/debug.h	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,61 @@
+/* Copyright (c) 2012 mbed.org, MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+ * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+ 
+#ifndef DEBUG_H
+#define DEBUG_H
+
+/** @file debug.h */
+
+#ifndef NDEBUG
+
+#include <stdarg.h>
+#include <stdio.h>
+
+/** Output a debug message
+ * 
+ * @param format printf-style format string, followed by variables
+ */
+static inline void debug(const char *format, ...) {
+    va_list args;
+    va_start(args, format);
+    vfprintf(stderr, format, args);
+    va_end(args);
+}
+
+/** Conditionally output a debug message
+ * 
+ * @param condition output only if condition is true
+ * @param format printf-style format string, followed by variables
+ */
+static inline void debug_if(bool condition, const char *format, ...) {
+    if(condition) {
+        va_list args;
+        va_start(args, format);
+        vfprintf(stderr, format, args);
+        va_end(args);
+    }
+}
+
+#else
+
+static inline void debug(const char *format, ...) {}
+static inline void debug(bool condition, const char *format, ...) {}
+
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hal/hal.cpp	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *    Semtech Apps Team       - Modified to support the MBED sx1276 driver
+ *                              library.
+ *                              Possibility to use original or Semtech's MBED
+ *                              radio driver. The selection is done by setting
+ *                              USE_SMTC_RADIO_DRIVER preprocessing directive
+ *                              in lmic.h
+ *******************************************************************************/
+#include "mbed.h"
+#include "lmic.h"
+#include "mbed_debug.h"
+
+#if USE_SMTC_RADIO_DRIVER
+
+#else
+
+extern void radio_irq_handler( u1_t dio );
+
+static DigitalOut nss( D10 );
+static SPI spi( D11, D12, D13 ); // ( mosi, miso, sclk )
+ 
+static DigitalInOut rst( A0 );
+static DigitalOut rxtx( A4 );
+ 
+static InterruptIn dio0( D2 );
+static InterruptIn dio1( D3 );
+static InterruptIn dio2( D4 ); 
+
+static void dio0Irq( void )
+{
+    radio_irq_handler( 0 );
+}
+
+static void dio1Irq( void )
+{
+    radio_irq_handler( 1 );
+}
+static void dio2Irq( void )
+{
+    radio_irq_handler( 2 );
+}
+
+#endif
+
+static u1_t irqlevel = 0;
+static u4_t ticks = 0;
+
+static Timer timer;
+static Ticker ticker;
+
+static void reset_timer( void )
+{
+    ticks += timer.read_us( ) >> 6;
+    timer.reset( );
+}
+
+void hal_init( void )
+{
+     __disable_irq( );
+     irqlevel = 0;
+
+#if USE_SMTC_RADIO_DRIVER
+
+#else
+    // configure input lines
+    dio0.mode( PullDown );
+    dio0.rise( dio0Irq );
+    dio0.enable_irq( );
+    dio1.mode( PullDown );   
+    dio1.rise( dio1Irq );
+    dio1.enable_irq( );
+    dio2.mode( PullDown );
+    dio2.rise( dio2Irq );
+    dio2.enable_irq( );
+    // configure reset line
+    rst.input( );
+    // configure spi
+    spi.frequency( 8000000 );
+    spi.format( 8, 0 );
+    nss = 1;
+#endif
+    // configure timer
+    timer.start( );
+    ticker.attach_us( reset_timer, 10000000 ); // reset timer every 10sec
+     __enable_irq( );
+}
+
+#if USE_SMTC_RADIO_DRIVER
+
+#else
+
+void hal_pin_rxtx( u1_t val )
+{
+    rxtx = !val;
+}
+
+void hal_pin_nss( u1_t val )
+{
+    nss = val;
+}
+
+void hal_pin_rst( u1_t val )
+{
+    if( val == 0 || val == 1 )
+    { // drive pin
+        rst.output( );
+        rst = val;
+    } 
+    else
+    { // keep pin floating
+        rst.input( );
+    }
+}
+
+u1_t hal_spi( u1_t out )
+{
+    return spi.write( out );
+}
+
+#endif
+
+void hal_disableIRQs( void )
+{
+    __disable_irq( );
+    irqlevel++;
+}
+
+void hal_enableIRQs( void )
+{
+    if( --irqlevel == 0 )
+    {
+        __enable_irq( );
+    }
+}
+
+void hal_sleep( void )
+{
+    // NOP
+}
+
+u4_t hal_ticks( void )
+{
+    hal_disableIRQs( );
+    int t = ticks + ( timer.read_us( ) >> 6 );
+    hal_enableIRQs( );
+    return t;
+}
+
+static u2_t deltaticks( u4_t time )
+{
+    u4_t t = hal_ticks( );
+    s4_t d = time - t;
+    if( d <= 0 )
+    {
+        return 0;    // in the past
+    }
+    if( ( d >> 16 ) != 0 )
+    {
+        return 0xFFFF; // far ahead
+    }
+    return ( u2_t )d;
+}
+
+void hal_waitUntil( u4_t time )
+{
+    while( deltaticks( time ) != 0 ); // busy wait until timestamp is reached
+}
+
+u1_t hal_checkTimer( u4_t time )
+{
+    return ( deltaticks( time ) < 2 );
+}
+
+void hal_failed( void )
+{
+    while( 1 );
+}
+
+//////////////////////////////////////////////////////////////////////
+// DEBUG CODE BELOW (use CFG_DEBUG)
+//////////////////////////////////////////////////////////////////////
+#ifdef CFG_DEBUG
+
+void debug_init( void )
+{
+    // print banner
+    debug( "\r\n============== DEBUG STARTED ==============\r\n" );
+}
+
+void debug_char( u1_t c )
+{
+    debug( "%c", c );
+}
+
+void debug_hex( u1_t b )
+{
+    debug( "%02X", b );
+}
+
+void debug_buf( const u1_t* buf, u2_t len )
+{
+    while( len-- )
+    {
+        debug_hex( *buf++ );
+        debug_char( ' ' );
+    }
+    debug_char( '\r' );
+    debug_char( '\n' );
+}
+
+void debug_uint( u4_t v )
+{
+    for( s1_t n = 24; n >= 0; n -= 8 )
+    {
+        debug_hex( v >> n );
+    }
+}
+
+void debug_str( const u1_t* str )
+{
+    while( *str )
+    {
+        debug_char( *str++ );
+    }
+}
+
+void debug_val( const u1_t* label, u4_t val )
+{
+    debug_str( label );
+    debug_uint( val );
+    debug_char( '\r' );
+    debug_char( '\n' );
+}
+
+void debug_led( u1_t val )
+{
+    debug_val( "LED = ", val );
+}
+
+void debug_event( int ev ) 
+{
+    static const u1_t* evnames[] = 
+    {
+        [EV_SCAN_TIMEOUT]   = "SCAN_TIMEOUT",
+        [EV_BEACON_FOUND]   = "BEACON_FOUND",
+        [EV_BEACON_MISSED]  = "BEACON_MISSED",
+        [EV_BEACON_TRACKED] = "BEACON_TRACKED",
+        [EV_JOINING]        = "JOINING",
+        [EV_JOINED]         = "JOINED",
+        [EV_RFU1]           = "RFU1",
+        [EV_JOIN_FAILED]    = "JOIN_FAILED",
+        [EV_REJOIN_FAILED]  = "REJOIN_FAILED",
+        [EV_TXCOMPLETE]     = "TXCOMPLETE",
+        [EV_LOST_TSYNC]     = "LOST_TSYNC",
+        [EV_RESET]          = "RESET",
+        [EV_RXCOMPLETE]     = "RXCOMPLETE",
+        [EV_LINK_DEAD]      = "LINK_DEAD",
+        [EV_LINK_ALIVE]     = "LINK_ALIVE",
+    };
+    debug( "%s\r\n", evnames[ev] );
+}
+#endif // CFG_DEBUG
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lmic.cpp	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,2078 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#include "lmic.h"
+
+#define PAMBL_SYMS 8
+#define MINRX_SYMS 5
+#define PAMBL_FSK  5
+#define PRERX_FSK  1
+#define RXLEN_FSK  (1+5+2)
+
+#define BCN_INTV_osticks       sec2osticks(BCN_INTV_sec)
+#define TXRX_GUARD_osticks     ms2osticks(TXRX_GUARD_ms)
+#define JOIN_GUARD_osticks     ms2osticks(JOIN_GUARD_ms)
+#define DELAY_DNW1_osticks     sec2osticks(DELAY_DNW1)
+#define DELAY_DNW2_osticks     sec2osticks(DELAY_DNW2)
+#define DELAY_JACC1_osticks    sec2osticks(DELAY_JACC1)
+#define DELAY_JACC2_osticks    sec2osticks(DELAY_JACC2)
+#define DELAY_EXTDNW2_osticks  sec2osticks(DELAY_EXTDNW2)
+#define BCN_RESERVE_osticks    ms2osticks(BCN_RESERVE_ms)
+#define BCN_GUARD_osticks      ms2osticks(BCN_GUARD_ms)
+#define BCN_WINDOW_osticks     ms2osticks(BCN_WINDOW_ms)
+#define AIRTIME_BCN_osticks    us2osticks(AIRTIME_BCN)
+
+DEFINE_LMIC;
+DECL_ON_LMIC_EVENT;
+
+
+// Fwd decls.
+static void engineUpdate(void);
+static void startScan (void);
+
+
+// ================================================================================
+// BEG OS - default implementations for certain OS suport functions
+
+#if !HAS_os_calls
+
+#ifndef os_rlsbf2
+u2_t os_rlsbf2 (xref2cu1_t buf) {
+    return (u2_t)(buf[0] | (buf[1]<<8));
+}
+#endif
+
+#ifndef os_rlsbf4
+u4_t os_rlsbf4 (xref2cu1_t buf) {
+    return (u4_t)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24));
+}
+#endif
+
+
+#ifndef os_rmsbf4
+u4_t os_rmsbf4 (xref2cu1_t buf) {
+    return (u4_t)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24));
+}
+#endif
+
+
+#ifndef os_wlsbf2
+void os_wlsbf2 (xref2u1_t buf, u2_t v) {
+    buf[0] = v;
+    buf[1] = v>>8;
+}
+#endif
+
+#ifndef os_wlsbf4
+void os_wlsbf4 (xref2u1_t buf, u4_t v) {
+    buf[0] = v;
+    buf[1] = v>>8;
+    buf[2] = v>>16;
+    buf[3] = v>>24;
+}
+#endif
+
+#ifndef os_wmsbf4
+void os_wmsbf4 (xref2u1_t buf, u4_t v) {
+    buf[3] = v;
+    buf[2] = v>>8;
+    buf[1] = v>>16;
+    buf[0] = v>>24;
+}
+#endif
+
+#ifndef os_getBattLevel
+u1_t os_getBattLevel (void) {
+    return MCMD_DEVS_BATT_NOINFO;
+}
+#endif
+
+#ifndef os_crc16
+static const u2_t CRC_TABLE[] = {
+    0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+    0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+    0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+    0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+    0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+    0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+    0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+    0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+    0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+    0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+    0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+    0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+    0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+    0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+    0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+    0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+    0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+    0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+    0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+    0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+    0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+    0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+    0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+    0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+    0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+    0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+    0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+    0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+    0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+    0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+    0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+    0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+u2_t os_crc16 (xref2u1_t data, uint len) {
+    u2_t fcs = 0;
+    for( uint u=0; u<len; u++ ) {
+    u1_t b = data[u];
+    fcs = (CRC_TABLE[(fcs ^ b) & 0xFF] ^ ((fcs >> 8) & 0xFF));
+    }
+    return fcs;
+}
+#endif
+
+#endif // !HAS_os_calls
+
+// END OS - default implementations for certain OS suport functions
+// ================================================================================
+
+// ================================================================================
+// BEG AES
+
+static void micB0 (u4_t devaddr, u4_t seqno, int dndir, int len) {
+    os_clearMem(AESaux,16);
+    AESaux[0]  = 0x49;
+    AESaux[5]  = dndir?1:0;
+    AESaux[15] = len;
+    os_wlsbf4(AESaux+ 6,devaddr);
+    os_wlsbf4(AESaux+10,seqno);
+}
+
+
+static int aes_verifyMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) {
+    micB0(devaddr, seqno, dndir, len);
+    os_copyMem(AESkey,key,16);
+    return os_aes(AES_MIC, pdu, len) == os_rmsbf4(pdu+len);
+}
+
+
+static void aes_appendMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) {
+    micB0(devaddr, seqno, dndir, len);
+    os_copyMem(AESkey,key,16);
+    // MSB because of internal structure of AES
+    os_wmsbf4(pdu+len, os_aes(AES_MIC, pdu, len));
+}
+
+
+static void aes_appendMic0 (xref2u1_t pdu, int len) {
+    os_getDevKey(AESkey);
+    os_wmsbf4(pdu+len, os_aes(AES_MIC|AES_MICNOAUX, pdu, len));  // MSB because of internal structure of AES
+}
+
+
+static int aes_verifyMic0 (xref2u1_t pdu, int len) {
+    os_getDevKey(AESkey);
+    return os_aes(AES_MIC|AES_MICNOAUX, pdu, len) == os_rmsbf4(pdu+len);
+}
+
+
+static void aes_encrypt (xref2u1_t pdu, int len) {
+    os_getDevKey(AESkey);
+    os_aes(AES_ENC, pdu, len);
+}
+
+
+static void aes_cipher (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t payload, int len) {
+    if( len <= 0 )
+    return;
+    os_clearMem(AESaux, 16);
+    AESaux[0] = AESaux[15] = 1; // mode=cipher / dir=down / block counter=1
+    AESaux[5] = dndir?1:0;
+    os_wlsbf4(AESaux+ 6,devaddr);
+    os_wlsbf4(AESaux+10,seqno);
+    os_copyMem(AESkey,key,16);
+    os_aes(AES_CTR, payload, len);
+}
+
+
+static void aes_sessKeys (u2_t devnonce, xref2cu1_t artnonce, xref2u1_t nwkkey, xref2u1_t artkey) {
+    os_clearMem(nwkkey, 16);
+    nwkkey[0] = 0x01;
+    os_copyMem(nwkkey+1, artnonce, LEN_ARTNONCE+LEN_NETID);
+    os_wlsbf2(nwkkey+1+LEN_ARTNONCE+LEN_NETID, devnonce);
+    os_copyMem(artkey, nwkkey, 16);
+    artkey[0] = 0x02;
+
+    os_getDevKey(AESkey);
+    os_aes(AES_ENC, nwkkey, 16);
+    os_getDevKey(AESkey);
+    os_aes(AES_ENC, artkey, 16);
+}
+
+// END AES
+// ================================================================================
+
+
+// ================================================================================
+// BEG LORA
+
+#if CFG_eu868 // ========================================
+
+#define maxFrameLen(dr) ((dr)<=DR_SF9 ? maxFrameLens[(dr)] : 0xFF)
+const u1_t maxFrameLens [] = { 64,64,64,123 };
+
+const u1_t _DR2RPS_CRC[] = {
+    ILLEGAL_RPS,
+    (u1_t)MAKERPS(SF12, BW125, CR_4_5, 0, 0),
+    (u1_t)MAKERPS(SF11, BW125, CR_4_5, 0, 0),
+    (u1_t)MAKERPS(SF10, BW125, CR_4_5, 0, 0),
+    (u1_t)MAKERPS(SF9,  BW125, CR_4_5, 0, 0),
+    (u1_t)MAKERPS(SF8,  BW125, CR_4_5, 0, 0),
+    (u1_t)MAKERPS(SF7,  BW125, CR_4_5, 0, 0),
+    (u1_t)MAKERPS(SF7,  BW250, CR_4_5, 0, 0),
+    (u1_t)MAKERPS(FSK,  BW125, CR_4_5, 0, 0),
+    ILLEGAL_RPS
+};
+
+static const s1_t TXPOWLEVELS[] = {
+    20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0
+};
+#define pow2dBm(mcmd_ladr_p1) (TXPOWLEVELS[(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT])
+
+#elif CFG_us915 // ========================================
+
+#define maxFrameLen(dr) ((dr)<=DR_SF11CR ? maxFrameLens[(dr)] : 0xFF)
+const u1_t maxFrameLens [] = { 24,66,142,255,255,255,255,255,  66,142 };
+
+const u1_t _DR2RPS_CRC[] = {
+    ILLEGAL_RPS,
+    MAKERPS(SF10, BW125, CR_4_5, 0, 0),
+    MAKERPS(SF9 , BW125, CR_4_5, 0, 0),
+    MAKERPS(SF8 , BW125, CR_4_5, 0, 0),
+    MAKERPS(SF7 , BW125, CR_4_5, 0, 0),
+    MAKERPS(SF8 , BW500, CR_4_5, 0, 0),
+    ILLEGAL_RPS ,
+    ILLEGAL_RPS ,
+    ILLEGAL_RPS ,
+    MAKERPS(SF12, BW500, CR_4_5, 0, 0),
+    MAKERPS(SF11, BW500, CR_4_5, 0, 0),
+    MAKERPS(SF10, BW500, CR_4_5, 0, 0),
+    MAKERPS(SF9 , BW500, CR_4_5, 0, 0),
+    MAKERPS(SF8 , BW500, CR_4_5, 0, 0),
+    MAKERPS(SF7 , BW500, CR_4_5, 0, 0),
+    ILLEGAL_RPS
+};
+
+#define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1)))
+
+#endif // ================================================
+
+static const u1_t SENSITIVITY[7][3] = {
+    // ------------bw----------
+    // 125kHz    250kHz    500kHz
+    { 141-109,  141-109, 141-109 },  // FSK
+    { 141-127,  141-124, 141-121 },  // SF7
+    { 141-129,  141-126, 141-123 },  // SF8
+    { 141-132,  141-129, 141-126 },  // SF9
+    { 141-135,  141-132, 141-129 },  // SF10
+    { 141-138,  141-135, 141-132 },  // SF11
+    { 141-141,  141-138, 141-135 }   // SF12
+};
+
+int getSensitivity (rps_t rps) {
+    return -141 + SENSITIVITY[getSf(rps)][getBw(rps)];
+}
+
+ostime_t calcAirTime (rps_t rps, u1_t plen) {
+    u1_t bw = getBw(rps);  // 0,1,2 = 125,250,500kHz
+    u1_t sf = getSf(rps);  // 0=FSK, 1..6 = SF7..12
+    if( sf == FSK ) {
+    // MLu - 12012015
+    return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8
+        * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000;
+    //return (plen+/*preamble*/5+/*syncword*/2+/*len*/1+/*crc*/2) * /*bits/byte*/8
+    //    * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000;
+    }
+    u1_t sfx = 4*(sf+(7-SF7));
+    u1_t q = sfx - (sf >= SF11 ? 8 : 0);
+    int tmp = 8*plen - sfx + 28 + (getNocrc(rps)?0:16) - (getIh(rps)?20:0);
+    if( tmp > 0 ) {
+    tmp = (tmp + q - 1) / q;
+    tmp *= getCr(rps)+5;
+    tmp += 8;
+    } else {
+    tmp = 8;
+    }
+    tmp = (tmp<<2) + /*preamble*/49 /* 4 * (8 + 4.25) */;
+    // bw = 125000 = 15625 * 2^3
+    //      250000 = 15625 * 2^4
+    //      500000 = 15625 * 2^5
+    // sf = 7..12
+    //
+    // osticks =  tmp * OSTICKS_PER_SEC * 1<<sf / bw
+    //
+    // 3 => counter reduced divisor 125000/8 => 15625
+    // 2 => counter 2 shift on tmp
+    sfx = sf+(7-SF7) - (3+2) - bw;
+    int div = 15625;
+    if( sfx > 4 ) {
+    // prevent 32bit signed int overflow in last step
+    div >>= sfx-4;
+    sfx = 4;
+    }
+    // Need 32bit arithmetic for this last step
+    return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div;
+}
+
+extern inline s1_t  rssi2s1 (int v);
+extern inline int   s12rssi (s1_t v);
+extern inline float  s12snr (s1_t v);
+extern inline s1_t   snr2s1 (double v);
+extern inline int   getRssi (rxqu_t*rxq);
+extern inline void  setRssi (rxqu_t*rxq, int v);
+
+extern inline rps_t updr2rps (dr_t dr);
+extern inline rps_t dndr2rps (dr_t dr);
+extern inline int isFasterDR (dr_t dr1, dr_t dr2);
+extern inline int isSlowerDR (dr_t dr1, dr_t dr2);
+extern inline dr_t  incDR    (dr_t dr);
+extern inline dr_t  decDR    (dr_t dr);
+extern inline dr_t  assertDR (dr_t dr);
+extern inline dr_t  validDR  (dr_t dr);
+extern inline dr_t  lowerDR  (dr_t dr, u1_t n);
+
+extern inline sf_t  getSf    (rps_t params);
+extern inline rps_t setSf    (rps_t params, sf_t sf);
+extern inline bw_t  getBw    (rps_t params);
+extern inline rps_t setBw    (rps_t params, bw_t cr);
+extern inline cr_t  getCr    (rps_t params);
+extern inline rps_t setCr    (rps_t params, cr_t cr);
+extern inline int   getNocrc (rps_t params);
+extern inline rps_t setNocrc (rps_t params, int nocrc);
+extern inline int   getIh    (rps_t params);
+extern inline rps_t setIh    (rps_t params, int ih);
+extern inline rps_t makeRps  (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc);
+extern inline int   sameSfBw (rps_t r1, rps_t r2);
+
+// END LORA
+// ================================================================================
+
+
+// Adjust DR for TX retries
+//  - indexed by retry count
+//  - return steps to lower DR
+static const u1_t DRADJUST[2+TXCONF_ATTEMPTS] = {
+    // normal frames - 1st try / no retry
+    0,
+    // confirmed frames
+    0,0,0,0,0,1,1,1,2
+};
+
+
+// Table below defines the size of one symbol as
+//   symtime = 256us * 2^T(sf,bw)
+// 256us is called one symunit. 
+//                 SF:                      
+//      BW:     |__7___8___9__10__11__12
+//      125kHz     |  2   3   4   5   6   7
+//      250kHz     |  1   2   3   4   5   6
+//      500kHz     |  0   1   2   3   4   5
+//  
+// Times for half symbol per DR
+// Per DR table to minimize rounding errors
+static const ostime_t DR2HSYM_osticks[] = {
+#if CFG_eu868
+#define dr2hsym(dr) (DR2HSYM_osticks[(dr)])
+    us2osticksRound(128<<7),  // DR_SF12
+    us2osticksRound(128<<6),  // DR_SF11
+    us2osticksRound(128<<5),  // DR_SF10
+    us2osticksRound(128<<4),  // DR_SF9
+    us2osticksRound(128<<3),  // DR_SF8
+    us2osticksRound(128<<2),  // DR_SF7
+    us2osticksRound(128<<1),  // DR_SF7B
+    us2osticksRound(80)       // FSK -- not used (time for 1/2 byte)
+#elif CFG_us915
+#define dr2hsym(dr) (DR2HSYM_osticks[(dr)&7])  // map DR_SFnCR -> 0-6
+    us2osticksRound(128<<5),  // DR_SF10   DR_SF12CR
+    us2osticksRound(128<<4),  // DR_SF9    DR_SF11CR
+    us2osticksRound(128<<3),  // DR_SF8    DR_SF10CR
+    us2osticksRound(128<<2),  // DR_SF7    DR_SF9CR
+    us2osticksRound(128<<1),  // DR_SF8C   DR_SF8CR
+    us2osticksRound(128<<0)   // ------    DR_SF7CR
+#endif
+};
+
+
+static ostime_t calcRxWindow (u1_t secs, dr_t dr) {
+    ostime_t rxoff, err;
+    if( secs==0 ) {
+    // aka 128 secs (next becaon)
+    rxoff = LMIC.drift;
+    err = LMIC.lastDriftDiff;
+    } else {
+    // scheduled RX window within secs into current beacon period
+    rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp;
+    err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp;
+    }
+    u1_t rxsyms = MINRX_SYMS;
+    err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns;
+    LMIC.rxsyms = MINRX_SYMS + (err / dr2hsym(dr));
+
+    return (rxsyms-PAMBL_SYMS) * dr2hsym(dr) + rxoff;
+}
+
+
+// Setup beacon RX parameters assuming we have an error of ms (aka +/-(ms/2))
+static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) {
+    if( ini ) {
+    LMIC.drift = 0;
+    LMIC.maxDriftDiff = 0;
+    LMIC.missedBcns = 0;
+    LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF;
+    }
+    ostime_t hsym = dr2hsym(DR_BCN);
+    LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym;
+    LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym;
+}
+
+
+// Setup scheduled RX window (ping/multicast slot)
+static void rxschedInit (xref2rxsched_t rxsched) {
+    os_clearMem(AESkey,16);
+    os_clearMem(LMIC.frame+8,8);
+    os_wlsbf4(LMIC.frame, LMIC.bcninfo.time);
+    os_wlsbf4(LMIC.frame+4, LMIC.devaddr);
+    os_aes(AES_ENC,LMIC.frame,16);
+    u1_t intvExp = rxsched->intvExp;
+    ostime_t off = os_rlsbf2(LMIC.frame) & (0x0FFF >> (7 - intvExp)); // random offset (slot units)
+    rxsched->rxbase = (LMIC.bcninfo.txtime +
+               BCN_RESERVE_osticks +
+               ms2osticks(BCN_SLOT_SPAN_ms * off)); // random offset osticks
+    rxsched->slot   = 0;
+    rxsched->rxtime = rxsched->rxbase - calcRxWindow(/*secs BCN_RESERVE*/2+(1<<intvExp),rxsched->dr);
+    rxsched->rxsyms = LMIC.rxsyms;
+}
+
+
+static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) {
+  again:
+    if( rxsched->rxtime - cando >= 0 )
+    return 1;
+    u1_t slot;
+    if( (slot=rxsched->slot) >= 128 )
+    return 0;
+    u1_t intv = 1<<rxsched->intvExp;
+    if( (rxsched->slot = (slot += (intv))) >= 128 )
+    return 0;
+    rxsched->rxtime = rxsched->rxbase
+    + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp)
+    - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr);
+    rxsched->rxsyms = LMIC.rxsyms;
+    goto again;
+}
+
+
+static ostime_t rndDelay (u1_t secSpan) {
+    u2_t r = os_getRndU2();
+    ostime_t delay = r;
+    if( delay > OSTICKS_PER_SEC )
+    delay = r % (u2_t)OSTICKS_PER_SEC;
+    if( secSpan > 0 )
+    delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC;
+    return delay;
+}
+
+
+static void txDelay (ostime_t reftime, u1_t secSpan) {
+    reftime += rndDelay(secSpan);
+    if( LMIC.globalDutyRate == 0  ||  (reftime - LMIC.globalDutyAvail) > 0 ) {
+    LMIC.globalDutyAvail = reftime;
+    LMIC.opmode |= OP_RNDTX;
+    }
+}
+
+
+static void setDrJoin (u1_t reason, u1_t dr) {
+    EV(drChange, INFO, (e_.reason    = reason,
+            e_.deveui    = MAIN::CDEV->getEui(),
+            e_.dr        = dr|DR_PAGE,
+            e_.txpow     = LMIC.adrTxPow,
+            e_.prevdr    = LMIC.datarate|DR_PAGE,
+            e_.prevtxpow = LMIC.adrTxPow));
+    LMIC.datarate = dr;
+    DO_DEVDB(updateDatarate, dr);
+}
+
+
+static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) {
+    EV(drChange, INFO, (e_.reason    = reason,
+            e_.deveui    = MAIN::CDEV->getEui(),
+            e_.dr        = dr|DR_PAGE,
+            e_.txpow     = pow,
+            e_.prevdr    = LMIC.datarate|DR_PAGE,
+            e_.prevtxpow = LMIC.adrTxPow));
+    if( pow != KEEP_TXPOW )
+    LMIC.adrTxPow = pow;
+    LMIC.datarate = dr;
+    DO_DEVDB(updateDatarate, dr);
+    LMIC.opmode |= OP_NEXTCHNL;
+}
+
+
+void LMIC_stopPingable (void) {
+    LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI);
+}
+
+
+void LMIC_setPingable (u1_t intvExp) {
+    // Change setting
+    LMIC.ping.intvExp = (intvExp & 0x7);
+    LMIC.opmode |= OP_PINGABLE;
+    // App may call LMIC_enableTracking() explicitely before
+    // Otherwise tracking is implicitly enabled here
+    if( (LMIC.opmode & (OP_TRACK|OP_SCAN)) == 0  &&  LMIC.bcninfoTries == 0 )
+    LMIC_enableTracking(0);
+}
+
+
+#if CFG_eu868
+// ================================================================================
+//
+// BEG: EU868 related stuff
+//
+enum { BAND_MILLI=0, BAND_CENTI=1, BAND_DECI=2 };
+static const u4_t iniChannelFreq[12] = {
+    // Join frequencies and duty cycle limit (0.1%)
+    EU868_F1|BAND_MILLI, EU868_F2|BAND_MILLI, EU868_F3|BAND_MILLI,
+    EU868_J4|BAND_MILLI, EU868_J5|BAND_MILLI, EU868_J6|BAND_MILLI,
+    // Default operational frequencies
+    EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI,
+    EU868_F4|BAND_MILLI, EU868_F5|BAND_MILLI, EU868_F6|BAND_DECI
+};
+
+static void initDefaultChannels (bit_t join) {
+    LMIC.channelMap = 0x3F;
+    u1_t su = join ? 0 : 6;
+    for( u1_t fu=0; fu<6; fu++,su++ ) {
+    LMIC.channelFreq[fu] = iniChannelFreq[su];
+    LMIC.channelDrs[fu]  = DR_SF12 | (DR_SF7<<4);
+    }
+    if( !join )
+    LMIC.channelDrs[1] = DR_SF12 | (DR_FSK<<4);
+
+    LMIC.bands[BAND_MILLI].txcap = 1000;  // 0.1%
+    LMIC.bands[BAND_MILLI].txpow = 14;
+    LMIC.bands[BAND_CENTI].txcap = 100;   // 1%
+    LMIC.bands[BAND_CENTI].txpow = 14;
+    LMIC.bands[BAND_DECI ].txcap = 10;    // 10%
+    LMIC.bands[BAND_DECI ].txpow = 27;
+    LMIC.bands[BAND_MILLI].avail = 
+    LMIC.bands[BAND_CENTI].avail =
+    LMIC.bands[BAND_DECI ].avail = os_getTime();
+}
+
+static u4_t convFreq (xref2u1_t ptr) {
+    u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100;
+    if( freq >= EU868_FREQ_MIN && freq <= EU868_FREQ_MAX )
+    freq = 0;
+    return freq;
+}
+
+static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) {
+    if( chidx >= MAX_CHANNELS )
+    return 0;
+    if( freq >= 869400000 && freq <= 869650000 )
+    freq |= BAND_DECI;  // 10% 27dBm
+    else if( (freq >= 868000000 && freq <= 868600000) ||
+         (freq >= 869700000 && freq <= 870000000)  )
+    freq |= BAND_CENTI;  // 1% 14dBm
+    //else 
+    //  freq |= BAND_MILLI;  // 0.1% 14dBm
+    LMIC.channelFreq[chidx] = freq;
+    LMIC.channelDrs [chidx] = drs < 0 ? (DR_SF12|(DR_SF7<<4)) : drs;
+    return 1;
+}
+
+static u1_t mapChannels (u1_t chpage, u2_t chmap) {
+    if( chpage != 0 || (chmap & ~((u2_t)(1<<MAX_CHANNELS)-1)) != 0 )
+    return 0;  // illegal input
+    for( u1_t chnl=0; chnl<MAX_CHANNELS; chnl++ ) {
+    if( (chmap & (1<<chnl)) != 0 && LMIC.channelFreq[chnl] == 0 )
+        chmap &= ~(1<<chnl); // ignore - channel is not defined
+    }
+    LMIC.channelMap = chmap;
+    return 1;
+}
+
+
+static void updateTx (ostime_t txbeg) {
+    u4_t freq = LMIC.channelFreq[LMIC.txChnl];
+    // Update global/band specific duty cycle stats
+    ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
+    // Update channel/global duty cycle stats
+    xref2band_t band = &LMIC.bands[freq & 0x3];
+    LMIC.freq  = freq & ~(u4_t)3;
+    LMIC.txpow = band->txpow;
+    band->avail = txbeg + airtime * band->txcap;
+    // Update obsolete avail's to prevent rollover
+    // (needed for devices that send rarely - e.g. once/twice a day)
+    for( u1_t b=0; b<MAX_BANDS; b++ ) {
+    if( LMIC.bands[b].avail - txbeg < 0 )
+        LMIC.bands[b].avail = txbeg;
+    }
+    if( LMIC.globalDutyRate != 0 )
+    LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
+}
+
+static ostime_t nextTx (ostime_t now) {
+    // If we do not find a suitable channel stop sending (misconfigured device)
+    ostime_t mintime = now + /*10h*/36000*OSTICKS_PER_SEC;
+    s1_t     bmask   = 0;
+    for( u1_t b=0; b<MAX_BANDS; b++ ) {
+    xref2band_t band = &LMIC.bands[b];
+    if( band->txcap == 0 ) // band not setup
+        continue;
+    if( now - band->avail >= 0 ) {
+        bmask = (bmask < 0 ? 0 : bmask) | (1<<b);
+    }
+    else if( mintime - band->avail > 0 ) {
+        mintime = band->avail;
+    }
+    }
+    if( bmask == 0 )
+    return mintime;
+    u1_t chnl, ccnt;
+    u2_t cset, mask;
+    chnl = cset = ccnt = 0;
+    mask = 1;
+    while( mask && mask <= LMIC.channelMap ) {
+    // Channel is enabled AND and in a ready band and can handle the datarate
+    if( (mask & LMIC.channelMap) != 0  &&  (bmask & (1 << (LMIC.channelFreq[chnl] & 0x3))) != 0) {
+        u1_t drs = LMIC.channelDrs[chnl];
+        if( !(isSlowerDR((dr_t)LMIC.datarate, (dr_t)(drs & 0xF)) ||   // lowest datarate
+          isFasterDR((dr_t)LMIC.datarate, (dr_t)(drs >>  4))) ) { // fastest datarate
+        cset |= mask;
+        ccnt++;
+        }
+    }
+    mask <<= 1;
+    chnl++;
+    }
+    if( ccnt == 0 ) // No eligible channel - misconfigured device?
+    return mintime;
+    ccnt = os_getRndU2() % ccnt;
+    mask = 1;
+    chnl = 0;
+    do {
+    if( (cset & mask) != 0 ) {
+        if( ccnt==0 ) {
+        LMIC.txChnl = chnl;
+        return now;
+        }
+        ccnt--;
+    }
+    mask <<= 1;
+    chnl++;
+    } while(1);
+}
+
+static void setBcnRxParams (void) {
+    LMIC.dataLen = 0;
+    LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3;
+    LMIC.rps  = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
+}
+
+#define setRx1Params() /*LMIC.freq/rps remain unchanged*/
+
+static void initJoinLoop (void) {
+    LMIC.txChnl = os_getRndU1() % 3;
+    LMIC.adrTxPow = 14;
+    setDrJoin(DRCHG_SET, DR_SF7);
+    initDefaultChannels(1);
+    ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
+    LMIC.txend = LMIC.bands[BAND_MILLI].avail;
+}
+
+
+static ostime_t nextJoinState (void) {
+    // Try 869.x and then 864.x with same DR
+    // If both fail try next lower datarate
+    LMIC.opmode &= ~OP_NEXTCHNL;
+    LMIC.txend = LMIC.bands[BAND_MILLI].avail;
+    if( LMIC.txChnl < 3 ) {
+    LMIC.txChnl += 3;
+    } else {
+    if( LMIC.datarate == DR_SF12 ) {
+        setDrJoin(DRCHG_NOJACC, DR_SF7);
+        // We tried one round of FSK..SF12
+        // Now pick new random channel and insert a random delay and try another round.
+        LMIC.txChnl = os_getRndU1() % 3;
+        if( LMIC.txCnt < 5 )
+        LMIC.txCnt += 1;
+        // Delay by 7,15,31,63,127,255 secs
+        return rndDelay((4<<LMIC.txCnt)-1) | 1;   // |1 - trigger EV_JOIN_FAILED event
+    }
+    LMIC.txChnl = LMIC.txChnl==5 ? 0 : LMIC.txChnl-2;
+    setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
+    }
+    // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it)
+    return 3*OSTICKS_PER_SEC;
+}
+
+//
+// END: EU868 related stuff
+//
+// ================================================================================
+#elif CFG_us915
+// ================================================================================
+//
+// BEG: US915 related stuff
+//
+
+static void initDefaultChannels (void) {
+    for( u1_t i=0; i<4; i++ )
+    LMIC.channelMap[i] = 0xFFFF;
+    LMIC.channelMap[4] = 0x00FF;
+}
+
+static u4_t convFreq (xref2u1_t ptr) {
+    u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100;
+    if( freq >= US915_FREQ_MIN && freq <= US915_FREQ_MAX )
+    freq = 0;
+    return freq;
+}
+
+static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) {
+    if( chidx < 72 || chidx >= 72+MAX_XCHANNELS )
+    return 0; // channels 0..71 are hardwired
+    chidx -= 72;
+    LMIC.xchFreq[chidx] = freq;
+    LMIC.xchDrs[chidx] = drs < 0 ? (DR_SF10|(DR_SF8C<<4)) : drs;
+    LMIC.channelMap[chidx>>4] |= (1<<(chidx&0xF));
+    return 1;
+}
+
+
+static u1_t mapChannels (u1_t chpage, u2_t chmap) {
+    if( chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF ) {
+    u2_t en125 = chpage == MCMD_LADR_CHP_125ON ? 0xFFFF : 0x0000;
+    for( u1_t u=0; u<4; u++ )
+        LMIC.channelMap[u] = en125;
+    LMIC.channelMap[64/16] = chmap;
+    } else {
+    if( chpage >= (72+MAX_XCHANNELS+15)/16 )
+        return 0;
+    LMIC.channelMap[chpage] = chmap;
+    }
+    return 1;
+}
+
+static void updateTx (ostime_t txbeg) {
+    u1_t chnl = LMIC.txChnl;
+    if( chnl < 64 ) {
+    LMIC.freq = US915_125kHz_UPFBASE + chnl*US915_125kHz_UPFSTEP;
+    LMIC.txpow = 30;
+    return;
+    }
+    LMIC.txpow = 26;
+    if( chnl < 64+8 ) {
+    LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP;
+    } else {
+    ASSERT(chnl < 64+8+MAX_XCHANNELS);
+    LMIC.freq = LMIC.xchFreq[chnl-72];
+    }
+
+    // Update global duty cycle stats
+    if( LMIC.globalDutyRate != 0 ) {
+    ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
+    LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
+    }
+}
+
+// US does not have duty cycling - return now as earliest TX time
+#define nextTx(now) (_nextTx(),(now))
+static void _nextTx (void) {
+    u1_t  chnl  = LMIC.txChnl;
+    u1_t  cnt   = 0;
+    bit_t bw500 = (LMIC.datarate >= DR_SF8C);
+
+    if( (LMIC.chRnd & 0xF) == 0 ) {
+    chnl += LMIC.chRnd>>4;
+    LMIC.chRnd = os_getRndU1();
+    }
+    LMIC.chRnd--;
+  again:
+    chnl += 1;
+    if( bw500 ) {
+    // Only channels 64..71
+    chnl = 64+(chnl&0x7);
+    // At least one 500kHz channel must be enabled
+    // - otherwise MAC should not have used such a datarate
+    ASSERT((LMIC.channelMap[64/16]&0xFF)!=0x00);
+    if( ++cnt == 8 )
+        return; // no appropriate channel enabled stay where we are
+    } else {
+    // At least one 125kHz channel must be enabled
+    // - otherwise MAC should not have used DR_SF10-7
+    ASSERT((LMIC.channelMap[0]|LMIC.channelMap[1]|LMIC.channelMap[2]|LMIC.channelMap[3])!=0x0000);
+    chnl = chnl&0x3F;
+    if( ++cnt == 64 )
+        return; // no appropriate channel enabled stay where we are
+    }
+    if( (LMIC.channelMap[(chnl >> 4)] & (1<<(chnl&0xF))) == 0 )
+    goto again;  // not enabled
+    LMIC.txChnl = chnl;
+}
+
+static void setBcnRxParams (void) {
+    LMIC.dataLen = 0;
+    LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl*US915_500kHz_DNFSTEP;
+    LMIC.rps  = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
+}
+
+#define setRx1Params(void) {                         \
+    LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \
+    if( /* TX datarate */LMIC.rxsyms < DR_SF8C )                \
+    LMIC.rxsyms += DR_SF10CR - DR_SF10;                \
+    else if( LMIC.rxsyms == DR_SF8C )                    \
+    LMIC.rxsyms = DR_SF7CR;                        \
+    LMIC.rps = dndr2rps(LMIC.rxsyms);                    \
+}
+
+static void initJoinLoop (void) {
+    LMIC.txChnl = os_getRndU1() & 0x3F;
+    LMIC.adrTxPow = 20;
+    ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
+    LMIC.txend = os_getTime();
+    setDrJoin(DRCHG_SET, DR_SF7);
+}
+
+static ostime_t nextJoinState (void) {
+    // Try the following:
+    //   SF7/8/9/10  on a random channel 0..63
+    //   SF8C        on a random channel 64..71
+    //
+    LMIC.opmode &= ~OP_NEXTCHNL;
+    LMIC.txend = os_getTime();
+    if( LMIC.datarate != DR_SF8C ) {
+    LMIC.txChnl = 64+(LMIC.txChnl&7);
+    LMIC.datarate = DR_SF8C;
+    } else {
+    LMIC.txChnl = os_getRndU1() & 0x3F;
+    LMIC.datarate = DR_SF7 - (LMIC.txCnt++ & 3);
+    if( (LMIC.txCnt & 3) == 0 ) {
+        // Tried SF10/SF8C
+        // Delay by 7,15,31,63,127,255 secs
+        return rndDelay((4<<(LMIC.txCnt>>2))-1) | 1;   // |1 - trigger EV_JOIN_FAILED event
+    }
+    }
+    // Always wait 500ms in case there was a TX and we just missed it.
+    // Sending immediately would not be picked up because GW is still busy txing missed frame.
+    return (OSTICKS_PER_SEC/2) & ~1;
+}
+
+//
+// END: US915 related stuff
+//
+// ================================================================================
+#else
+#error Unsupported frequency band!
+#endif
+
+
+static void runEngineUpdate (xref2osjob_t osjob) {
+    engineUpdate();
+}
+
+
+static void reportEvent (ev_t ev) {
+    EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = ev));
+    ON_LMIC_EVENT(ev);
+    engineUpdate();
+}
+
+
+static void stateJustJoined (void) {
+    LMIC.seqnoDn     = LMIC.seqnoUp = 0;
+    LMIC.rejoinCnt   = 0;
+    LMIC.dnConf      = LMIC.adrChanged = LMIC.ladrAns = LMIC.devsAns = 0;
+    LMIC.moreData    = LMIC.dn2Ans = LMIC.snchAns = LMIC.dutyCapAns = 0;
+    LMIC.pingSetAns  = 0;
+    LMIC.upRepeat    = 0;
+    LMIC.adrAckReq   = LINK_CHECK_INIT;
+    LMIC.dn2Dr       = DR_DNW2;
+    LMIC.dn2Freq     = FREQ_DNW2;
+    LMIC.bcnChnl     = CHNL_BCN;
+    LMIC.ping.freq   = FREQ_PING;
+    LMIC.ping.dr     = DR_PING;
+}
+
+
+// ================================================================================
+// Decoding frames
+
+
+// Decode beacon  - do not overwrite bcninfo unless we have a match!
+static int decodeBeacon (void) {
+    ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this
+    xref2u1_t d = LMIC.frame;
+    if( os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d, OFF_BCN_CRC1) )
+    return 0;   // first (common) part fails CRC check
+    // First set of fields is ok
+    u4_t bcnnetid = os_rlsbf4(&d[OFF_BCN_NETID]) & 0xFFFFFF;
+    if( bcnnetid != LMIC.netid )
+    return -1;  // not the beacon we're looking for
+
+    LMIC.bcninfo.flags &= ~(BCN_PARTIAL|BCN_FULL);
+    // Match - update bcninfo structure
+    os_copyMem((xref2u1_t)&LMIC.bcninfo.rxq, (xref2u1_t)&LMIC.rxq, SIZEOFEXPR(LMIC.rxq));
+    LMIC.bcninfo.txtime = LMIC.rxtime - AIRTIME_BCN_osticks;
+    LMIC.bcninfo.time   = os_rlsbf4(&d[OFF_BCN_TIME]);
+    LMIC.bcninfo.flags |= BCN_PARTIAL;
+
+    // Check 2nd set
+    if( os_rlsbf2(&d[OFF_BCN_CRC2]) != os_crc16(d,OFF_BCN_CRC2) )
+    return 1;
+    // Second set of fields is ok
+    LMIC.bcninfo.lat    = (s4_t)os_rlsbf4(&d[OFF_BCN_LAT-1]) >> 8; // read as signed 24-bit
+    LMIC.bcninfo.lon    = (s4_t)os_rlsbf4(&d[OFF_BCN_LON-1]) >> 8; // ditto
+    LMIC.bcninfo.info   = d[OFF_BCN_INFO];
+    LMIC.bcninfo.flags |= BCN_FULL;
+    return 2;
+}
+
+
+static bit_t decodeFrame (void) {
+    xref2u1_t d = LMIC.frame;
+    u1_t hdr    = d[0];
+    u1_t ftype  = hdr & HDR_FTYPE;
+    int  dlen   = LMIC.dataLen;
+    if( dlen < OFF_DAT_OPTS+4 ||
+    (hdr & HDR_MAJOR) != HDR_MAJOR_V1 ||
+    (ftype != HDR_FTYPE_DADN  &&  ftype != HDR_FTYPE_DCDN) ) {
+    // Basic sanity checks failed
+    EV(specCond, WARN, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
+                e_.eui    = MAIN::CDEV->getEui(),
+                e_.info   = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]),
+                e_.info2  = hdr + (dlen<<8)));
+      norx:
+    LMIC.dataLen = 0;
+    return 0;
+    }
+    // Validate exact frame length
+    // Note: device address was already read+evaluated in order to arrive here.
+    int  fct   = d[OFF_DAT_FCT];
+    u4_t addr  = os_rlsbf4(&d[OFF_DAT_ADDR]);
+    u4_t seqno = os_rlsbf2(&d[OFF_DAT_SEQNO]);
+    int  olen  = fct & FCT_OPTLEN;
+    int  ackup = (fct & FCT_ACK) != 0 ? 1 : 0;   // ACK last up frame
+    int  poff  = OFF_DAT_OPTS+olen;
+    int  pend  = dlen-4;  // MIC
+
+    if( addr != LMIC.devaddr ) {
+    EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS,
+                e_.eui    = MAIN::CDEV->getEui(),
+                e_.info   = addr,
+                e_.info2  = LMIC.devaddr));
+    goto norx;
+    }
+    if( poff > pend ) {
+    EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16)));
+    goto norx;
+    }
+
+    int port = -1;
+    int replayConf = 0;
+
+    if( pend > poff )
+    port = d[poff++];
+
+    seqno = LMIC.seqnoDn + (u2_t)(seqno - LMIC.seqnoDn);
+
+    if( !aes_verifyMic(LMIC.nwkKey, LMIC.devaddr, seqno, /*dn*/1, d, pend) ) {
+    EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_MIC,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = Base::lsbf4(&d[pend]),
+               e_.info2  = seqno));
+    goto norx;
+    }
+    if( seqno < LMIC.seqnoDn ) {
+    if( (s4_t)seqno > (s4_t)LMIC.seqnoDn ) {
+        EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER,
+                e_.eui    = MAIN::CDEV->getEui(),
+                e_.info   = LMIC.seqnoDn, 
+                e_.info2  = seqno));
+        goto norx;
+    }
+    if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) {
+        EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE,
+                e_.eui    = MAIN::CDEV->getEui(),
+                e_.info   = LMIC.seqnoDn, 
+                e_.info2  = seqno));
+        goto norx;
+    }
+    // Replay of previous sequence number allowed only if
+    // previous frame and repeated both requested confirmation
+    replayConf = 1;
+    }
+    else {
+    if( seqno > LMIC.seqnoDn ) {
+        EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP,
+                e_.eui    = MAIN::CDEV->getEui(),
+                e_.info   = LMIC.seqnoDn, 
+                e_.info2  = seqno));
+    }
+    LMIC.seqnoDn = seqno+1;  // next number to be expected
+    DO_DEVDB(updateSeqnoDn, LMIC.seqnoDn);
+    // DN frame requested confirmation - provide ACK once with next UP frame
+    LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0);
+    }
+
+    if( LMIC.dnConf || (fct & FCT_MORE) )
+    LMIC.opmode |= OP_POLL;
+
+    // We heard from network
+    LMIC.adrChanged = 0;
+    LMIC.adrAckReq = LINK_CHECK_INIT;
+
+    // Process OPTS
+    int m = LMIC.rxq.rssi - RSSI_OFF - getSensitivity(LMIC.rps);
+    LMIC.margin = m < 0 ? 0 : m > 254 ? 254 : m;
+
+    xref2u1_t opts = &d[OFF_DAT_OPTS];
+    int oidx = 0;
+    while( oidx < olen ) {
+    switch( opts[oidx] ) {
+    case MCMD_LCHK_ANS: {
+        //int gwmargin = opts[oidx+1];
+        //int ngws = opts[oidx+2];
+        oidx += 3;
+        continue;
+    }
+    case MCMD_LADR_REQ: {
+        u1_t p1     = opts[oidx+1];            // txpow + DR
+        u2_t chmap  = os_rlsbf2(&opts[oidx+2]);// list of enabled channel
+        u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK;     // channel page
+        u1_t uprpt  = opts[oidx+4] & MCMD_LADR_REPEAT_MASK;     // up repeat count
+        oidx += 5;
+
+        LMIC.ladrAns = 0x80 |     // Include an answer into next frame up
+        MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK;
+        if( !mapChannels(chpage, chmap) )
+        LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK;
+        dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT);
+        if( !validDR(dr) ) {
+        LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK;
+        dr = (dr_t)LMIC.datarate;
+        EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
+                   e_.eui    = MAIN::CDEV->getEui(),
+                   e_.info   = Base::lsbf4(&d[pend]),
+                   e_.info2  = Base::msbf4(&opts[oidx-4])));
+        }
+        LMIC.upRepeat = uprpt;
+        setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1));
+        LMIC.adrChanged = 1;  // Trigger an ACK to NWK
+        continue;
+    }
+    case MCMD_DEVS_REQ: {
+        LMIC.devsAns = 1;
+        oidx += 1;
+        continue;
+    }
+    case MCMD_DN2P_SET: {
+        dr_t dr = (dr_t)(opts[oidx+1] & 0x0F);
+        u4_t freq = convFreq(&opts[oidx+2]);
+        oidx += 5;
+        LMIC.dn2Ans = 0x80;   // answer pending
+        if( validDR(dr) )
+        LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK;
+        if( freq != 0 )
+        LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK;
+        if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) {
+        LMIC.dn2Dr = dr;
+        LMIC.dn2Freq = freq;
+        DO_DEVDB(updateDn2,LMIC.dn2Dr,LMIC.dn2Freq);
+        }
+        continue;
+    }
+    case MCMD_DCAP_REQ: {
+        u1_t cap = opts[oidx+1];
+        oidx += 2;
+        // A value cap=0xFF means device is OFF unless enabled again manually
+        // We just set duty cap to 0xF which is 0.003% -- pretty much off.
+        // We don't check 0xF0 bits if cap!=0xFF
+        LMIC.globalDutyRate  = cap & 0xF;
+        LMIC.globalDutyAvail = os_getTime();
+        DO_DEVDB(updateDutyCap,cap);
+        LMIC.dutyCapAns = 1;
+        continue;
+    }
+    case MCMD_SNCH_REQ: {
+        u1_t chidx = opts[oidx+1];  // channel
+        u4_t freq  = convFreq(&opts[oidx+2]); // freq
+        u1_t drs   = opts[oidx+5];  // datarate span
+        LMIC.snchAns = 0x80;
+        if( freq != 0 && setupChannel(chidx,freq,drs) )
+        LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK;
+        oidx += 6;
+    }
+    case MCMD_PING_SET: {
+        u4_t freq = convFreq(&opts[oidx+1]);
+        oidx += 4;
+        u1_t flags = 0x80;
+        if( freq != 0 ) {
+        flags |= MCMD_PING_ANS_FQACK;
+        LMIC.ping.freq = freq;
+        DO_DEVDB(updateClassB,LMIC.ping.intvExp,freq,LMIC.ping.dr);
+        }
+        LMIC.pingSetAns = flags;
+        continue;
+    }
+    case MCMD_BCNI_ANS: {
+        // Ignore if tracking already enabled
+        if( (LMIC.opmode & OP_TRACK) == 0 ) {
+        LMIC.bcnChnl = opts[oidx+3];
+        // Enable tracking - bcninfoTries
+        LMIC.opmode |= OP_TRACK;
+        // Cleared later in txComplete handling - triggers EV_BEACON_FOUND
+        ASSERT(LMIC.bcninfoTries!=0);
+        // Setup RX parameters
+        LMIC.bcninfo.txtime =  (LMIC.rxtime
+                       - calcAirTime(LMIC.rps, LMIC.dataLen)
+                       + ms2osticks(os_rlsbf2(&opts[oidx+1]) * 10)
+                       + ms2osticksCeil(5)
+                       - BCN_INTV_osticks);
+        LMIC.bcninfo.flags = 0;  // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared)
+        calcBcnRxWindowFromMillis(10,1);  // error of +/-5 ms 
+
+        EV(lostFrame, INFO, (e_.reason  = EV::lostFrame_t::MCMD_BCNI_ANS,
+                     e_.eui     = MAIN::CDEV->getEui(),
+                     e_.lostmic = Base::lsbf4(&d[pend]),
+                     e_.info    = (LMIC.missedBcns |
+                           (osticks2us(LMIC.bcninfo.txtime + BCN_INTV_osticks
+                                   - LMIC.bcnRxtime) << 8)),
+                     e_.time    = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks)));
+        }
+        oidx += 4;
+        continue;
+    }
+    }
+    EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = Base::lsbf4(&d[pend]),
+               e_.info2  = Base::msbf4(&opts[oidx])));
+    break;
+    }
+    if( oidx != olen ) {
+    EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = 0x1000000 + (oidx) + (olen<<8)));
+    }
+
+    if( !replayConf ) {
+    // Handle payload only if not a replay
+    // Decrypt payload - if any
+    if( port >= 0  &&  pend-poff > 0 )
+        aes_cipher(port <= 0 ? LMIC.nwkKey : LMIC.artKey, LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff);
+
+    EV(dfinfo, DEBUG, (e_.deveui  = MAIN::CDEV->getEui(),
+               e_.devaddr = LMIC.devaddr,
+               e_.seqno   = seqno,
+               e_.flags   = (port < 0 ? EV::dfinfo_t::NOPORT : 0) | EV::dfinfo_t::DN,
+               e_.mic     = Base::lsbf4(&d[pend]),
+               e_.hdr     = d[LORA::OFF_DAT_HDR],
+               e_.fct     = d[LORA::OFF_DAT_FCT],
+               e_.port    = port,
+               e_.plen    = dlen,
+               e_.olen    = olen,
+               memcpy(e_.opts, opts, olen)));
+    } else {
+    EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_REPLAY,
+                e_.eui    = MAIN::CDEV->getEui(),
+                e_.info   = Base::lsbf4(&d[pend]),
+                e_.info2  = seqno));
+    }
+
+    if( // NWK acks but we don't have a frame pending
+    (ackup && LMIC.txCnt == 0) ||
+    // We sent up confirmed and we got a response in DNW1/DNW2
+    // BUT it did not carry an ACK - this should never happen
+    // Do not resend and assume frame was not ACKed.
+    (!ackup && LMIC.txCnt != 0) ) {
+    EV(specCond, ERR, (e_.reason = EV::specCond_t::SPURIOUS_ACK,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = seqno,
+               e_.info2  = ackup));
+    }
+
+    if( LMIC.txCnt != 0 ) // we requested an ACK
+    LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK;
+
+    if( port < 0 ) {
+    LMIC.txrxFlags |= TXRX_NOPORT;
+    LMIC.dataBeg = LMIC.dataLen = 0;
+    } else {
+    LMIC.dataBeg = poff;
+    LMIC.dataLen = pend-poff;
+    }
+    return 1;
+}
+
+
+// ================================================================================
+// TX/RX transaction support
+
+
+static void setupRx2 (void) {
+    LMIC.txrxFlags = TXRX_DNW2;
+    LMIC.rps = dndr2rps(LMIC.dn2Dr);
+    LMIC.freq = LMIC.dn2Freq;
+    LMIC.dataLen = 0;
+    os_radio(RADIO_RX);
+}
+
+
+static void schedRx2 (ostime_t delay, osjobcb_t func) {
+    // Add 1.5 symbols we need 5 out of 8. Try to sync 1.5 symbols into the preamble.
+    LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dn2Dr);
+    os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
+}
+
+static void setupRx1 (osjobcb_t func) {
+    LMIC.txrxFlags = TXRX_DNW1;
+    // Turn LMIC.rps from TX over to RX
+    LMIC.rps = setNocrc(LMIC.rps,1);
+    LMIC.dataLen = 0;
+    LMIC.osjob.func = func;
+    os_radio(RADIO_RX);
+}
+
+
+// Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime
+static void txDone (ostime_t delay, osjobcb_t func) {
+    // NOTE: LMIC.rxsyms carries the modified DR for TX (LMIC.datarate is the base DR - [CONFIRM mode])
+    u1_t dr = LMIC.rxsyms;    // rxschedInit - would overwrite rxsyms
+    if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) {
+    rxschedInit(&LMIC.ping);    // note: reuses LMIC.frame buffer!
+    LMIC.opmode |= OP_PINGINI;
+    }
+    // Change RX frequency / rps (US only) before we increment txChnl
+    setRx1Params();
+    // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.])
+    // Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune
+    // into the middle of the 8 symbols preamble.
+#if CFG_eu868
+    if( /* TX datarate */LMIC.rxsyms == DR_FSK ) {
+    LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
+    LMIC.rxsyms = RXLEN_FSK;
+    }
+    else
+#endif
+    {
+    LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(dr);
+    LMIC.rxsyms = MINRX_SYMS;
+    }
+    os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
+}
+
+
+// ======================================== Join frames
+
+
+static void onJoinFailed (xref2osjob_t osjob) {
+    // Notify app - must call LMIC_reset() to stop joining
+    // otherwise join procedure continues.
+    reportEvent(EV_JOIN_FAILED);
+}
+
+
+static bit_t processJoinAccept (void) {
+    ASSERT(LMIC.txrxFlags != TXRX_DNW1 || LMIC.dataLen != 0);
+    ASSERT((LMIC.opmode & OP_TXRXPEND)!=0);
+
+    if( LMIC.dataLen == 0 ) {
+      nojoinframe:
+    if( (LMIC.opmode & OP_OTA) != 0 ) {
+    if( (LMIC.opmode & OP_JOINING) == 0 ) {
+        ASSERT((LMIC.opmode & OP_REJOIN) != 0);
+        // REJOIN attempt for roaming
+        LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND);
+        if( LMIC.rejoinCnt < 10 )
+        LMIC.rejoinCnt++;
+        reportEvent(EV_REJOIN_FAILED);
+        return 1;
+    }
+    }
+    LMIC.opmode &= ~OP_TXRXPEND;
+    ostime_t delay = nextJoinState();
+    EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC,
+                e_.eui    = MAIN::CDEV->getEui(),
+                e_.info   = LMIC.datarate|DR_PAGE,
+                e_.info2  = osticks2ms(delay)));
+    // Build next JOIN REQUEST with next engineUpdate call
+    // Optionally, report join failed.
+    // Both after a random/chosen amount of ticks.
+    os_setTimedCallback(&LMIC.osjob, os_getTime()+delay,
+                (delay&1) != 0
+                ? FUNC_ADDR(onJoinFailed)      // one JOIN iteration done and failed
+                : FUNC_ADDR(runEngineUpdate)); // next step to be delayed
+    return 1;
+    }
+    u1_t hdr  = LMIC.frame[0];
+    u1_t dlen = LMIC.dataLen;
+    u4_t mic  = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt!
+    if( (dlen != LEN_JA && dlen != LEN_JAEXT)
+    || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) {
+    EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = dlen < 4 ? 0 : mic,
+               e_.info2  = hdr + (dlen<<8)));
+      badframe:
+    goto nojoinframe;
+    }
+    aes_encrypt(LMIC.frame+1, dlen-1);
+    if( !aes_verifyMic0(LMIC.frame, dlen-4) ) {
+    EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC,
+               e_.info   = mic));
+    goto badframe;
+    }
+
+    u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR);
+    LMIC.devaddr = addr;
+    LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_BCN_NETID-1]) >> 8;
+
+#if CFG_eu868
+    initDefaultChannels(0);
+#endif
+    if( dlen > LEN_JA ) {
+    dlen = OFF_CFLIST;
+#if CFG_eu868
+    u1_t chidx=3;
+#elif CFG_us915
+    u1_t chidx=72;
+#endif
+    for( ; chidx<8; chidx++, dlen+=3 )
+        setupChannel(chidx, os_rlsbf4(&LMIC.frame[dlen-1]) >> 8, -1);
+    }
+
+    // already incremented when JOIN REQ got sent off
+    aes_sessKeys(LMIC.devNonce-1, &LMIC.frame[OFF_JA_ARTNONCE], LMIC.nwkKey, LMIC.artKey);
+    DO_DEVDB(updateJoinAcc, LMIC.devaddr, LMIC.nwkKey, LMIC.artKey);
+
+    EV(joininfo, INFO, (e_.arteui  = MAIN::CDEV->getArtEui(),
+            e_.deveui  = MAIN::CDEV->getEui(),
+            e_.devaddr = LMIC.devaddr,
+            e_.oldaddr = oldaddr,
+            e_.nonce   = LMIC.devNonce-1,
+            e_.mic     = mic,
+            e_.flags   = ((LMIC.opmode & OP_REJOIN) != 0
+                      ? EV::joininfo_t::REJOIN_ACCEPT
+                      : EV::joininfo_t::ACCEPT)));
+    
+    ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0);
+    if( (LMIC.opmode & OP_REJOIN) != 0 ) {
+    LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt);
+    }
+    LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
+    stateJustJoined();
+    reportEvent(EV_JOINED);
+    return 1;
+}
+
+static void processRx1Jacc (xref2osjob_t osjob) {
+    if( LMIC.dataLen == 0 )
+    LMIC.txrxFlags = 0;  // nothing in 1st/2nd DN slot
+    processJoinAccept();
+}
+
+static void setupRx1Jacc (xref2osjob_t osjob) {
+    setupRx1(FUNC_ADDR(processRx1Jacc));
+}
+
+
+static void jreqDone (xref2osjob_t osjob) {
+    txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc));
+}
+
+// ======================================== Data frames
+
+// Fwd decl.
+static bit_t processDnData(void);
+
+static void processRx2DnData (xref2osjob_t osjob) {
+    if( LMIC.dataLen == 0 )
+    LMIC.txrxFlags = 0;  // nothing in 1st/2nd DN slot
+    processDnData();
+}
+
+
+static void setupRx2DnData (xref2osjob_t osjob) {
+    LMIC.osjob.func = FUNC_ADDR(processRx2DnData);
+    setupRx2();
+}
+
+
+static void processRx1DnData (xref2osjob_t osjob) {
+    if( LMIC.dataLen == 0 || !processDnData() )
+    schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData));
+}
+
+
+static void setupRx1DnData (xref2osjob_t osjob) {
+    setupRx1(FUNC_ADDR(processRx1DnData));
+}
+
+
+static void updataDone (xref2osjob_t osjob) {
+    txDone(DELAY_DNW1_osticks, FUNC_ADDR(setupRx1DnData));
+}
+
+// ======================================== 
+
+
+static void buildDataFrame (void) {
+    bit_t txdata = ((LMIC.opmode & (OP_TXDATA|OP_POLL)) != OP_POLL);
+    u1_t dlen = txdata ? LMIC.pendTxLen : 0;
+
+    // Piggyback MAC options
+    // Prioritize by importance
+    int  end = OFF_DAT_OPTS;
+    if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ) {
+    // Indicate pingability in every UP frame
+    LMIC.frame[end] = MCMD_PING_IND;
+    LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4);
+    end += 2;
+    }
+    if( LMIC.dutyCapAns ) {
+    LMIC.frame[end] = MCMD_DCAP_ANS;
+    end += 1;
+    LMIC.dutyCapAns = 0;
+    }
+    if( LMIC.dn2Ans ) {
+    LMIC.frame[end+0] = MCMD_DN2P_ANS;
+    LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU;
+    end += 2;
+    LMIC.dn2Ans = 0;
+    }
+    if( LMIC.devsAns ) {  // answer to device status
+    LMIC.frame[end+0] = MCMD_DEVS_ANS;
+    LMIC.frame[end+1] = LMIC.margin;
+    LMIC.frame[end+2] = os_getBattLevel();
+    end += 3;
+    LMIC.devsAns = 0;
+    }
+    if( LMIC.ladrAns ) {  // answer to ADR change
+    LMIC.frame[end+0] = MCMD_LADR_ANS;
+    LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU;
+    end += 2;
+    LMIC.ladrAns = 0;
+    }
+    if( LMIC.bcninfoTries > 0 ) {
+    LMIC.frame[end] = MCMD_BCNI_REQ;
+    end += 1;
+    }
+    if( LMIC.adrChanged ) {
+    LMIC.adrAckReq = LMIC.adrAckReq < 0 ? 0 : LMIC.adrAckReq;
+    LMIC.adrChanged = 0;
+    }
+    if( LMIC.pingSetAns != 0 ) {
+    LMIC.frame[end+0] = MCMD_PING_ANS;
+    LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU;
+    end += 2;
+    LMIC.pingSetAns = 0;
+    }
+    if( LMIC.snchAns ) {
+    LMIC.frame[end+0] = MCMD_SNCH_ANS;
+    LMIC.frame[end+1] = LMIC.snchAns;
+    end += 2;
+    LMIC.snchAns = 0;
+    }
+    ASSERT(end <= OFF_DAT_OPTS+16);
+
+    u1_t flen = end + (txdata ? 5+dlen : 4);
+    if( flen > MAX_LEN_FRAME ) {
+    // Options and payload too big - delay payload
+    txdata = 0;
+    flen = end+4;
+    }
+    LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1;
+    LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled
+                  | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0)
+                  | (end-OFF_DAT_OPTS));
+    os_wlsbf4(LMIC.frame+OFF_DAT_ADDR,  LMIC.devaddr);
+
+    if( LMIC.txCnt == 0 ) {
+    LMIC.seqnoUp += 1;
+    DO_DEVDB(updateSeqnoUp, LMIC.seqnoUp);
+    } else {
+    EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = LMIC.seqnoUp-1,
+               e_.info2  = (LMIC.txCnt+1) | (DRADJUST[LMIC.txCnt+1] << 8) | ((LMIC.datarate|DR_PAGE)<<16)));
+    }
+    os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1);
+
+    // Clear pending DN confirmation
+    LMIC.dnConf = 0;
+
+    if( txdata ) {
+    if( LMIC.pendTxConf ) {
+        // Confirmed only makes sense if we have a payload (or at least a port)
+        LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1;
+        LMIC.txCnt += 1;
+    }
+    LMIC.frame[end] = LMIC.pendTxPort;
+    os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen);
+    aes_cipher(LMIC.pendTxPort==0 ? LMIC.nwkKey : LMIC.artKey,
+           LMIC.devaddr, LMIC.seqnoUp-1,
+           /*up*/0, LMIC.frame+end+1, dlen);
+    }
+    aes_appendMic(LMIC.nwkKey, LMIC.devaddr, LMIC.seqnoUp-1, /*up*/0, LMIC.frame, flen-4);
+
+    EV(dfinfo, DEBUG, (e_.deveui  = MAIN::CDEV->getEui(),
+               e_.devaddr = LMIC.devaddr,
+               e_.seqno   = LMIC.seqnoUp-1,
+               e_.flags   = (LMIC.pendTxPort < 0 ? EV::dfinfo_t::NOPORT : EV::dfinfo_t::NOP),
+               e_.mic     = Base::lsbf4(&LMIC.frame[flen-4]),
+               e_.hdr     = LMIC.frame[LORA::OFF_DAT_HDR],
+               e_.fct     = LMIC.frame[LORA::OFF_DAT_FCT],
+               e_.port    = LMIC.pendTxPort,
+               e_.plen    = txdata ? dlen : 0,
+               e_.olen    = end-LORA::OFF_DAT_OPTS,
+               memcpy(e_.opts, LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS)));
+    LMIC.dataLen = flen;
+}
+
+
+// Callback from HAL during scan mode or when job timer expires.
+static void onBcnRx (xref2osjob_t job) {
+    // If we arrive via job timer make sure to put radio to rest.
+    os_radio(RADIO_RST);
+    os_clearCallback(&LMIC.osjob);
+    if( LMIC.dataLen == 0 ) {
+    // Nothing received - timeout
+    LMIC.opmode &= ~(OP_SCAN | OP_TRACK);
+    reportEvent(EV_SCAN_TIMEOUT);
+    return;
+    }
+    if( decodeBeacon() <= 0 ) {
+    // Something is wrong with the beacon - continue scan
+    LMIC.dataLen = 0;
+    os_radio(RADIO_RXON);
+    os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnRx));
+    return;
+    }
+    // Found our 1st beacon
+    // We don't have a previous beacon to calc some drift - assume
+    // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm
+    calcBcnRxWindowFromMillis(13,1);
+    LMIC.opmode &= ~OP_SCAN;          // turn SCAN off
+    LMIC.opmode |=  OP_TRACK;         // auto enable tracking
+    reportEvent(EV_BEACON_FOUND);    // can be disabled in callback
+}
+
+
+// Enable receiver to listen to incoming beacons
+// netid defines when scan stops (any or specific beacon)
+// This mode ends with events: EV_SCAN_TIMEOUT/EV_SCAN_BEACON
+// Implicitely cancels any pending TX/RX transaction.
+// Also cancels an onpoing joining procedure.
+static void startScan (void) {
+    ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0);
+    if( (LMIC.opmode & OP_SHUTDOWN) != 0 )
+    return;
+    // Cancel onging TX/RX transaction
+    LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0;
+    LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND);
+    setBcnRxParams();
+    LMIC.rxtime = LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1);
+    os_setTimedCallback(&LMIC.osjob, LMIC.rxtime, FUNC_ADDR(onBcnRx));
+    os_radio(RADIO_RXON);
+}
+
+
+bit_t LMIC_enableTracking (u1_t tryBcnInfo) {
+    if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 )
+    return 0;  // already in progress or failed to enable
+    // If BCN info requested from NWK then app has to take are
+    // of sending data up so that MCMD_BCNI_REQ can be attached.
+    if( (LMIC.bcninfoTries = tryBcnInfo) == 0 )
+    startScan();
+    return 1;  // enabled
+}
+
+
+void LMIC_disableTracking (void) {
+    LMIC.opmode &= ~(OP_SCAN|OP_TRACK);
+    LMIC.bcninfoTries = 0;
+    engineUpdate();
+}
+
+
+// ================================================================================
+//
+// Join stuff
+//
+// ================================================================================
+
+static void buildJoinRequest (u1_t ftype) {
+    // Do not use pendTxData since we might have a pending
+    // user level frame in there. Use RX holding area instead.
+    xref2u1_t d = LMIC.frame;
+    d[OFF_JR_HDR] = ftype;
+    os_getArtEui(d + OFF_JR_ARTEUI);
+    os_getDevEui(d + OFF_JR_DEVEUI);
+    os_wlsbf2(d + OFF_JR_DEVNONCE, LMIC.devNonce);
+    aes_appendMic0(d, OFF_JR_MIC);
+
+    EV(joininfo,INFO,(e_.deveui  = MAIN::CDEV->getEui(),
+              e_.arteui  = MAIN::CDEV->getArtEui(),
+              e_.nonce   = LMIC.devNonce,
+              e_.oldaddr = LMIC.devaddr,
+              e_.mic     = Base::lsbf4(&d[LORA::OFF_JR_MIC]),
+              e_.flags   = ((LMIC.opmode & OP_REJOIN) != 0
+                    ? EV::joininfo_t::REJOIN_REQUEST
+                    : EV::joininfo_t::REQUEST)));
+    LMIC.dataLen = LEN_JR;
+    LMIC.devNonce++;
+    DO_DEVDB(updateDevNonce, LMIC.devNonce);
+}
+
+static void startJoining (xref2osjob_t osjob) {
+    reportEvent(EV_JOINING);
+}
+
+// Start join procedure if not already joined.
+bit_t LMIC_startJoining (void) {
+    if( LMIC.devaddr == 0 ) {
+    // There should be no TX/RX going on
+    ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0);
+    // Cancel scanning
+    LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL);
+    // Setup state
+    LMIC.rejoinCnt = LMIC.txCnt = 0;
+    initJoinLoop();
+    LMIC.opmode |= OP_OTA|OP_JOINING;
+    // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS
+    os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining));
+    return 1;
+    }
+    return 0;
+}
+
+
+// ================================================================================
+//
+//
+//
+// ================================================================================
+
+static void processPingRx (xref2osjob_t osjob) {
+    if( LMIC.dataLen != 0 ) {
+    LMIC.txrxFlags = TXRX_PING;
+    if( decodeFrame() ) {
+        reportEvent(EV_RXCOMPLETE);
+        return;
+    }
+    }
+    // Pick next ping slot
+    engineUpdate();
+}
+
+
+static bit_t processDnData (void) {
+    ASSERT((LMIC.opmode & OP_TXRXPEND)!=0);
+
+    if( LMIC.dataLen == 0 ) {
+      norx:
+    if( LMIC.txCnt != 0 ) {
+        if( LMIC.txCnt <= TXCONF_ATTEMPTS ) {
+        // Schedule another retransmission
+        txDelay(LMIC.rxtime, RETRY_PERIOD_secs);
+        LMIC.opmode &= ~OP_TXRXPEND;
+        engineUpdate();
+        return 1;
+        }
+        LMIC.txrxFlags = TXRX_NACK | TXRX_NOPORT;
+    } else {
+        // Nothing received - implies no port
+        LMIC.txrxFlags = TXRX_NOPORT;
+    }
+    LMIC.adrAckReq += 1;
+    LMIC.dataBeg = LMIC.dataLen = 0;
+      txcomplete:
+    LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND);
+    if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0  &&  (LMIC.opmode & OP_LINKDEAD) != 0 ) {
+        LMIC.opmode &= ~OP_LINKDEAD;
+        reportEvent(EV_LINK_ALIVE);
+    }
+    reportEvent(EV_TXCOMPLETE);
+    // If we haven't heard from NWK in a while although we asked for a sign
+    // assume link is dead - notify application and keep going
+    if( LMIC.adrAckReq > LINK_CHECK_DEAD ) {
+        // We haven't heard from NWK for some time although
+        // We asked for a response for some time - assume we're disconnected. Lower DR one notch.
+        EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD,
+                  e_.eui    = MAIN::CDEV->getEui(),
+                  e_.info   = LMIC.adrAckReq));
+        setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW);
+        LMIC.adrAckReq = LINK_CHECK_CONT;
+        if( (LMIC.opmode & OP_OTA) != 0 ) {
+            LMIC.opmode |= OP_REJOIN|OP_LINKDEAD;
+        }
+        else {
+            LMIC.opmode |= OP_LINKDEAD;
+        }
+        reportEvent(EV_LINK_DEAD);
+    }
+    // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan
+    if( LMIC.bcninfoTries > 0 ) {
+        if( (LMIC.opmode & OP_TRACK) != 0 ) {
+        reportEvent(EV_BEACON_FOUND);
+        LMIC.bcninfoTries = 0;
+        }
+        else if( --LMIC.bcninfoTries == 0 ) {
+        startScan();   // NWK did not answer - try scan
+        }
+    }
+    return 1;
+    }
+    if( !decodeFrame() ) {
+    if( (LMIC.txrxFlags & TXRX_DNW1) != 0 )
+        return 0;
+    goto norx;
+    }
+    // If we received a frame reset counter
+    LMIC.adrAckReq = LINK_CHECK_INIT;
+    LMIC.rejoinCnt = 0;
+    goto txcomplete;
+}
+
+
+static void processBeacon (xref2osjob_t osjob) {
+    ostime_t lasttx = LMIC.bcninfo.txtime;   // save here - decodeBeacon might overwrite
+    u1_t flags = LMIC.bcninfo.flags;
+    ev_t ev;
+
+    if( LMIC.dataLen != 0 && decodeBeacon() >= 1 ) {
+    ev = EV_BEACON_TRACKED;
+    if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) {
+        // We don't have a previous beacon to calc some drift - assume
+        // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm
+        calcBcnRxWindowFromMillis(13,0);
+        goto rev;
+    }
+    // We have a previous BEACON to calculate some drift
+    s2_t drift = BCN_INTV_osticks - (LMIC.bcninfo.txtime - lasttx);
+    if( LMIC.missedBcns > 0 ) {
+        drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1);
+    }
+    if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) {
+        s2_t diff = LMIC.drift - drift;
+        if( diff < 0 ) diff = -diff;
+        LMIC.lastDriftDiff = diff;
+        if( LMIC.maxDriftDiff < diff )
+        LMIC.maxDriftDiff = diff;
+        LMIC.bcninfo.flags &= ~BCN_NODDIFF;
+    }
+    LMIC.drift = drift;
+    LMIC.missedBcns = LMIC.rejoinCnt = 0;
+    LMIC.bcninfo.flags &= ~BCN_NODRIFT;
+    EV(devCond,INFO,(e_.reason = EV::devCond_t::CLOCK_DRIFT,
+             e_.info   = drift,
+             e_.info2  = /*occasion BEACON*/0));
+    ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0);
+    } else {
+    ev = EV_BEACON_MISSED;
+    LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift;
+    LMIC.bcninfo.time   += BCN_INTV_sec;
+    LMIC.missedBcns++;
+    // Delay any possible TX after surmised beacon - it's there although we missed it
+    txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4);
+    if( (LMIC.opmode & OP_OTA) != 0 ) {
+    if( LMIC.missedBcns > MAX_MISSED_BCNS )
+        LMIC.opmode |= OP_REJOIN;  // try if we can roam to another network
+    }
+    if( LMIC.bcnRxsyms > MAX_RXSYMS ) {
+        LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN);
+        reportEvent(EV_LOST_TSYNC);
+        return;
+    }
+    }
+    LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - calcRxWindow(0,DR_BCN);
+    LMIC.bcnRxsyms = LMIC.rxsyms;    
+  rev:
+    if( (LMIC.opmode & OP_PINGINI) != 0 )
+    rxschedInit(&LMIC.ping);  // note: reuses LMIC.frame buffer!
+    reportEvent(ev);
+}
+
+
+static void startRxBcn (xref2osjob_t osjob) {
+    LMIC.osjob.func = FUNC_ADDR(processBeacon);
+    os_radio(RADIO_RX);
+}
+
+
+static void startRxPing (xref2osjob_t osjob) {
+    LMIC.osjob.func = FUNC_ADDR(processPingRx);
+    os_radio(RADIO_RX);
+}
+
+
+// Decide what to do next for the MAC layer of a device
+static void engineUpdate (void) {
+    // Check for ongoing state: scan or TX/RX transaction
+    if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) 
+    return;
+
+    if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) {
+    LMIC_startJoining();
+    return;
+    }
+
+    ostime_t now    = os_getTime();
+    ostime_t rxtime = 0;
+    ostime_t txbeg  = 0;
+
+    if( (LMIC.opmode & OP_TRACK) != 0 ) {
+    // We are tracking a beacon
+    ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 );
+    rxtime = LMIC.bcnRxtime - RX_RAMPUP;
+    }
+
+    if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) {
+    // Need to TX some data...
+    // Assuming txChnl points to channel which first becomes available again.
+    bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0);
+    // Find next suitable channel and return availability time
+    if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) {
+        txbeg = LMIC.txend = nextTx(now);
+        LMIC.opmode &= ~OP_NEXTCHNL;
+    } else {
+        txbeg = LMIC.txend;
+    }
+    // Delayed TX or waiting for duty cycle?
+    if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0)  &&  (txbeg - LMIC.globalDutyAvail) < 0 )
+        txbeg = LMIC.globalDutyAvail;
+    // If we're tracking a beacon...
+    // then make sure TX-RX transaction is complete before beacon
+    if( (LMIC.opmode & OP_TRACK) != 0 &&
+        txbeg + (jacc ? JOIN_GUARD_osticks : TXRX_GUARD_osticks) - rxtime > 0 ) {
+        // Not enough time to complete TX-RX before beacon - postpone after beacon.
+        // In order to avoid clustering of postponed TX right after beacon randomize start!
+        txDelay(rxtime + BCN_RESERVE_osticks, 16);
+        txbeg = 0;
+        goto checkrx;
+    }
+    // Earliest possible time vs overhead to setup radio
+    if( txbeg - (now + TX_RAMPUP) < 0 ) {
+        // We could send right now!
+        dr_t txdr = (dr_t)LMIC.datarate;
+        if( jacc ) {
+        u1_t ftype;
+        if( (LMIC.opmode & OP_REJOIN) != 0 ) {
+            txdr = lowerDR(txdr, LMIC.rejoinCnt);
+            ftype = HDR_FTYPE_REJOIN;
+        } else {
+            ftype = HDR_FTYPE_JREQ;
+        }
+        buildJoinRequest(ftype);
+        LMIC.osjob.func = FUNC_ADDR(jreqDone);
+        } else {
+        if( LMIC.seqnoUp == 0xFFFFFFFF ) {
+            // Roll over of up seq counter
+            EV(specCond, ERR, (e_.reason = EV::specCond_t::UPSEQNO_ROLL_OVER,
+                       e_.eui    = MAIN::CDEV->getEui()));
+            // Rerun join procedure - start with current datarate
+            LMIC.devaddr = 0;
+            LMIC_startJoining();
+            reportEvent(EV_RESET);
+            return;
+        }
+        buildDataFrame();
+        // NOTE: a channel decision for LMIC.txChnl above will never be rendered invalid
+        // by chosing a slower datarate (it could be invalided ONLY by a faster datarate).
+        txdr = lowerDR(txdr, DRADJUST[LMIC.txCnt]);
+        LMIC.osjob.func = FUNC_ADDR(updataDone);
+        }
+        LMIC.rps    = setCr(updr2rps(txdr), (cr_t)LMIC.errcr);
+        LMIC.rxsyms = txdr;  // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1
+        LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL;
+        updateTx(txbeg);
+        os_radio(RADIO_TX);
+        return;
+    }
+    // Cannot yet TX
+    if( (LMIC.opmode & OP_TRACK) == 0 )
+        goto txdelay; // We don't track the beacon - nothing else to do - so wait for the time to TX
+    // Consider RX tasks
+    if( txbeg == 0 ) // zero indicates no TX pending
+        txbeg += 1;  // TX delayed by one tick (insignificant amount of time)
+    } else {
+    // No TX pending - no scheduled RX
+    if( (LMIC.opmode & OP_TRACK) == 0 )
+        return;
+    }
+
+    // Are we pingable?
+  checkrx:
+    if( (LMIC.opmode & OP_PINGINI) != 0 ) {
+    // One more RX slot in this beacon period?
+    if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) {
+        if( txbeg != 0  &&  (txbeg - LMIC.ping.rxtime) < 0 )
+        goto txdelay;
+        LMIC.rxsyms  = LMIC.ping.rxsyms;
+        LMIC.rxtime  = LMIC.ping.rxtime;
+        LMIC.freq    = LMIC.ping.freq;
+        LMIC.rps     = dndr2rps(LMIC.ping.dr);
+        LMIC.dataLen = 0;
+        ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 );
+        os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing));
+        return;
+    }
+    // no - just wait for the beacon
+    }
+
+    if( txbeg != 0  &&  (txbeg - rxtime) < 0 )
+    goto txdelay;
+
+    setBcnRxParams();
+    LMIC.rxsyms = LMIC.bcnRxsyms;
+    LMIC.rxtime = LMIC.bcnRxtime;
+    if( now - rxtime >= 0 ) {
+    LMIC.osjob.func = FUNC_ADDR(processBeacon);
+    os_radio(RADIO_RX);
+    return;
+    }
+    os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn));
+    return;
+
+  txdelay:
+    EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = osticks2ms(txbeg-now),
+               e_.info2  = LMIC.seqnoUp-1));
+    os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate));
+}
+
+
+void LMIC_setAdrMode (bit_t enabled) {
+    LMIC.adrEnabled = enabled ? FCT_ADREN : 0;
+}
+
+
+//  Should we have/need an ext. API like this?
+void LMIC_setDrTxpow (dr_t dr, s1_t txpow) {
+    setDrTxpow(DRCHG_SET, dr, txpow);
+}
+
+
+void LMIC_shutdown (void) {
+    os_clearCallback(&LMIC.osjob);
+    os_radio(RADIO_RST);
+    LMIC.opmode |= OP_SHUTDOWN;
+}
+
+
+void LMIC_reset (void) {
+    EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV,
+               e_.eui    = MAIN::CDEV->getEui(),
+               e_.info   = EV_RESET));
+    os_radio(RADIO_RST);
+    os_clearCallback(&LMIC.osjob);
+
+    os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC));
+    LMIC.devaddr     = 0;
+    LMIC.devNonce    = os_getRndU2();
+    LMIC.opmode      = OP_NONE;
+    LMIC.errcr       = CR_4_5;
+    LMIC.adrEnabled  = FCT_ADREN;
+    LMIC.dn2Dr       = DR_DNW2;   // we need this for 2ns DN window of join accept
+    LMIC.dn2Freq     = FREQ_DNW2; // ditto
+#if CFG_us915
+    initDefaultChannels();
+#endif
+}
+
+
+void LMIC_init (void) {
+    LMIC.opmode = OP_SHUTDOWN;
+}
+
+
+void LMIC_clrTxData (void) {
+    LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND|OP_POLL);
+    LMIC.pendTxLen = 0;
+    if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING
+    return;
+    os_clearCallback(&LMIC.osjob);
+    os_radio(RADIO_RST);
+    engineUpdate();
+}
+
+
+void LMIC_setTxData (void) {
+    LMIC.opmode |= OP_TXDATA;
+    if( (LMIC.opmode & OP_JOINING) == 0 )
+    LMIC.txCnt = 0;             // cancel any ongoing TX/RX retries
+    engineUpdate();
+}
+
+
+//
+int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) {
+    if( dlen > SIZEOFEXPR(LMIC.pendTxData) )
+    return -2;
+    if( data != (xref2u1_t)0 )
+    os_copyMem(LMIC.pendTxData, data, dlen);
+    LMIC.pendTxConf = confirmed;
+    LMIC.pendTxPort = port;
+    LMIC.pendTxLen  = dlen;
+    LMIC_setTxData();
+    return 0;
+}
+
+
+// Send a payload-less message to signal device is alive
+void LMIC_sendAlive (void) {
+    LMIC.opmode |= OP_POLL;
+    engineUpdate();
+}
+
+
+// Check if other networks are around.
+void LMIC_tryRejoin (void) {
+    LMIC.opmode |= OP_REJOIN;
+    engineUpdate();
+}
+
+void LMIC_startABP(u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey)
+{
+    LMIC.netid = netid;
+    LMIC.devaddr = devaddr;
+    memcpy(LMIC.nwkKey, nwkKey, 16);
+    memcpy(LMIC.artKey, artKey, 16);
+    
+#if CFG_eu868
+    initDefaultChannels(0);
+#endif
+
+    LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
+    stateJustJoined();
+    //engineUpdate();
+    reportEvent(EV_JOINED);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lmic.h	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,244 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#define NEW_CHNL 1
+
+#ifndef _lmic_h_
+#define _lmic_h_
+
+#define CFG_DEBUG    1
+#define CFG_eu868    1
+//#define CFG_us915    0
+
+#define USE_SMTC_RADIO_DRIVER 1
+
+//#define CFG_sx1272_radio    0
+#define CFG_sx1276_radio    1
+
+#include "mbed.h"
+#include "oslmic.h"
+#include "lorabase.h"
+
+enum { MAX_FRAME_LEN      =  64 };   // Library cap on max frame length
+enum { TXCONF_ATTEMPTS    =   8 };   // transmit attempts for confirmed frames
+enum { MAX_MISSED_BCNS    =  20 };   // threshold for triggering rejoin requests
+enum { MAX_RXSYMS         = 100 };   // stop tracking beacon beyond this
+
+enum { LINK_CHECK_CONT    =   6 };   // continue with this after reported dead link
+enum { LINK_CHECK_DEAD    =  12 };   // after this UP frames and no response from NWK assume link is dead
+enum { LINK_CHECK_INIT    = -12 };   // UP frame count until we inc datarate
+
+enum { TIME_RESYNC        = 6*128 }; // secs
+enum { TXRX_GUARD_ms      =  6000 };  // msecs - don't start TX-RX transaction before beacon
+enum { JOIN_GUARD_ms      =  9000 };  // msecs - don't start Join Req/Acc transaction before beacon
+enum { TXRX_BCNEXT_secs   =     2 };  // secs - earliest start after beacon time
+enum { RETRY_PERIOD_secs  =     3 };  // secs - random period for retrying a confirmed send
+
+#if CFG_eu868 // EU868 spectrum ====================================================
+
+enum { MAX_CHANNELS = 8 };      // library may not support all 16 channels
+enum { MAX_BANDS    = 4 };
+
+enum { LIMIT_CHANNELS = (1<<4) };   // EU868 will never have more channels
+struct band_t {
+    u2_t     txcap;  // duty cycle limitation: 1/txcap
+    s1_t     txpow;  // maximum TX power
+    ostime_t avail;  // channel is blocked until this time
+};
+TYPEDEF_xref2band_t;
+
+#elif CFG_us915  // US915 spectrum =================================================
+
+enum { MAX_XCHANNELS = 2 };      // extra channels in RAM, channels 0-71 are immutable 
+enum { MAX_TXPOW_125kHz = 30 };
+
+#endif // ==========================================================================
+
+// Keep in sync with evdefs.hpp::drChange
+enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD };
+enum { KEEP_TXPOW = -128 };
+
+
+struct rxsched_t {
+    u1_t     dr;
+    u1_t     intvExp;   // 0..7
+    u1_t     slot;      // runs from 0 to 128
+    u1_t     rxsyms;
+    ostime_t rxbase;
+    ostime_t rxtime;    // start of next spot
+    u4_t     freq;
+};
+TYPEDEF_xref2rxsched_t;
+
+
+// Information extracted from (last) beacon
+enum { BCN_NONE    = 0x00,
+       BCN_PARTIAL = 0x01,
+       BCN_FULL    = 0x02,
+       BCN_NODRIFT = 0x04,    // no drift value measured
+       BCN_NODDIFF = 0x08 };  // no differential drift measured yet
+struct bcninfo_t {
+    rxqu_t   rxq;
+    ostime_t txtime;  // time when beacon was sent
+    u1_t     flags;
+    u4_t     time;    // GPS time in seconds
+    // This part is valid only if flags==BCN_FULL
+    u1_t     info;    // info field
+    s4_t     lat;
+    s4_t     lon;
+};
+
+// purpose of receive window - lmic_t.rxState
+enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3 };
+// Netid values /  lmic_t.netid
+enum { NETID_NONE=(int)~0U, NETID_MASK=0xFFFFFF };
+// MAC operation modes (lmic_t.opmode).
+enum { OP_NONE     = 0x0000,
+       OP_SCAN     = 0x0001, // radio scan to find a beacon
+       OP_TRACK    = 0x0002, // track my networks beacon (netid)
+       OP_JOINING  = 0x0004, // device joining in progress (blocks other activities)
+       OP_TXDATA   = 0x0008, // TX user data (buffered in pendTxData)
+       OP_POLL     = 0x0010, // send empty UP frame to ACK confirmed DN/fetch more DN data
+       OP_REJOIN   = 0x0020, // occasionally send JOIN REQUEST
+       OP_SHUTDOWN = 0x0040, // prevent MAC from doing anything
+       OP_TXRXPEND = 0x0080, // TX/RX transaction pending
+       OP_RNDTX    = 0x0100, // prevent TX lining up after a beacon
+       OP_PINGINI  = 0x0200, // pingable is initialized and scheduling active
+       OP_PINGABLE = 0x0400, // we're pingable
+       OP_NEXTCHNL = 0x0800, // find a new channel
+       OP_LINKDEAD = 0x1000, // link was reported as dead
+       OP_OTA      = 0x2000, // Over the Air Activation in use
+};
+// TX-RX transaction flags - report back to user
+enum { TXRX_ACK    = 0x80,   // confirmed UP frame was acked
+       TXRX_NACK   = 0x40,   // confirmed UP frame was not acked
+       TXRX_NOPORT = 0x20,   // set if a frame with a port was RXed, clr if no frame/no port
+       TXRX_DNW1   = 0x01,   // received in 1st DN slot
+       TXRX_DNW2   = 0x02,   // received in 2dn DN slot
+       TXRX_PING   = 0x04 }; // received in a scheduled RX slot
+// Event types for event callback
+enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND,
+         EV_BEACON_MISSED, EV_BEACON_TRACKED, EV_JOINING,
+         EV_JOINED, EV_RFU1, EV_JOIN_FAILED, EV_REJOIN_FAILED,
+         EV_TXCOMPLETE, EV_LOST_TSYNC, EV_RESET,
+         EV_RXCOMPLETE, EV_LINK_DEAD, EV_LINK_ALIVE };
+typedef enum _ev_t ev_t;
+
+
+struct lmic_t {
+    // Radio settings TX/RX (also accessed by HAL)
+    ostime_t    txend;
+    ostime_t    rxtime;
+    rxqu_t      rxq;
+    u4_t        freq;
+    rps_t       rps;
+    u1_t        rxsyms;
+    s1_t        txpow;     // dBm
+
+    osjob_t     osjob;
+
+    // Channel scheduling
+#if CFG_eu868
+    band_t      bands[MAX_BANDS];
+    u4_t        channelFreq[MAX_CHANNELS];
+    u1_t        channelDrs[MAX_CHANNELS];
+    u2_t        channelMap;
+#elif CFG_us915
+    u4_t        xchFreq[MAX_XCHANNELS];  // extra channel frequencies (if device is behind a repeater)
+    u1_t        xchDrs[MAX_XCHANNELS];   // extra channel datarate ranges  ---XXX: ditto
+    u2_t        channelMap[(72+MAX_XCHANNELS+15)/16];  // enabled bits
+    u1_t        chRnd;        // channel randomizer
+#endif
+    u1_t        txChnl;          // channel for next TX
+    u1_t        globalDutyRate;  // max rate: 1/2^k
+    ostime_t    globalDutyAvail; // time device can send again
+    
+    u4_t        netid;        // current network id (~0 - none)
+    u2_t        opmode;
+    u1_t        upRepeat;     // configured up repeat
+    s1_t        adrTxPow;     // ADR adjusted TX power
+    u1_t        datarate;     // current data rate
+    u1_t        errcr;        // error coding rate (used for TX only)
+    u1_t        rejoinCnt;    // adjustment for rejoin datarate
+    s2_t        drift;        // last measured drift
+    s2_t        lastDriftDiff;
+    s2_t        maxDriftDiff;
+
+    u1_t        pendTxPort;
+    u1_t        pendTxConf;   // confirmed data
+    u1_t        pendTxLen;    // +0x80 = confirmed
+    u1_t        pendTxData[MAX_LEN_PAYLOAD];
+
+    u2_t        devNonce;     // last generated nonce
+    u1_t        nwkKey[16];   // network session key
+    u1_t        artKey[16];   // application router session key
+    devaddr_t   devaddr;
+    u4_t        seqnoDn;      // device level down stream seqno
+    u4_t        seqnoUp;
+
+    u1_t        dnConf;       // dn frame confirm pending: LORA::FCT_ACK or 0
+    s1_t        adrAckReq;    // counter until we reset data rate (0=off)
+    u1_t        adrChanged;
+
+    u1_t        margin;
+    bit_t       ladrAns;      // link adr adapt answer pending
+    bit_t       devsAns;      // device status answer pending
+    u1_t        adrEnabled;
+    u1_t        moreData;     // NWK has more data pending
+    bit_t       dutyCapAns;   // have to ACK duty cycle settings
+    u1_t        snchAns;      // answer set new channel
+    // 2nd RX window (after up stream)
+    u1_t        dn2Dr;
+    u4_t        dn2Freq;
+    u1_t        dn2Ans;       // 0=no answer pend, 0x80+ACKs
+
+    // Class B state
+    u1_t        missedBcns;   // unable to track last N beacons
+    u1_t        bcninfoTries; // how often to try (scan mode only)
+    u1_t        pingSetAns;   // answer set cmd and ACK bits
+    rxsched_t   ping;         // pingable setup
+
+    // Public part of MAC state
+    u1_t        txCnt;
+    u1_t        txrxFlags;  // transaction flags (TX-RX combo)
+    u1_t        dataBeg;    // 0 or start of data (dataBeg-1 is port)
+    u1_t        dataLen;    // 0 no data or zero length data, >0 byte count of data
+    u1_t        frame[MAX_LEN_FRAME];
+
+    u1_t        bcnChnl;
+    u1_t        bcnRxsyms;    // 
+    ostime_t    bcnRxtime;
+    bcninfo_t   bcninfo;      // Last received beacon info
+};
+DECLARE_LMIC;
+
+
+void  LMIC_setDrTxpow   (dr_t dr, s1_t txpow);  // set default/start DR/txpow
+void  LMIC_setAdrMode   (bit_t enabled);        // set ADR mode (if mobile turn off)
+bit_t LMIC_startJoining (void);
+
+void  LMIC_shutdown     (void);
+void  LMIC_init         (void);
+void  LMIC_reset        (void);
+void  LMIC_clrTxData    (void);
+void  LMIC_setTxData    (void);
+int   LMIC_setTxData2   (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed);
+void  LMIC_sendAlive    (void);
+
+bit_t LMIC_enableTracking  (u1_t tryBcnInfo);
+void  LMIC_disableTracking (void);
+
+void  LMIC_stopPingable  (void);
+void  LMIC_setPingable   (u1_t intvExp);
+void  LMIC_tryRejoin     (void);
+void  LMIC_startABP      (u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey);
+
+#endif // _lmic_h_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lorabase.h	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,372 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#ifndef _lorabase_h_
+#define _lorabase_h_
+
+// ================================================================================
+// BEG: Keep in sync with lorabase.hpp
+//
+
+enum _cr_t { CR_4_5=0, CR_4_6, CR_4_7, CR_4_8 };
+enum _sf_t { FSK=0, SF7, SF8, SF9, SF10, SF11, SF12, SFrfu };
+enum _bw_t { BW125=0, BW250, BW500, BWrfu };
+typedef u1_t cr_t;
+typedef u1_t sf_t;
+typedef u1_t bw_t;
+typedef u1_t dr_t;
+// Radio parameter set (encodes SF/BW/CR/IH/NOCRC)
+typedef u2_t rps_t;
+TYPEDEF_xref2rps_t;
+
+enum { ILLEGAL_RPS = 0xFF };
+enum { DR_PAGE_EU868 = 0x00 };
+enum { DR_PAGE_US915 = 0x10 };
+
+// Global maximum frame length
+enum { STD_PREAMBLE_LEN  =  8 };
+enum { MAX_LEN_FRAME     = 64 };
+enum { LEN_DEVNONCE      =  2 };
+enum { LEN_ARTNONCE      =  3 };
+enum { LEN_NETID         =  3 };
+enum { DELAY_JACC1       =  5 }; // in secs
+enum { DELAY_DNW1        =  1 }; // in secs down window #1
+enum { DELAY_EXTDNW2     =  1 }; // in secs
+enum { DELAY_JACC2       =  DELAY_JACC1+(int)DELAY_EXTDNW2 }; // in secs
+enum { DELAY_DNW2        =  DELAY_DNW1 +(int)DELAY_EXTDNW2 }; // in secs down window #1
+enum { BCN_INTV_exp      = 7 };
+enum { BCN_INTV_sec      = 1<<BCN_INTV_exp };
+enum { BCN_INTV_ms       = BCN_INTV_sec*1000L };
+enum { BCN_INTV_us       = BCN_INTV_ms*1000L };
+enum { BCN_RESERVE_ms    = 2120 };   // space reserved for beacon and NWK management
+enum { BCN_GUARD_ms      = 3000 };   // end of beacon period to prevent interference with beacon
+enum { BCN_SLOT_SPAN_ms  =   30 };   // 2^12 reception slots a this span
+enum { BCN_WINDOW_ms     = BCN_INTV_ms-(int)BCN_GUARD_ms-(int)BCN_RESERVE_ms };
+enum { BCN_RESERVE_us    = 2120000 };
+enum { BCN_GUARD_us      = 3000000 };
+enum { BCN_SLOT_SPAN_us  =   30000 };
+
+#if CFG_eu868 // ==============================================
+
+enum _dr_eu868_t { DR_SF12=0, DR_SF11, DR_SF10, DR_SF9, DR_SF8, DR_SF7, DR_SF7B, DR_FSK, DR_NONE };
+enum { DR_DFLTMIN = DR_SF7 };
+enum { DR_PAGE = DR_PAGE_EU868 };
+
+// Default frequency plan for EU 868MHz ISM band
+// Bands:
+//  g1 :   1%  14dBm  
+//  g2 : 0.1%  14dBm  
+//  g3 :  10%  27dBm  
+//                 freq             band     datarates
+enum { EU868_F1 = 868100000,      // g1   SF7-12 
+       EU868_F2 = 868300000,      // g1   SF7-12 FSK SF7/250         
+       EU868_F3 = 868500000,      // g1   SF7-12         
+       EU868_F4 = 868850000,      // g2   SF7-12         
+       EU868_F5 = 869050000,      // g2   SF7-12         
+       EU868_F6 = 869525000,      // g3   SF7-12         
+       EU868_J4 = 864100000,      // g2   SF7-12  used during join
+       EU868_J5 = 864300000,      // g2   SF7-12   ditto
+       EU868_J6 = 864500000,      // g2   SF7-12   ditto
+};
+enum { EU868_FREQ_MIN = 863000000,
+       EU868_FREQ_MAX = 870000000 };
+
+enum { CHNL_PING         = 5 };
+enum { FREQ_PING         = EU868_F6 };  // default ping freq
+enum { DR_PING           = SF9 };       // default ping DR
+enum { CHNL_DNW2         = 5 };
+enum { FREQ_DNW2         = EU868_F6 };
+enum { DR_DNW2           = DR_SF9 };
+enum { CHNL_BCN          = 5 };
+enum { FREQ_BCN          = EU868_F6 };
+enum { DR_BCN            = DR_SF9 };
+enum { AIRTIME_BCN       = 185344 };  // micros
+
+#elif CFG_us915  // =========================================
+
+enum _dr_us915_t { DR_SF10=0, DR_SF9, DR_SF8, DR_SF7, DR_SF8C, DR_NONE,
+           // Devices behind a router:
+           DR_SF12CR=8, DR_SF11CR, DR_SF10CR, DR_SF9CR, DR_SF8CR, DR_SF7CR };
+enum { DR_DFLTMIN = DR_SF8C };
+enum { DR_PAGE = DR_PAGE_US915 };
+
+// Default frequency plan for US 915MHz
+enum { US915_125kHz_UPFBASE = 902300000,
+       US915_125kHz_UPFSTEP =    200000,
+       US915_500kHz_UPFBASE = 903000000,
+       US915_500kHz_UPFSTEP =   1600000,
+       US915_500kHz_DNFBASE = 923300000,
+       US915_500kHz_DNFSTEP =    600000
+};
+enum { US915_FREQ_MIN = 902000000,
+       US915_FREQ_MAX = 928000000 };
+
+enum { CHNL_PING         = 2 };
+enum { FREQ_PING         = US915_500kHz_DNFBASE + CHNL_PING*US915_500kHz_DNFSTEP };  // default ping freq
+enum { DR_PING           = DR_SF10CR };       // default ping DR
+enum { CHNL_DNW2         = 1 };
+enum { FREQ_DNW2         = US915_500kHz_DNFBASE + CHNL_DNW2*US915_500kHz_DNFSTEP };
+enum { DR_DNW2           = DR_SF10CR };
+enum { CHNL_BCN          = 0 };
+enum { FREQ_BCN          = US915_500kHz_DNFBASE + CHNL_BCN*US915_500kHz_DNFSTEP };
+enum { DR_BCN            = DR_SF10CR };
+enum { AIRTIME_BCN       = 82432 };  // micros
+
+#endif // ===================================================
+
+
+enum {
+    // Beacon frame format
+    OFF_BCN_RFU      = 0,
+    OFF_BCN_NETID    = 4,         
+    OFF_BCN_CMAP     = 7,
+    OFF_BCN_TIME     = 9,
+    OFF_BCN_CRC1     = 13,
+    OFF_BCN_INFO     = 15,
+    OFF_BCN_LAT      = 16,
+    OFF_BCN_LON      = 19,
+    OFF_BCN_CRC2     = 22,
+    LEN_BCN          = 24
+};
+enum {
+    // Join Request frame format
+    OFF_JR_HDR      = 0,
+    OFF_JR_ARTEUI   = 1,
+    OFF_JR_DEVEUI   = 9,
+    OFF_JR_DEVNONCE = 17,
+    OFF_JR_MIC      = 19,
+    LEN_JR          = 23
+};
+enum {
+    // Join Accept frame format
+    OFF_JA_HDR      = 0,
+    OFF_JA_ARTNONCE = 1,
+    OFF_JA_NETID    = 4,
+    OFF_JA_DEVADDR  = 7,
+    OFF_JA_RFU      = 11,
+    OFF_CFLIST      = 14,
+    LEN_JA          = 17,
+    LEN_JAEXT       = 17+16
+};
+enum {
+    // Data frame format
+    OFF_DAT_HDR      = 0,
+    OFF_DAT_ADDR     = 1,
+    OFF_DAT_FCT      = 5,
+    OFF_DAT_SEQNO    = 6,
+    OFF_DAT_OPTS     = 8,
+};
+enum { MAX_LEN_PAYLOAD = MAX_LEN_FRAME-(int)OFF_DAT_OPTS-4 };
+enum {
+    // Bitfields in frame format octet
+    HDR_FTYPE   = 0xE0,
+    HDR_RFU     = 0x1C,
+    HDR_MAJOR   = 0x03
+};
+enum { HDR_FTYPE_DNFLAG = 0x20 };  // flags DN frame except for HDR_FTYPE_PROP
+enum {
+    // Values of frame type bit field
+    HDR_FTYPE_JREQ   = 0x00,
+    HDR_FTYPE_JACC   = 0x20,
+    HDR_FTYPE_DAUP   = 0x40,  // data (unconfirmed) up
+    HDR_FTYPE_DADN   = 0x60,  // data (unconfirmed) dn
+    HDR_FTYPE_DCUP   = 0x80,  // data confirmed up
+    HDR_FTYPE_DCDN   = 0xA0,  // data confirmed dn
+    HDR_FTYPE_REJOIN = 0xC0,  // rejoin for roaming
+    HDR_FTYPE_PROP   = 0xE0
+};
+enum {
+    HDR_MAJOR_V1 = 0x00,
+};
+enum {
+    // Bitfields in frame control octet
+    FCT_ADREN  = 0x80,
+    FCT_ADRARQ = 0x40,
+    FCT_ACK    = 0x20,
+    FCT_MORE   = 0x10,
+    FCT_OPTLEN = 0x0F,
+};
+enum {
+    NWKID_MASK = (int)0xFE000000,
+    NWKID_BITS = 7
+};
+
+// MAC uplink commands   downwlink too
+enum {
+    // Class A
+    MCMD_LCHK_REQ = 0x02, // -  link check request : -
+    MCMD_LADR_ANS = 0x03, // -  link ADR answer    : u1:7-3:RFU, 3/2/1: pow/DR/Ch ACK
+    MCMD_DCAP_ANS = 0x04, // -  duty cycle answer  : -
+    MCMD_DN2P_ANS = 0x05, // -  2nd DN slot status : u1:7-2:RFU  1/0:datarate/channel ack
+    MCMD_DEVS_ANS = 0x06, // -  device status ans  : u1:battery 0,1-254,255=?, u1:7-6:RFU,5-0:margin(-32..31)
+    MCMD_SNCH_ANS = 0x07, // -  set new channel    : u1: 7-2=RFU, 1/0:DR/freq ACK
+    // Class B
+    MCMD_PING_IND = 0x10, // -  pingability indic  : u1: 7=RFU, 6-4:interval, 3-0:datarate
+    MCMD_PING_ANS = 0x11, // -  ack ping freq      : u1: 7-1:RFU, 0:freq ok
+    MCMD_BCNI_REQ = 0x12, // -  next beacon start  : -
+};
+
+// MAC downlink commands
+enum {
+    // Class A
+    MCMD_LCHK_ANS = 0x02, // link check answer  : u1:margin 0-254,255=unknown margin / u1:gwcnt
+    MCMD_LADR_REQ = 0x03, // link ADR request   : u1:DR/TXPow, u2:chmask, u1:chpage/repeat
+    MCMD_DCAP_REQ = 0x04, // duty cycle cap     : u1:255 dead [7-4]:RFU, [3-0]:cap 2^-k
+    MCMD_DN2P_SET = 0x05, // 2nd DN window param: u1:7-4:RFU/3-0:datarate, u3:freq
+    MCMD_DEVS_REQ = 0x06, // device status req  : -
+    MCMD_SNCH_REQ = 0x07, // set new channel    : u1:chidx, u3:freq, u1:DRrange
+    // Class B
+    MCMD_PING_SET = 0x11, // set ping freq      : u3: freq
+    MCMD_BCNI_ANS = 0x12, // next beacon start  : u2: delay(10ms), u1:channel
+};
+
+enum {
+    MCMD_LADR_ANS_RFU    = 0xF8, // RFU bits
+    MCMD_LADR_ANS_POWACK = 0x04, // 0=not supported power level
+    MCMD_LADR_ANS_DRACK  = 0x02, // 0=unknown data rate
+    MCMD_LADR_ANS_CHACK  = 0x01, // 0=unknown channel enabled
+};
+enum {
+    MCMD_DN2P_ANS_RFU    = 0xFC, // RFU bits
+    MCMD_DN2P_ANS_DRACK  = 0x02, // 0=unknown data rate
+    MCMD_DN2P_ANS_CHACK  = 0x01, // 0=unknown channel enabled
+};
+enum {
+    MCMD_SNCH_ANS_RFU    = 0xFC, // RFU bits
+    MCMD_SNCH_ANS_DRACK  = 0x02, // 0=unknown data rate
+    MCMD_SNCH_ANS_FQACK  = 0x01, // 0=rejected channel frequency
+};
+enum {
+    MCMD_PING_ANS_RFU   = 0xFE,
+    MCMD_PING_ANS_FQACK = 0x01
+};
+
+enum {
+    MCMD_DEVS_EXT_POWER   = 0x00, // external power supply
+    MCMD_DEVS_BATT_MIN    = 0x01, // min battery value
+    MCMD_DEVS_BATT_MAX    = 0xFE, // max battery value
+    MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level
+};
+
+// Bit fields byte#3 of MCMD_LADR_REQ payload
+enum {
+    MCMD_LADR_CHP_125ON   = 0x60,  // special channel page enable, bits applied to 64..71
+    MCMD_LADR_CHP_125OFF  = 0x70,  //  ditto
+    MCMD_LADR_N3RFU_MASK  = 0x80,
+    MCMD_LADR_CHPAGE_MASK = 0xF0,
+    MCMD_LADR_REPEAT_MASK = 0x0F,
+    MCMD_LADR_REPEAT_1    = 0x01,
+    MCMD_LADR_CHPAGE_1    = 0x10
+};
+// Bit fields byte#0 of MCMD_LADR_REQ payload
+enum {
+    MCMD_LADR_DR_MASK    = 0xF0,
+    MCMD_LADR_POW_MASK   = 0x0F,
+    MCMD_LADR_DR_SHIFT   = 4,
+    MCMD_LADR_POW_SHIFT  = 0,
+#if CFG_eu868
+    MCMD_LADR_SF12      = DR_SF12<<4,
+    MCMD_LADR_SF11      = DR_SF11<<4,
+    MCMD_LADR_SF10      = DR_SF10<<4,
+    MCMD_LADR_SF9       = DR_SF9 <<4,
+    MCMD_LADR_SF8       = DR_SF8 <<4,
+    MCMD_LADR_SF7       = DR_SF7 <<4,
+    MCMD_LADR_SF7B      = DR_SF7B<<4,
+    MCMD_LADR_FSK       = DR_FSK <<4,
+
+    MCMD_LADR_20dBm     = 0,
+    MCMD_LADR_14dBm     = 1,
+    MCMD_LADR_11dBm     = 2,
+    MCMD_LADR_8dBm      = 3,
+    MCMD_LADR_5dBm      = 4,
+    MCMD_LADR_2dBm      = 5,
+#elif CFG_us915
+    MCMD_LADR_SF10      = DR_SF10<<4,
+    MCMD_LADR_SF9       = DR_SF9 <<4,
+    MCMD_LADR_SF8       = DR_SF8 <<4,
+    MCMD_LADR_SF7       = DR_SF7 <<4,
+    MCMD_LADR_SF8C      = DR_SF8C<<4,
+    MCMD_LADR_SF12CR    = DR_SF12CR<<4,
+    MCMD_LADR_SF11CR    = DR_SF11CR<<4,
+    MCMD_LADR_SF10CR    = DR_SF10CR<<4,
+    MCMD_LADR_SF9CR     = DR_SF9CR<<4,
+    MCMD_LADR_SF8CR     = DR_SF8CR<<4,
+    MCMD_LADR_SF7CR     = DR_SF7CR<<4,
+
+    MCMD_LADR_30dBm     = 0,
+    MCMD_LADR_28dBm     = 1,
+    MCMD_LADR_26dBm     = 2,
+    MCMD_LADR_24dBm     = 3,
+    MCMD_LADR_22dBm     = 4,
+    MCMD_LADR_20dBm     = 5,
+    MCMD_LADR_18dBm     = 6,
+    MCMD_LADR_16dBm     = 7,
+    MCMD_LADR_14dBm     = 8,
+    MCMD_LADR_12dBm     = 9,
+    MCMD_LADR_10dBm     = 10
+#endif
+};
+
+// Device address
+typedef u4_t devaddr_t;
+
+
+// RX quality (device)
+enum { RSSI_OFF=64, SNR_SCALEUP=4 };
+struct rxqu_t {
+    s1_t rssi;    // offset by RSSI_OFF, max physical RSSI range -198..+63
+    s1_t snr;     // scaled by SNR_SCALEUP, max physical SNR range -32..+31.75
+};
+TYPEDEF_xref2rxqu_t;
+
+
+inline sf_t  getSf   (rps_t params)            { return   (sf_t)(params &  0x7); }
+inline rps_t setSf   (rps_t params, sf_t sf)   { return (rps_t)((params & ~0x7) | sf); }
+inline bw_t  getBw   (rps_t params)            { return  (bw_t)((params >> 3) & 0x3); }
+inline rps_t setBw   (rps_t params, bw_t cr)   { return (rps_t)((params & ~0x18) | (cr<<3)); }
+inline cr_t  getCr   (rps_t params)            { return  (cr_t)((params >> 5) & 0x3); }
+inline rps_t setCr   (rps_t params, cr_t cr)   { return (rps_t)((params & ~0x60) | (cr<<5)); }
+inline int   getNocrc(rps_t params)            { return        ((params >> 7) & 0x1); }
+inline rps_t setNocrc(rps_t params, int nocrc) { return (rps_t)((params & ~0x80) | (nocrc<<7)); }
+inline int   getIh   (rps_t params)            { return        ((params >> 8) & 0xFF); }
+inline rps_t setIh   (rps_t params, int ih)    { return (rps_t)((params & ~0xFF00) | (ih<<8)); }
+inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc) {
+    return sf | (bw<<3) | (cr<<5) | (nocrc?(1<<7):0) | ((ih&0xFF)<<8);
+}
+#define MAKERPS(sf,bw,cr,ih,nocrc) ((rps_t)((sf) | ((bw)<<3) | ((cr)<<5) | ((nocrc)?(1<<7):0) | ((ih&0xFF)<<8)))
+// Two frames with params r1/r2 would interfere on air: same SFx + BWx 
+inline int sameSfBw(rps_t r1, rps_t r2) { return ((r1^r2)&0x1F) == 0; }
+
+extern const u1_t _DR2RPS_CRC[];
+inline rps_t updr2rps (dr_t dr) { return (rps_t)_DR2RPS_CRC[dr+1]; }
+inline rps_t dndr2rps (dr_t dr) { return setNocrc(updr2rps(dr),1); }
+inline int isFasterDR (dr_t dr1, dr_t dr2) { return dr1 > dr2; }
+inline int isSlowerDR (dr_t dr1, dr_t dr2) { return dr1 < dr2; }
+inline dr_t  incDR    (dr_t dr) { return _DR2RPS_CRC[dr+2]==ILLEGAL_RPS ? dr : (dr_t)(dr+1); } // increase data rate
+inline dr_t  decDR    (dr_t dr) { return _DR2RPS_CRC[dr  ]==ILLEGAL_RPS ? dr : (dr_t)(dr-1); } // decrease data rate
+inline dr_t  assertDR (dr_t dr) { return _DR2RPS_CRC[dr+1]==ILLEGAL_RPS ? DR_DFLTMIN : dr; }   // force into a valid DR
+inline bit_t validDR  (dr_t dr) { return _DR2RPS_CRC[dr+1]!=ILLEGAL_RPS; } // in range
+inline dr_t  lowerDR  (dr_t dr, u1_t n) { while(n--){dr=decDR(dr);} return dr; } // decrease data rate by n steps
+
+//
+// BEG: Keep in sync with lorabase.hpp
+// ================================================================================
+
+
+// Convert between dBm values and power codes (MCMD_LADR_XdBm)
+s1_t pow2dBm (u1_t mcmd_ladr_p1);
+// Calculate airtime
+ostime_t calcAirTime (rps_t rps, u1_t plen);
+// Sensitivity at given SF/BW
+int getSensitivity (rps_t rps);
+
+
+#endif // _lorabase_h_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/oslmic.cpp	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#include "lmic.h"
+
+// RUNTIME STATE
+static struct {
+    osjob_t* scheduledjobs;
+    osjob_t* runnablejobs;
+} OS;
+
+void os_init (void) {
+    memset(&OS, 0x00, sizeof(OS));
+    hal_init();
+    radio_init();
+    LMIC_init();
+}
+
+ostime_t os_getTime (void) {
+    return hal_ticks();
+}
+
+static u1_t unlinkjob (osjob_t** pnext, osjob_t* job) {
+    for( ; *pnext; pnext = &((*pnext)->next)) {
+        if(*pnext == job) { // unlink
+            *pnext = job->next;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+// clear scheduled job
+void os_clearCallback (osjob_t* job) {
+    hal_disableIRQs();
+    unlinkjob(&OS.scheduledjobs, job) || unlinkjob(&OS.runnablejobs, job);
+    hal_enableIRQs();
+}
+
+// schedule immediately runnable job
+void os_setCallback (osjob_t* job, osjobcb_t cb) {
+    osjob_t** pnext;
+    hal_disableIRQs();
+    // remove if job was already queued
+    os_clearCallback(job);
+    // fill-in job
+    job->func = cb;
+    job->next = NULL;
+    // add to end of run queue
+    for(pnext=&OS.runnablejobs; *pnext; pnext=&((*pnext)->next));
+    *pnext = job;
+    hal_enableIRQs();
+}
+
+// schedule timed job
+void os_setTimedCallback (osjob_t* job, ostime_t time, osjobcb_t cb) {
+    osjob_t** pnext;
+    hal_disableIRQs();
+    // remove if job was already queued
+    os_clearCallback(job);
+    // fill-in job
+    job->deadline = time;
+    job->func = cb;
+    job->next = NULL;
+    // insert into schedule
+    for(pnext=&OS.scheduledjobs; *pnext; pnext=&((*pnext)->next)) {
+        if(time < (*pnext)->deadline) {
+            // enqueue before next element and stop
+            job->next = *pnext;
+            break;
+        }
+    }
+    *pnext = job;
+    hal_enableIRQs();
+}
+
+// execute jobs from timer and from run queue
+void os_runloop (void) {
+    while(1) {
+        osjob_t* j = NULL;
+        hal_disableIRQs();
+	// check for runnable jobs
+	if(OS.runnablejobs) {
+            j = OS.runnablejobs;
+            OS.runnablejobs = j->next;
+	} else if(OS.scheduledjobs && hal_checkTimer(OS.scheduledjobs->deadline)) { // check for expired timed jobs
+            j = OS.scheduledjobs;
+            OS.scheduledjobs = j->next;
+        } else { // nothing pending
+            hal_sleep(); // wake by irq (timer already restarted)
+        }
+        hal_enableIRQs();
+        if(j) { // run job callback
+            if(j->func) {
+                j->func(j);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/oslmic.h	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *******************************************************************************/
+
+#ifndef _oslmic_h_
+#define _oslmic_h_
+
+// Dependencies required for the LoRa MAC in C to run.
+// These settings can be adapted to the underlying system.
+// You should not, however, change the lmic.[hc]
+
+
+
+//================================================================================
+//================================================================================
+// Target platform as C library
+typedef unsigned char      bit_t;
+typedef unsigned char      u1_t;
+typedef   signed char      s1_t;
+typedef unsigned short     u2_t;
+typedef          short     s2_t;
+typedef unsigned int       u4_t;
+typedef          int       s4_t;
+typedef unsigned long long u8_t;
+typedef          long long s8_t;
+typedef unsigned int       uint;
+typedef const char* str_t;
+
+#define CFG_noassert 1
+#include <string.h>
+#include "hal.h"
+#define EV(a,b,c) /**/
+#define DO_DEVDB(meth,...) /**/
+#if !CFG_noassert
+#define ASSERT(cond) if(!(cond)) hal_failed()
+#else
+#define ASSERT(cond) /**/
+#endif
+
+#define os_clearMem(a,b)   memset(a,0,b)
+#define os_copyMem(a,b,c)  memcpy(a,b,c)
+
+typedef     struct osjob_t osjob_t;
+typedef      struct band_t band_t;
+typedef      struct rxqu_t rxqu_t;
+typedef   struct chnldef_t chnldef_t;
+typedef   struct rxsched_t rxsched_t;
+typedef   struct bcninfo_t bcninfo_t;
+typedef        const u1_t* xref2cu1_t;
+typedef              u1_t* xref2u1_t;
+#define TYPEDEF_xref2rps_t     typedef         rps_t* xref2rps_t
+#define TYPEDEF_xref2rxqu_t    typedef        rxqu_t* xref2rxqu_t
+#define TYPEDEF_xref2rxsched_t typedef     rxsched_t* xref2rxsched_t
+#define TYPEDEF_xref2chnldef_t typedef     chnldef_t* xref2chnldef_t
+#define TYPEDEF_xref2band_t    typedef        band_t* xref2band_t
+#define TYPEDEF_xref2osjob_t   typedef       osjob_t* xref2osjob_t
+
+#define SIZEOFEXPR(x) sizeof(x)
+
+#define ON_LMIC_EVENT(ev)  onEvent(ev)
+#define DECL_ON_LMIC_EVENT void onEvent(ev_t e)
+
+extern u4_t AESAUX[];
+extern u4_t AESKEY[];
+#define AESkey ((u1_t*)AESKEY)
+#define AESaux ((u1_t*)AESAUX)
+#define FUNC_ADDR(func) (&(func))
+
+u1_t radio_rand1 (void);
+#define os_getRndU1() radio_rand1()
+
+#define DEFINE_LMIC  struct lmic_t LMIC
+#define DECLARE_LMIC extern struct lmic_t LMIC
+
+void radio_init(void);
+void radio_irq_handler (u1_t dio);
+void os_init (void);
+void os_runloop (void);
+
+//================================================================================
+
+
+#ifndef RX_RAMPUP
+#define RX_RAMPUP  (us2osticks(2000))
+#endif
+#ifndef TX_RAMPUP
+#define TX_RAMPUP  (us2osticks(2000))
+#endif
+
+#ifndef OSTICKS_PER_SEC
+#define OSTICKS_PER_SEC 15625
+#elif OSTICKS_PER_SEC < 10000 || OSTICKS_PER_SEC > 64516
+#error Illegal OSTICKS_PER_SEC - must be in range [10000:64516]. One tick must be 15.5us .. 100us long.
+#endif
+
+typedef s4_t  ostime_t;
+
+#if !HAS_ostick_conv
+#define us2osticks(us)   ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC) / 1000000))
+#define ms2osticks(ms)   ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC)    / 1000))
+#define sec2osticks(sec) ((ostime_t)( (s8_t)(sec) * OSTICKS_PER_SEC))
+#define osticks2ms(os)   ((s4_t)(((os)*(s8_t)1000    ) / OSTICKS_PER_SEC))
+#define osticks2us(os)   ((s4_t)(((os)*(s8_t)1000000 ) / OSTICKS_PER_SEC))
+// Special versions
+#define us2osticksCeil(us)  ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000))
+#define us2osticksRound(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000))
+#define ms2osticksCeil(ms)  ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 999) / 1000))
+#define ms2osticksRound(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 500) / 1000))
+#endif
+
+
+struct osjob_t;  // fwd decl.
+typedef void (*osjobcb_t) (struct osjob_t*);
+struct osjob_t {
+    struct osjob_t* next;
+    ostime_t deadline;
+    osjobcb_t  func;
+};
+TYPEDEF_xref2osjob_t;
+
+
+#if !HAS_os_calls
+
+#ifndef os_getDevKey
+void os_getDevKey (xref2u1_t buf);
+#endif
+#ifndef os_getArtEui
+void os_getArtEui (xref2u1_t buf);
+#endif
+#ifndef os_getDevEui
+void os_getDevEui (xref2u1_t buf);
+#endif
+#ifndef os_setCallback
+void os_setCallback (xref2osjob_t job, osjobcb_t cb);
+#endif
+#ifndef os_setTimedCallback
+void os_setTimedCallback (xref2osjob_t job, ostime_t time, osjobcb_t cb);
+#endif
+#ifndef os_clearCallback
+void os_clearCallback (xref2osjob_t job);
+#endif
+#ifndef os_getTime
+ostime_t os_getTime (void);
+#endif
+#ifndef os_getTimeSecs
+uint os_getTimeSecs (void);
+#endif
+#ifndef os_radio
+void os_radio (u1_t mode);
+#endif
+#ifndef os_getBattLevel
+u1_t os_getBattLevel (void);
+#endif
+
+#ifndef os_rlsbf4
+//! Read 32-bit quantity from given pointer in little endian byte order.
+u4_t os_rlsbf4 (xref2cu1_t buf);
+#endif
+#ifndef os_wlsbf4
+//! Write 32-bit quntity into buffer in little endian byte order.
+void os_wlsbf4 (xref2u1_t buf, u4_t value);
+#endif
+#ifndef os_rmsbf4
+//! Read 32-bit quantity from given pointer in big endian byte order.
+u4_t os_rmsbf4 (xref2cu1_t buf);
+#endif
+#ifndef os_wmsbf4
+//! Write 32-bit quntity into buffer in big endian byte order.
+void os_wmsbf4 (xref2u1_t buf, u4_t value);
+#endif
+#ifndef os_rlsbf2
+//! Read 16-bit quantity from given pointer in little endian byte order.
+u2_t os_rlsbf2 (xref2cu1_t buf);
+#endif
+#ifndef os_wlsbf2
+//! Write 16-bit quntity into buffer in little endian byte order.
+void os_wlsbf2 (xref2u1_t buf, u2_t value);
+#endif
+
+//! Get random number (default impl for u2_t).
+#ifndef os_getRndU2
+#define os_getRndU2() ((u2_t)((os_getRndU1()<<8)|os_getRndU1()))
+#endif
+#ifndef os_crc16
+u2_t os_crc16 (xref2u1_t d, uint len);
+#endif
+
+#endif // !HAS_os_calls
+
+// ======================================================================
+// AES support 
+// !!Keep in sync with lorabase.hpp!!
+
+#ifndef AES_ENC  // if AES_ENC is defined as macro all other values must be too
+#define AES_ENC       0x00 
+#define AES_DEC       0x01
+#define AES_MIC       0x02
+#define AES_CTR       0x04
+#define AES_MICNOAUX  0x08
+#endif
+#ifndef AESkey  // if AESkey is defined as macro all other values must be too
+extern xref2u1_t AESkey;
+extern xref2u1_t AESaux;
+#endif
+#ifndef os_aes
+u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len);
+#endif
+
+
+
+#endif // _oslmic_h_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/radio.cpp	Thu Jan 22 12:50:49 2015 +0000
@@ -0,0 +1,1092 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corporation.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    IBM Zurich Research Lab - initial API, implementation and documentation
+ *    Semtech Apps Team       - Modified to support the MBED sx1276 driver
+ *                              library.
+ *                              Possibility to use original or Semtech's MBED
+ *                              radio driver. The selection is done by setting
+ *                              USE_SMTC_RADIO_DRIVER preprocessing directive
+ *                              in lmic.h
+ *******************************************************************************/
+#include "lmic.h"
+
+#if USE_SMTC_RADIO_DRIVER
+#include "sx1276-hal.h"
+
+/*!
+ * Syncword for lora networks (nibbles swapped)
+ */
+#define LORA_MAC_SYNCWORD           0x34
+
+/*
+ * Callback functions prototypes
+ */
+/*!
+ * @brief Function to be executed on Radio Tx Done event
+ */
+void OnTxDone( void );
+
+/*!
+ * @brief Function to be executed on Radio Rx Done event
+ */
+void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
+
+/*!
+ * @brief Function executed on Radio Tx Timeout event
+ */
+void OnTxTimeout( void );
+
+/*!
+ * @brief Function executed on Radio Rx Timeout event
+ */
+void OnRxTimeout( void );
+
+/*!
+ * @brief Function executed on Radio Rx Error event
+ */
+void OnRxError( void );
+
+/*!
+ * @brief Function executed on Radio Fhss Change Channel event
+ */
+void OnFhssChangeChannel( uint8_t channelIndex );
+
+/*!
+ * @brief Function executed on CAD Done event
+ */
+void OnCadDone( void );
+
+/*
+ * Radio object declraration
+ */
+SX1276MB1xAS Radio( OnTxDone, OnTxTimeout, OnRxDone, OnRxTimeout, OnRxError, NULL, NULL );
+
+static const u2_t LORA_RXDONE_FIXUP[] = {
+    [FSK]  =     us2osticks(0), // (   0 ticks)
+    [SF7]  =     us2osticks(0), // (   0 ticks)
+    [SF8]  =  us2osticks(1648), // (  54 ticks)
+    [SF9]  =  us2osticks(3265), // ( 107 ticks)
+    [SF10] =  us2osticks(7049), // ( 231 ticks)
+    [SF11] = us2osticks(13641), // ( 447 ticks)
+    [SF12] = us2osticks(31189), // (1022 ticks)
+};
+
+void OnTxDone( void )
+{
+    ostime_t now = os_getTime( );
+    // save exact tx time
+    LMIC.txend = now - us2osticks( 43 ); // TXDONE FIXUP
+    
+    // go from stanby to sleep
+    Radio.Sleep( );
+    // run os job (use preset func ptr)
+    os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
+{
+    ostime_t now = os_getTime( );
+    // save exact rx time
+    LMIC.rxtime = now - LORA_RXDONE_FIXUP[getSf( LMIC.rps )];
+    // read the PDU and inform the MAC that we received something
+    LMIC.dataLen = size;
+    // now read the FIFO
+    memcpy( LMIC.frame, payload, size );
+    // read rx quality parameters
+    LMIC.rxq.snr  = snr; // SNR [dB] * 4
+    LMIC.rxq.rssi = rssi; // RSSI [dBm] (-196...+63)
+
+    // go from stanby to sleep
+    Radio.Sleep( );
+    // run os job (use preset func ptr)
+    os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnTxTimeout( void )
+{
+    ostime_t now = os_getTime( );
+
+    // indicate error
+
+    // go from stanby to sleep
+    Radio.Sleep( );
+    // run os job (use preset func ptr)
+    os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnRxTimeout( void )
+{
+    ostime_t now = os_getTime( );
+    // indicate timeout
+    LMIC.dataLen = 0;
+
+    // go from stanby to sleep
+    Radio.Sleep( );
+    // run os job (use preset func ptr)
+    os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+void OnRxError( void )
+{
+    ostime_t now = os_getTime( );
+
+    // indicate error
+    LMIC.dataLen = 0;
+
+    // go from stanby to sleep
+    Radio.Sleep( );
+    // run os job (use preset func ptr)
+    os_setCallback( &LMIC.osjob, LMIC.osjob.func );
+}
+
+/*!
+ * LMIC API implementation
+ */
+// RADIO STATE
+// (initialized by radio_init( ), used by radio_rand1( ))
+static u1_t randbuf[16];
+
+// get random seed from wideband noise rssi
+void radio_init( void ) 
+{
+    hal_disableIRQs( );
+    
+    // seed 15-byte randomness via noise rssi
+    // Set LoRa modem ON
+    Radio.SetModem( MODEM_LORA );
+    // Disable LoRa modem interrupts
+    Radio.Write( REG_LR_IRQFLAGSMASK, RFLR_IRQFLAGS_RXTIMEOUT |
+                  RFLR_IRQFLAGS_RXDONE |
+                  RFLR_IRQFLAGS_PAYLOADCRCERROR |
+                  RFLR_IRQFLAGS_VALIDHEADER |
+                  RFLR_IRQFLAGS_TXDONE |
+                  RFLR_IRQFLAGS_CADDONE |
+                  RFLR_IRQFLAGS_FHSSCHANGEDCHANNEL |
+                  RFLR_IRQFLAGS_CADDETECTED );
+
+    // Set radio in continuous reception
+    Radio.Rx( 0 );
+
+    for( int i = 1; i < 16; i++ )
+    {
+        for( int j = 0; j < 8; j++ )
+        {
+            u1_t b; // wait for two non-identical subsequent least-significant bits
+            while( ( b = Radio.Read( REG_LR_RSSIWIDEBAND ) & 0x01 ) == ( Radio.Read( REG_LR_RSSIWIDEBAND ) & 0x01 ) );
+            randbuf[i] = ( randbuf[i] << 1 ) | b;
+        }
+    }
+    randbuf[0] = 16; // set initial index
+
+    // Change LoRa modem SyncWord
+    Radio.Write( REG_LR_SYNCWORD, LORA_MAC_SYNCWORD );
+    
+    Radio.Sleep( );
+    
+    hal_enableIRQs( );
+}
+
+// return next random byte derived from seed buffer
+// (buf[0] holds index of next byte to be returned)
+u1_t radio_rand1( void )
+{
+    u1_t i = randbuf[0];
+    ASSERT( i != 0 );
+    if( i == 16 )
+    {
+        os_aes( AES_ENC, randbuf, 16 ); // encrypt seed with any key
+        i = 0;
+    }
+    u1_t v = randbuf[i++];
+    randbuf[0] = i;
+    return v;
+}
+
+void os_radio( u1_t mode )
+{
+    hal_disableIRQs( );
+    switch( mode ) 
+    {
+    case RADIO_RST:
+        // put radio to sleep
+        Radio.Sleep( );
+        break;
+
+    case RADIO_TX:
+        // transmit frame now
+        //ASSERT( Radio.GetState( ) == IDLE );
+
+        Radio.SetChannel( LMIC.freq );
+        if( getSf( LMIC.rps ) == FSK )
+        { // FSK modem
+            Radio.SetTxConfig( MODEM_FSK, LMIC.txpow, 25e3, 0, 50e3, 0, 5, false, true, 0, 0, false, 3e6 );
+        }
+        else
+        { // LoRa modem
+            
+            Radio.SetTxConfig( MODEM_LORA, LMIC.txpow, 0, getBw( LMIC.rps ), getSf( LMIC.rps ) + 6, getCr( LMIC.rps ) + 1, 8, getIh( LMIC.rps ) ? true : false, ( getNocrc(LMIC.rps) == 0 ) ? true : false, 0, 0, false, 3e6 );
+        }
+
+        //starttx( ); // buf=LMIC.frame, len=LMIC.dataLen
+        Radio.Send( LMIC.frame, LMIC.dataLen );
+        break;
+
+    case RADIO_RX:
+        // receive frame now (exactly at rxtime)
+        //ASSERT( Radio.GetState( ) == IDLE );
+
+        Radio.SetChannel( LMIC.freq );
+        if( getSf( LMIC.rps ) == FSK )
+        { // FSK modem
+            //Radio.SetRxConfig( MODEM_FSK, 50e3, 50e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, false );
+            Radio.SetRxConfig( MODEM_FSK, 50e3, 50e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, true );
+        }
+        else
+        { // LoRa modem
+            
+            Radio.SetRxConfig( MODEM_LORA, getBw( LMIC.rps ), getSf( LMIC.rps ) + 6, getCr( LMIC.rps ) + 1, 0, 8, LMIC.rxsyms, getIh( LMIC.rps ) ? true : false, getIh(LMIC.rps), ( getNocrc(LMIC.rps) == 0 ) ? true : false, 0, 0, true, false );
+        }
+
+        // now instruct the radio to receive
+        hal_waitUntil( LMIC.rxtime ); // busy wait until exact rx time
+
+        //startrx( RXMODE_SINGLE ); // buf = LMIC.frame, time = LMIC.rxtime, timeout=LMIC.rxsyms
+        if( getSf( LMIC.rps ) == FSK )
+        { // FSK modem
+            Radio.Rx( 50e3 ); // Max Rx window 50 ms
+        }
+        else
+        { // LoRa modem
+            Radio.Rx( 3e6 ); // Max Rx window 3 seconds
+        }
+        break;
+
+    case RADIO_RXON:
+        // start scanning for beacon now
+
+        //ASSERT( Radio.GetState( ) == IDLE );
+
+        Radio.SetChannel( LMIC.freq );
+        if( getSf( LMIC.rps ) == FSK )
+        { // FSK modem
+            Radio.SetRxConfig( MODEM_FSK, 50e3, 50e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, true );
+        }
+        else
+        { // LoRa modem
+            Radio.SetRxConfig( MODEM_LORA, getBw( LMIC.rps ), getSf( LMIC.rps ) + 6, getCr( LMIC.rps ) + 1, 0, 8, LMIC.rxsyms, getIh( LMIC.rps ) ? true : false, getIh(LMIC.rps), ( getNocrc(LMIC.rps) == 0 ) ? true : false, 0, 0, true, true );
+        }
+
+        //startrx( RXMODE_SCAN) ; // buf = LMIC.frame
+        Radio.Rx( 0 );
+        break;
+    }
+    hal_enableIRQs( );
+}
+
+#else
+
+// ---------------------------------------- 
+// Registers Mapping
+#define RegFifo                                    0x00 // common
+#define RegOpMode                                  0x01 // common
+#define FSKRegBitrateMsb                           0x02
+#define FSKRegBitrateLsb                           0x03
+#define FSKRegFdevMsb                              0x04
+#define FSKRegFdevLsb                              0x05
+#define RegFrfMsb                                  0x06 // common
+#define RegFrfMid                                  0x07 // common
+#define RegFrfLsb                                  0x08 // common
+#define RegPaConfig                                0x09 // common
+#define RegPaRamp                                  0x0A // common
+#define RegOcp                                     0x0B // common
+#define RegLna                                     0x0C // common
+#define FSKRegRxConfig                             0x0D
+#define LORARegFifoAddrPtr                         0x0D
+#define FSKRegRssiConfig                           0x0E
+#define LORARegFifoTxBaseAddr                      0x0E
+#define FSKRegRssiCollision                        0x0F
+#define LORARegFifoRxBaseAddr                      0x0F 
+#define FSKRegRssiThresh                           0x10
+#define LORARegFifoRxCurrentAddr                   0x10
+#define FSKRegRssiValue                            0x11
+#define LORARegIrqFlagsMask                        0x11 
+#define FSKRegRxBw                                 0x12
+#define LORARegIrqFlags                            0x12 
+#define FSKRegAfcBw                                0x13
+#define LORARegRxNbBytes                           0x13 
+#define FSKRegOokPeak                              0x14
+#define LORARegRxHeaderCntValueMsb                 0x14 
+#define FSKRegOokFix                               0x15
+#define LORARegRxHeaderCntValueLsb                 0x15 
+#define FSKRegOokAvg                               0x16
+#define LORARegRxPacketCntValueMsb                 0x16 
+#define LORARegRxpacketCntValueLsb                 0x17 
+#define LORARegModemStat                           0x18 
+#define LORARegPktSnrValue                         0x19 
+#define FSKRegAfcFei                               0x1A
+#define LORARegPktRssiValue                        0x1A 
+#define FSKRegAfcMsb                               0x1B
+#define LORARegRssiValue                           0x1B 
+#define FSKRegAfcLsb                               0x1C
+#define LORARegHopChannel                          0x1C 
+#define FSKRegFeiMsb                               0x1D
+#define LORARegModemConfig1                        0x1D 
+#define FSKRegFeiLsb                               0x1E
+#define LORARegModemConfig2                        0x1E 
+#define FSKRegPreambleDetect                       0x1F
+#define LORARegSymbTimeoutLsb                      0x1F 
+#define FSKRegRxTimeout1                           0x20
+#define LORARegPreambleMsb                         0x20 
+#define FSKRegRxTimeout2                           0x21
+#define LORARegPreambleLsb                         0x21 
+#define FSKRegRxTimeout3                           0x22
+#define LORARegPayloadLength                       0x22 
+#define FSKRegRxDelay                              0x23
+#define LORARegPayloadMaxLength                    0x23 
+#define FSKRegOsc                                  0x24
+#define LORARegHopPeriod                           0x24 
+#define FSKRegPreambleMsb                          0x25
+#define LORARegFifoRxByteAddr                      0x25
+#define LORARegModemConfig3                        0x26
+#define FSKRegPreambleLsb                          0x26
+#define FSKRegSyncConfig                           0x27
+#define LORARegFeiMsb                              0x28
+#define FSKRegSyncValue1                           0x28
+#define LORAFeiMib                                 0x29
+#define FSKRegSyncValue2                           0x29
+#define LORARegFeiLsb                              0x2A
+#define FSKRegSyncValue3                           0x2A
+#define FSKRegSyncValue4                           0x2B
+#define LORARegRssiWideband                        0x2C
+#define FSKRegSyncValue5                           0x2C
+#define FSKRegSyncValue6                           0x2D
+#define FSKRegSyncValue7                           0x2E
+#define FSKRegSyncValue8                           0x2F
+#define FSKRegPacketConfig1                        0x30
+#define FSKRegPacketConfig2                        0x31
+#define LORARegDetectOptimize                      0x31
+#define FSKRegPayloadLength                        0x32
+#define FSKRegNodeAdrs                             0x33
+#define LORARegInvertIQ                            0x33
+#define FSKRegBroadcastAdrs                        0x34
+#define FSKRegFifoThresh                           0x35
+#define FSKRegSeqConfig1                           0x36
+#define FSKRegSeqConfig2                           0x37
+#define LORARegDetectionThreshold                  0x37
+#define FSKRegTimerResol                           0x38
+#define FSKRegTimer1Coef                           0x39
+#define LORARegSyncWord                            0x39
+#define FSKRegTimer2Coef                           0x3A
+#define FSKRegImageCal                             0x3B
+#define FSKRegTemp                                 0x3C
+#define FSKRegLowBat                               0x3D
+#define FSKRegIrqFlags1                            0x3E
+#define FSKRegIrqFlags2                            0x3F
+#define RegDioMapping1                             0x40 // common
+#define RegDioMapping2                             0x41 // common
+#define RegVersion                                 0x42 // common
+// #define RegAgcRef                                  0x43 // common
+// #define RegAgcThresh1                              0x44 // common
+// #define RegAgcThresh2                              0x45 // common
+// #define RegAgcThresh3                              0x46 // common
+// #define RegPllHop                                  0x4B // common
+// #define RegTcxo                                    0x58 // common
+#define RegPaDac                                   0x5A // common
+// #define RegPll                                     0x5C // common
+// #define RegPllLowPn                                0x5E // common
+// #define RegFormerTemp                              0x6C // common
+// #define RegBitRateFrac                             0x70 // common
+
+// ----------------------------------------
+// spread factors and mode for RegModemConfig2
+#define SX1272_MC2_FSK  0x00
+#define SX1272_MC2_SF7  0x70
+#define SX1272_MC2_SF8  0x80
+#define SX1272_MC2_SF9  0x90
+#define SX1272_MC2_SF10 0xA0
+#define SX1272_MC2_SF11 0xB0
+#define SX1272_MC2_SF12 0xC0
+// bandwidth for RegModemConfig1
+#define SX1272_MC1_BW_125  0x00
+#define SX1272_MC1_BW_250  0x40
+#define SX1272_MC1_BW_500  0x80
+// coding rate for RegModemConfig1
+#define SX1272_MC1_CR_4_5 0x08
+#define SX1272_MC1_CR_4_6 0x10
+#define SX1272_MC1_CR_4_7 0x18
+#define SX1272_MC1_CR_4_8 0x20
+#define SX1272_MC1_IMPLICIT_HEADER_MODE_ON 0x04 // required for receive
+#define SX1272_MC1_RX_PAYLOAD_CRCON        0x02
+#define SX1272_MC1_LOW_DATA_RATE_OPTIMIZE  0x01 // mandated for SF11 and SF12
+// transmit power configuration for RegPaConfig
+#define SX1272_PAC_PA_SELECT_PA_BOOST 0x80
+#define SX1272_PAC_PA_SELECT_RFIO_PIN 0x00
+
+
+// sx1276 RegModemConfig1
+#define SX1276_MC1_BW_125                0x70
+#define SX1276_MC1_BW_250                0x80
+#define SX1276_MC1_BW_500                0x90
+#define SX1276_MC1_CR_4_5            0x02
+#define SX1276_MC1_CR_4_6            0x04
+#define SX1276_MC1_CR_4_7            0x06
+#define SX1276_MC1_CR_4_8            0x08
+
+#define SX1276_MC1_IMPLICIT_HEADER_MODE_ON    0x01 
+                                                    
+// sx1276 RegModemConfig2          
+#define SX1276_MC2_RX_PAYLOAD_CRCON        0x04
+
+// sx1276 RegModemConfig3          
+#define SX1276_MC3_LOW_DATA_RATE_OPTIMIZE  0x08
+#define SX1276_MC3_AGCAUTO                 0x04
+
+// preamble for lora networks (nibbles swapped)
+#define LORA_MAC_PREAMBLE           0x34
+
+#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG1 0x0A
+#ifdef CFG_sx1276_radio
+#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2 0x70
+#elif CFG_sx1272_radio
+#define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2 0x74
+#endif
+
+
+
+// ---------------------------------------- 
+// Constants for radio registers
+#define OPMODE_LORA      0x80
+#define OPMODE_MASK      0x07
+#define OPMODE_SLEEP     0x00
+#define OPMODE_STANDBY   0x01
+#define OPMODE_FSTX      0x02
+#define OPMODE_TX        0x03
+#define OPMODE_FSRX      0x04
+#define OPMODE_RX        0x05
+#define OPMODE_RX_SINGLE 0x06 
+#define OPMODE_CAD       0x07 
+
+// ----------------------------------------
+// Bits masking the corresponding IRQs from the radio
+#define IRQ_LORA_RXTOUT_MASK 0x80
+#define IRQ_LORA_RXDONE_MASK 0x40
+#define IRQ_LORA_CRCERR_MASK 0x20
+#define IRQ_LORA_HEADER_MASK 0x10
+#define IRQ_LORA_TXDONE_MASK 0x08
+#define IRQ_LORA_CDDONE_MASK 0x04
+#define IRQ_LORA_FHSSCH_MASK 0x02
+#define IRQ_LORA_CDDETD_MASK 0x01
+
+#define IRQ_FSK1_MODEREADY_MASK         0x80
+#define IRQ_FSK1_RXREADY_MASK           0x40
+#define IRQ_FSK1_TXREADY_MASK           0x20
+#define IRQ_FSK1_PLLLOCK_MASK           0x10
+#define IRQ_FSK1_RSSI_MASK              0x08
+#define IRQ_FSK1_TIMEOUT_MASK           0x04
+#define IRQ_FSK1_PREAMBLEDETECT_MASK    0x02
+#define IRQ_FSK1_SYNCADDRESSMATCH_MASK  0x01
+#define IRQ_FSK2_FIFOFULL_MASK          0x80
+#define IRQ_FSK2_FIFOEMPTY_MASK         0x40
+#define IRQ_FSK2_FIFOLEVEL_MASK         0x20
+#define IRQ_FSK2_FIFOOVERRUN_MASK       0x10
+#define IRQ_FSK2_PACKETSENT_MASK        0x08
+#define IRQ_FSK2_PAYLOADREADY_MASK      0x04
+#define IRQ_FSK2_CRCOK_MASK             0x02
+#define IRQ_FSK2_LOWBAT_MASK            0x01
+
+// ----------------------------------------
+// DIO function mappings                D0D1D2D3
+#define MAP_DIO0_LORA_RXDONE   0x00  // 00------
+#define MAP_DIO0_LORA_TXDONE   0x40  // 01------
+#define MAP_DIO1_LORA_RXTOUT   0x00  // --00----
+#define MAP_DIO1_LORA_NOP      0x30  // --11----
+#define MAP_DIO2_LORA_NOP      0xC0  // ----11--
+
+#define MAP_DIO0_FSK_READY     0x00  // 00------ (packet sent / payload ready)
+#define MAP_DIO1_FSK_NOP       0x30  // --11----
+#define MAP_DIO2_FSK_TXNOP     0x04  // ----01--
+#define MAP_DIO2_FSK_TIMEOUT   0x08  // ----10--
+
+
+// FSK IMAGECAL defines
+#define RF_IMAGECAL_AUTOIMAGECAL_MASK               0x7F
+#define RF_IMAGECAL_AUTOIMAGECAL_ON                 0x80
+#define RF_IMAGECAL_AUTOIMAGECAL_OFF                0x00  // Default
+
+#define RF_IMAGECAL_IMAGECAL_MASK                   0xBF
+#define RF_IMAGECAL_IMAGECAL_START                  0x40
+
+#define RF_IMAGECAL_IMAGECAL_RUNNING                0x20
+#define RF_IMAGECAL_IMAGECAL_DONE                   0x00  // Default
+
+
+// RADIO STATE
+// (initialized by radio_init(), used by radio_rand1())
+static u1_t randbuf[16];
+
+
+#ifdef CFG_sx1276_radio
+#define LNA_RX_GAIN (0x20|0x1)
+#elif CFG_sx1272_radio
+#define LNA_RX_GAIN (0x20|0x03)
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif
+
+static void writeReg (u1_t addr, u1_t data ) {
+    hal_pin_nss(0);
+    hal_spi(addr | 0x80);
+    hal_spi(data);
+    hal_pin_nss(1);
+}
+
+static u1_t readReg (u1_t addr) {
+    hal_pin_nss(0);
+    hal_spi(addr & 0x7F);
+    u1_t val = hal_spi(0x00);
+    hal_pin_nss(1);
+    return val;
+}
+
+static void writeBuf (u1_t addr, xref2u1_t buf, u1_t len) {
+    hal_pin_nss(0);
+    hal_spi(addr | 0x80);
+    for (u1_t i=0; i<len; i++) {
+        hal_spi(buf[i]);
+    }
+    hal_pin_nss(1);
+}
+
+static void readBuf (u1_t addr, xref2u1_t buf, u1_t len) {
+    hal_pin_nss(0);
+    hal_spi(addr & 0x7F);
+    for (u1_t i=0; i<len; i++) {
+        buf[i] = hal_spi(0x00);
+    }
+    hal_pin_nss(1);
+}
+
+static void opmode (u1_t mode) {
+    writeReg(RegOpMode, (readReg(RegOpMode) & ~OPMODE_MASK) | mode);
+}
+
+static void opmodeLora(void) {
+    u1_t u = OPMODE_LORA;
+#ifdef CFG_sx1276_radio
+    u |= 0x8;   // TBD: sx1276 high freq
+#endif
+    writeReg(RegOpMode, u);
+}
+
+static void opmodeFSK(void) {
+    u1_t u = 0;
+#ifdef CFG_sx1276_radio
+    u |= 0x8;   // TBD: sx1276 high freq
+#endif
+    writeReg(RegOpMode, u);
+}
+
+// configure LoRa modem (cfg1, cfg2)
+static void configLoraModem (void) {
+    sf_t sf = getSf(LMIC.rps);
+
+#ifdef CFG_sx1276_radio
+    u1_t mc1 = 0, mc2 = 0, mc3 = 0;
+
+    switch (getBw(LMIC.rps)) {
+    case BW125: mc1 |= SX1276_MC1_BW_125; break;
+    case BW250: mc1 |= SX1276_MC1_BW_250; break;
+    case BW500: mc1 |= SX1276_MC1_BW_500; break;
+    default:
+        ASSERT(0);
+    }
+    switch( getCr(LMIC.rps) ) {
+    case CR_4_5: mc1 |= SX1276_MC1_CR_4_5; break;
+    case CR_4_6: mc1 |= SX1276_MC1_CR_4_6; break;
+    case CR_4_7: mc1 |= SX1276_MC1_CR_4_7; break;
+    case CR_4_8: mc1 |= SX1276_MC1_CR_4_8; break;
+    default:
+        ASSERT(0);
+    }
+
+    if (getIh(LMIC.rps)) {
+        mc1 |= SX1276_MC1_IMPLICIT_HEADER_MODE_ON;
+        writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length
+    }
+    // set ModemConfig1
+    writeReg(LORARegModemConfig1, mc1);
+
+    mc2 = (SX1272_MC2_SF7 + ((sf-1)<<4));
+    if (getNocrc(LMIC.rps) == 0) {
+        mc2 |= SX1276_MC2_RX_PAYLOAD_CRCON;
+    }
+    writeReg(LORARegModemConfig2, mc2);
+    
+    mc3 = SX1276_MC3_AGCAUTO;
+    if (sf == SF11 || sf == SF12) {
+        mc3 |= SX1276_MC3_LOW_DATA_RATE_OPTIMIZE;
+    }
+    writeReg(LORARegModemConfig3, mc3);
+#elif CFG_sx1272_radio
+    u1_t mc1 = (getBw(LMIC.rps)<<6);
+
+    switch( getCr(LMIC.rps) ) {
+    case CR_4_5: mc1 |= SX1272_MC1_CR_4_5; break;
+    case CR_4_6: mc1 |= SX1272_MC1_CR_4_6; break;
+    case CR_4_7: mc1 |= SX1272_MC1_CR_4_7; break;
+    case CR_4_8: mc1 |= SX1272_MC1_CR_4_8; break;
+    }
+    
+    if (sf == SF11 || sf == SF12) {
+        mc1 |= SX1272_MC1_LOW_DATA_RATE_OPTIMIZE;
+    }
+    
+    if (getNocrc(LMIC.rps) == 0) {
+        mc1 |= SX1272_MC1_RX_PAYLOAD_CRCON;
+    }
+    
+    if (getIh(LMIC.rps)) {
+        mc1 |= SX1272_MC1_IMPLICIT_HEADER_MODE_ON;
+        writeReg(LORARegPayloadLength, getIh(LMIC.rps)); // required length
+    }
+    // set ModemConfig1
+    writeReg(LORARegModemConfig1, mc1);
+    
+    // set ModemConfig2 (sf, AgcAutoOn=1 SymbTimeoutHi=00)
+    writeReg(LORARegModemConfig2, (SX1272_MC2_SF7 + ((sf-1)<<4)) | 0x04);
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif /* CFG_sx1272_radio */
+}
+
+static void configChannel (void) {
+    // set frequency: FQ = (FRF * 32 Mhz) / (2 ^ 19)
+    u8_t frf = ((u8_t)LMIC.freq << 19) / 32000000;
+    writeReg(RegFrfMsb, (u1_t)(frf>>16));
+    writeReg(RegFrfMid, (u1_t)(frf>> 8));
+    writeReg(RegFrfLsb, (u1_t)(frf>> 0));
+}
+
+
+
+static void configPower (void) {
+#ifdef CFG_sx1276_radio
+    // no boost used for now
+    s1_t pw = (s1_t)LMIC.txpow;
+    if(pw > 14) {
+    pw = 14;
+    } else if(pw < -1) {
+    pw = -1;
+    }
+    // check board type for BOOST pin
+    writeReg(RegPaConfig, (u1_t)(0x00|((pw+1)&0xf)));
+    writeReg(RegPaDac, readReg(RegPaDac)|0x4);
+
+#elif CFG_sx1272_radio
+    // set PA config (2-17 dBm using PA_BOOST)
+    s1_t pw = (s1_t)LMIC.txpow;
+    if(pw > 17) {
+    pw = 17;
+    } else if(pw < 2) {
+    pw = 2;
+    }
+    writeReg(RegPaConfig, (u1_t)(0x80|(pw-2)));
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif /* CFG_sx1272_radio */
+}
+
+static void txfsk (void) {
+    // select FSK modem (from sleep mode)
+    writeReg(RegOpMode, 0x10); // FSK, BT=0.5
+    ASSERT(readReg(RegOpMode) == 0x10);
+    // enter standby mode (required for FIFO loading))
+    opmode(OPMODE_STANDBY);
+    // set bitrate
+    writeReg(FSKRegBitrateMsb, 0x02); // 50kbps
+    writeReg(FSKRegBitrateLsb, 0x80);
+    // set frequency deviation
+    writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz
+    writeReg(FSKRegFdevLsb, 0x99);
+    // frame and packet handler settings
+    writeReg(FSKRegPreambleMsb, 0x00);
+    writeReg(FSKRegPreambleLsb, 0x05);
+    writeReg(FSKRegSyncConfig, 0x12);
+    writeReg(FSKRegPacketConfig1, 0xD0);
+    writeReg(FSKRegPacketConfig2, 0x40);
+    writeReg(FSKRegSyncValue1, 0xC1);
+    writeReg(FSKRegSyncValue2, 0x94);
+    writeReg(FSKRegSyncValue3, 0xC1);
+    // configure frequency
+    configChannel();
+    // configure output power
+    configPower();
+
+    // set the IRQ mapping DIO0=PacketSent DIO1=NOP DIO2=NOP
+    writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TXNOP);
+
+    // initialize the payload size and address pointers    
+    writeReg(FSKRegPayloadLength, LMIC.dataLen+1); // (insert length byte into payload))
+
+    // download length byte and buffer to the radio FIFO
+    writeReg(RegFifo, LMIC.dataLen);
+    writeBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+
+    // enable antenna switch for TX
+    hal_pin_rxtx(1);
+    
+    // now we actually start the transmission
+    opmode(OPMODE_TX);
+}
+
+static void txlora (void) {
+    // select LoRa modem (from sleep mode)
+    //writeReg(RegOpMode, OPMODE_LORA);
+    opmodeLora();
+    ASSERT((readReg(RegOpMode) & OPMODE_LORA) != 0);
+
+    // enter standby mode (required for FIFO loading))
+    opmode(OPMODE_STANDBY);
+    // configure LoRa modem (cfg1, cfg2)
+    configLoraModem();
+    // configure frequency
+    configChannel();
+    // configure output power
+    writeReg(RegPaRamp, (readReg(RegPaRamp) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec
+    configPower();
+    // set sync word
+    writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE);
+    
+    // set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP
+    writeReg(RegDioMapping1, MAP_DIO0_LORA_TXDONE|MAP_DIO1_LORA_NOP|MAP_DIO2_LORA_NOP);
+    // clear all radio IRQ flags
+    writeReg(LORARegIrqFlags, 0xFF);
+    // mask all IRQs but TxDone
+    writeReg(LORARegIrqFlagsMask, ~IRQ_LORA_TXDONE_MASK);
+
+    // initialize the payload size and address pointers    
+    writeReg(LORARegFifoTxBaseAddr, 0x00);
+    writeReg(LORARegFifoAddrPtr, 0x00);
+    writeReg(LORARegPayloadLength, LMIC.dataLen);
+       
+    // download buffer to the radio FIFO
+    writeBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+
+    // enable antenna switch for TX
+    hal_pin_rxtx(1);
+    
+    // now we actually start the transmission
+    opmode(OPMODE_TX);
+}
+
+// start transmitter (buf=LMIC.frame, len=LMIC.dataLen)
+static void starttx (void) {
+    ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP );
+    if(getSf(LMIC.rps) == FSK) { // FSK modem
+        txfsk();
+    } else { // LoRa modem
+        txlora();
+    }
+    // the radio will go back to STANDBY mode as soon as the TX is finished
+    // the corresponding IRQ will inform us about completion.
+}
+
+enum { RXMODE_SINGLE, RXMODE_SCAN, RXMODE_RSSI };
+
+static const u1_t rxlorairqmask[] = {
+    [RXMODE_SINGLE] = IRQ_LORA_RXDONE_MASK|IRQ_LORA_RXTOUT_MASK,
+    [RXMODE_SCAN]   = IRQ_LORA_RXDONE_MASK,
+    [RXMODE_RSSI]   = 0x00,
+};
+
+// start LoRa receiver (time=LMIC.rxtime, timeout=LMIC.rxsyms, result=LMIC.frame[LMIC.dataLen])
+static void rxlora (u1_t rxmode) {
+    // select LoRa modem (from sleep mode)
+    opmodeLora();
+    ASSERT((readReg(RegOpMode) & OPMODE_LORA) != 0);
+    // enter standby mode (warm up))
+    opmode(OPMODE_STANDBY);
+    // don't use MAC settings at startup
+    if(rxmode == RXMODE_RSSI) { // use fixed settings for rssi scan
+        writeReg(LORARegModemConfig1, RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG1);
+        writeReg(LORARegModemConfig2, RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2);
+    } else { // single or continuous rx mode
+        // configure LoRa modem (cfg1, cfg2)
+        configLoraModem();
+        // configure frequency
+        configChannel();
+    }
+    // set LNA gain
+    writeReg(RegLna, LNA_RX_GAIN); 
+    // set max payload size
+    writeReg(LORARegPayloadMaxLength, 64);
+    // use inverted I/Q signal (prevent mote-to-mote communication)
+    writeReg(LORARegInvertIQ, readReg(LORARegInvertIQ)|(1<<6));
+    // set symbol timeout (for single rx)
+    writeReg(LORARegSymbTimeoutLsb, LMIC.rxsyms);
+    // set sync word
+    writeReg(LORARegSyncWord, LORA_MAC_PREAMBLE);
+    
+    // configure DIO mapping DIO0=RxDone DIO1=RxTout DIO2=NOP
+    writeReg(RegDioMapping1, MAP_DIO0_LORA_RXDONE|MAP_DIO1_LORA_RXTOUT|MAP_DIO2_LORA_NOP);
+    // clear all radio IRQ flags
+    writeReg(LORARegIrqFlags, 0xFF);
+    // enable required radio IRQs
+    writeReg(LORARegIrqFlagsMask, ~rxlorairqmask[rxmode]);
+
+    // enable antenna switch for RX
+    hal_pin_rxtx(0);
+
+    // now instruct the radio to receive
+    if (rxmode == RXMODE_SINGLE) { // single rx
+        hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time
+    opmode(OPMODE_RX_SINGLE);
+    } else { // continous rx (scan or rssi)
+    opmode(OPMODE_RX); 
+    }
+}
+
+static void rxfsk (u1_t rxmode) {
+    // only single rx (no continuous scanning, no noise sampling)
+    ASSERT( rxmode == RXMODE_SINGLE );
+    // select FSK modem (from sleep mode)
+    //writeReg(RegOpMode, 0x00); // (not LoRa)
+    opmodeFSK();
+    ASSERT((readReg(RegOpMode) & OPMODE_LORA) == 0);
+    // enter standby mode (warm up))
+    opmode(OPMODE_STANDBY);
+    // configure frequency
+    configChannel();
+    // set LNA gain
+    //writeReg(RegLna, 0x20|0x03); // max gain, boost enable
+    writeReg(RegLna, LNA_RX_GAIN);
+    // configure receiver
+    writeReg(FSKRegRxConfig, 0x1E); // AFC auto, AGC, trigger on preamble?!?
+    // set receiver bandwidth
+    writeReg(FSKRegRxBw, 0x0B); // 50kHz SSb
+    // set AFC bandwidth
+    writeReg(FSKRegAfcBw, 0x12); // 83.3kHz SSB
+    // set preamble detection
+    writeReg(FSKRegPreambleDetect, 0xAA); // enable, 2 bytes, 10 chip errors
+    // set sync config
+    writeReg(FSKRegSyncConfig, 0x12); // no auto restart, preamble 0xAA, enable, fill FIFO, 3 bytes sync
+    // set packet config
+    writeReg(FSKRegPacketConfig1, 0xD8); // var-length, whitening, crc, no auto-clear, no adr filter
+    writeReg(FSKRegPacketConfig2, 0x40); // packet mode
+    // set sync value
+    writeReg(FSKRegSyncValue1, 0xC1);
+    writeReg(FSKRegSyncValue2, 0x94);
+    writeReg(FSKRegSyncValue3, 0xC1);
+    // set preamble timeout
+    writeReg(FSKRegRxTimeout2, 0xFF);//(LMIC.rxsyms+1)/2);
+    // set bitrate
+    writeReg(FSKRegBitrateMsb, 0x02); // 50kbps
+    writeReg(FSKRegBitrateLsb, 0x80);
+    // set frequency deviation
+    writeReg(FSKRegFdevMsb, 0x01); // +/- 25kHz
+    writeReg(FSKRegFdevLsb, 0x99);
+    
+    // configure DIO mapping DIO0=PayloadReady DIO1=NOP DIO2=TimeOut
+    writeReg(RegDioMapping1, MAP_DIO0_FSK_READY|MAP_DIO1_FSK_NOP|MAP_DIO2_FSK_TIMEOUT);
+
+    // enable antenna switch for RX
+    hal_pin_rxtx(0);
+    
+    // now instruct the radio to receive
+    hal_waitUntil(LMIC.rxtime); // busy wait until exact rx time
+    opmode(OPMODE_RX); // no single rx mode available in FSK
+}
+
+static void startrx (u1_t rxmode) {
+    ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP );
+    if(getSf(LMIC.rps) == FSK) { // FSK modem
+        rxfsk(rxmode);
+    } else { // LoRa modem
+        rxlora(rxmode);
+    }
+    // the radio will go back to STANDBY mode as soon as the RX is finished
+    // or timed out, and the corresponding IRQ will inform us about completion.
+}
+
+// get random seed from wideband noise rssi
+void radio_init (void) {
+    hal_disableIRQs();
+
+    // manually reset radio
+#ifdef CFG_sx1276_radio
+    hal_pin_rst(0); // drive RST pin low
+#else
+    hal_pin_rst(1); // drive RST pin high
+#endif
+    hal_waitUntil(os_getTime()+ms2osticks(1)); // wait >100us
+    hal_pin_rst(2); // configure RST pin floating!
+    hal_waitUntil(os_getTime()+ms2osticks(5)); // wait 5ms
+
+    opmode(OPMODE_SLEEP);
+    // some sanity checks, e.g., read version number
+    u1_t v = readReg(RegVersion);
+#ifdef CFG_sx1276_radio
+    ASSERT(v == 0x12 ); 
+#elif CFG_sx1272_radio
+    ASSERT(v == 0x22);
+#else
+#error Missing CFG_sx1272_radio/CFG_sx1276_radio
+#endif
+    // seed 15-byte randomness via noise rssi
+    rxlora(RXMODE_RSSI);
+    while( (readReg(RegOpMode) & OPMODE_MASK) != OPMODE_RX ); // continuous rx
+    for(int i=1; i<16; i++) {
+        for(int j=0; j<8; j++) {
+            u1_t b; // wait for two non-identical subsequent least-significant bits
+            while( (b = readReg(LORARegRssiWideband) & 0x01) == (readReg(LORARegRssiWideband) & 0x01) );
+            randbuf[i] = (randbuf[i] << 1) | b;
+        }
+    }
+    randbuf[0] = 16; // set initial index
+  
+#ifdef CFG_sx1276_radio
+    // chain calibration
+    writeReg(RegPaConfig, 0);
+    
+    // Launch Rx chain calibration for LF band
+    writeReg(FSKRegImageCal, (readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_MASK)|RF_IMAGECAL_IMAGECAL_START);
+    while((readReg(FSKRegImageCal)&RF_IMAGECAL_IMAGECAL_RUNNING) == RF_IMAGECAL_IMAGECAL_RUNNING){ ; }
+
+    // Sets a Frequency in HF band
+    u4_t frf = 868000000;
+    writeReg(RegFrfMsb, (u1_t)(frf>>16));
+    writeReg(RegFrfMid, (u1_t)(frf>> 8));
+    writeReg(RegFrfLsb, (u1_t)(frf>> 0));
+
+    // Launch Rx chain calibration for HF band 
+    writeReg(FSKRegImageCal, (readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_MASK)|RF_IMAGECAL_IMAGECAL_START);
+    while((readReg(FSKRegImageCal) & RF_IMAGECAL_IMAGECAL_RUNNING) == RF_IMAGECAL_IMAGECAL_RUNNING) { ; }
+#endif /* CFG_sx1276_radio */
+
+    opmode(OPMODE_SLEEP);
+    hal_enableIRQs();
+}
+
+// return next random byte derived from seed buffer
+// (buf[0] holds index of next byte to be returned)
+u1_t radio_rand1 (void) {
+    u1_t i = randbuf[0];
+    ASSERT( i != 0 );
+    if( i==16 ) {
+        os_aes(AES_ENC, randbuf, 16); // encrypt seed with any key
+        i = 0;
+    }
+    u1_t v = randbuf[i++];
+    randbuf[0] = i;
+    return v;
+}
+
+u1_t radio_rssi (void) {
+    hal_disableIRQs();
+    u1_t r = readReg(LORARegRssiValue);
+    hal_enableIRQs();
+    return r;
+}
+
+static const u2_t LORA_RXDONE_FIXUP[] = {
+    [FSK]  =     us2osticks(0), // (   0 ticks)
+    [SF7]  =     us2osticks(0), // (   0 ticks)
+    [SF8]  =  us2osticks(1648), // (  54 ticks)
+    [SF9]  =  us2osticks(3265), // ( 107 ticks)
+    [SF10] =  us2osticks(7049), // ( 231 ticks)
+    [SF11] = us2osticks(13641), // ( 447 ticks)
+    [SF12] = us2osticks(31189), // (1022 ticks)
+};
+
+// called by hal ext IRQ handler
+// (radio goes to stanby mode after tx/rx operations)
+void radio_irq_handler (u1_t dio) {
+    ostime_t now = os_getTime();
+    if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem
+    u1_t flags = readReg(LORARegIrqFlags);
+    if( flags & IRQ_LORA_TXDONE_MASK ) {
+        // save exact tx time
+        LMIC.txend = now - us2osticks(43); // TXDONE FIXUP
+    } else if( flags & IRQ_LORA_RXDONE_MASK ) {
+        // save exact rx time
+        LMIC.rxtime = now - LORA_RXDONE_FIXUP[getSf(LMIC.rps)];
+        // read the PDU and inform the MAC that we received something
+        LMIC.dataLen = (readReg(LORARegModemConfig1) & SX1272_MC1_IMPLICIT_HEADER_MODE_ON) ?
+        readReg(LORARegPayloadLength) : readReg(LORARegRxNbBytes);
+        // set FIFO read address pointer
+        writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr)); 
+        // now read the FIFO
+        readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+        // read rx quality parameters
+        LMIC.rxq.snr  = readReg(LORARegPktSnrValue); // SNR [dB] * 4
+        LMIC.rxq.rssi = readReg(LORARegPktRssiValue) - 125 + 64; // RSSI [dBm] (-196...+63)
+    } else if( flags & IRQ_LORA_RXTOUT_MASK ) {
+        // indicate timeout
+        LMIC.dataLen = 0;
+    }
+        // mask all radio IRQs
+        writeReg(LORARegIrqFlagsMask, 0xFF);
+        // clear radio IRQ flags
+        writeReg(LORARegIrqFlags, 0xFF);
+    } else { // FSK modem
+    u1_t flags1 = readReg(FSKRegIrqFlags1);
+    u1_t flags2 = readReg(FSKRegIrqFlags2);
+    if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) {
+        // save exact tx time
+        LMIC.txend = now;
+        } else if( flags2 & IRQ_FSK2_PAYLOADREADY_MASK ) {
+        // save exact rx time
+        LMIC.rxtime = now;
+        // read the PDU and inform the MAC that we received something
+        LMIC.dataLen = readReg(FSKRegPayloadLength);
+        // now read the FIFO
+        readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
+        // read rx quality parameters
+        LMIC.rxq.snr  = 0; // determine snr
+        LMIC.rxq.rssi = 0; // determine rssi
+    } else if( flags1 & IRQ_FSK1_TIMEOUT_MASK ) {
+        // indicate timeout
+        LMIC.dataLen = 0;
+    } else {
+            while(1);
+        }
+    }
+    // go from stanby to sleep
+    opmode(OPMODE_SLEEP);
+    // run os job (use preset func ptr)
+    os_setCallback(&LMIC.osjob, LMIC.osjob.func);
+}
+
+void os_radio (u1_t mode) {
+    hal_disableIRQs();
+    switch (mode) {
+      case RADIO_RST:
+        // put radio to sleep
+        opmode(OPMODE_SLEEP);
+        break;
+
+      case RADIO_TX:
+    // transmit frame now
+        starttx(); // buf=LMIC.frame, len=LMIC.dataLen
+        break;
+      
+      case RADIO_RX:
+    // receive frame now (exactly at rxtime)
+        startrx(RXMODE_SINGLE); // buf=LMIC.frame, time=LMIC.rxtime, timeout=LMIC.rxsyms
+        break;
+
+      case RADIO_RXON:
+        // start scanning for beacon now
+        startrx(RXMODE_SCAN); // buf=LMIC.frame
+        break;
+    }
+    hal_enableIRQs();
+}
+
+#endif // USE_SMTC_RADIO_DRIVER