Merge remote-tracking branch 'refs/remotes/mysensors/development'

This commit is contained in:
petewill
2016-10-08 07:03:23 -06:00
982 changed files with 60584 additions and 135687 deletions
+5 -1
View File
@@ -1,2 +1,6 @@
stino.settings
*~
*bak
*bak
MyPrivateConfig.h
Documentation/html
doxygen_sqlite3.db
-2
View File
@@ -1,2 +0,0 @@
*.o
*.elf
-66
View File
@@ -1,66 +0,0 @@
PROJECT = MyOtaBootloader
INCLUDES = ../libraries/MySensors
MCU = atmega328p
CLK = 16000000L
ISP_PORT = com5
ISP_SPEED = 115200
ISP_PROTOCOL = stk500v2
ISP_MCU = m328p
# HFUSE = D8 - 4096 byte boot
ISP_HFUSE = D8
# LFUSE = 4E - Low power resonator (16MHz), fast start, clkdiv/8
ISP_LFUSE = FF
# EFUSE = 06 - 1.8V brownout
ISP_EFUSE = 06
ISP_ARGS = -c$(ISP_PROTOCOL) -P$(ISP_PORT) -b$(ISP_SPEED) -p$(ISP_MCU)
ifeq ($(OS),Windows_NT)
BINPATH = C:/Program Files (x86)/Arduino/hardware/tools/avr/bin/
JAVAPATH = C:/Program Files/Java/jdk1.7.0_40/bin/
JAVAC = $(JAVAPATH)javac.exe
JAVA = $(JAVAPATH)java.exe
else
UNAME_S := $(shell uname -s)
UNAME_P := $(shell uname -p)
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_S),Linux)
ifeq ($(UNAME_P),x86_64)
BINPATH = ../arduino/bin-linux64/bin/
else ifneq ($(filter %86,$(UNAME_P)),)
BINPATH = ../arduino/bin-linux32/bin/
else ifneq ($(filter arm%,$(UNAME_M)),)
BINPATH = /usr/bin/
endif
endif
endif
CFLAGS = -c -g -Os -fno-inline-small-functions -fno-split-wide-types -mshort-calls -Wall -mmcu=$(MCU) -DF_CPU=$(CLK) -std=gnu99
LDFLAGS = -Os -Wl,--section-start=.text=0x7000 -nostartfiles -mmcu=$(MCU)
all: clean out
clean:
- rm *.o
- rm *.elf
- rm *.hex
$(PROJECT).o: $(PROJECT).c
"$(BINPATH)avr-gcc" $(CFLAGS) -I$(INCLUDES) $< -o $@
$(PROJECT).elf: $(PROJECT).o
"$(BINPATH)avr-gcc" $(LDFLAGS) -o $@ $< -lm
$(PROJECT).hex: $(PROJECT).elf
"$(BINPATH)avr-objcopy" -O ihex -R .eeprom $< $@
out: $(PROJECT).hex
"$(BINPATH)avr-size" $(PROJECT).elf
load: clean out isp
isp: $(PROJECT).hex
"$(BINPATH)avrdude" $(ISP_ARGS) -e -u -Ulock:w:0x3f:m -qq -Uefuse:w:0x$(ISP_EFUSE):m -Uhfuse:w:0x$(ISP_HFUSE):m -Ulfuse:w:0x$(ISP_LFUSE):m -Ulock:w:0x0f:m
"$(BINPATH)avrdude" $(ISP_ARGS) -V -q -s -Uflash:w:$(PROJECT).hex
"$(BINPATH)avrdude" $(ISP_ARGS)
-207
View File
@@ -1,207 +0,0 @@
#include "MyOtaBootloader.h"
int main(void) __attribute__ ((naked)) __attribute__ ((section (".init9")));
volatile char dummy;
EMPTY_INTERRUPT(WDT_vect);
static uint16_t calcCRCrom (const void* ptr, uint16_t len) {
uint16_t crc = ~0;
for (uint16_t i = 0; i < len; ++i)
crc = _crc16_update(crc, pgm_read_byte((uint16_t) ptr + i));
return crc;
}
static uint8_t validFirmware () {
return calcCRCrom(0, fc.blocks * FIRMWARE_BLOCK_SIZE) == fc.crc;
}
static void reboot() {
wdt_enable(WDTO_15MS);
for (;;);
}
static void startup() {
if (validFirmware()) {
wdt_disable();
clock_prescale_set(orgClockDiv);
((void(*)()) 0)();
} else
reboot();
}
static void boot_program_page(uint32_t page, uint8_t *buf) {
uint8_t sreg = SREG;
cli();
eeprom_busy_wait();
boot_page_erase(page);
boot_spm_busy_wait();
for (uint16_t i = 0; i < SPM_PAGESIZE; i += 2) {
uint16_t w = *buf++;
w += (*buf++) << 8;
boot_page_fill(page + i, w);
}
boot_page_write(page);
boot_spm_busy_wait();
boot_rww_enable();
SREG = sreg;
}
static boolean sendWrite(MyMessage message) {
return write(nc.parentNodeId, message.array, HEADER_SIZE + mGetLength(message), (message.destination == BROADCAST_ADDRESS));
}
static uint8_t sendAndWait(uint8_t reqType, uint8_t resType) {
msg.type = reqType;
for (uint8_t i = 0; i < 10; i++) {
sendWrite(msg);
for (uint16_t j = 0; j < 20; j++) {
wdt_reset();
for (uint16_t j = 0; j < 100; j++) {
wdt_reset();
uint8_t pipe;
boolean avail = available(&pipe);
if (avail && pipe<=6) {
read(rmsg.array,pipe);
if(!(mGetVersion(rmsg) == PROTOCOL_VERSION))
continue;
if (rmsg.destination == nc.nodeId) {
if (mGetCommand(rmsg) == C_INTERNAL) {
if (rmsg.type == I_FIND_PARENT_RESPONSE) {
if (rmsg.data[0] < nc.distance - 1) {
nc.distance = rmsg.data[0] + 1;
nc.parentNodeId = rmsg.sender;
eeprom_write_byte((uint8_t*)EEPROM_PARENT_NODE_ID_ADDRESS, nc.parentNodeId);
eeprom_write_byte((uint8_t*)EEPROM_DISTANCE_ADDRESS, nc.distance);
}
}
}
if ((mGetCommand(rmsg) == mGetCommand(msg)) && (rmsg.type == resType))
return 1;
}
}
delaym(1);
}
}
}
return 0;
}
int main () {
asm volatile ("clr __zero_reg__");
// switch to 4 MHz
orgClockDiv = clock_prescale_get();
clock_prescale_set(F_CPU_DIV);
MCUSR = 0;
// enable watchdog to avoid deadlock
wdt_enable(WDTO_8S);
#ifdef LED_DEBUG
// enable LEDs for debugging and blink once as visual "bootloader started" sign
led_init();
blink(LED_MBOTH, 250);
#endif
#ifdef UART_DEBUG
// enable UART for debugging and send start message
uart_init();
uart_puts("Go\n");
#endif
wdt_reset();
// initialize RF module
begin();
wdt_reset();
// Read settings from EEPROM
eeprom_read_block((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(struct NodeConfig));
wdt_reset();
msg.sender = nc.nodeId;
msg.last = nc.nodeId;
address(nc.nodeId);
msg.sensor = NODE_SENSOR_ID;
mSetLength(msg, 0);
mSetPayloadType(msg, P_STRING);
mSetAck(msg,false);
mSetVersion(msg, PROTOCOL_VERSION);
nc.distance = 255;
mSetCommand(msg, C_INTERNAL);
msg.destination = BROADCAST_ADDRESS;
if (!sendAndWait(I_FIND_PARENT, I_FIND_PARENT_RESPONSE))
// starting existing firmware if no parent responds (if firmware is valid)
// reboot();
startup();
msg.destination = GATEWAY_ADDRESS;
if (nc.nodeId == AUTO) {
openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(nc.nodeId));
if (!sendAndWait(I_ID_REQUEST, I_ID_RESPONSE))
reboot();
if (mGetPayloadType(rmsg) == P_BYTE)
nc.nodeId = rmsg.data[0];
else if (mGetPayloadType(rmsg) == P_STRING)
nc.nodeId = atoi(rmsg.data);
if (nc.nodeId == AUTO)
reboot();
eeprom_write_byte((uint8_t*)EEPROM_NODE_ID_ADDRESS, nc.nodeId);
msg.sender = nc.nodeId;
msg.last = nc.nodeId;
address(nc.nodeId);
}
// Read settings from EEPROM
eeprom_read_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(struct FirmwareConfig));
mSetPayloadType(msg,P_CUSTOM);
FirmwareConfigRequest *firmwareConfigRequest = (FirmwareConfigRequest *)msg.data;
mSetLength(msg, sizeof(FirmwareConfigRequest));
mSetCommand(msg, C_STREAM);
firmwareConfigRequest->type = fc.type;
firmwareConfigRequest->version = fc.version;
if (!sendAndWait(ST_FIRMWARE_CONFIG_REQUEST, ST_FIRMWARE_CONFIG_RESPONSE))
// starting existing firmware if no firmware config response (if firmware is valid)
// reboot();
startup();
FirmwareConfigResponse *firmwareConfigResponse = (FirmwareConfigResponse *)rmsg.data;
if (firmwareConfigResponse->version == fc.version)
if (firmwareConfigResponse->blocks == fc.blocks)
if (firmwareConfigResponse->crc == fc.crc)
if (validFirmware())
startup();
fc.type = firmwareConfigResponse->type;
fc.version = firmwareConfigResponse->version;
fc.blocks = firmwareConfigResponse->blocks;
fc.crc = firmwareConfigResponse->crc;
FirmwareRequest *firmwareRequest = (FirmwareRequest *)msg.data;
mSetLength(msg, sizeof(FirmwareRequest));
firmwareRequest->type = fc.type;
firmwareRequest->version = fc.version;
for (uint16_t block = 0; block < fc.blocks; block++) {
firmwareRequest->block = block;
if (!sendAndWait(ST_FIRMWARE_REQUEST, ST_FIRMWARE_RESPONSE))
reboot();
FirmwareResponse *firmwareResponse = (FirmwareResponse *)rmsg.data;
uint8_t offset = (block * FIRMWARE_BLOCK_SIZE) % SPM_PAGESIZE;
memcpy(progBuf + offset, firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
if (offset == SPM_PAGESIZE - FIRMWARE_BLOCK_SIZE)
boot_program_page((block * FIRMWARE_BLOCK_SIZE) - offset, progBuf);
}
wdt_reset();
if (validFirmware()) {
eeprom_write_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(struct FirmwareConfig));
#ifdef UART_DEBUG
uart_puts("fw ok\n");
} else {
uart_puts("fw nok\n");
#endif
}
reboot();
}
-67
View File
@@ -1,67 +0,0 @@
#ifndef MyOtaBootloader_H
#define MyOtaBootloader_H
#include <avr/boot.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <stdint.h>
#include <string.h>
#include <util/crc16.h>
#include "MyMessage.h"
#include "MySensor.h"
#include "MyOtaBootloaderRF24.h"
#define FIRMWARE_BLOCK_SIZE 16
struct FirmwareConfig
{
uint16_t type;
uint16_t version;
uint16_t blocks;
uint16_t crc;
};
typedef struct
{
uint16_t type;
uint16_t version;
} FirmwareConfigRequest;
typedef struct
{
uint16_t type;
uint16_t version;
uint16_t blocks;
uint16_t crc;
} FirmwareConfigResponse;
typedef struct
{
uint16_t type;
uint16_t version;
uint16_t block;
} FirmwareRequest;
typedef struct
{
uint16_t type;
uint16_t version;
uint16_t block;
uint8_t data[FIRMWARE_BLOCK_SIZE];
} FirmwareResponse;
static struct NodeConfig nc;
static struct FirmwareConfig fc;
static MyMessage msg;
static MyMessage rmsg;
static clock_div_t orgClockDiv = 0;
static uint8_t progBuf[SPM_PAGESIZE];
#endif // MyOtaBootloader_H
-214
View File
@@ -1,214 +0,0 @@
:1070000011E0A0E0B1E0E0E4FDE702C005900D92E0
:10701000A030B107D9F711E0A0E0B1E001C01D92A6
:10702000A83DB107E1F711248091610090E08F70D5
:107030009070909301018093000182E090E020E83D
:107040000FB6F89420936100809361000FBE14BEC8
:1070500029E288E190E00FB6F894A895809360004B
:107060000FBE20936000A8951092CF011092D0011E
:107070001092D1011092D2011092D3011092D4013A
:107080001092D5011092D60184B18E6284B92498F1
:1070900085B1857D85B92A9A8AE090E0B3D184E0F4
:1070A0006FE520D281E06BE31DD285E06CE41AD25B
:1070B00086E0E5D1682F607D676286E013D280E0CC
:1070C000DED1682F6C6080E00DD287E060E70AD2E5
:1070D0002A9881E090E096D180E5A6D183E7A4D1FB
:1070E0002A9A81E090E08ED18DE1C9D1682F666047
:1070F0008DE1F8D18CE1C3D1682F6F638CE1F2D1BF
:1071000082EEADD181EEABD104D280E0B8D1682F50
:107110006E7F80E0E7D182E00FEF1CEF21EE38EACE
:1071200048EA50E060E070E002D2A89582E091E089
:1071300060E070E043E050E02DEE39E38AD5A89599
:107140008091020180930E0180930D014FD22FEFA9
:10715000209313018091110192E0909310012093EC
:107160000401887083608093110120930F0187E0F0
:1071700068E04AD3882309F42AD310920F01809142
:1071800002018F3F09F038C081E00FEF1CEF21EEC4
:1071900038EA48EA50E060E070E0C9D183E064E09A
:1071A00033D3882309F407D38091320182958695E1
:1071B0008770813019F48091350105C0882329F446
:1071C00085E391E028D580930201809102018F3FF1
:1071D00009F4F1D280910201F999FECF1FBA12BCD5
:1071E00011BC80BD0FB6F894FA9AF99A0FBE80933D
:1071F0000E0180930D01FAD185E091E06BE171E021
:1072000048E050E02DEE39E324D5909111019871BA
:10721000809110018770806280931001946C90932C
:1072200011018091050190910601909315018093C1
:1072300014018091070190910801909317018093A8
:10724000160180E061E0E0D2882309F4C0D22091E9
:10725000370130913801809107019091080128177A
:107260003907C9F42091390130913A01809109011F
:1072700090910A012817390771F420913B01309150
:107280003C0180910B0190910C012817390719F4EA
:107290005DD281119CD240913501509136015093BD
:1072A0000601409305012091370130913801309358
:1072B0000801209307018091390190913A01909340
:1072C0000A018093090180913B0190913C019093C8
:1072D0000C0180930B01F4E1EF2EF1E0FF2E809181
:1072E00010018770806380931001509315014093C3
:1072F00014013093170120931601C0E0D0E000E99B
:107300001FEFBB24B394E5E0CE2E71E1D72E4DC024
:10731000F701D583C48382E063E076D2882309F441
:107320004AD2902F90599F77E92FF0E0E15BFE4F12
:10733000ABE3B1E080E10D9001928150E1F790372D
:1073400009F030C03FB7F894F999FECF83E0F80117
:1073500080935700E89507B600FCFDCFA801AFE485
:10736000B1E02C9111968C91119790E0982F88277D
:10737000820F911D1296FA010C01B0925700E89508
:1073800011244E5F5F4FF1E0AF3CBF0751F7F801AA
:10739000C0925700E89507B600FCFDCFD092570089
:1073A000E8953FBF2196005F1F4F809109019091A2
:1073B0000A01C817D90708F4ABCFA895C7D188230D
:1073C00049F08BE191E065E071E048E050E024EFA6
:1073D00039E35BD4F0D180E090E0F999FECF92BD23
:1073E00081BDF89A80B50895F999FECF1FBA92BD74
:1073F00081BD60BD0FB6F894FA9AF99A0FBE089550
:107400000000089540E050E00BC020E030E00000B4
:107410002F5F3F4F61E0243F3607C9F74F5F5F4F53
:107420004817590790F308950F931F93182F00E002
:10743000E7DF17FD02C02B9801C02B9AE1DF2D9AE0
:1074400083B1110F82958170182BDADF2D98D8DF68
:107450000F5F083069F7812F1F910F9108951F93D7
:10746000182F2A9881E090E0CDDF812FDDDF182FE3
:107470002A9A81E090E0C6DF812F1F9108951F9323
:10748000182F2A9881E090E0BDDF812F8F71CCDF2B
:107490008FEFCADF182F2A9A81E090E0B3DF812FA7
:1074A0001F9108950F931F93CF93DF93182FEB0134
:1074B000042F2A9881E090E0A5DF1F71812F806260
:1074C000B3DF182F03C08991AFDF01500023D9F734
:1074D0002A9A81E090E096DF812FDF91CF911F9172
:1074E0000F9108950F931F93182F062F2A9881E06C
:1074F00090E088DF1F71812F806296DF182F802F28
:1075000093DF2A9A81E090E07DDF812F1F910F9118
:10751000089580E0B4DF81FD09C080E0B0DF682F0E
:10752000626080E0DFDF85E090E06CDF08957F92AD
:107530008F929F92AF92BF92CF92DF92EF92FF9283
:107540000F931F93DF93CF93CDB7DEB728970FB676
:10755000F894DEBF0FBECDBF782E480159016A01F5
:107560007B01882381F40093CF019092D001209376
:10757000D101B092D2014093D301D092D401609353
:10758000D501F092D60189829A82AB82BC82CD82EB
:10759000DE82EF82F88681E0871538F0872D865FDE
:1075A000BE016F5F7F4F45E07DDF82E068DF21E055
:1075B00030E002C0220F331F7A94E2F7682F622B6B
:1075C00082E090DF28960FB6F894DEBF0FBECDBFE5
:1075D000CF91DF911F910F91FF90EF90DF90CF90AF
:1075E000BF90AF909F908F907F9008957F928F92E1
:1075F0009F92AF92BF92CF92DF92EF92FF920F9342
:107600001F93DF93CF93CDB7DEB728970FB6F894CB
:10761000DEBF0FBECDBF8F3F09F483C0082F10E03F
:1076200020E030E040E050E060E070E089831A83C1
:107630002B833C834D835E836F83788788243CEF64
:10764000932E21EEA22E98EAB92E88EAC82EDD24C8
:10765000EE24FF24080D81E09981091708F080E0ED
:10766000192D91E0AA819A1608F090E0E82FE10F19
:1076700081E0E11708F080E0982B1E2F2A2D772457
:107680007394BB81AB1608F07724920F81E09217B8
:1076900008F080E0782A292F3B2D91E0EC81BE167E
:1076A00008F090E0E72DE30F81E0E31708F080E0B9
:1076B000982B3E2F4C2D77247394FD81CF1608F024
:1076C0007724940F81E0941708F080E0782A492FFE
:1076D0005D2D91E08E81D81608F090E0E72DE50F42
:1076E00081E0E51708F080E0982B5E2F6E2D77245F
:1076F0007394AF81EA1608F07724960F81E096170D
:1077000008F080E0782A692F7F0C772D59016A01F3
:10771000E92E80E00CDF81E09501A6016E2D772D2A
:1077200006DF28960FB6F894DEBF0FBECDBFCF910F
:10773000DF911F910F91FF90EF90DF90CF90BF905E
:10774000AF909F908F907F90089518954091090178
:1077500050910A0164E0440F551F6A95E1F7E0E09B
:10776000F0E02FEF3FEF19C084912827822F8295F8
:107770008227082E869586958025082E8695802559
:107780008770022E232F869507948795302D282702
:1077900006948795302528273196E417F50720F3BE
:1077A00040E080910B0190910C012817390709F4F2
:1077B00041E0842F089528E088E190E00FB6F89426
:1077C000A895809360000FBE20936000FFCFBEDFBE
:1077D0008823C1F088E10FB6F8948093600010927E
:1077E00060000FBE809100019091010120E80FB66A
:1077F000F89420936100809361000FBEE0E0F0E018
:1078000009950895D8DF08952F923F924F925F9285
:107810006F927F928F929F92AF92BF92CF92DF92A0
:10782000EF92FF920F931F93DF93CF93CDB7DEB705
:10783000E3970FB6F894DEBF0FBECDBF6BA7809362
:1078400012016624B9E02B2E312C2C0E3D1E2E0188
:107850000894411C511CD101EDE0F1E081E201905E
:107860000D928150E1F7109103017C842B852AA7AA
:1078700050DE299881E090E0C5DD81EEF0DD82EEFA
:10788000EEDD80E0FCDD682F6E7F80E02BDE30E0F7
:1078900040E050E060E070E080E090E01CA73DA791
:1078A0004EA75FA768AB79AB8AAB9BAB8824ACEFE4
:1078B0009A2EF1EEAF2EE8EABE2E78EAC72EDD242E
:1078C000EE24FF24012F080D81E0011708F080E06D
:1078D000192D91E0EDA59E1608F090E0E82FE10F3C
:1078E00081E0E11708F080E0982B1E2F2A2DE1E0BF
:1078F000FEA5AF1608F0E0E0920F81E0921708F0C5
:1079000080E0E82B292F3B2D91E08FA5B81608F0D9
:1079100090E0E30F81E0E31708F080E0982B3E2F22
:107920004C2DE1E0F8A9CF1608F0E0E0940F81E0DB
:10793000941708F080E0E82B492F5D2D91E089A98C
:10794000D81608F090E0E50F81E0E51708F080E038
:10795000982B5E2F6E2D81E0EAA9EE1608F080E0EC
:10796000E92FE60F91E0E61708F090E0892B8F0DE4
:1079700009831A832B833C834D835E83EF8388873F
:107980008AE0B20145E08EDD80E1B20145E08ADDAA
:10799000FAA5FF3F11F010EA01C010EB769476943F
:1079A000769427E0720E2A9881E090E02BDD812FFB
:1079B0003BDD81017101E70CF11C04C0F8018191EC
:1079C0008F0132DD0E151F05C9F72A9A81E090E07C
:1079D00019DD299A81E090E015DD299881E090E099
:1079E00011DD8FEF3CDD8073E1F387E060E77ADD46
:1079F00084FF02C081EE33DD8CDD80E040DD682F46
:107A0000616080E06FDD87E060E76CDD0091CF01B1
:107A10001091D0012091D1013091D2014091D30138
:107A20005091D4016091D5017091D601802F812BA6
:107A3000822B832B842B852B862B872B61F009834C
:107A40001A832B833C834D835E836F8378878AE020
:107A5000B20145E027DD82EE02DD81EE00DD299AEC
:107A600081E090E0CFDCAA24BB24A895CC24DD24BF
:107A7000A89587E104DD80FD9CC08FEFF0DCE82E47
:107A8000E694F7E0EF22EF1609F493C02A9881E01C
:107A900090E0B8DC80E6C8DC8FEFC6DCF82E2A9ACE
:107AA00081E090E0AFDC20E22F1518F482EED7DC05
:107AB000FF242A9881E090E0A5DC81E6B5DC0EE2A7
:107AC00011E006C08FEFB0DCF80181938F01FA94CA
:107AD000FF20C1F72A9A81E090E094DC87E060E71C
:107AE00001DD2A9881E090E08DDC8E2D886A9CDC97
:107AF0008E2D9ADC2A9A81E090E084DC809131011D
:107B00008770823009F058C0909130018091020155
:107B1000981709F04EC080913201682F70E0CB01B8
:107B200087709070039709F034C0809133018830DA
:107B300009F02FC040913501242F332727FD3095C0
:107B40008091040190E001972817390714F5242F3C
:107B50002F5F2093040140912F0140930301F99975
:107B6000FECF1FBA81E090E092BD81BD40BD0FB64F
:107B7000F894FA9AF99A0FBEF999FECF1FBA42E02B
:107B800050E052BD41BD20BD0FB6F894FA9AF99A63
:107B90000FBE8091110190E0862797278770907023
:107BA000892B39F4809133015BA5851711F481E0AD
:107BB00019C081E090E026DC0894C11CD11C64E669
:107BC000C616D10409F054CF0894A11CB11C74E16D
:107BD000A716B10409F049CF63948AE0681609F04A
:107BE0003ACE80E0E3960FB6F894DEBF0FBECDBF6D
:107BF000CF91DF911F910F91FF90EF90DF90CF9089
:107C0000BF90AF909F908F907F906F905F904F90BC
:107C10003F902F900895FC0188279927E89421919F
:107C20002032E9F3293010F02E30C8F32B3241F026
:107C30002D3239F4689404C00E94613E820F911D78
:107C4000219120532A30C0F31EF4909581959F4FC7
:107C50000895A0E0B0E0EFE2FEE30C94733E7C01F7
:107C6000EB018A01690109C0CE012196F60109954F
:107C7000F70181937F010150104001151105A1F713
:107C8000CDB7DEB7E8E00C948F3EA0E0B0E0EBE4C7
:107C9000FEE30C94733EEC017B018A01690109C08B
:107CA000CE012196F70161917F01F60109950150FE
:107CB000104001151105A1F7CDB7DEB7E8E00C942F
:107CC0008F3E7AE0979F902D879F802D910D1124F4
:107CD00008952F923F924F925F926F927F928F9270
:107CE0009F92AF92BF92CF92DF92EF92FF920F934B
:107CF0001F93CF93DF93CDB7DEB7CA1BDB0B0FB655
:107D0000F894DEBF0FBECDBF09942A883988488811
:107D10005F846E847D848C849B84AA84B984C884A7
:107D2000DF80EE80FD800C811B81AA81B981CE0F9E
:107D3000D11D0FB6F894DEBF0FBECDBFED01089583
:040000030000700089
:00000001FF
-154
View File
@@ -1,154 +0,0 @@
#ifndef MyOtaBootloaderHW_H
#define MyOtaBootloaderHW_H
#include <stdlib.h>
#include <avr/power.h>
//#define LED_DEBUG
//#define UART_DEBUG
// hardware
// ********
#define F_CPU_DIV clock_div_4
#define F_CPU_REAL F_CPU / (1 << F_CPU_DIV)
#define UART_BAUD 9600UL
#define DELAY_M 125 * (1 << F_CPU_DIV)
// boolean definition
// ******************
typedef uint8_t bool;
typedef uint8_t boolean;
#define false 0x0
#define true 0x1
#define LOW 0x0
#define HIGH 0x1
// delay
// *****
static void delayu() {
asm("nop");
}
static void delaym(uint16_t t) {
uint16_t d, a;
for (d = 0; d < t; d++)
for (a = 0; a < DELAY_M; a++)
asm("nop");
}
// LED debug
// *********
#ifdef LED_DEBUG
#define LED_DDR DDRD
#define LED_PORT PORTD
#define LED_RED 6 // Arduino Pin 6 <-> Bit 6 of port D
#define LED_GREEN 7 // Arduino Pin 7 <-> Bit 7 of port D
#define LED_MRED (1 << LED_RED)
#define LED_MGREEN (1 << LED_GREEN)
#define LED_MBOTH (LED_MRED | LED_MGREEN)
static void led_init() {
LED_DDR |= LED_MBOTH;
LED_PORT &= ~LED_MBOTH;
}
static void blink(uint8_t mask, uint16_t time) {
LED_PORT |= mask;
if (time > 0)
{
delaym(time);
LED_PORT &= ~mask;
delaym(time);
}
}
#endif
// UART debug
// **********
#ifdef UART_DEBUG
static void uart_init()
{
UCSR0A = _BV(U2X0); //Double speed mode USART0
UCSR0B = _BV(RXEN0) | _BV(TXEN0);
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
UBRR0L = (uint8_t)( (F_CPU_REAL + UART_BAUD * 4L) / (UART_BAUD * 8L) - 1 );
}
static void uart_putc(char ch) {
while (!(UCSR0A & _BV(UDRE0)));
UDR0 = ch;
}
static void uart_puts(char *s)
{
while (*s)
uart_putc(*s++);
}
static void uart_puti(uint8_t i)
{
char s[5];
itoa( i, s, 10 );
uart_puts( s );
}
#endif
// SPI communication
// *****************
#define SPI_DDR DDRB
#define SPI_PORT PORTB
#define SPI_PIN PINB
#define SPI_SCLK 5 // Arduino Pin 13 <-> Bit 5 of port B
#define SPI_MISO 4 // Arduino Pin 12 <-> Bit 4 of port B
#define SPI_MOSI 3 // Arduino Pin 11 <-> Bit 3 of port B
#define SPI_CSN 2 // Arduino Pin 10 <-> Bit 2 of port B
#define SPI_CE 1 // Arduino Pin 9 <-> Bit 1 of port B
#define csnlow() SPI_PORT &= ~(1<<SPI_CSN);delaym(1)
#define csnhigh() SPI_PORT |= (1<<SPI_CSN);delaym(1)
#define celow() SPI_PORT &= ~(1<<SPI_CE);delaym(1)
#define cehigh() SPI_PORT |= (1<<SPI_CE);delaym(1)
static void SPIinit()
{
SPI_DDR |= (1 << SPI_MOSI) + (1 << SPI_SCLK) + (1 << SPI_CE) + (1 << SPI_CSN);
SPI_DDR &= ~(1 << SPI_MISO);
SPI_PORT &= ~((1 << SPI_MOSI) + (1 << SPI_SCLK) + (1 << SPI_CE));
SPI_PORT |= (1 << SPI_CSN);
}
static uint8_t SPItransfer(uint8_t value)
{
uint8_t i;
for(i = 0 ;i < 8;i++) {
delayu();
if ((value & 0x80) == 0x00)
SPI_PORT &= ~(1 << SPI_MOSI);
else
SPI_PORT |= (1 << SPI_MOSI);
value = (value << 1); // shift next bit into MSB..
delayu();
SPI_PORT |= (1 << SPI_SCLK);
value |= ((SPI_PIN >> SPI_MISO) & 0x01); // capture current MISO bit
delayu();
SPI_PORT &= ~(1 << SPI_SCLK);
delayu();
}
return value;
}
static uint8_t spiTrans(uint8_t cmd){
uint8_t status;
csnlow();
status = SPItransfer( cmd );
csnhigh();
return status;
}
#endif // MyOtaBootloaderHW_H
-253
View File
@@ -1,253 +0,0 @@
#ifndef MyOtaBootloaderRF24_H
#define MyOtaBootloaderRF24_H
#include "MyOtaBootloaderHW.h"
#include "../libraries/MySensors/utility/nRF24L01.h"
#include <stdlib.h>
// hardware-agnostic interface to transceiver
// ******************************************
// static void begin()
// static void address(uint8_t addr)
// static boolean write(uint8_t next, uint8_t* packet, uint8_t length, boolean multicast)
// static bool available(uint8_t* pipe_num)
// static void read(uint8_t* buf, uint8_t pipe)
typedef enum { RF24_PA_MIN = 0,RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_PA_ERROR } rf24_pa_dbm_e ;
typedef enum { RF24_1MBPS = 0, RF24_2MBPS, RF24_250KBPS } rf24_datarate_e;
uint64_t pipe0_reading_address; /**< Last address set on pipe 0 for reading. */
static uint8_t read_register(uint8_t reg)
{
csnlow();
SPItransfer( R_REGISTER | ( REGISTER_MASK & reg ) );
uint8_t result = SPItransfer(0xff);
csnhigh();
return result;
}
static uint8_t write_registers(uint8_t reg, const uint8_t* buf, uint8_t len)
{
uint8_t status;
csnlow();
status = SPItransfer( W_REGISTER | ( REGISTER_MASK & reg ) );
while ( len-- )
SPItransfer(*buf++);
csnhigh();
return status;
}
static uint8_t write_register(uint8_t reg, uint8_t value)
{
uint8_t status;
csnlow();
status = SPItransfer( W_REGISTER | ( REGISTER_MASK & reg ) );
SPItransfer(value);
csnhigh();
return status;
}
static uint8_t read_payload(uint8_t* buf, uint8_t data_len)
{
#ifdef UART_DEBUG
uart_puts("-> ");
#endif
uint8_t status;
uint8_t* current = buf;
csnlow();
status = SPItransfer( R_RX_PAYLOAD );
while ( data_len-- ) {
*current = SPItransfer(0xFF);
#ifdef UART_DEBUG
uart_puti(*current);
uart_putc(',');
#endif
current++;
}
csnhigh();
#ifdef UART_DEBUG
uart_putc('\n');
#endif
return status;
}
#define get_status() spiTrans(NOP)
static void powerUp(void)
{
if (!(read_register(CONFIG) & _BV(PWR_UP))){
write_register(CONFIG,read_register(CONFIG) | _BV(PWR_UP));
delaym(5);
}
}
#define convert_64_bit_to_byte_array(value, data) ((*(uint64_t *)(data)) = (uint64_t)(value))
static void startListening(void)
{
powerUp();
write_register(CONFIG, read_register(CONFIG) | _BV(PRIM_RX));
write_register(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
if (pipe0_reading_address){
uint8_t addressa[8];
convert_64_bit_to_byte_array(pipe0_reading_address, addressa);
write_registers(RX_ADDR_P0, addressa, 5);
}
spiTrans(FLUSH_RX);
spiTrans(FLUSH_TX);
cehigh();
}
static void stopListening(void)
{
celow();
spiTrans(FLUSH_TX);
spiTrans(FLUSH_RX);
write_register(CONFIG, read_register(CONFIG) & ~_BV(PRIM_RX));
}
static void write_payload(const uint8_t* buf, uint8_t data_len, const uint8_t writeType)
{
#ifdef UART_DEBUG
uart_puts("<- ");
#endif
const uint8_t* current = buf;
csnlow();
SPItransfer(writeType);
while ( data_len-- ) {
#ifdef UART_DEBUG
uart_puti(*current);
uart_putc(',');
#endif
SPItransfer(*current++);
}
csnhigh();
#ifdef UART_DEBUG
uart_putc('\n');
#endif
}
static bool writem( const uint8_t* buf, uint8_t len, const bool multicast )
{
write_payload( buf, len, multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD) ;
cehigh();
celow();
while( ! ( get_status() & ( _BV(TX_DS) | _BV(MAX_RT) ))) { }
uint8_t status = write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
if( status & _BV(MAX_RT)){
spiTrans(FLUSH_TX);
return 0;
}
return 1;
}
static uint8_t getDynamicPayloadSize(void)
{
uint8_t result = 0;
csnlow();
SPItransfer( R_RX_PL_WID );
result = SPItransfer(0xff);
csnhigh();
if(result > 32) {
spiTrans(FLUSH_RX);
return 0;
}
return result;
}
static bool available(uint8_t* pipe_num)
{
if (!(read_register(FIFO_STATUS) & _BV(RX_EMPTY) )){
if (pipe_num) {
uint8_t status = get_status();
*pipe_num = ( status >> RX_P_NO ) & 0b111;
}
return 1;
}
return 0;
}
static void writeAckPayload(uint8_t pipe, const uint8_t* buf, uint8_t len)
{
const uint8_t* current = buf;
uint8_t data_len = len;
csnlow();
SPItransfer(W_ACK_PAYLOAD | ( pipe & 0b111 ) );
while ( data_len-- )
SPItransfer(*current++);
csnhigh();
}
static void read( uint8_t* buf, uint8_t pipe ){
read_payload(buf, getDynamicPayloadSize());
write_register(STATUS,_BV(RX_DR) | _BV(MAX_RT) | _BV(TX_DS));
writeAckPayload(pipe,&pipe, 1 );
}
static void openWritingPipe(uint64_t value)
{
uint8_t valuea[8];
convert_64_bit_to_byte_array(value, valuea);
write_registers(RX_ADDR_P0, valuea, 5);
write_registers(TX_ADDR, valuea, 5);
}
static void openReadingPipe(uint8_t child, uint64_t address)
{
if (child == 0)
pipe0_reading_address = address;
uint8_t addressa[8];
convert_64_bit_to_byte_array(address, addressa);
if ( child < 2 )
write_registers(RX_ADDR_P0 + child, addressa, 5);
write_register(EN_RXADDR,read_register(EN_RXADDR) | _BV(ERX_P0 + child));
}
static boolean write(uint8_t next, uint8_t* packet, uint8_t length, boolean multicast) {
powerUp();
stopListening();
openWritingPipe(TO_ADDR(next));
bool ok = writem(packet, length, multicast);
startListening();
return ok;
}
static void address(uint8_t addr)
{
if (addr != BROADCAST_ADDRESS) {
openReadingPipe(WRITE_PIPE, TO_ADDR(addr));
openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(addr));
}
}
static void begin(void)
{
pipe0_reading_address = 0;
SPIinit();
delaym( 10 ) ;
write_register(SETUP_RETR, 5 << ARD | 15 << ARC);
write_register(EN_AA, 0b00111111 & ~_BV(BROADCAST_PIPE)) ;
write_register(RF_CH, RF24_CHANNEL);
write_register(RF_SETUP, (read_register(RF_SETUP) & 0b11010000) | ((RF24_PA_LEVEL << 1) + 1) | ((RF24_DATARATE & 0b00000010 ) << 4) | ((RF24_DATARATE & 0b00000001 ) << 3));
write_register(CONFIG, read_register(CONFIG) | _BV(CRCO) | _BV(EN_CRC)) ;
write_register(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT));
csnlow();
SPItransfer(ACTIVATE);
SPItransfer(0x73);
csnhigh();
write_register(FEATURE, read_register(FEATURE) | _BV(EN_ACK_PAY) | _BV(EN_DPL) );
write_register(DYNPD, read_register(DYNPD) | _BV(DPL_P5) | _BV(DPL_P4) | _BV(DPL_P3) | _BV(DPL_P2) | _BV(DPL_P1) | _BV(DPL_P0));
spiTrans(FLUSH_RX);
spiTrans(FLUSH_TX);
powerUp(); //Power up by default when begin() is called
write_register(CONFIG, read_register(CONFIG) & ~_BV(PRIM_RX));
openReadingPipe(BROADCAST_PIPE, TO_ADDR(BROADCAST_ADDRESS));
}
#endif // MyOtaBootloaderRF24_H
-47
View File
@@ -1,47 +0,0 @@
See NodeJsController/Readme.md for usage instructions...
<h1>Technical details to write your own controller</h1>
All initialization the bootloader does (finding parent / requesting nodeId on first start) uses the same packets as a normal MySensors sketch. There is no need for the controller to distinguish between packets from the bootloader and packets from normal sketch execution. The controller only needs to care about two additional request/response communications. All communication is binary.
<h2>FirmwareConfig</h2>
<ul>
<li>the bootloader sends a FirmwareConfigRequest packet to the gateway to request information about the firmware it should execute:
<p>
typedef struct<br/>
{<br/>
&nbsp;uint16_t type;<br/>
&nbsp;uint16_t version;<br/>
} FirmwareConfigRequest;
</p>
<li>the gateway (the controller) responds with a FirmwareConfigResponse including details about the firmware the sensor should execute:
<p>
typedef struct<br/>
{<br/>
&nbsp;uint16_t type;<br/>
&nbsp;uint16_t version;<br/>
&nbsp;uint16_t blocks;<br/>
&nbsp;uint16_t crc;<br/>
} FirmwareConfigResponse;
</p>
</ul>
<h2>Firmware</h2>
<ul>
<li>the bootloader sends a FirmwareRequest packet to the gateway to request a specific subset (block) of the compiled firmware:
<p>
typedef struct<br/>
{<br/>
&nbsp;uint16_t type;<br/>
&nbsp;uint16_t version;<br/>
&nbsp;uint16_t block;<br/>
} FirmwareRequest;
</p>
<li>the gateway (the controller) responds with a FirmwareResponse including the specific block of the compiled firmware:
</ul>
<p>
typedef struct<br/>
{<br/>
&nbsp;uint16_t type;<br/>
&nbsp;uint16_t version;<br/>
&nbsp;uint16_t block;<br/>
&nbsp;uint8_t data[FIRMWARE_BLOCK_SIZE];<br/>
} FirmwareResponse;
</p>
+1 -1
View File
@@ -1 +1 @@
To get started, <a href="http://www.clahub.com/agreements/mysensors/Arduino">sign the Contributor License Agreement</a>.
To get started, <a href="http://www.clahub.com/agreements/mysensors/MySensors">sign the Contributor License Agreement</a>.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,638 @@
%!PS-Adobe-3.0 EPSF-3.0
%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
%%Title: alicenfriends.eps
%%CreationDate: Fri Aug 14 20:44:59 2015
%%DocumentData: Clean7Bit
%%LanguageLevel: 2
%%Pages: 1
%%BoundingBox: 14 14 401 161
%%EndComments
%%BeginProlog
% Use own dictionary to avoid conflicts
10 dict begin
%%EndProlog
%%Page: 1 1
% Translate for offset
14.173228346456694 14.173228346456694 translate
% Translate to begin of first scanline
0 145.99761739627107 translate
385.99370078740156 -145.99761739627107 scale
% Image geometry
386 146 8
% Transformation matrix
[ 386 0 0 146 0 0 ]
% Strings to hold RGB-samples per scanline
/rstr 386 string def
/gstr 386 string def
/bstr 386 string def
{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
true 3
%%BeginData: 29697 ASCII Bytes
colorimage
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
mf*Lgk1A)6iqn<,JcE.X#5dQ7e(<UVm/MS~>
mf*L_[u5`oX7LoBJcE.X#4o<^IuCB!m/MS~>
mf*Lb`h]'!^%R$WJcE.X#56'4R\[Xjm/MS~>
nG`Rbe#`JG!j'cDJcC<$\c2a+e#`JG!j'cDmf.e~>
nG`RUIgl5T!Xs2:JcC<$\c2`sIgl5T!Xs2:mf.e~>
nG`RYRQp^.!^Dh9JcC<$\c2a"RQp^.!^Dh9mf.e~>
nc'$^ZDa_1c-t%LWMm#_JcC<$]Di<(ZDa_1c-t%LWMm#_n,In~>
nc'$4)uoseCk$O\"99.FJcC<$]Di;S)uoseCk$O\"99.Fn,In~>
nc'$B9/\\'G(P'>3&X7GJcC<$]Di;a9/\\'G(P'>3&X7Gn,In~>
o)AjWXJiYsr;Hig];V^jJcC<$^&J6"XJiYsr;Hig];V^jnGe"~>
o)Aj-$ii!,r;HiM2uj)fJcC<$^&J5M$ii!,r;HiM2uj)fnGe"~>
o)Aj;4uQ?Er6>GR=>i1WJcC<$^&J5[4uQ?Er6>GR=>i1WnGe"~>
oD\sgYc,kGq>LQBW3,1OpjrHrs2"^<pop^BrV?Erc)@c+nbD_UJ,~>
oD\sZ(]\OOq>LP9!#!iPpjrHrs2"^<lPKV@qYC*oDZCF#nbD_UJ,~>
oD\s^82b_:q9B.h2FQ9hpjrHrs2"^<mmo?]bk]NfGrA(EnbD_UJ,~>
oD\p@W4;a!rW]m2]%Z99pjrHrs24j=d\s\Qp\kB,W4L^EnbDeWJ,~>
oD\o?!&4-@rWY+X?J=N2pjrHrs24j=HiPkop\k@P!++C@nbDeWJ,~>
oD\o[2H.cmc3@9,HeRTOpjrHrs24j=Qo7mcpW`tC2LF0>nbDeWJ,~>
o`#$jXJkR\rWhqkWPHj;lhH&2JcE^h"8f0ume6Vbn>N>%cen'`pA]X~>
o`#$b$inTHrWh!V$+8+:lhH&2JcE^h"7lp$cM%5BecQ5dcen'`pA]X~>
o`#$e4uT\>c3Ld;4Li_plhH&2JcE^h"84o:Z1cHJ[Q$CZcen'`pA]X~>
o`#!SW42Eu#.:D2^!GU+JcC<$`W$#$W42Eu#.:D2^!GU+pA]X~>
o`#!!!%ddA"tot!^!GU+JcC<$`W$"G!%ddA"tot!^!GU+pA]X~>
o`#!12Gs+l##dF8^!GU+JcC<$`W$"W2Gs+l##dF8^!GU+pA]X~>
o`#!EW6Ou6#L99EXhE'BpjrHrs2P'?f;QsdrWg`IV6.GWjnSTMJ,~>
o`"uN!,V<,#BBfIXhE'BpjrHrs2P'?MZ@ScrWdHD=foB`jnSTMJ,~>
o`"ug2Kn`<#C8:KXhE'BpjrHrs2P'?Uc*Dgc3JYREim%$jnSTMJ,~>
o`#!AW71D<#LfWHUT_MfnU^^ks2P'?e#:afrWgoNUSG$/g@=q;J,~>
o`"uB!.XY?#Cc_LUT_MfnU^^ks2P'?IfOujrWdoQ:SY,0g@=q;J,~>
o`"u]2M1SH#D+jKUT_MfnU^^ks2P'?RPocic3JqZC87uKg@=q;J,~>
o`#!EW6Ou6#L99ET;K'Cl[f(es2P'?f;QsdrWg`IV4XWncg1?*J,~>
o`"uN!,V<,#BBfIT;K'Cl[f(es2P'?MZ@ScrWdHD=eDS"cg1?*J,~>
o`"ug2Kn`<#C8:KT;K'Cl[f(es2P'?Uc*Dgc3JYREhB5;cg1?*J,~>
o`#!SW42Eu#I^S.T;/U0jamG_s2P'?jf#Z\rWfm1VOsWe`oZspJ,~>
o`#!!!%ddA#;?-rT;/U0jamG_s2P'?ZN)QKrWb+WDkEf.`oZspJ,~>
o`#!12Gs+l#?*O3T;/U0jamG_s2P'?__tBac3I9+JY/^@`oZspJ,~>
o`#$jXJkR\rW)HeW!&`eXhinGs+13irr`/"W:'BY!:]ud"J;Ej_;G"eJ,~>
o`#$b$inTHrWq'W#b@T^XhinGs+13irr_j$!7(<;#hB(`SY)XY_;G"eJ,~>
o`#$e4uT\>c3Uj<4.r4?XhinGs+13irr_sY2R2u$#du]STV%s\_;G"eJ,~>
oD\p@W4;g#rWp$4WMuPaWkI,;s+13hrr^]J\Gc1$#e6h4WM#rb^"W5\J,~>
oD\o?!&49DrWk@]9rXeZWkI,;s+13hrr[WK0`(PC#W2TVWM#rb^"W5\J,~>
oD\o[2H.ioc3RH/C8ml"WkI,;s+13hrr\WH;p>!]#Z`iuWM#rb^"W5\J,~>
oD\sgYc,kGq>L]EW2Q\pTV8C!g4B9Ts2G!@pop^BrV?F!bc%J>WM#rb^"W5\J,~>
oD\sZ(]\OOq>L\<!"G?qTV8C!g4B9Ts2G!@lPKV@qYC*sD?(-6WM#rb^"W5\J,~>
oD\s^82b_:q9B:k2F!e4TV8C!g4B9Ts2G!@mmo?]bk]NjGW%dXWM#rb^"W5\J,~>
o)AjWXJiYsr;I)n\u;R,WMlG`XhinGs+13grrhSfW4;9nrX85,W2Q\pW1Tfd_;G"eJ,~>
o)Aj-$ii!,r;I)T2ZNr(WMlG`XhinGs+13grrg(F!&2^rrX7:9!!@GEW1Tfd_;G"eJ,~>
o)Aj;4uQ?Er6>\Y=#N$nWMlG`XhinGs+13grrgS12H-mYc3q'^2E6JkW1Tfd_;G"eJ,~>
nc'!]Xf/2-`4a1%Wi2lqW<K/kUT_]!JcC<$_uC,/Xf/2-`4a1%Wi2lqW<K/kUT_]!p]#a~>
nc'<<&c_n_<'OG@#653WWMubeUT_]!JcC<$_uCFc&c_n_<'OG@#653WWMubeUT_]!p]#a~>
nc'<J6T-i"BO#:P3]93XWMubeUT_]!JcC<$_uCFq6T-i"BO#:P3]93XWMubeUT_]!p]#a~>
nG`RP];b\+rM^""US"Elcg-/^JcEUe!p7+oql'Im#,n;hVRFh:p]#a~>
nG`RCAI887$OBIpWMuhkT;K'Cl[f(es2+d;fjtKf!"0',RAm0aUS"Elcg1?*J,~>
nG`RGJNWig$Ti*pWMuhkT;K'Cl[f(es2+d;h17]d2@DT]SuJ]fUS"Elcg1?*J,~>
nGa0lg;'hfW2?GjWMuhnUS"<b_:nF1s+13ertFqV^oaK4VP^;kWMl\hT;/U+g@=q;J,~>
nGa0lg:2J\!%`&ZWMuhnUS"<b_:nF1s+13ertFqV\9fb,/8q)YWMl\hT;/U+g@=q;J,~>
nGa0lg:N8B2H57#WMuhnUS"<b_:nF1s+13ertFqV]8KWH<.U^AWMl\hT;/U+g@=q;J,~>
nG`jjjj1W?W2#uWTV2:V"f&35cfOqQs+13ersJP_cb6?WUS"0OT`1PeUTM5UjnSTMJ,~>
nG`jjjj1V@!)K5ZTV2:V"f&35cfOqQs+13ersJP_cb3?Z:S48PT`1PeUTM5UjnSTMJ,~>
nG`jjjj1V[2JSkVTV2:V"f&35cfOqQs+13ersJP_cb4<VC7h,kT`1PeUTM5UjnSTMJ,~>
n,F!ihp8U,Vk]ZIR$jD?YJ&9>nU^^ks1nXHn__ZuW2HDYR$a;1VQ[\ZhspF?J,~>
n,F!ihp5U/;kobJR$jD?YJ&9>nU^^ks1nXHn__Z!!)oY\R$a;1VQ[\ZhspF?J,~>
n,F!ihp6R+DPNVeR$jD?YJ&9>nU^^ks1nXHn__Z<2K#:XR$a;1VQ[\ZhspF?J,~>
n,F!plef=QYHORgS=cL`_9_$)pjrHrs1nXHq!6hLW3<CsS=H4F[_^;IlhL2RJ,~>
n,F!plec=T>HaZhS=cL`_9_$)pjrHrs1nXHq!6gM!*cY!S=H4F[_^;IlhL2RJ,~>
n,F!pled:PG-@O.S=cL`_9_$)pjrHrs1nXHq!6gh2Kl9rS=H4F[_^;IlhL2RJ,~>
mf*gmn]%N>`iQ,;XK^%)jmi+ss+13art"np`i-MhXJi)!Yg;%bnbDeWJ,~>
mf*gmnT"VB`iQ,;XK^%)jmi+ss+13art"npEWBalXJi)!Yg;%bnbDeWJ,~>
mf*gmnVoGY`iQ,;XK^%)jmi+ss+13art"npNAcC.XJi)!Yg;%bnbDeWJ,~>
m/II?W6+/;]tM8&lgab$s+13]rsR2PalU@P]tqbTnbD_UJ,~>
m/IH@!-RD>]tM8&lgab$s+13]rsO2SFlgHQ]tqbTnbD_UJ,~>
m/IH[2N[%:]tM8&lgab$s+13]rsP/OOQF<l]tqbTnbD_UJ,~>
q>UHZp!`o=!7Uk\qpPQ0JcC<$bPqS,p!`o=!7Uk\qpPQ0p]#a~>
q>UH0omcr@!.Xo^qgSU\JcC<$bPqRWomcr@!.Xo^qgSU\p]#a~>
q>UH>opYk<!1Nh?qjIN0JcC<$bPqReopYk<!1Nh?qjIN0p]#a~>
q>UHDk,8:/S,WTeo^qVKkl^>Fo_=";_#FD`k,8:/p]#a~>
q>UGEjo>C2S,WTZhqucV^B;[:hs5jn_#FCajo>C2p]#a~>
q>UG`ju*4.S,WT]k2t@nblcPZk3mm$_#FD'ju*4.p]#a~>
q>UHZp!`o=!7Uk\qpPQ0VuI,'p$:Sfc,IE+Z`iks$E^YA^;A(Dh<=D,JcEso!9s1*rhohGr4i-X
!9s7XJ,~>
q>UH0omcr@!.Xo^qgSU\VuI,$j27dMCfEYN+;F;F$4n=*5YPjDS%I;HJcEso!5J2VrVuqJr+l1Z
!5J:.J,~>
q>UH>opYk<!1Nh?qjIN0VuI,%l-R#7Mg/o;:.=Y1$:7HaB6&g!YfYeuJcEso!6t2*r\abFr.b*;
!6t9<J,~>
m/I1:W71eEeu&HOqs<.d_7$HDW!]W7^;J(9e(3@2iVMaFjPAYFe'6"S^::BFW!9<0]Zo-non!.^
rr^`Ke,T?snGe"~>
m/I0;!.Y%Heu&HOo>d>F814oF!"',_5Ytj%Iu9hhWqsF:ZC[YNIr&Lp5VNoN!!W]S4D^(jj+76L
rr[`NJ,fGtnGe"~>
m/I0V2Oa[Deu&HOp=$72DH/n!2@;TC>\7qsJr,_VSGK/kTp1e%Jp)*J>Z4#g2?l6>A;dA-l%/lR
rr\]JRfE<:nGe"~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10\Gla3n_V<tX8]%%\&6JFjmW$+rWi>kkMOY*];tq0"LQ%r
lhJa)&,Pqal/LOLe'lI]ahkd+\,3K3YO\UlXo5R+]`,VPa2u]Wf&#9ImI^Corr_fVj5'Xa"2CeL
f)2oNecaghlLFZJs*t~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10\Gla0f:Q;)#lXf./5U/K[aOU)rWi/N\tX:G3!fK`"=-Zn
_s>SI&+ADn^T3iVIt)!:?q`gN/GgOi'_25(&H*^O4T7SQ>[VZ'LS(3NbMW35rr_fVj5'Xa")@mP
f)2oNecaghlLFZJs*t~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10\Gla1hm7f<4T75C;.b!5UU.o"c3MM&V3cOB=$-C>"Aj*/
d-o6Z&+\l0c+U?PR[Sq^JoYO<=o1tp7e/G;6iBd\Ac??MItEfOTX1uHf&lnHrr_fVj5'Xa",8^g
f)2oNecaghlLFZJs*t~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10]Di93h8QLpW3*_Xj6ulqrWMiUf#+rqVusNVk5=*9*;K-k
in`22ahPEnX/N/,]Y;A-dF6S"hVRAYl1"TB!;c9d!;QNb!:0XK']@j"e'Z:Z^UgbNYdV9cbLYP4
nbgT1"7b[:h>Gh+^Snour3Z:N"Pr,'nbE(_J,~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10]Di9!RoBq!!"_83Y1!$orWM)hMGP`#!!=cs\+ebZ*:)@s
X.+l`?plgo#QPDY4?ukVG^k40T:`I)^XN);!:TLY!:0U@!5nfN'Vh:YIs>@/6o6"h(aqXOBSW4h
fD!Jg"7b[:h>Gh+CB//$r3Z:N"Pr,'nbE(_J,~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10]Di9'Y]rJm2F(DiT!QSic31bUM.TN^2?R'H`qeKk*:Mq7
]s+/iJo"di4>oQ&AS?=BQ("/DZa7Qoc.r3Y!:o^\!:TmK!7:_i'Xk?DR[&JVCgTsu8QKMOLnM)o
htbIq"7b[:h>Gh+L,Oe;r3Z:N"Pr,'nbE(_J,~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10^AeN.f".iZYK#>^rQG0HoAJ'.WMlo7hY?F:(%pnPgssZa
\?`*9Zb42tdb3L@mI^=rrt5"ql/CFFahbEdYe.p&gue/)e,KO?lJp^uecW[c^Xi=iq:5])j6H10
rVqB~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10^AeMcLGT,6'Ng[<qTJjEhPPZW"98j*TBkhC($!34Q\0*B
0H:3,+?WJ.I#jo3bMW'6rt4GG^SdEC?q;[O(cYWBQabT@e,KO?lJp^uecT[fCY&Ejq:5])j6H10
rVqB~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10^AeMmTNsQ96>DU_bfe9<]:+7M3&Ws@Zg[5[($X)YXdtAd
>YI.F:M"[#R&@:4f&leJrt4VTc+:$BJo=[P8Rd%3XiB8oe,KO?lJp^uecUXbL=Z:0q:5])j6H10
rVqB~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10_>af;gq'G_[aFC=_u9r9i5_ptWP[Tmp\tm!mGZaC`P/XW
YIqcqdb<gMo_u)p%eo>Kf#tn<X0B4UdG4!\gA_9FlJp^uecW[c^Xi=iq:5])j6H10rVqB~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10_>af0QS\[B.!WW*_u9r,VHaN0"B;Q$p\tldbGUD;<'VE+
'Kf3&I$:n[htsYX%d1m1LM04,#T,O'GaH2.gA_9FlJp^uecT[fCY&Ejq:5])j6H10rVqB~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10_>af3X^*hC:4B7f_p/P)RVb2G3-Dt5p\tljf".c;G\Bc2
7V-^rR&\'Tk5;I`%d_ZUTSRMe4@s"/Q*&<tgA_9FlJp^uecUXbL=Z:0q:5])j6H10rVqB~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10_uBr8cE4%RdI+[H)#NC9Xf/SSp&+IGf?hCAXKT+Pc._.?
oDY?]%JT5Jf#t_0YeJ<9k4/B.rr_fVj5'Xa"2CeLf)2oNecaghlLFZJs*t~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10_uBr(E"E!tGg<u$)"t7C&-+#7nFthPMf_W<$kt6lCm1d/
g\[TB%Hkd0LM/da(dMc$\(CB5rr_fVj5'Xa")@mPf)2oNecaghlLFZJs*t~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10_uBr-NC#NuI^-Q2(s8Ok5W2N7o(hFiUlf[q5=Se#MlOA1
j8>MK%IDQTTSR/K8SEp^`o$UNrr_fVj5'Xa",8^gf)2oNecaghlLFZJs*t~>
m/I1:W71eEf!t_`r6VLD`U:26%/o.sW33/(\]`O_nG81F$hrl=bJ1Ed\&[+_o\KNLe#:aqr7:B;
J,~>
m/I0;!.Y%Hf!t_`pN-ZD<6hrO%/Qt`!#5eV2J@`De*i=s$g5-cBLF*W/6n+Bhqe;7IfOuur7:B;
J,~>
m/I0V2Oa[Df!t_`q2[TsBX,"k%*Y5C2F]l$@#1Aeh"$L*$gc!8LhosV=b!6ek2$%>RPpW7r7:B;
J,~>
m/I1:W71eEf"(e`i390#o!//um(TF(fCAWirsS\liR>WOZb+B5lh]ZA"4O3ts8:I;s*t~>
m/I0;!.Y%Hf"(e`UDNu9g9LV]b!5qSYOVCArsS;AVgh5c+?3nm_scXc"+L<#s8:I;s*t~>
m/I0V2Oa[Df"(e`[7<,l\ZsEaY?Wt`]^bcNrsSDN\sdP<:L]$Ud.65s".D-:s8:I;s*t~>
m/I1:W71eEf":qbrl(hIm&g6kiNfDujRN"rrsJYmi6B!>[):;Mn`]uTe#:aqr7:B;J,~>
m/I0;!.Y%Hf":qbqc!PPb,tXIWZ2"UiUQ\orsJAHUMN10,Z&``fBE5:IfOuur7:B;J,~>
m/I0V2Oa[Df":qbr,stuYH?(SS4>JnipleprsJJT[Ynfg;K\89hrt(BRPpW7r7:B;J,~>
m/I1:W71eEf"D"cr5,DWrN#o%qT#PDhs:&gs8;m(qWQMN[]d<fh!P%-rr^`Ke,T?snGe"~>
m/I0;!.Y%Hf"D"cpIkR'qQ'T"nlu/ih!=`ds8;m(n%4?[-knXWQco-krr[`NJ,fGtnGe"~>
m/I0V2Oa[Df"D"cq/807bcB"na)*_Rh<Xies8;m(o#[PS<^pPVXk*=9rr\]JRfE<:nGe"~>
m/I1:W71eEf"D"aa/I[hrWUfM[+aj@JcGNF$N'5Hage=Z`nKbToD\pAW71eEf'r]i~>
m/I0;!.Y%Hf"D"a>QBqbrWRlR8_<;'JcGNF$M2H1?n_^7=HgYkoD\oB!.Y%Hf'r]i~>
m/I0V2Oa[Df"D"aI5[+`c38kZCtS(KJcGNF$MN&TJm:<=H`GH`oD\o]2Oa[Df'r]i~>
m/I1:W71eEf"M(bk,I#(rX&Xi*X+(3UEp'uh#@9UYV-4sHN*mYfLlMS0rb0OfLlMS0rb0Oj&66f
8a66R"e-V>-("]3rWKGY#8VXUrWU`K]\;]HJcGBB#6*uEageJMo`#$BW71eEf'r]i~>
m/I0;!.Y%Hf"M(b[fh`^rX&Xi*X+(3UEp'uh#@9UYV-4sHN*mYfLlMS0rb0OfLlMS0rb0Oj&66f
8a66R"e-V>-("]3rWKGY#8VXUrWRZLK%fPcJcGBB#5cH-?n`/`o`##C!.Y%Hf'r]i~>
m/I0V2Oa[Df"M(b`]/qJc3aPr(^)(TKH:7EZi85QO!Y&DA,aagY<<*n.#H6pY<<*n.#H6p\3gK(
42(X="b%?o*ddtec301T"qkG9c38_VQ.kR!JcGBB#5lrPJm:\:o`##^2Oa[Df'r]i~>
m/I1:W71eEf"M(bZb$,+r]L6Oj8=HD8`T`"6N6to36qNd:O`<B.@^25c<C5H.@^25c<C5H/&hG_
G@:FLW?p!le0P+orjXr5ro[(+nbr^&WlEcYpjrJ7rrDrfrr^`Ke,T?snGe"~>
m/I0;!.Y%Hf"M(b+>ircr]L6Oj8=HD8`T`"6N6to36qNd:O`<B.@^25c<C5H.@^25c<C5H/&hG_
G@:FLW?p!le0P+orjXr5ro[(+nbr\R*5gLrpjrJ7rrDZ^rr[`NJ,fGtnGe"~>
m/I0V2Oa[Df"M(b:0#O/c92.f\BJY"41NLK29sL4/Z6n+5\A<e+b%V[Vb,1Z+b%V[Vb,1Z,G<9(
?r#"mM&r\BX<%AecBTuZcFZ>Rn]h;@9#THLpjrJ7rrDcarr\]JRfE<:nGe"~>
m/I1:W71eEf"V.coVgO7rXJqerr2fqjo58W$3'off)GXPn/20(#OD9d!=.rbrWW3/rr+mnrr*);
!<2s]f)GXLn/1]p"llPgb2;`?s5Et[e#:aqr7:B;J,~>
m/I0;!.Y%Hf"V.chZ/u4rXJqerr2fqjo58W$3'off)GXPn/20(#OD9d!=.rbrWW3/rr+mnrr*);
!<2s]f)GXLn/1]p"g.j"b2;`?s5Et[IfOuur7:B;J,~>
m/I0V2Oa[Df"V.cju/=kc40i`cHaW6]$AUF#L:UMY5Z]M_\:>@#/C&\!=$=5c3;E"cH\#4rlt\<
!7&kFY5Z]I_\;Rc"eIWNb2;`?s5Et[RPpW7r7:B;J,~>
m/I1:W71eEf(o?aVuZ&OrVursrVursrW3*!rr!'."TSZ(rd#DnVrRdU"F:nsVrRdU$/\G`!!!-#
rWW6!rWE&urr2j$!X/Q+nbraVW2R8QjnOE,ir9,0W71eEf'r]i~>
m/I0;!.Y%Hf(o?a!!'b'rVursrVursrW3*!rr!'."TSZ(rd#DnVrRdU"F:nsVrRdU$/\G`!!!-#
rWW6!rWE&urr2j$!X/Q+nbra,!/%i.jnOE,ir9+1!.Y%Hf'r]i~>
m/I0V2Oa[Df(o?a2?9]Gc2[kCc2[kCc2n!kcM[qR"98MKc=7McLq*H0"Ci*CLq*H0$+2r%!!!)G
c32Ajc3)8jcHc@m!X&K)n]h?;2NPDpjnOE,ir9+L2Oa[Df'r]i~>
m/I1:W71eEf"V.coVgO7rVursrVursrWE6#rWE#mrWLT0=r*(rrXdG<=r*(rr\fE(rr)osrWW6!
rWW3"rr*#rm/@4?W2$K2g@9aoir9,0W71eEf'r]i~>
m/I0;!.Y%Hf"V.chZ/u4rVursrVursrWE6#rWE#mrWLT0=r*(rrXdG<=r*(rr\fE(rr)osrWW6!
rWW3"rr*#rm/@3_!041tg@9aoir9+1!.Y%Hf'r]i~>
m/I0V2Oa[Df"V.cju/=kc2[kCc2[kCc3+-mc2u2bc31+h8J6(Cc4Hst8J6'hc7EbQcHZF8c32Aj
c3;DlcHZL7m*5g&2O1D\g@9aoir9+L2Oa[Df'r]i~>
m/I10W5\f7f"M(bZaTi'rVursrVursrWE6#r][%LrW20equ-`]#QF])rr*)E!<2sW^]+39^B"94
5,%?B#-XqtVRFh:JcFa0"3IL\s8:I;s*t~>
m/I/q!*'!rf"M(b+=R*WrVursrVursrWE6#r][%LrW20equ-`]#QF])rr*)E!<2sW^]+39^B"94
5,%?B"uI-eVRFh:JcFa0"(2+.s8:I;s*t~>
m/I0>2L,9"f"M(b:Jf:+c2[kCc2[kCc3+-mc8(%Gc2kN]qp#>V#0tKqrlt\D!7&kAS,U\:RfLaZ
15"r=#$=<cVRFh:JcFa0"+iFUs8:I;s*t~>
mJd1crhohor78R]!p6K/o)8UhrVccsrVeSQrr0SJXSi"6;?$Ph_>O5)/,oNB_>O5)/,oMsR/Yu!
!<2u!-.;e@-NO/F]bmjKr52D&rWg6;V4XQhb3OWYj8T,Yrhohor7:B;J,~>
mJd1`rVurir78R]!k/;So)8UhrVccsrVeSQrr0SJXSi"6;?$Ph_>O5)/,oNB_>O5)/,oMsR/Yu!
!<2u!-.;e@-NO/F]bmjKr52D&rWd0<CS.E.b3OWYj8T,VrVurir7:B;J,~>
mJd1ar\acMr78R]!ld6Ko$.48rQYBCrQ[2!cH_gkN6KM;6-db>S]o<1,L4RuS]o<1,L4RXI*T,K
!7&lK*idMc+99^4R1kP'baq,kc3JVQI\3FAb3OWYj8T,Wr\acMr7:B;J,~>
mJd1+rhoh5r78O\!m[ajoDS^irVccsrVccsrr*qU63d`^\GlK;5Q_<kh#@;_5Q_<kh#@;n/H[&Y
!<2s$RiVYUQ3I9e"iEY3#=>rlrWp<<W2#u^[a48js5X+Z`r;n:_>MmRs*t~>
mJd/grVusie,@k1rrR->H1V"8!<)ls!<)ls!<2s<_a4S56Fd$o\3L-&;r%BO\3L-&;r%BOa"7B7
PQ:^^"e-P8,*iE3rWU2+#7!T*oDT#)!'R!IVRFh:JcFd1!*K4#!D(]<f'r]i~>
mJd05r\adqeG[t2rrS3=JFh%d!<(1C!<(1C!<17aSi$]V23lJ4Pr&OI6aP(`Pr&OI6aP(`U*KdW
GQ@`g"b%9i)gVYdc39ds"pQrMo?IV_2IN5NVRFh:JcFd1!.4]'!H6Kdf'r]i~>
mf*L?W3,'_W4r5]]DhuXW582ErW1Lrm/@@\ZDa^tTV8C!eE_\cjSoG5W3,'_W4r5]nGe"~>
mf*K@!"c1F!+l,[]DhtL!*87!rW1Lrm/@@B1]U.ATV8C!eE_\cjSoF6!"c1F!+l,[nGe"~>
mf*K[2FG/I2M1nY]Dhtk2J^+Fc2k!hm*5sH=#O`dTV8C!eE_\cjSoFQ2FG/I2M1nYnGe"~>
n,EgOW2o:bkGtPm`l?3s]Di#QWN4SJaSl?Phu;+bL$npKq8TD4W2-)YVRFY)n\5#-qrZMZfAc\2
rs[\]X6flLX/NVL`ln/Is*t~>
n,Ef_!!pXF`<uZg`l?3s]Di"l$kS?naSl?Phu;+bL$npKmV3%9Br%DoVRFY)n\5#-o='X-MlZHQ
rsY4o#flta$NP=h`ln/Is*t~>
n,Efu2E]Igcos@G`l?3s]Di#*5=(1NaNarb[Ebj*D!oRW`-4:,IAEO.VRFY)n\5#-p;WnsUqQK*
rsZ"f4Oa>W4uS>=`ln/Is*t~>
nGa!VWMneUq!lk4W40ahnbBcs#2dJ'W3+MBb5MOA-O1iLn,<CX`i8s9#c+,cUpA"olhJ9q!r16G
r29[ud.jpH%cF=biW&]Ce>T=T`QRfHo)F4~>
nG`un"9>A"q!jmW!.)W-nbBcs#2cmS!$-gZb5MOA-O1iLn,<gJD%$D2R&$CIUpA"olhJ9q!pb=E
qu?d,FR-74%\j.,VuQP^SHAk<`QRfHo)F4~>
nGa!,3B"JKq!kOI2N>]!nbBcs#2d(&2FrV?b0C-G+9id$n'2EOGs4,rSYVpNUpA"olhJ9q!q2RB
r&+U@OmfUU%^nAU])VQqY;eG"`QRfHo)F4~>
nc'0hXJj\Lq!mY0\u;UCbNeWm^&J3#g;^JuVuj`lqml\-o@UDJW!T>rT:i'e`nKbR_#Fi-ZDa_1
c-t%LWMm#_[/U^+XJj\Lq!mY0\u;UCbNeWmoDa=~>
nc'0H$ikq[q!mY0GQ8&@bNeWm^&JE)g;]%!!!>unp::/6hMs/f"%%pCUS"3VZGtVapqZn<^Dm4R
-u=BT0E_@i\$Z/HcO9f+s7l-Lj*1P1S]g<gq"+N9~>
nc'0R5;o)4q!mY0NA`ntbNeWm^&JE)g;]LW2E59:b.5e/]8TWG3+J2MUS"3VZGtVapqZn<btBrQ
:3M59;`H[)`jGaWffqBcs7l-Lj,GrZW6=Jrq"+N9~>
o)B<pYc,qKs7,C7h8Ze"X2a2bn\+r2q!6YV\ZD[,W2d\lqS3"Bo$t4JW2Z_oVPBfVU8YN?g?\OL
rrhSfW4;9nrWM`&W2\mtrr`8W\G5dt&bWB,d/X.9lJp^?Z`'qEj6H1+s*t~>
o)B<^(]\[Xs7,C7h8WUu&?b'Yn\+r2q!6YV\W8fU!!c>to"Y/:g4hEd!#&M$VPBfVU8YN?g?\OL
rrg(F!&2^rrWLe3!!KXIrr`.j0^JH4&`X].FT;C2lJp^?=o],:j6H1+s*t~>
o)B<d8N)_2s7,C7h8XXs6FPq>n\+r2q!6YV\X6\`2EGH=a1on4\V=*D2FDMTVPBfVU8YN?g?\OL
rrgS12H-mYc31RX2EA[orr`2*>jqCd&a;kFOoPIOlJp^?G;_drj6H1+s*t~>
oD]L%];WRFs7km>h:BQaZ)Fh[j6HEQrt"njhp9-EWhlSjW2dGUjnm*t"7k3eZM^X0VPKoXT:r'_
\]WXlpqZn4pop^BrV?Erc)@c+nbC6+"6m;/l1Y,jr4AoDrr;cYj5&;*`NcAEgZ.Soo`'F~>
oD]Ku2ulMHs7km>h:BQa4ochVj6HEQrt"njhp9-ET4Tb^!!b`1[e@c@(#"s[,QIfZ7=1`(Tq@sN
Uoh2Mg?\OLrrhX)!-%f1rWR3?'%QOIao;IX/3puDrtG,d!(d"eq!-nqf#>Tl!$ftQlM1#OJ,~>
oD]L"@Q&0&s7km>h:BQa@l@07j6HEQrt"njhp9-EU3]j$2EG,jUWUX&'tKEE9K"dsAVm1XTq@sN
Uoh2Mg?\OLrrhd]2L*IAc38JO7,%2+ao;If=_Q(RrtG3;2K/Nnq!-nqf#>U<2GhublM1#OJ,~>
o`#[*_Pk!@s7l-Eh:C0/f@@7,[+jX2pq?\8q!mJ"b.Y*\Una``rhot)bLl%Sdf'FKmblR,Wr8k-
WMl\iTq@sMU8Frr_9_$)pqQh2d\s\Qp\kB,W4L^EnbCB/"7O"6jn8WgrkbVDqZ$?\j5&;;f@SQ[
W3k=>nbDeWJ,~>
o`#['9E6d8s7l-Eh:C0/f?cMX3S*6\pq?\@q!mJ"b.Y*\T5Hq4!rr<EC6$$rdf(0]cDH(T#64`-
*`n)kUnOEQTVA<`ZGFlElhJ6p"+1)&p@nOj1&tY7jmi,jrr^S8/@5$L(&LZ)0^o&:n`/HVf%8O#
3<2(jj72p;s*t~>
o`#[(E].1ns7l-Eh:C0/f@"(c@G'W1pq?\@q!mJ"b.Y*\TmpK@3&WZeG)WB<d`rcTZ'p8K3]8lW
96$)iUnOEQTVA<`ZGFlElhJ6p".1uFb4j0c<Ao3Xjmi,jrr^qj=i9q4(&W)1>k7pin`/HVf%8O%
@5_QHj72p;s*t~>
o`#]SW3Z0tq!mY*f%/[;jQ,7%W4:[KnbBcs%J]JTg<.+7WhuMaU84[bW!9B7bLke>p>GoSp[7(p
dD)s^W;`\)V4jNSU84`i[_9i;jmi,\rr`/"W:'BY#On[k^sCp(ps8sBl*B"Ao`#]SW3Z0tq!mY*
f%/[;jQ,7%W4:[KnbDhXJ,~>
o`#\O!$(2%q!mY*f%/[;jQ+r1!);24nbBcs%f#SUg<.+7WhuMVBM0`_rW!-83GOSS`oc4Y#3sL:
P'BG;rW!]>*C="[>9=YQM4^H8Uo:E(]uo'jnbBm!"7lp$cM%5BecQ5dcen'`ci4*^*0:!hrt]hZ
*9[@sn`nr]f%oBIjOb:L:"nn&q"F`<~>
o`#\l2GEK;q!mY*f%/[;jQ,&d2K7F9nbBcs%f#SUg<.+7WhuMYHY?5?r\asH=E]XnXh`l?#/[X:
NdbA,r\bNR8n3"%FA#&7Oe8;@Uo:E(]uo'jnbBm!"84o:Z1cHJ[Q$CZcen'`ci4*l9W.g\rt^kX
9^MfSn`nr]f%oBIjP*3`E80[Jq"F`<~>
p&>l[W2]%]q!mY0f%/[;lLFE0f;bd`j6H10]DiK<n`AKI]sb&AUnaWUTVA3XVu*CtXKf@VajSYk
rnRS;q</1Dh;-i7e'#eHZ`a5)$)sl%WMlbmUnaX_T+.KjVl-JqZFRftg?%l"^&J/qW42Eu#.:D2
^!GU+cMmnBoD]ZYW2]%]q!mY0f%/[;lLFE0f;bd`j6H10p]#a~>
p&>ka!!L(:q!mY0f%/[;lLFE0[0H\Fj6H10]DiN=n`AKI]sb&AUnaWUOEFep/f"9a#7hk7=C?N+
P5_$Q](s&IVOjBFIt)-:6mEKF!!s,]4]cW`TqnHWrgsXI6jb37Xg5LRce@LNpq?\/ZN)QKrWY"U
FhQ,Yn]q.7p[eD%LB%Ils7l-Lj4;f;j6H1)lEpisB]JCJq"Of=~>
p&>l%2EB%^q!mY0f%/[;lLFE0^cOH-j6H10]DiN=n`AKI]sb&AUnaWUQ%O-e<CHaC#<ta\C2e]T
NrG7CV>7%sRZs%uJqA)]?V<fb2@2WE?u^gbUSOZYrgsXU@m*S`Xg5LRce@LNpq?\/__tBac3@3*
LV;$kn]q.7q=FV'TJegTs7l-Lj4;f;j6H1)lG,J>K]D@fq"Of=~>
pAZ&hWMnbTq!mY0h:CEBlLFZEn`nT%W6+)bnbB]q)Yijag<[UGZEL:*UnaZWT:_dMU8+N\Vl-Jn
m&1,fWi2kqW2]cp"K%uiTqM@V&"oYlUo(/qYIVKleDKT^pq-P-f;QsdrWg`IV6.GWjnQ[l)W7Tn
i;`TBlJp^Oh;dejq"XCEe#94ej6H10q#>j~>
pAZ&+"9>4sq!mY0h:CEBlLFZEn`ltR!/fh%nbB]q)u/sbg<[UGZEL:*UnaZWT:_[2H=g/Q4tIuo
"76'u%O<Mb9iPbNNhE8FUS4@ZT+@ThUSOceXF7Xqb1,>7nbB`r!fI%po_o)9!*QG;cfOr<ru.X8
!2KShn`o/jf%oBOnbDnLlD=X`MW=!lq"Xl>~>
pAZ&>3B">Gq!mY0h:CEBlLFZEn`mJ@2P/sonbB]q)u/sbg<[UGZEL:*UnaZWT:_^;LO!cQ?rToh
3:/U65Yb3_C2\KUQD:=QUS4@ZT+@ThUSOceXc9m-b1,>7nbB`r!i._toZd\f2KGq5cfOr<ru/=+
2R`Y\n`o/jf%oBOnbDnLlEiN/T&],+q"Xl>~>
p\u6#XJjhPq!mY0h:CEBlLFZLs7l-Lc)@Sgj6H10\c3?<na#)^b/V-'ZELC/VPKu]TqS.XTDkM^
U&C\dUSRjb!MlI_T`(JqTV8*TUo()mYHkUM`lcfglgab[rrUZJe+EVEe#9%:YJJr_fDbmLoBuVu
mB!8>s7l-Lj5&;Bj6H10s8VlalHQ9+dGsN^q"ar?~>
p\u5V$il@gq!mY0h:CEBlLFZLs7l-LNW90\j6H10\c3?<na#)^b/V-'ZELC/VPKu]TqS.XTDkJc
Oc56UrHJ?gHN&6oLl[OUSbJfUT+[fjU8+N]W3*8/[_9]'eDKT^ppg>*IfOujrWdoQ:SY,0g@<ep
!Vb@:ruB2j!0@0Tn`o/jf%oBOnbE.aq!mX0!!B4\lLFZFs*t~>
p\u5a5;oG>q!mY0h:CEBlLFZLs7l-LU,G!>j6H10\c3?<na#)^b/V-'ZELC/VPKu]TqS.XTDkJc
QBRMprIt?.M#N56Od;5rT(eoVT+[fjU8+N]W3*8/[_9]'eDKT^ppg>*RPocic3JqZC87uKg@<ep
!VtdDruBTS2Q-TMn`o/jf%oBOnbE.aq!mXD2E8/*lLFZFs*t~>
q#:lrYGf_Gq!mY0h:CEBlLFZJrsJPklGKR#eDoiapp^8;q!m\3g<[p]_7@"[YH=h)W2?AcU@t8\
T_k>]T_4ulU8+N]W2cu'ZF7<[`lcW]jm2I3rVluU`o[35rrUfNc1Lu?cD[S3VRFh:f`)$2Zd%t=
&+d!'c2[S/lJp^Oh;dejq#14"q!mXeW2eA-lLFZGs*t~>
q#:lZ'EDtLq!mY0h:CEBlLFZJrsJPkl=U)+\)ZcDpp^8;q!m\3g<[p]_7@"[YH=h)W2?AcU@t8\
T_k>]T_4ulU8+N]W2cu'ZF7<[`lcW]jm2I3rVlth=L7n<rrS)YD"R]3EWB77VRFh:f`)#J+DgNH
&).cuD#a;#lJp^Oh;dejq#14"q!mW^!"HEtlLFZGs*t~>
q#:lb7Q-2)q!mY0h:CEBlLFZJrsJPkl@_&\^uO_Mpp^8;q!m\3g<[p]_7@"[YH=h)W2?AcU@t8\
T_k>]T_4ulU8+N]W2cu'ZF7<[`lcW]jm2I3rVlu(Hc,cprrSuSG4a&bH8]f9VRFh:f`)#]:Q+]A
&*$,;MuWSBlJp^Oh;dejq#14"q!mX&2F,(<lLFZGs*t~>
q>V!%\u<ICs7,C7h:C0;lLFZHrsAJjlFs4$g#MAfZi:U3na#;oeBc:W_7dOm[^**=p8S.lVZ!:k
VPs0f&$<%@[^j)f`lcWWhrX1oq"apnqV8]kf(ZrZ!p-Aio_o*(W2?2^YK#\Xrr_Dp\EWJ^&,Nl9
_>O?'lJp^Of&Q&cq"t'tq!mX`W3Ft8lLF`NJ,~>
q>Uuo1]U)@s7,C7h:C0;lLFZHrsAJjl;[g1biA!YZi:U3na#;oeBc:W_7dOm[^**=p8S.lVZ!:k
VPs0f&$<%@[^j)f`lcWWhrX1oq"apnn!?!^LZcGP!j_kXo_o(L!,nL)YK#\Xrr]_e0Yd)V&+;=T
8GE/SlJp^Of&Q&cq"t'tq!mWL!%#kLlLF`NJ,~>
q>Uur?T)ius7,C7h:C0;lLFZHrsAJjl?>-ad,XE]Zi:U3na#;oeBc:W_7dOm[^**=p8S.lVZ!:k
VPs0f&$<%@[^j)f`lcWWhrX1oq"apnnuZA%T^9Gn!lHonoZd\?2M%KqYK#\Xrr^8J>g;a;&+X'c
Du'G'lJp^Of&Q&cq"t'tq!mWn2H%o^lLF`NJ,~>
q>UrIW4`'.q!-nqf%/[BnbDq[$2F&T[]$^lj6HECrsSVllfI$Xg=4Hk`khA^!P,Z;Z2qDEot:^K
`lcWWg>D#XnbDYS"RsjD_Y:nH"8f0ume6V[nG]?hT;&I(hq\57n]%caqsOIob,DuNs7km>h:C05
lLFZFrsAJjl*dP#h;deqqu;0~>
q>Uq8!'L#Uq!-nqf%/[BnbDq[$2F&Q3ri"2j6HECrsSVllfI$Xg=4Hk`khA^!P,Z;Z2qDEot:^K
`lcWWg>D#XnbDYS"Oh`89]s7l"7lp$cM%5CecQ2KT;&I(hq\57f2r`FoBuVgA,nads7km>h:C05
lLFZFrsAJjjuWQ;h;deqqu;0~>
q>UqY2J)^aq!-nqf%/[BnbDq[$2F&R@lA;^j6HECrsSVllfI$Xg=4Hk`khA^!P,Z;Z2qDEot:^K
`lcWWg>D#XnbDYS"PfA9EpUXB"84o:Z1cHK[Q$@AT;&I(hq\57hg7hgp?qqjKf3N<s7km>h:C05
lLFZFrsAJjk@6Dhh;deqqu;0~>
q>Up#an>]/n`/HVf%oBVq"Odpq!m:Sdam77nbB*`"ShNQjkodc!S#HoaoTi<p"BQ+jm2I3kl1\F
r4r=7d\s\Sp\kH.W2ZbhTrPf[h#@H(e,&G2%Jn3>s7l-Eh:C05j72p=rsAJji4IFgh;e&#r;V9~>
q>Uom@,q:^n`/HVf%oBVq"Odpq!lm2_UdQ'nbB*`"ShNQjkodc!S#HoaoTi<p"BQ+jm2I3kl1[i
pV?e2HiPksp\kFU!)9GcTrPf[h#@G'J+)b(%I[]bs7l-Eh:C05j72p=rsAJjePO-6h;e&#r;V9~>
q>UopK'E=1n`/HVf%oBVq"Odpq!m!Xa4B),nbB*`"ShNQjkodc!S#HoaoTi<p"BQ+jm2I3kl1\$
q8!"4Qo7mepWa%F2JT4aTrPf[h#@GCRe#hF%J#,8s7l-Eh:C05j72p=rsAJjfQ5*ah;e&#r;V9~>
p&><flJ:%Bh;deqp&>?nlJp^Of%oW]pmV3`q!n=K!V,rLrri,$W6YA;rX$fIW2ZbhTrPf[_#F]1
lJ:%Bh;deqp&>?nlJp^Of%oW]q#15C~>
p&><flJ:%Bh;deqp&>?nlJp^Of%oW]pmV3`q!n=K!V,rLrrhX)!-%f1rX!HB%@O2gTrPf[_#F]1
lJ:%Bh;deqp&>?nlJp^Of%oW]q#15C~>
p&><flJ:%Bh;deqp&>?nlJp^Of%oW]pmV3`q!n=K!V,rLrrhd]2L*IAc3\_R5G"jITrPf[_#F]1
lJ:%Bh;deqp&>?nlJp^Of%oW]q#15C~>
p&>'_lMfT@j6H1+rrV`Uj8Rj9lLFY#s5EtZil^T-rrhSfW4;9nrX85,W2Q\pW1Tfd_;E'.!V,IS
h#cX%nalb^n`o06h#cm3q#15C~>
p&>'_lMfT@j6H1+rrV`Uj8Rj9lLFY#s5EtZX(9,grrg(F!&2^rrX7:9!!@GEW1Tfd_;E'.!V,IS
h#cX%nalb^n`o06h#cm3q#15C~>
p&>'_lMfT@j6H1+rrV`Uj8Rj9lLFY#s5EtZ]nOhRrrgS12H-mYc3q'^2E6JkW1Tfd_;E'.!V,IS
h#cX%nalb^n`o06h#cm3q#15C~>
p&>'_lMffFlLFZ@rrVuclMffFlLFY#s5Et]qU`<acLK$V$gFdlW40^Y`k&1KW;`b"VOsWe`oZ7\
!Tit3rrMZTro4(FnbD_U!r2<Nro4(FnbE(_J,~>
p&>'_lMffFlLFZ@rrVuclMffFlLFY#s5Et]mu'"BE7`53'Yb7\!%_)6=#:-^%<neNVOsWe`oZ7\
!OL\ArrMZTro4(FnbD_U!r2<Nro4(FnbE(_J,~>
p&>'_lMffFlLFZ@rrVuclMffFlLFY#s5Et]nt]VdNo.tY'[9!'2Gn-mC/m;Q5DQ5;VOsWe`oZ7\
!Q4'XrrMZTro4(FnbD_U!r2<Nro4(FnbE(_J,~>
p&>$^rTXFRq!n@bq!n=J!V,r9s5*bYnBS/Pn]Lk5k.@ViW;W\"W2#u^[a4:$rr_Ss\F&5S!:g$Q
!V,sTrrMobrTXFRq#15C~>
p&>$^rTXFRq!n@bq!n=J!V,r9s5*bYdr5jhf?4*pfjtKf!"0',RAm0aUS"Elcg0]m"2cfHbM`C#
nbq_RnbDYS!Vl3`l2gGMrVqB~>
p&>$^rTXFRq!n@bq!n=J!V,r9s5*bYgl=a?hobs#h17]d2@DT]SuJ]fUS"Elcg0]m"41:Xf&6Q.
nbq_RnbDYS!Vl3`l2gGMrVqB~>
p&Fs[!;Q$_!;QK^!;M-Fh#@HTiqggt'CjelXer%oVPpJnW2?>^UT_Mfn`ToToZ"5rrnmbRnGr@Q
rrDilnGr@_s*t~>
p&Fs[!;Q$_!;QK^!;M-Fh#@HQX7+42'CjedHRo.JDk3l,W2?>^UT_Mfn`ToThcM#!qqqGOnGr@Q
rrDilnGr@_s*t~>
p&Fs[!;Q$_!;QK^!;M-Fh#@HR^%0>G'CjegMc;T6JY9!AW2?>^UT_Mfn`ToTk'Kn8r87PPnGr@Q
rrDilnGr@_s*t~>
p&FsbmJm(YJcDJE$M``/]rJ$+T:_dLrgs@hXhE'Bpuq_[cEPdph>dEJmJm(YrVqB~>
p&FsbmJm(YJcDJE$M``/]iG,/T:_dLrgs@hXhE'Bpuq_[E#=)rh>dEJmJm(YrVqB~>
p&FsbmJm(YJcDJE$M``/]l>rFT:_dLrgs@hXhE'Bpuq_[N_,]5h>dEJmJm(YrVqB~>
JcD_L!pIbldJjgKhp8U,Vk]ZIR$jD?YJ&9>n`ToQh>"M[J,~>
JcD_L!kVjXdJjgKhp5U/;kobJR$jD?YJ&9>n`ToQS*RTaJ,~>
JcD_L!m-&@dJjgKhp6R+DPNVeR$jD?YJ&9>n`ToQYj]+%J,~>
nc&[ks8N(Ms1SF9qqf&reb.Gl&bt\L];Vp:T:DFDTshe`hs:'@rr<&fs*t~>
nc&[ks8N(Ms1SF9o:J9!KB(J]&bt\LB)l/>T:DFDTshe`hs:'@rr<&fs*t~>
nc&[ks8N(Ms1SF9p9SF8Sa"Z(&bt\LJi7eUT:DFDTshe`hs:'@rr<&fs*t~>
q#:DZ&X31n!s&B$!.k0]rrhi7ZFfJRrt"np`i-MhXJi)!Yg;%bnbBs#r;Zp'(hW(,rr<&fs*t~>
q#:DZ&X31n!s&B$!.k0]rrgea*&u!hrt"npEWBalXJi)!Yg;%bnbBs#r;Zp'(hW(,rr<&fs*t~>
q#:DZ&X31n!s&B$!.k0]rrh,59OcK$rt"npNAcC.XJi)!Yg;%bnbBs#r;Zp'(hW(,rr<&fs*t~>
q#:CRW&+DP!!%TM[Jp=-i:#.p$dbiojh\XD_8km3q!A"^mE,@Zrr<&trrV>(3;3@P!:Tr<~>
q#:CRW&+DP!!%TM[Jp=*UX.Eh$[_qsjh\XD_8km3q!A"^b@(5!rr<&trrV>(3;3@P!:Tr<~>
q#:CRW&+DP!!%TM[Jp=+[b9.0$^Wc5jh\XD_8km3q!A"^eq3hOrr<&trrV>(3;3@P!:Tr<~>
q>UTJ5lWakrr3'#s8N)urrfS]"Wj97rrfS[#8VW:s.oYpl1NO)VuY<E^\cI[l14iWn]%lirn%/M
!;lcr#QF`+S0A+\RK!3i!1Y>?+JSN.J,~>
q>UTJ5lWakrr3'#s8N)urrfS]"Wj97rrfS[#8VW:s.oYp^\**U!!%NHC\rS]^[hC-f2s#Zqq(iJ
!;lcr#QF`+S0A+\RK!3i!1Y>?+JSN.J,~>
q>UTJ5lWakrr3'#s8N)urrfS]"Wj97rrfS[#8VW:s.oYpc1RP)2?93DLARD>c1:l;hg8&!r7CrK
!;lcr#QF`+S0A+\RK!3i!1Y>?+JSN.J,~>
q>US?WrJK0rr3Z4s8N*!s0Wofs5mejs1(,8s6*7-JcERd!qjOokl1Y3k,8:/o`#'gc*,^pec,UL
rVmSmF$fp,W$TpmcQ]1brr=@os3i9tp&BO~>
q>US?WrJK0rr3Z4s8N*!s0Wofs5mejs1(,8s6*7-JcERd!om7dkl1X4jo>C2o`#'WC_VZnec,UL
rVmSmF$fp,W$TpmcQ]1brr=@os3i9tp&BO~>
q>US?WrJK0rr3Z4s8N*!s0Wofs5mejs1(,8s6*7-JcERd!pOsIkl1XOju*4.o`#'\MajB1ec,UL
rVmSmF$fp,W$TpmcQ]1brr=@os3i9tp&BO~>
qYq-*0E(qB*<#p;!<<'!s8P9pqu6[\fDbdNnJHiW_>a]9e[Dd2o]uMWl1NO)VuY<E^\cI[l1P&Y
kciG/e,T:F"WHD#s#@gnrrp+ps8N)7rr3#94SA`)~>
qYq-*0E(qB*<#p;!<<'!s8P9pqu6[\fDbdNnJHiW_>a]1K.%^ahs::B^\**U!!%NHC\rS]^\.U/
],5YUe,T:F"WHD#s#@gnrrp+ps8N)7rr3#94SA`)~>
qYq-*0E(qB*<#p;!<<'!s8P9pqu6[\fDbdNnJHiW_>a]4SQ8FLk3N$Ic1RP)2?93DLARD>c1V)=
a[qW`e,T:F"WHD#s#@gnrrp+ps8N)7rr3#94SA`)~>
qYpT*IfB?T@($%7!<<'!s8N9$qu?U"!X/Q+JcEIa"RFI>^?skY"4O3ts8:I9rrN&ldJj1HrVm)_
>%7O3"o\H#"oSc-rrE#srrN')p&BO~>
qYpT*IfB?T@($%7!<<'!s8N9$qu?U"!X/Q+JcEIa"N,I$5g/*c"+L<#s8:I9rrMlWdJj1HrVm)_
>%7O3"o\H#"oSc-rrE#srrN')p&BO~>
qYpT*IfB?T@($%7!<<'!s8N9$qu?U"!X/Q+JcEIa"ONE*B\),A".D-:s8:I9rrMr^dJj1HrVm)_
>%7O3"o\H#"oSc-rrE#srrN')p&BO~>
qYpOqqu@($s8N*!rrE*!#Q=Q"!<`4Ms0r"0roNP3rr_fVj5'Xa"2CeLf)2oNecaghlLFYkrr<&s
rrr3+s8N9$rr30#$31#*rVlftr<`/sJ,~>
qYpOqqu@($s8N*!rrE*!#Q=Q"!<`4Ms0r"0qm4K1rr_fVj5'Xa")@mPf)2oNecaghlLFYkrr<&s
rrr3+s8N9$rr30#$31#*rVlftr<`/sJ,~>
qYpOqqu@($s8N*!rrE*!#Q=Q"!<`4Ms0r"0r57+Mrr_fVj5'Xa",8^gf)2oNecaghlLFYkrr<&s
rrr3+s8N9$rr30#$31#*rVlftr<`/sJ,~>
qu6`c%Jg#'$i:D$rrE*!!<<'_\blC-5,*W.SGr]]lJp^uecW[c^Xi=iq:5`*j6H10pr`U:!;uj#
o`tNs2Sf=l"i*%rrrBk6rrKu!p&BO~>
qu6`c%Jg#'$i:D$rrE*!!<<'_\blC-5,*W.SGr]]lJp^uecT[fCY&Ejq:5`*j6H10lcT5-!;uj#
o`tNs2Sf=l"i*%rrrBk6rrKu!p&BO~>
qu6`c%Jg#'$i:D$rrE*!!<<'_\blC-5,*W.SGr]]lJp^uecUXbL=Z:0q:5`*j6H10n&kY1!;uj#
o`tNs2Sf=l"i*%rrrBk6rrKu!p&BO~>
qu6\\@f66B@[$fls8N*!s0Wofs5[nos1T>PrVaZ3JcE+W"8pF.rpp'kn`o/jr71tbW5$p][/&8V
h;deX\]N"o!!)ut']N;&s8Sr?dJqT:VZ6Yr-.2bA-a34CJ,~>
qu6\\@f66B@[$fls8N*!s0Wofs5[nos1T>PrVaZ3JcE+W"8?WHqssahn`o/jr71sc!,L0`[/&8V
h;de.2IO]-!!)ut']N;&s8Sr?dJqT:VZ6Yr-.2bA-a34CJ,~>
qu6\\@f66B@[$fls8N*!s0Wofs5[nos1T>PrVaZ3JcE+W"8Rl"r:9jin`o/jr71t)2MTf\[/&8V
h;de<@"QQ$!!)ut']N;&s8Sr?dJqT:VZ6Yr-.2bA-a34CJ,~>
qu6[T_YjE<_DD2ls8N)urrfMY"!4'5rrp>-#7!T+JcE+W"od5bZd/+5rr_fVj5'Xa"2CeLf)2oN
ecjm_\Zsrhb5_D?!s9B[p&=soS0@qWSGrNl!1>#5,,4`0J,~>
qu6[T_YjE<_DD2ls8N)urrfMY"!4'5rrp>-#7!T+JcE+W"oFK7+E5s)rr_fVj5'Xa")@mPf)2oN
ecjmE4s$?nb5_D?!s9B[p&=soS0@qWSGrNl!1>#5,,4`0J,~>
qu6[T_YjE<_DD2ls8N)urrfMY"!4'5rrp>-#7!T+JcE+W"oPB':QF?)rr_fVj5'Xa",8^gf)2oN
ecjmMAkfT/b5_D?!s9B[p&=soS0@qWSGrNl!1>#5,,4`0J,~>
JcC]/"Rs^<^?G:j"7b[:h>Gh+^Snour3Z=O"jjoaamSr!s*t~>
JcC]/"Oh;u5eHai"7b[:h>Gh+CB//$r3Z=O"h7JDFlrIos*t~>
JcC]/"Pf#%BZ]uJ"7b[:h>Gh+L,Oe;r3Z=O"i,7KOQlP8s*t~>
JcCW-!rh?JmJd:;W71eEf)#CJe]RZ[s*t~>
JcCW-!rKIEmJd9<!.Y%Hf)#CJK4O;@s*t~>
JcCW-!rU'bmJd9W2Oa[Df)#CJSV?O*s*t~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\pAW71eEes?>d~>
JcC<$oD\oB!.Y%Hes?>d~>
JcC<$oD\o]2Oa[Des?>d~>
JcC<$oD\p7W5\f7es?>d~>
JcC<$oD\o#!*'!res?>d~>
JcC<$oD\oE2L,9"es?>d~>
JcC<$o`"pjrhohor7726J,~>
JcC<$o`"pgrVurir7726J,~>
JcC<$o`"phr\acMr7726J,~>
JcC<$o`"p2rhoh5r7726J,~>
JcC<$o`"nnrVusie,@j`s*t~>
JcC<$o`"o<r\adqeG[sas*t~>
JcC<$p&>6FW3,'_W4r5]QN)I~>
JcC<$p&>5G!"c1F!+l,[QN)I~>
JcC<$p&>5b2FG/I2M1nYQN)I~>
JcC<$pAYQVW2o:bkGtPm`l?3sQiDR~>
JcC<$pAYPf!!pXF`<uZg`l?3sQiDR~>
JcC<$pAYQ'2E]Igcos@G`l?3sQiDR~>
JcC<$p\t`]WMneUq!lk4W40ahnbALOJ,~>
JcC<$p\t_u"9>A"q!jmW!.)W-nbALOJ,~>
JcC<$p\t`33B"JKq!kOI2N>]!nbALOJ,~>
JcC<$q#:ooXJj\Lq!mY0\u;UCbNeWmRK%d~>
JcC<$q#:oO$ikq[q!mY0GQ8&@bNeWmRK%d~>
JcC<$q#:oY5;o)4q!mY0NA`ntbNeWmRK%d~>
JcC<$q>V'"Yc,qKs7,C7h8Ze"X2a2bnX9E-~>
JcC<$q>V&e(]\[Xs7,C7h8WUu&?b'YnX9E-~>
JcC<$q>V&k8N)_2s7,C7h8XXs6FPq>nX9E-~>
JcC<$qYq6,];WRFs7km>h:BQaZ)Fh[j6HE.s*t~>
JcC<$qYq6'2ulMHs7km>h:BQa4ochVj6HE.s*t~>
JcC<$qYq6)@Q&0&s7km>h:BQa@l@07j6HE.s*t~>
JcC<$qu7E1_Pk!@s7l-Eh:C0/f@@7,[+jX2pm_;6~>
JcC<$qu7E.9E6d8s7l-Eh:C0/f?cMX3S*6\pm_;6~>
JcC<$qu7E/E].1ns7l-Eh:C0/f@"(c@G'W1pm_;6~>
JcC<$qu7GZW3Z0tq!mY*f%/[;jQ,7%W4:[KnbAXSJ,~>
JcC<$qu7FV!$(2%q!mY*f%/[;jQ+r1!);24nbAXSJ,~>
JcC<$qu7Fs2GEK;q!mY*f%/[;jQ,&d2K7F9nbAXSJ,~>
JcC<$r;RVbW2]%]q!mY0f%/[;lLFE0f;bd`j6H10Sc=3~>
JcC<$r;RUh!!L(:q!mY0f%/[;lLFE0[0H\Fj6H10Sc=3~>
JcC<$r;RV,2EB%^q!mY0f%/[;lLFE0^cOH-j6H10Sc=3~>
JcC<$rVmeoWMnbTq!mY0h:CEBlLFZEn`nT%W6+)bnbA^UJ,~>
JcC<$rVme2"9>4sq!mY0h:CEBlLFZEn`ltR!/fh%nbA^UJ,~>
JcC<$rVmeE3B">Gq!mY0h:CEBlLFZEn`mJ@2P/sonbA^UJ,~>
JcC<$rr3u*XJjhPq!mY0h:CEBlLFZLs7l-Lc)@Sgj6H10TDsE~>
JcC<$rr3t]$il@gq!mY0h:CEBlLFZLs7l-LNW90\j6H10TDsE~>
JcC<$rr3th5;oG>q!mY0h:CEBlLFZLs7l-LU,G!>j6H10TDsE~>
JcC<$s8NW$YGf_Gq!mY0h:CEBlLFZJrsJPklGKR#eDoiapn7Y;~>
JcC<$s8NVa'EDtLq!mY0h:CEBlLFZJrsJPkl=U)+\)ZcDpn7Y;~>
JcC<$s8NVi7Q-2)q!mY0h:CEBlLFZJrsJPkl@_&\^uO_Mpn7Y;~>
JcC<$&HDU<W58E3n`o/jf%/mHnbE"]$2F&U^8S9hj6H0-s*t~>
JcC<$&HD2U!(ckan`o/jf%/mHnbE"]$2F&U<<+>hj6H0-s*t~>
JcC<$&HD<.2K/Ekn`o/jf%/mHnbE"]$2F&UG;`@Gj6H0-s*t~>
JcC<$&-'eN]_qg)lJp^Of%oW]q"aprq!mUWW4:[DlM.(QJ,~>
JcC<$&-$/?4SSmNlJp^Of%oW]q"aprq!mK.!);;0lM.(QJ,~>
JcC<$&-%>AAblB$lJp^Of%oW]q"aprq!mNW2K7L4lM.(QJ,~>
JcC<$%fcCIp&FgWj5&;;h;e&#p\tQpn_h%-f%oBVpnIe=~>
JcC<$%fc!*j8\oEj5&;;h;e&#p\tQpn^UIQf%oBVpnIe=~>
JcC<$%fc*Ol2UPKj5&;;h;e&#p\tQpn^qp'f%oBVpnIe=~>
JcC<$r;R&mlJ:%Bh;deqp&>?nlJp^Of%oW]pnRk>~>
JcC<$r;R&mlJ:%Bh;deqp&>?nlJp^Of%oW]pnRk>~>
JcC<$r;R&mlJ:%Bh;deqp&>?nlJp^Of%oW]pnRk>~>
JcC<$r;QfflMfT@j6H1+rrV`Uj8Rj9lLFYEs*t~>
JcC<$r;QfflMfT@j6H1+rrV`Uj8Rj9lLFYEs*t~>
JcC<$r;QfflMfT@j6H1+rrV`Uj8Rj9lLFYEs*t~>
JcC<$r;QfflMffFlLFZ@rrVuclMffFlLFYEs*t~>
JcC<$r;QfflMffFlLFZ@rrVuclMffFlLFYEs*t~>
JcC<$r;QfflMffFlLFZ@rrVuclMffFlLFYEs*t~>
JcC<$r;QcerTXFRq!n@bq!n=J!V,r[s*t~>
JcC<$r;QcerTXFRq!n@bq!n=J!V,r[s*t~>
JcC<$r;QcerTXFRq!n@bq!n=J!V,r[s*t~>
JcC<$r;Z]b!;Q$_!;QK^!;N>hJ,~>
JcC<$r;Z]b!;Q$_!;QK^!;N>hJ,~>
JcC<$r;Z]b!;Q$_!;QK^!;N>hJ,~>
JcC<$r;Z]imJm(YU]5i~>
JcC<$r;Z]imJm(YU]5i~>
JcC<$r;Z]imJm(YU]5i~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$rVu]oMZ82~>
JcC<$rVu]oMZ82~>
JcC<$rVu]oMZ82~>
JcC<$rVlitK`?Q~>
JcC<$rVlitK`?Q~>
JcC<$rVlitK`?Q~>
JcC<$rVlitqu6`f&c;S-#l>/#s043^(4)DKJ,~>
JcC<$rVlitqu6`f&c;S-#l>/#s043^(4)DKJ,~>
JcC<$rVlitqu6`f&c;S-#l>/#s043^(4)DKJ,~>
JcC<$rVlitqYpRlVZ-T%Uf%0&37%Wg:O\c4J,~>
JcC<$rVlitqYpRlVZ-T%Uf%0&37%Wg:O\c4J,~>
JcC<$rVlitqYpRlVZ-T%Uf%0&37%Wg:O\c4J,~>
JcC<$rVu]o!roMqrr3*kZ2[#>rr3#h(n1JT~>
JcC<$rVu]o!roMqrr3*kZ2[#>rr3#h(n1JT~>
JcC<$rVu]o!roMqrr3*kZ2[#>rr3#h(n1JT~>
JcC<$rVlitqYpg!(?YYZ(B+:3#6G&/".K7?~>
JcC<$rVlitqYpg!(?YYZ(B+:3#6G&/".K7?~>
JcC<$rVlitqYpg!(?YYZ(B+:3#6G&/".K7?~>
JcC<$rVlitq>U_=LB!'Hs8N2uQ2c@~>
JcC<$rVlitq>U_=LB!'Hs8N2uQ2c@~>
JcC<$rVlitq>U_=LB!'Hs8N2uQ2c@~>
JcC<$rVlitq>U`H.K:pBs8P9WQ2c@~>
JcC<$rVlitq>U`H.K:pBs8P9WQ2c@~>
JcC<$rVlitq>U`H.K:pBs8P9WQ2c@~>
JcC<$rVlitq#:CGOY-S4#/1qVrVaZ3Rf@m~>
JcC<$rVlitq#:CGOY-S4#/1qVrVaZ3Rf@m~>
JcC<$rVlitq#:CGOY-S4#/1qVrVaZ3Rf@m~>
JcC<$rVu]orr3%S%u^Dd"iN_4#=>tis*t~>
JcC<$rVu]orr3%S%u^Dd"iN_4#=>tis*t~>
JcC<$rVu]orr3%S%u^Dd"iN_4#=>tis*t~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
JcC<$JcGcMJ,~>
%%EndData
showpage
%%Trailer
end
%%EOF
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

@@ -0,0 +1,628 @@
%!PS-Adobe-3.0 EPSF-3.0
%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
%%Title: alicenfriends2.eps
%%CreationDate: Fri Aug 14 20:45:18 2015
%%DocumentData: Clean7Bit
%%LanguageLevel: 2
%%Pages: 1
%%BoundingBox: 14 14 581 114
%%EndComments
%%BeginProlog
% Use own dictionary to avoid conflicts
10 dict begin
%%EndProlog
%%Page: 1 1
% Translate for offset
14.173228346456694 14.173228346456694 translate
% Translate to begin of first scanline
0 98.998898194262807 translate
565.99370078740162 -98.998898194262807 scale
% Image geometry
566 99 8
% Transformation matrix
[ 566 0 0 99 0 0 ]
% Strings to hold RGB-samples per scanline
/rstr 566 string def
/gstr 566 string def
/bstr 566 string def
{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
true 3
%%BeginData: 33278 ASCII Bytes
colorimage
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
mf*Lgk1A)6iqn<,LAqJTk1A)6iqn<,KDu/Qk1A)6iqr*BJ,~>
mf*L_[u5`oX7LoBLAqJL[u5`oX7LoBKDu/I[u5`oX7P]XJ,~>
mf*Lb`h]'!^%R$WLAqJO`h]'!^%R$WKDu/L`h]'!^%UgmJ,~>
nG`Rbe#`JG!j'cDJcCW-!r16Gr29[ud.i1lL]7GNe#`JG!j'cDmf.e~>
nG`RUIgl5T!Xs2:JcCW-!pb=Equ?d,FR+MXL]7GAIgl5T!Xs2:mf.e~>
nG`RYRQp^.!^Dh9JcCW-!q2RBr&+U@Omdl$L]7GERQp^.!^Dh9mf.e~>
nc'$^ZDa_1c-t%LWMm#_JcC]/%-b'rW4't"d($'RYNCE9M>n"KZDa_1c-t%LWMm#_n,In~>
nc'$4)uoseCk$O\"99.FJcC]/%)3b^!%;Z5F>*gt'XrYnM>n"!)uoseCk$O\"99.Fn,In~>
nc'$B9/\\'G(P'>3&X7GJcC]/%*_F'2GSFIHW)m^7`L#ZM>n"/9/\\'G(P'>3&X7Gn,In~>
o)AjWXJiYsr;Hig];V^jJcCc1"R3"d\F0:o"Rs%%WTA^2MuNqEXJiYsr;Hig];V^jnGe"~>
o)Aj-$ii!,r;HiM2uj)fJcCc1"MY?F0[BUq"P,O4"LE[ZMuNpp$ii!,r;HiM2uj)fnGe"~>
o)Aj;4uQ?Er6>GR=>i1WJcCc1"O0+g;mH8G"L_Dj3Q$FJMuNq)4uQ?Er6>GR=>i1WnGe"~>
oD\sgYc,kGq>LQBW3,1OpjrI.rri,$W6YA;rWUQFXl]*JJcCf2"Sf4"cMdS<"jO!Jjmi-;s*t~>
oD\sZ(]\OOq>LP9!#!iPpjrI.rrhX)!-%f1rWR3?'%QOIJcCf2"R?m)E;KD0"`FARea`G+s*t~>
oD\s^82b_:q9B.h2FQ9hpjrI.rrhd]2L*IAc38JO7,%2+JcCf2"Rel>Gg-!/"aN!og@=t0s*t~>
oD\p@W4;a!rW]m2]%Z99pjrI0rr^]J\GQ%"#.UVCg?%l"JcCl4"4F-XqtL'o\>[!mjmi-=s*t~>
oD\o?!&4-@rWY+X?J=N2pjrI0rr[WK0_Y8?"u60bg?%l"JcCl4"+1)&p@nOj1&tY7jmi-=s*t~>
oD\o[2H.cmc3@9,HeRTOpjrI0rr\WH;p+j[#$!R.g?%l"JcCl4".1uFb4j0c<Ao3Xjmi-=s*t~>
o`#$jXJkR\rWhqkWPHj;lhH&2QN%'aXJkR\rWhqkWPHj;lhH&2PQ(a^XJkR\rWhqkWPHj;lhL2R
J,~>
o`#$b$inTHrWh!V$+8+:lhH&2QN%'Y$inTHrWh!V$+8+:lhH&2PQ(aV$inTHrWh!V$+8+:lhL2R
J,~>
o`#$e4uT\>c3Ld;4Li_plhH&2QN%'\4uT\>c3Ld;4Li_plhH&2PQ(aY4uT\>c3Ld;4Li_plhL2R
J,~>
o`#!SW42Eu#.:D2^!GU+JcD&9!p-Aio_o'&W3!\Yhsl9tPQ(^GW42Eu#.:D2^!GU+pA]X~>
o`#!!!%ddA"tot!^!GU+JcD&9!j_kXo_o%J!-Q!$hsl9tPQ(]j!%ddA"tot!^!GU+pA]X~>
o`#!12Gs+l##dF8^!GU+JcD&9!lHonoZdY>2M\ulhsl9tPQ(^%2Gs+l##dF8^!GU+pA]X~>
o`#!EW6Ou6#L99EXhE'BpjrI3rrUfNc1Lu?cD[SA^!Gg8JcCu7!nXBqo_o*@W26W0cfOrrs*t~>
o`"uN!,V<,#BBfIXhE'BpjrI3rrS)YD"R]3EWB7E^!Gg8JcCu7!fI%po_o)9!*QG;cfOrrs*t~>
o`"ug2Kn`<#C8:KXhE'BpjrI3rrSuSG4a&bH8]fG^!Gg8JcCu7!i._toZd\f2KGq5cfOrrs*t~>
o`#!AW71D<#LfWHUT_MfnU^_,rrUZJe+EVEe#9%:YJJr_JcCu7!n4*so_o*EW2$,k_:nGRs*t~>
o`"uB!.XY?#Cc_LUT_MfnU^_,rrRZMJ+W^FIfN9>YJJr_JcCu7!e13"o_o)F!)KAn_:nGRs*t~>
o`"u]2M1SH#D+jKUT_MfnU^_,rrSWIK(R=nJi7A=YJJr_JcCu7!h)$!oZd\n2JT"j_:nGRs*t~>
o`#!EW6Ou6#L99ET;K'Cl[f)&rrUfNc1Lu?cD[S3VRFh:JcCu7!nXBqo_o*@W26,`[a4:6s*t~>
o`"uN!,V<,#BBfIT;K'Cl[f)&rrS)YD"R]3EWB77VRFh:JcCu7!fI%po_o)9!*Pqk[a4:6s*t~>
o`"ug2Kn`<#C8:KT;K'Cl[f)&rrSuSG4a&bH8]f9VRFh:JcCu7!i._toZd\f2KGFe[a4:6s*t~>
o`#!SW42Eu#I^S.T;/U0jamGurrV;\\+KY)[]$'qUT_]!JcCu7!p-Aio_o*(W2?2^YK#\us*t~>
o`#!!!%ddA#;?-rT;/U0jamGurrTM,/G/oH0E?$`UT_]!JcCu7!j_kXo_o(L!,nL)YK#\us*t~>
o`#!12Gs+l#?*O3T;/U0jamGurrU(r;=r-=;`93!UT_]!JcCu7!lHonoZd\?2M%KqYK#\us*t~>
o`#$jXJkR\rW)HeW!&`eXhinGs-N`fqlHaapAP']rhoslU928hJcCu7"8f0ume6V[nG]?hT;&I(
htR/$~>
o`#$b$inTHrWq'W#b@T^XhinGs-N`fo*bWEpAP?K!skHiU928hJcCu7"7lp$cM%5CecQ2KT;&I(
htR/$~>
o`#$e4uT\>c3Uj<4.r4?XhinGs-N`fp-(C>p<ErP3'3e*U928hJcCu7"84o:Z1cHK[Q$@AT;&I(
htR/$~>
oD\p@W4;g#rWp$4WMuPaWkI,;s-EZed\s\Sp\kH.W2ZbhTrPf[JcCr6"4F-XrV-9s\>ZC+TV8C!
g@tVt~>
oD\o?!&49DrWk@]9rXeZWkI,;s-EZeHiPksp\kFU!)9GcTrPf[JcCr6"+1)&qY0sp2#p@MTV8C!
g@tVt~>
oD\o[2H.ioc3RH/C8ml"WkI,;s-EZeQo7mepWa%F2JT4aTrPf[JcCr6".1uFbkKBg<]4]lTV8C!
g@tVt~>
oD\sgYc,kGq>L]EW2Q\pTV8C!g4B9irri,$W6YA;rX$fIW2ZbhTrPf[JcCr6"Sf4"cMdS<$-]?H
WMuPaWkI-\s*t~>
oD\sZ(]\OOq>L\<!"G?qTV8C!g4B9irrhX)!-%f1rX!HB%@O2gTrPf[JcCr6"R?m)E;KD0$#T_P
RAljQWkI-\s*t~>
oD\s^82b_:q9B:k2F!e4TV8C!g4B9irrhd]2L*IAc3\_R5G"jITrPf[JcCr6"Rel>Gg-!/$$\?m
SuJBVWkI-\s*t~>
o)AjWXJiYsr;I)n\u;R,WMlG`XhinGs-<Tel)^hnmem%ln@GQsWMuhgU928hJcCo5"R3"d\F0:o
$h1a+W2ZbpT;&I(htR/$~>
o)Aj-$ii!,r;I)T2ZNr(WMlG`XhinGs-<Te^C1)rcM[YLei*R2GH%O5U928hJcCo5"MY?F0[BUq
$e@6:"*^`CT;&I(htR/$~>
o)Aj;4uQ?Er6>\Y=#N$nWMlG`XhinGs-<Tebs"$aZ2DlT[T>@HLT.5EU928hJcCo5"O0+g;mH8G
$as+p3/=K3T;&I(htR/$~>
nc'!]Xf/2-`4a1%Wi2lqW<K/kUT_]!JcCu7$gFdlW40^Y`k&1KW;`b"VOsWe`oVdOOoGgRXf/2-
`4a1%Wi2lqW<K/kUT_]!p]#a~>
nc'<<&c_n_<'OG@#653WWMubeUT_]!JcCu7'Yb7\!%_)6=#:-^%<neNVOsWe`oVdOOoH-1&c_n_
<'OG@#653WWMubeUT_]!p]#a~>
nc'<J6T-i"BO#:P3]93XWMubeUT_]!JcCu7'[9!'2Gn-mC/m;Q5DQ5;VOsWe`oVdOOoH-?6T-i"
BO#:P3]93XWMubeUT_]!p]#a~>
nG`RP];b\+rM^""US"Elcg-/^PQ(^H];b\+rM^""US"Elcg-/^OT,CE];b\+rM^""US"Elcg1?*
J,~>
nG`RCAI887$OBIpWMuhkT;K'Cl[f)"rrUkb$2ac2#Yg_dWMlSaVRFh:JcCi3!nh,kqu@*19pqu[
W2#u^[a4:6s*t~>
nG`RGJNWig$Ti*pWMuhkT;K'Cl[f)"rrV#-4T%)F4)1@dWMlSaVRFh:JcCi3!o8Dir&+pEC7_E(
W2#u^[a4:6s*t~>
nGa0lg;'hfW2?GjWMuhnUS"<b_:nF1s-*Hrn_1d\VPpAhW2ZbpVPBcYYJJr_JcCi3'CjelXer%o
VPpJnW2?>^UT_Mfnb<'6~>
nGa0lg:2J\!%`&ZWMuhnUS"<b_:nF1s-*Hrn_1L"/-%5>SZ/TeVPBcYYJJr_JcCi3'CjedHRo.J
Dk3l,W2?>^UT_Mfnb<'6~>
nGa0lg:N8B2H57#WMuhnUS"<b_:nF1s-*Hrn_1U5<&R^,TW+ohVPBcYYJJr_JcCi3'CjegMc;T6
JY9!AW2?>^UT_Mfnb<'6~>
nG`jjjj1W?W2#uWTV2:V"f&35cfOqQs-*HipuTi8Wi2\bT:hkVT*;!l^!Gg8JcCi3$M``/]rJ$+
T:_dLrgs@hXhE'Bq"Of=~>
nG`jjjj1V@!)K5ZTV2:V"f&35cfOqQs-*HipuTi8<WGpfT:hkVT*;!l^!Gg8JcCi3$M``/]iG,/
T:_dLrgs@hXhE'Bq"Of=~>
nG`jjjj1V[2JSkVTV2:V"f&35cfOqQs-*HipuTi8EAhR(T:hkVT*;!l^!Gg8JcCi3$M``/]l>rF
T:_dLrgs@hXhE'Bq"Of=~>
n,F!ihp8U,Vk]ZIR$jD?YJ&9>nU^_&rt4eYca'LIUR@L:R@:"V^!GU+JcCc1&b4c)ZDa[rR@'A0
R\m:%cen9ds*t~>
n,F!ihp5U/;kobJR$jD?YJ&9>nU^_&rt4eYcX$TMUR@L:R@:"V^!GU+JcCc1&b4c)?3!p!R@'A0
R\m:%cen9ds*t~>
n,F!ihp6R+DPNVeR$jD?YJ&9>nU^_&rt4eYcZqEdUR@L:R@:"V^!GU+JcCc1&b4c)GrBQ8R@'A0
R\m:%cen9ds*t~>
n,F!plef=QYHORgS=cL`_9_$)pjrI-rt5%lhn,hjYFh>TT:r^0cen'`JcCc1&bt\L];Vp:T:DFD
Tshe`hs:(2s*t~>
n,F!plec=T>HaZhS=cL`_9_$)pjrI-rt5%lhe)pnYFh>TT:r^0cen'`JcCc1&bt\LB)l/>T:DFD
Tshe`hs:(2s*t~>
n,F!pled:PG-@O.S=cL`_9_$)pjrI-rt5%lhh!b0YFh>TT:r^0cen'`JcCc1&bt\LJi7eUT:DFD
Tshe`hs:(2s*t~>
mf*gmn]%N>`iQ,;XK^%)jmi+ss,[0jq!lA&]>gqNW3!5Lg?%l"JcC]/&,>\7W4U'NW2Qf%ce@LN
q"=Z;~>
mf*gmnT"VB`iQ,;XK^%)jmi+ss,[0jq!iA)B?%$OW3!5Lg?%l"JcC]/&,>[8!,'<QW2Qf%ce@LN
q"=Z;~>
mf*gmnVoGY`iQ,;XK^%)jmi+ss,[0jq!j>%K#XmjW3!5Lg?%l"JcC]/&,>[S2M/rMW2Qf%ce@LN
q"=Z;~>
m/II?W6+/;]tM8&lgab$s,6mbd&>(J_7dOua6W9jJcCQ+$dbiojh\XD_8km3q"+N9~>
m/IH@!-RD>]tM8&lgab$s,6mbHiS<N_7dOua6W9jJcCQ+$[_qsjh\XD_8km3q"+N9~>
m/IH[2N[%:]tM8&lgab$s,6mbQSsre_7dOua6W9jJcCQ+$^Wc5jh\XD_8km3q"+N9~>
q>UHZp!`o=!7Uk\qpPQ0JcD8?!9s1*rhohGr4i-X!9o(7RK!9Op!`o=!7Uk\qpPQ0p]#a~>
q>UH0omcr@!.Xo^qgSU\JcD8?!5J2VrVuqJr+l1Z!5F*bRK!9%omcr@!.Xo^qgSU\p]#a~>
q>UH>opYk<!1Nh?qjIN0JcD8?!6t2*r\abFr.b*;!6p)pRK!93opYk<!1Nh?qjIN0p]#a~>
q>UHDk,8:/S,WTeo^qVKkl^>Fo_=RK!7U)/!7R&0"8DH[mJ?&Im/HVWrgNiee)mS/e+`lm~>
q>UGEjo>C2S,WTZhqucV^B;[:hs6F)!.X,2!.U+1"6\S!bPJsRa8XrrqjRNbJ*$_2J+rtn~>
q>UG`ju*4.S,WT]k2t@nblcPZk3nH4!1N%.!1K#L"7514f)!Vke,JJ0r0mWcRcZL.ReQi4~>
q>UHZp!`o=!7Uk\qpPQ0VuI,'p$:Sfc,IE+Z`iks$E^YA^;A(Dh<=D,Sc8]Sp!`o=!7Uk\qpPQ0
Sc9&rp$CYhcH3r7Z`WYo$F7"J^;eILi9Bk2VZ-Y\p!`o=!7Uk\qpPQ0p]#a~>
q>UH0omcr@!.Xo^qgSU\VuI,$j27dMCfEYN+;F;F$4n=*5YPjDS%I;HSc8])omcr@!.Xo^qgSU\
Sc9&oj2\'UE*u?r+;!r@$610B5Zhu\UVGRXVZ-Y2omcr@!.Xo^qgSU\p]#a~>
q>UH>opYk<!1Nh?qjIN0VuI,%l-R#7Mg/o;:.=Y1$:7HaB6&g!YfYeuSc8]7opYk<!1Nh?qjIN0
Sc9&pl-m5>Ne27W9g\8+$;4,uBR>c4[a!k.VZ-Y@opYk<!1Nh?qjIN0p]#a~>
m/I1:W71eEeu&HOqs<.d_7$HDW!]W7^;J(9e(3@2iVMaFjPAYFe'6"S^::BFW!9<0]Zo-nop5U[
e#:aqr77D<#5@H8cG-fgrMU(-[(OE#dF6b(h;S1n!9O49#Lq2Dahkd!XSo(&WO'7\f&lN%T`5,C
W71eEf'r]i~>
m/I0;!.Y%Heu&HOo>d>F814oF!"',_5Ytj%Iu9hhWqsF:ZC[YNIr&Lp5VNoN!!W]S4D^(jj-K]I
IfOuur77D<#3Wm^E'u)[r;[0A,Wo1:G^kaFSuVV#!4Mlu#D.>2?q`g2%/g/2"<]g?LUYn4T`5+D
!.Y%Hf'r]i~>
m/I0V2Oa[Deu&HOp=$72DH/n!2@;TC>\7qsJr,_VSGK/kTp1e%Jp)*J>Z4#g2?l6>A;dA-l'D>O
RPpW7r77D<#40^4NG;ESrAG!O93H"KIY*NCPaqQJ!2KOX#D@VHE+<-H4oI8D3DO+@TZ5meT`5+_
2Oa[Df'r]i~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10\Gla3n_V<tX8]%%\&6JFjmW$+rWi>kkMOY*];tq0"LQ%r
lhI:U"7b[:h>Gh+^Snour3Z:N"Pr,'nbB9e"T%6,^U!j;#HPJUcJ.FKqr7S[q=*S'dDii#rhp")
]ZoI.riZ8'n`o/jr71tbW5$p][/&8Uh;dejq#15C~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10\Gla0f:Q;)#lXf./5U/K[aOU)rWi/N\tX:G3!fK`"=-Zn
_s=,u"7b[:h>Gh+CB//$r3Z:N"Pr,'nbB9e"Ri[=6lc1+#7*:gE0dfXp>Z&Vmaet@GZ[':rW!*=
4D_(Uql]r$n`o/jr71sc!,L0`[/&8Uh;dejq#15C~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10\Gla1hm7f<4T75C;.b!5UU.o"c3MM&V3cOB=$-C>"Aj*/
d-me1"7b[:h>Gh+L,Oe;r3Z:N"Pr,'nbB9e"S0HmC.eI`#<Q"#G`\W*b2U\O`3uM,IVN"Kr\apN
A;e.`r3$&%n`o/jr71t)2MTf\[/&8Uh;dejq#15C~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10]Di93h8QLpW3*_Xj6ulqrWMiUf#+rqVusNVk5;1X"7b[:
h>Gh+^Snour3Z:N"Pr,'nbBEi$h`B"]W8!1\Ad"Yo[<^Hq<H_T\uVd.Yf5JlZ2XprlJp^uecW[c
^Xi=iq:5])j6H10rVqB~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10]Di9!RoBq!!"_83Y1!$orWM)hMGP`#!!=cs\+cj$"7b[:
h>Gh+CB//$r3Z:N"Pr,'nbBEi$fdjm49toi0N<e0imRf6m_Y8p1^F'](f[-fZ2XprlJp^uecT[f
CY&Ejq:5])j6H10rVqB~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10]Di9'Y]rJm2F(DiT!QSic31bUM.TN^2?R'H`qcS5"7b[:
h>Gh+L,Oe;r3Z:N"Pr,'nbBEi$gG!P@66A,;eUoW^!b16`2eu:<BE)s89hK*Z2XprlJp^uecUXb
L=Z:0q:5])j6H10rVqB~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10^AeN.f".iZYK#>^rQG0HoAJ'.WMlo7hY=/O"7b[:h>Gh+
^Snour3Z:N"Pr,'nbBQm$NBD?\>cF/_qFJSaSlPCkLmhXW2R&Jl27sh"7b[:h>Gh+^Snour3Z:N
"Pr,'nbE(_J,~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10^AeMcLGT,6'Ng[<qTJjEhPPZW"98j*TBiQX"7b[:h>Gh+
CB//$r3Z:N"Pr,'nbBQm$N%kk0E_@e:m0,paSlP;\rp#,!!"6_^\>/9"7b[:h>Gh+CB//$r3Z:N
"Pr,'nbE(_J,~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10^AeMmTNsQ96>DU_bfe9<]:+7M3&Ws@ZgXsp"7b[:h>Gh+
L,Oe;r3Z:N"Pr,'nbBQm$N/A==?/9)Ara.:aNb.5V2f4U2E"Blc2"dI"7b[:h>Gh+L,Oe;r3Z:N
"Pr,'nbE(_J,~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10_>af;gq'G_[aFC=_u9r9i5_ptWP[TmY5\UolJp^uecW[c
^Xi=iq:5])j6H10\Glj,f!MBS\C9aA^]"K(e%DZYYfl=@rr_fVj5'Xa"2CeLf)2oNecaghlLFZJ
s*t~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10_>af0QS\[B.!WW*_u9r,VHaN0"B;Q$Y5\UolJp^uecT[f
CY&Ejq:5])j6H10\GlilLElj"0Rpt9^]"JXIlmi6)Kj<6rr_fVj5'Xa")@mPf)2oNecaghlLFZJ
s*t~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10_>af3X^*hC:4B7f_p/P)RVb2G3-Dt5Y5\UolJp^uecUXb
L=Z:0q:5])j6H10\GlisS52%$;hD'o^Wm(dJlt&k8WhT5rr_fVj5'Xa",8^gf)2oNecaghlLFZJ
s*t~>
p&>-alJp^uecW[c^Xi=iq:5])j6H10_uBr8cE4%RdI+[H#Q*T(Xf/SSos"Gun`o/jr71tbW5$p]
[/&8Uh;dejpq$J0l,BX$_;"[nrWi/P_Q0WHh"\J\"7b[:h>Gh+^Snour3Z:N"Pr,'nbE(_J,~>
p&>-alJp^uecT[fCY&Ejq:5])j6H10_uBr(E"E!tGg<u$#PPH2&-+#7n?Dopn`o/jr71sc!,L0`
[/&8Uh;dejpq$J0^Jk==8=&DJrWhSS9F(V_Xn]L-"7b[:h>Gh+CB//$r3Z:N"Pr,'nbE(_J,~>
p&>-alJp^uecUXbL=Z:0q:5])j6H10_uBr-NC#NuI^-Q2#Ki`Z5W2N7o!&,rn`o/jr71t)2MTf\
[/&8Uh;dejpq$J0b]5[&@?A:7c3M.J@lcM[]_K)<"7b[:h>Gh+L,Oe;r3Z:N"Pr,'nbE(_J,~>
m/I1:W71eEf!t_`r6VLD`U:26#Q<VnW34V>po=>re#:aqr78(O"m3FqZI8KQrWi2I[&C=`nbBKk
"4O3ts8:I;s*t~>
m/I0;!.Y%Hf!t_`pN-ZD<6hrO#PtG[!#Cmjpo=>rIfOuur78(O"h-Gk*.7%WrWh_?,QK6lnbBKk
"+L<#s8:I;s*t~>
m/I0V2Oa[Df!t_`q2[TsBX,"k#L&]>2Fa\9po=>rRPpW7r78(O"i=P'7tAI^c3M4>9/]_YnbBKk
".D-:s8:I;s*t~>
m/I1:W71eEf"(e`i390#o!//um(TF(fCAX;rr^`Ke,T?sZ2Y!t^SnTsrN6&'iOGhng@=sHrr^`K
e,T?snGe"~>
m/I0;!.Y%Hf"(e`UDNu9g9LV]b!5qSYOVChrr[`NJ,fGtZ2Y!Z6i\X,qQ9`$W\=E0^%(m+rr[`N
J,fGtnGe"~>
m/I0V2Oa[Df"(e`[7<,l\ZsEaY?Wt`]^bcurr\]JRfE<:Z2Y!_AN!;JbcT.pS5V=N`pri4rr\]J
RfE<:nGe"~>
m/I1:W71eEf":qbrl(hIm&g6kiNfDujRN#Jrr^`Ke,T?sZMt'`XK^IXWW!+XYc>\(nbBWo"4O3t
s8:I;s*t~>
m/I0;!.Y%Hf":qbqc!PPb,tXIWZ2"UiUQ]Grr[`NJ,fGtZMt&s$lG<,WW!*n(^GNOnbBWo"+L<#
s8:I;s*t~>
m/I0V2Oa[Df":qbr,stuYH?(SS4>JniplfHrr\]JRfE<:ZMt'*4[bCUWQk^46opUpnbBWo".D-:
s8:I;s*t~>
m/I1:W71eEf"D"cr5,DWrN#o%qT#PDhs:'@rr^`Ke,T?sZi:-QW5eY?rVurjrWi;AW41LBnbB]q
"4O3ts8:I;s*t~>
m/I0;!.Y%Hf"D"cpIkR'qQ'T"nlu/ih!=a=rr[`NJ,fGtZi:,H!*JZrrVurjrWi"%!*Rq8nbB]q
"+L<#s8:I;s*t~>
m/I0V2Oa[Df"D"cq/807bcB"na)*_Rh<Xj>rr\]JRfE<:Zi:,\2JU1;c2[k:c3MC/2L!g8nbB]q
".D-:s8:I;s*t~>
m/I1:W71eEf"D"aa/I[hrWUfM[+aj@Yl=gSW71eEf!PGYbG`[lrW0o)o)8jBW3O\+lhJ*l"4O3t
s8:I;s*t~>
m/I0;!.Y%Hf"D"a>QBqbrWRlR8_<;'Yl=fT!.Y%Hf!PGYBE2kTrW0o)o)8iC!(P2ilhJ*l"+L<#
s8:I;s*t~>
m/I0V2Oa[Df"D"aI5[+`c38kZCtS(KYl=fo2Oa[Df!PGYIl;JVc2jLqo$.Gk2JLFnlhJ*l".D-:
s8:I;s*t~>
m/I1:W71eEf"M(bk,I#(rX&Xi*X+(3UEp'uh#@9UYV-4sHN*mYfLlMS0rb0OfLlMS0rb0Oj&66f
8a66R"e-V>-("]3rWKGY#8VXUrWU`K]\;]HZ2XpTW71eEf!YM[ii(0$oDT'sPTBuY_kSL]1#2d-
"L'Na(4,uZ("c.c"Ym"Urn'jM"Ym"Uro6o`"\ItXrWSc6#:5`;rr*&)0*`8=rr)p`a79-5dAXCo
g?\OHrr^`Ke,T?snGe"~>
m/I0;!.Y%Hf"M(b[fh`^rX&Xi*X+(3UEp'uh#@9UYV-4sHN*mYfLlMS0rb0OfLlMS0rb0Oj&66f
8a66R"e-V>-("]3rWKGY#8VXUrWRZLK%fPcZ2XoU!.Y%Hf!YM[WrR,8oDT'sPTBuY_kSL]1#2d-
"L'Na(4,uZ("c.c"Ym"Urn'jM"Ym"Uro6o`"\ItXrWSc6#:5`;rr*&)0*`8=rr)p`a79-5GQ<$3
g?\OHrr[`NJ,fGtnGe"~>
m/I0V2Oa[Df"M(b`]/qJc3aPr(^)(TKH:7EZi85QO!Y&DA,aagY<<*n.#H6pY<<*n.#H6p\3gK(
42(X="b%?o*ddtec301T"qkG9c38_VQ.kR!Z2Xop2Oa[Df!YM[[5N9Ro?I[CGSgQ.SqJ-f.'rou
"HOl6&nCpd'sBb*"=T,PcEBA9"=T,PcF?4H"?X<rc38Y8"s/-lrltY--3b)mrltO$U@HM5IQ!/-
g?\OHrr\]JRfE<:nGe"~>
m/I1:W71eEf"M(bZb$,+r]L6Oj8=HD8`T`"6N6to36qNd:O`<B.@^25c<C5H.@^25c<C5H/&hG_
G@:FLW?p!le0P+orjXr5ro[(+nbr^&WlEcYpp0o%e#:aqr78:U!k5i>o):cP8)sMu(.mQaJO9A8
\2_e5k>-R+r[F?5rm"^Hr[F?5rm"^Hr[YtcqKH?Hri%3lrmW4orr0DecMm!&IdI%<];W0Xg?\OI
rr^`Ke,T?snGe"~>
m/I0;!.Y%Hf"M(b+>ircr]L6Oj8=HD8`T`"6N6to36qNd:O`<B.@^25c<C5H.@^25c<C5H/&hG_
G@:FLW?p!le0P+orjXr5ro[(+nbr\R*5gLrpp0o%IfOuur78:U!\?J-o):cP8)sMu(.mQaJO9A8
\2_e5k>-R+r[F?5rm"^Hr[F?5rm"^Hr[YtcqKH?Hri%3lrmW4orr0DecMm!&IdI%<4W:ZHg?\OI
rr[`NJ,fGtnGe"~>
m/I0V2Oa[Df"M(b:0#O/c92.f\BJY"41NLK29sL4/Z6n+5\A<e+b%V[Vb,1Z+b%V[Vb,1Z,G<9(
?r#"mM&r\BX<%AecBTuZcFZ>Rn]h;@9#THLpp0o%RPpW7r78:U!`N7Eo$0Au3Om:I&j*VIBf42]
PqAoZ]0KuRc67Y[cDO7Zc67Y[cDO7Zc6K"+b@")jcA<UBcE&&ecH_\,Vp;n>B'deI>"H$?g?\OI
rr\]JRfE<:nGe"~>
m/I1:W71eEf"V.coVgO7rXJqerr2fqjo58W$3'off)GXPn/20(#OD9d!=.rbrWW3/rr+mnrr*);
!<2s]f)GXLn/1]p"llPgb2;`prr^`Ke,T?s[f6F(W8[:G%KPJerqlcXrr2g%rr,+4rr*)j)#jL>
lhgY^#OD-`"oJi/r]%anrWU+<rr,+4rr)rf)!q5+j/Aobb2;a&rr^`Ke,T?snGe"~>
m/I0;!.Y%Hf"V.chZ/u4rXJqerr2fqjo58W$3'off)GXPn/20(#OD9d!=.rbrWW3/rr+mnrr*);
!<2s]f)GXLn/1]p"g.j"b2;`prr[`NJ,fGt[f6Ep!35Sg%KPJerqlcXrr2g%rr,+4rr*)j)#jL>
lhgY^#OD-`"oJi/r]%anrWU+<rr,+4rr)rf)!q5+XoOcrb2;a&rr[`NJ,fGtnGe"~>
m/I0V2Oa[Df"V.cju/=kc40i`cHaW6]$AUF#L:UMY5Z]M_\:>@#/C&\!=$=5c3;E"cH\#4rlt\<
!7&kFY5Z]I_\;Rc"eIWNb2;`prr\]JRfE<:[f6Et2Os<_%KNu`cHH:"cHaW=cH\2Jrlt\b'@+l-
^\c:W#/BoX"j@A"c7PUdc39a=cH\2JrltP^'C=!KSi3+Ib2;a&rr\]JRfE<:nGe"~>
m/I1:W71eEf(o?aVuZ&OrVursrVursrW3*!rr!'."TSZ(rd#DnVrRdU"F:nsVrRdU$/\G`!!!-#
rWW6!rWE&urr2j$!X/Q+nbrOP_l2o$"4O3ts8:I=s21c8kjeWP!<)ls!<)lu!<2ur#71P6"9/:G
(hT]+rr*%H(hT]+rr*5]9aLe""9/9'r;QZtquH`qr<EB-!!DTe!9hD#oD\pAW71eEf'r]i~>
m/I0;!.Y%Hf(o?a!!'b'rVursrVursrW3*!rr!'."TSZ(rd#DnVrRdU"F:nsVrRdU$/\G`!!!-#
rWW6!rWE&urr2j$!X/Q+nbrO&_Z9$&"+L<#s8:I=s2+g:^@D1&!<)ls!<)lu!<2ur#71P6"9/:G
(hT]+rr*%H(hT]+rr*5]9aLe""9/9'r;QZtquH`qr<EB-!!DTe!5?DPoD\oB!.Y%Hf'r]i~>
m/I0V2Oa[Df(o?a2?9]Gc2[kCc2[kCc2n!kcM[qR"98MKc=7McLq*H0"Ci*CLq*H0$+2r%!!!)G
c32Ajc3)8jcHc@m!X&K)n]h-5_`$i\".D-:s8:I=s2-_pW:A.5!<(1C!<(1E!7&mg"pkD4!m])$
'2jXrrltXV'2jXrrlthX4pM&f!m]'obl5libQ,r6r<<<+!!;N4!3!jpoD\o]2Oa[Df'r]i~>
m/I1:W71eEf"V.coVgO7rVursrVursrWE6#rWE#mrWLT0=r*(rrXdG<=r*(rr\fE(rr)osrWW6!
rWW3"rr*#rm/@4?W2$K2g@;NL"4O3ts8:HVrrVqniUQmI!<)ls!<)m"!<2s"qYC*nd#_EHN;ief
d#_EHN;if7KD>HF!<2s$r;Q[!quH`q"8_3[#2H/MUpA/)^&J2`W71eEf'r]i~>
m/I0;!.Y%Hf"V.chZ/u4rVursrVursrWE6#rWE#mrWLT0=r*(rrXdG<=r*(rr\fE(rr)osrWW6!
rWW3"rr*#rm/@3_!041tg@;NL"+L<#s8:HVrrV?`Wq$&g!<)ls!<)m"!<2s"qYC*nd#_EHN;ief
d#_EHN;if7KD>HF!<2s$r;Q[!quH`q"8_3[#,_HZUpA/)^&J1a!.Y%Hf'r]i~>
m/I0V2Oa[Df"V.cju/=kc2[kCc2[kCc3+-mc2u2bc31+h8J6(Cc4Hst8J6'hc7EbQcHZF8c32Aj
c3;DlcHZL7m*5g&2O1D\g@;NL".D-:s8:HVrrVOFSFOl)!<(1C!<(1G!7&jkbPBEdWHCcFErO>q
WHCcFEmDr]CWXlI!7&jmbl5lkbQ,r6!mBcP#+IN6UpA/)^&J2'2Oa[Df'r]i~>
m/I10W5\f7f"M(bZaTi'rVursrVursrWE6#r][%LrW20equ-`]#QF])rr*)E!<2sW^]+39^B"94
5,%?B#-XqtVRFh:Zi:-LW5\f7f!YMZ[(QA.rVursrVursrWE6#r][%LrW20equ-`]#QF])rr*)E
!<2sW^]+39^B"945,%0=#-t2#U928h^&J2VW5\f7f'r]i~>
m/I/q!*'!rf"M(b+=R*WrVursrVursrWE6#r][%LrW20equ-`]#QF])rr*)E!<2sW^]+39^B"94
5,%?B"uI-eVRFh:Zi:,8!*'!rf!YMZ,WtqorVursrVursrWE6#r][%LrW20equ-`]#QF])rr*)E
!<2sW^]+39^B"945,%0="udBiU928h^&J1B!*'!rf'r]i~>
m/I0>2L,9"f"M(b:Jf:+c2[kCc2[kCc3+-mc8(%Gc2kN]qp#>V#0tKqrlt\D!7&kAS,U\:RfLaZ
15"r=#$=<cVRFh:Zi:,Z2L,9"f!YMZ:g2*6c2[kCc2[kCc3+-mc8(%Gc2kN]qp#>V#0tKqrlt\D
!7&kAS,U\:RfLaZ15"c8#$FEeU928h^&J1d2L,9"f'r]i~>
mJd1crhohor78R]!p6K/o)8UhrVccsrVeSQrr0SJXSi"6;?$Ph_>O5)/,oNB_>O5)/,oMsR/Yu!
!<2u!-.;e@-NO/F]bmjKr52D&rWg6;V4XQhb3QG7!<2tr!;l]F[Jp?gW6YA5rVursrVursr\FQR
rk0/Mr;=H0rr,X.r;<6=rr,X.r;<6=rr+@6rnfL3rr/uFdf.ZB!<2u2(p*Z5_boh##K!F=TV8C!
g:mRerr/hrqu#%7s*t~>
mJd1`rVurir78R]!k/;So)8UhrVccsrVeSQrr0SJXSi"6;?$Ph_>O5)/,oNB_>O5)/,oMsR/Yu!
!<2u!-.;e@-NO/F]bmjKr52D&rWd0<CS.E.b3QG7!;l`q!;$->[Jp?0!-%f+rVursrVursr\FQR
rk0/Mr;=H0rr,X.r;<6=rr,X.r;<6=rr+@6rnfL3rr/uFdf.ZB!<2u2(p*Z5_boh##AF0VTV8C!
g:mRequ-QqoDI2/s*t~>
mJd1ar\acMr78R]!ld6Ko$.48rQYBCrQ[2!cH_gkN6KM;6-db>S]o<1,L4RuS]o<1,L4RXI*T,K
!7&lK*idMc+99^4R1kP'baq,kc3JVQI\3FAb3QG7!;ugS!;??A[Jp?B2L*I;c2[kCc2[kCc8,IG
cC#?)bflYEcH\VEbfk\acH\VEbfk\acH[S\cF#)$cH_@iWm7Ue!7&lX'9(/ZSjDbh#C&.YTV8C!
g:mRer;JSSpAEM2s*t~>
mJd1+rhoh5r78O\!m[ajoDS^irVccsrVccsrr*qU63d`^\GlK;5Q_<kh#@;_5Q_<kh#@;n/H[&Y
!<2s$RiVYUQ3I9e"iEY3#=>rlrWp<<W2#u^[a49HrrC(<VuX[3f!PGZbG`M\o_ngjrVccsrVccs
rr*qU63d`^\GlK;5Q_<kh#@;_5Q_<kh#@;n/H[&Y!<2s$RiVYUQ3I9e"iEY3#=>rgrW'e6W!&`e
Xhio/rrC(<VuX[3f'r]i~>
mJd/grVusie,@k1rrR->H1V"8!<)ls!<)ls!<2s<_a4S56Fd$o\3L-&;r%BO\3L-&;r%BOa"7B7
PQ:^^"e-P8,*iE3rWU2+#7!T*oDT#)!'R!IVRFh:[/U,.rVusie,@k)rrZp7?i'Eo!!)us!!)us
!!*#t*Q(2K#=>s"rjY2q#ufXYrjY2q#ufXYrl6Sm*g?[(rWSc4"!X$7rr*)=63d`^\F0>%AH4li
T;&I(hnK*j=T/:$8CROgnGe"~>
mJd05r\adqeG[t2rrS3=JFh%d!<(1C!<(1C!<17aSi$]V23lJ4Pr&OI6aP(`Pr&OI6aP(`U*KdW
GQ@`g"b%9i)gVYdc39ds"pQrMo?IV_2IN5NVRFh:[/U,Qr\adqeG[t*rr\'8E6Rt"!!)uC!!)uC
!!*#D*M,+m"uV[kcBU3f#=GiCcBU3f#=GiCcCuQc(jH&[c38Y6!ZQFgrlt\=2$O7BQ0up'FZ*X)
T;&I(hnK*jHi>o(Dq+a:nGe"~>
mf*L?W3,'_W4r5]]DhuXW582ErW1Lrm/@@\ZDa^tTV8C!eEaOB#1KNQiif;prRS=T"4O3[m_\r(
e0jGY!U[2lW!/uhUpA/)^]+MeW3,'_W4r5]nGe"~>
mf*K@!"c1F!+l,[]DhtL!*87!rW1Lrm/@@B1]U.ATV8C!eEaOB#(HUaWtYPFrRS=T"+LD6cGKP]
e0jGY$I^p6<2HC_UpA/)^]+Lf!"c1F!+l,[nGe"~>
mf*K[2FG/I2M1nY]Dhtk2J^+Fc2k!hm*5sH=#O`dTV8C!eEaOB#+@G;]gao3rRS=T".D2PZ,4ce
X<?]O$FEqoDl0>&UpA/)^]+M,2FG/I2M1nYnGe"~>
n,EgOW2o:bkGtPm`l?3s]Di#QWN4SJaSl?Phu;+bL$npKq8TD4W2-)YVRFY)n[AH+go.:?s6$G]
]#Mb5pp9u'a/c/[o>pn7@c.173/-Vr%.q?6W2Z_kTV8C!eEapM%,@e^m/Q7LW4L!gbP:W-J,~>
n,Ef_!!pXF`<uZg`l?3s]Di"l$kS?naSl?Phu;+bL$npKmV3%9Br%DoVRFY)n[AH+QN.<3s2>?G
FiMGEpp9u'Qk]^7hT5["@c.173/-Vr%,tV"&9k(KTV8C!eEapM%$pr"a8a*d!-Q<$bP:W-J,~>
n,Efu2E]Igcos@G`l?3s]Di#*5=(1NaNarb[Ebj*D!oRW`-4:,IAEO.VRFY)n[AH+XYr;js3Nqf
MoNc[pp9u'VaWRd]?&8$:USKL/Ss[s%)$XB6&2D7TV8C!eEapM%';EMe,RcY2N,SpbP:W-J,~>
nGa!VWMneUq!lk4W40ahnbBcs#2dJ'W3+MBb5MOA-O1iLn,<CX`i8s9#c+,cUpA"olhIsh%cF=b
iW&]Ce>T=T`QRfH[/UBgaKMSA_rq!ArWK,H#9nlerXS5-WMlepVPBcVW4'k+n\P58i2N[6s7l-6
W2R2EbOPAus*t~>
nG`un"9>A"q!jmW!.)W-nbBcs#2cmS!$-gZb5MOA-O1iLn,<gJD%$D2R&$CIUpA"olhIsh%\j.,
VuQP^SHAk<`QRfH[/UBg]ibOA<P&%MrWK,H#9nlerXQeF#mF%mVPBcVW4'k+n\P58UBCRps7l,T
!s!YdbOPAus*t~>
nGa!,3B"JKq!kOI2N>]!nbBcs#2d(&2FrV?b0C-G+9id$n'2EOGs4,rSYVpNUpA"olhIsh%^nAU
])VQqY;eG"`QRfH[/UBg^i_XsBr0%sc3/qG"rhCDc46mj4#q&JVPBcVW4'k+n\P58[Q-7Ws7l,f
3&Zc8bOPAus*t~>
nc'0hXJj\Lq!mY0\u;UCbNeWm^&J3#g;^JuVuj`lqml\-o@UDJW!T>rT:i'e`nKbR\,R$.XJj\L
q!mY0\u;UCbNeWm[JpR!g;^P!Vl6iHla6X'm`qu6W2Z_mUS+6RW4'_"lhJ?s&FZ^!ec5F7lJoIS
WP?XAnbD_UJ,~>
nc'0H$ikq[q!mY0GQ8&@bNeWm^&JE)g;]%!!!>unp::/6hMs/f"%%pCUS"3VZGtVapp^87cO9f+
s7l-Lj*1P1S]g<gppL,.le9@T;@3CBFN1]e&^GM<!!l;_VPBfVU8YN?g?\ONrt*Kb!/(=Hn`o.Z
!"#4>lLFZ@s*t~>
nc'0R5;o)4q!mY0NA`ntbNeWm^&JE)g;]LW2E59:b.5e/]8TWG3+J2MUS"3VZGtVapp^87ffqBc
s7l-Lj,GrZW6=JrppL,.le9@YDETmIH`a[#&[?aS2EPZBVPBfVU8YN?g?\ONrt*jJ2P'mCn`o.o
2E[q]lLFZ@s*t~>
o)B<pYc,qKs7,C7h8Ze"X2a2bn\+r2q!6YV\ZD[,W2d\lqS3"Bo$t4JW2Z_oVPBfVU8YN?g?\OC
rt4q.W6kSEn`o/j`j;VE`oH@S[Jp^2le9LlXehnnW3FJ+qn2n1qrc,)r2:"#US+6QUoh2Mg?\ON
rt4q.W6kSEn`o/j`j;VE`oH@SoDa=~>
o)B<^(]\[Xs7,C7h8WUu&?b'Yn\+r2q!6YV\W8fU!!c>to"Y/:g4hEd!#&M$VPBfVU8YN?g?\OC
rt49.!-J8@n`o/j```?RY2eg;[Jp^2le9LlW,!^[!#fOVp:UA=p:>&W!!"$RSYi0TT:r'_\]WXl
pqm%Citqc<s8VWSj5%^Y!"c<nlLFKGJ,~>
o)B<d8N)_2s7,C7h8XXs6FPq>n\+r2q!6YV\X6\`2EGH=a1on4\V=*D2FDMTVPBfVU8YN?g?\OC
rt4Ke2Ne%>n`o/j`cj=-[c?ZC[Jp^2le9LlWd[`%2F_e^b.Q"6b,';N2E"0UTVeKWT:r'_\]WXl
pqm%Ckt*dqs8VWSj5%_!2F=k4lLFKGJ,~>
oD]L%];WRFs7km>h:BQaZ)Fh[j6HEQrt"njhp9-EWhlSjW2dGUjnm*t"7k3eZM^X0VPKoXT:r'_
\]WXlpp^8:r4AoDrr;cYj5&;*`NcAEgZ.So[Jpg5leg%)ZDs^rVPpGn^!bs:`;To9jj(K<rhp@,
W2?AaT:_gQWj9Iehs:'SrtG=BW58K5q!-nqf#>V5W34n8lM1#OJ,~>
oD]Ku2ulMHs7km>h:BQa4ochVj6HEQrt"njhp9-ET4Tb^!!b`1[e@c@(#"s[,QIfZ7=1`(Tq@sN
Uoh2Mg?\OCrtG,d!(d"eq!-nqf#>Tl!$ftQlM.de&GYSKcbZoeP$0F@!!bi<Y3s7-(\5"T63dJm
.T_Y-UnOEQTqn]o_9_$)pqm%EpG`.Dqu?HVj5&;*`BF8He)T`go`'F~>
oD]L"@Q&0&s7km>h:BQa@l@07j6HEQrt"njhp9-EU3]j$2EG,jUWUX&'tKEE9K"dsAVm1XTq@sN
Uoh2Mg?\OCrtG3;2K/Nnq!-nqf#>U<2GhublM.de&GYSKcbZoeQuZ.d2EG/pT#ARm(WW:s?90u(
;K8#"UnOEQTqn]o_9_$)pqm%Eq-l7#r;ZQWj5&;*`FCf&f&Q&jo`'F~>
o`#[*_Pk!@s7l-Eh:C0/f@@7,[+jX2pq?\8q!mJ"b.Y*\Una``rhot)bLl%Sdf'FKmblR,Wr8k-
WMl\iTq@sMU8Frr_9_$)pp^8<rkbVDqZ$?\j5&;;f@SQ[W3k=>nbBNl(&7=\g<.+7WhuMcUo(,i
W4'^ujmrArrWM`Oe]"`kW"Z//WMl\iTq@sMU8Frr]uo'jnbC!$(&l(K\GH7#n`/HVf%8O([&C@d
j72p;s*t~>
o`#['9E6d8s7l-Eh:C0/f?cMX3S*6\pq?\@q!mJ"b.Y*\T5Hq4!rr<EC6$$rdf(0]cDH(T#64`5
-s).uUnOEQTVA<`ZGFlElhIsh(&LZ)0^o&:n`/HVf%8O#3<2(jj72oPrtY>!jk@kjZDsdjDF#KO
!!=iUNjd[<ci+m7ZA)O#!!!*?6>W-gUnOEQTVA<`ZG"E7jmi,_rtYE)!&3mAq!mD#f%/I)coD(d
f&Q;qp&BO~>
o`#[(E].1ns7l-Eh:C0/f@"(c@G'W1pq?\@q!mJ"b.Y*\TmpK@3&WZeG)WB<d`rcTZ'p8K3]8l\
;/q_oUnOEQTVA<`ZGFlElhIsh(&W)1>k7pin`/HVf%8O%@5_QHj72oPrtY>!jk@kjZDsdmIpl&3
2E4TLN2*r-cd!K<Tn[DQ2E!Nd@t1,JUnOEQTVA<`ZG"E7jmi,_rtYHP2I,hQq!mD#f%/I)dU>%B
f]2Msp&BO~>
o`#]SW3Z0tq!mY*f%/[;jQ,7%W4:[KnbBcs%J]JTg<.+7WhuMaU84[bW!9B7bLke>p>GoSp[7(p
dD)s^W"Go&US+6OTVA6\XgYpcg?%l"[f7,jW3Z0tq!mY*f%/[;jQ,7%W4:[KnbBKk)Yijag<.+;
Xf/%iTV8-VVQ$MmX0opog#MGmhYm`Qm,ZpL^pNs:'<.q-UnaTTT:hpSW3*MAb1btIpqZnFdAXAF
s7l-Lh:C05j5f:ZY,JkdlLFZCs*t~>
o`#\O!$(2%q!mY*f%/[;jQ+r1!);24nbBcs%f#SUg<.+7WhuMVBM0`_rW!-83GOSS`oc4Y#3sL:
P'BG;rW!]>*C="cUSOZXT:_dNUo:E(]uo'jnbBQm(3fg!kPt>IlJ:%Bh;dSWfGFT_g?S5'[/V'>
n`AKI]sb&AUnX6(@6l4E!!!KY=*Tn2b3%R[#Nj17Q@)CX"9&99&2I4/SYi3XTV%jLU84`i[_9i;
jmi,]rt]hZ*9[@sn`nr]f%oBIjOb:L:"nn&q"F`<~>
o`#\l2GEK;q!mY*f%/[;jQ,&d2K7F9nbBcs%f#SUg<.+7WhuMYHY?5?r\asH=E]XnXh`l?#/[X:
NdbA,r\bNR8n3"+UnjcYT:_dNUo:E(]uo'jnbBQm(6pdOm/QkNlJ:%Bh;dSWgIO5AgZn>([/V'>
n`AKI]sb&AUnX?7G%"0,2E!crC4;27YJB#?#JdO:OFUn>3;tfO6!/&\TVeN[TV%jLU84`i[_9i;
jmi,]rt^kX9^MfSn`nr]f%oBIjP*3`E80[Jq"F`<~>
p&>l[W2]%]q!mY0f%/[;lLFE0f;bd`j6H10]DiK<n`AKI]sb&AUnaWUTVA3XVu*CtXKf@VajSYk
rnRS;q</1Dh;-i7e'#eHZ`a5)$)sl%WMlbmUnaX^T+%EgUo:E$\\ZVGjmi,Rrts=cWTjHXn`o/c
f%oBOnaYnrWi3YllLFYUrtP7ujkA%s\[&?9UnaZWT:r!SV5UBkW!BQ6^;J(>e("BQ!9*_:!9*q5
#M.AOahtZoXSA^uWMl\iUS4@ZT+%EgUo:E$\\ZVGjmi,\rts=cWTjHXn`o/cf%oBOnaYnrWi3Yl
lLFZDs*t~>
p&>ka!!L(:q!mY0f%/[;lLFE0[0H\Fj6H10]DiN=n`AKI]sb&AUnaWUOEFep/f"9a#7hk7=C?N+
P5_$Q](s&IVOjBFIt)-:6mEKF!!s,]4]cW`TqnHWrLXLlUSOijZFRftg?%l"[Jq)!!!L(:q!mY0
f%/[;lLFE0[0H\Fj6H10Zi:a7n`AKN_7?kRW2-2_TV%'S:HB[0qu?s?-9tC8FFSoAScGec^&Y24
Sd1.&G[O?+*Y8/K$4A.@<,@A^UnaWVrLXLlUSOijZFRftg?%l"^],.+!!L(:q!mY0f%/[;lLFE0
[0H\Fj6H10p]#a~>
p&>l%2EB%^q!mY0f%/[;lLFE0^cOH-j6H10]DiN=n`AKI]sb&AUnaWUQ%O-e<CHaC#<ta\C2e]T
NrG7CV>7%sRZs%uJqA)]?V<fb2@2WE?u^gbUSOZYrLXLlUSOijZFRftg?%l"[Jq):2EB%^q!mY0
f%/[;lLFE0^cOH-j6H10Zi:a7n`AKN_7?kRW2-2_TV%?nC0aLtr&+dL:0_CMH\-qMPQ76KVuW@e
PR!%sIW/n#83d#1$9h6kDh!qDUnaWVrLXLlUSOijZFRftg?%l"^],.D2EB%^q!mY0f%/[;lLFE0
^cOH-j6H10p]#a~>
pAZ&hWMnbTq!mY0h:CEBlLFZEn`nT%W6+)bnbB]q)Yijag<[UGZEL:*UnaZWT:_dMU8+N\Vl-Jn
m&1,fWi2kqW2]cp"K%uiTqM@V&"oYlUo(/qZFRfoeDKT^ppC&>i2N[5s7l-Lj5&;Bj6H10q!mXu
W2RhulLFYTrt+tqleg47_7?kTXf/+mUS=F\T*_3cUSOcbVl?\sl_jf_ri#gqs/6*uUnaWVT`(Jo
TV8*TUo(/qZFRfoeDKT^pqHbHi2N[5s7l-Lj5&;Bj6H10q!mXuW2RhulLFZEs*t~>
pAZ&+"9>4sq!mY0h:CEBlLFZEn`ltR!/fh%nbB]q)u/sbg<[UGZEL:*UnaZWT:_[2H=g/Q4tIuo
"76'u%O<Mb9iPbNNhE8FUS4@ZT+@ThUSOceYHk^Tb1,>7nbBKk)P[E8U]:,ZlJp^Oh;dejq"XCE
V?6gUj6H10ZMts?na#)^b/(WoYH=b#UnaZWT:_R/G\0rE4tJ3&($ko2!u!(74]#3mH&J8iUS4?R
rLXXoTq\<XVQ-i*\\ZG<hs9h-^Af+H"9>4sq!mY0h:CEBlLFZEn`ltR!/fh%nbDnZJ,~>
pAZ&>3B">Gq!mY0h:CEBlLFZEn`mJ@2P/sonbB]q)u/sbg<[UGZEL:*UnaZWT:_^;LO!cQ?rToh
3:/U65Yb3_C2\KUQD:=QUS4@ZT+@ThUSOceYHk^Tb1,>7nbBKk)R_Xa[f?-mlJp^Oh;dejq"XCE
ZoBt8j6H10ZMts?na#)^b/(WoYH=b#UnaZWT:_X9L3[ZI?rU&p7-lfB3($PC?u9n2Lm4U/US4?R
rLXXoTq\<XVQ-i*\\ZG<hs9h-^Af+[3B">Gq!mY0h:CEBlLFZEn`mJ@2P/sonbDnZJ,~>
p\u6#XJjhPq!mY0h:CEBlLFZLs7l-Lc)@Sgj6H10\c3?<na#)^b/V-'ZELC/VPKu]TqS.XTDkM^
U&C\dUSRjb!MlI_T`(JqTV8*TUo()mYHkUM`lcfglgabUruCF-W7h4Gn`o/jf%oBOnbE.aq!mXo
W2S,(lLFYRrtP7uleg47`kf3oZELC/VPKu]TqS.XTDbG]U&C\dUSRjb!i2Qap7VAV'VM.nU8+N]
W2cu'ZF7Hdb1,>7nbBcs*:9i+g&Lj;lJp^Oh;dejq#CBhn`nAtW6sYjnbDq[J,~>
p\u5V$il@gq!mY0h:CEBlLFZLs7l-LNW90\j6H10\c3?<na#)^b/V-'ZELC/VPKu]TqS.XTDkJc
Oc56UrHJ?gHN&6oLl[OUSbJfUT+[fjU8+N]W3*8/[_9]'eDKT^pp0o>b7"B3s7l-Lj5&;Bj6H10
s8VlalAbiHWT3:6posc4q!m\-eB5eI\[JfIXf/+mUS=HUT`(M^TE(J\Mu\P0GQE)9rd+d%O,]*c
pRqJW'VM.nU8+N]W2cu'ZF7Hdb1,>7nbBcs*6S`jO8o"FlJp^Oh;dejq#CBhn`l,7"0(>GnbDq[
J,~>
p\u5a5;oG>q!mY0h:CEBlLFZLs7l-LU,G!>j6H10\c3?<na#)^b/V-'ZELC/VPKu]TqS.XTDkJc
QBRMprIt?.M#N56Od;5rT(eoVT+[fjU8+N]W3*8/[_9]'eDKT^pp0o>eiu'js7l-Lj5&;Bj6H10
s8VlalCp0r[c?ZCposc4q!m\-eB5eI\[JfIXf/+mUS=HUT`(M^TE(P^P5pU@L&m'creUc=QB[T&
pRqJW'VM.nU8+N]W2cu'ZF7Hdb1,>7nbBcs*7d>4VuQP^lJp^Oh;dejq#CBhn`li,34@l4nbDq[
J,~>
q#:lrYGf_Gq!mY0h:CEBlLFZJrsJPklGKR#eDoiapp^8;q!m\3g<[p]_7@"[YH=h)W2?AcU@t8\
T_k>]T_4ulU8+N]W2cu'ZF7<[`lcW]jm2I3Yl>7*YGf_Gq!mY0h:CEBlLFZJrsJPklGKR#eDoia
poaW3q!m\3hpfup`kf3oZELC1Wi2ejUS@FT!204]!20"Y'r%J!VPpMpXfo%=\\5i!b0Sf'lgab]
rt"_)W6PA;n`o/jf%oBOnbE(_$Ma/V_l0Tbj6H10qYu'~>
q#:lZ'EDtLq!mY0h:CEBlLFZJrsJPkl=U)+\)ZcDpp^8;q!m\3g<[p]_7@"[YH=h)W2?AcU@t8\
T_k>]T_4ulU8+N]W2cu'ZF7<[`lcW]jm2I3Yl>6g'EDtLq!mY0h:CEBlLFZJrsJPkl=U)+\)ZcD
poaW3q!m\3hpfup`kf3oZELC1Wi2ejUS@FT!204]!20"Y'r%J!VPpMpXfo%=\\5i!b0Sf'lgab]
rt!iu!,V]1n`o/jf%oBOnbE(_$Ma/VB)iCMj6H10qYu'~>
q#:lb7Q-2)q!mY0h:CEBlLFZJrsJPkl@_&\^uO_Mpp^8;q!m\3g<[p]_7@"[YH=h)W2?AcU@t8\
T_k>]T_4ulU8+N]W2cu'ZF7<[`lcW]jm2I3Yl>6o7Q-2)q!mY0h:CEBlLFZJrsJPkl@_&\^uO_M
poaW3q!m\3hpfup`kf3oZELC1Wi2ejUS@FT!204]!20"Y'r%J!VPpMpXfo%=\\5i!b0Sf'lgab]
rt"-Z2N.V1n`o/jf%oBOnbE(_$Ma/VKJl*2j6H10qYu'~>
q>V!%\u<ICs7,C7h:C0;lLFZHrsAJjlFs4$g#MAfZi:U3na#;oeBc:W_7dOm[^**=p8S.lVZ!:k
VPs0f&$<%@[^j)f`lcWWhrX1opoXQ,qRWWAr;Z<Pj5&;;j6H10qu6utn`mieYhA(%nYuO"q!m\3
hpfup`kf@%\[JfIXnA[pW2BNh!N)ggWY)A:ZF7<W]tq_2cd^\+jm2I3\,R!8\u<ICs7,C7h:C0;
lLFZHrsAJjlFs4$g#MAfqYu'~>
q>Uuo1]U)@s7,C7h:C0;lLFZHrsAJjl;[g1biA!YZi:U3na#;oeBc:W_7dOm[^**=p8S.lVZ!:k
VPs0f&$<%@[^j)f`lcWWhrX1opoXQ,mkb#8p]'dKj5&;;j6H10qu6utn`j*S--YE7nYuO"q!m\3
hpfup`kf@%\[JfIXnA[pW2BNh!N)ggWY)A:ZF7<W]tq_2cd^\+jm2I3\,R!-1]U)@s7,C7h:C0;
lLFZHrsAJjl;[g1biA!YqYu'~>
q>Uur?T)ius7,C7h:C0;lLFZHrsAJjl?>-ad,XE]Zi:U3na#;oeBc:W_7dOm[^**=p8S.lVZ!:k
VPs0f&$<%@[^j)f`lcWWhrX1opoXQ,nm=:nq>^!Mj5&;;j6H10qu6utn`k<V;UFIhnYuO"q!m\3
hpfup`kf@%\[JfIXnA[pW2BNh!N)ggWY)A:ZF7<W]tq_2cd^\+jm2I3\,R!0?T)ius7,C7h:C0;
lLFZHrsAJjl?>-ad,XE]qYu'~>
q>UrIW4`'.q!-nqf%/[BnbDq[$2F&T[]$^lj6HECrsSVllfI$Xg=4Hk`khA^!P,Z;Z2qDEot:^K
`lcWWg>D#XnbB'_%a(`_r;ZQWj5&;;h<OP*q>Ucrn`dK\\D?31pnduuq!m\3hqHf6ccX8Dot:C:
[f*K4[^l(E$c0j*cd^\&hrX1opp'i/b,DuNs7km>h:C05lLFZFrsAJjl*dP#h;deqqu;0~>
q>Uq8!'L#Uq!-nqf%/[BnbDq[$2F&Q3ri"2j6HECrsSVllfI$Xg=4Hk`khA^!P,Z;Z2qDEot:^K
`lcWWg>D#XnbB'_%V,Q%p]($Rj5&;;h<OP*q>Ucrn`Dn5:#4jppnduuq!m\3hqHf6ccX8Dot:C:
[f*K4[^l(E$c0j*cd^\&hrX1opp'i/A,nads7km>h:C05lLFZFrsAJjjuWQ;h;deqqu;0~>
q>UqY2J)^aq!-nqf%/[BnbDq[$2F&R@lA;^j6HECrsSVllfI$Xg=4Hk`khA^!P,Z;Z2qDEot:^K
`lcWWg>D#XnbB'_%YZfPq>^6Tj5&;;h<OP*q>Ucrn`OC?E8BR>pnduuq!m\3hqHf6ccX8Dot:C:
[f*K4[^l(E$c0j*cd^\&hrX1opp'i/Kf3N<s7km>h:C05lLFZFrsAJjk@6Dhh;deqqu;0~>
q>Up#an>]/n`/HVf%oBVq"Odpq!m:Sdam77nbB*`"ShNQjkodc!S#HoaoTi<p"BQ+jm2I3UAk]!
an>]/n`/HVf%oBVq"Odpq!m:Sdam77nbAaV"ShNQjkoab!S#HoaoTi<p"BQ+jm2I3WrEP)an>]/
n`/HVf%oBVq"Odpq!m:Sdam77nbE%^J,~>
q>Uom@,q:^n`/HVf%oBVq"Odpq!lm2_UdQ'nbB*`"ShNQjkodc!S#HoaoTi<p"BQ+jm2I3UAk\k
@,q:^n`/HVf%oBVq"Odpq!lm2_UdQ'nbAaV"ShNQjkoab!S#HoaoTi<p"BQ+jm2I3WrEOs@,q:^
n`/HVf%oBVq"Odpq!lm2_UdQ'nbE%^J,~>
q>UopK'E=1n`/HVf%oBVq"Odpq!m!Xa4B),nbB*`"ShNQjkodc!S#HoaoTi<p"BQ+jm2I3UAk\n
K'E=1n`/HVf%oBVq"Odpq!m!Xa4B),nbAaV"ShNQjkoab!S#HoaoTi<p"BQ+jm2I3WrEP!K'E=1
n`/HVf%oBVq"Odpq!m!Xa4B),nbE%^J,~>
p&><flJ:%Bh;deqp&>?nlJp^Of%oW]pmV3`q!n=K!V,rErs8/[h:C05j6HF4rsAJcj5&;;h<OP*
O8f7UnbqbSnbACL#k@39f%/[;lM1&P$2EfGh:C05lLFZJs*t~>
p&><flJ:%Bh;deqp&>?nlJp^Of%oW]pmV3`q!n=K!V,rErs8/[h:C05j6HF4rsAJcj5&;;h<OP*
O8f7UnbqbSnbACL#k@39f%/[;lM1&P$2EfGh:C05lLFZJs*t~>
p&><flJ:%Bh;deqp&>?nlJp^Of%oW]pmV3`q!n=K!V,rErs8/[h:C05j6HF4rsAJcj5&;;h<OP*
O8f7UnbqbSnbACL#k@39f%/[;lM1&P$2EfGh:C05lLFZJs*t~>
p&>'_lMfT@j6H1+rrV`Uj8Rj9lLFY#s.95kn`p,0!orS9oD\m^lJqm"!p]=NJcD5>!V,ISh#cX%
nalb^n`o06h#cm3q#15C~>
p&>'_lMfT@j6H1+rrV`Uj8Rj9lLFY#s.95kn`p,0!orS9oD\m^lJqm"!p]=NJcD5>!V,ISh#cX%
nalb^n`o06h#cm3q#15C~>
p&>'_lMfT@j6H1+rrV`Uj8Rj9lLFY#s.95kn`p,0!orS9oD\m^lJqm"!p]=NJcD5>!V,ISh#cX%
nalb^n`o06h#cm3q#15C~>
p&>'_lMffFlLFZ@rrVuclMffFlLFY#s.95kn`p,6!p]=NoD\men`p,6!p]=NJcD5>!V,ISir\N9
q"+Leq!mYKir\N9q#15C~>
p&>'_lMffFlLFZ@rrVuclMffFlLFY#s.95kn`p,6!p]=NoD\men`p,6!p]=NJcD5>!V,ISir\N9
q"+Leq!mYKir\N9q#15C~>
p&>'_lMffFlLFZ@rrVuclMffFlLFY#s.95kn`p,6!p]=NoD\men`p,6!p]=NJcD5>!V,ISir\N9
q"+Leq!mYKir\N9q#15C~>
p&>$^rTXFRq!n@bq!n=J!V,r9s.95jnbq_RnbDYS!Vl3`l2gGMJcD5>!:g$Q!V,sTrrMobrTXFR
q#15C~>
p&>$^rTXFRq!n@bq!n=J!V,r9s.95jnbq_RnbDYS!Vl3`l2gGMJcD5>!:g$Q!V,sTrrMobrTXFR
q#15C~>
p&>$^rTXFRq!n@bq!n=J!V,r9s.95jnbq_RnbDYS!Vl3`l2gGMJcD5>!:g$Q!V,sTrrMobrTXFR
q#15C~>
p&Fs[!;Q$_!;QK^!;M-FT)\`Y!;Q$_!;QK^!;M-FS,`EV!;Q$_!;QK^!;QNmJ,~>
p&Fs[!;Q$_!;QK^!;M-FT)\`Y!;Q$_!;QK^!;M-FS,`EV!;Q$_!;QK^!;QNmJ,~>
p&Fs[!;Q$_!;QK^!;M-FT)\`Y!;Q$_!;QK^!;M-FS,`EV!;Q$_!;QK^!;QNmJ,~>
p&FsbmJm(YJcD>Ar:o[ZqtPaBS,`E]mJm(YrVqB~>
p&FsbmJm(YJcD>Ar:o[ZqtPaBS,`E]mJm(YrVqB~>
p&FsbmJm(YJcD>Ar:o[ZqtPaBS,`E]mJm(YrVqB~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
nc&[ks8N(Ms+14MrrW9$rr@WMJcGNF!!)KfJ,~>
nc&[ks8N(Ms+14MrrW9$rr@WMJcGNF!!)KfJ,~>
nc&[ks8N(Ms+14MrrW9$rr@WMJcGNF!!)KfJ,~>
q#:DZ&X31n!s&B$!.k06rrE*^qu6[\!;HKq!<<'!JcCZ.r;Zp'(hW(,rr<&fs*t~>
q#:DZ&X31n!s&B$!.k06rrE*^qu6[\!;HKq!<<'!JcCZ.r;Zp'(hW(,rr<&fs*t~>
q#:DZ&X31n!s&B$!.k06rrE*^qu6[\!;HKq!<<'!JcCZ.r;Zp'(hW(,rr<&fs*t~>
q#:CRW&+DP!!%TMOT,@^/'@hj!mE4rp\t<qs8N(Ms,6mW!<)p!k&WNUrr<&fs*t~>
q#:CRW&+DP!!%TMOT,@^/'@hj!mE4rp\t<qs8N(Ms,6mW!<)p!k&WNUrr<&fs*t~>
q#:CRW&+DP!!%TMOT,@^/'@hj!mE4rp\t<qs8N(Ms,6mW!<)p!k&WNUrr<&fs*t~>
q>UTJ5lWakrr3'#s8N)urrfS]"Wj97rrfS[#8VW:s.fSq!3K&qrt%u+!<<)[;[EV!lMpkas8N)u
rrei9"X9B6rriCd(]tcVrr3&s&b1GPUAk2nqu6X$rr3+k+pB0Yrr3-%S0\C_U@eJ9~>
q>UTJ5lWakrr3'#s8N)urrfS]"Wj97rrfS[#8VW:s.fSq!3K&qrt%u+!<<)[;[EV!lMpkas8N)u
rrei9"X9B6rriCd(]tcVrr3&s&b1GPUAk2nqu6X$rr3+k+pB0Yrr3-%S0\C_U@eJ9~>
q>UTJ5lWakrr3'#s8N)urrfS]"Wj97rrfS[#8VW:s.fSq!3K&qrt%u+!<<)[;[EV!lMpkas8N)u
rrei9"X9B6rriCd(]tcVrr3&s&b1GPUAk2nqu6X$rr3+k+pB0Yrr3-%S0\C_U@eJ9~>
q>US?WrJK0rr3Z4s8N*!s0Wofs5mejs1(,8s6*7-JcDPG-ij9!fDkm&/cPhNs!u+fqfcHJrrE*!
!<<(t-.Dn?,,G8=!)`:d;kj9k!MX:Zs.TGm!<)p3jE!Zfs/76ms3Dmbs8N'Fe,RlCYP%pG~>
q>US?WrJK0rr3Z4s8N*!s0Wofs5mejs1(,8s6*7-JcDPG-ij9!fDkm&/cPhNs!u+fqfcHJrrE*!
!<<(t-.Dn?,,G8=!)`:d;kj9k!MX:Zs.TGm!<)p3jE!Zfs/76ms3Dmbs8N'Fe,RlCYP%pG~>
q>US?WrJK0rr3Z4s8N*!s0Wofs5mejs1(,8s6*7-JcDPG-ij9!fDkm&/cPhNs!u+fqfcHJrrE*!
!<<(t-.Dn?,,G8=!)`:d;kj9k!MX:Zs.TGm!<)p3jE!Zfs/76ms3Dmbs8N'Fe,RlCYP%pG~>
qYq-*0E(qB*<#p;!<<'!s8P9pqu6[\fDbdNnJHiWV#L`$s0#u5s(8D5!;ld(r=/f,!<<'!s8P'o
rr3;=2ZNdWh>dMr0E1tP6IgYeU&Xui"WHD#s#@gnrrp+ps8N)7rr3#94SA`)~>
qYq-*0E(qB*<#p;!<<'!s8P9pqu6[\fDbdNnJHiWV#L`$s0#u5s(8D5!;ld(r=/f,!<<'!s8P'o
rr3;=2ZNdWh>dMr0E1tP6IgYeU&Xui"WHD#s#@gnrrp+ps8N)7rr3#94SA`)~>
qYq-*0E(qB*<#p;!<<'!s8P9pqu6[\fDbdNnJHiWV#L`$s0#u5s(8D5!;ld(r=/f,!<<'!s8P'o
rr3;=2ZNdWh>dMr0E1tP6IgYeU&Xui"WHD#s#@gnrrp+ps8N)7rr3#94SA`)~>
qYpT*IfB?T@($%7!<<'!s8N9$qu?U"!X/Q+JcDPG)Z]p<0B1tEs8N*!s56TX!!!-$rrE*!!<<'&
rVlg"r<`N(!<)p#35GRR5(Wa,rr<&trs%Of@fQH3rVlg#r<`N(!<)os!W<>ts*t~>
qYpT*IfB?T@($%7!<<'!s8N9$qu?U"!X/Q+JcDPG)Z]p<0B1tEs8N*!s56TX!!!-$rrE*!!<<'&
rVlg"r<`N(!<)p#35GRR5(Wa,rr<&trs%Of@fQH3rVlg#r<`N(!<)os!W<>ts*t~>
qYpT*IfB?T@($%7!<<'!s8N9$qu?U"!X/Q+JcDPG)Z]p<0B1tEs8N*!s56TX!!!-$rrE*!!<<'&
rVlg"r<`N(!<)p#35GRR5(Wa,rr<&trs%Of@fQH3rVlg#r<`N(!<)os!W<>ts*t~>
qYpOqqu@($s8N*!rrE*!#Q=Q"!<`4Ms.B<1!<<)&F*"PbrrE*!2MCo+rrE*!!<<'!s8N9$rr3-"
$31#*rVm#(8,n7GJcDDC!!)rs"o/Q+rrrB#rrr?-s8N)trr3#t$2+=K~>
qYpOqqu@($s8N*!rrE*!#Q=Q"!<`4Ms.B<1!<<)&F*"PbrrE*!2MCo+rrE*!!<<'!s8N9$rr3-"
$31#*rVm#(8,n7GJcDDC!!)rs"o/Q+rrrB#rrr?-s8N)trr3#t$2+=K~>
qYpOqqu@($s8N*!rrE*!#Q=Q"!<`4Ms.B<1!<<)&F*"PbrrE*!2MCo+rrE*!!<<'!s8N9$rr3-"
$31#*rVm#(8,n7GJcDDC!!)rs"o/Q+rrrB#rrr?-s8N)trr3#t$2+=K~>
qu6`c%Jg#'$i:D$rrE*!!<<'_\blC-5,*W.TDnlkrr<!Vrr3'#s8N3#rsR$Ns8N*!rrE*!2Sf=l
"McqqrrDus![?5]JcDAB!!)rs"n`-#s#.[lrrp1rs8N)7rr3#94SA`)~>
qu6`c%Jg#'$i:D$rrE*!!<<'_\blC-5,*W.TDnlkrr<!Vrr3'#s8N3#rsR$Ns8N*!rrE*!2Sf=l
"McqqrrDus![?5]JcDAB!!)rs"n`-#s#.[lrrp1rs8N)7rr3#94SA`)~>
qu6`c%Jg#'$i:D$rrE*!!<<'_\blC-5,*W.TDnlkrr<!Vrr3'#s8N3#rsR$Ns8N*!rrE*!2Sf=l
"McqqrrDus![?5]JcDAB!!)rs"n`-#s#.[lrrp1rs8N)7rr3#94SA`)~>
qu6\\@f66B@[$fls8N*!s0Wofs5[nos1T>PrVaZ3JcDPG!!)ip)?Bg;.@L4o1B@IT!<<'!s8Sr?
dJqT:VZ6Yrr;Qh^0o1D\TDnlkrVmSk>$h7/VBXFfd3,=drr=@ms3i@!p&BO~>
qu6\\@f66B@[$fls8N*!s0Wofs5[nos1T>PrVaZ3JcDPG!!)ip)?Bg;.@L4o1B@IT!<<'!s8Sr?
dJqT:VZ6Yrr;Qh^0o1D\TDnlkrVmSk>$h7/VBXFfd3,=drr=@ms3i@!p&BO~>
qu6\\@f66B@[$fls8N*!s0Wofs5[nos1T>PrVaZ3JcDPG!!)ip)?Bg;.@L4o1B@IT!<<'!s8Sr?
dJqT:VZ6Yrr;Qh^0o1D\TDnlkrVmSk>$h7/VBXFfd3,=drr=@ms3i@!p&BO~>
qu6[T_YjE<_DD2ls8N)urrfMY"!4'5rrp>-#7!T+JcDPG!!)ip%KQP/a=RK8PlUja!<<'!rr3+k
+oigWrr2rur;Qio$0m*ATE"ii!s9B[p&=soS0@qWSGrNl!1>#5,,4`0J,~>
qu6[T_YjE<_DD2ls8N)urrfMY"!4'5rrp>-#7!T+JcDPG!!)ip%KQP/a=RK8PlUja!<<'!rr3+k
+oigWrr2rur;Qio$0m*ATE"ii!s9B[p&=soS0@qWSGrNl!1>#5,,4`0J,~>
qu6[T_YjE<_DD2ls8N)urrfMY"!4'5rrp>-#7!T+JcDPG!!)ip%KQP/a=RK8PlUja!<<'!rr3+k
+oigWrr2rur;Qio$0m*ATE"ii!s9B[p&=soS0@qWSGrNl!1>#5,,4`0J,~>
JcC<$f)GcYAqBu5s7lVE~>
JcC<$f)GcYAqBu5s7lVE~>
JcC<$f)GcYAqBu5s7lVE~>
JcC<$fDbp;0BRicJcGNFJ,~>
JcC<$fDbp;0BRicJcGNFJ,~>
JcC<$fDbp;0BRicJcGNFJ,~>
JcC<$f`)!X&\3JmJcGKEJ,~>
JcC<$f`)!X&\3JmJcGKEJ,~>
JcC<$f`)!X&\3JmJcGKEJ,~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
JcC<$JcC<$bQ!(~>
%%EndData
showpage
%%Trailer
end
%%EOF
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

@@ -0,0 +1,968 @@
%!PS-Adobe-3.0 EPSF-3.0
%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
%%Title: mysigningillustrated1.eps
%%CreationDate: Sat Aug 15 23:25:21 2015
%%DocumentData: Clean7Bit
%%LanguageLevel: 2
%%Pages: 1
%%BoundingBox: 14 14 938 203
%%EndComments
%%BeginProlog
% Use own dictionary to avoid conflicts
10 dict begin
%%EndProlog
%%Page: 1 1
% Translate for offset
14.173228346456694 14.173228346456694 translate
% Translate to begin of first scanline
0 187.99775466853211 translate
922.98897637795278 -187.99775466853211 scale
% Image geometry
923 188 8
% Transformation matrix
[ 923 0 0 188 0 0 ]
% Strings to hold RGB-samples per scanline
/rstr 923 string def
/gstr 923 string def
/bstr 923 string def
{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
true 3
%%BeginData: 51452 ASCII Bytes
colorimage
JbO`iJbPl4l+[5UJbO`iJbS'rJ,~>
JbO`iJbPl4l+[5UJbO`iJbS'rJ,~>
JbO`iJbPl4l+[5UJbO`iJbS'rJ,~>
JbO`iJbQ\K"6[G6[etUN5hcVG3W),B@/YNVVSBmsJbO`iJbSg2J,~>
JbO`iJbQ\K"6[G6[etUN5hcDA3W),B@/YNVVSBmsJbO`iJbSg2J,~>
JbO`iJbQ\K"6[G6[etUN5hcDA3W),B@/YNVVSBmsJbO`iJbSg2J,~>
JbO`iJbR+WrkAYlIXYBa5To>!)^%.+3B9>b3C#Sb5WLhi3B9>b3C#Sb5WLhi3U-tN5R)=ZI\slp
eq*RHpOW'h~>
JbO`iJbR+WrkAYlIXYBa5To=p)^%.+3B9>b3C#Sb5WLhi3B9>b3C#Sb5WLhi3U-bH5R)I^I\slp
eq*RHpOW'h~>
JbO`iJbR+WrkAYlIXYBa5To=p)^%.+3B9>b3C#Sb5WLhi3B9>b3C#Sb5WLhi3U-bH5R)XcI\slp
eq*RHpOW'h~>
JbO`iJbRIa#OfFBIXXjK+nH$N0KDC0IWU%9[_K8c[_L_QpC?Ap^UCnc^OCWRIR#nY&+^,4-pVHp
S&!U+pOW'rpOE~>
JbO`iJbRIa#OfFBIXXjK*:j:C0KDC3IX$==[_K8c[_L_QpC?Ap^UCnc^OCWVIR#nY&G$#/-pVHp
S&!U+pOW'rpOE~>
JbO`iJbRIa#OfFBIXXjK*:j:C0KDC7IXQ[B[_K8c[_L_QpC?Ap^UCnc^OCW[IR#nY&G$#/-pVHp
S&!U+pOW'rpOE~>
JbO`iJbRais1\\sITSi`pBh3L3C$DiFH3fE]XIPAkI/./IWU$!3C!lF$3gr)8Uc-'^Oc-1pQG9$~>
JbO`iJbRais1\\sITSi`pB1dF3C$DiG`K5I^p`tEkI/./IX$<%3C!lF"9o?$8Uc-'^Oc-1pQG9$~>
JbO`iJbRais1\\sITSi`pB1dF3C$DiI?(bN`O>LJkI/./IXQZ*3C!lF"9oH'8Uc-'^Oc-1pQG9$~>
JbO`iJbS!p"Mh3i8NHMP"r93qIX]&?T(`HP[]adl3C*<G"=QNaQ+qnEJbPT,J,~>
JbO`iJbS!p"Mh3i8NHMJ"rfR!IX]&?T(`HP[]aps3C*<A"=QNaQ+qnEJbPT,J,~>
JbO`iJbS!p"Mh3i8NHMJ"s>p&IX]&?T(`HP[]b+)3C*<A"=QNaQ+qnEJbPT,J,~>
JbO`iJbS7""n03dIQ8PH$NE*hIWW$,kDf3?]YB2M:d>fm$3_#3E/q^%JbO`iT_Es~>
JbO`iJbS7""n03dIQ8PH"TLIbIX&<0kDf3?^qYVQ:d>ur"9fN&E/q^%JbO`iT_Es~>
JbO`iJbS7""n03dIQ8PH"TLIbIXSZ5kDf3?`P7.V:d?0""9f]+E/q^%JbO`iT_Es~>
JbO`iJbSI("kL,2?pG1t$3W@cI\tA@p\aahhnOnb3=Yim"V!J*L;2o&pOW(9pOE~>
JbO`iJbSI("kL,2?pG4u"9^_]I\tA@p\aahhnOne3=boh"V*P+L;2o&pOW(9pOE~>
JbO`iJbSI("kL,2?pG>#"9^_]I\tA@p\aahhnOnk3>),k"VEb.L;2o&pOW(9pOE~>
JbO`iJbSX-"5fu)3Vj'd#WZjQ^Xdu1nFm+T^Q*Cr)#=RA3G"])JbO`iX7q,~>
JbO`iJbSX-"5fu)3Vij^#WZsT^Xdu1nFm+T^Q*P!)#=@;3G=o,JbO`iX7q,~>
JbO`iJbSX-"5fu)3Vij^#W[0Z^Xdu1nFm+T^Q*_&)#=@;3Gb20JbO`iX7q,~>
JbO`iJbSg2"R!4B5S<o$"<]^RS$mnBj7``G^Q*b'+SuKJ5^op"JbO`iYkNY~>
JbO`iJbSg2"R!4B5Sa2""<]^RS$mnBj7``G^Q*b'+Su9D5^op"JbO`iYkNY~>
JbO`iJbSg2"R!4B5T9P'"<]^RS$mnBj7``G^Q*b'+Su9D5^op"JbO`iYkNY~>
JbO`iJbSs6"2BRe+T)QK+Adg$JbRai"2Bk(+T)QK&P7_lJbO`i[.f(~>
JbO`iJbSs6"2BRe+T)?E+Adg$JbRai"2Bk(+T)?E'hO.pJbO`i[.f(~>
JbO`iJbSs6"2BRe+T)?E+Adg$JbRai"2Bk(+T)?E)G,[uJbO`i[.f(~>
JbO`iJbT-;"4i`F./XDS(.j7hJbRC_"1EYD(AnLA-tS[gJbO`i\bCU~>
JbO`iJbT-;"4i`F./X2M)bGdmJbRC_"1EYD)uKg@-tnmjJbO`i\bCU~>
JbO`iJbT-;"4i`F./X2M+A%<rJbRC_"1EYD+T)?E-u>0nJbO`i\bCU~>
JbO`iJbK9(Rogj4$3V,9I^\]rpVce.hkk?Qq[*BiL<Y;DJbQkPJ,~>
JbO`iJbK9(Rog[/"9]Z8I^\]rpVce.hkkHTqZHscL<Y;DJbQkPJ,~>
JbO`iJbK9(Rog[/"9]i=I^\]rpVce.hkkZZqZHscL<Y;DJbQkPJ,~>
JbO`iL%b],Rp[B;$3Mu$[dSeA](Z<BPuoI5$3LlBVWWO)JbR"TJ,~>
JbO`iL%b],Rp[B;"9U?![dSeA](Z<BPuoI5"9TB@VWWO)JbR"TJ,~>
JbO`iL%b],Rp[B;"9U?'[dSeA](Z<BPuoI5"9TQEVWWO)JbR"TJ,~>
JbO`iM>%,8VI1PF$3DVp[Xn1XpB0T^?mts\![r0!JbO`i`:nc~>
JbO`iM>%,8VI1PF"9Kum[Xn1XpB0T^?mtsV![r<%JbO`i`:nc~>
JbO`iM>%,8VI1PF"9Kus[Xn1XpB0T^?mtsV![rK*JbO`i`:nc~>
JbO`iN;!CNBINfd""7idmt(55pB0!3:_3L3!_JF'JbO`iaS12~>
JbO`iN;!CNBINf^""7idmt(55pB0!3:_<R.!_JF'JbO`iaS12~>
JbO`iN;!CNBINf^""7idmt(55pB0!3:_<R.!_JF'JbO`iaS12~>
JbO`iOS8h>Pu.Dd"!D!KkCNB%pB&Tg5lD#l+^h29JbO`ibkHV~>
JbO`iOS8h>Pu.D^"!D!KkCNB%pB&Tg5lCff*+5Z4JbO`ibkHV~>
JbO`iOS8h>Pu.D^"!D!KkCNB%pB&Tg5lCff*+5Z4JbO`ibkHV~>
JbO`iPP5.0L+rT6!sh`#kCNAspB0lmBINfd!_JmMJbO`ichDq~>
JbO`iPP5.0L+rT0!sh`#kCNAspB0lmBINf^!_JmMJbO`ichDq~>
JbO`iPP5.0L+rT0!sh`#kCNAspB0lmBINf^!_JmMJbO`ichDq~>
JbO`iQM1HqE$>2f!Y8\IJbPB&"5fSE&,ln;8Xo1"pOW(epOE~>
JbO`iQM1HqEub/^!YAbJJbPB&"5fSE&H2e68Xo1"pOW(epOE~>
JbO`iQM1HqGoZed!YAbJJbPB&"5fSE&H2e68Xo1"pOW(epOE~>
JbO`iRJ-c[?kNAF![)I,JbP,t!j>]:rX&]uS*tq&JbR^hJ,~>
JbO`iRJ-c[?krYD![)R/JbP,t!j>];rWE9oS*tq&JbR^hJ,~>
JbO`iRJ-c[?lK"I![)d5JbP,t!j>];rWE9oS*tq&JbR^hJ,~>
JbO`iSG*&];#L_&0TP%ApP/DDVI0l4$3Eegmt(4apXo4j~>
JbO`iSG*&];#LLu0TP%ApP/DDVI0o5"9M/amt(4apXo4j~>
JbO`iSG*&];#LLu0TP%ApP/DDVI1#8"9M/amt(4apXo4j~>
JbO`iTD&E<BHd?^!_JmEJbO`i"8A[#%K6\98Xo1"pOW(npOE~>
JbO`iTD&E<BH7!S!_JmEJbO`i"8A[#%fQS48Xo1"pOW(npOE~>
JbO`iTD&E<BH7!S!_JmEJbO`i"8A[#&cMn78Xo1"pOW(npOE~>
JbO`iUA"`XRlUHT!AL!opOW)7pB%4"%K6\88Z%(5JbS!pJ,~>
JbO`iUA"`XRlUHN!AL!opOW)7pB%4"%fQS38Z%(5JbS!pJ,~>
JbO`iUA"`XRlUHN!AL!opOW)7pB%4"&cMn68Z%(5JbS!pJ,~>
JbO`iV"Y,0?k!GM%rgEWpOW)2pBRR'%13FSE9DfHJbS*sJ,~>
JbO`iV"Y,0?k*;B&9H`[pOW)2pBRR'%KlnLF6A,KJbS*sJ,~>
JbO`iV"Y,0?kEME&:*/apOW)2pBRR'&Hi4TH09bQJbS*sJ,~>
JbO`iVtU8UL*6I&!D0/>pOW),pAq-VrX&[&[Xn1(pYtpt~>
JbO`iVtU8UL*6Hu!D0/>pOW),pAq-VrWE6u[Xn1(pYtpt~>
JbO`iVtU8UL*6Hu!D0/>pOW),pAq-VrWE6u[Xn1(pYtpt~>
JbO`iWV6Fs;#Ue'(5D"'pOW)(pBT8g%13FcQ1':uJbS:#J,~>
JbO`iWV6Fs;#US!)i!O,pOW)(pBT8j%KlnSQ1':uJbS:#J,~>
JbO`iWV6Fs;#US!+GT'1pOW)(pBT8p&Hi4VQ1':uJbS:#J,~>
JbO`iX7lXa+T;]K=LNeTJbS7"!LkM:$3=8RJbO`ikP'K~>
JbO`iX7lXa+T;KE=LNeTJbS7"!LkM:"9DWLJbO`ikP'K~>
JbO`iX7lXa+T;KE=LNeTJbS7"!LkM:"9DWLJbO`ikP'K~>
JbO`iY4i1iIN]%*(7"?>pOW(spBJE@#RUnmRt:<bpZ_F&~>
JbO`iY4i1iIN/In)jTlCpOW(spBJE@#Qt8aRt:<bpZ_F&~>
JbO`iY4i1iIN/In+I2DHpOW(spBJE@#Qt8aRt:<bpZ_F&~>
JbO`iYkJ@ZBFP:U5dl7<JbRpn#NpOY$475]hgtNQp[%X)~>
JbO`iYkJ@ZBFY.J5dl7<JbRpn#NpOY"9]3UhgtNQp[%X)~>
JbO`iYkJ@ZBFt@M5dl7<JbRpn#NpOY"9]<^hgtNQp[%X)~>
JbO`iZM+CW@/^K6=Or&tJbRai!N&Hf$3=8dJbO`ime;5~>
JbO`iZM+CW@/^90=Or&tJbRai!N&Hf"9DW^JbO`ime;5~>
JbO`iZM+CW@/^90=Or&tJbRai!N&Hf"9DW^JbO`ime;5~>
JbO`i[.ad^?j[5HId_j^JbRUe#1d`&$49nmJbO`inFqG~>
JbO`i[.ad^?j$T<Id_j^JbRUe#1di*"9_iaJbO`inFqG~>
JbO`i[.ad^?j$T<Id_j^JbRUe#1e&3"9_iaJbO`inFqG~>
JbO`i[eBgc@/^K6FROeTJbRIa#4HXE$4:2(JbO`io(RY~>
JbO`i[eBgc@/^90Gjg4XJbRIa#4HdJ"9`,qJbO`io(RY~>
JbO`i[eBgc@/^90IIDa]JbRIa#4HsO"9`,qJbO`io(RY~>
JbO`i\G$$mIf9ZTA`nX=JbR=]#4H42$4:b8JbO`io_3k~>
JbO`i\G$$mIf9HNB]js@JbR=]#4H42"9`],JbO`io_3k~>
JbO`i\G$$mIf9HND!-BDJbR=]#4H42"9`],JbO`io_3k~>
JbO`i\b?8f&IJkVkCNAYpVuq3kAU\6$>AURpOW)4pOE~>
JbO`i\b?8f'a+YQkCNAYpVuq3kAU\0"DHtLpOW)4pOE~>
JbO`i\b?8f)?^1ZkCNAYpVuq3kAU\0"DHtLpOW)4pOE~>
JbO`i]CuK4+:8H@eq*RHpVQY/hf&i.%=[bspOW)6pOE~>
JbO`i]CuK4+9Vg4eq*RHpVQY/hfB&+%Y!ktpOW)6pOE~>
JbO`i]CuK4+9Vg4eq*RHpVQY/hg#J1&Us2"pOW)6pOE~>
JbO`i^%V]n8I>eQ]Rfg.pV-A+hd?cu%=[bspOW)8pOE~>
JbO`i^%V]n8H]/E^k)62pV-A+hd?Qi%Y!ktpOW)8pOE~>
JbO`i^%V]n8H]/E`I[c7pV-A+hd?Qi&Us2"pOW)8pOE~>
JbO`i^@qbb%13W8JbO`i](ZBZ?j[5MNe-qUp\X]8~>
JbO`i^@qbb%Km?4JbO`i](ZBZ?j$TBNe-qUp\X]8~>
JbO`i^@qbb&HiZ7JbO`i](ZBZ?j$TENe-qUp\X]8~>
JbO`i_"S#`3=6*qmt(4apUBl#emJgl0[51CJbT-;J,~>
JbO`i_"S#`3<TIemt(4apUBl#emJU`0[51CJbT-;J,~>
JbO`i_"S#`3<TIemt(4apUBl#emJU`0[51CJbT-;J,~>
JbO`i_=n(e%146&JbO`iZhFX93=6+4mt(4ap]'u<~>
JbO`i_=n(e%Kmp!JbO`iZhFX=3<TJ+mt(4ap]'u<~>
JbO`i_=n(e&Hj6$JbO`iZhFXB3<TJ1mt(4ap]'u<~>
JbO`i_tO;b3=6+BJbO`iYP/0K%gioGJbO`i!;D!~>
JbO`i_tO;b3<TJ6JbO`iYP/0K&-N`FJbO`i!;D!~>
JbO`i_tO;b3<TJ6JbO`iYP/0K&-NoKJbO`i!;D!~>
JbO`i`:jD(&IL5CJbO`iXnN"^5RIi`kCNAZpOE~>
JbO`i`:jD('a-5AJbO`iXnN"^5Qh3TkCNAZpOE~>
JbO`i`:jD()?_bFJbO`iXnN"^5Qh3TkCNAZpOE~>
JbO`i`qKW)8I>fHJbO`iWV6P!&IJl"JbOfkJ,~>
JbO`i`qKW)8H]0@JbO`iWV6P!'a+YoJbOfkJ,~>
JbO`i`qKW)8H]0EJbO`iWV6P!)?^1tJbOfkJ,~>
JbO`ia7f_]+:979JbO`iVtUA`?j[5`^k)66pOE~>
JbO`ia7f_]+9Wh3JbO`iVtUA`?j$TT^k)66pOE~>
JbO`ia7f_]+9Wh3JbO`iVtUA`?j$TT^k)66pOE~>
JbO`iaS,e6%15p_pOW(6pBA2X$4:20JbOroJ,~>
JbO`iaS,e6%KoUZpOW(6pBA>\"9`-$JbOroJ,~>
JbO`iaS,e6&Hkp]pOW(6pBAMa"9`-$JbOroJ,~>
JbO`ianGmV$4;65pOW(3pB63V$5'.dpPAQo~>
JbO`ianGmY"9a1)pOW(3pB63V";RebpPAQo~>
JbO`ianGm_"9a1)pOW(3pB63V"<+.gpPAQo~>
JbO`ib4c!4$4<Y]pOW(1pB8`4$4:0lpPJWp~>
JbO`ib4c!("9bTQpOW(1pB8`4"9`+`pPJWp~>
JbO`ib4c!("9bTQpOW(1pB8`4"9`+`pPJWp~>
JbO`ibkD8/3=6PHJbO`iS+d&T#RW!2JbP)sJ,~>
JbO`ibkD8/3<U;GJbO`iS+d&T#QuR,JbP)sJ,~>
JbO`ibkD8/3<UJLJbO`iS+d&T#QuR,JbP)sJ,~>
JbO`ic1_A00FAr[JbO`iRJ-j8-jgE\JbP,tJ,~>
JbO`ic1_A00E`NUJbO`iRJ-j8-j1$WJbP,tJ,~>
JbO`ic1_A00E`NUJbO`iRJ-j8-j1-ZJbP,tJ,~>
JbO`icM%J!-jhBkJbO`iQM1J`$4:0lpPnot~>
JbO`icM%J!-j1seJbO`iQM1J`"9`+`pPnot~>
JbO`icM%J!-j1seJbO`iQM1J`"9`+`pPnot~>
JbO`ich@S*+::-tJbO`iPkP<M#RW!CJbP6"J,~>
JbO`ich@S*+9X^nJbO`iPkP<M#QuR=JbP6"J,~>
JbO`ich@S*+9X^nJbO`iPkP<M#QuR=JbP6"J,~>
JbO`id.[Y20FC'rpOW($pB9%q$6dZFpQ5-"~>
JbO`id.[Y20EaXlpOW($pB9%q"<>[;pQ5-"~>
JbO`id.[Y20EaXlpOW($pB9%q"<>[;pQ5-"~>
JbO`id.[T,$7LK"JbP6""QrSg$DrCKOnXA~>
JbO`id.[T,"=SiqJbP6""QrSa"K$bEOnXA~>
JbO`id.[T,"=SiqJbP6""QrSa"K$bEOnXA~>
JbO`idJ!`>$6ekhpOW'upB,LE$BB]3P4sJ~>
JbO`idJ!`>"<?l]pOW'upB,XC"HJ'-P4sJ~>
JbO`idJ!`>"<?l]pOW'upB,gH"HJ'-P4sJ~>
JbO`ide<i_$4l$FpOW'spB.9"$=JGZPP9S~>
JbO`ide<i_";'IApOW'spB.8q"CQfTPP9S~>
JbO`ide<i_";'IApOW'spB.8q"CQfTPP9S~>
JbO`ie+WsJ$4>%/pOW'qpB/YW$:'1:PkT\~>
JbO`ie+WsJ"9cu#pOW'qpB/YW"@.P4PkT\~>
JbO`ie+WsJ"9cu#pOW'qpB/YW"@.P4PkT\~>
JbO`ieFs'r$4<8RpOW'opB9A-$:+'2pQkQ(~>
JbO`ieFs'r"9b3FpOW'opB9A-"@2F,pQkQ(~>
JbO`ieFs'r"9b3FpOW'opB9A-"@2F,pQkQ(~>
JbO`ieFs"m$=JGZJbOlm"Re#O..;`]QhQ"~>
JbO`ieFs"m"CQfTJbOlm"Re#I..;`]QhQ"~>
JbO`ieFs"m"CQfTJbOlm"Re#I..;`]QhQ"~>
JbO`ieb9,V$8@&*JbOfk"Re#O.-H0UR.l+~>
JbO`ieb9,Y">GE$JbOfk"Re#I.-H0UR.l+~>
JbO`ieb9,]">GE$JbOfk"Re#I.-H0UR.l+~>
JbO`if(T9k$4>sIpOW'jpB9YE$6e;XpR1c+~>
JbO`if(T9k"9dn=pOW'jpB9YE"<?<MpR1c+~>
JbO`if(T9k"9dn=pOW'jpB9YE"<?<MpR1c+~>
JbO`if(T5"$@[R#JbO`is7ZUe$5W)]pR:i,~>
JbO`if(T5""FbprJbO`is7ZUe"<6f\pR:i,~>
JbO`if(T5""FbprJbO`is7ZUe"<d/apR:i,~>
JbO`ifCo?M$7LK"JbO`irq?Qa8I?m7JbPZ.J,~>
JbO`ifCo?M"=SiqJbO`irq?Qa8H^I1JbPZ.J,~>
JbO`ifCo?M"=SiqJbO`irq?Qa8H^I1JbPZ.J,~>
JbO`ifCo=Y$I+.rJbO`irV$E_-ji4jpRCo-~>
JbO`ifCo=]"O2MlJbO`irV$E_-j2edpRCo-~>
JbO`ifCo=b"O2MlJbO`irV$E_-j2edpRCo-~>
JbO`if_5H-$;#gCJbO`iqtC3]-ji4jpRLu.~>
JbO`if_5H-"A+1=JbO`iqtC3]-j2edpRLu.~>
JbO`if_5H-"A+1=JbO`iqtC3]-j2edpRLu.~>
JbO`if_5Fj$I+.rJbO`iqY(*L+UV>&pRV&/~>
JbO`if_5Fe"O2MlJbO`iqY(*L*!BAppRV&/~>
JbO`if_5Fe"O2MlJbO`iqY(*L*!BAppRV&/~>
JbO`ig%PQO$;#gCJbO`iq"FmJ%gm9,pR_,0~>
JbO`ig%PQO"A+1=JbO`iq"FmJ&-R'*pR_,0~>
JbO`ig%PQO"A+1=JbO`iq"FmJ&-R90pR_,0~>
JbO`ig%POr$FYN[JbO`ip\+d*#R[bdpRh21~>
JbO`ig%POr"L`mUJbO`ip\+d*#R%>^pRh21~>
JbO`ig%POr"L`mUJbO`ip\+d*#R%>^pRh21~>
JbO`ig@kZa$6XooJbO`ip%JQX$4>%/pRq82~>
JbO`ig@kZa"<`9iJbO`ip%JQX"9cu#pRq82~>
JbO`ig@kZa"<`9iJbO`ip%JQX"9cu#pRq82~>
JbO`ig@kYj$@[R#JbO`io_/H$$6\egpS%>3~>
JbO`ig@kYn"FbprJbO`io_/H$"<d/apS%>3~>
JbO`ig@kYs"FbprJbO`io_/H$"<d/apS%>3~>
JbO`ig@kXl$I+.rJbO`ioCi;O$:'1:U%a'~>
JbO`ig@kXg"O2MlJbO`ioCi;O"@.P4U%a'~>
JbO`ig@kXg"O2MlJbO`ioCi;O"@.P4U%a'~>
JbO`ig\1cb$7LK"JbO`io(N7L+UW[LpS.D4~>
JbO`ig\1cb"=SiqJbO`io(N7L*!C_ApS.D4~>
JbO`ig\1cb"=SiqJbO`io(N7L*!C_ApS.D4~>
JbO`ig\1c($?1RjJbO`inb3.1$4=FspS7J5~>
JbO`ig\1c("ET.gJbO`inb3.1"9cMkpS7J5~>
JbO`ig\1c("F5RmJbO`inb3.1"9c\ppS7J5~>
JbO`ig\1b@$Eo$TJbO`inFm!5$6XooU\B9~>
JbO`ig\1b@"L!CNJbO`inFm!5"<`9iU\B9~>
JbO`ig\1b@"L!CNJbO`inFm!5"<`9iU\B9~>
JbO`ig\1al$J'e&JbO`in+QlD$?1RjV"]B~>
JbO`ig\1al"P/.uJbO`in+QlD"ET.gV"]B~>
JbO`ig\1al"P/.uJbO`in+QlD"F5RmV"]B~>
JbO`ih"Lm=$4VR\JbO`ime6h.#R\@upSIV7~>
JbO`ih"Lm=":g"WJbO`ime6h.#R%qopSIV7~>
JbO`ih"Lm=";-4ZJbO`ime6h.#R%qopSIV7~>
JbO`ih"Llb$:'1:JbO`imIp[I$7LK"V>#K~>
JbO`ih"Llb"@.P4JbO`imIp[I"=SiqV>#K~>
JbO`ih"Llb"@.P4JbO`imIp[I"=SiqV>#K~>
JbO`ih"Ll>$<VlRJbO`im.UQH$?1RjVY>T~>
JbO`ih"Ll>"B^6LJbO`im.UQH"ET.gVY>T~>
JbO`ih"Ll>"B^6LJbO`im.UQH"F5RmVY>T~>
JbO`ih"Ll8$@[R#JbO`im.UVF#RK==pS[b9~>
JbO`ih"Ll8"FbprJbO`im.UVF#RK==pS[b9~>
JbO`ih"Ll8"FbprJbO`im.UVF#RK==pS[b9~>
JbO`ih"Ll!$AO-+JbO`ilh:I0$;#gCVtY]~>
JbO`ih"Ll!"GVL%JbO`ilh:I0"A+1=VtY]~>
JbO`ih"Ll!"GVL%JbO`ilh:I0"A+1=VtY]~>
JbO`ih"LkX$CcV@JbO`ilLt>l$FYN[W:tf~>
JbO`ih"LkX"Iju:JbO`ilLt>p"L`mUW:tf~>
JbO`ih"LkX"Iju:JbO`ilLt>u"L`mUW:tf~>
JbO`ih"Lk1$DrCKJbO`ilLt@W$7LK"W:tf~>
JbO`ih"Lk1"K$bEJbO`ilLt@W"=SiqW:tf~>
JbO`ih"Lk1"K$bEJbO`ilLt@W"=SiqW:tf~>
JbO`ih"Lk($CcV@JbO`il1Y6-$AO-+WV:o~>
JbO`ih"Lk("Iju:JbO`il1Y6-"GVL%WV:o~>
JbO`ih"Lk("Iju:JbO`il1Y6-"GVL%WV:o~>
JbO`ih"LjU$>>"bJbO`il1Y8($5e?gWV:o~>
JbO`ih"LjU"DEA\JbO`il1Y8(";l^aWV:o~>
JbO`ih"LjU"DEA\JbO`il1Y8(";l^aWV:o~>
JbO`ih=h#3$475npOW'ipZV>Q8IC3'pT!t<~>
JbO`ih=h#3"9]3cpOW'ipZV>Q8Had!pT!t<~>
JbO`ih=h#3"9]<fpOW'ipZV>Q8Had!pT!t<~>
JbO`ih=h$a$5NttJbO`iJbSF'!mM&hJbQ2=J,~>
JbO`ih=h$e"<.JmJbO`iJbSF'!mLibJbQ2=J,~>
JbO`ih=h$j"<[hrJbO`iJbSF'!mLibJbQ2=J,~>
JbO`ihY.4n$;&JL$J'e&JbO`il1Y6N$DrCKX7q,~>
JbO`ihY.4q"A-i@"P/.uJbO`il1Y6N"K$bEX7q,~>
JbO`ihY.5""A-i@"P/.uJbO`il1Y6N"K$bEX7q,~>
JbO`ihtIDR$4YneIL7LlJbO`iJbSI(!j;qbJbQ5>J,~>
JbO`ihtIDR":j>`IKV(fJbO`iJbSI(!j;_\JbQ5>J,~>
JbO`ihtIDR";0PcIKV(fJbO`iJbSI(!j;_\JbQ5>J,~>
JbO`ii:d>=$4=6@pB*to+Q%bEJbO`ilLt?6$DrCKXS75~>
JbO`ii:d>="9c14pB*ti+Q%bEJbO`ilLt?6"K$bEXS75~>
JbO`ii:d>="9c14pB*ti+Q%bEJbO`ilLt?6"K$bEXS75~>
JbO`iiV*Fn$4<:$pB9qF$5h$=pOW'ipZqPTY6m:@pT4+>~>
JbO`iiV*Fq"9b4mpB9qF";oC7pOW'ipZqPTY66k:pT4+>~>
JbO`iiV*G""9b4mpB9qF";oC7pOW'ipZqPTY66k:pT4+>~>
JbO`iiqEOO$4<[-pB9q]$4XFppOW'ipZqPT+U[+XpT=1?~>
JbO`iiqEOO"9bV!pB9q]":hkkpOW'ipZqPT*!G/MpT=1?~>
JbO`iiqEOO"9bV!pB9q]";/(npOW'ipZqPT*!G/MpT=1?~>
JbO`ijS&el+:8S&q"Fo)$4:A5JbO`iJbSR+!e(JuJbQ;@J,~>
JbO`ijS&el+9W2!q"Fo)"9`E,JbO`iJbSR+!e(8sJbQ;@J,~>
JbO`ijS&el+9W;$q"Fo)"9`Q0JbO`iJbSR+!e(9#JbQ;@J,~>
JbO`ik4]&B:^RO`htHdIL'S+qQ%A[\pOW),pB&nQ+bBBipOE~>
JbO`ik4]&B:]pnThtHdIL'RnlQ%A[\pOW),pB&nK*.djdpOE~>
JbO`ik4]&B:]pnThtHdIL'RnoQ%A[\pOW),pB&nK*.djdpOE~>
JbO`ikP#+6%13B1oCiFO:^RO`^Oc-1pOW)-pB!#U[Xn1UpOE~>
JbO`ikP#+6%Km',oCiFO:]pnT^Oc-1pOW)-pB!#O[Xn1UpOE~>
JbO`ikP#+6&HiB/oCiFO:]pnT^Oc-1pOW)-pB!#O[Xn1UpOE~>
JbO`ilLtMGE#]2l@.3UZ"g9<]$8A?.pOW'ip[RtZPmUoMpTF7@~>
JbO`ilLtMGEu+r^@.3UZ"g9<W">H^(pOW'ip[RtZPltKGpTF7@~>
JbO`ilLtMGGo$Sd@.3UZ"g9<W">H^(pOW'ip[RtZPltKGpTF7@~>
JbO`im.UOr@/^K65gAZi"b%3o$6bF\pOW'ip[e+\mg9>OpTF7@~>
JbO`im.UOr@/^905gAZi"b%Bn"<<GQpOW'ip[e+\mfWoIpTF7@~>
JbO`im.UOr@/^905gAZi"b%Qs"<<GQpOW'ip[e+\mfWoIpTF7@~>
JbO`in+Qn7L+*$.!Edq:pBI*8$47;OYCZG!pOW)3pB!nnVLeKFpOE~>
JbO`in+Qn7L+*$(!Edq:pBI*8"9]9DYCZG!pOW)3pB!nhVLeKFpOE~>
JbO`in+Qn7L+*$(!Edq:pBI*8"9]9DYCZG!pOW)3pB!nhVLeKFpOE~>
JbO`io(N3o?m,FU!Y9bCjS&\f=T&R.5`XEHpOW'ip\FObPmV2UpTO=A~>
JbO`io(N3o?m,FO!YBhDjS&\j=T&@(5`XEHpOW'ip\FObPltcOpTO=A~>
JbO`io(N3o?m,FO!YBhDjS&\o=T&@(5`XEHpOW'ip\FObPltcOpTO=A~>
JbO`iq"Fs\^Q)tm#lY/4+DA4@pB0TJ:^I%-":mbHVSIcWJbO`irq?KO$6aupYP3P~>
JbO`iq"Fs\^Q)tm#lXr.+DA4@pB0TJ:^I%'";!hIVSIcWJbO`irq?KO"<<!eYP3P~>
JbO`iq"Fs\^Q)tm#lXr.+DA4@pB0TJ:^I%'";!hIVSIcWJbO`irq?KO"<<!eYP3P~>
ReQitJ\UHa!2faE")'\[&,Q\85`XFJpB%O0+o;TJ-pU&sIK5+R^ZW?3kCNB#pAu`Meq*S"pOE~>
ReQitJ\UHa!2faE")'\[&GlS35`XFJpB%O3*;]j?-pU&sIK5+R^ZW?3kCNB#pAu`Geq*S"pOE~>
ReQitJ\UHa!2faE")'\[&GlS35`XFJpB%O7*;]j?-pU&sIK5+R^ZW?3kCNB#pAu`Geq*S"pOE~>
U\=r\^Q*8'#_<?<$3Ul"I\tB;pB9BgIUtNj$JPmj(-m>Peq*RopB#"8Ne-r/pOE~>
U\=r\^Q*@t#_<-6"9]8rI\tB;pB9BgIV:`m"PX%^)aJkUeq*RopB#"2Ne-r/pOE~>
U\=r\^Q*S%#_<-6"9]8rI\tB;pB9BgIV_#q"PX%^+@(CZeq*RopB#"2Ne-r/pOE~>
VtU;EN^+c`$@aHa5WNe4FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\
FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\
FFJ=\FFJ=\FFJ=\FFJ=\FFGEUIWU$\IWU$\IWU$\IWU$\IWU$\IWU$\IWU$\IX]&?^[K2:"4k5q
[XWCJFFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\
FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\FFJ=\
FFJ=\FFJ=\FFG9QIWU$\IWU$\IWU$\IWU$\IWU$\IWU$\IRuOb3=Yck!]aqtJbQ8?!k\k*JbQDC
J,~>
VtU;EN^+T["Fhg[5WNn7G^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amd
G^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amd
G^amdG^amdG^amdG^amdG^^i]IX$<dIX$<dIX$<dIX$<dIX$<dIX$<dIX$<dIX]&?^[K2:"4k5q
[XWONG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amd
G^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amdG^amd
G^amdG^amdG^^]YIX$<dIX$<dIX$<dIX$<dIX$<dIX$<dIRuOb3=bif!]aqtJbQ8?!l+q(JbQDC
J,~>
VtU;EN^+T["Fhg[5WO+=I=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?Tn
I=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?Tn
I=?TnI=?TnI=?TnI=?TnI=<AgIXQZnIXQZnIXQZnIXQZnIXQZnIXQZnIXQZnIX]&?^[K2:"4k5q
[XW^SI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?Tn
I=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?TnI=?Tn
I=?TnI=?TnI=<5cIXQZnIXQZnIXQZnIXQZnIXQZnIXQZnIRuOb3>)&i!]aqtJbQ8?!lY:-JbQDC
J,~>
WqQS'BH[6\":mM7S%sULJbO`iqtC6E^N<RCr!EKjNn/dUYP/&)#_D`SpOE~>
WqQS'BH[6V";!S<S%sULJbO`iqtC6E^N`jGqud'dNn/dUYP/&##_D`SpOE~>
WqQS'BH[6V";!SAS%sULJbO`iqtC6E^O93Lqud'dNn/dUYP/&##_D`SpOE~>
XnMn4?k*)B"!D9cmt(4apOW).pB0<B:^I%-!Z$7JJbQJE!]^V4JbQGDJ,~>
XnMn4?k3/="!D9cmt(4apOW).pB0<B:^I%'!ZQUOJbQJE!]^D.JbQGDJ,~>
XnMn4?kNA@"!D9cmt(4apOW).pB0<B:^I%'![)sTJbQJE!]^D.JbQGDJ,~>
YP/'e0`DC\5aC5XpOW'ipZV>QY@SmV$3=8RJbQPG!b_q<JbQGDJ,~>
YP/'e0`D1V5aC5XpOW'ipZV>QY@npT"9DWLJbQPG!b__6JbQGDJ,~>
YP/'e0`D1V5aC5XpOW'ipZV>QYA>3X"9DWLJbQPG!b__6JbQGDJ,~>
ZM+RdE"E?`+c_-JJbO`iiV*Ac=T/X.0X$'%[eBig$?1RjZ1ib~>
ZM+RdEt8BV*0,UEJbO`iiV*Ag=T/F(0X$'%[eBig"ET.gZ1ib~>
ZM+RdGn1#\*0,UEJbO`iiV*Al=T/F(0X$'%[eBig"F5RmZ1ib~>
ZhFWX&./b0YCZG!pOW(ppBKf0(((C&c%5W!pB&,;5_8[7pOE~>
ZhFWX%g3##YCZG!pOW(ppBKf0)[$9tc%5W!pB&,55_8[7pOE~>
ZhFWX%g3##YCZG!pOW(ppBKf0+9Vg$c%5W!pB&,55_8[7pOE~>
[J'j>-jg;Xeq*RHpOW(kpB@E:$4:J8JbQbM!q-I5JbQGDJ,~>
[J'j>-j0ZLeq*RHpOW(kpB@E:"9`E,JbQbM!q-7/JbQGDJ,~>
[J'j>-j0ZLeq*RHpOW(kpB@E:"9`E,JbQbM!q-7/JbQGDJ,~>
\+^'p=:,B`]Rfg.pOW(gpB5pN$5&bYpU^)#&IR]PpTjOD~>
\+^'p=9JaT^k)62pOW(gpB5pN";RDWpU^)#'a3]NpTjOD~>
\+^'p=9JaT`I[c7pOW(gpB5pN"<*b\pU^)#)?f5SpTjOD~>
\G$-`-jg6/JbS$q!9\q-JbSj3"QrSg$>B&*!9\q-b4bs&$FYN[ZM/k~>
\G$-`-j0g)JbS$q!9\q-JbSj3"QrSa"DIE$!9\q-b4bs&"L`mUZM/k~>
\G$-`-j0g)JbS$q!9\q-JbSj3"QrSa"DIE$!9\q-b4bs&"L`mUZM/k~>
p%JGo^^:.X]FTQ#iX,bn$Ki[c$@rQE$3BlgJI6r8JI;8]!$m'~>
p%JGo^]X_R^^u&"iWK>h"Qq=e"G$^9"9J67JHUN,JHYiQ!!n)~>
p%JGo^]X_R`=RS'iWK>h"QqXn"G$^9"9J68JHUN,JHYiQ!!n)~>
p@eSq8]T'^IL6EIL+!DA,aX(\j/<)h+poZEJZ+-uoV_nO%14AOpo&[T,RQo2s7cQfs7cQfs7cQf
s7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQf
s7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cPCp](!fp](!f
p](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp]#1>E<#\4p](!f
p](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!fp](!f
J+s!;s7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQf
s7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQfs7cQf
s7cQfs7cQfrsST$p](!fp](!fEC,0~>
p@eSq8]T'^IKTd=L+!2?/A$JZj3Ij[/d*ksJ^8nnoZmZC%Kn?"ps4GI/-HhN>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<]=^5*7=^5*7
=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^3?X-sTn[=^5*7
=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7
Ip$#Z>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>RO\>=^5*7=^5*7(D_N~>
p@eSq8]T'^IKTd=L+!2D2;`uZj7`\O3<V@OJbO`io_/L7&HjuSq"K9?2$=gX>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<]=^5*7=^5*7
=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^3?X-sTn[=^5*7
=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7=^5*7
Ip$#Z>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7>Zk<7
>Zk<7>Zk<7>RO\>=^5*7=^5*7(D_N~>
p\+_k8_1u-$C(l(=Ht;(%jI@=JZ\"8"_8rm-@DbCXb!VGXTXt2$6lGFXTWhnF>fbBs3(EDT*f7_
s0_k.?;&Lm~>
p\+_k8_1u-$C(l"=Ht;(&2(T@J_&o5"bn^g0Ug==eq)EDec`T1">8$sec_0fS.mu7>g$@R3<U[[
>d[f<)A+EK~>
p\+_k8_1u-$C(l"=Ht;(&38YBJcFg2"fOG`3Oea6s+14Arrh40"?H5QrrfM_`"Y4_>g$@R3<U[[
>d[f<)A+EK~>
q"Fec8]T'_Y6YJWPpi,I,aYH\XlQSeBI7OBFFgQFXmiFpU*L(9qlL!A,&]N7JcEmm!k/M5JcE1Y
"&!1Kb^]~>
q"Fec8]T'_Y6Y8UPpi,K/A'5Zf&YBbK.r??S:T7Bf'q5maXdQrqpkn1/sIA:JR$?&!^[#1JR#Wg
!tc7s.=_~>
q"Fec8]T'_Y6Y8ZPpi,Q2;e.Zs5a1_T/j8=`.@r>s7$$jmkk5]qu6f!3P$4kJR$?&!^[#1JR#Wg
!tc7s.=_~>
q=jn\!7o38#rr>pPpho>,aYH\XlQSeBHh7>I"ADNXmiFrW3)U&$?H*I"EG]&3hM!'bPqit2DtmL
2E"D#JcE@^!CCf*b^]~>
q=jn\!7o38#rr,jPphoB/A'5Zf&YBbK.`3=RXs%@f'q5ocdorJ"HNH3"HG*q*BA[Ub?P<*'GLZQ
'GMQgJR#fl!>ZLN.=_~>
q=jn\!7o38#rr,jPphoG2;e.Zs5a1_T/j8=[t4R1s7$$lp]'Cp"KMG#"KFJh*BA[Ub?P<*'GLZQ
'GMQgJR#fl!>ZLN.=_~>
qY("U:r^`cL'eHO.$\`sQ[u:!XTW;aA0u\TXb!V@XTE,B*WE/DE[#E_T7R$brrCCB$34ajs1J@5
mk]Cjb^]~>
qY("U:r^`cL'/'J.$\j)]7JPnec^UZL+o'Ieq)E=ecL^3,lZ?tN\6P:3ItGX>Q@Fo"99d">eF;C
<X]rG.=_~>
qY("U:r^`cL'/'J.$]'8hgtgfrrerSW&_><s+14:rrT;*/,oPOW]@X23ItGX>Q@Fo"99d">eF;C
<X]rG.=_~>
r:^9]3D%37#rr>p.%>0$Q`[3;XlZYgBHgt6I&MUuJZ\7?#TX7rX`>Uu6D&i/aSu6Rr!EG]JcEF`
!q.q/r60Z~>
r:^9]3D%37#rr,j.%>9/]@P;Zf&bHdK._m4Ra6Q9J_'/<#UBP:erDA?(--qNaBS]lqud!rJR#ln
!`f^Nr$d-~>
r:^9]3D%37#rr,j.%>K>huEG%s5j7aT/ii1\+bIRJcG'9#V-%]s/J)](--qNaBS]lqud!rJR#ln
!`f^Nr$d-~>
rV$EW3De;Hbm1EZ#Ul0],aX(aj/iGt,%2gGJZXL*nuVnjq5jm6$9W;RA3-?6s2Y->hu*l^BS$2s
rrVX@PP`RS~>
rV$EW3De;Hbm1ET#Ul0`/A$Jaj44?c/qPdGJ_#D'o%!feq:5e#"A4V(L)i:*>fU(L;#LLs-@oF8
>QRq7*r@C:~>
rV$EW3De;Hbm1ET#Ul0f2;`ubj8T7S3MSUDJcC<$o)A^aq>U\d"B_QZW#[mL>fU(L;#LLs-@oF8
>QRq7*r@C:~>
rq?KO3D%06#F,W%Q#J"8W.D)3XT`A^>partJZXL*l)b/V$5"OW6_Ar0`W#pOrX&Y7JcEIa!oFii
qTOH~>
rq?KO3D%06#F,DtQ#eLgc[j[.ecg[YHn_#8J_#D'l.-'N";NJE)**7Q`EWBirWE3fJR#oo!`01H
qC-p~>
rq?KO3D%06#F,DtQ$G7EpOWA*rro#TS2n(QJcC<$l2LtF"<'.o)EE@R`EWBirWE3fJR#oo!`01H
qC-p~>
s7ZT>3DdW<#&k#JAL*V@JZ\"8"DAub-@CBHJZ\CC!3#dss/6&@!+nTjJcE[g"5aq&:kAY\rrULu
Z2)Uo~>
s7ZT>3DdW<#'18NBIpdAJ_&o5"G8=S0UdRDJ_';@!71Pns3Cfn'Rpu\JR$,u"&K:,*J%J0>QRP,
,l&m>~>
s7ZT>3DdW<#'geWCc1,EJcFg2"J7]E3OaS>JcG3=!;HBjs7ZXH-'oN1JR$,u"&K:,*J%J0>QRP,
,l&m>~>
"8C&*8]/dVN[U0(,aX(aj/iGt,#ot;JZXL*k,ec,,Zm+"JcEUe!fm]7JcELb!m`I'prn6~>
"8C&*8]/dVN[U0+/A$Jaj44?c/o`S6J_#D'k10Zq/9W$oJR$&s!]1$QJR#rp!_WqIpaL^~>
"8C&*8]/dVN[U0/2;`ubj8T7S3K?,/JcC<$k5PRa23]+AJR$&s!]1$QJR#rp!_WqIpaL^~>
"1C3]f#5f:N[U0(,aX(aj/iGt,#Tb8r2fstJZXL*nuVnjr2g*B,Z7"%JcERd!Uij9s1eR8[j8]^
b^]~>
"1C3]f#5f:N[U0+/A$Jaj44?c/o<;2r71koJ_#D'o%!fer72"2/8Z7bJR$#r!EL4l>eaMF63nq4
.=_~>
"1C3]f#5f:N[U0/2;`ubj8T7S3Joi+r;QckJcC<$o)A^ar;Qo"22W;4JR$#r!EL4l>eaMF63nq4
.=_~>
^Bt"%Q%9;"$3>5SJI6r8isGg/#_<>6$@rQ\$37g6b^]~>
^B=RtQ%9(q"9G"uJHUN,irfCQ#_<,0"G$^P"99eB.=_~>
^B=RtQ%9(q"9HFHJHUN,irfD$#_<,0"G$^P"99eB.=_~>
71-fE[_0A`]YCnf^UCei[_0A`]YCnf^UCei[_0A`]YCnf^UCei[_0A`]YCnf^UCei[_0A`]YCnf
^UCei[_)s:RgOfIR/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=
R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=
R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R/hm=R0*eB$A8>G!/(9=
!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=
!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=
!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=
!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=
!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9=!/(9="+pr,$A&5.s+14IrrEJDp<8$~>
6j19>[_TYd^q[=n^UCqm[_TYd^q[=n^UCqm[_TYd^q[=n^UCqm[_TYd^q[=n^UCqm[_TYd^q[=n
^UCqm[_N$8Rfn;diQM?+UBH.lJ^!3!!X,8$JMVj5JM[0Z!<aeD.=_~>
6j19>[`-"i`P8k#^UD+r[`-"i`P8k#^UD+r[`-"i`P8k#^UD+r[`-"i`P8k#^UD+r[`-"i`P8k#
^UD+r[`&B=Rfn<BiVrr^bQNLsJcFg2IK[jt/2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<
1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<
1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(5s"f*T1G^R<1Ft=</2K(51G^R<1Ft=</2K(5
1G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(5
1G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1G&P^IOm92/2K(51G^R<1Ft=<
/2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=<
/2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(51G^R<1Ft=</2K(5s"am21G^R<
1B]*!.=_~>
!Xc]TrkAHGr4`6Er4`6Er4`6Er4`6Er4`6Er4`6Er4`6Er4`6Er4`6ErP&FAQ(&,3RGjP8$?QW+
R=UdeR0!_;$A&5.s+14IrrEJDp<8$~>
!X-9RrkAHKr4`6Ir4`6Ir4`6Ir4`6Ir4`6Ir4`6Ir4`6Ir4`6Ir4`6IrP&F;Q(%o-bi-bI"IoUG
b^n"!bQ7p;"G&@[1P%@Z1B@V+p*kL~>
!X-9WrkAHPr4`6Nr4`6Nr4`6Nr4`6Nr4`6Nr4`6Nr4`6Nr4`6Nr4`6NrP&F;Q(%o-s5EtZ"N1Fo
s+142rrN>A"G&@[1P%@Z1B@V+p*kL~>
!"/Y=!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SQJ!t(N_$A"W(R0!_A$A"Ur
RH0b;$@3&1s+13$s82fr$MDVn~>
!!N57!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SQJ!sG*Y"G+eUbQ7pB"G+dJ
biHtL"JZ*N1P%?51]&s."SF3o~>
!!N57!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SNI!5SQJ!sG*Y"G-U3rrN>I"G-T(
s5a1]"O%""1P%?51]0$0/-PVpJ,~>
!"-WZs1\XCQ(&,3RGjP8$?QW+R=UdeR0!_;$A&5.s+14IrrEJDp<8$~>
!!L3Ts1\X=Q(%o-bi-bI"IoUGb^n"!bQ7p;"G&@[1P%@Z1B@V+p*kL~>
!!L3Ts1\X=Q(%o-s5EtZ"N1Fos+142rrWDB">#*[JMVj5r%e?V-h]!>~>
!"-WZs1\XCS!=>3RGjP8$@3&1R=UdeR0!_A$A&5.s+14IrrEJDp<8$~>
!!L3Ts1\X=S!=,-bi-bI"JZ*Nb^n"!bQ7pB"G&@[1P%@Z1B@V+p*kL~>
!!L3Ts1\X=S!=,-s5EtZ"O%""s+142rrN>I"G&@[1P%@Z1B@V+p*kL~>
!"-WZs1\[DQ(&,5JXPMc!t',WK7TGARH9h<$?QW+s+13$s82fr$MDVn~>
!!L3Ts1\X=Q(%o-bi-bI"IoUGb^n"!bQ7p;"G&@[1P%@Z1B7RN.=_~>
!!L3Ts1\X=Q(%o-s5EtZ"N1Fos+142rrN>A"G&@[1P%@[1BB$0odPC~>
!"-WZs1\XCS!=>3RGjP8$@3&1R=UdeR0!_A$A&5.s+14IrrEJDp<8$~>
!!L3Ts1\X=S!=,-bi-bI"JZ*Nb^n"!bQ7pB"G&@[1P%@Z1B@V+p*kL~>
!!L3Ts1\X=S!=,-s5EtZ"O%""s+142rrWDJ">#*[JMVj5r%e?V-h]!>~>
!"-WZs1\XCQ(&,3RGsV:K*l]-JXM(Wjd?4'$?QW+s+13$s82fs$GuM\J,~>
!!L3Ts1\X=Q(%o-bi-bI"IoUGb^n"!bQ7p;"G&@[1P%@Z1B@V+p*kL~>
!!L3Ts1\X=Q(%o-s5EtZ"N1Fos+142rrN>A"G&@[1P%@Z1B@V+p*kL~>
!"-WZs1\XCS!=>3RGjP8$@3&1R=UdeR0!_A$A&5.s+14Irr<E!b^]~>
!!L3Ts1\X=S!=,-bi-bI"JZ*Nb^n"!bQ7pB"G&@[1P%@Z1B7RN.=_~>
!!L3Ts1\X=S!=,-s5EtZ"O%""s+142rrN>I"G&@[1P%@[1BB$0odPC~>
!"-WZs1\XCQ(&,3RGjP8$?QW+R=UdeR0!_;$A&5.s+14IrrEJDp<8$~>
!!L3Ts1\X=Q(%o-bi-bI"IoUGb^n"!bQ7p;"G&@[1P%@Z1B@V+p*kL~>
!!L3Ts1\X=Q(%o-s5EtZ"N1Fos+142rrWDB">#*[JMVj5r%e?V-h]!>~>
!"/\?!\48-o=tJZ$7im+pV6bZr<`Q*M>b3iD"S,@!5H=a$Cd:fKB&@$(]\FKR0#GM<rSKe480'_
!*8ra!Z27gpmD+!q$I*Gq3_4EpBgp$<rSKe)#+F<!8a+G$@3&3X-irLq$I-&<qr'a(^_J`R0.:5
!,_M!#7:H3R@+llD=a.qD>X_@!;2`](^_J^R/e*"$329HR0!_A$A&6Wrr>.Z$3:.8qu6^Z!4M\&
q[*=8qu6^Z$9J"_!bMFoq>ULX$9J%`!0Hj\!!*#u!]pCjq>UM.!0I3[!'Br_!!)or!+tp3!/H4,
!=H^;b^]~>
!!N;:!i5kso=tO+!!I,Q^\GSFV#^Gs!!)r4!%[^F#>DnJ^BC$k"QR]-Y6b4(bl7YCYPu`G!;?Ts
!3c:D"0W"6F8=#\Y6k"-!-@t[!!)`r"TYaMbc_?J"9;?VbQ7pB"KKZG!;Q`u!3c4B"0W"6F8F)^
;?R!pbQj75!3aVM#lsP?!)i^t!)iO7"0W"6F8=#\Y6k"-!->!\!X,8$JMR?`/-,VO!!)mP!\=<O
pbMmrqZHmFr%eF+!!F>=1BT0.!%dt&""XET*VqPl$hXW+'JhNr!!)gN#:oiT/2K(5!;HZs'`9`f
/-,AH!&9*^!<aeD.=_~>
!!N;:!c.j1o=tNG!!K.5^\GSFC]OD:!)3@,!Moeh":'du^qU#APm$.I!g!_#q>UN)!':,Z!O`"&
"9:LZrr\>h"P`bP!K[W`"9@3QrrKP.pB1IWrr3"\$2=W'[bh#c"O%"LrrKP.q#g[Yq#:GX#mB#Z
rrN0'B))B2Act0ps,[P!o)A[iqud#.pAY5V#mB#YrrJ2fq#g]PV>gT"bQNJQ!](hsr<*-8/Gq:*
*<6fK1B8-d"9C;0rA+Qs!!Fi(q_J9o!"/NX"<@IF/1g]T!A"6D"9g)&1E?H0q(i'm!"/]]!?D18
"9Bf"rA+Hp!;6Nq0nV7_"=NmlJ,~>
!"/\?!\48-oY:X,$46ci^\PYFD#"D@!5H=bD"S,8!<0_8$C.(figBib!,_P"!]pB_rg<aJpBgp$
<r89fK*hqcD>BS")#+F=!*8ub!'Bo^!<?1"R/e)u$3:-Di0aWRF:,%9!'Bo^!!)k`"G7$g!,_S#
!]p_hq3_:G$9eBV![Ib_opGesp^.!$pmD8;$46cMq3_4"p^.$%<i;>e$?QW+rrA/V$31)(rrP7^
[eg%!$3:-grVlsM$361arrQd4OS\t[gBn8f[ep()OS&q\!<3!"3rl_brrQd4OT,7[/,'AO!4Mk+
!+ts4!<A46rr<E!b^]~>
!!N;:!i5kso=tNG"9\_?^\PYF/,9;N9Y\BY/+j#F9`Dd/"I5GZj3.S.#lsbErr<&&rlb?BpB1Ks
YPZNGEs)UGq9/lC$2=W(!3c:D!!)`r"TYaMbc_?I"98GZbQ7p;"KKZG!;?Tr;YnT>Es)UGq9/m?
"B,'>#64mhbfm6r!;+J8;YU@p;Y\H<Es)UGq9/lC$2=W(!3`<E!X,"rJMR96q#g[3rA+L+!!)jO
!%%CC!<=\M1B]6/"9:"L1]IFT/G_.)*<ZKFqD/*Zo`PF41G^Q^!;JAM!!Fi/1FrP#"98H"1B7FL
"9AKRK/3K1odPC~>
!!N;:!c.j1o=tM["9_E6^\Y_HV#^;o"Gsn/V#^/k!/^]i!sFmY"QTL`O9oOprrTV/3r]-[[K-%)
!'9uV"$-PfB)2H-O9t[b!':#W!O`"&"9:L^rrJ2fpB1J.hu<`\`!!.@!O`"&"9>n,rrY:b"D7N1
"1A14[eg"*[K$I0rrS8fB(Z*'!;HZs[eTk)3WoRtq>UJW$2=W'3i;oY"N1Fp1BB$,q#g[DrA+Kq
!#58c!#522!"/Z\"!%@E$MlO[,le;J1BS!f";L_h!A"6D"9g)-1E?H0q(i'u!"/]]!?D17"99/7
1BAd%pB1I'K/3NZ"SF3o~>
!"/\?!\48-oY:Z)!$VbD^VBZ]$],2jCpq!k[\X<</,[-e[_0CL!/\.sqnE4?S!=??R0"W6D>BS$
3ri=\R05SOD4-%UR/hm6R05SO+ppL/R04?,D4-%TR0,MX!*9#c"?QU#R=kL,!/(-9";h,UR=kL,
"G<*Z$39%Q"+pr,$EBKs3rj,cKDq8AK:*&R,5[c23rgH34o(KI(^`G'R0%44!<&;e(]\FHR131(
$35^HK:/-.$46ccqO%IJ!$Vb5q3_@&!,\-VrL!aL$34,+R0/'KH4#9("1AODL].5TgBn9KrVlp\
!4Mh*"PX7j!4Mh*"$6hiOT,7_3sl;pg\_!PAcRqhrrcp63XTKlrrQd4[e]q)3rl_brrQd4OT,7]
3rl_crrP7h!<)p!AcT8js7uZq$GuM\J,~>
!!N;:!i5kso>(A+">0h6^VBZ]$VU['V7+1S[VQ'LD#D$Q[_TZY!5Q%YqnE49S!=-:bQ=sR!;Oe<
!!'5%bl7\4bl7YE/d.=5bQ=sS!;anA!!E;*r650@oZRB?#lshG"0W"6;W,b#"JZ+"bl7\>bQEt1
"EXIarr<*Q"HiQ)"0W"6Oo*?(;?TnsbQ=sS!;4P</d);0r6,6q"9^@Qbl7YE/d.=5bQ=sS!;anA
!!E;*W6>;GUBH0)!u(k@1&Wm1*<ZKcr\FU,!!)mP"!%LI!;nV\/-#eQ/2K(5!!EE4qD8-O!%e"'
"Y9WR$NqdL1BK*-!;SDP/-#\F1]IFY/2K(5!!)jO""XET'`Bif!.cs$1B@V+p*kL~>
!!N;:!c.j1oY:]g!%S$P^q[Y]^^C,F/&8U4^U>Wq"Ju%2#I_U.!)12:`V[CP"IkYZj8T1B#\No4
!k&(irr3&.!+td$"L\:k!!N,t!g!_#qu6c,!!G@\rrTV/B(Z*)O9oOqrr\>h"Ld7g!X-^MX8`70
!+u*-"(hZ=g\h'S[K&>g"Sr'!O9l'drrN0'[f6:.O9oOmrrh'V"9ahbrrh'V"9ah_rrfb13WK<Y
rrS8gB)DT1[K$FkVuHi%bQKJL1D'Ti">M\`1G]CA"=u)[1E?H0q_JWg"9]Di1G^R<*<63Lr\FZt
!!EEFq_J:"!"/QY"<@IL!!N*R$7mth/0+^)/2K(5rA+Kq!#55b![RgRr\GH5!#-t?1Ft=</0+]n
,qLi.,leW11G^R<1G")5s"aZY-h]!>~>
!"/\?!\48-oY:Y<!/V$_^VBZ]!\47erP&NK^VC0VM>b0m]YD6U!/^Nd!t(<_$K@HT(]\FKR0#GM
<rSKg3rj-fR03isD$g:=R0"W6D>KY%<XJj)R0#GMD=j4t,QM]WR0"3*D;gla$?QWTR0#GMD>KY&
K*hU2r0[Zp!,W&Cq3_>;$34r@R0!]qD>fk(,QM]UR0.:5!*8o`!Z27EqO%Hn!,W&Cq3_:0!,_S#
!`fX.W0m\oF:*X"!]pCDqYpU(!4Mn,!]pCjr;Qg[$9J"_!k&F9rr2s[rX&Wdqu6_0!0I'W":>.;
$39c!!bMG@q>ULX!4M_'!bMForr3$]!4M_'!]pCjrr3%3!4IIYq>UI&_Y23(~>
!!N;:!i5ksoY:]g"=n9k9Y\@?^]UD5!2]Y/"i.m0"9?79^BMEf^]4AMq7d"7Q(%p:bQ=sS!;Oe<
!!'5%bl7\5bQX+3/oq.nqTK#E#QX\E!\OURr650@oZRB?#QX\E!j;n1i626*S-5k9rrDl=!cnJs
r6,>,">4`u;YeN<Es)L@bQ<n0!<(+CY6Y.%bQC`G!3c7C"0W"6;Z"ZAOT[:X"B,*?!j;k0q9/mm
"B)2B!X,"rK/3T\!!"SI1BJj*!<+bU/-#\J1BT0."<I:o"r%1S1G^Q^rWE3Aq_S6P!%e"'"XO9Z
*<[)L1]IIN1BK*-!;JAM!!Oo01FrM+q(i('!!)sRrr@U+pbMpR-h]!>~>
!!N;:!c.j1oY:]*"DfRkM8-h'^]SBQ/&;+u"i\5M"AHW0^BMTkM#^5bq7d"7Q(%p:rrS8gB)2H.
[K&AfrrTV/B(>m)Ad"2V"Le1,!g!\"qYpZP!!Kb/rrTV/B(Z*)O9fIorrS8gB&N[j"N1GDrrTV/
B)2H.3WtQBrrm!;O?*NQq>ULW"D7T3!bMRIrVlq\#\Nf1!WW?aq>UPY#m@^6rrm!;O?*NQqYpVY
#\Nr5"5<eY[]'3-"N1Fq1BJQs$MuU]///4$r\FTr!#5>e"!%@E/GV(+!!FT!1E?Jp"TL%O1BJj&
$MlO_'EfEa"=5Vn1BJj&'_sNd*<6fL1BJj&$N;g_*<6fL1BJQs'`Kli,leX11\`a,"=NmlJ,~>
!"/\?!\48-p:psKM$bcK,QMYqqnN7`!2]S-"2IS1D#M*P^q[ZY!/^Nd!t(N_$K@HT,QM]WR0#GM
<rSKg3rj-gR0IL8,+08J<r/3c(]\FKR0"3*<rJEf3rj-iR0"3*D>BS$3ri=?R0!_A$EBKq3rj-n
R0"3*D>fk,D$hJT(][V4R0$Cr!<&;e3sn.KR0"3*D>0G"(][V2R0!]q<rA?iD$hJT(][V5R0"3*
D>BS$(][U9R0!_A$A/9Z3rn%3rrQd4OT,7]3rl_frrS8h!;HL#3rkBBs#U4B$361crrQd4OSf%^
[L,(q3rkB9rrQd4[e]q)3rl_brrQd4OT,7]3rl_brrQd4OT,7]AcT8js7uZp$MDVn~>
!!N;:!i5ksotUjJ9E]1.#QU^9r4iE2!%Yk^^B;7O!2]Y/"i.mg!!'h1^B4V.Pm$-k!j;k0prra<
!3cCGrrDT5##YSFY6b34qof,F#lseF"0W"2YPl]F!;+J:Y6b4(bl7YCYMm\,"JZ+"bl7\=bQ=sS
!;t%F;?V'E#lq1)bQ9C";Z4f@!!JG_bQ=sS!;F\>Y6b34prii&"9>Y'bQ^rJF0>lMYPZNFY6b4)
bQG$T!3`BG!X,8$K/3Q[!"8WZ#V5rU/2K(5!!)pQ!Xf,FpbN:/!!"Q+/-#Yj"9:"N1]IFT/Gh4-
'EfF-!!"SD1]IIN1BK*-!;JAM!!Oo01FrM+pbVpM!%e+*rr@U+pbMmQodPC~>
!!N;:!c.j1otUjJ!!K-7#V3^*r4iDN!,TIJ^B9;m/&;+u"i.m0!)13(^B4V.Pm$.I!g!\"q>UN)
!':,Z!k&)@nc&am"P]"E3r8jXO9oOprrS8g3rT'[[K'q3rrS8gB)2H.[K&AHrrN>I"KVP([K'q8
rrS8gB)MZ4!!M&<#s.n^"5<eY[f6:.[K$I.rrS8gB(u<,O9muBrrQd83rAp\!!M&<#s.q_!g!_#
q>UMX#s,$b!X-^MK/3QL!$1nl!Z_7Jr\FTr!#5Af"=sNU,qSjJ$69*R1G^$O'FP$Nr%eC#!"/QY
"TSZV*<6H?1BJj&'_sNd*<6fL1BJj&$N;g_*<6fL1BJj&$N;g_,leX11\ig-/-PVpJ,~>
!"/\?!\48-otUi,!,TGe$7im+rkJZO^afD$n\51A9E9qF^B=n/RgOf.R0"3*D>BS$3ri=\R0#GM
D=X)!3ri<L<XL&IR0"W6D>BS$D$hKcR0#GMD=j4t,QM]WR0!]qD;glb$?QW-X-j#N!,_P"![Ib_
rL!jO!*5Lg$6o85#8de2R@.n!!;i/c,QM]VR0&!J!;;f^D$hKbR0G_Q<gctq,5R]/,QM]WR0%44
,,pt3$?QW,rrP7^OS&PS3rl_frrO\N[dsG&3rl^N$38iZrrQd4OSf%^AcRqmO:"&BrrQd4[e]q)
3rl_brrQd4OT,7]3rl_brrNN-[f6:.AcT8js7uZq$GuM\J,~>
!!N;:!i5ksoYCJ,#,T*c"DkifrkJZOV#ULln\51A!!'h1^B4V(Rfqcq!j;n1prra<!3cCGrrDT5
rr<2*bUs$>qof,F#QXYD!`B/BrQP9AoZRB?#QX\E!g*Tci626*S-5k9rrDl=!j;k0r650@"L$$%
"EXC_#d4O;Oj)$I"B,0A!j;k0pWN\["B+s;!`B/Br650@"L$$%"EXIa!j;k0prie;"EUQd!X,"r
K/3T\!!"SC1BK*-!<"\T/-#\@1BoB1!&Fuc$iDg\!!"SJ1C#H2!%]o6"<I1lrrDjN!\=<OpbVpM
"Y;Gc/-#\G1BJj*!<"_R!.cs$1B@V+p*kL~>
!!N;:!c.j1otUlj!%Yj]!!K.5^])"O^MEm#_"#/AM#^5bq7d"7Q(%p:rrS8gB)2H.[K&AfrrTV/
B(Gs,[K&AggAhB2qu6_Z#\Nl3!WWAXrr3&.!+tm'!g!\"q>UM."D6Tl!X-FEX8`70!+u',!g!\"
rVm,2!':/7!!M'PrrS8g"TJE&!!Kb.rrS8fB(u<,3WunbrrN0'g\q-W[K&AggAhB2qu6_Z#\Nl3
!WWAXWW*&'_uqWE1BJQs$M6+U*<6fP1BJQs'_4$a*<6d)!!F>@1BJj&$MuUa*<6F&/-#f(pG2js
!#55b!Z_7Tq(i'u!"/]]!Z_7Tq(i'd";Lhk![Rg\JM['W!<aeD.=_~>
!"/\?!\48-pV7W\V$[4!^VC0VV8'd\^q[X@$46ci^qd^trP/9G#f"/K!/\J*]YOK\!t(N_$K@HT
,QM]WR0#GM<rSKg3rj-hR0\EP!/$]-$35_UR0"3*D>BS$K*hVWR0#GMD=j4t(]\FLRK!To!8s7H
$@3&ZR0#GMD>KY&K*hU2rg=%E$35^HR0iqHq3_J?$34q2$34r?R0"3*D>9M#<XM"_R0!]qKDq8E
K*hU2R@+B^KDV&<(]\FKR0&!J!3)@f$@3&2rrfb;$37O.rrP7^[f6:.gBmrNrrtOb[f8Dr3rT'[
AcRqjrs7?d!8IPR$38iTrrQd4[e]q)3rl_brrQd4OT,7]3rl_cs8NE+!<)p!AcT8js7uZq$GuM\
J,~>
!!N;:!i5ksp:q?VC]l9(^PN(D^q[Y"^q[WX"9\`(^]D3crP/9G#f".T!5Q@a^qfo`!sG*Y"QR]-
Y6Y.'bl7YCYPucG!:n>=Es+,>b`N#\qof,F#lsbE!cnKBrQP9AoZRB?#lshG"-E]h;W,b#"JZ+"
bl7\=bQ;hg0)a!!Es+,>b`N#\priq`"9?Z."9A>C!j;n1pWN[k"HiE%!g*U<rQGLf">7pBOT[<6
bQ=sS!;Ob>Es,C?bQ7pB"G/E3'EeO=p+lb$!!)sR!Z_CXnhUM%!!"f+!!F>@1]IFT/Gq:/*<Za+
1F3/3p+u^Kq(i('!!)dMrr<3R1G^Q^!;\JR,m4>brA4HRJM['W!<aeD.=_~>
!!N;:!c.j1p:q?V.g0\(^J"cH^q[Y"^q[Va"9_Et^]D3hrP/9G#eu2r9YeEX`PDGe!sG*Y"QTL`
O9fIorrTV/3r]-\[K'q1rs(RfOT5>m"H`N\!g!_#q#:CV"Le=0!k&)@o`"uS$"j&6"(hZ=[c%/e
"O%"LrrTV/B)2H.3WtQCrs(RfOT5>m"H`EY#!)m!s(2CGqu6_Z$"iu4!o!\Xp&>)*"H`T^#<E!L
s8Q[6OSf%ZO9oOorrP4a[]09."O%"#1BRU[";LPc!Z_7TrA+K_"=<Up%35E_1E?Gu/1`S5,le;L
1C"9j*A]Jc"<I4m![Rg\q(i'm!#55b![RgRr\FTr!#58c!u(k@-2o\%,leX11\`a,"=NmlJ,~>
!"/\?!\48-p:q#O$;A4L^e>)/^VBc`!l)Ner!EQ,Cr!g#qnNBM^e=`b^\knI^B4h.RgS/C!#YD:
!<?1"R/f>C$31)(R0$Cr4oUiN3sn.JR0"W6D>9M#$35_XR/f>C$31))R/eN,$3:-DiL'e#$?QWT
R0#GMD>T_'K*hrKrL!^f$9eBV!]p`PpmD:d$33-p4o1QJ,QM]VR0$Ch<qVj^(]\FPR0$Cr4oUiN
3sn.JR0"W6D>9M#$35^ZR0/'KF:*U!!0I*c!<A5XrrP7^[f6:.[L,(crs(Uc[f>(h!0I3[!bMFo
r;Qh[$>0;;!bMdOp\t;,!4M_'!]pCjq>UM.!0I3[!'Bo^!<BS,rr?[*$31'Xrr<E!b^]~>
!!N;:!i5ksotUnb"Gsn.^]4Lo^VBc`!l)N$qud-&V80j\qnNBM^]4AM^\knI^B4V(Rfqcq!Nuh'
"9AM+rlb?BpB1Ior6,3p"Hi]-!WW@cr6,5G#QXYD!g*U<rQG6ApB1IorlbDH#knH%!8l!%"IoUp
bl7\>bQDnh"HiZ,!\OV<rlbED"HiK'"YKpU!!JG]bQ=sR!;F\=/c`*KbQ=sS!<(+C/d.=9bQ7bI
Oo3E)Y6Y.'bQ<n0/ud%r"IoUG1B7g\"9AKRq_J:)!!)sR!YbbXnhUP&!!"f2*<Z?nr\OQS!%e%(
#R^b]1G^Q^"=<durrDjN!\=<OpbVpM"=u>b/-,DI!!)pQ!!)`r!"4B7!!MsDJ,~>
!!N;:!c.j1p:q'4!!L*P^PMpn^q[Y`^]M9I!;ls&/&8s8^\beM^PMqa^q]f_s1\X=Q(%p:rrJ2f
p&k@Vrr3#-!;?Tr[f-4-gAhBUrrTV/"T/3#O9fInrrQd8OT,7\[K-%)!4Mn,!K[T]"9<&krrN>A
"KVP([K'q9rrP4a"TA?%gAhBUrrTV/"Si!#gAh@g!!N/u!g!\"q#:EM!'9fQ!g!_#rr3&S!!N<$
!k&(3qu6_Z#\Nl3!bMRsWW*#&_utZJ!A"6K"98f*1BJQs'`Bfi!!Fi(o.pUl!#.4F$Nph91BJj&
$N)[e/-#f//2J:H">_V)1BJj&'_sNd*<6fL1BJj&$N;g^*<>g:!>I*a1BAd%pB1I:K/3NZ"SF3o~>
!"/\?!\48-pqR$I^a&o:rkASm$37iUrkSNJ!PhD+$31)&^]hKg9E9par4r?I!t(N_$K@HR)"n:;
!*9)e!'Bo^!!)qb!Z1oSrg<h)$39sk!Z1oSpmD0j!/(9=!'Bo^!!)tc!#YJ<!'T2,!Xa5\WgNiK
pBgmEr0[U'!,__'!c88EpR)+#$46e/R0"3*D>9M#3ri=UR08/1,QM]\R0"3*D>oq)D$g:?R0"3*
D>9M#$35^YR0!_A$A&6Wrr?[.$31)(rrP7^[f6:.[L,(mrrA/X$4o8us#U4hs8S/f!8IPRAcRqj
rrO\N[f6:.[L*$/rrQd4[e]q)3rl_brrQd4OT,7[489-_B)DT.B(lW4!.t4O$GuM\J,~>
!!N;:!i5kspV6pHV$I(:rkAS0"AHSFrkSNJ!Pe^4"9;-l^]hKg!!'gLr4r?I!sG*Y"QR],Y6jn*
!<B>&bQ%Y;"9;?ubQ=sS!<(+C;?TnqbQ=sS!;Ob>OT[<8bQ%Y;"9;@!bQ4mRp]LSZhTQ$(UBIU@
!!)`r!-A+_!j;n1rQG=@"B,$="Kr+7"B,*?!j;n1pWWX;!3c+?!j;k0rlbGI#lsqJ!`B/!r6,5G
#lsbE!g*U<WQYDHUBH-(!])A-qZHm,rA+L+!!)sR!YbbXq_J3[qZI$B1FrM+r\FT`":P2brr<$M
rA+L+!!)sR!YbbOpG;gLq(i('!!)dMrr<0Q1G^Q^p]LRCq_J3PpB1I1JhmE1-h]!>~>
!!N;:!c.j1pV6pHC^<_MrkAR9"Gsn.rkSNJ!JpgR"9=;T^]hK0!)12Cr4r?I!sG*Y"QTL_O9tU`
!':,Z!O`"&"9>n/rrS8gB)V`2!!Kb.rrS8gB))B-Ad"4prrKP.pB1K)rr3"\$2=YZrrN>I"KVP'
[K-%)!8IJP!g!_#rVlp!"Le+*"HWom"Le1,!g!_#q#:E(!'9fQ!g!\"rr3%]$"j/9!WWA3r;Qh[
$"iu4!bMRsWW*&'bQKJJ1BAKrqZHm>rA+Kq!#5Af!WW?Sr%e@)!;cm0/2J:H'JhNr!!F;u1F3#.
r%eBp!#5Af!WW?LpbMst!#55b!Z_7Tq(i'u!"/]]!?D18"T'tU,lmZB!%!4Q!<aeD.=_~>
!"/\?!\48-pqR!HV$[5$^BD=Z!/\Ji_#1qL[_0C$rX&Z+V>S+4^VEMCM8/u(s1\XCQ(&-?R04c8
D4-%UR0A52R6(CLrg<mN!,\-Vrg<b=qO%D;$3:!l!X\pEr0[U3!,_S#",!!K!<&;g3rj,cKDq8=
KDLuB(]\E@K:.?!$9d45!Xa#VWgNiKq$I-&D>]e'K*hVVR0!]qD>9M$K*hTqq3_:0!,_M!!]pB_
pR)>^R3VcLR@/[7!;r5d$34r@R0"W6D>KY&K:*&HWL3epF:*U!qu6XHrX&Z+g]%3S3rl_grrTV9
3rApWOS]@g3rf4A!4Mn,#<N8Rs8Q[2OT#1\gBmrYrrNN-[eTk(AcT:8rrP7^[e]q)AcRqlrrP7^
[f6:/gBn:"qYpV/!4IIYq>UI&_Y23(~>
!!N;:!i5kspV6mGC]mH1^BBAr!5QAK_#1qL[_S^FrWE3OqnNBM^]4AM^\knI^B4V(Rfqcq!j;k0
prra<!3cCGrrDZ7!cnKBrQG>*"9AAD!j;k0prif%"B,3BrrD]8!j;n1rQG@b"9akCbQ7p;"KKZG
!;Q`t!;jtAEs,D>bQ<n0!;F\=Es)L=bQ=sR!;F_;!!'4sbQGtj#QXkJ!cnKBrQG>*"9AAD!j;k0
prif%"B)5C!X,"rJM[3[!A"6M"98i/1BK*-!<"\T'EfHR1B7g["9]`$/-#\L1C,N3"<B9S!!"SK
1BJR"'`Bfh,m45B1]IIN1BK*-!;JAM!!Oo01FrM+rA+Nr"9^%I1]IH+1\`a,"=NmlJ,~>
!!N;:!c.j1pV6mG.g1_-^B@459YeFB_#1qL[`*%grWE4;qnNBMM#^5b^\knI^B4V(RfqdO!g!\"
q>UN)!':,Z!k&)@oD\kQ"Le:/!bMRIr;Qh[#\Nl3!bMSDrr3&.!+tm'!g!_#rVlp["9dN[!X-FE
X8`4/!;Q`tB)DT03WunirrQd8B(u<,3Ws'jrrS8fB))B-[K&A^rr__O#\O)9!]gImrVlq2"D7T3
!g!\"q#:D-"Lb<0!X-FEJM[6\!\>>lrWE3Ar\FTr!#5Af!WW?Sr%e@)!;cp!/-6gl'`Kln*<63n
/0t9'r%eB^"=="&!YbbOpbMst!#55b!Z_7Tq(i'u!"/]]$QT3]/2K(5$NpSg/Gq:1,leW11G^R<
1G")5s"aZY-h]!>~>
!"/\?!\48-pV6c&p^.-S^V@S"rP&?Fr4`EN$48;u^])"P^VEMCM8/u(s1\[DS!=>5j-]rc!,_P"
!]pB_rg<gL!,_@r!'Br_!$_?/!Z1oSqO%G[K*hVWR0#GMD=j4t,QM]ZRK!To4l2S/$@3&3X-j)P
!,\-Vp6bnBp^.!FpR)'i!/(*8!Z1oSpmD1a$9e'M"UY6^R@-/3$32:JR0"3*D>KY&U4WdQWL3ep
H4#6'q#C@$!':,Z!]pCjrr3&S$39f"#IXt#s8P.\[f6:3gBmqBs(27mrVlk0p^.!^q#:D-!4M_'
!]pCjq>UM.!0I3[!]pCjr;Zd(!+u-.!bMG@JcGQG!"/DCJ,~>
!!N;:!i5ksp:pY.p]L_9^V@S"rP&?Jr4`Dl"9`PU^])"P^VBgL^q]f_s1\X=S!=-:bQ=sS!;Oe<
!!'5%bl7\7bQ%Y<"9<NAbQ=sS!;Xh@a`@S_rQP9AoZRB?#QXeH"-E]hOl":`"JZ+"bl7\7bQ%Y<
"9<N<bQ<n00)<]mY6b4'bQ9C"OnR!#OT[<9bQ%Y<"9<NAbQ=sS!;Xh@a`@S_WQYDHUBH-(q(i:&
"9]`$1FrM+rA+Kq";LPc"r%1\1FrM+rA+[!"99u/!!"SL1BB$,p]LR;pbVpMq(i('!!)dMrr<3R
1G^Q^!;nVT,m4>sq_S6PJM['W!!MsDJ,~>
!!N;:!c.j1pV6fd!;H["V8'd\_#;"J`VdIS9EYKp^VBc`"i#lo9YeF@_#M.O"IkYZj8T1B$"j#5
!k&(irr3&.!+tm'!O`"'"9@3TrrS8gB)2H/mlCT[rr3&.!+tm'!g!\"r;Qh1"9dQ\!X-^MX8`70
!+tm'!O`"'"9@3OrrQd8OSSnXO9oOorrUpT"SMd!Ad"4qs0_q&"9@3TrrS8gB)2H/mlCT[WW*#&
bQNJQq(i9j"9^8,1E?H0rA+N`"=5Vr1C>Zh1FrM//2J:H'`Bfl$Npem,le;N1BAKrp]UUJqD/1!
!#55b!Z_7Tq(i'u!"/]]!Z_7Tr%eBg"9eTX![Rg\JM[*X!A"BH.=_~>
!"/\?!\48-pV?M1#6:U8^VEMCD#(gKV$[4;^])"P^VEMCM8/u(s1\XCQ(&-?R0"W6D>BS$3ri=\
R0#GMD=j7j$3:-ZrL!^4!,_P"!`fXPrL!^K!,_@r!Z1oSr0[Y($34,aR0!_;$EBKq3rj-iRJ.$h
!/('7!X\p[q3_:0!,_M!!eUg[p6bu!$6oJ;pBgp$KDh2>,QM]WR0$Cr,,pt3$?QW-rrQd4OSSnX
gBmr[rrP7^[f-4-3rl_`rs,t>3rf4A!4Mk+#*9/lgL^GIrVuU!!<CmMrrQd4[e]q)AcRqgrrP7^
[f6:.3rl_err]\:!0I-Y!bMG@JcGQG!=H^;b^]~>
!!N;:!i5kspV6ccpB1^O^q[Y"!!&kk^BA?U9Y\@A^]hKg!!'gLr4r?I!sFmY"QR]-Y6Y.'bl7YC
YPucG!;+J8OnStY0)`uqY6Y.'bQ9C"F8a>_!;+J:Y6b4+bQC`G!3b;(!X,"rWm(JCoZR;tpB1IK
prif%">9Mo!j;k0pWN\["B+s;"]>JEbfl4D"9:(QbQ=sR!;Ob>/d-6pbQ7p;"GAQ5/-#Z$q(i6r
":I"A/-#\K1BK*-!;82Q'EfF4/-#\J1Bn3i$Sqb_/H.F*-2.NB$huOX!;SDQ/-#Z$qD/1(!!*!S
!\=<Oq_J<g"9:"N1]IH+1\`a,"=NmlJ,~>
!!N;:!c.j1pV6c&pB1_;^q[X@!%Yk\^B?%iM8-h)^]hK0!)12Cr4r?I!sFmY"QTL`O9fIorrTV/
3r]-\[K'q3rr?[*"9=P^rrS8fB)2H/gAhB2rr3&.!+tm'!g!_#qu6]t"?PE>!X-FEX8`70!+tm'
!+ts.!0I!U!bMRsq>UMX#\Ni2!]gImpAY9r"P`tVB(lE.OT#1\O9fIorr_!U"P]pU!sHOF/;5k]
*<6HC1C5Tg$NqJ+1E?H0rA+Kq!#5,_"oncW1E?H0rA+X/!!F;g!"/Z\!#5/1!$1kk![Rg\q(i'm
!"/NX!Z_7Tr\FTr!#5;d!WW?1r%eC#!#0u?pbMpR-h]!>~>
!"/\?!\48-q7mc\M$a6t^UCei[_N4MCqma#C^M]-^VKc^"Mb?E^VBc`"i%iH!/\Jg_#M.O$Cd:f
igBib!,_P"!]pB_rg<gL!,_Cs"`4SHR@/^+R0!]q<rJEf(]\FKR0"3*<rJEf3rj-iR0"W6D>T_&
3skuGR0/'KH4$[?!]pC!opGu#$36K^KDh2>$34-%R0!]qKDCo:(]\FIR0"3*<qi!h(][U)R;E92
R@/^+R0!]q<rJEf(]\FKR0"3*<iVPiK*lo3KDu"7$9It^!=Ai+rrP7^[f-4-O:"&Crs,t>3rf4A
!4Mh*"Zm&PAcRqlrrTV9!;c`q$361`rrQd4[e]q)O:#UqrrNN-g]%3S3rl_drrQd>!;uiuAcT8j
s7uZq$GuM\J,~>
!!N;:!i5kspqRZ[9E\&(^UCqm[YY+eV8'd\.g0\'^VKc^"Gm7T^VBc`"i%hQ!5QAI_#M.O"IkYZ
j3.S.#lsbErr<&&rlkBBoZRA7"B,-@"-E]dYPu`IY6b4)bQG$T!3c@FrrD]8!j;k0qTK!@"B+.$
!X,8$Wm(JCoZRA7"B,-@"-E]dYPHBDOT[<4bQ=sS!;F\>Y6b34prj#I#lq0QbYeRAqof/)"9>Y)
bQ=sS!;Xh@Y6b34WQYDHUBH3*!Xf,Oq(i7%";EXJ/-#\J1BIpe*VV>o'EfF4/-#\J1BoB1"<@IB
/H.F,'Ef-I1BSp+">'@)rrDgM!Xf,WqD/1!":P2b!\=<Oq_J=*!!F#61]IH+1\`a,"=NmlJ,~>
!!N;:!c.j1pqRZ[!!J.n^UD+r[S-fS^q[X\!!K.5^VKc^"AArT^VBc`"i#lo9YeF@_#M.O"IkYZ
j8T1B$"j#5!k&(irr3&.!+tm'!WWA3qu6_0"?QM]!g!_#q>UMX#s/%b!k&)@o`"uS#\Nu6"1A14
[bq)d"O%"LrrTV/B(Z*)!!Kb-rrQd83r&^VAd"4krrS8gB(u<,O9muBrrS8g3r]-\!!Kb-rrQd8
3rT'[O9oOprrS8g3iN&["O%"%1BT0.">'@)#8@:e1G^$O'`Kog/-5eS/GCq)!!Fi(*<6fQ1C#He
*<63_!"/Z\$NL;U/2K(51G]^J"SjhT,leYT1BK*-"SjhT'EfHU1BJQs'`0Zh*<63g/H.F,,leX1
1\`a,"=NmlJ,~>
!"/\?!\48-pqRXj$;A4L]YD4o^ha>X^V@R\$34nW^qfra"i+p]/&/ju^]hKg9E9par4r?I!t(<_
$K@HT,QM]WR0#GM<rSKg3rj-jR0"344o1QJ<XJj*R0"W6D>KY%<XJj)R0#GMD=j4t(]\FLRK!To
4lD_0$?QWTR0#GMD=s:u(^`G&R0$Cr!;`)dK:*&HKDCo:,QM]WR0A5;K*hTZq3_:$$3:'n!Z28-
qO%Cc$3:$m![Ib_qO%Cc$37#m!Xa#VKDu&.$4;RbrrS8h3r]-\3rl_errP7h3r&^[AdUU!s#U4h
r;QcOrX&Z+OT,7]3soTrrrS8h!;ZWrAcT:8rr]\:!6k??!g!`hrVlp\!4Mb(rsAa<rVlq2!4IIY
q>UF%p<3MuJ,~>
!!N;:!i5kspqR]B#R$"!^VIV!^q_H'9Y\?XC]ja7^VIYa^BMD'"DkifrkJ]P^]4AM^\knI^B4V(
Rfqcq!j;k0prra<!3cCGrrD`9"0W"6Oo!9'/d,)ObQ=sR!;Xh?/d,)Mbl7\8bQ=sS!;anAOTYfL
i626*S-5k9rrD`9"0W"6Oo!9'/d,)JbQ<n00)<]mY6Y.&bQQ%/"9>Y%bR(HZ"B*IfY6b>oqTK!n
"B,6C!j;k0q9/mm"B)2B!X,"rK/3TM"9^IU1C"9j*A]K&!!)pQ""XET*VhJr/-#en1FrM+q_J3m
rWEH+/2K(5!!FVE1BIpe'_sQb!;J>P'Eedmq_J9]";Lhk!\=<OqD/4""9^%L1]IH+1\`a+"SO8E
+Fj~>
!!N;:!c.j1pqR\^#R%rX^W!t&`MEP;M8-g@.g.B<^VIYa^BLG6"JrlJrkJ]PM#^5b^\knI^B4V(
RfqdO!g!\"q>UN)!':,Z!k&)@p&>)T#mC,)"5<eY[f6:.O9fIprr_!U"Le:/!k&)@o`"uS$"j&6
!bMQ<hu<`\`!!.@!k&)@p&>)T#mC,)"5<eY[e]q)Ad"4krrS8fB(u<-mlCS4q>U_^#m@^:s,[Ni
qYpZP!!Kb0rrS8fB);N0gAhAbW;co%_ut]K!t,571&Ea4/-#f(1G^$O'`9`i*<63n/G_.-*<63n
1E?H0q_J3[rWE3.r\FTr!!N-S""XET-2KD!,leYS1BRU[*@^nB#qQ&Z,qLi.*<6fM1BJ6n"T:+X
,leX11\ig-/-PYq!$Hd~>
!"/\?!\48-qS36KV$[4!qS*B6$35pt^VEMM$3:(@#J\&J$35pt^])"P^VEMCM8/u(s1\XCS!=??
R0"3*D>BS$3ri=\R0#GMD>'A"K*hU2qO%G<$34rBR0"3*D>T_'(^^7[r0[UJ!,_@r![Ib_qO%Fd
$34r%R0!_A$EBKq3rj-kR0/'K!/(-9"+pp\D>BS$$35_SR0"3*D>'A#<XK06KDh2F<XK06K:/-[
$35_TR0/'K!,__'!Z1oSqj@O'$34qAR0!_A$A/9[AdSkFr;Qk2$37O6rrP7^[f$.*g\qN[B)MZ/
48oQjL]@B9!4Me)!0I0e#*AlbgBmr)qYpZP$37O2rrQd4[eTk)3sktrrVlt3$4;mnrrP7^[eg"+
O9turrr3%3!4IIYq>UI&_YD=UI2mb~>
!!N;:!i5ksq7m-JC]l9(qS*AR"9?6P^VBgP"AJg1#J\%S"9?6P^])"P^VBgL^q]f_s1\X=S!=-:
bQ=sS!;Oe<!!'5%bl7\9bQ;hg0)<]mEs)LBbQ=sS!;jtBY6b=3qoo'?oZRB?#QX\E!\OTUi626*
UBIU@rrD`9!cnJsq9/n]"9A8A!g*U<q9/oD#lsYB""j]Z0)Woq/d);0rlbFb">9Mo!cnJErlbGI
#lskH"0W"6!2n$B"JZ*P1B]6/"9],21BT0."9AEW!\=<Oq_J3mrWE3IrA+I*!<**(0f(*U!;\JO
$iU85/2K(&":H[T1BJR"!;SGN!;J>P/-#eQrA+R-!!EEUr\FU,!!)gN!Xf,2rA4HRJM['W!<aeF
.01A(J,~>
!!N;:!c.j1q7m-J.g0\(qS*@f"AHVG^PMpn"H!,n#JZ)q"AHVG^])"P^PMqa^q]f_s1\X=S!=-:
rrS8gB)2H.[K&AfrrTV/B(c0*3WtQ?rrP4aB)_f3O9oOrrr\>h"D7T3!k&)@o`"uS#\Nr5"5<eY
B&N[j"O%"LrrTV/B(c0*3WtQ?rrP4aB)2H.Ad"4krrS8gB(l6-gAh?ZOT#1^gAh?ZOT,7]3WtQ?
rrP4aB)_f3O9oOrrr\>h"D4S3!sHgN/;,e^*<63F*W7c#*<63V/2K(&!#5;d!"8c/r%e?o!<**(
0f'LD'`0Ze/-,YPr\FWa"<Arh1Bf<c$Nq.pq_J:"!#52a"!%@E'`Bfi*<63FrA+Kq!#58c""XET
'`Kli,leX11\`a,"=Nsn!?2i*~>
!"/\?!%[gO"&*M</,?p^^a'7\rkAKL9_f"r!,VYLrkJ]P^e=`b^\knI^B=n/RgOf.R0"W6D>BS$
3ri=\R/f>B$3U?s<XL&GR0"344oUiL,kq]I!*8ub!'Bl]!!*"d!Z1oSq3_:$$6nGs!t',WK<(CA
3rj-kR0$Cr,5@Q-(^`G%R0!]qKDCo8,kq]H4oLcKKD5cR!<&;e<XL&GR0"344oUiL,kq]I!*6"c
!Xa#VJc>^^p^."_rVlp\!4Mb(!'Br_!!)ut!]pCjqYpg_$361fs,[RKq#:CW$>0/7!bMG@p]($r
!<A5ZrrP7^[e]q)3sn%Jrr?[)$31'YrrEJDq9/ng+n'p~>
!!N;:!Moek"9XK2"DmnK"/Q8+M>k6j^]=05!<Atq^])"P^VBgL^q]f_s1\X=Q(%p:bQ=sR!;Oe<
!!'5%bQ%Y:"9_WH/d-7kbQk<X"HgpPY6an+!<B>#bQ%Y:"9h]Ibc_<Qq9/rE#m>>.bQ7p;"KK]G
!;4P;/d-7kbQG$T"HiQ)!g*U<q9/lC#l"N&Oo<K(F8,k<;Z4f@/d-7kbQk<X"HgpPY6an+!<B=$
bQ7p;"G&?/!;HZs/H.F,/-#\H1BB$,p]LR2r\FU,!!)jO#qQ&Z!%]o^$NqLG1BT0."=<n#rrDdL
!%%FD!<=\L1BK*-!;SDQ/-#enr\FNSp&k@'K/3N2-i#1m)]\<s~>
!!N;:!Ghc2"9YJ#"Jtq/")J5G_#D(LM#d>S!@ttb^])"P^PMqa^q]f_s1\X=Q(%p:rrS8fB)2H.
[K&AfrrKP.p&kN,gAhB2q>UMX#mC8-!K[T^"9:L[rrKP.p&kQ-s8S/eB)2H/O9l'EiVrr^`!!.@
!k&)@pAY6L!!M'PrrS8g"Sr'!Ad"4krrJ2ep]UNp!'9lX!4Mn,"5<eYg\LjNO9l'hrrJ2ep]LRX
VZ-]#_utZJ!@7a@"T:+X*<6fM1BAKrp]LRCr\FTr!#58c#os!K$8XIh!!FkK1BJQs"SsnU,leYR
1B8-b"98f,1BJQs'_sNi*<63n/2JRPp&k@9K/3N2-i#1m)]\<s~>
!"/\?!%[gO"&'s?M>Y*g_#D(NM$a6ZrP&BK,l@uL9`;a)^]hKg9E9par4r?I!t(N_$K@HT(]\FK
R0#GM<rSKe48'!a!1=Z(D>9M'D$g8qR@+of$336bR/f>B$31)*R0"W6D>BS%D$g8DigBiTH4$[?
!]pC!p6bt!!,_M!")SAFKDLu;$35_SR/e*!$336eR/qqWr!EE(qj@L&!,_M!#&O\IK:/,Eq$I*b
V3qAlH4#6's8N'Lr!EF9qu6^Z!4M\&!+u-9!':#W!]pCjqYpiU$361fs#U4hq#:H)$38iXrrQd4
[eB_$[epI4!;c]s3rl_brs#n=!0I6\B(cQ3!/(:O$MhmJA0L^AJ,~>
!!N;:!Moek"9WHG!5SNI!5STK"&&hJ_#;"KV$Qu%!/^ZhrP/TO^]4AM^\knI^B4V.Pm$-k!j;n1
prra<!3cCG!!)]q"&c</!;F\A;?SVrbc_?K"9=S[bQ%Y:"9h]Ibc_9Pprih<"9>XbbQ7pB"KK]G
!;=V<Y6b4'bQ:ZF0)EcnOT[<4bQ4mRp]LSZqof%<qud!tr6,5G#ls_D##YRVbfm6sq#g\[V9AuD
UBH-(!])A-qud!Gr%eC*!!)dM!A"6K"99J?1BK*-!;SDU*<Z?n1FrM+pG2jb":P&^rrD^J!#5>6
!#5;d!\=<OpbN*g"99u/1B@+O!#1&A!!N*H!u2gMpOE~>
!!N;:!Ghc2"9WHG9YgS@!5STK!rrI?_#;"KC^BqA!5SQJrP/TOM#^5b^\knI^B4V.Pm$.I!g!_#
q>UN)!':,Z!O`"%"9Z)o$"ir3"ondfs8S/eq#pNn!O`"%"9l76s,[Luq#:Bq"?PNA!X-^MX8`70
!+ts)!g!_#p\t9p"H`K[!bMRsq>UJW$2=Z"rr<&s"9>n.rrS8gB(u<0!!JD`s,[Q_"Jl&!"O%""
1BK?Q!;lus1BJQs'`0Zh/2K(&!;m!"/H%@+*<6fL1C"9j$8XIY!#5/`!WW?Dq_J:"!#5,_!A"6K
"99bF1BJQs'_jHg!!EWb1F3%p"99a$1BB$0q'cC\*;B2X~>
!"/_@rP&KD]YCnfpV6aDo=kF:]YCnfrP/<HqnN'Es1\XCQ(&-?R/iuMR/hm=R/hm=R/hm=R/iuK
R/hm=R/hm4R/hm=R/hm=R/hlcR0/'KF:*SepR)#6rg<b=opGf4j-]n"rg<b=UmV=;$?QW+s+13$
s82fs$Gu\a!E^oPpOE~>
!!N;:rP&KD^q[=npV6aDo=kF:^q[=nrP/<HqnN'Es1\X=Q(%p9bQ,cFbQ,bXbQ7p;"G+dJbiHtL
"IoUG1P%?51]&s/"=O'q!>HjZpOE~>
!!N;:rP&KD`P8k#pV6aDo=kF:`P8k#rP/<HqnN'Es1\X=Q(%p9rrDNJrrDM\rrN>A"G-T(s5a1^
"N1EoJM[<^""Z5a/F,(o/2K(5n1t%qmkXqpkq`Dm1G^TE1BoBd1Ft=</:fVW1B@V+q^DO[-3!Xh~>
!"/_@rP&?FoY:=>qS*$CrP&?FqS2R9s1\XCS!=>3RGjP8$@3&1R=UdeR0!_A$A&5.s+14IrrEJD
r6,11/H,<n~>
!!N;:rP&?JoY:=>qS*$GrP&?JqS2R9s1\X=S!=,-bi-bI"JZ*Nb^n"!bQ7pB"G&@[1P%@Z1B@V+
r$_XV-2mRg~>
!!N;:rP&?OoY:=>qS*$LrP&?OqS2R9s1\X=S!=,-s5EtZ"O%""s+142rrN>I"G&@[1P%@Z1B@V+
r$_XV-2mRg~>
!"/_@rP&?JotUC>r4i6HrP&?JrkIj9s1\XCQ(&,3RGjP8$?QW+R=UdeR0!_;$A&5.s+14Irr<E)
bQ>XG2#R)u~>
!!N;:rP&?JotUC>r4i6HrP&?JrkIj9s1\X=Q(%o-bi-bI"IoUGb^n"!bQ7p;"G&@[1P%@Z1B7RV
.0:k,/,]-l~>
!!N;:rP&?JotUC>r4i6HrP&?JrkIj9s1\X=Q(%o-s5EtZ"N1Fos+142rrN>A"G&@[1P%@[1BB$0
r@%dq%P.JOJ,~>
!"/\?rP/?Iq7lL9r4hO4s1\[DS!=>5JXPMc!t'>]K7TGARH9h<$@3&1s+13$s82g#$Gt$2^-=dX
pOE~>
!!N89rP/?Iq7lL9r4hO4s1\X=S!=,-bi-bI"JZ*Nb^n"!bQ7pB"G&@[1P%@Z1Bmt0.4HSR2#I#t~>
!!N89rP/?Iq7lL9r4hO4s1\X=S!=,-s5EtZ"O%""s+142rrWDJ">#*[JMVj5r%eN[-RgJk%Q+(W
J,~>
!"/D7qnL[ss1\XCQ(&,3RGjP8$?QW+R=UdeR0!_;$A&5.s+14IrribHbe?/%qY,L~>
!!Mu1qnL[ss1\X=Q(%o-bi-bI"IoUGb^n"!bQ7p;"G&@[1P%@Z1Bdn/.4,$OqY,L~>
!!Mu1qnL[ss1\X=Q(%o-s5EtZ"N1Fos+142rrN>A"G&@[1P%@Z1Bdn/.4,$OqY,L~>
!"-WZs1\XCRrf!gRGsV:K*lo3JXM(Wjd?4'$@3&1s+13$s82g!$I#b,8,;n0~>
!!L3Ts1\X=Rs5'ebi-bI"JZ*Nb^n"!bQ7pB"G&@[1P%@Z1B[h0,Rcg2pOE~>
!!L3Ts1\X=RsbEjs5EtZ"O%""s+142rrN>I"G&@[1P%@[1Bf<4.3o<aq=fC~>
!"-WZs1\XCF<CpARGjP8$=O9mR=UdeR0!_($A&5.s+14IrrWV5.nf[3J,~>
!!L3Ts1\X=GT[-?bi-bI"G-c-b^n"!bQ7p!"G&@[1P%@Z1BRb*!'g8QJ,~>
!!L3Ts1\X=I38ZDs5EtZ"JZ*Ns+142rrWD!">#*[JMVj5r%eEX,QL((pOE~>
!"-WZs1\XC+E.[=RGjP8$4dJpR=UdeR0!^+$A&5.s+14IrrNNE:&"C4~>
!!L3Ts1\X=+EI[:bi-bI":t]eb^n"!bQ7nY"G&@[1P%@Z1BI[c621,(~>
!!L3Ts1\X=+F+*@s5EtZ":t]es+142rrN<7"G&@[1P%@Z1BI[c621,(~>
!"cpQ!c[lCJI:?C!XR$[JI6r8isGh"%=nk;$@rQ\$33`mpOE~>
!"cpK!d")GJHXp7!XR$\JHUN,irfCu%Y4b6"G$^P"9:[[pOE~>
!"cpK!dXMPJHXp7!XR$_JHUN,irfD%&V1(9"G$^P"9:[[pOE~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
JbO`iJbO`iJbO`iJbS=$J,~>
p\+^gOT#Rf!4IIQU%\TfOSB.`!4IIQJbO`iJbQ,;!WP7\$33-]pB(A,!7:Ij~>
p\+^g3rT6_!<7WEU%\Tf3qrgY!<7WEJbO`iJbQ,;!S[VR"9=PXpB(?pB$g:&~>
p\+^C!<**%B)dGVU%\TB!;HZtB)dGVJbO`iJbQ,;!K[<\"9@3NpB%c'[a=c!~>
q"Fgh48T?b<e:">pAt;Up^.$%[Xn1(pOW'ipSmleg\V<Y!4M^t!k&F9d.`%~>
q"FgD!;cm!YCZGBpAt:Tp]LTts+0oqpOW'ipSmleOS].]!<;lh!bMRId.`%~>
q"FfN!;cm!s+0p=pAs"Kp]LV0s+0oqpOW'ipSmle3r9$\B)h]$!WWA3d.`%~>
q=b6rO9u!Bp]$<^!4Ki?!rlj4JbRpn"91F_OFd.WpOW'ipSmlns#UQgp\4]"$39qs"1AOD!7:Ij~>
q=b6r3Wq)8p]!8W!<:"3!ri74JbRpn"5<eUgO]*MpOW'ipSmlngAhA[p\4[f"D7N)"(hZ=B$g:&~>
q=b6N!!JD`pY,ZNB)fgD!k&*;JbRpn"-<LDs+0oqpOW'ipSmlnO8oF`p\2)r"Le1$!rrH,[a=c!~>
q=b!k$37O6pB1GW$9H-"!k&F9JbRso"94Gi3e@$VpOW'ipSdff[L,(opB1FW!4Mk#!WQd2$31(Q
pOE~>
q=b!""9AK$pB1FV"H^Rr!bMRIJbRso"91CbOFd.WpOW'ipSdffAd"4opB0.M!<<#l!WN0!"9<&\
pOE~>
q=au'"D7]5pB0.M"P^lh!WWA3JbRso"5<eYgO]*MpOW'ipSdff!!M'TpB-KWB)hi(!O`"-"9>mW
pOE~>
q=as!$3:"u"5=._gW&si[L*?AJbRso"5=._gO]*MpOW'ipS%<`s(28>rq?Ws$46e5$39\l"TLO`
gY;]7!<A5FpOE~>
q=ar&"D7T+"-<X<s2Od8Acr6KJbRso"-<X<s+0oqpOW'ipS%<`rr<'!rq?W*"9`>F"D79""PWo/
s,d3_!<Cm<pOE~>
q=apj"Le7&"$-RHs2Od8!!EG5JbRso"$-RHs+0oqpOW'ipS%<`[K'q>rq?V/"9c/F"Ldpr"HWVF
s#U,^!BU8EpOE~>
qY(*lO:#UtpB1FW!4KuC!WQd2$31'WpYGQGs(2UMJbO`iJbO`iT_AQg$38i[pBLWtOSAb^!;#pe
s#U3mq[*<al1]]~>
qY(*l3WunhpB0.M!<:.7!WN0!"9<%bpYGQGrr<21JbO`iJbO`iT_APs">9`RpBJ&+g\8,ZB(Pa!
gAj8qqZHn\l1]]~>
qY(*H!!N?"pB-KWB)fsH!O`"-"9>l]pYGQG[K$I1JbO`iJbO`iT_AP#"H`Z_pBG4js7^+.[e'4q
O8rkmqZHoRl1]]~>
qY(*lAdVT:pB1G-!0G=s#6,#;!<3B+JbS!p"1AO:gO]*MpZhJUs#U58g\qN\!0Dd+JbR%U"9/]8
B)20&rsAbtpC%!_$48igp]$<^!4M1eJ,~>
qY(*l!!KM'pB1Eq!8GWi#.=X;B!qhSJbS!p"(hZgs+0oqpZhJUgAj;7OT#@`!8E)!JbR%U"1A=8
[e]Y![KL46pC#^U"9ah\p]!8W!<;?YJ,~>
qY(*#!!N?"pB.i(3rd=r#%du@[UoZNJbS!p!rrIcs+0oqpZhJUO8tCB3rT6_3rad*JbR%U"(hZ=
s7uBjAd#R6pC!&_"9dKRpY,ZNB)h/jJ,~>
qY(*lAcRqjpB1G-!0G=s#6+[bp\tR#JbS$q"92s?AqB]-pZ_DSs#U3mq[*<aJbO`i`V0MD$46cp
q"Fji$39\l"91Fi3rSdTs#U4BlM#f~>
qY(*l!!(dOpB1Eq!8GWi#.=NdpU13:JbS$q"9/?([Xn1(pZ_DSgAj8qqZHn\JbO`i`V0LI"9\_Z
q"Fiu"D79""5<eYOT"nUgAh6.lM#f~>
qY(*#!':/XpB.i(3rd=r#%fprpLXQ:JbS$q"1A14s+0oqpZ_DSO8rkmqZHoRJbO`i`V0K8"9^dc
q"Fi%"Ldpr"-<Kcg\pjKO8q?AlM#f~>
qY(*lAcRqjpB1G-!0G1o!riT-JbS$q"5=._gO]*MpZ_D[s#UPp3qrCKO9turJbO`ia7fW7OSoLd
<r)IkrsAbtpB1FW!0I0R"92s5OQuRj~>
qY(*l!!(dOpB1Eq!8GKe!k&5DJbS$q"-<X<s+0oqpZ_D[gAh?ZOSAML3WoH_JbO`ia7fW73rK0]
YPIno[KL46pB0.M!8IJH"9/?$gZnN`~>
qY(*#!':/XpB.i(3rd1n!bMSDJbS$q"$-RHs+0oqpZ_D[O8oCdg\:Hs!!Hp6JbO`ia7fVh!<!$#
s7uBjAd#R6pB-KW3rf0Q"1A1js6B?/~>
qY(*lAcRqjpB1G-!0G1o!riT-JbS'r"94Gi3e@$VpZV>Rs#UQKrV$Eo3rk@npOW(VpB1FW$>0//
!riT-oCi@e3rl_fpB1G-!0HL?J,~>
qY(*l!!(dOpB1Eq!8GKe!k&5DJbS'r"91CbOFd.WpZV>RgAhA<rV$EK!!(c)pOW(VpB0.M"Le1$
!k&5DoCi@A!!*&tpB1Eq!8Hf5J,~>
qY(*#!':/XpB.i(3rd1n!bMSDJbS'r"5<eYgO]*MpZV>RO8oF<rV$DU!':.2pOW(VpB-KW"TS>m
!bMSDoCi?K!+u90pB.i(3reL>J,~>
qY(*lO:#UtpB1G-!0G1o!riT-JbS'r"91F_[Xn1(pZV>Rs#U4BrV$EoAcRpDpOW(UpB0.W!8IDF
!riT-r:^3kq[*L-p]!;T[f,q&s(27mlM#f~>
qY(*l3WunhpB1Eq!8GKe!k&5DJbS'r"5<eUs+0oqpZV>RgAh6.rV$Eo!!(c)pOW(UpB-K[0)thC
!k&5Dr:^3"qZI)+pY,ZJs8DZorr<&RlM#f~>
qY(*H!!N?"pB.i(3rd1n!bMSDJbS'r"-<Lps+0oqpZV>RO8q?ArV$E&!':.2pOW(UpB*GZOT54P
!bMSDr:^2'qZI*&pQ,Aes8DZo[K&AglM#f~>
qY(*lO:#%dpB1FW!4Kl@!riT-JbS'r!o"%^JbO`ikP#)Y3rl_fpB1G-!0Dd+JbR%U"91F_[eoe#
rsAc(pAk6j$3b&9s#U4hrV$EoAcRqXpOE~>
qY(*l3WuYapB0.M!<:%4!k&5DJbS'r!g!OsJbO`ikP#)5!!*&tpB1Eq!8E)!JbR%U"5<eUs82Nl
[KL4?pAhZ!"9l7.gAh6RrV$Eo!!(d=pOE~>
qY(*H!!N?"pB-KWB)fjE!bMSDJbS'r!]gImJbO`ikP#(?!+u90pB.i(3rad*JbR%U"-<Lps82Nl
Ad#R?pAeh&"9ni"O8rnmrV$E&!':/FpOE~>
q=as!$3:"u"5=._gVrmgrsAaWpYY]H[L,'HpOW)%pB1FW!4Mk#"92s5OFd.WpWN:5s,[Rgr:^<n
3rl_dpB(@%!;#pes#U4hrV$EoAcRqXpOE~>
q=ar&"D7T+"-<XHs2F^6[KL2npYY]HAd"3HpOW)%pB0.M!<<#l"9/?$gO]*MpWN:5s#L:dr:^<J
!!*&rpB%c+B(Pa!gAh6RrV$Eo!!(d=pOE~>
q=apj"Le7&"$-RHs2F^6Ad#PnpYY]H!!M&-pOW)%pB-KWB)hi("1A1js+0oqpWN:5gAhBVr:^;T
!+u9.pB"q0[e'4qO8rnmrV$E&!':/FpOE~>
q=b!k$37O6pB1GW$9H-"!riT-JbS*s"92s5OFd.WpZM8Qs#U4hrV$EoAcRpDpOW(]pB%c1!;uQm
gBmrWpB(@%!;#pes#U4hrV$EoAcRqXpOE~>
q=b!""9AK$pB1FV"H^Rr!k&5DJbS*s"9/?$gO]*MpZM8QgAh6RrV$Eo!!(c)pOW(]pB"q0B)MB)
O9B1lpB%c+B(Pa!gAh6RrV$Eo!!(d=pOE~>
q=au'"D7]5pB0.M"P^lh!bMSDJbS*s"1A1js+0oqpZM8QO8rnmrV$E&!':.2pOW(]pAt<t[f#k$
3WunfpB"q0[e'4qO8rnmrV$E&!':/FpOE~>
q=b6rO9turp]$<^!4Ki?!riT-JbS*s"91F_[Xn1(pZM8Qs#U4hrV$EoAcRpDpOW(]pB1FW$>0;3
"9/].OS\\QrsAbtpB1FW!4Mk#"92s5OQuRj~>
q=b6r3WoH_p]!8W!<:"3!k&5DJbS*s"5<eUs+0oqpZM8QgAh6RrV$Eo!!(c)pOW(]pB0.M"Le=(
"1A=4g\UXG[KL46pB0.M!<<#l"9/?$gZnN`~>
q=b6N!!Hp6pY,ZNB)fgD!bMSDJbS*s"-<Lps+0oqpZM8QO8rnmrV$E&!':.2pOW(]pB-KW"TSJq
"(hZss8)HkAd#R6pB-KWB)hi("1A1js6B?/~>
q"Fgh48T?bB#F?ErsAaWpYbcJrsAb\JbO`ikP#)Y3rl_fpB1G-!0Dd+JbR:\!<;m&!+u'$!riT-
oCi@e3rl_fpB1G-!0HL?J,~>
q"FgD!;cm![_qh@[KL2npYbcJ[KJ5_JbO`ikP#)5!!*&tpB1Eq!8E)!JbR:\!4M_,!4M^t!k&5D
oCi@A!!*&tpB1Eq!8Hf5J,~>
q"FfN!;cm!s24R4Ad#PnpYbcJAd"4qJbO`ikP#(?!+u90pB.i(3rad*JbR:\!+u'1!<;lh!bMSD
oCi?K!+u90pB.i(3reL>J,~>
p\+^gOT#Rf!4Kc=!riT-JbS*s"9/].gO]*MpZM8Qs#U4hrV$EoAcRpDpOW([pAt;+rX&Z+g\CLE
rsAbtpB1FW!4Mk#"92s5OQuRj~>
p\+^g3rT6_!<9q1!k&5DJbS*s"1A=ns+0oqpZM8QgAh6RrV$Eo!!(c)pOW([pAt9orWE6Ss7l<i
[KL46pB0.M!<<#l"9/?$gZnN`~>
p\+^C!<**%B)faB!bMSDJbS*s"(h[ts+0oqpZM8QO8rnmrV$E&!':.2pOW([pAq]&rWE7`s7l<i
Ad#R6pB-KWB)hi("1A1js6B?/~>
Z1e>"$35YOJbO`i_Y4043rl_fpB1G-!0Dd+JbOilJ,~>
Z1e=."D35ZJbO`i_Y4/e!!*&tpB1Eq!8E)!JbOilJ,~>
Z1e<3"L`mUJbO`i_Y4.o!+u90pB.i(3rad*JbOilJ,~>
Z1e>"$35YOJbO`i_Y4043rl_fpB1G-!0Dd+JbOilJ,~>
Z1e=."D35ZJbO`i_Y4/e!!*&tpB1Eq!8E)!JbOilJ,~>
Z1e<3"L`mUJbO`i_Y4.o!+u90pB.i(3rad*JbOilJ,~>
%%EndData
showpage
%%Trailer
end
%%EOF
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+10
View File
@@ -0,0 +1,10 @@
/**
@mainpage MySensors library and examples documentation
@copyright (C) 2013-2015 Sensnology AB
Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*/
/**
@defgroup internals MySensors internal APIs and functionalities
*/
+21
View File
@@ -0,0 +1,21 @@
<!-- HTML footer for doxygen 1.8.6-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby
<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/></a> $doxygenversion </li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer"><small>
$generatedby &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/>
</a> $doxygenversion
</small></address>
<!--END !GENERATE_TREEVIEW-->
</body>
</html>
+55
View File
@@ -0,0 +1,55 @@
<!-- HTML header for doxygen 1.8.6-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
+21
View File
@@ -0,0 +1,21 @@
<!-- HTML footer for doxygen 1.8.6-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby
<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/></a> $doxygenversion </li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer"><small>
$generatedby &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/>
</a> $doxygenversion
</small></address>
<!--END !GENERATE_TREEVIEW-->
</body>
</html>
+55
View File
@@ -0,0 +1,55 @@
<!-- HTML header for doxygen 1.8.6-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-338
View File
@@ -1,338 +0,0 @@
/*
MYSBootloader: OTA bootloader for Mysensor nodes (www.mysensors.org)
Original OTA bootloader code by ToSa
Optimized and extended by tekka
Version 1.1 / 20150314
Size: 2002 bytes
Tested with MYSController 0.1.2.276 (goo.gl/9DCWNo)
MCU: Atmega328p
bootsz: 1024W
fuses for ISP:
EX = 0xFE (use 0x06 for Arduino IDE, boards.txt)
HI = 0xDA
LO = 0xF7
nRF24L01+ connected to pins:
CE = 9
CSN = 10
Successfully tested with:
16Mhz extXTAL, 3.3V & 5V
8Mhz intRC, 3.3V & 5V
1Mhz intRC, 3.3 & 5V
128kHz intRC, 3.3V & 5V
* Version 1.1
- use eeprom_update instead of eeprom_write to reduce wear out
- bootloader commands: erase eeprom, set node id
- verify incoming FW blocks for type & address
- communicate over static parent (if set and found) else broadcast to find nearest node
- adjusted timings
* Version 1.0
Initial release
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
*/
#include "MYSBootloader.h"
#define MYSBOOTLOADER_MAJVER 1
#define MYSBOOTLOADER_MINVER 1
#define MYSBOOTLOADER_VERSION (MYSBOOTLOADER_MINVER * 256 + MYSBOOTLOADER_MAJVER)
#define MAX_RESEND 5
// procedures and functions
static void programPage(uint16_t page, uint8_t *buf);
static uint16_t calcCRCrom (const void* ptr, uint16_t len);
static uint8_t IsFirmwareValid();
static void reboot();
static void startup();
static boolean sendWrite(MyMessage message);
static bool sendAndWait(uint8_t reqType, uint8_t resType);
int main(void) __attribute__ ((OS_main)) __attribute__ ((section (".init9")));
static void programPage(uint16_t page, uint8_t *buf) {
// these function calls use "out" commands: save some bytes and cycles :)
__boot_page_erase_short(page);
boot_spm_busy_wait();
for (uint16_t i = 0; i < SPM_PAGESIZE; i += 2) {
uint16_t data_word = *buf++;
data_word += (*buf++) << 8;
__boot_page_fill_short(page + i, data_word);
}
__boot_page_write_short(page);
boot_spm_busy_wait();
__boot_rww_enable_short();
}
static uint16_t calcCRCrom (const void* ptr, uint16_t len) {
// init 0xFFFF
uint16_t crc = ~0;
for (uint16_t i = 0; i < len; i++) {
crc = _crc16_update(crc, pgm_read_byte((uint16_t) ptr + i));
}
return crc;
}
static uint8_t IsFirmwareValid () {
return calcCRCrom(0, fc.blocks * FIRMWARE_BLOCK_SIZE) == fc.crc;
}
static void reboot() {
// wait for pending eeprom activities
eeprom_busy_wait();
// trigger watchdog ASAP
watchdogConfig(WATCHDOG_16MS);
// endless loop
while (1);
}
static void startup() {
if (IsFirmwareValid()) {
// WD off
watchdogConfig(WATCHDOG_OFF);
// run sketch
((void(*)()) 0)();
} else {
reboot();
}
}
static boolean sendWrite(MyMessage message) {
return write(nc.parentNodeId, message.array, HEADER_SIZE + mGetLength(message), (message.destination == BROADCAST_ADDRESS));
}
static bool sendAndWait(uint8_t reqType, uint8_t resType) {
outMsg.type = reqType;
// outer loop, retries
for (uint8_t i = 0; i < MAX_RESEND; i++) {
sendWrite(outMsg);
// loop 20 times, wait time 0.1s if no/wrong data => 2s
for (uint8_t j = 0; j < 20; j++) {
// loop 100 times, wait 1ms if no/wrong data => 0.1s
for (uint8_t k = 0; k < 100; k++) {
watchdogReset();
// Tx FIFO data available? (we don't care about the pipe here)
if (available(NULL)) {
// read message from FIFO, skip if size = 0
if (readMessage(inMsg.array) > 0) {
// protocol compatible? if not ignore msg
if ((mGetVersion(inMsg) != PROTOCOL_VERSION)) {
continue;
}
// msg for us?
if (inMsg.destination == nc.nodeId) {
// internal command: find parent
if ((mGetCommand(inMsg) == C_INTERNAL) && (inMsg.type == I_FIND_PARENT_RESPONSE)) {
// static parent found?
if (configuredParentID == inMsg.sender) {
configuredParentFound = true;
}
if ( ((inMsg.bValue < nc.distance - 1) && ( !configuredParentFound) ) || (configuredParentID == inMsg.sender)) {
// got new routing info, update settings
nc.distance = inMsg.bValue + 1;
nc.parentNodeId = inMsg.sender;
}
}
// did we receive expected reply?
if ((mGetCommand(inMsg) == mGetCommand(outMsg)) && (inMsg.type == resType)) {
return true;
}
}
}
} else {
// wait 1ms if no data available
_delay_ms(1);
}
}
}
}
return false;
}
// main start
int main(void) {
asm volatile ("clr __zero_reg__");
// reset MCU status register
MCUSR = 0;
// enable watchdog to avoid deadlock
watchdogConfig(WATCHDOG_8S);
// initialize SPI
SPIinit();
// initialize RF module
RFinit();
// Read node config from EEPROM, i.e. nodeId, parent nodeId, distance
eeprom_read_block((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(struct NodeConfig));
// Read firmware config from EEPROM, i.e. type, version, CRC, blocks
eeprom_read_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
// find nearest node during reboot: invalidate parent node settings, since we have to re-discover them for every single reboot
configuredParentID = nc.parentNodeId;
// nc.parentNodeId = 0xFF;
nc.distance = 0xFF;
// prepare for I_FIND_PARENTS
outMsg.sender = nc.nodeId;
outMsg.last = nc.nodeId;
outMsg.sensor = 0xFF;
outMsg.destination = BROADCAST_ADDRESS;
// set header
mSetVersion(outMsg, PROTOCOL_VERSION);
mSetLength(outMsg, 0);
mSetCommand(outMsg, C_INTERNAL);
mSetAck(outMsg,false);
mSetPayloadType(outMsg, P_STRING);
// set reading & writing pipe address
setAddress(nc.nodeId);
// network up? get neighbors, else startup
if (!sendAndWait(I_FIND_PARENT, I_FIND_PARENT_RESPONSE)) {
startup();
}
// all messages to gateway
outMsg.destination = GATEWAY_ADDRESS;
// if no node id assigned, request new id
if (nc.nodeId == AUTO) {
// listen to broadcast
openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(BROADCAST_ADDRESS));
if (sendAndWait(I_ID_REQUEST, I_ID_RESPONSE)) {
// save id to eeprom
eeprom_update_byte((uint8_t*)EEPROM_NODE_ID_ADDRESS, atoi(inMsg.data));
}
// we could go on and set everything right here, but rebooting will take care of that - and saves some bytes :)
reboot();
}
// wuff
watchdogReset();
// prepare for FW config request
RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)outMsg.data;
mSetLength(outMsg, sizeof(RequestFirmwareConfig));
mSetCommand(outMsg, C_STREAM);
mSetPayloadType(outMsg,P_CUSTOM);
// copy node settings to reqFWConfig
memcpy(reqFWConfig,&fc,sizeof(NodeFirmwareConfig));
// add bootloader information
reqFWConfig->BLVersion = MYSBOOTLOADER_VERSION;
// send node config and request FW config from controller
if (!sendAndWait(ST_FIRMWARE_CONFIG_REQUEST, ST_FIRMWARE_CONFIG_RESPONSE)) {
startup();
}
NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)inMsg.data;
// bootloader commands
if (firmwareConfigResponse->blocks == 0) {
// verify flag
if (firmwareConfigResponse->crc == 0xDA7A){
// cmd 0x01 clear eeprom
if(firmwareConfigResponse->bl_command == 0x01) {
for(uint16_t i = 0; i < EEPROM_SIZE; i++) eeprom_update_byte((uint8_t *)i,0xFF);
} else
// cmd 0x02 set id
if(firmwareConfigResponse->bl_command == 0x02) {
eeprom_update_byte((uint8_t*)EEPROM_NODE_ID_ADDRESS, (uint8_t)firmwareConfigResponse->bl_data);
}
}
// final step
reboot();
}
// compare with current node configuration, if equal startup
if (!memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) {
startup();
}
// *********** from here on we will fetch new FW
// invalidate current CRC
fc.crc = 0xFFFF;
// write fetched type and version in case OTA fails (BL will reboot and re-request FW with stored settings)
eeprom_update_block(&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS,sizeof(NodeFirmwareConfig));
// copy new FW config
memcpy(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig));
RequestFWBlock *firmwareRequest = (RequestFWBlock *)outMsg.data;
mSetLength(outMsg, sizeof(RequestFWBlock));
firmwareRequest->type = fc.type;
firmwareRequest->version = fc.version;
// request FW from controller, load FW counting backwards
uint16_t block = fc.blocks;
do {
firmwareRequest->block = block - 1;
// request FW block
if (!sendAndWait(ST_FIRMWARE_REQUEST, ST_FIRMWARE_RESPONSE)) {
reboot();
}
ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)inMsg.data;
// did we receive requested block?
if (!memcmp(firmwareRequest,firmwareResponse,sizeof(RequestFWBlock))) {
// calculate page offset
uint8_t offset = ((block - 1) * FIRMWARE_BLOCK_SIZE) % SPM_PAGESIZE;
// write to buffer
memcpy(progBuf + offset, firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
// program if page full
if (offset == 0) {
programPage(((block - 1) * FIRMWARE_BLOCK_SIZE), progBuf);
}
block--;
}
} while (block);
// wuff
watchdogReset();
// all blocks transmitted, calc CRC and write to eeprom if valid
if (IsFirmwareValid()) {
// if FW is valid, write settings to eeprom
eeprom_update_block(&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
}
// final step
reboot();
}
-65
View File
@@ -1,65 +0,0 @@
#ifndef MyOtaBootloader_H
#define MyOtaBootloader_H
#include <avr/power.h>
#include <stdint.h>
#include <string.h>
#include <util/crc16.h>
#include <util/delay.h>
#include "MyMessage.h"
#include "MySensor.h"
#include "boot.h"
#include "MYSBootloaderRF24.h"
#define FIRMWARE_BLOCK_SIZE 16
#define EEPROM_SIZE 1024 // 1024 bytes for ATMEGA328
typedef struct {
union {
uint16_t type;
uint16_t bl_command;
};
union {
uint16_t version;
uint16_t bl_data
};
uint16_t blocks;
uint16_t crc;
} __attribute__((packed)) NodeFirmwareConfig;
typedef struct {
uint16_t type;
uint16_t version;
uint16_t blocks;
uint16_t crc;
uint16_t BLVersion;
} __attribute__((packed)) RequestFirmwareConfig;
typedef struct {
uint16_t type;
uint16_t version;
uint16_t block;
} __attribute__((packed)) RequestFWBlock;
typedef struct {
uint16_t type;
uint16_t version;
uint16_t block;
uint8_t data[FIRMWARE_BLOCK_SIZE];
} __attribute__((packed)) ReplyFWBlock;
static struct NodeConfig nc;
static NodeFirmwareConfig fc;
static MyMessage outMsg;
static MyMessage inMsg;
static boolean configuredParentFound = false;
static uint8_t configuredParentID = 0xFF;
//static clock_div_t orgClockDiv = 0;
static uint8_t progBuf[SPM_PAGESIZE];
#endif // MyOtaBootloader_H
-128
View File
@@ -1,128 +0,0 @@
:1078000011E0A0E0B1E0E0EDFFE702C005900D92CD
:10781000A230B107D9F721E0A2E0B1E001C01D928A
:10782000A83DB207E1F7CDB7DEB76897DEBFCDBFA1
:10783000112414BE88E18093600089E28093600087
:107840008FEE84B963E083E2D9D16FE584E2D6D1CB
:107850006BE381E2D3D16CE485E2D0D167E286E2CA
:10786000CDD12ED263E780E5C9D166E08DE3C6D1E4
:107870006FE38CE3C3D16EEF80E2C0D18FE19EE471
:107880000197F1F700C0000043E050E060E070E0D5
:107890008DEC91E06CD348E050E06BE171E085EC59
:1078A00091E065D38091CE01809300018FEF8093AA
:1078B000CF012091CD012093A5012093A4018093B5
:1078C000AA018093A60182E08093A7018091A8017C
:1078D000887083608093A8012F3FD1F130E040E0B1
:1078E00050E060E070E080E090E034504E41574559
:1078F00067457F4F8F4F9F4F29013A014B015C0134
:10790000298B5A8A4B8B7C8A6D8B9E8A8F8BB88E83
:1079100085E0FE017196A0EDB1E001900D928A958F
:10792000E1F745E0BE016F5E7F4F8AE255D149869F
:107930005A866B867C868D869E86AF86B88A45E0A1
:10794000BE01675F7F4F8BE247D163E082E256D191
:1079500068E087E0BAD1811101C09CD11092A601E4
:107960008091CD018F3FF1F489838CEF8A8381EE82
:107970008B8388EA8C838D831E821F82188645E064
:10798000BE016F5F7F4F8BE227D164E083E09DD122
:10799000882339F08AE891E0B8D2682F80E090E03F
:1079A00005D36FD1A8958091A701877080658093DA
:1079B000A7018091A8018871846C8093A80188E058
:1079C000E5ECF1E0ABEAB1E001900D928A95E1F7C8
:1079D00081E091E09093B4018093B30161E080E095
:1079E00074D1811101C056D180918E0190918F0187
:1079F000892B09F580919001909191018A379A4DD8
:107A000081F680918A0190918B018130910569F412
:107A1000A12CB12C6FEFC501C9D2FFEFAF1ABF0A7D
:107A2000A11424E0B206B1F7BCCF029709F0B9CF98
:107A300060918C01B3CF48E050E06AE871E085ECDA
:107A400091E080D2892B09F425D18FEF9FEF90939D
:107A5000CC018093CB0148E050E06BE171E085EC14
:107A600091E095D288E0EAE8F1E0A5ECB1E0019080
:107A70000D928A95E1F78091A701877080638093CA
:107A8000A7018091C5019091C6019093AC018093AC
:107A9000AB018091C7019091C8019093AE01809392
:107AA000AD01A090C901B090CA0110E101E0E5E08C
:107AB0007E2EF1E16F2E450181E0881A91089092A7
:107AC000B0018092AF0163E082E0FFD0882309F427
:107AD00068CF46E050E06AE871E08BEA91E032D28C
:107AE000892B09F045C091E0A91A90EFB90A8A2DB7
:107AF00082958077A82FB0E0AE5FBE4FE0E9F1E05D
:107B0000912F01900D929A95E1F7811130C074E0A8
:107B1000AA0CBB1C7A95E1F783E0F50187BFE895D5
:107B200007B600FCFDCF4091020180E090E0DC014F
:107B3000AE5FBE4FFC01EA0DFB1D11962C91119713
:107B400030E0322F2227240F311D090107BFE895AD
:107B5000112412964C9102968038910541F7F50157
:107B600077BEE89507B600FCFDCF67BEE8955401E7
:107B7000A114B10409F09FCFA8954FD0882309F430
:107B800010CF48E050E06BE171E085EC91E0FFD16F
:107B900008CF98E087FF02C02B9A01C02B982D9A3E
:107BA00023B1880F22952170822B2D98915091F747
:107BB00008951F93CF93DF93EB01142F2A98E9DFE9
:107BC0001C0F1C1721F08FEFE4DF8993FACF2A9A5C
:107BD000DF91CF911F9108951F93CF93DF93EB0116
:107BE000142F2A98D6DF1C0F1C1719F08991D1DFAA
:107BF000FBCF2A9ADF91CF911F910895CF93DF9306
:107C00001F92CDB7DEB7698341E0BE016F5F7F4F42
:107C1000E3DF0F90DF91CF9108958091C90190919A
:107C2000CA0124E0880F991F2A95E1F7E0E0F0E00F
:107C30002FEF3FEFE817F907D1F044912427422FA7
:107C400042954227042E469546954025042E46959A
:107C500040254770022E232F469507944795302DD7
:107C6000242706944795302524273196E3CF81E0D9
:107C70004091CB015091CC012417350709F080E0E9
:107C80000895F999FECF88E18093600088E08093A1
:107C90006000FFCFC2DF882341F088E1809360005D
:107CA00010926000E0E0F0E00994EBDF40E060E07B
:107CB00070E082EE91CF40E060E070E081EE8CCF2A
:107CC000F5DFF9DF60E787E299CF6F927F928F92BD
:107CD0009F92AF92BF92CF92DF92EF92FF920F935B
:107CE0001F93CF93DF93CDB7DEB7AA97DEBFCDBF8B
:107CF000762E8093A90105E051E2652EE4EAF1E0D9
:107D0000DE011196862D01900D928A95E1F7FB8098
:107D10001C81169516951695195F2091CE0129980C
:107D20002AA7CEDF6EEF80E269DF2AA530E040E0CF
:107D300050E060E070E080E090E034504E41574504
:107D400067457F4F8F4F9F4F2AA33BA34CA35DA353
:107D50006EA37FA388A799A745E0BE016E5D7F4F04
:107D60008AE23ADF45E0BE016E5D7F4F80E334DF9B
:107D70008FEFF81202C080EB01C080EA412FBE01F4
:107D80006F5F7F4F29DF299A9AE69A95F1F700C035
:107D9000299840E060E070E08FEF0BDF8073C9F35B
:107DA00060E787E22BDF84FD86DF6FE080E226DF7D
:107DB00087DF8090D0019090D101A090D201B09047
:107DC000D301C090D401D090D501E090D601F090BD
:107DD000D7019401A501B601C701A0E08AD029F01E
:107DE00045E060ED71E08AE2F7DE299A44E1E42E95
:107DF00034E6F32EA89541E060E070E087E1D9DE3B
:107E000080FD5EC041E060E070E080E6D2DE182FC9
:107E1000813210F04BDF10E0412F63E871E081E622
:107E2000C8DE60E787E2EADE1123E9F1809186018E
:107E300087708230C1F5909185018091CD019813B2
:107E400032C050918701852F8770833019F580915A
:107E500088018830F9F47091000140918401741315
:107E600003C081E08093820160918A01262F30E077
:107E70008091CF0190E001972817390724F4809171
:107E80008201882311F0741305C06F5F6093CF01E6
:107E90004093CE018091A8018527877021F48091BD
:107EA0008801871559F0FA9409F0A4CFEA9409F0F3
:107EB0009FCF015009F022CF80E009C081E007C0C8
:107EC0008FE99FE00197F1F700C00000ECCFAA9680
:107ED000DEBFCDBFDF91CF911F910F91FF90EF904B
:107EE000DF90CF90BF90AF909F908F907F906F90DA
:107EF00008950024A7FD00942A1730054005500579
:107F000060057005800590050895FC018827992774
:107F1000E89421912032E9F3293010F02E30C8F393
:107F20002B3239F02D3231F4689403C018D0820F0F
:107F3000911D219120532A30C8F31EF4909581950C
:107F40009F4F0895FB01DC0104C08D9101908019C1
:107F500021F441505040C8F7881B990B08957AE0EE
:107F6000979F902D879F802D910D11240895DC01FE
:107F7000CB01FC01F999FECF06C0F2BDE1BDF89A34
:107F8000319600B40D9241505040B8F70895DC018D
:107F9000A40FB51F4150504040F0CB01840F951FF6
:107FA0002E9105D041505040D8F70895262FF999C9
:107FB000FECF92BD81BDF89A019700B4021639F048
:107FC0001FBA20BD0FB6F894FA9AF99A0FBE089519
:027FD000FF00B0
:040000030000780081
:00000001FF
-128
View File
@@ -1,128 +0,0 @@
#ifndef MYSBootloaderHW_H
#define MYSBootloaderHW_H
#include <stdlib.h>
// hardware
#define F_CPU_DIV clock_div_4
#define F_CPU_REAL F_CPU / (F_CPU_DIV)
#define BAUD_SETTING (( (F_CPU_REAL + BAUD_RATE * 4L) / ((BAUD_RATE * 8L))) - 1 )
#define BAUD_ACTUAL (F_CPU_REAL/(8 * ((BAUD_SETTING)+1)))
#define BAUD_ERROR (( 100*(BAUD_RATE - BAUD_ACTUAL) ) / BAUD_RATE)
#define UART_SRA UCSR0A
#define UART_SRB UCSR0B
#define UART_SRC UCSR0C
#define UART_SRL UBRR0L
#define UART_UDR UDR0
// boolean definition
typedef uint8_t bool;
typedef uint8_t boolean;
#define false 0x0
#define true 0x1
#define LOW 0x0
#define HIGH 0x1
// Watchdog definitions and functions
#define WATCHDOG_OFF (0)
#define WATCHDOG_16MS (_BV(WDE))
#define WATCHDOG_32MS (_BV(WDP0) | _BV(WDE))
#define WATCHDOG_64MS (_BV(WDP1) | _BV(WDE))
#define WATCHDOG_125MS (_BV(WDP1) | _BV(WDP0) | _BV(WDE))
#define WATCHDOG_250MS (_BV(WDP2) | _BV(WDE))
#define WATCHDOG_500MS (_BV(WDP2) | _BV(WDP0) | _BV(WDE))
#define WATCHDOG_1S (_BV(WDP2) | _BV(WDP1) | _BV(WDE))
#define WATCHDOG_2S (_BV(WDP2) | _BV(WDP1) | _BV(WDP0) | _BV(WDE))
#define WATCHDOG_4S (_BV(WDP3) | _BV(WDE))
#define WATCHDOG_8S (_BV(WDP3) | _BV(WDP0) | _BV(WDE))
inline void watchdogReset() {
asm volatile ("wdr");
}
void watchdogConfig(uint8_t x) {
WDTCSR = _BV(WDCE) | _BV(WDE);
WDTCSR = x;
}
// SPI communication
#define SPI_DDR DDRB
#define SPI_PORT PORTB
#define SPI_PIN PINB
#define SPI_SCLK 5 // Arduino Pin 13 <-> Bit 5 of port B
#define SPI_MISO 4 // Arduino Pin 12 <-> Bit 4 of port B
#define SPI_MOSI 3 // Arduino Pin 11 <-> Bit 3 of port B
#define SPI_CSN 2 // Arduino Pin 10 <-> Bit 2 of port B
#define SPI_CE 1 // Arduino Pin 9 <-> Bit 1 of port B
#define CE_PULSE_LENGTH 20 // IMPORTANT: minimum CE pulse width 10us, see nRF24L01 specs. Set 20us to be on the safe side
#define csnlow() SPI_PORT &= ~_BV(SPI_CSN)
#define csnhigh() SPI_PORT |= _BV(SPI_CSN)
#define celow() SPI_PORT &= ~_BV(SPI_CE)
#define cehigh() SPI_PORT |= _BV(SPI_CE)
static void SPIinit() {
// set pin mode: MOSI,SCLK,CE = OUTPUT, MISO = INPUT
SPI_DDR = _BV(SPI_MOSI) | _BV(SPI_SCLK) | _BV(SPI_CE) | _BV(SPI_CSN) | ~_BV(SPI_MISO);
}
static uint8_t SPItransfer(uint8_t value) {
// bit bang SPI
for(uint8_t i = 0; i<8; i++) {
if (value & 0x80) SPI_PORT |= _BV(SPI_MOSI); else SPI_PORT &= ~_BV(SPI_MOSI);
//_delay_us(1);
value <<= 1;
SPI_PORT |= _BV(SPI_SCLK);
//_delay_us(1);
value |= ((SPI_PIN >> SPI_MISO) & 0x01);
SPI_PORT &= ~_BV(SPI_SCLK);
//_delay_us(1);
}
return value;
}
// UART debug
static void uart_init() {
//Double speed mode USART0
UART_SRA = _BV(U2X0);
UART_SRB = _BV(RXEN0) | _BV(TXEN0);
UART_SRC = _BV(UCSZ00) | _BV(UCSZ01);
UART_SRL = (uint8_t)( (F_CPU + BAUD_RATE * 4L) / (BAUD_RATE * 8L) - 1 );
}
void putch(char ch) {
while (!(UART_SRA & _BV(UDRE0)));
UART_UDR = ch;
}
uint8_t getch() {
uint8_t ch;
// wait until char received
while(!(UART_SRA & _BV(RXC0)));
// framing error?
if (!(UART_SRA & _BV(FE0))) {
watchdogReset();
}
ch = UART_UDR;
return ch;
}
static void put_string(char *s) {
while (*s)
putch(*s++);
}
static void put_int(uint8_t i) {
char s[5];
itoa( i, s, 10 );
put_string( s );
}
#endif // MYSBootloaderHW_H
-208
View File
@@ -1,208 +0,0 @@
#ifndef MYSBootloaderRF24_H
#define MYSBootloaderRF24_H
#include "MYSBootloaderHW.h"
#include "nRF24L01.h"
typedef enum { RF24_PA_MIN = 0,RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_PA_ERROR } rf24_pa_dbm_e ;
typedef enum { RF24_1MBPS = 0, RF24_2MBPS, RF24_250KBPS } rf24_datarate_e;
#define _write_register(reg) ( (reg) | W_REGISTER)
#define _read_register(reg) ( (reg) | R_REGISTER)
#define addr_width 5
uint64_t pipe0_reading_address = 0;
static uint8_t BurstReadAddress(uint8_t addr, uint8_t* buf, uint8_t len) {
csnlow();
uint8_t status = SPItransfer( addr );
while ( len-- ) {
status = SPItransfer(NOP);
*buf++ = status;
}
csnhigh();
return status;
}
static uint8_t BurstWriteAddress(uint8_t addr, uint8_t* buf, uint8_t len) {
csnlow();
uint8_t status = SPItransfer( addr );
while ( len-- ) {
status = SPItransfer(*buf++);
}
csnhigh();
return status;
}
static uint8_t ReadAddress(uint8_t addr) {
return BurstReadAddress(addr,NULL,1);
}
static uint8_t WriteAddress(uint8_t addr, uint8_t val) {
return BurstWriteAddress(addr,&val,1);
}
#define BurstWriteRegister(reg,buf,len) BurstWriteAddress(_write_register(reg), buf, len)
#define WriteRegister(reg,val) WriteAddress(_write_register(reg), val)
#define BurstReadRegister(reg,buf,len) BurstReadAddress(_read_register(reg), buf, len)
#define ReadRegister(reg) ReadAddress(_read_register(reg))
#define get_status() BurstReadAddress(NOP, NULL, 0)
void Flush_RX(void) {
BurstWriteAddress(FLUSH_RX, NULL, 0);
}
void Flush_TX(void) {
BurstWriteAddress(FLUSH_TX, NULL, 0);
}
static void Flush_RXTX_CLI(void) {
// flush RX and TX buffer
Flush_RX();
Flush_TX();
// clear registers
WriteRegister(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
}
static void startListening(void) {
// for size reasons, we do not read back the register content, set PRIM_RX
WriteRegister(CONFIG, _BV(PWR_UP) | _BV(CRCO) | _BV(EN_CRC) | _BV(PRIM_RX)) ;
Flush_RXTX_CLI();
// write pipe0 address
if (pipe0_reading_address){
BurstWriteRegister(RX_ADDR_P0, (uint8_t*)&pipe0_reading_address, addr_width);
}
cehigh();
}
static void stopListening(void) {
celow();
Flush_RXTX_CLI();
// for size reasons, we do not read back the register content, clear PRIM_RX
WriteRegister(CONFIG, _BV(PWR_UP) | _BV(CRCO) | _BV(EN_CRC) | ~_BV(PRIM_RX)) ;
}
static boolean write_buf(uint8_t* buf, uint8_t len, const bool multicast ) {
// write payload to FIFO
BurstWriteRegister( multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD, buf, len) ;
// CE pulse to start transmission
cehigh();
// IMPORTANT: minimum CE pulse width 10us, see nRF24L01 specs
_delay_us(CE_PULSE_LENGTH);
// start transmitting
celow();
// wait until sent or ACKed, here we potentially have a deadlock when transmitter not connected/not working => wdt recovers
while( ! ( get_status() & ( _BV(TX_DS) | _BV(MAX_RT) ))) { }
// read interrupts and clear
uint8_t status = WriteRegister(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
// max retries? clear FIFO
if( status & _BV(MAX_RT)){
Flush_TX();
return false;
}
return true;
}
static uint8_t getDynamicPayloadSize(void) {
uint8_t result = ReadAddress(R_RX_PL_WID);
if(result > 32) {
Flush_RX();
result = 0;
}
return result;
}
static boolean available(uint8_t* pipe_num) {
if (!(ReadAddress(FIFO_STATUS) & _BV(RX_EMPTY) )){
if (pipe_num) {
uint8_t status = get_status();
*pipe_num = ( status >> RX_P_NO ) & 0b111;
}
return true;
}
return false;
}
static uint8_t readMessage(uint8_t* buf) {
// read payload
uint8_t PL_LEN = getDynamicPayloadSize();
BurstReadRegister(R_RX_PAYLOAD,buf,PL_LEN);
// reset interrupts
WriteRegister(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
return PL_LEN;
}
static void openWritingPipe(uint64_t value) {
BurstWriteRegister(RX_ADDR_P0, (uint8_t*)&value, addr_width);
BurstWriteRegister(TX_ADDR, (uint8_t*)&value, addr_width);
}
static void openReadingPipe(uint8_t pipe, uint64_t address) {
// If this is pipe 0, cache the address. This is needed because openWritingPipe() will overwrite the pipe 0 address, so
// startListening() will have to restore it
if (pipe == WRITE_PIPE) {
memcpy(&pipe0_reading_address,&address,addr_width);
}
// only write full address for pipe 0 and 1
// since we do not use the BROADCASTING PIPE in this bootloader, we can remove the check
//if ( pipe < 2 )
{
BurstWriteRegister(RX_ADDR_P0 + pipe, (uint8_t*)&address, addr_width);
}
}
static boolean write(uint8_t destination, uint8_t* buf, uint8_t len, boolean multicast) {
stopListening();
openWritingPipe(TO_ADDR(destination));
boolean result = write_buf(buf, len, multicast);
startListening();
return result;
}
static void setAddress(uint8_t addr)
{
if (addr != BROADCAST_ADDRESS) {
openReadingPipe(WRITE_PIPE, TO_ADDR(addr));
openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(addr));
// enable pipe
WriteRegister(EN_RXADDR,_BV(ERX_P0 + WRITE_PIPE) | _BV(ERX_P0 + CURRENT_NODE_PIPE));
}
}
static void RFinit(void){
// set address width
WriteRegister(SETUP_AW, (addr_width-2) % 4);
// auto retransmit delay 1500us, auto retransmit count 15
WriteRegister(SETUP_RETR, 5 << ARD | 15 << ARC);
// enable auto ack on all pipes, except broadcasting pipe
WriteRegister(EN_AA, 0b00111111 & ~_BV(BROADCAST_PIPE)) ;
// set channel
WriteRegister(RF_CH, RF24_CHANNEL);
// set data rate
WriteRegister(RF_SETUP, ((RF24_DATARATE & 0b00000010 ) << 4) | ((RF24_DATARATE & 0b00000001 ) << 3) | ((RF24_PA_LEVEL << 1) + 1));
// flush RX and TX FIFO, clear interrupts
Flush_RXTX_CLI();
// activate to unlock features
WriteAddress(ACTIVATE,0x73);
// enable payload with ACK and dynamic payload length
WriteRegister(FEATURE, _BV(EN_ACK_PAY) | _BV(EN_DPL) );
// Enable dynamic payload length on all pipes
WriteRegister(DYNPD, _BV(DPL_P5) | _BV(DPL_P4) | _BV(DPL_P3) | _BV(DPL_P2) | _BV(DPL_P1) | _BV(DPL_P0));
//powerUp, enable 16bit CRC, no RX mode
WriteRegister(CONFIG, _BV(PWR_UP) | _BV(CRCO) | _BV(EN_CRC) | ~_BV(PRIM_RX));
// stabilize 5ms
_delay_ms(5);
}
#endif // MYSBootloaderRF24_H
-65
View File
@@ -1,65 +0,0 @@
PROJECT = MYSBootloader
INCLUDES = ../libraries/MySensors
MCU = atmega328p
CLK = 16000000L
ISP_PORT = com5
ISP_SPEED = 115200
ISP_PROTOCOL = stk500v2
ISP_MCU = m328p
ISP_HFUSE = DA
ISP_LFUSE = F7
ISP_EFUSE = 06
ISP_ARGS = -c$(ISP_PROTOCOL) -P$(ISP_PORT) -b$(ISP_SPEED) -p$(ISP_MCU)
ifeq ($(OS),Windows_NT)
BINPATH = C:/Program Files (x86)/Arduino/hardware/tools/avr/bin/
JAVAPATH = C:/Program Files/Java/jdk1.7.0_40/bin/
JAVAC = $(JAVAPATH)javac.exe
JAVA = $(JAVAPATH)java.exe
else
UNAME_S := $(shell uname -s)
UNAME_P := $(shell uname -p)
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_S),Linux)
ifeq ($(UNAME_P),x86_64)
BINPATH = ../arduino/bin-linux64/bin/
else ifneq ($(filter %86,$(UNAME_P)),)
BINPATH = ../arduino/bin-linux32/bin/
else ifneq ($(filter arm%,$(UNAME_M)),)
BINPATH = /usr/bin/
endif
endif
endif
CFLAGS = -x c -mno-interrupts -funsigned-char -funsigned-bitfields -DF_CPU=$(CLK)-Os -fno-inline-small-functions -fno-split-wide-types -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -mrelax -Wall -mmcu=$(MCU) -c -std=gnu99 -MD -MP -MF "$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -MT"$(@:%.o=%.o)"
LDFLAGS = -nostartfiles -Wl,-s -Wl,-static -Wl,-Map="$(OutputFileName).map" -Wl,--start-group -Wl,--end-group -Wl,--gc-sections -mrelax -Wl,-section-start=.text=0x7800 -mmcu=$(MCU)
all: clean out
clean:
- rm *.o
- rm *.elf
- rm *.hex
$(PROJECT).o: $(PROJECT).c
"$(BINPATH)avr-gcc" $(CFLAGS) -I$(INCLUDES) $< -o $@
$(PROJECT).elf: $(PROJECT).o
"$(BINPATH)avr-gcc" $(LDFLAGS) -o $@ $< -lm
$(PROJECT).hex: $(PROJECT).elf
"$(BINPATH)avr-objcopy" -O ihex -R .eeprom $< $@
out: $(PROJECT).hex
"$(BINPATH)avr-size" $(PROJECT).elf
load: clean out isp
isp: $(PROJECT).hex
"$(BINPATH)avrdude" $(ISP_ARGS) -e -u -Ulock:w:0x3f:m -qq -Uefuse:w:0x$(ISP_EFUSE):m -Uhfuse:w:0x$(ISP_HFUSE):m -Ulfuse:w:0x$(ISP_LFUSE):m -Ulock:w:0x0f:m
"$(BINPATH)avrdude" $(ISP_ARGS) -V -q -s -Uflash:w:$(PROJECT).hex
"$(BINPATH)avrdude" $(ISP_ARGS)
-62
View File
@@ -1,62 +0,0 @@
# See: http://code.google.com/p/arduino/wiki/Platforms
menu.cpu=Processor
uno.name=Arduino Uno Optiboot
uno.vid.0=0x2341
uno.pid.0=0x0043
uno.vid.1=0x2341
uno.pid.1=0x0001
uno.upload.tool=avrdude
uno.upload.protocol=arduino
uno.upload.maximum_size=32256
uno.upload.maximum_data_size=2048
uno.upload.speed=115200
uno.bootloader.tool=avrdude
uno.bootloader.low_fuses=0xFF
uno.bootloader.high_fuses=0xDE
uno.bootloader.extended_fuses=0x05
uno.bootloader.unlock_bits=0x3F
uno.bootloader.lock_bits=0x0F
uno.bootloader.file=optiboot/optiboot_atmega328.hex
uno.build.mcu=atmega328p
uno.build.f_cpu=16000000L
uno.build.board=AVR_UNO
uno.build.core=arduino
uno.build.variant=standard
## Arduino Pro or Pro Mini (3V3 & 5V, 16 MHz) w/ ATmega328 MYSBootloader
## -------------------------------------------------
proMYSBL.name=ATmega328 16Mhz MYSBootloader
proMYSBL.upload.tool=avrdude
proMYSBL.upload.protocol=arduino
proMYSBL.upload.maximum_size=30720
proMYSBL.upload.maximum_data_size=2048
proMYSBL.upload.speed=115200
proMYSBL.bootloader.tool=avrdude
proMYSBL.bootloader.low_fuses=0xF7
proMYSBL.bootloader.high_fuses=0xDA
proMYSBL.bootloader.extended_fuses=0x06
proMYSBL.bootloader.unlock_bits=0x3F
proMYSBL.bootloader.lock_bits=0x0F
proMYSBL.bootloader.file=MySensors/MYSBootloader.hex
proMYSBL.build.mcu=atmega328p
proMYSBL.build.f_cpu=16000000L
proMYSBL.build.board=AVR_UNO
proMYSBL.build.core=arduino
proMYSBL.build.variant=standard
-848
View File
@@ -1,848 +0,0 @@
/* Modified to use out for SPM access
** Peter Knight, Optiboot project http://optiboot.googlecode.com
**
** Todo: Tidy up
**
** "_short" routines execute 1 cycle faster and use 1 less word of flash
** by using "out" instruction instead of "sts".
**
** Additional elpm variants that trust the value of RAMPZ
*/
/* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007 Eric B. Weddington
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. */
/* $Id: boot.h,v 1.27.2.3 2008/09/30 13:58:48 arcanum Exp $ */
#ifndef _AVR_BOOT_H_
#define _AVR_BOOT_H_ 1
/** \file */
/** \defgroup avr_boot <avr/boot.h>: Bootloader Support Utilities
\code
#include <avr/io.h>
#include <avr/boot.h>
\endcode
The macros in this module provide a C language interface to the
bootloader support functionality of certain AVR processors. These
macros are designed to work with all sizes of flash memory.
Global interrupts are not automatically disabled for these macros. It
is left up to the programmer to do this. See the code example below.
Also see the processor datasheet for caveats on having global interrupts
enabled during writing of the Flash.
\note Not all AVR processors provide bootloader support. See your
processor datasheet to see if it provides bootloader support.
\todo From email with Marek: On smaller devices (all except ATmega64/128),
__SPM_REG is in the I/O space, accessible with the shorter "in" and "out"
instructions - since the boot loader has a limited size, this could be an
important optimization.
\par API Usage Example
The following code shows typical usage of the boot API.
\code
#include <inttypes.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
void boot_program_page (uint32_t page, uint8_t *buf)
{
uint16_t i;
uint8_t sreg;
// Disable interrupts.
sreg = SREG;
cli();
eeprom_busy_wait ();
boot_page_erase (page);
boot_spm_busy_wait (); // Wait until the memory is erased.
for (i=0; i<SPM_PAGESIZE; i+=2)
{
// Set up little-endian word.
uint16_t w = *buf++;
w += (*buf++) << 8;
boot_page_fill (page + i, w);
}
boot_page_write (page); // Store buffer in flash page.
boot_spm_busy_wait(); // Wait until the memory is written.
// Reenable RWW-section again. We need this if we want to jump back
// to the application after bootloading.
boot_rww_enable ();
// Re-enable interrupts (if they were ever enabled).
SREG = sreg;
}\endcode */
#include <avr/eeprom.h>
#include <avr/io.h>
#include <inttypes.h>
#include <limits.h>
/* Check for SPM Control Register in processor. */
#if defined (SPMCSR)
# define __SPM_REG SPMCSR
#elif defined (SPMCR)
# define __SPM_REG SPMCR
#else
# error AVR processor does not provide bootloader support!
#endif
/* Check for SPM Enable bit. */
#if defined(SPMEN)
# define __SPM_ENABLE SPMEN
#elif defined(SELFPRGEN)
# define __SPM_ENABLE SELFPRGEN
#else
# error Cannot find SPM Enable bit definition!
#endif
/** \ingroup avr_boot
\def BOOTLOADER_SECTION
Used to declare a function or variable to be placed into a
new section called .bootloader. This section and its contents
can then be relocated to any address (such as the bootloader
NRWW area) at link-time. */
#define BOOTLOADER_SECTION __attribute__ ((section (".bootloader")))
/* Create common bit definitions. */
#ifdef ASB
#define __COMMON_ASB ASB
#else
#define __COMMON_ASB RWWSB
#endif
#ifdef ASRE
#define __COMMON_ASRE ASRE
#else
#define __COMMON_ASRE RWWSRE
#endif
/* Define the bit positions of the Boot Lock Bits. */
#define BLB12 5
#define BLB11 4
#define BLB02 3
#define BLB01 2
/** \ingroup avr_boot
\def boot_spm_interrupt_enable()
Enable the SPM interrupt. */
#define boot_spm_interrupt_enable() (__SPM_REG |= (uint8_t)_BV(SPMIE))
/** \ingroup avr_boot
\def boot_spm_interrupt_disable()
Disable the SPM interrupt. */
#define boot_spm_interrupt_disable() (__SPM_REG &= (uint8_t)~_BV(SPMIE))
/** \ingroup avr_boot
\def boot_is_spm_interrupt()
Check if the SPM interrupt is enabled. */
#define boot_is_spm_interrupt() (__SPM_REG & (uint8_t)_BV(SPMIE))
/** \ingroup avr_boot
\def boot_rww_busy()
Check if the RWW section is busy. */
#define boot_rww_busy() (__SPM_REG & (uint8_t)_BV(__COMMON_ASB))
/** \ingroup avr_boot
\def boot_spm_busy()
Check if the SPM instruction is busy. */
#define boot_spm_busy() (__SPM_REG & (uint8_t)_BV(__SPM_ENABLE))
/** \ingroup avr_boot
\def boot_spm_busy_wait()
Wait while the SPM instruction is busy. */
#define boot_spm_busy_wait() do{}while(boot_spm_busy())
#define __BOOT_PAGE_ERASE (_BV(__SPM_ENABLE) | _BV(PGERS))
#define __BOOT_PAGE_WRITE (_BV(__SPM_ENABLE) | _BV(PGWRT))
#define __BOOT_PAGE_FILL _BV(__SPM_ENABLE)
#define __BOOT_RWW_ENABLE (_BV(__SPM_ENABLE) | _BV(__COMMON_ASRE))
#define __BOOT_LOCK_BITS_SET (_BV(__SPM_ENABLE) | _BV(BLBSET))
#define __boot_page_fill_short(address, data) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r0, %3\n\t" \
"out %0, %1\n\t" \
"spm\n\t" \
"clr r1\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_FILL), \
"z" ((uint16_t)address), \
"r" ((uint16_t)data) \
: "r0" \
); \
}))
#define __boot_page_fill_normal(address, data) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r0, %3\n\t" \
"sts %0, %1\n\t" \
"spm\n\t" \
"clr r1\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_FILL), \
"z" ((uint16_t)address), \
"r" ((uint16_t)data) \
: "r0" \
); \
}))
#define __boot_page_fill_alternate(address, data)\
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r0, %3\n\t" \
"sts %0, %1\n\t" \
"spm\n\t" \
".word 0xffff\n\t" \
"nop\n\t" \
"clr r1\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_FILL), \
"z" ((uint16_t)address), \
"r" ((uint16_t)data) \
: "r0" \
); \
}))
#define __boot_page_fill_extended(address, data) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r0, %4\n\t" \
"movw r30, %A3\n\t" \
"sts %1, %C3\n\t" \
"sts %0, %2\n\t" \
"spm\n\t" \
"clr r1\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"i" (_SFR_MEM_ADDR(RAMPZ)), \
"r" ((uint8_t)__BOOT_PAGE_FILL), \
"r" ((uint32_t)address), \
"r" ((uint16_t)data) \
: "r0", "r30", "r31" \
); \
}))
#define __boot_page_fill_extended_short(address, data) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r0, %4\n\t" \
"movw r30, %A3\n\t" \
"out %1, %C3\n\t" \
"out %0, %2\n\t" \
"spm\n\t" \
"clr r1\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"i" (_SFR_IO_ADDR(RAMPZ)), \
"r" ((uint8_t)__BOOT_PAGE_FILL), \
"r" ((uint32_t)address), \
"r" ((uint16_t)data) \
: "r0", "r30", "r31" \
); \
}))
#define __boot_page_erase_short(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"out %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_ERASE), \
"z" ((uint16_t)address) \
); \
}))
#define __boot_page_erase_normal(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"sts %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_ERASE), \
"z" ((uint16_t)address) \
); \
}))
#define __boot_page_erase_alternate(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"sts %0, %1\n\t" \
"spm\n\t" \
".word 0xffff\n\t" \
"nop\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_ERASE), \
"z" ((uint16_t)address) \
); \
}))
#define __boot_page_erase_extended(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r30, %A3\n\t" \
"sts %1, %C3\n\t" \
"sts %0, %2\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"i" (_SFR_MEM_ADDR(RAMPZ)), \
"r" ((uint8_t)__BOOT_PAGE_ERASE), \
"r" ((uint32_t)address) \
: "r30", "r31" \
); \
}))
#define __boot_page_erase_extended_short(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r30, %A3\n\t" \
"out %1, %C3\n\t" \
"out %0, %2\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"i" (_SFR_IO_ADDR(RAMPZ)), \
"r" ((uint8_t)__BOOT_PAGE_ERASE), \
"r" ((uint32_t)address) \
: "r30", "r31" \
); \
}))
#define __boot_page_write_short(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"out %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_WRITE), \
"z" ((uint16_t)address) \
); \
}))
#define __boot_page_write_normal(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"sts %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_WRITE), \
"z" ((uint16_t)address) \
); \
}))
#define __boot_page_write_alternate(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"sts %0, %1\n\t" \
"spm\n\t" \
".word 0xffff\n\t" \
"nop\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_PAGE_WRITE), \
"z" ((uint16_t)address) \
); \
}))
#define __boot_page_write_extended(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r30, %A3\n\t" \
"sts %1, %C3\n\t" \
"sts %0, %2\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"i" (_SFR_MEM_ADDR(RAMPZ)), \
"r" ((uint8_t)__BOOT_PAGE_WRITE), \
"r" ((uint32_t)address) \
: "r30", "r31" \
); \
}))
#define __boot_page_write_extended_short(address) \
(__extension__({ \
__asm__ __volatile__ \
( \
"movw r30, %A3\n\t" \
"out %1, %C3\n\t" \
"out %0, %2\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"i" (_SFR_IO_ADDR(RAMPZ)), \
"r" ((uint8_t)__BOOT_PAGE_WRITE), \
"r" ((uint32_t)address) \
: "r30", "r31" \
); \
}))
#define __boot_rww_enable_short() \
(__extension__({ \
__asm__ __volatile__ \
( \
"out %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_RWW_ENABLE) \
); \
}))
#define __boot_rww_enable() \
(__extension__({ \
__asm__ __volatile__ \
( \
"sts %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_RWW_ENABLE) \
); \
}))
#define __boot_rww_enable_alternate() \
(__extension__({ \
__asm__ __volatile__ \
( \
"sts %0, %1\n\t" \
"spm\n\t" \
".word 0xffff\n\t" \
"nop\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_RWW_ENABLE) \
); \
}))
/* From the mega16/mega128 data sheets (maybe others):
Bits by SPM To set the Boot Loader Lock bits, write the desired data to
R0, write "X0001001" to SPMCR and execute SPM within four clock cycles
after writing SPMCR. The only accessible Lock bits are the Boot Lock bits
that may prevent the Application and Boot Loader section from any
software update by the MCU.
If bits 5..2 in R0 are cleared (zero), the corresponding Boot Lock bit
will be programmed if an SPM instruction is executed within four cycles
after BLBSET and SPMEN (or SELFPRGEN) are set in SPMCR. The Z-pointer is
don't care during this operation, but for future compatibility it is
recommended to load the Z-pointer with $0001 (same as used for reading the
Lock bits). For future compatibility It is also recommended to set bits 7,
6, 1, and 0 in R0 to 1 when writing the Lock bits. When programming the
Lock bits the entire Flash can be read during the operation. */
#define __boot_lock_bits_set_short(lock_bits) \
(__extension__({ \
uint8_t value = (uint8_t)(~(lock_bits)); \
__asm__ __volatile__ \
( \
"ldi r30, 1\n\t" \
"ldi r31, 0\n\t" \
"mov r0, %2\n\t" \
"out %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_LOCK_BITS_SET), \
"r" (value) \
: "r0", "r30", "r31" \
); \
}))
#define __boot_lock_bits_set(lock_bits) \
(__extension__({ \
uint8_t value = (uint8_t)(~(lock_bits)); \
__asm__ __volatile__ \
( \
"ldi r30, 1\n\t" \
"ldi r31, 0\n\t" \
"mov r0, %2\n\t" \
"sts %0, %1\n\t" \
"spm\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_LOCK_BITS_SET), \
"r" (value) \
: "r0", "r30", "r31" \
); \
}))
#define __boot_lock_bits_set_alternate(lock_bits) \
(__extension__({ \
uint8_t value = (uint8_t)(~(lock_bits)); \
__asm__ __volatile__ \
( \
"ldi r30, 1\n\t" \
"ldi r31, 0\n\t" \
"mov r0, %2\n\t" \
"sts %0, %1\n\t" \
"spm\n\t" \
".word 0xffff\n\t" \
"nop\n\t" \
: \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_LOCK_BITS_SET), \
"r" (value) \
: "r0", "r30", "r31" \
); \
}))
/*
Reading lock and fuse bits:
Similarly to writing the lock bits above, set BLBSET and SPMEN (or
SELFPRGEN) bits in __SPMREG, and then (within four clock cycles) issue an
LPM instruction.
Z address: contents:
0x0000 low fuse bits
0x0001 lock bits
0x0002 extended fuse bits
0x0003 high fuse bits
Sounds confusing, doesn't it?
Unlike the macros in pgmspace.h, no need to care for non-enhanced
cores here as these old cores do not provide SPM support anyway.
*/
/** \ingroup avr_boot
\def GET_LOW_FUSE_BITS
address to read the low fuse bits, using boot_lock_fuse_bits_get
*/
#define GET_LOW_FUSE_BITS (0x0000)
/** \ingroup avr_boot
\def GET_LOCK_BITS
address to read the lock bits, using boot_lock_fuse_bits_get
*/
#define GET_LOCK_BITS (0x0001)
/** \ingroup avr_boot
\def GET_EXTENDED_FUSE_BITS
address to read the extended fuse bits, using boot_lock_fuse_bits_get
*/
#define GET_EXTENDED_FUSE_BITS (0x0002)
/** \ingroup avr_boot
\def GET_HIGH_FUSE_BITS
address to read the high fuse bits, using boot_lock_fuse_bits_get
*/
#define GET_HIGH_FUSE_BITS (0x0003)
/** \ingroup avr_boot
\def boot_lock_fuse_bits_get(address)
Read the lock or fuse bits at \c address.
Parameter \c address can be any of GET_LOW_FUSE_BITS,
GET_LOCK_BITS, GET_EXTENDED_FUSE_BITS, or GET_HIGH_FUSE_BITS.
\note The lock and fuse bits returned are the physical values,
i.e. a bit returned as 0 means the corresponding fuse or lock bit
is programmed.
*/
#define boot_lock_fuse_bits_get_short(address) \
(__extension__({ \
uint8_t __result; \
__asm__ __volatile__ \
( \
"ldi r30, %3\n\t" \
"ldi r31, 0\n\t" \
"out %1, %2\n\t" \
"lpm %0, Z\n\t" \
: "=r" (__result) \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_LOCK_BITS_SET), \
"M" (address) \
: "r0", "r30", "r31" \
); \
__result; \
}))
#define boot_lock_fuse_bits_get(address) \
(__extension__({ \
uint8_t __result; \
__asm__ __volatile__ \
( \
"ldi r30, %3\n\t" \
"ldi r31, 0\n\t" \
"sts %1, %2\n\t" \
"lpm %0, Z\n\t" \
: "=r" (__result) \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t)__BOOT_LOCK_BITS_SET), \
"M" (address) \
: "r0", "r30", "r31" \
); \
__result; \
}))
/** \ingroup avr_boot
\def boot_signature_byte_get(address)
Read the Signature Row byte at \c address. For some MCU types,
this function can also retrieve the factory-stored oscillator
calibration bytes.
Parameter \c address can be 0-0x1f as documented by the datasheet.
\note The values are MCU type dependent.
*/
#define __BOOT_SIGROW_READ (_BV(__SPM_ENABLE) | _BV(SIGRD))
#define boot_signature_byte_get_short(addr) \
(__extension__({ \
uint16_t __addr16 = (uint16_t)(addr); \
uint8_t __result; \
__asm__ __volatile__ \
( \
"out %1, %2\n\t" \
"lpm %0, Z" "\n\t" \
: "=r" (__result) \
: "i" (_SFR_IO_ADDR(__SPM_REG)), \
"r" ((uint8_t) __BOOT_SIGROW_READ), \
"z" (__addr16) \
); \
__result; \
}))
#define boot_signature_byte_get(addr) \
(__extension__({ \
uint16_t __addr16 = (uint16_t)(addr); \
uint8_t __result; \
__asm__ __volatile__ \
( \
"sts %1, %2\n\t" \
"lpm %0, Z" "\n\t" \
: "=r" (__result) \
: "i" (_SFR_MEM_ADDR(__SPM_REG)), \
"r" ((uint8_t) __BOOT_SIGROW_READ), \
"z" (__addr16) \
); \
__result; \
}))
/** \ingroup avr_boot
\def boot_page_fill(address, data)
Fill the bootloader temporary page buffer for flash
address with data word.
\note The address is a byte address. The data is a word. The AVR
writes data to the buffer a word at a time, but addresses the buffer
per byte! So, increment your address by 2 between calls, and send 2
data bytes in a word format! The LSB of the data is written to the lower
address; the MSB of the data is written to the higher address.*/
/** \ingroup avr_boot
\def boot_page_erase(address)
Erase the flash page that contains address.
\note address is a byte address in flash, not a word address. */
/** \ingroup avr_boot
\def boot_page_write(address)
Write the bootloader temporary page buffer
to flash page that contains address.
\note address is a byte address in flash, not a word address. */
/** \ingroup avr_boot
\def boot_rww_enable()
Enable the Read-While-Write memory section. */
/** \ingroup avr_boot
\def boot_lock_bits_set(lock_bits)
Set the bootloader lock bits.
\param lock_bits A mask of which Boot Loader Lock Bits to set.
\note In this context, a 'set bit' will be written to a zero value.
Note also that only BLBxx bits can be programmed by this command.
For example, to disallow the SPM instruction from writing to the Boot
Loader memory section of flash, you would use this macro as such:
\code
boot_lock_bits_set (_BV (BLB11));
\endcode
\note Like any lock bits, the Boot Loader Lock Bits, once set,
cannot be cleared again except by a chip erase which will in turn
also erase the boot loader itself. */
/* Normal versions of the macros use 16-bit addresses.
Extended versions of the macros use 32-bit addresses.
Alternate versions of the macros use 16-bit addresses and require special
instruction sequences after LPM.
FLASHEND is defined in the ioXXXX.h file.
USHRT_MAX is defined in <limits.h>. */
#if defined(__AVR_ATmega161__) || defined(__AVR_ATmega163__) \
|| defined(__AVR_ATmega323__)
/* Alternate: ATmega161/163/323 and 16 bit address */
#define boot_page_fill(address, data) __boot_page_fill_alternate(address, data)
#define boot_page_erase(address) __boot_page_erase_alternate(address)
#define boot_page_write(address) __boot_page_write_alternate(address)
#define boot_rww_enable() __boot_rww_enable_alternate()
#define boot_lock_bits_set(lock_bits) __boot_lock_bits_set_alternate(lock_bits)
#elif (FLASHEND > USHRT_MAX)
/* Extended: >16 bit address */
#define boot_page_fill(address, data) __boot_page_fill_extended_short(address, data)
#define boot_page_erase(address) __boot_page_erase_extended_short(address)
#define boot_page_write(address) __boot_page_write_extended_short(address)
#define boot_rww_enable() __boot_rww_enable_short()
#define boot_lock_bits_set(lock_bits) __boot_lock_bits_set_short(lock_bits)
#else
/* Normal: 16 bit address */
#define boot_page_fill(address, data) __boot_page_fill_short(address, data)
#define boot_page_erase(address) __boot_page_erase_short(address)
#define boot_page_write(address) __boot_page_write_short(address)
#define boot_rww_enable() __boot_rww_enable_short()
#define boot_lock_bits_set(lock_bits) __boot_lock_bits_set_short(lock_bits)
#endif
/** \ingroup avr_boot
Same as boot_page_fill() except it waits for eeprom and spm operations to
complete before filling the page. */
#define boot_page_fill_safe(address, data) \
do { \
boot_spm_busy_wait(); \
eeprom_busy_wait(); \
boot_page_fill(address, data); \
} while (0)
/** \ingroup avr_boot
Same as boot_page_erase() except it waits for eeprom and spm operations to
complete before erasing the page. */
#define boot_page_erase_safe(address) \
do { \
boot_spm_busy_wait(); \
eeprom_busy_wait(); \
boot_page_erase (address); \
} while (0)
/** \ingroup avr_boot
Same as boot_page_write() except it waits for eeprom and spm operations to
complete before writing the page. */
#define boot_page_write_safe(address) \
do { \
boot_spm_busy_wait(); \
eeprom_busy_wait(); \
boot_page_write (address); \
} while (0)
/** \ingroup avr_boot
Same as boot_rww_enable() except waits for eeprom and spm operations to
complete before enabling the RWW mameory. */
#define boot_rww_enable_safe() \
do { \
boot_spm_busy_wait(); \
eeprom_busy_wait(); \
boot_rww_enable(); \
} while (0)
/** \ingroup avr_boot
Same as boot_lock_bits_set() except waits for eeprom and spm operations to
complete before setting the lock bits. */
#define boot_lock_bits_set_safe(lock_bits) \
do { \
boot_spm_busy_wait(); \
eeprom_busy_wait(); \
boot_lock_bits_set (lock_bits); \
} while (0)
#endif /* _AVR_BOOT_H_ */
+100
View File
@@ -0,0 +1,100 @@
#############################################################################
#
# Makefile for MySensors
#
# Description:
# ------------
# use make all and make install to install the gateway
#
CONFIG_FILE=Makefile.inc
include $(CONFIG_FILE)
GATEWAY_BIN=mysGateway
GATEWAY=examples_linux/$(GATEWAY_BIN)
GATEWAY_C_SOURCES=$(wildcard drivers/Linux/*.c)
GATEWAY_CPP_SOURCES=$(wildcard drivers/Linux/*.cpp) examples_linux/mysGateway.cpp
OBJECTS=$(patsubst %.c,%.o,$(GATEWAY_C_SOURCES)) $(patsubst %.cpp,%.o,$(GATEWAY_CPP_SOURCES))
DEPS+=$(patsubst %.c,%.d,$(GATEWAY_C_SOURCES)) $(patsubst %.cpp,%.d,$(GATEWAY_CPP_SOURCES))
CINCLUDE=-I. -I./core -I./drivers/Linux
ifeq ($(SOC),$(filter $(SOC),BCM2835 BCM2836))
RPI_C_SOURCES=$(wildcard drivers/RPi/*.c)
RPI_CPP_SOURCES=$(wildcard drivers/RPi/*.cpp)
OBJECTS+=$(patsubst %.c,%.o,$(RPI_C_SOURCES)) $(patsubst %.cpp,%.o,$(RPI_CPP_SOURCES))
DEPS+=$(patsubst %.c,%.d,$(RPI_C_SOURCES)) $(patsubst %.cpp,%.d,$(RPI_CPP_SOURCES))
CINCLUDE+=-I./drivers/RPi
endif
.PHONY: all gateway cleanconfig clean install uninstall force
all: $(GATEWAY)
# Gateway Build
$(GATEWAY): $(OBJECTS)
$(CXX) $(LDFLAGS) -o $@ $(OBJECTS)
# Include all .d files
-include $(DEPS)
%.o: %.cpp
$(CXX) $(CXXFLAGS) $(CINCLUDE) -MMD -c -o $@ $<
%.o: %.c
$(CC) $(CFLAGS) $(CINCLUDE) -MMD -c -o $@ $<
# clear configuration files
cleanconfig:
@echo "[Cleaning configuration]"
rm -rf $(CONFIG_FILE)
# clear build files
clean:
@echo "[Cleaning]"
rm -rf build $(OBJECTS) $(GATEWAY) $(DEPS)
$(CONFIG_FILE):
@echo "[Running configure]"
@./configure --no-clean
install: all install-gateway install-initscripts
install-gateway:
@echo "Installing $(GATEWAY) to $(GATEWAY_DIR)"
@install -m 0755 $(GATEWAY) $(GATEWAY_DIR)
install-initscripts:
ifeq ($(INIT_SYSTEM), systemd)
install -m0644 initscripts/mysgateway.systemd /etc/systemd/system/mysgateway.service
@sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" /etc/systemd/system/mysgateway.service
systemctl daemon-reload
@echo "MySensors gateway has been installed, to add to the boot run:"
@echo " sudo systemctl enable mysgateway.service"
@echo "To start the gateway run:"
@echo " sudo systemctl start mysgateway.service"
else ifeq ($(INIT_SYSTEM), sysvinit)
install -m0755 initscripts/mysgateway.sysvinit /etc/init.d/mysgateway
@sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" /etc/init.d/mysgateway
@echo "MySensors gateway has been installed, to add to the boot run:"
@echo " sudo update-rc.d mysgateway defaults"
@echo "To start the gateway run:"
@echo " sudo service mysgateway start"
endif
uninstall:
ifeq ($(INIT_SYSTEM), systemd)
@echo "Stopping daemon mysgateway (ignore errors)"
-@systemctl stop mysgateway.service
@echo "removing files"
rm /etc/systemd/system/mysgateway.service $(GATEWAY_DIR)/$(GATEWAY_BIN)
else ifeq ($(INIT_SYSTEM), sysvinit)
@echo "Stopping daemon mysgateway (ignore errors)"
-@service mysgateway stop
@echo "removing files"
rm /etc/init.d/mysgateway $(GATEWAY_DIR)/$(GATEWAY_BIN)
endif
+845
View File
@@ -0,0 +1,845 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in RAM or EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyConfig.h
*
* MySensors specific configurations
*/
#ifndef MyConfig_h
#define MyConfig_h
#include <stdint.h>
/**********************************
* Serial and debug options
***********************************/
// Enable MY_DEBUG in sketch to show debug prints. This option will add a lot to the size of the
// final sketch but is helpful to see what is actually is happening during development
//#define MY_DEBUG
// Enable MY_SPECIAL_DEBUG in sketch to activate I_DEBUG messages if MY_DEBUG is disabled.
// I_DEBUG requests are:
// R: routing info (only repeaters): received msg XXYY (as stream), where XX is the node and YY the routing node
// V: CPU voltage
// F: CPU frequency
// M: free memory
// E: clear MySensors EEPROM area and reboot (i.e. "factory" reset)
//#define MY_SPECIAL_DEBUG
// Enable MY_DEBUG_VERBOSE_SIGNING flag for verbose debug prints related to signing.
// Requires DEBUG to be enabled.
// This will add even more to the size of the final sketch!
//#define MY_DEBUG_VERBOSE_SIGNING
// Enable this in sketch if you want to use TX(1), RX(0) as normal I/O pin
//#define MY_DISABLED_SERIAL
// Enable MY_CORE_ONLY flag if you want to use core functions without loading the framework
//#define MY_CORE_ONLY
// Turn off debug if serial pins is used for other stuff
#ifdef MY_DISABLED_SERIAL
#undef MY_DEBUG
#endif
/**
* @def MY_BAUD_RATE
* @brief Serial output baud rate (debug prints and serial gateway speed).
*/
#ifndef MY_BAUD_RATE
#define MY_BAUD_RATE 115200
#endif
// Disables over-the-air reset of node
//#define MY_DISABLE_REMOTE_RESET
/**********************************
* Radio selection and node config
***********************************/
// Selecting uplink transport layer is optional (for a gateway node).
//#define MY_RADIO_NRF24
//#define MY_RADIO_RFM69
//#define MY_RS485
/**
* @def MY_RAM_ROUTING_TABLE_FEATURE
* @brief If enabled, the routing table is kept in RAM (if memory allows) and saved in regular intervals.
* note: AVR has limited memory, use with care
*/
#define MY_RAM_ROUTING_TABLE_FEATURE
/**
* @def MY_ROUTING_TABLE_SAVE_INTERVAL_MS
* @brief Interval to dump content of routing table to eeprom
*/
#ifndef MY_ROUTING_TABLE_SAVE_INTERVAL_MS
#define MY_ROUTING_TABLE_SAVE_INTERVAL_MS (10*60*1000ul)
#endif
/**
* @def MY_TRANSPORT_SANITY_CHECK
* @brief If enabled, node will check transport in regular intervals to detect HW issues and re-initialize in case of failure.
* This feature is enabled for all repeater nodes (incl. GW)
*/
//#define MY_TRANSPORT_SANITY_CHECK
/**
* @def MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS
* @brief Interval (in ms) of transport sanity checks
*/
#ifndef MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS
#define MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS (60*1000ul)
#endif
/**
* @def MY_TRANSPORT_DISCOVERY_INTERVAL_MS
* @brief This is a gateway-only feature: Interval (in ms) to issue network discovery checks
*/
#ifndef MY_TRANSPORT_DISCOVERY_INTERVAL_MS
#define MY_TRANSPORT_DISCOVERY_INTERVAL_MS (10*60*1000ul)
#endif
/**
*@def MY_TRANSPORT_UPLINK_CHECK_DISABLED
*@brief If set, uplink check to GW is disabled during transport initialisation
*/
//#define MY_TRANSPORT_UPLINK_CHECK_DISABLED
/**
*@def MY_TRANSPORT_DONT_CARE_MODE
*@brief If set, transport traffic is unmonitored and GW connection is optional
*/
//#define MY_TRANSPORT_DONT_CARE_MODE
/**
*@def MY_TRANSPORT_MAX_TX_FAILURES
*@brief Set to override max. consecutive TX failures until SNP is initiated
*/
//#define MY_TRANSPORT_MAX_TX_FAILURES (10u)
/**
* @def MY_REGISTRATION_FEATURE
* @brief If enabled, node has to register to gateway/controller before allowed to send sensor data.
*/
#define MY_REGISTRATION_FEATURE
/**
* @def MY_REGISTRATION_RETRIES
* @brief Number of registration retries if no reply received from GW/controller
*/
#ifndef MY_REGISTRATION_RETRIES
#define MY_REGISTRATION_RETRIES (3u)
#endif
/**
* @def MY_REGISTRATION_DEFAULT
* @brief Node registration default - this applies if no registration response is received from controller
*/
#define MY_REGISTRATION_DEFAULT true
/**
* @def MY_REGISTRATION_CONTROLLER
* @brief If enabled, node registration request has to be handled by controller
*/
// #define MY_REGISTRATION_CONTROLLER
/**
* @def MY_CORE_COMPATIBILITY_CHECK
* @brief If enabled, library compatibility is checked during node registration. Incompatible libraries are unable to send sensor data.
*/
#define MY_CORE_COMPATIBILITY_CHECK
/**
* @def MY_NODE_ID
* @brief Node id defaults to AUTO (tries to fetch id from controller).
*/
#ifndef MY_NODE_ID
#define MY_NODE_ID AUTO
#endif
/**
* @def MY_PARENT_NODE_ID
* @brief Node parent defaults to AUTO (tries to find a parent automatically).
*/
#ifndef MY_PARENT_NODE_ID
#define MY_PARENT_NODE_ID AUTO
#endif
/**
* @def MY_PARENT_NODE_IS_STATIC
* @brief Enable MY_PARENT_NODE_IS_STATIC to disable fall back if parent node fails
*/
//#define MY_PARENT_NODE_IS_STATIC
/**
* @def MY_REPEATER_FEATURE
* @brief Enables repeater functionality (relays messages from other nodes)
*/
//#define MY_REPEATER_FEATURE
/**
* @def MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS
* @brief Timeout (in ms) to re-establish link if node is send to sleep and transport is not ready.
*/
#ifndef MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS
#define MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS (10*1000ul)
#endif
/**
* @def MY_SMART_SLEEP_WAIT_DURATION_MS
* @brief The wait period (in ms) before going to sleep when using smartSleep-functions.
*
* This period has to be long enough for controller to be able to send out
* potential buffered messages.
*/
#ifndef MY_SMART_SLEEP_WAIT_DURATION_MS
#define MY_SMART_SLEEP_WAIT_DURATION_MS (500ul)
#endif
/**********************************
* Over the air firmware updates
***********************************/
// Enable MY_OTA_FIRMWARE_FEATURE in sketch to allow safe over-the-air firmware updates.
// This feature requires external flash and the DualOptiBoot boot-loader.
// Note: You can still have OTA FW updates without external flash but it
// requires the MYSBootloader and disabled MY_OTA_FIRMWARE_FEATURE
//#define MY_OTA_FIRMWARE_FEATURE
/**
* @def MY_OTA_FLASH_SS
* @brief Slave select pin for external flash.
*/
#ifndef MY_OTA_FLASH_SS
#define MY_OTA_FLASH_SS 8
#endif
/**
* @def MY_OTA_FLASH_JDECID
* @brief Flash jdecid.
*/
#ifndef MY_OTA_FLASH_JDECID
#define MY_OTA_FLASH_JDECID 0x1F65
#endif
/**********************************
* Gateway config
***********************************/
/**
* @def MY_GATEWAY_MAX_RECEIVE_LENGTH
* @brief Max buffersize needed for messages coming from controller.
*/
#ifndef MY_GATEWAY_MAX_RECEIVE_LENGTH
#define MY_GATEWAY_MAX_RECEIVE_LENGTH (100u)
#endif
/**
* @def MY_GATEWAY_MAX_SEND_LENGTH
* @brief Max buffer size when sending messages.
*/
#ifndef MY_GATEWAY_MAX_SEND_LENGTH
#define MY_GATEWAY_MAX_SEND_LENGTH (120u)
#endif
/**
* @def MY_GATEWAY_MAX_CLIENTS
* @brief Max number of parallel clients (sever mode).
*/
#ifndef MY_GATEWAY_MAX_CLIENTS
#define MY_GATEWAY_MAX_CLIENTS (1u)
#endif
/**********************************
* Information LEDs blinking
***********************************/
// If one of the following is defined here, or in the sketch, the pin will be used for the
// corresponding led function.
// They have to be enabled here (or in your sketch). Replace x with the pin number you have the LED on.
//
// NOTE!! that on some platforms (for example sensebender GW) the hardware variant can enable LEDs by default,
// These defaults can be overridden by defining one of these.
//#define MY_DEFAULT_ERR_LED_PIN x
//#define MY_DEFAULT_TX_LED_PIN x
//#define MY_DEFAULT_RX_LED_PIN x
// The following setting allows you to inverse the LED blinking
// When MY_WITH_LEDS_BLINKING_INVERSE is enabled LEDSs are normally turned on and switches
// off when blinking
//#define MY_WITH_LEDS_BLINKING_INVERSE
/**********************************************
* Gateway inclusion button/mode configuration
**********************************************/
// Enabled inclusion mode feature
//#define MY_INCLUSION_MODE_FEATURE
// Enables inclusion-mode button feature on the gateway device
//#define MY_INCLUSION_BUTTON_FEATURE
// Disable inclusion mode button if inclusion mode feature is not enabled
#ifndef MY_INCLUSION_MODE_FEATURE
#undef MY_INCLUSION_BUTTON_FEATURE
#endif
/**
* @def MY_INCLUSION_MODE_BUTTON_PIN
* @brief The default input pin used for the inclusion mode button.
*/
#ifndef MY_INCLUSION_MODE_BUTTON_PIN
#if defined(ARDUINO_ARCH_ESP8266)
#define MY_INCLUSION_MODE_BUTTON_PIN 5
#else
#define MY_INCLUSION_MODE_BUTTON_PIN 3
#endif
#endif
/**
* @def MY_INCLUSION_MODE_DURATION
* @brief Number of seconds (default one minute) inclusion mode should be enabled.
*/
#ifndef MY_INCLUSION_MODE_DURATION
#define MY_INCLUSION_MODE_DURATION 60
#endif
/**
* @def MY_INCLUSION_BUTTON_PRESSED
* @brief The logical level indicating a pressed inclusion mode button.
*/
#if defined(MY_INCLUSION_BUTTON_EXTERNAL_PULLUP)
#define MY_INCLUSION_BUTTON_PRESSED HIGH
#else
#define MY_INCLUSION_BUTTON_PRESSED LOW
#endif
/**********************************
* Message Signing Settings
***********************************/
/**
* @def MY_SIGNING_ATSHA204
* @brief Enables HW backed signing functionality in library.
*
* For any signing related functionality to be included, this define or @ref MY_SIGNING_SOFT has to be enabled.
*/
//#define MY_SIGNING_ATSHA204
/**
* @def MY_SIGNING_SOFT
* @brief Enables SW backed signing functionality in library.
*
* For any signing related functionality to be included, this define or @ref MY_SIGNING_ATSHA204 has to be enabled.
*/
//#define MY_SIGNING_SOFT
/**
* @def MY_SIGNING_REQUEST_SIGNATURES
* @brief Enable this to inform gateway to sign all messages sent to this node.
*
* If used for a gateway, gateway will only request signatures from nodes that in turn
* request signatures from gateway.
*/
//#define MY_SIGNING_REQUEST_SIGNATURES
/**
* @def MY_VERIFICATION_TIMEOUT_MS
* @brief Define a suitable timeout for a signature verification session
*
* Consider the turnaround from a nonce being generated to a signed message being received
* which might vary, especially in networks with many hops. 5s ought to be enough for anyone.
*/
#ifndef MY_VERIFICATION_TIMEOUT_MS
#define MY_VERIFICATION_TIMEOUT_MS 5000
#endif
/**
* @def MY_SIGNING_NODE_WHITELISTING
* @brief Enable to turn on whitelisting
*
* When enabled, a signing node will salt the signature with it's unique signature and nodeId.<br>
* The verifying node will look up the sender in a local table of trusted nodes and
* do the corresponding salting in order to verify the signature.<br>
* For this reason, if whitelisting is enabled on one of the nodes in a sign-verify pair, both
* nodes have to implement whitelisting for this to work.<br>
* Note that a node can still transmit a non-salted message (i.e. have whitelisting disabled)
* to a node that has whitelisting enabled (assuming the receiver does not have a matching entry
* for the sender in it's whitelist). The whitelist to use is defined as the value of the flag.
*/
//#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}}
/**
* @def MY_SIGNING_ATSHA204_PIN
* @brief Atsha204 default pin setting
*
* Pin where ATSHA204 is attached
*/
#ifndef MY_SIGNING_ATSHA204_PIN
#define MY_SIGNING_ATSHA204_PIN 17
#endif
/**
* @def MY_SIGNING_SOFT_RANDOMSEED_PIN
* @brief Pin used for random generation in soft signing
*
* Do not connect anything to this when soft signing is enabled
*/
#ifndef MY_SIGNING_SOFT_RANDOMSEED_PIN
#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
#endif
/**********************************
* RS485 Driver Defaults
***********************************/
/**
* @def MY_RS485_BAUD_RATE
* @brief The RS485 BAUD rate.
*/
#ifndef MY_RS485_BAUD_RATE
#define MY_RS485_BAUD_RATE 9600
#endif
/**
* @def MY_RS485_MAX_MESSAGE_LENGTH
* @brief The maximum message length used for RS485.
*/
#ifndef MY_RS485_MAX_MESSAGE_LENGTH
#define MY_RS485_MAX_MESSAGE_LENGTH 40
#endif
/**********************************
* NRF24L01P Driver Defaults
***********************************/
// Enables RF24 encryption (all nodes and gateway must have this enabled, and all must be personalized with the same AES key)
//#define MY_RF24_ENABLE_ENCRYPTION
/**
* @def MY_DEBUG_VERBOSE_RF24
* @brief Enable MY_DEBUG_VERBOSE_RF24 flag for verbose debug prints related to the RF24 driver. Requires DEBUG to be enabled.
*/
//#define MY_DEBUG_VERBOSE_RF24
/**
* @def MY_RF24_SPI_MAX_SPEED
* @brief MY_RF24_SPI_MAX_SPEED to overrule default nRF24L01+ SPI speed.
*/
//#define MY_RF24_SPI_MAX_SPEED 4000000
/**
* @def MY_RF24_CE_PIN
* @brief Default RF24 chip enable pin setting. Override in sketch if needed.
*/
#ifndef MY_RF24_CE_PIN
#if defined(ARDUINO_ARCH_ESP8266)
#define MY_RF24_CE_PIN 4
#elif defined(ARDUINO_ARCH_SAMD)
#define MY_RF24_CE_PIN 27
#elif defined(LINUX_ARCH_RASPBERRYPI)
#define MY_RF24_CE_PIN 22
#else
#define MY_RF24_CE_PIN 9
#endif
#endif
/**
* @def MY_RF24_CS_PIN
* @brief Default RF24 chip select pin setting. Override in sketch if needed.
*/
#ifndef MY_RF24_CS_PIN
#if defined(ARDUINO_ARCH_ESP8266)
#define MY_RF24_CS_PIN 15
#elif defined(ARDUINO_ARCH_SAMD)
#define MY_RF24_CS_PIN 3
#elif defined(LINUX_ARCH_RASPBERRYPI)
#define MY_RF24_CS_PIN 24
#else
#define MY_RF24_CS_PIN 10
#endif
#endif
/**
* @def MY_RX_MESSAGE_BUFFER_FEATURE
* @brief This enabled the receiving buffer feature.
*
* This feature is currently not supported for RFM69 and RS485, for RF24 MY_RF24_IRQ_PIN has to be defined.
*/
//#define MY_RX_MESSAGE_BUFFER_FEATURE
/**
* @def MY_RX_MESSAGE_BUFFER_SIZE
* @brief Declare the amount of incoming messages that can be buffered.
*/
#ifdef MY_RX_MESSAGE_BUFFER_FEATURE
#ifndef MY_RX_MESSAGE_BUFFER_SIZE
#define MY_RX_MESSAGE_BUFFER_SIZE (20)
#endif
#endif
/**
* @def MY_RF24_PA_LEVEL
* @brief Default RF24 PA level. Override in sketch if needed.
*/
#ifndef MY_RF24_PA_LEVEL
#define MY_RF24_PA_LEVEL RF24_PA_MAX
#endif
/**
* @def MY_RF24_CHANNEL
* @brief RF channel for the sensor net, 0-125.
* Frequence: 2400 Mhz - 2525 Mhz Channels: 126
* http://www.mysensors.org/radio/nRF24L01Plus.pdf
* 0 => 2400 Mhz (RF24 channel 1)
* 1 => 2401 Mhz (RF24 channel 2)
* 76 => 2476 Mhz (RF24 channel 77)
* 83 => 2483 Mhz (RF24 channel 84)
* 124 => 2524 Mhz (RF24 channel 125)
* 125 => 2525 Mhz (RF24 channel 126)
* In some countries there might be limitations, in Germany for example only the range 2400,0 - 2483,5 Mhz is allowed
* http://www.bundesnetzagentur.de/SharedDocs/Downloads/DE/Sachgebiete/Telekommunikation/Unternehmen_Institutionen/Frequenzen/Allgemeinzuteilungen/2013_10_WLAN_2,4GHz_pdf.pdf
*/
#ifndef MY_RF24_CHANNEL
#define MY_RF24_CHANNEL 76
#endif
/**
* @def MY_RF24_DATARATE
* @brief RF24 datarate (RF24_250KBPS for 250kbs, RF24_1MBPS for 1Mbps or RF24_2MBPS for 2Mbps).
*/
#ifndef MY_RF24_DATARATE
#define MY_RF24_DATARATE RF24_250KBPS
#endif
/**
* @def MY_RF24_BASE_RADIO_ID
* @brief RF24 radio network identifier.
*
* This acts as base value for sensor nodeId addresses. Change this (or channel) if you have more than one sensor network.
*/
#ifndef MY_RF24_BASE_RADIO_ID
#define MY_RF24_BASE_RADIO_ID 0x00,0xFC,0xE1,0xA8,0xA8
#endif
/**
* @def MY_RF24_ADDR_WIDTH
* @brief RF24 address width.
*
* This defines the width of the base address.
*/
#ifndef MY_RF24_ADDR_WIDTH
#define MY_RF24_ADDR_WIDTH 5
#endif
// Enable SOFTSPI for NRF24L01, useful for the W5100 Ethernet module
//#define MY_SOFTSPI
/**
* @def MY_SOFT_SPI_SCK_PIN
* @brief Soft SPI SCK pin.
*/
#ifndef MY_SOFT_SPI_SCK_PIN
#define MY_SOFT_SPI_SCK_PIN 14
#endif
/**
* @def MY_SOFT_SPI_MISO_PIN
* @brief Soft SPI MISO pin.
*/
#ifndef MY_SOFT_SPI_MISO_PIN
#define MY_SOFT_SPI_MISO_PIN 16
#endif
/**
* @def MY_SOFT_SPI_MOSI_PIN
* @brief Soft SPI MOSI pin.
*/
#ifndef MY_SOFT_SPI_MOSI_PIN
#define MY_SOFT_SPI_MOSI_PIN 15
#endif
/**********************************
* RFM69 Driver Defaults
***********************************/
/**
* @def MY_RFM69_FREQUENCY
* @brief RFM69 frequency to use (RF69_433MHZ for 433MHz, RF69_868MHZ for 868MHz or RF69_915MHZ for 915MHz).
*
* This must match the hardware version of the RFM69 radio.
*/
#ifndef MY_RFM69_FREQUENCY
#define MY_RFM69_FREQUENCY RF69_868MHZ
#endif
/**
* @def MY_IS_RFM69HW
* @brief Enable this if you're running the RFM69HW model.
*/
//#define MY_IS_RFM69HW
/**
* @def MY_RFM69HW
* @brief Set to true if @ref MY_IS_RFM69HW is set.
*/
#ifdef MY_IS_RFM69HW
#define MY_RFM69HW true
#else
#define MY_RFM69HW false
#endif
/**
* @def MY_RFM69_NETWORKID
* @brief RFM69 Network ID. Use the same for all nodes that will talk to each other.
*/
#ifndef MY_RFM69_NETWORKID
#define MY_RFM69_NETWORKID 100
#endif
/**
* @def MY_RF69_IRQ_PIN
* @brief RF69 IRQ pin.
*/
#ifndef MY_RF69_IRQ_PIN
#define MY_RF69_IRQ_PIN RF69_IRQ_PIN
#endif
/**
* @def MY_RF69_SPI_CS
* @brief RF69 SPI chip select pin.
*/
#ifndef MY_RF69_SPI_CS
#define MY_RF69_SPI_CS RF69_SPI_CS
#endif
/**
* @def MY_RF69_IRQ_NUM
* @brief RF69 IRQ pin number.
*/
#ifndef MY_RF69_IRQ_NUM
#if defined(ARDUINO_ARCH_ESP8266)
#define MY_RF69_IRQ_NUM MY_RF69_IRQ_PIN
#else
#define MY_RF69_IRQ_NUM RF69_IRQ_NUM
#endif
#endif
// Enables RFM69 encryption (all nodes and gateway must have this enabled, and all must be personalized with the same AES key)
//#define MY_RFM69_ENABLE_ENCRYPTION
/**************************************
* Ethernet Gateway Transport Defaults
***************************************/
// The gateway options available
//#define MY_GATEWAY_W5100
//#define MY_GATEWAY_ENC28J60
//#define MY_GATEWAY_ESP8266
//#define MY_GATEWAY_LINUX
/**
* @def MY_PORT
* @brief The Ethernet TCP/UDP port to open on controller or gateway.
*/
#ifndef MY_PORT
#ifdef MY_GATEWAY_MQTT_CLIENT
#define MY_PORT 1883
#else
#define MY_PORT 5003
#endif
#endif
// Static ip address of gateway (if this is disabled, DHCP will be used)
//#define MY_IP_ADDRESS 192,168,178,66
// Enables UDP mode for Ethernet gateway (W5100)
//#define MY_USE_UDP
/**
* @def MY_IP_RENEWAL_INTERVAL
* @brief DHCP, default renewal setting in milliseconds.
*/
#ifndef MY_IP_RENEWAL_INTERVAL
#define MY_IP_RENEWAL_INTERVAL 60000
#endif
/**
* @def MY_MAC_ADDRESS
* @brief Ethernet MAC address.
*
* This needs to be unique on the network.
*/
#ifndef MY_MAC_ADDRESS
#define MY_MAC_ADDRESS 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
#endif
// Controller ip-address, if this is defined, gateway will act as a client trying to contact controller on MY_PORT.
// If MY_CONTROLLER_IP_ADDRESS is left un-defined, gateway acts as server allowing incoming connections.
//#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 254
/**************************************
* Node Locking
***************************************/
/**
* @defgroup MyLockgrp MyNodeLock
* @ingroup internals
* @{
* @brief The node lock feature is a security related feature. It locks a node that suspect itself for being
* under some form of attack.
*
* This is achieved by having a counter stored in EEPROM which decrements when suspicious activity is detected.
* If the counter reaches 0, node will not work anymore and will transmit a @ref I_LOCKED message to the
* gateway/controller with 30m intervals. Payload is a string with a reason for the locking.
* The string is abbreviated to accomodate a signature. The following abbreviations exist at the moment:
* - LDB (Locked During Boot)
* - TMNR (Too Many Nonce Requests)
* - TMFV (Too Many Failed Verifications)
*
* Typically, the counter only decrements when suspicious activity happens in a row.
* It is reset if legit traffic is present.
* Examples of malicious activity are:
* - Repeatedly incorrectly checksummed OTA firmware
* - Repeated requests for signing nonces without properly signed messages arriving
* - Repeatedly failed signature verifications
*
* If counter reaches zero, node locks down and EEPROM has to be erased/reset to reactivate node.
* Node can also be unlocked by grounding a pin (see @ref MY_NODE_UNLOCK_PIN).
*
* The size of the counter can be adjusted using @ref MY_NODE_LOCK_COUNTER_MAX.
*
* @def MY_NODE_LOCK_FEATURE
* @brief Enable this to activate intrusion prevention mechanisms on the node.
*/
//#define MY_NODE_LOCK_FEATURE
/**
* @def MY_NODE_UNLOCK_PIN
* @brief By grounding this pin durig reset of a locked node, the node will unlock.
*
* If using a secure bootloader, grounding the pin is the only option to reactivate the node.
* If using stock Android bootloader or a DualOptiBoot it is also possible to download a sketch
* using serial protocol to erase EEPROM to unlock the node.
*/
#ifndef MY_NODE_UNLOCK_PIN
#define MY_NODE_UNLOCK_PIN 14
#endif
/**
* @def MY_NODE_LOCK_COUNTER_MAX
* @brief Maximum accepted occurances of suspected malicious activity in a node.
*
* Counter decrements on reoccuring incidents but resets if legitimate behaviour is identified.
*/
#ifndef MY_NODE_LOCK_COUNTER_MAX
#define MY_NODE_LOCK_COUNTER_MAX 5
#endif
/** @}*/ // Node lock group
/**********************************
* ESP8266 Defaults
***********************************/
/**
* @def MY_ESP8266_SERIAL_MODE
* @brief Serial modes: SERIAL_FULL, SERIAL_RX_ONLY, SERIAL_TX_ONLY
*
* SERIAL_FULL: Default mode.
* SERIAL_TX_ONLY: allows to use RX (GPIO3) as a general purpose input/output.
* SERIAL_RX_ONLY: allows to use TX (GPIO1) as a general purpose input/output.
*/
#ifndef MY_ESP8266_SERIAL_MODE
#define MY_ESP8266_SERIAL_MODE SERIAL_FULL
#endif
/**************************************
* Linux Settings
***************************************/
/**
* @def MY_LINUX_SERIAL_PORT
* @brief Serial device port
*/
#ifndef MY_LINUX_SERIAL_PORT
#define MY_LINUX_SERIAL_PORT "/dev/ttyACM0"
#endif
/**
* @def MY_IS_SERIAL_PTY
* @brief Set serial as a pseudo terminal.
*
* Enable this if you need to connect to a controller running on the same device.
*/
//#define MY_IS_SERIAL_PTY
/**
* @def MY_LINUX_SERIAL_PTY
* @brief Symlink name for the PTY device.
*/
#ifndef MY_LINUX_SERIAL_PTY
#define MY_LINUX_SERIAL_PTY "/dev/ttyMySensorsGateway"
#endif
/**
* @def MY_LINUX_SERIAL_GROUPNAME
* @brief Grant access to the specified system group for the serial device.
*/
//#define MY_LINUX_SERIAL_GROUPNAME "tty"
/**
* @def MY_LINUX_CONFIG_FILE
* @brief Set the filepath for the gateway config file
*
* For now the configuration file is only used to store the emulated eeprom state
*/
#ifndef MY_LINUX_CONFIG_FILE
#define MY_LINUX_CONFIG_FILE "/etc/mysensors.dat"
#endif
#endif // MyConfig_h
// Doxygen specific constructs, not included when built normally
// This is used to enable disabled macros/definitions to be included in the documentation as well.
#if DOXYGEN
#define MY_SIGNING_ATSHA204
#define MY_SIGNING_SOFT
#define MY_SIGNING_REQUEST_SIGNATURES
#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}}
#define MY_IS_RFM69HW
#define MY_PARENT_NODE_IS_STATIC
#define MY_REGISTRATION_CONTROLLER
#define MY_DEBUG_VERBOSE_RF24
#define MY_TRANSPORT_SANITY_CHECK
#define MY_RF24_IRQ_PIN
#define MY_RX_MESSAGE_BUFFER_FEATURE
#define MY_RX_MESSAGE_BUFFER_SIZE
#define MY_NODE_LOCK_FEATURE
#define MY_REPEATER_FEATURE
#define MY_TRANSPORT_DONT_CARE_MODE
#define MY_LINUX_SERIAL_GROUPNAME
#define MY_IS_SERIAL_PTY
#endif
+351
View File
@@ -0,0 +1,351 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MySensors.h
*
* MySensors main interface (includes all necessary code for the library)
*/
#ifndef MySensors_h
#define MySensors_h
#include "core/MySensorsCore.h"
// Detect node type
/**
* @def MY_GATEWAY_FEATURE
* @brief Is set for gateway sketches.
*/
/**
* @def MY_IS_GATEWAY
* @brief Is true when @ref MY_GATEWAY_FEATURE is set.
*/
/**
* @def MY_NODE_TYPE
* @brief Contain a string describing the class of sketch/node (gateway/repeater/sensor).
*/
#if defined(MY_GATEWAY_SERIAL) || defined(MY_GATEWAY_W5100) || defined(MY_GATEWAY_ENC28J60) || defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) || defined(MY_GATEWAY_MQTT_CLIENT)
#define MY_GATEWAY_FEATURE
#define MY_IS_GATEWAY (true)
#define MY_NODE_TYPE "GW"
#elif defined(MY_REPEATER_FEATURE)
#define MY_IS_GATEWAY (false)
#define MY_NODE_TYPE "REPEATER"
#else
#define MY_IS_GATEWAY (false)
#define MY_NODE_TYPE "NODE"
#endif
// Enable radio "feature" if one of the radio types was enabled
#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RS485)
#define MY_RADIO_FEATURE
#endif
// HARDWARE
#if defined(ARDUINO_ARCH_ESP8266)
// Remove PSTR macros from debug prints
#undef PSTR
#define PSTR(x) (x)
//#undef F
//#define F(x) (x)
#include "core/MyHwESP8266.cpp"
#elif defined(ARDUINO_ARCH_AVR)
#include "core/MyHwATMega328.cpp"
#elif defined(ARDUINO_ARCH_SAMD)
#include "core/MyHwSAMD.cpp"
#elif defined(__linux__)
// Remove PSTR macros from debug prints
#undef PSTR
#define PSTR(x) (x)
//#undef F
//#define F(x) (x)
#define PROGMEM
#define vsnprintf_P(...) vsnprintf( __VA_ARGS__ )
#define snprintf_P(...) snprintf( __VA_ARGS__ )
#define memcpy_P memcpy
#define pgm_read_dword(x) (*x)
#define pgm_read_byte_near(x) (*x)
#include "core/MyHwLinuxGeneric.cpp"
// Ugly hack
#ifdef LINUX_ARCH_RASPBERRYPI
#undef hwDigitalWrite
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
#endif
#endif
// LEDS
#if !defined(MY_DEFAULT_ERR_LED_PIN) & defined(MY_HW_ERR_LED_PIN)
#define MY_DEFAULT_ERR_LED_PIN MY_HW_ERR_LED_PIN
#endif
#if !defined(MY_DEFAULT_TX_LED_PIN) && defined(MY_HW_TX_LED_PIN)
#define MY_DEFAULT_TX_LED_PIN MY_HW_TX_LED_PIN
#endif
#if !defined(MY_DEFAULT_RX_LED_PIN) && defined(MY_HW_TX_LED_PIN)
#define MY_DEFAULT_RX_LED_PIN MY_HW_TX_LED_PIN
#endif
#if defined(MY_LEDS_BLINKING_FEATURE)
#error MY_LEDS_BLINKING_FEATURE is now removed from MySensors core,\
define MY_DEFAULT_ERR_LED_PIN, MY_DEFAULT_TX_LED_PIN or\
MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs
#endif
/**
* @def MY_DEFAULT_LED_BLINK_PERIOD
* @brief Default LEDs blinking period in milliseconds.
*/
#ifndef MY_DEFAULT_LED_BLINK_PERIOD
#define MY_DEFAULT_LED_BLINK_PERIOD 300
#endif
#if defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#include "core/MyLeds.cpp"
#else
#include "core/MyLeds.h"
#endif
#include "core/MyIndication.cpp"
// INCLUSION MODE
#if defined(MY_INCLUSION_MODE_FEATURE)
#include "core/MyInclusionMode.cpp"
#endif
// SIGNING
#if defined(MY_SIGNING_ATSHA204) || defined(MY_SIGNING_SOFT)
#define MY_SIGNING_FEATURE
#endif
#include "core/MySigning.cpp"
#include "drivers/ATSHA204/sha256.cpp"
#if defined(MY_SIGNING_FEATURE)
// SIGNING COMMON FUNCTIONS
#if defined(MY_SIGNING_ATSHA204) && defined(MY_SIGNING_SOFT)
#error Only one signing engine can be activated
#endif
#if defined(MY_SIGNING_ATSHA204)
#include "core/MySigningAtsha204.cpp"
#include "drivers/ATSHA204/ATSHA204.cpp"
#elif defined(MY_SIGNING_SOFT)
#include "core/MySigningAtsha204Soft.cpp"
#endif
#endif
// FLASH
#if defined(MY_OTA_FIRMWARE_FEATURE)
#include "drivers/SPIFlash/SPIFlash.cpp"
#include "core/MyOTAFirmwareUpdate.cpp"
#endif
// GATEWAY - TRANSPORT
#if defined(MY_CONTROLLER_IP_ADDRESS) || defined(MY_CONTROLLER_URL_ADDRESS)
#define MY_GATEWAY_CLIENT_MODE
#endif
#if defined(MY_USE_UDP) && !defined(MY_GATEWAY_CLIENT_MODE)
#error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS for UDP
#endif
#if defined(MY_GATEWAY_MQTT_CLIENT)
#if defined(MY_RADIO_FEATURE)
// We assume that a gateway having a radio also should act as repeater
#define MY_REPEATER_FEATURE
#endif
// GATEWAY - COMMON FUNCTIONS
// We support MQTT Client using W5100, ESP8266 and Linux
#if !defined(MY_GATEWAY_CLIENT_MODE)
#error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS
#endif
#if !defined(MY_MQTT_PUBLISH_TOPIC_PREFIX)
#error You must specify a topic publish prefix MY_MQTT_PUBLISH_TOPIC_PREFIX for this MQTT client
#endif
#if !defined(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX)
#error You must specify a topic subscribe prefix MY_MQTT_SUBSCRIBE_TOPIC_PREFIX for this MQTT client
#endif
#if !defined(MY_MQTT_CLIENT_ID)
#error You must define a unique MY_MQTT_CLIENT_ID for this MQTT client
#endif
#include "core/MyGatewayTransport.cpp"
#include "core/MyProtocolMySensors.cpp"
#if defined(MY_GATEWAY_LINUX)
#include "drivers/Linux/EthernetClient.h"
#include "drivers/Linux/EthernetServer.h"
#include "drivers/Linux/IPAddress.h"
#endif
#include "drivers/PubSubClient/PubSubClient.cpp"
#include "core/MyGatewayTransportMQTTClient.cpp"
#elif defined(MY_GATEWAY_FEATURE)
// GATEWAY - COMMON FUNCTIONS
#include "core/MyGatewayTransport.cpp"
#include "core/MyProtocolMySensors.cpp"
// GATEWAY - CONFIGURATION
#if defined(MY_RADIO_FEATURE)
// We assume that a gateway having a radio also should act as repeater
#define MY_REPEATER_FEATURE
#endif
#if !defined(MY_PORT)
#error You must define MY_PORT (controller or gatway port to open)
#endif
#if defined(MY_GATEWAY_ESP8266)
// GATEWAY - ESP8266
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_LINUX)
// GATEWAY - Generic Linux
#include "drivers/Linux/EthernetClient.h"
#include "drivers/Linux/EthernetServer.h"
#include "drivers/Linux/IPAddress.h"
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_W5100)
// GATEWAY - W5100
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_ENC28J60)
// GATEWAY - ENC28J60
#if defined(MY_USE_UDP)
#error UDP mode is not available for ENC28J60
#endif
#include "core/MyGatewayTransportEthernet.cpp"
#elif defined(MY_GATEWAY_SERIAL)
// GATEWAY - SERIAL
#include "core/MyGatewayTransportSerial.cpp"
#endif
#endif
// RAM ROUTING TABLE
#if defined(MY_RAM_ROUTING_TABLE_FEATURE) && defined(MY_REPEATER_FEATURE)
// activate feature based on architecture
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_SAMD) || defined(LINUX_ARCH_RASPBERRYPI)
#define MY_RAM_ROUTING_TABLE_ENABLED
#elif defined(ARDUINO_ARCH_AVR)
// memory limited, enable with care
// #define MY_RAM_ROUTING_TABLE_ENABLED
#endif
#endif
#if defined(MY_TRANSPORT_DONT_CARE_MODE) && !defined(MY_GATEWAY_FEATURE)
// enables transport don't care mode, i.e. transport link is not monitored and GW connection optional
#if !defined(MY_PARENT_NODE_ID) || MY_PARENT_NODE_ID==255
#error MY_TRANSPORT_DONT_CARE_MODE requires MY_PARENT_NODE_ID set
#endif
#if !defined(MY_PARENT_NODE_IS_STATIC)
#define MY_PARENT_NODE_IS_STATIC
#endif
#ifdef MY_REGISTRATION_FEATURE
#undef MY_REGISTRATION_FEATURE
#endif
#ifdef MY_TRANSPORT_SANITY_CHECK
#undef MY_TRANSPORT_SANITY_CHECK
#endif
#ifndef MY_TRANSPORT_UPLINK_CHECK_DISABLED
#define MY_TRANSPORT_UPLINK_CHECK_DISABLED
#endif
#ifdef MY_REGISTRATION_FEATURE
#undef MY_REGISTRATION_FEATURE
#endif
#endif
// RADIO
#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RS485)
// SOFTSPI
#ifdef MY_SOFTSPI
#if defined(ARDUINO_ARCH_ESP8266)
#error Soft SPI is not available on ESP8266
#endif
#include "drivers/AVR/DigitalIO/DigitalIO.h"
#endif
#if defined(MY_RADIO_NRF24) && defined(__linux__) && !defined(LINUX_ARCH_RASPBERRYPI)
#error No support for nRF24 radio on this platform
#endif
#include "core/MyTransport.cpp"
#if (defined(MY_RADIO_NRF24) && defined(MY_RADIO_RFM69)) || (defined(MY_RADIO_NRF24) && defined(MY_RS485)) || (defined(MY_RADIO_RFM69) && defined(MY_RS485))
#error Only one forward link driver can be activated
#endif
#if defined(MY_RADIO_NRF24)
#if defined(MY_RF24_ENABLE_ENCRYPTION)
#include "drivers/AES/AES.cpp"
#endif
#include "drivers/RF24/RF24.cpp"
#include "core/MyTransportNRF24.cpp"
#elif defined(MY_RS485)
#include "drivers/AltSoftSerial/AltSoftSerial.cpp"
#include "core/MyTransportRS485.cpp"
#elif defined(MY_RADIO_RFM69)
#include "drivers/RFM69/RFM69.cpp"
#include "core/MyTransportRFM69.cpp"
#endif
#endif
#if defined(MY_PARENT_NODE_IS_STATIC) && (MY_PARENT_NODE_ID == AUTO)
#error Parent is static but no parent ID defined.
#endif
// Make sure to disable child features when parent feature is disabled
#if !defined(MY_RADIO_FEATURE)
#undef MY_OTA_FIRMWARE_FEATURE
#undef MY_REPEATER_FEATURE
#undef MY_SIGNING_NODE_WHITELISTING
#undef MY_SIGNING_FEATURE
#endif
#if !defined(MY_GATEWAY_FEATURE)
#undef MY_INCLUSION_MODE_FEATURE
#undef MY_INCLUSION_BUTTON_FEATURE
#endif
#if !defined(MY_CORE_ONLY)
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_RADIO_FEATURE)
#error No forward link or gateway feature activated. This means nowhere to send messages! Pretty pointless.
#endif
#endif
#include "core/MyCapabilities.h"
#include "core/MyMessage.cpp"
#include "core/MySensorsCore.cpp"
#include <Arduino.h>
#if !defined(MY_CORE_ONLY)
#if defined(ARDUINO_ARCH_ESP8266)
#include "core/MyMainESP8266.cpp"
#elif defined(__linux__)
#include "core/MyMainLinux.cpp"
#else
#include "core/MyMainDefault.cpp"
#endif
#endif
#endif
// Doxygen specific constructs, not included when built normally
// This is used to enable disabled macros/definitions to be included in the documentation as well.
#if DOXYGEN
#define MY_GATEWAY_FEATURE
#define MY_LEDS_BLINKING_FEATURE //!< \deprecated use MY_DEFAULT_RX_LED_PIN, MY_DEFAULT_TX_LED_PIN and/or MY_DEFAULT_ERR_LED_PIN instead **** DEPRECATED, DO NOT USE ****
#endif
-17
View File
@@ -1,17 +0,0 @@
const dbAddress = '127.0.0.1';
const dbPort = 27017;
const dbName = 'MySensorsDb';
var dbc = require('mongodb').MongoClient;
dbc.connect('mongodb://' + dbAddress + ':' + dbPort + '/' + dbName, function(err, db) {
if (err) {
console.log('Error connecting to database at mongodb://' + dbAddress + ':' + dbPort + '/' + dbName);
} else {
db.dropDatabase(function(err, result) {
if (err)
console.log('Error dropping database at mongodb://' + dbAddress + ':' + dbPort + '/' + dbName);
else
console.log('Successfully dropped database at mongodb://' + dbAddress + ':' + dbPort + '/' + dbName);
});
}
});
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh
# pidfile must be first argument
PIDFILE=$1
mkdir -p /home/pi/logs
cd /home/pi/Arduino/NodeJsController
/opt/node/bin/node NodeJsController.js >>/home/pi/logs/NodeJsController.log 2>&1 </dev/null &
CHILD="$!"
echo $CHILD > $PIDFILE
-160
View File
@@ -1,160 +0,0 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: mysensors
# Required-Start: $remote_fs $syslog $mongodb
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: MySensors NodeJs Controller
# Description: MySensors NodeJs Controller
### END INIT INFO
# Author: Foo Bar <foobar@baz.org>
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="MySensors NodeJs Controller"
NAME="MySensors.sh"
PIDFILE="/usr/local/var/run/$NAME.pid"
DAEMON="/home/pi/Arduino/NodeJsController/$NAME"
DAEMON_ARGS="$PIDFILE"
DAEMON_USER="pi"
SCRIPTNAME=/etc/init.d/$NAME
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --chuid $DAEMON_USER --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --chuid $DAEMON_USER --pidfile $PIDFILE --exec $DAEMON -- \
$DAEMON_ARGS \
|| return 2
# Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time.
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP),
# then implement that here.
#
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
return 0
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
#reload|force-reload)
#
# If do_reload() is not implemented then leave this commented out
# and leave 'force-reload' as an alias for 'restart'.
#
#log_daemon_msg "Reloading $DESC" "$NAME"
#do_reload
#log_end_msg $?
#;;
restart|force-reload)
#
# If the "reload" option is implemented then remove the
# 'force-reload' alias
#
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:
-710
View File
@@ -1,710 +0,0 @@
//const gwType = 'Ethernet';
//const gwAddress = '10.0.1.99';
//const gwPort = 9999;
const gwType = 'Serial';
//const gwPort = 'COM4';
const gwPort = '/dev/ttyAMA0';
const gwBaud = 115200;
const dbAddress = '127.0.0.1';
const dbPort = 27017;
const dbName = 'MySensorsDb';
const fwSketches = [ ];
const fwDefaultType = 0xFFFF; // index of hex file from array above (0xFFFF
const FIRMWARE_BLOCK_SIZE = 16;
const BROADCAST_ADDRESS = 255;
const NODE_SENSOR_ID = 255;
const C_PRESENTATION = 0;
const C_SET = 1;
const C_REQ = 2;
const C_INTERNAL = 3;
const C_STREAM = 4;
const V_TEMP = 0;
const V_HUM = 1;
const V_LIGHT = 2;
const V_DIMMER = 3;
const V_PRESSURE = 4;
const V_FORECAST = 5;
const V_RAIN = 6;
const V_RAINRATE = 7;
const V_WIND = 8;
const V_GUST = 9;
const V_DIRECTION = 10;
const V_UV = 11;
const V_WEIGHT = 12;
const V_DISTANCE = 13;
const V_IMPEDANCE = 14;
const V_ARMED = 15;
const V_TRIPPED = 16;
const V_WATT = 17;
const V_KWH = 18;
const V_SCENE_ON = 19;
const V_SCENE_OFF = 20;
const V_HEATER = 21;
const V_HEATER_SW = 22;
const V_LIGHT_LEVEL = 23;
const V_VAR1 = 24;
const V_VAR2 = 25;
const V_VAR3 = 26;
const V_VAR4 = 27;
const V_VAR5 = 28;
const V_UP = 29;
const V_DOWN = 30;
const V_STOP = 31;
const V_IR_SEND = 32;
const V_IR_RECEIVE = 33;
const V_FLOW = 34;
const V_VOLUME = 35;
const V_LOCK_STATUS = 36;
const I_BATTERY_LEVEL = 0;
const I_TIME = 1;
const I_VERSION = 2;
const I_ID_REQUEST = 3;
const I_ID_RESPONSE = 4;
const I_INCLUSION_MODE = 5;
const I_CONFIG = 6;
const I_PING = 7;
const I_PING_ACK = 8;
const I_LOG_MESSAGE = 9;
const I_CHILDREN = 10;
const I_SKETCH_NAME = 11;
const I_SKETCH_VERSION = 12;
const I_REBOOT = 13;
const S_DOOR = 0;
const S_MOTION = 1;
const S_SMOKE = 2;
const S_LIGHT = 3;
const S_DIMMER = 4;
const S_COVER = 5;
const S_TEMP = 6;
const S_HUM = 7;
const S_BARO = 8;
const S_WIND = 9;
const S_RAIN = 10;
const S_UV = 11;
const S_WEIGHT = 12;
const S_POWER = 13;
const S_HEATER = 14;
const S_DISTANCE = 15;
const S_LIGHT_LEVEL = 16;
const S_ARDUINO_NODE = 17;
const S_ARDUINO_REPEATER_NODE = 18;
const S_LOCK = 19;
const S_IR = 20;
const S_WATER = 21;
const S_AIR_QUALITY = 22;
const ST_FIRMWARE_CONFIG_REQUEST = 0;
const ST_FIRMWARE_CONFIG_RESPONSE = 1;
const ST_FIRMWARE_REQUEST = 2;
const ST_FIRMWARE_RESPONSE = 3;
const ST_SOUND = 4;
const ST_IMAGE = 5;
const P_STRING = 0;
const P_BYTE = 1;
const P_INT16 = 2;
const P_UINT16 = 3;
const P_LONG32 = 4;
const P_ULONG32 = 5;
const P_CUSTOM = 6;
var fs = require('fs');
var path = require('path');
var requestify = require('requestify');
var appendedString="";
function crcUpdate(old, value) {
var c = old ^ value;
for (var i = 0; i < 8; ++i) {
if ((c & 1) > 0)
c = ((c >> 1) ^ 0xA001);
else
c = (c >> 1);
}
return c;
}
function pullWord(arr, pos) {
return arr[pos] + 256 * arr[pos + 1];
}
function pushWord(arr, val) {
arr.push(val & 0x00FF);
arr.push((val >> 8) & 0x00FF);
}
function pushDWord(arr, val) {
arr.push(val & 0x000000FF);
arr.push((val >> 8) & 0x000000FF);
arr.push((val >> 16) & 0x000000FF);
arr.push((val >> 24) & 0x000000FF);
}
function loadFirmware(fwtype, fwversion, sketch, db) {
var filename = path.basename(sketch);
console.log("compiling firmware: " + filename);
var req = {
files: [{
filename: filename,
content: fs.readFileSync(sketch).toString()
}],
format: "hex",
version: "105",
build: {
mcu: "atmega328p",
f_cpu: "16000000L",
core: "arduino",
variant: "standard"
}
};
requestify.post('https://codebender.cc/utilities/compile/', req).then(function(res) {
var body = JSON.parse(res.getBody());
if (body.success) {
console.log("loading firmware: " + filename);
fwdata = [];
var start = 0;
var end = 0;
var pos = 0;
var hex = body.output.split("\n");
for(l in hex) {
line = hex[l].trim();
if (line.length > 0) {
while (line.substring(0, 1) != ":")
line = line.substring(1);
var reclen = parseInt(line.substring(1, 3), 16);
var offset = parseInt(line.substring(3, 7), 16);
var rectype = parseInt(line.substring(7, 9), 16);
var data = line.substring(9, 9 + 2 * reclen);
var chksum = parseInt(line.substring(9 + (2 * reclen), 9 + (2 * reclen) + 2), 16);
if (rectype == 0) {
if ((start == 0) && (end == 0)) {
if (offset % 128 > 0)
throw new Error("error loading hex file - offset can't be devided by 128");
start = offset;
end = offset;
}
if (offset < end)
throw new Error("error loading hex file - offset lower than end");
while (offset > end) {
fwdata.push(255);
pos++;
end++;
}
for (var i = 0; i < reclen; i++) {
fwdata.push(parseInt(data.substring(i * 2, (i * 2) + 2), 16));
pos++;
}
end += reclen;
}
}
}
var pad = end % 128; // ATMega328 has 64 words per page / 128 bytes per page
for (var i = 0; i < 128 - pad; i++) {
fwdata.push(255);
pos++;
end++;
}
var blocks = (end - start) / FIRMWARE_BLOCK_SIZE;
var crc = 0xFFFF;
for (var i = 0; i < blocks * FIRMWARE_BLOCK_SIZE; ++i) {
var v = crc;
crc = crcUpdate(crc, fwdata[i]);
}
db.collection('firmware', function(err, c) {
c.update({
'type': fwtype,
'version': fwversion
}, {
$set: {
'filename': filename,
'blocks': blocks,
'crc': crc,
'data': fwdata
}
}, {
upsert: true
}, function(err, result) {
if (err)
console.log('Error writing firmware to database');
});
});
console.log("loading firmware done. blocks: " + blocks + " / crc: " + crc);
} else {
console.log("error: %j", res.body);
}
});
}
/*
function decode(msg) {
var msgs = msg.toString().split(";");
rsender = +msgs[0];
rsensor = +msgs[1];
rcommand = +msgs[2];
rtype = +msgs[3];
var pl = msgs[4].trim();
rpayload = [];
for (var i = 0; i < pl.length; i+=2) {
var b = parseInt(pl.substring(i, i + 2), 16);
rpayload.push(b);
}
}
*/
function encode(destination, sensor, command, acknowledge, type, payload) {
var msg = destination.toString(10) + ";" + sensor.toString(10) + ";" + command.toString(10) + ";" + acknowledge.toString(10) + ";" + type.toString(10) + ";";
if (command == 4) {
for (var i = 0; i < payload.length; i++) {
if (payload[i] < 16)
msg += "0";
msg += payload[i].toString(16);
}
} else {
msg += payload;
}
msg += '\n';
return msg.toString();
}
function saveProtocol(sender, payload, db) {
db.collection('node', function(err, c) {
c.update({
'id': sender
}, {
$set: {
'protocol': payload
}
}, {
upsert: true
}, function(err, result) {
if (err)
console.log("Error writing protocol to database");
});
});
}
function saveSensor(sender, sensor, type, db) {
db.collection('node', function(err, c) {
c.update({
'id': sender
}, {
$addToSet: {
sensor: type
}
}, function(err, result) {
if (err)
console.log("Error writing sensor to database");
});
});
}
function saveValue(sender, sensor, type, payload, db) {
var cn = "Value-" + sender.toString() + "-" + sensor.toString();
db.createCollection(cn, function(err, c) {
c.save({
'timestamp': new Date().getTime(),
'type': type,
'value': payload
}, function(err, result) {
if (err)
console.log("Error writing value to database");
});
});
}
function saveBatteryLevel(sender, payload, db) {
var cn = "BatteryLevel-" + sender.toString();
db.createCollection(cn, function(err, c) {
c.save({
'timestamp': new Date().getTime(),
'value': payload
}, function(err, result) {
if (err)
console.log("Error writing battery level to database");
});
});
}
function saveSketchName(sender, payload, db) {
db.collection('node', function(err, c) {
c.update({
'id': sender
}, {
$set: {
'sketchName': payload
}
}, function(err, result) {
if (err)
console.log("Error writing sketch name to database");
});
});
}
function saveSketchVersion(sender, payload, db) {
db.collection('node', function(err, c) {
c.update({
'id': sender
}, {
$set: {
'sketchVersion': payload
}
}, function(err, result) {
if (err)
console.log("Error writing sketch version to database");
});
});
}
function sendTime(destination, sensor, gw) {
var payload = new Date().getTime();
var command = C_INTERNAL;
var acknowledge = 0; // no ack
var type = I_TIME;
var td = encode(destination, sensor, command, acknowledge, type, payload);
console.log('-> ' + td.toString());
gw.write(td);
}
function sendNextAvailableSensorId(db, gw) {
db.collection('node', function(err, c) {
c.find({
$query: { },
$orderby: {
'id': 1
}
}).toArray(function(err, results) {
if (err)
console.log('Error finding nodes');
var id = 1;
for (var i = 0; i < results.length; i++)
if (results[i].id > i + 1) {
id = i + 1;
break;
}
if (id < 255) {
c.save({
'id': id
}, function(err, result) {
if (err)
console.log('Error writing node to database');
var destination = BROADCAST_ADDRESS;
var sensor = NODE_SENSOR_ID;
var command = C_INTERNAL;
var acknowledge = 0; // no ack
var type = I_ID_RESPONSE;
var payload = id;
var td = encode(destination, sensor, command, acknowledge, type, payload);
console.log('-> ' + td.toString());
gw.write(td);
});
}
});
});
}
function sendConfig(destination, gw) {
var payload = "M";
var sensor = NODE_SENSOR_ID;
var command = C_INTERNAL;
var acknowledge = 0; // no ack
var type = I_CONFIG;
var td = encode(destination, sensor, command, acknowledge, type, payload);
console.log('-> ' + td.toString());
gw.write(td);
}
function sendFirmwareConfigResponse(destination, fwtype, fwversion, db, gw) {
// keep track of type/versin info for each node
// at the same time update the last modified date
// could be used to remove nodes not seen for a long time etc.
db.collection('node', function(err, c) {
c.update({
'id': destination
}, {
$set: {
'type': fwtype,
'version': fwversion,
'reboot': 0
}
}, {
upsert: true
}, function(err, result) {
if (err)
console.log("Error writing node type and version to database");
});
});
if (fwtype == 0xFFFF) {
// sensor does not know which type / blank EEPROM
// take predefined type (ideally selected in UI prior to connection of new sensor)
if (fwDefaultType == 0xFFFF)
throw new Error('No default sensor type defined');
fwtype = fwDefaultType;
}
db.collection('firmware', function(err, c) {
c.findOne({
$query: {
'type': fwtype
},
$orderby: {
'version': -1
}
}, function(err, result) {
if (err)
console.log('Error finding firmware for type ' + fwtype);
else if (!result)
console.log('No firmware found for type ' + fwtype);
else {
var payload = [];
pushWord(payload, result.type);
pushWord(payload, result.version);
pushWord(payload, result.blocks);
pushWord(payload, result.crc);
var sensor = NODE_SENSOR_ID;
var command = C_STREAM;
var acknowledge = 0; // no ack
var type = ST_FIRMWARE_CONFIG_RESPONSE;
var td = encode(destination, sensor, command, acknowledge, type, payload);
console.log('-> ' + td.toString());
gw.write(td);
}
});
});
}
function sendFirmwareResponse(destination, fwtype, fwversion, fwblock, db, gw) {
db.collection('firmware', function(err, c) {
c.findOne({
'type': fwtype,
'version': fwversion
}, function(err, result) {
if (err)
console.log('Error finding firmware version ' + fwversion + ' for type ' + fwtype);
var payload = [];
pushWord(payload, result.type);
pushWord(payload, result.version);
pushWord(payload, fwblock);
for (var i = 0; i < FIRMWARE_BLOCK_SIZE; i++)
payload.push(result.data[fwblock * FIRMWARE_BLOCK_SIZE + i]);
var sensor = NODE_SENSOR_ID;
var command = C_STREAM;
var acknowledge = 0; // no ack
var type = ST_FIRMWARE_RESPONSE;
var td = encode(destination, sensor, command, acknowledge, type, payload);
console.log('-> ' + td.toString());
gw.write(td);
});
});
}
function saveRebootRequest(destination, db) {
db.collection('node', function(err, c) {
c.update({
'id': destination
}, {
$set: {
'reboot': 1
}
}, function(err, result) {
if (err)
console.log("Error writing reboot request to database");
});
});
}
function checkRebootRequest(destination, db, gw) {
db.collection('node', function(err, c) {
c.find({
'id': destination
}, function(err, item) {
if (err)
console.log('Error checking reboot request');
else if (item.reboot == 1)
sendRebootMessage(destination, gw);
});
});
}
function sendRebootMessage(destination, gw) {
var sensor = NODE_SENSOR_ID;
var command = C_INTERNAL;
var acknowledge = 0; // no ack
var type = I_REBOOT;
var payload = "";
var td = encode(destination, sensor, command, acknowledge, type, payload);
console.log('-> ' + td.toString());
gw.write(td);
}
function appendData(str, db, gw) {
pos=0;
while (str.charAt(pos) != '\n' && pos < str.length) {
appendedString=appendedString+str.charAt(pos);
pos++;
}
if (str.charAt(pos) == '\n') {
rfReceived(appendedString.trim(), db, gw);
appendedString="";
}
if (pos < str.length) {
appendData(str.substr(pos+1,str.length-pos-1), db, gw);
}
}
function rfReceived(data, db, gw) {
if ((data != null) && (data != "")) {
console.log('<- ' + data);
// decoding message
var datas = data.toString().split(";");
var sender = +datas[0];
var sensor = +datas[1];
var command = +datas[2];
var ack = +datas[3];
var type = +datas[4];
var rawpayload="";
if (datas[5]) {
rawpayload = datas[5].trim();
}
var payload;
if (command == C_STREAM) {
payload = [];
for (var i = 0; i < rawpayload.length; i+=2)
payload.push(parseInt(rawpayload.substring(i, i + 2), 16));
} else {
payload = rawpayload;
}
// decision on appropriate response
switch (command) {
case C_PRESENTATION:
if (sensor == NODE_SENSOR_ID)
saveProtocol(sender, payload, db);
saveSensor(sender, sensor, type, db);
break;
case C_SET:
saveValue(sender, sensor, type, payload, db);
break;
case C_REQ:
break;
case C_INTERNAL:
switch (type) {
case I_BATTERY_LEVEL:
saveBatteryLevel(sender, payload, db);
break;
case I_TIME:
sendTime(sender, sensor, gw);
break;
case I_VERSION:
break;
case I_ID_REQUEST:
sendNextAvailableSensorId(db, gw);
break;
case I_ID_RESPONSE:
break;
case I_INCLUSION_MODE:
break;
case I_CONFIG:
sendConfig(sender, gw);
break;
case I_PING:
break;
case I_PING_ACK:
break;
case I_LOG_MESSAGE:
break;
case I_CHILDREN:
break;
case I_SKETCH_NAME:
saveSketchName(sender, payload, db);
break;
case I_SKETCH_VERSION:
saveSketchVersion(sender, payload, db);
break;
case I_REBOOT:
break;
}
break;
case C_STREAM:
switch (type) {
case ST_FIRMWARE_CONFIG_REQUEST:
var fwtype = pullWord(payload, 0);
var fwversion = pullWord(payload, 2);
sendFirmwareConfigResponse(sender, fwtype, fwversion, db, gw);
break;
case ST_FIRMWARE_CONFIG_RESPONSE:
break;
case ST_FIRMWARE_REQUEST:
var fwtype = pullWord(payload, 0);
var fwversion = pullWord(payload, 2);
var fwblock = pullWord(payload, 4);
sendFirmwareResponse(sender, fwtype, fwversion, fwblock, db, gw);
break;
case ST_FIRMWARE_RESPONSE:
break;
case ST_SOUND:
break;
case ST_IMAGE:
break;
}
break;
}
checkRebootRequest(sender, db, gw);
}
}
var dbc = require('mongodb').MongoClient;
dbc.connect('mongodb://' + dbAddress + ':' + dbPort + '/' + dbName, function(err, db) {
if(err) {
console.log('Error connecting to database at mongodb://' + dbAddress + ':' + dbPort + '/' + dbName);
return;
}
console.log('Connected to database at mongodb://' + dbAddress + ':' + dbPort + '/' + dbName);
db.createCollection('node', function(err, collection) { });
db.createCollection('firmware', function(err, collection) { });
// ToDo : check for new hex files / only load if new / get type and version from filename
for (var i = 0; i < fwSketches.length; i++)
loadFirmware(i, 1, fwSketches[i], db);
var gw;
if (gwType == 'Ethernet') {
gw = require('net').Socket();
gw.connect(gwPort, gwAddress);
gw.setEncoding('ascii');
gw.on('connect', function() {
console.log('connected to ethernet gateway at ' + gwAddress + ":" + gwPort);
}).on('data', function(rd) {
appendData(rd.toString(), db, gw);
}).on('end', function() {
console.log('disconnected from gateway');
}).on('error', function() {
console.log('connection error - trying to reconnect');
gw.connect(gwPort, gwAddress);
gw.setEncoding('ascii');
});
} else if (gwType == 'Serial') {
var SerialPort = require('serialport').SerialPort;
gw = new SerialPort(gwPort, { baudrate: gwBaud });
gw.open();
gw.on('open', function() {
console.log('connected to serial gateway at ' + gwPort);
}).on('data', function(rd) {
appendData(rd.toString(), db, gw);
}).on('end', function() {
console.log('disconnected from gateway');
}).on('error', function() {
console.log('connection error - trying to reconnect');
gw.open();
});
} else {
throw new Error('unknown Gateway type');
}
});
-306
View File
@@ -1,306 +0,0 @@
<h1>MySensors with NodeJS Controller and OTA bootloader on RPi</h1>
<h2>Hardware</h2>
<p>Headless Raspberry Pi model B with wired Ethernet</p>
<p>I'm connecting a monitor only for first time boot to get the IP address and then connect via Putty/xRDP</p>
<h2>Download and install latest Raspbian version on a fresh SD-Card</h2>
<p>Tutorials for this task are available on the internet (<a href="http://downloads.raspberrypi.org/raspbian_latest">download</a>, <a href="http://sourceforge.net/projects/win32diskimager">copy to SD</a>)</p>
<h2>Setup base parameters</h2>
<p>(adjust to your needs - here: RPi-IP: 10.0.1.5 / Router-IP: 10.0.1.1 / password: test / timezone: Europe,Berlin / hostname: mysensors)</p>
<ul>
<li>login as pi / raspberry
<li>sudo nano /etc/network/interfaces
<p><font color="#FF0000">#</font>iface eth0 inet dhcp<br/>
<font color="#FF0000">iface eth0 inet static<br/>
address 10.0.1.5<br/>
netmask 255.255.255.0<br/>
gateway 10.0.1.1<br/>
nameserver 10.0.1.1</font></p>
<li>sudo raspi-config
<ul>
<li>1. Expand Filesystem
<li>2. Change User Password<br/>
<p>test</p>
<li>4. Internationalisation Options
<ul>
<li>I2. Change Timezone
<p>Europe / Berlin</p>
</ul>
<li>8. Advanced Options
<ul>
<li>A2. Hostname
<p>mysensors</p>
<li>A3. Memory Split
<p>16</p>
</ul>
</ul>
<li>Finish
<li>Reboot
</ul>
<h2>Install latest updates</h2>
<ul>
<li>login as pi / test
<li>sudo apt-get update
<li>sudo apt-get upgrade
<li>sudo reboot
</ul>
<!--
<h2>Install Remote Desktop</h2>
<p>Optional… my RPi is headless and I use xRDP to connect from a Windows PC to run a graphical UI if needed (e.g. for Arduino IDE)</p>
<ul>
<li>login as pi / test
<li>sudo apt-get install xrdp
<li><i>to confirm it's working as expected try to connect from a remote computer to the RPi (e.g. from a Windows PC using the "Remote Desktop Connection" tool)</i>
</ul>
-->
<h2>Install NodeJS (here: latest stable version available for RPi: 0.10.28)</h2>
<ul>
<li>login as pi / test
<li>sudo mkdir /opt/node
<li>wget http://nodejs.org/dist/v0.10.28/node-v0.10.28-linux-arm-pi.tar.gz
<li>tar xvzf node-v0.10.28-linux-arm-pi.tar.gz
<li>rm node-v0.10.28-linux-arm-pi.tar.gz
<li>sudo cp -r node-v0.10.28-linux-arm-pi/* /opt/node
<li>rm -rf node-v0.10.28-linux-arm-pi/
<li>sudo nano /etc/profile
<p><font color="#FF0000">NODE_JS_HOME="/opt/node"<br/>
PATH="$PATH:$NODE_JS_HOME/bin"</font><br/>
export PATH</p>
<li>sudo reboot
<li><i>to confirm it's working as expected login as pi/test and run "node -v" which should return the installed node version (here: v0.10.28)</i>
</ul>
<h2>Install MongoDB</h2>
<ul>
<li>login as pi / test
<li>sudo useradd -c mongodb -d /home/mongodb -m -U mongodb
<li>sudo mkdir -p /opt/mongo/bin
<li>sudo mkdir /var/lib/mongodb
<li>sudo chown mongodb:nogroup /var/lib/mongodb
<li>sudo mkdir /var/log/mongodb
<li>sudo chown mongodb:nogroup /var/log/mongodb
<li>wget https://github.com/brice-morin/ArduPi/raw/master/mongodb-rpi/mongo/bin/mongo
<li>wget https://github.com/brice-morin/ArduPi/raw/master/mongodb-rpi/mongo/bin/mongod
<li>wget https://github.com/brice-morin/ArduPi/raw/master/mongodb-rpi/mongo/bin/mongodump
<li>wget https://github.com/brice-morin/ArduPi/raw/master/mongodb-rpi/mongo/bin/bsondump
<li>sudo mv mongo /opt/mongo/bin/
<li>sudo mv mongod /opt/mongo/bin/
<li>sudo mv mongodump /opt/mongo/bin/
<li>sudo mv bsondump /opt/mongo/bin/
<li>sudo chmod -R 775 /opt/mongo/bin
<li>sudo ln -s /opt/mongo/bin/mongod /usr/bin/mongod
<li>wget https://raw.githubusercontent.com/skrabban/mongo-nonx86/master/debian/mongodb.conf
<li>wget https://raw.githubusercontent.com/skrabban/mongo-nonx86/master/debian/init.d
<li>sudo mv mongodb.conf /etc/mongodb.conf
<li>sudo mv init.d /etc/init.d/mongodb
<li>sudo chmod 755 /etc/init.d/mongodb
<li>sudo update-rc.d mongodb defaults
<li>sudo reboot
<li><i>to confirm it's working as expected login as pi/test and run "/opt/mongo/bin/mongo" which should start the mongo shell (type "exit" to close the shell again)</i>
<li><i>if the RPi loses power without proper shutdown while mongod is running, you can't restart without manually removing the pid file first. To do so run “sudo rm /var/lib/mongodb/mongod.lock”</i>
</ul>
<!--
<h2>Install Arduino (and update to latest 1.0.5)</h2>
<ul>
<li>login as pi / test
<li>sudo apt-get install arduino
<li>wget http://arduino.googlecode.com/files/arduino-1.0.5-linux32.tgz
<li>tar zxvf arduino-1.0.5-linux32.tgz
<li>rm arduino-1.0.5-linux32.tgz
<li>cd arduino-1.0.5
<li>rm -rf hardware/tools
<li>sudo cp -ru lib /usr/share/arduino
<li>sudo cp -ru libraries /usr/share/arduino
<li>sudo cp -ru tools /usr/share/arduino
<li>sudo cp -ru hardware /usr/share/arduino
<li>sudo cp -ru examples /usr/share/doc/arduino-core
<li>sudo cp -ru reference /usr/share/doc/arduino-core
<li>cd ~
<li>rm -rf arduino-1.0.5
</ul>
<h2>Enable programming via GPIO UART (for serial gateway only)</h2>
<ul>
<li>login as pi / test
<li>wget https://github.com/SpellFoundry/avrdude-rpi/archive/master.zip
<li>unzip master.zip
<li>rm master.zip
<li>cd avrdude-rpi-master
<li>sudo cp autoreset /usr/bin
<li>sudo cp avrdude-autoreset /usr/bin
<li>sudo mv /usr/bin/avrdude /usr/bin/avrdude-original
<li>sudo ln -s /usr/bin/avrdude-autoreset /usr/bin/avrdude
<li>cd ..
<li>rm -rf avrdude-rpi-master
</ul>
-->
<h2>Install MySensors from Git branch development (here: password: test)</h2>
<ul>
<li>login as pi / test
<li>git clone https://github.com/mysensors/Arduino.git
<li>cd Arduino
<li>git checkout development
<li>cd NodeJsController
<li>npm install
<li><i>to confirm that the NodeJsController is working as expected and connects to the local mongo DB: "node NodeJsController.js" which should start the controller, connect to the database and run into an error connecting to the gateway. Stop node again via Ctrl-c</i>
</ul>
<h2>Start NodeJsController on boot</h2>
<ul>
<li>cd ~/Arduino/NodeJsController
<li>chmod 755 *.sh
<li>sudo cp MySensorsInitScript.sh /etc/init.d/mysensors
<li>sudo mkdir -p /usr/local/var/run
<li>sudo chmod -R 777 /usr/local/var/run
<li>sudo update-rc.d mysensors defaults
<li>sudo reboot
<li><i>to confirm that NodeJsController is starting at boot, check the log file /home/pi/logs/NodeJsController.log</i>
</ul>
<!--
<h2>Configure Arduino</h2>
<ul>
<li>start graphical UI (startx on RPi or via RDP)
<li>start Arduino IDE
<li>File - Preferences
<ul>
<li>set sketchbook location to /home/pi/Arduino
<li>enable verbose output for compilation
<li>enable verbose output for upload
</ul>
<li>Close Arduino IDE
</ul>
-->
<h1>Serial Gateway</h1>
<h2>Hardware</h2>
<ul>
<li>as described for the standard Serial Gateway - using the standard Serial Gateway sketch with DEBUG (in MyConfig.h) disabled
<li>I'm using the version with inclusion mode button and LEDs based on no Arduino but a plain ATmega328p on a breadboard
<li>Connect directly to the serial GPIO pins of the RPi:
<p>Arduino GND <-> Raspberry Pi GND (pin 6)<br/>
Arduino RX <-> Raspberry Pi TX (pin 8)<br/>
Arduino TX <-> Raspberry Pi RX (pin 10)<br/>
Arduino RESET <-> Raspberry Pi GPIO22 (pin 15)</p>
<li>Right now I'm using a separate power supply for the serial gateway instead of the 3.3V or 5V pins of the RPi because I ran in some issues with power consumption during programming
</ul>
<h2>Disable default use of RPi serial port</h2>
<ul>
<li>sudo nano /etc/inittab
<p><font color="#FF0000">#</font>T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100</p>
<li>sudo nano /boot/cmdline.txt - remove part of the file:
<p><font color="#FF0000"><strike>console=ttyAMA0,115200</strike></font></p>
<li>cd /dev
<li>sudo ln -s ttyAMA0 ttyUSB9
<li>sudo apt-get install minicom
<li>sudo reboot
</ul>
<!--
<h2>Flash (assuming that the ATmega has a valid Arduino bootloader installed)</h2>
<ul>
<li>login as pi / test
<li>start Arduino IDE
<ul>
<li>Tools - Board - Arduino - Arduino Pro or Pro Mini (5V, 16 MHz) w/ ATmega328
<li>Tools - Serial Port - /dev/ttyUSB9
<li>File - Sketchbook - SerialGateway
<li>File - Upload
</ul>
</ul>
-->
<h2>Setup NodeJsController</h2>
<ul>
<li>nano ~/Arduino/NodeJsController/NodeJsController.js
<p><font color="#FF0000">//</font>const gwType = 'Ethernet';<br/>
<font color="#FF0000">//</font>const gwAddress = '10.0.1.99';<br/>
<font color="#FF0000">//</font>const gwPort = 9999;<br/>
<font color="#FF0000"><strike>//</strike></font>const gwType = 'Serial';<br/>
<font color="#FF0000"><strike>//</strike></font>const gwPort = '<font color="#FF0000">/dev/ttyAMA0</font>';<br/>
<font color="#FF0000"><strike>//</strike></font>const gwBaud = 115200;</p>
<li>sudo service mysensors restart (at this point stop/restart doesn't work. use "sudo killall node" followed by "sudo service mysensors start" instead)
</ul>
<h1>Ethernet Gateway</h1>
<h2>Hardware</h2>
<ul>
<li>as described for the standard Ethernet Gateway - using the standard Ethernet Gateway sketch with DEBUG (in MyConfig.h) disabled
<li>I'm using the version with inclusion mode button and LEDs based on no Arduino but a plain ATmega328p on a breadboard and the ENC28J60 Ethernet module
<li>here: 10.0.1.99 / port: 9999
</ul>
<!--
<h2>Adjust Sourcecode (here: address 10.0.1.99 / port: 9999)</h2>
<ul>
<li>[I you are running into compilation errors due to missing files in the utility directory, do the following]
cp -R ~/Arduino/libraries/UIPEthernet/src/utility ~/Arduino/libraries/UIPEthernet/utility
<li>nano ~/Arduino/libraries/MySensors/MyConfig.h
<p><font color="#FF0000">//</font>#define DEBUG</p>
<li>start Arduino IDE
<ul>
<li>File - Sketchbook - EthernetGateway
<li>Edit source code
<p>#define IP_PORT 9999<br/>
IPAddress myIp (10, 0, 1, 99);</p>
<li>Optional : adjust source code to given hardware
<ul>
<li>Correct include for Ethernet module type (if not using ENC28J60)
<li>Switch MyGateway constructor if using inclusion mode button and LEDs
</ul>
<li>File - Save
</ul>
<h2>Flash (assuming that the ATmega has a valid Arduino bootloader installed)</h2>
<ul>
<li>Connect ethernet gateway to RPi (setup as above for serial gateway or via USB FTDI cable
<li>login as pi / test
<li>start Arduino IDE
<ul>
<li>Tools - Board - Arduino - Arduino Pro or Pro Mini (5V, 16 MHz) w/ ATmega328
<li>Tools - Serial Port - /dev/ttyUSB9 (or /dev/ttyUSB0 if connecting through USB cable)
<li>File - Sketchbook - EthernetGateway
<li>File - Upload
</ul>
</ul>
-->
<h2>Setup NodeJsController</h2>
<ul>
<li>nano ~/Arduino/NodeJsController/NodeJsController.js
<p>const gwAddress = '<font color="#FF0000">10.0.1.99</font>';</p>
<li>sudo service mysensors restart (at this point stop/restart doesn't work. use "sudo killall node" followed by "sudo service mysensors start" instead)
</ul>
<h1>Sensor Node Bootloader</h1>
<h2>Hardware</h2>
<ul>
<li>as described for a simple Sensor Node
<li>here: no Arduino but a plain ATmega328p on a breadboard
<li>using DallasTemperature example (one sensor connected to pin 3)
</ul>
<h2>Compile Bootloader (can be skipped but good to see if everything is working)</h2>
<ul>
<li>cd ~/Arduino/Bootloader
<li>make
<li><i>this should compile the bootloader and produce three files in the same directory: MyOtaBootloader.o / MyOtaBootloader.elf / MyOtaBootloader.hex</i>
</ul>
<h2>Flash Bootloader</h2>
<ul>
<li>cd ~/Arduino/Bootloader
<li>nano Makefile
<li>adjust avrdude settings to the available programmer. In my case (mySmartUSB MK3 programmer temporarily connected to RPi USB port):
<p>ISP_PORT = /dev/ttyUSB0<br/>
ISP_SPEED = 115200<br/>
ISP_PROTOCOL = stk500v2<br/>
ISP_MCU = m328p</p>
<li>make load
</ul>
<h1>End-to-End Test</h1>
<h2>Add Firmware to Controller (here: DallasTemperatureSensor)</h2>
<ul>
<li>nano ~/Arduino/NodeJsController/NodeJsController.js
<p>const fwHexFiles = [ <font color="#FF0000">'../libraries/MySensors/examples/DallasTemperatureSensor/DallasTemperatureSensor.ino'</font> ];<br/>
const fwDefaultType = <font color="#FF0000">0</font>;</p>
<li>sudo service mysensors restart (at this point stop/restart doesn't work. use "sudo killall node" followed by "sudo service mysensors start" instead)
</ul>
<h2>Flash Firmware OTA</h2>
<ul>
<li>tail -f ~/logs/NodeJsController.log
<li>Power up sensor node with OTA bootloader
</ul>
<h2>Check functionality</h2>
<ul>
<li>in addition to watching the log file (tail command above) you can connect to the mongo db using a client (e.g. <a href="http://robomongo.org">Robomongo</a>) and check if new values are written to the database as a real end-to-end test once the bootloader finished uploading the firmware
</ul>
-28
View File
@@ -1,28 +0,0 @@
var NodeIdToReboot = 1;
const dbAddress = '127.0.0.1';
const dbPort = 27017;
const dbName = 'MySensorsDb';
var dbc = require('mongodb').MongoClient;
dbc.connect('mongodb://' + dbAddress + ':' + dbPort + '/' + dbName, function(err, db) {
if (err) {
console.log('Error connecting to database at mongodb://' + dbAddress + ':' + dbPort + '/' + dbName);
} else {
db.collection('node', function(err, c) {
c.update({
'id': NodeIdToReboot
}, {
$set: {
'reboot': 1
}
}, function(err, result) {
if (err)
console.log("Error writing reboot request to database");
else
console.log("Reboot request saved to database");
});
});
}
});
-11
View File
@@ -1,11 +0,0 @@
{
"name": "NodeJsController",
"version": "0.0.1",
"description": "MySensors Controller based on Node JS",
"dependencies": {
"mongodb": "~1.4.7",
"net": "~1.0.2",
"requestify": "^0.1.16",
"serialport": "^1.4.5"
}
}
+19 -1
View File
@@ -1,6 +1,24 @@
Arduino
=======
MySensors Arduino Library v1.4
MySensors Arduino Library v2.0.0-beta
Please visit www.mysensors.org for more information
Doxygen
-------
[master](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/branch/master/Doxygen_HTML/index.html) [development](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/branch/development/Doxygen_HTML/index.html)
CI statuses
-----------
Current build status of master branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/master/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/master/)
Current build status of development branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/development/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/development/)
Current build status of master branch (nightly build): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/master/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/master/)
Current build status of development branch (nightly build): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/development/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/development/)
Current build status of master branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/master/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/master/)
Current build status of development branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/)
Vendored Executable
+377
View File
@@ -0,0 +1,377 @@
#!/bin/bash
# This version is heavily based on the work of mz-fuzzy (https://github.com/mz-fuzzy)
# adapted to work with MySensors project.
# Original work: https://github.com/TMRh20/RF24/blob/master/configure
function help {
cat <<EOF
configure script for MySensors gateway.
Options:
Help:
-h, --help print this message
Building options:
--soc=[BCM2835|BCM2836|AM33XX|A10|A13|A20|H3]
SoC type to be used. [configure autodetected]
--cpu-flags=<CXXFLAGS> CPU defining/optimizing flags to be used. [configure autodetected]
--extra-cxxflags=<CXXFLAGS> Extra C flags passed to C/C++ compilation. []
--extra-ldflags=<LDFLAGS> Extra C flags passed to linking. []
--cxx_compiler=<CXX> C++ compiler [arm-linux-gnueabihf-g++][g++]
--no-clean Don't clean previous build artifacts.
Installation options:
--prefix=<PREFIX> Installation prefix path. [/usr/local]
--gateway-dir=<DIR> Gateway files installation directory. [PREFIX/bin]
MySensors options:
--my-debug=[enable|disable] Enables or disables MySensors core debugging. [enable]
--my-config-file=<FILE> Config file path. [/etc/mysensors.dat]
--my-gateway=[none|ethernet|serial|mqtt]
Gateway type, set to none to disable gateway feature. [ethernet]
--my-controller-url-address=<URL>
Controller or MQTT broker url.
--my-controller-ip-address=<IP>
Controller or MQTT broker ip.
--my-port=<PORT> The port to keep open on gateway mode.
If gateway is set to mqtt, it sets the broker port.
--my-serial-port=<PORT> Serial port. [/dev/ttyACM0]
--my-serial-baudrate=<BAUD> Serial baud rate. [115200]
--my-serial-is-pty Set the serial port to be a pseudo terminal. Use this if you want
to connect to a controller running on the same device.
--my-serial-pty=<NAME> Symlink name for the PTY device. [/dev/ttyMySensorsGateway]
--my-serial-groupname=<GROUP>
Grant access to the specified system group for the serial device.
--my-mqtt-client-id=<ID> MQTT client id.
--my-mqtt-publish-topic-prefix=<PREFIX>
MQTT publish topic prefix.
--my-mqtt-subscribe-topic-prefix=<PREFIX>
MQTT subscribe topic prefix.
--my-radio=[none|nrf24] Radio type, set to none to disable radio feature. [rf24]
--my-rf24-channel=<0-125> RF channel for the sensor net, 0-125. [76]
--my-rf24-pa-level=[RF24_PA_MAX|RF24_PA_LOW]
RF24 PA level. [RF24_PA_MAX]
--my-rf24-irq-pin=<PIN> Pin number connected to nRF24L01 IRQ pin.
--my-rx-message-buffer-size=<SIZE>
Buffer size for incoming messages when using rf24 interrupts. [20]
--my-leds-err-pin=<PIN> Error LED pin.
--my-leds-rx-pin=<PIN> Receive LED pin.
--my-leds-tx-pin=<PIN> Transmit LED pin.
--my-leds-blinking-inverse Inverse the blinking feature.
EOF
}
function die {
echo "[ERROR] $1"
exit $2
}
function detect_rpi_revision {
# get PI Revision from cpuinfo
local pirev=$(eval "cat /proc/cpuinfo 2>/dev/null | grep Revision | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$$//'")
echo ${pirev}
}
function detect_machine {
local cpu=$(eval "uname -m 2>/dev/null")
local machine=$(eval "cat /sys/firmware/devicetree/base/model 2>/dev/null")
local hardware=$(eval "grep sunxi_platform /sys/class/sunxi_info/sys_info 2>/dev/null | sed 's/^.*: \(.*\)$/\1/'")
if [ -z "$hardware" ]; then
local hardware=$(eval "grep Hardware /proc/cpuinfo 2>/dev/null | sed 's/^.*: \(.*\)$/\1/'")
fi
local soc="unknown"
local tp="unknown"
local rev="unknown"
if [ -z "$cpu" ]; then
cpu="unknown"
fi
case $hardware in
BCM2708)
soc="BCM2835"
if [[ $machine == "Raspberry"* ]]; then
tp="RPi"
rev=($(detect_rpi_revision))
fi
;;
BCM2709)
soc="BCM2836"
if [[ $machine == "Raspberry"* ]]; then
rev=($(detect_rpi_revision))
if [[ $rev == "a02082" || $rev == "a22082" ]]; then
tp="RPi3"
else
tp="Rpi2"
fi
fi
;;
sun4i|Sun4iw1p1)
soc="A10"
;;
sun5i|Sun4iw2p1)
soc="A13"
;;
Sun4iw2p2)
soc="A12"
;;
Sun4iw2p3)
soc="A10s"
;;
sun6i|Sun8iw1p1)
soc="A31"
;;
Sun8iw1p2)
soc="A31s"
;;
sun7i|Sun8iw2p1)
soc="A20"
if [[ $machine == "Banana Pi"* ]]; then
tp="BananaPi"
elif [[ $machine == "Banana Pro"* ]]; then
tp="BananaPro"
fi
;;
sun8i|Sun8iw7p1)
soc="H3"
;;
Sun8iw3p1)
soc="A23"
;;
Sun8iw5p1)
soc="A33"
;;
Sun8iw6p1)
soc="A83t"
;;
sun9i|Sun9iw1p1)
soc="A80"
;;
Sun9iw1p2)
soc="A80t"
;;
sun50i|Sun50iw1p1)
soc="A64"
;;
'Generic AM33XX'*)
soc="AM33XX"
;;
*)
soc="unknown"
esac
echo "${soc} ${tp} ${cpu} ${rev}"
}
function gcc_cpu_flags {
local soc=$1
case $soc in
BCM2835)
flags="-march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -DLINUX_ARCH_RASPBERRYPI"
;;
BCM2836)
flags="-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -DLINUX_ARCH_RASPBERRYPI"
;;
AM33XX)
flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard"
;;
A10)
flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard"
;;
A13)
flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard"
;;
A20)
flags="-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard"
;;
H3)
flags="-march=armv8-a -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard"
;;
*)
flags=""
esac
echo ${flags}
}
# Default values
debug=enable
gateway_type=ethernet
radio_type=nrf24
params="SOC CXXFLAGS LDFLAGS PREFIX CXX GATEWAY_DIR INIT_SYSTEM"
for opt do
if [ "$opt" = "-h" ] || [ "$opt" = "--help" ]; then
help
exit 0
fi
optarg="${opt#*=}"
case "$opt" in
--soc=*)
SOC="$optarg"
;;
--cpu-flags=*)
CPUFLAGS="$optarg"
;;
--extra-cxxflags=*)
CXXFLAGS="$optarg"
;;
--extra-ldflags=*)
LDFLAGS="$optarg"
;;
--cxx_compiler=*)
CXX="$optarg"
;;
--no-clean*)
NO_CLEAN="1"
;;
--prefix=*)
PREFIX="$optarg"
;;
--gateway-dir=*)
GATEWAY_DIR="$optarg"
;;
--my-debug=*)
debug=${optarg}
;;
--my-gateway=*)
gateway_type=${optarg}
;;
--my-config-file=*)
CXXFLAGS="-DMY_LINUX_CONFIG_FILE=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-radio=*)
radio_type=${optarg}
;;
--my-serial-port=*)
CXXFLAGS="-DMY_LINUX_SERIAL_PORT=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-serial-baudrate=*)
CXXFLAGS="-DMY_BAUD_RATE=${optarg} $CXXFLAGS"
;;
--my-serial-is-pty*)
CXXFLAGS="-DMY_IS_SERIAL_PTY $CXXFLAGS"
;;
--my-serial-pty=*)
CXXFLAGS="-DMY_LINUX_SERIAL_PTY=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-serial-groupname=*)
CXXFLAGS="-DMY_LINUX_SERIAL_GROUPNAME=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-rf24-channel=*)
CXXFLAGS="-DMY_RF24_CHANNEL=${optarg} $CXXFLAGS"
;;
--my-rf24-pa-level=*)
CXXFLAGS="-DMY_RF24_PA_LEVEL=${optarg} $CXXFLAGS"
;;
--my-controller-url-address=*)
CXXFLAGS="-DMY_CONTROLLER_URL_ADDRESS=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-controller-ip-address=*)
controller_ip=`echo ${optarg//./,}`
CXXFLAGS="-DMY_CONTROLLER_IP_ADDRESS=${controller_ip} $CXXFLAGS"
;;
--my-port=*)
CXXFLAGS="-DMY_PORT=${optarg} $CXXFLAGS"
;;
--my-mqtt-client-id=*)
CXXFLAGS="-DMY_MQTT_CLIENT_ID=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-mqtt-publish-topic-prefix=*)
CXXFLAGS="-DMY_MQTT_PUBLISH_TOPIC_PREFIX=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-mqtt-subscribe-topic-prefix=*)
CXXFLAGS="-DMY_MQTT_SUBSCRIBE_TOPIC_PREFIX=\\\"${optarg}\\\" $CXXFLAGS"
;;
--my-rf24-irq-pin=*)
CXXFLAGS="-DMY_RX_MESSAGE_BUFFER_FEATURE -DMY_RF24_IRQ_PIN=${optarg} $CXXFLAGS"
;;
--my-rx-message-buffer-size=*)
CXXFLAGS="-DMY_RX_MESSAGE_BUFFER_SIZE=${optarg} $CXXFLAGS"
;;
--my-leds-err-pin=*)
CXXFLAGS="-DMY_DEFAULT_ERR_LED_PIN=${optarg} $CXXFLAGS"
;;
--my-leds-rx-pin=*)
CXXFLAGS="-DMY_DEFAULT_RX_LED_PIN=${optarg} $CXXFLAGS"
;;
--my-leds-tx-pin=*)
CXXFLAGS="-DMY_DEFAULT_TX_LED_PIN=${optarg} $CXXFLAGS"
;;
--my-leds-blinking-inverse*)
CXXFLAGS="-DMY_WITH_LEDS_BLINKING_INVERSE $CXXFLAGS"
;;
*)
echo "[WARNING] Unknown option detected:$opt, ignored"
;;
esac
done
PREFIX=${PREFIX:-/usr/local}
GATEWAY_DIR=${GATEWAY_DIR:-${PREFIX}/bin}
CXX=${CXX:-g++}
if [ -z "${SOC}" ]; then
echo "[SECTION] Detecting target machine."
info=($(detect_machine))
SOC=${info[0]}
TYPE=${info[1]}
CPU=${info[2]}
REV=${info[3]}
echo "[OK] machine detected: SoC=${SOC}, Type=${TYPE}, CPU=${CPU}, REV=${REV}."
fi
if [ -z "${CPUFLAGS}" ]; then
CPUFLAGS=$(gcc_cpu_flags $SOC)
fi
CXXFLAGS="$CPUFLAGS -Ofast -g -Wall -Wextra $CXXFLAGS"
CFLAGS="-Ofast -g -Wall -Wextra $CFLAGS"
if [[ $TYPE == "RPi2" || $TYPE == "RPi3" || $REV == "0010" ]]; then
CXXFLAGS+="-D__RPI_BPLUS"
fi
if [[ ${debug} == "enable" ]]; then
CXXFLAGS="-DMY_DEBUG $CXXFLAGS"
fi
if [[ ${gateway_type} == "ethernet" ]]; then
CXXFLAGS="-DMY_GATEWAY_LINUX $CXXFLAGS"
elif [[ ${gateway_type} == "serial" ]]; then
CXXFLAGS="-DMY_GATEWAY_SERIAL $CXXFLAGS"
elif [[ ${gateway_type} == "mqtt" ]]; then
CXXFLAGS="-DMY_GATEWAY_LINUX -DMY_GATEWAY_MQTT_CLIENT $CXXFLAGS"
fi
if [[ ${radio_type} == "nrf24" ]]; then
CXXFLAGS="-DMY_RADIO_NRF24 $CXXFLAGS"
fi
LDFLAGS="-pthread $LDFLAGS"
if [ -x /usr/bin/systemctl ] || [ -x /bin/systemctl ]; then
INIT_SYSTEM=systemd
echo "[OK] init system detected: systemd"
elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then
INIT_SYSTEM=sysvinit
echo "[OK] init system detected: sysvinit"
else
echo "[FAILED] unknown init system"
fi
echo "[SECTION] Saving configuration."
echo -n "" > Makefile.inc
for param in ${params}; do
if [[ ${!param} ]]; then
echo "${param}=${!param}" >> Makefile.inc
fi
done
if [ -z "${NO_CLEAN}" ]; then
echo "[SECTION] Cleaning previous builds."
make clean >/dev/null
fi
echo "[OK] Finished."
+82
View File
@@ -0,0 +1,82 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyCapabilities_h
#define MyCapabilities_h
#if defined(MY_DISABLE_REMOTE_RESET)
#define MY_CAP_RESET "N"
#else
#define MY_CAP_RESET "R"
#endif
#if defined(MY_OTA_FIRMWARE_FEATURE)
#define MY_CAP_OTA_FW "O"
#else
#define MY_CAP_OTA_FW "N"
#endif
#if defined(MY_RADIO_NRF24)
#define MY_CAP_RADIO "N"
#elif defined(MY_RADIO_RFM69)
#define MY_CAP_RADIO "R"
#elif defined(MY_RS485)
#define MY_CAP_RADIO "S"
#else
#define MY_CAP_RADIO "-"
#endif
#if defined(MY_GATEWAY_FEATURE)
#define MY_CAP_TYPE "G"
#elif defined(MY_REPEATER_FEATURE)
#define MY_CAP_TYPE "R"
#else
#define MY_CAP_TYPE "N"
#endif
#if defined(ARDUINO_ARCH_SAMD)
#define MY_CAP_ARCH "S"
#elif defined(ARDUINO_ARCH_ESP8266)
#define MY_CAP_ARCH "E"
#elif defined(ARDUINO_ARCH_AVR)
#define MY_CAP_ARCH "A"
#else
#define MY_CAP_ARCH "-"
#endif
#if defined(MY_SIGNING_ATSHA204)
#define MY_CAP_SIGN "A"
#elif defined(MY_SIGNING_SOFT)
#define MY_CAP_SIGN "S"
#else
#define MY_CAP_SIGN "-"
#endif
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
#define MY_CAP_RXBUF "Q"
#else
#define MY_CAP_RXBUF "-"
#endif
#define MY_CAPABILITIES MY_CAP_RESET MY_CAP_RADIO MY_CAP_OTA_FW MY_CAP_TYPE MY_CAP_ARCH MY_CAP_SIGN MY_CAP_RXBUF
#endif /* MyCapabilities_h */
+89
View File
@@ -0,0 +1,89 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyEepromAddresses.h
* @brief Eeprom addresses for MySensors library data
*
* @defgroup MyEepromAddressesgrp MyEepromAddresses
* @ingroup internals
* @{
*
*/
#ifndef MyEepromAddresses_h
#define MyEepromAddresses_h
// EEPROM variable sizes, in bytes
#define SIZE_NODE_ID (1) //!< Size node ID
#define SIZE_PARENT_NODE_ID (1) //!< Size parent node ID
#define SIZE_DISTANCE (1) //!< Size GW distance
#define SIZE_ROUTES (256) //!< Size routing table
#define SIZE_CONTROLLER_CONFIG (24) //!< Size controller config
#define SIZE_FIRMWARE_TYPE (2) //!< Size firmware type
#define SIZE_FIRMWARE_VERSION (2) //!< Size firmware version
#define SIZE_FIRMWARE_BLOCKS (2) //!< Size firmware blocks
#define SIZE_FIRMWARE_CRC (2) //!< Size firmware CRC
#define SIZE_SIGNING_REQUIREMENT_TABLE (32) //!< Size signing requirement table
#define SIZE_WHITELIST_REQUIREMENT_TABLE (32) //!< Size whitelist requirement table
#define SIZE_SIGNING_SOFT_HMAC_KEY (32) //!< Size soft signing HMAC key
#define SIZE_SIGNING_SOFT_SERIAL (9) //!< Size soft signing serial
#define SIZE_RF_ENCRYPTION_AES_KEY (16) //!< Size RF AES encryption key
#define SIZE_NODE_LOCK_COUNTER (1) //!< Size node lock counter
/** @brief EEPROM start address */
#define EEPROM_START 0
/** @brief Address node ID */
#define EEPROM_NODE_ID_ADDRESS EEPROM_START
/** @brief Address parent node ID */
#define EEPROM_PARENT_NODE_ID_ADDRESS (EEPROM_NODE_ID_ADDRESS + SIZE_NODE_ID)
/** @brief Address distance to GW */
#define EEPROM_DISTANCE_ADDRESS (EEPROM_PARENT_NODE_ID_ADDRESS + SIZE_PARENT_NODE_ID)
/** @brief Address routing table */
#define EEPROM_ROUTES_ADDRESS (EEPROM_DISTANCE_ADDRESS + SIZE_DISTANCE)
/** @brief Address configuration bytes sent by controller */
#define EEPROM_CONTROLLER_CONFIG_ADDRESS (EEPROM_ROUTES_ADDRESS + SIZE_ROUTES)
/** @brief Address firmware type */
#define EEPROM_FIRMWARE_TYPE_ADDRESS (EEPROM_CONTROLLER_CONFIG_ADDRESS + SIZE_CONTROLLER_CONFIG)
/** @brief Address firmware version */
#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS + SIZE_FIRMWARE_TYPE)
/** @brief Address firmware blocks */
#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS + SIZE_FIRMWARE_VERSION)
/** @brief Address firmware CRC */
#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS + SIZE_FIRMWARE_BLOCKS)
/** @brief Address signing requirement table */
#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS + SIZE_FIRMWARE_CRC)
/** @brief Address whitelist requirement table */
#define EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS + SIZE_SIGNING_REQUIREMENT_TABLE)
/** @brief Address soft signing HMAC key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS + SIZE_WHITELIST_REQUIREMENT_TABLE)
/** @brief Address soft signing serial key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS + SIZE_SIGNING_SOFT_HMAC_KEY)
/** @brief Address RF AES encryption key. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS + SIZE_SIGNING_SOFT_SERIAL)
/** @brief Address node lock couner. This is set with @ref SecurityPersonalizer.ino */
#define EEPROM_NODE_LOCK_COUNTER (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS + SIZE_RF_ENCRYPTION_AES_KEY)
/** @brief First free address for sketch static configuration */
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER + SIZE_NODE_LOCK_COUNTER)
#endif // MyEepromAddresses_h
/** @}*/
+69
View File
@@ -0,0 +1,69 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyGatewayTransport.h"
extern bool transportSendRoute(MyMessage &message);
// global variables
extern MyMessage _msg;
extern MyMessage _msgTmp;
extern NodeConfig _nc;
inline void gatewayTransportProcess() {
if (gatewayTransportAvailable()) {
_msg = gatewayTransportReceive();
if (_msg.destination == GATEWAY_ADDRESS) {
// Check if sender requests an ack back.
if (mGetRequestAck(_msg)) {
// Copy message
_msgTmp = _msg;
mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop)
mSetAck(_msgTmp, true);
_msgTmp.sender = _nc.nodeId;
_msgTmp.destination = _msg.sender;
gatewayTransportSend(_msgTmp);
}
if (mGetCommand(_msg) == C_INTERNAL) {
if (_msg.type == I_VERSION) {
// Request for version. Create the response
gatewayTransportSend(buildGw(_msgTmp, I_VERSION).set(MYSENSORS_LIBRARY_VERSION));
#ifdef MY_INCLUSION_MODE_FEATURE
} else if (_msg.type == I_INCLUSION_MODE) {
// Request to change inclusion mode
inclusionModeSet(atoi(_msg.data) == 1);
#endif
} else {
_processInternalMessages();
}
} else {
// Call incoming message callback if available
if (receive) {
receive(_msg);
}
}
} else {
#if defined(MY_RADIO_FEATURE)
transportSendRoute(_msg);
#endif
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyGatewayTransport_h
#define MyGatewayTransport_h
#include "MyProtocol.h"
#include "MySensorsCore.h"
// Common gateway functions
void gatewayTransportProcess();
// Gateway "interface" functions
/**
* initialize the driver
*/
bool gatewayTransportInit();
/**
* Send message to controller
*/
bool gatewayTransportSend(MyMessage &message);
/*
* Check if a new message is available from controller
*/
bool gatewayTransportAvailable();
/*
* Pick up last message received from controller
*/
MyMessage& gatewayTransportReceive();
#endif /* MyGatewayTransportEthernet_h */
+450
View File
@@ -0,0 +1,450 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Tomas Hozza <thozza@gmail.com>
* Copyright (C) 2015 Tomas Hozza
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyGatewayTransport.h"
// global variables
extern MyMessage _msgTmp;
#if defined(MY_CONTROLLER_IP_ADDRESS)
IPAddress _ethernetControllerIP(MY_CONTROLLER_IP_ADDRESS);
#endif
#if defined(MY_IP_ADDRESS)
IPAddress _ethernetGatewayIP(MY_IP_ADDRESS);
#endif
uint8_t _ethernetGatewayMAC[] = { MY_MAC_ADDRESS };
uint16_t _ethernetGatewayPort = MY_PORT;
MyMessage _ethernetMsg;
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
typedef struct
{
char string[MY_GATEWAY_MAX_RECEIVE_LENGTH];
uint8_t idx;
} inputBuffer;
#if defined(MY_GATEWAY_ESP8266)
// Some re-defines to make code more readable below
#define EthernetServer WiFiServer
#define EthernetClient WiFiClient
#define EthernetUDP WiFiUDP
#if defined(MY_IP_ADDRESS)
IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS);
IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS);
#endif
#endif
#if defined(MY_USE_UDP)
EthernetUDP _ethernetServer;
#elif defined(MY_GATEWAY_LINUX)
EthernetServer _ethernetServer(_ethernetGatewayPort, MY_GATEWAY_MAX_CLIENTS);
#elif defined(MY_GATEWAY_CLIENT_MODE)
// Nothing to do here
#else
EthernetServer _ethernetServer(_ethernetGatewayPort);
#endif
#if defined(MY_GATEWAY_CLIENT_MODE)
static EthernetClient client = EthernetClient();
static inputBuffer inputString;
#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX)
static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS];
static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS];
static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS];
#else
static EthernetClient client = EthernetClient();
static inputBuffer inputString;
#endif
#ifndef MY_IP_ADDRESS
void gatewayTransportRenewIP();
#endif
// On W5100 boards with SPI_EN exposed we can use the real SPI bus together with radio
// (if we enable it during usage)
#ifdef MY_W5100_SPI_EN
void _w5100_spi_en(bool enable)
{
if (enable)
{
// Pull up pin
pinMode(MY_W5100_SPI_EN, INPUT);
digitalWrite(MY_W5100_SPI_EN, HIGH);
}
else
{
// Ground pin
pinMode(MY_W5100_SPI_EN, OUTPUT);
digitalWrite(MY_W5100_SPI_EN, LOW);
}
}
#else
#define _w5100_spi_en(x)
#endif
bool gatewayTransportInit() {
_w5100_spi_en(true);
#if defined(MY_GATEWAY_ESP8266)
#if defined(MY_ESP8266_SSID)
// Turn off access point
WiFi.mode (WIFI_STA);
#if defined(MY_ESP8266_HOSTNAME)
WiFi.hostname(MY_ESP8266_HOSTNAME);
#endif
#ifdef MY_IP_ADDRESS
WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp);
#endif
(void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
MY_SERIALDEVICE.print(F("."));
yield();
}
MY_SERIALDEVICE.print(F("IP: "));
MY_SERIALDEVICE.println(WiFi.localIP());
#endif
#elif defined(MY_GATEWAY_LINUX)
// Nothing to do here
#else
#ifdef MY_IP_ADDRESS
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP);
#else
// Get IP address from DHCP
if (!Ethernet.begin(_ethernetGatewayMAC)) {
MY_SERIALDEVICE.print(F("DHCP FAILURE..."));
_w5100_spi_en(false);
return false;
}
#endif
MY_SERIALDEVICE.print(F("IP: "));
MY_SERIALDEVICE.println(Ethernet.localIP());
// give the Ethernet interface a second to initialize
delay(1000);
#endif /* MY_GATEWAY_ESP8266 */
#ifdef MY_USE_UDP
_ethernetServer.begin(_ethernetGatewayPort);
#elif defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif
debug(PSTR("Eth: connect\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
_w5100_spi_en(true);
presentNode();
} else {
client.stop();
debug(PSTR("Eth: Failed to connect\n"));
}
#else
#if defined(MY_GATEWAY_LINUX) && defined(MY_IP_ADDRESS)
_ethernetServer.begin(_ethernetGatewayIP);
#else
// we have to use pointers due to the constructor of EthernetServer
_ethernetServer.begin();
#endif
#endif /* USE_UDP */
_w5100_spi_en(false);
return true;
}
bool gatewayTransportSend(MyMessage &message)
{
int nbytes = 0;
char *_ethernetMsg = protocolFormat(message);
setIndication(INDICATION_GW_TX);
_w5100_spi_en(true);
#if defined(MY_GATEWAY_CLIENT_MODE)
#if defined(MY_USE_UDP)
#if defined(MY_CONTROLLER_URL_ADDRESS)
_ethernetServer.beginPacket(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
#else
_ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT);
#endif
_ethernetServer.write(_ethernetMsg, strlen(_ethernetMsg));
// returns 1 if the packet was sent successfully
nbytes = _ethernetServer.endPacket();
#else
if (!client.connected()) {
client.stop();
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif
debug(PSTR("Eth: connect\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
_w5100_spi_en(true);
presentNode();
} else {
// connecting to the server failed!
debug(PSTR("Eth: Failed to connect\n"));
_w5100_spi_en(false);
return false;
}
}
nbytes = client.write(_ethernetMsg, strlen(_ethernetMsg));
#endif
#else
// Send message to connected clients
#if defined(MY_GATEWAY_ESP8266)
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++)
{
if (clients[i] && clients[i].connected())
{
nbytes += clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg));
}
}
#else
nbytes = _ethernetServer.write(_ethernetMsg);
#endif
#endif /* MY_GATEWAY_CLIENT_MODE */
_w5100_spi_en(false);
return (nbytes > 0);
}
#if (defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX)) && !defined(MY_GATEWAY_CLIENT_MODE)
bool _readFromClient(uint8_t i) {
while (clients[i].connected() && clients[i].available()) {
char inChar = clients[i].read();
if (inputString[i].idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
// if newline then command is complete
if (inChar == '\n' || inChar == '\r') {
// Add string terminator and prepare for the next message
inputString[i].string[inputString[i].idx] = 0;
debug(PSTR("Client %d: %s\n"), i, inputString[i].string);
inputString[i].idx = 0;
if (protocolParse(_ethernetMsg, inputString[i].string)) {
return true;
}
} else {
// add it to the inputString:
inputString[i].string[inputString[i].idx++] = inChar;
}
} else {
// Incoming message too long. Throw away
debug(PSTR("Client %d: Message too long\n"), i);
inputString[i].idx = 0;
// Finished with this client's message. Next loop() we'll see if there's more to read.
break;
}
}
return false;
}
#else
bool _readFromClient() {
while (client.connected() && client.available()) {
char inChar = client.read();
if (inputString.idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
// if newline then command is complete
if (inChar == '\n' || inChar == '\r') {
// Add string terminator and prepare for the next message
inputString.string[inputString.idx] = 0;
debug(PSTR("Eth: %s\n"), inputString.string);
inputString.idx = 0;
if (protocolParse(_ethernetMsg, inputString.string)) {
return true;
}
} else {
// add it to the inputString:
inputString.string[inputString.idx++] = inChar;
}
} else {
// Incoming message too long. Throw away
debug(PSTR("Eth: Message too long\n"));
inputString.idx = 0;
// Finished with this client's message. Next loop() we'll see if there's more to read.
break;
}
}
return false;
}
#endif
bool gatewayTransportAvailable()
{
_w5100_spi_en(true);
#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100)
// renew IP address using DHCP
gatewayTransportRenewIP();
#endif
#ifdef MY_USE_UDP
int packet_size = _ethernetServer.parsePacket();
if (packet_size) {
//debug(PSTR("UDP packet available. Size:%d\n"), packet_size);
#if defined(MY_GATEWAY_ESP8266)
_ethernetServer.read(inputString[0].string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
inputString[0].string[packet_size] = 0;
debug(PSTR("UDP packet received: %s\n"), inputString[0].string);
const bool ok = protocolParse(_ethernetMsg, inputString[0].string);
#else
_ethernetServer.read(inputString.string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
inputString.string[packet_size] = 0;
debug(PSTR("UDP packet received: %s\n"), inputString.string);
_w5100_spi_en(false);
const bool ok = protocolParse(_ethernetMsg, inputString.string);
#endif
if (ok)
{
setIndication(INDICATION_GW_RX);
}
return ok;
}
#elif defined(MY_GATEWAY_CLIENT_MODE)
if (!client.connected()) {
client.stop();
#if defined(MY_CONTROLLER_URL_ADDRESS)
if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
#else
if (client.connect(_ethernetControllerIP, MY_PORT)) {
#endif
debug(PSTR("Eth: connect\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
_w5100_spi_en(true);
presentNode();
} else {
debug(PSTR("Eth: Failed to connect\n"));
_w5100_spi_en(false);
return false;
}
}
if (_readFromClient()) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
#else
#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX)
// ESP8266: Go over list of clients and stop any that are no longer connected.
// If the server has a new client connection it will be assigned to a free slot.
bool allSlotsOccupied = true;
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (!clients[i].connected()) {
if (clientsConnected[i]) {
debug(PSTR("Client %d disconnected\n"), i);
clients[i].stop();
}
//check if there are any new clients
if (_ethernetServer.hasClient()) {
clients[i] = _ethernetServer.available();
inputString[i].idx = 0;
debug(PSTR("Client %d connected\n"), i);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
}
}
bool connected = clients[i].connected();
clientsConnected[i] = connected;
allSlotsOccupied &= connected;
}
if (allSlotsOccupied && _ethernetServer.hasClient()) {
//no free/disconnected spot so reject
debug(PSTR("No free slot available\n"));
EthernetClient c = _ethernetServer.available();
c.stop();
}
// Loop over clients connect and read available data
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
if (_readFromClient(i)) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
}
#else
// W5100/ENC module does not have hasClient-method. We can only serve one client at the time.
EthernetClient newclient = _ethernetServer.available();
// if a new client connects make sure to dispose any previous existing sockets
if (newclient) {
if (client != newclient) {
client.stop();
client = newclient;
debug(PSTR("Eth: connect\n"));
_w5100_spi_en(false);
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
_w5100_spi_en(true);
presentNode();
}
}
if (client) {
if (!client.connected()) {
debug(PSTR("Eth: disconnect\n"));
client.stop();
} else {
if (_readFromClient()) {
setIndication(INDICATION_GW_RX);
_w5100_spi_en(false);
return true;
}
}
}
#endif /* MY_GATEWAY_ESP8266 */
#endif
_w5100_spi_en(false);
return false;
}
MyMessage& gatewayTransportReceive()
{
// Return the last parsed message
return _ethernetMsg;
}
#if !defined(MY_IP_ADDRESS) && !defined(MY_GATEWAY_ESP8266) && !defined(MY_GATEWAY_LINUX)
void gatewayTransportRenewIP()
{
/* renew/rebind IP address
0 - nothing happened
1 - renew failed
2 - renew success
3 - rebinf failed
4 - rebind success
*/
static unsigned long next_time = hwMillis() + MY_IP_RENEWAL_INTERVAL;
unsigned long now = hwMillis();
// http://playground.arduino.cc/Code/TimingRollover
if ((long)(now - next_time) < 0)
return;
if (Ethernet.maintain() & ~(0x06)) {
debug(PSTR("IP was not renewed correctly\n"));
/* Error occured -> IP was not renewed */
return;
}
_w5100_spi_en(false);
next_time = now + MY_IP_RENEWAL_INTERVAL;
}
#endif
+166
View File
@@ -0,0 +1,166 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
// Topic structure: MY_MQTT_PUBLISH_TOPIC_PREFIX/NODE-ID/SENSOR-ID/CMD-TYPE/ACK-FLAG/SUB-TYPE
#include "MyGatewayTransport.h"
#if defined MY_CONTROLLER_IP_ADDRESS
IPAddress _brokerIp(MY_CONTROLLER_IP_ADDRESS);
#endif
#if defined(MY_GATEWAY_ESP8266)
#define EthernetClient WiFiClient
#if defined(MY_IP_ADDRESS)
IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS);
IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS);
#endif
#elif defined(MY_GATEWAY_LINUX)
// Nothing to do here
#else
uint8_t _MQTT_clientMAC[] = { MY_MAC_ADDRESS };
#endif
#if defined(MY_IP_ADDRESS)
IPAddress _MQTT_clientIp(MY_IP_ADDRESS);
#endif
static EthernetClient _MQTT_ethClient;
static PubSubClient _MQTT_client(_MQTT_ethClient);
static bool _MQTT_connecting = true;
static bool _MQTT_available = false;
static MyMessage _MQTT_msg;
bool gatewayTransportSend(MyMessage &message) {
if (!_MQTT_client.connected())
return false;
setIndication(INDICATION_GW_TX);
char *topic = protocolFormatMQTTTopic(MY_MQTT_PUBLISH_TOPIC_PREFIX, message);
debug(PSTR("Sending message on topic: %s\n"), topic);
return _MQTT_client.publish(topic, message.getString(_convBuffer));
}
void incomingMQTT(char* topic, uint8_t* payload, unsigned int length) {
debug(PSTR("Message arrived on topic: %s\n"), topic);
_MQTT_available = protocolMQTTParse(_MQTT_msg, topic, payload, length);
}
bool reconnectMQTT() {
debug(PSTR("Attempting MQTT connection...\n"));
// Attempt to connect
if (_MQTT_client.connect(MY_MQTT_CLIENT_ID
#if defined(MY_MQTT_USER) && defined(MY_MQTT_PASSWORD)
, MY_MQTT_USER, MY_MQTT_PASSWORD
#endif
)) {
debug(PSTR("MQTT connected\n"));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
// Once connected, publish an announcement...
//_MQTT_client.publish("outTopic","hello world");
// ... and resubscribe
_MQTT_client.subscribe(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "/+/+/+/+/+");
return true;
}
return false;
}
bool gatewayTransportConnect() {
#if defined(MY_GATEWAY_ESP8266)
while (WiFi.status() != WL_CONNECTED) {
delay(500); // delay calls yield
MY_SERIALDEVICE.print(".");
}
MY_SERIALDEVICE.print("IP: ");
MY_SERIALDEVICE.println(WiFi.localIP());
#elif defined(MY_GATEWAY_LINUX)
#if defined(MY_IP_ADDRESS)
//TODO
#endif
#else
#ifdef MY_IP_ADDRESS
Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp);
#else
// Get IP address from DHCP
if (!Ethernet.begin(_MQTT_clientMAC)) {
MY_SERIALDEVICE.print("DHCP FAILURE...");
_MQTT_connecting = false;
return false;
}
#endif
MY_SERIALDEVICE.print("IP: ");
MY_SERIALDEVICE.println(Ethernet.localIP());
// give the Ethernet interface a second to initialize
delay(1000);
#endif
return true;
}
bool gatewayTransportInit() {
_MQTT_connecting = true;
#if defined(MY_CONTROLLER_IP_ADDRESS)
_MQTT_client.setServer(_brokerIp, MY_PORT);
#else
_MQTT_client.setServer(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
#endif
_MQTT_client.setCallback(incomingMQTT);
#if defined(MY_GATEWAY_ESP8266)
// Turn off access point
WiFi.mode(WIFI_STA);
#if defined(MY_ESP8266_HOSTNAME)
WiFi.hostname(MY_ESP8266_HOSTNAME);
#endif
(void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD);
#ifdef MY_IP_ADDRESS
WiFi.config(_MQTT_clientIp, _gatewayIp, _subnetIp);
#endif
#endif
gatewayTransportConnect();
_MQTT_connecting = false;
return true;
}
bool gatewayTransportAvailable() {
if (_MQTT_connecting)
return false;
//keep lease on dhcp address
//Ethernet.maintain();
if (!_MQTT_client.connected()) {
//reinitialise client
if (gatewayTransportConnect())
reconnectMQTT();
return false;
}
_MQTT_client.loop();
return _MQTT_available;
}
MyMessage & gatewayTransportReceive() {
// Return the last parsed message
_MQTT_available = false;
return _MQTT_msg;
}
+80
View File
@@ -0,0 +1,80 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyProtocol.h"
#include "MyGatewayTransport.h"
#include "MyMessage.h"
#include "MyProtocol.h"
// global variables
extern MyMessage _msgTmp;
char _serialInputString[MY_GATEWAY_MAX_RECEIVE_LENGTH]; // A buffer for incoming commands from serial interface
uint8_t _serialInputPos;
MyMessage _serialMsg;
bool gatewayTransportSend(MyMessage &message) {
setIndication(INDICATION_GW_TX);
MY_SERIALDEVICE.print(protocolFormat(message));
// Serial print is always successful
return true;
}
bool gatewayTransportInit() {
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
// Send presentation of locally attached sensors (and node if applicable)
presentNode();
return true;
}
bool gatewayTransportAvailable() {
while (MY_SERIALDEVICE.available()) {
// get the new byte:
char inChar = (char) MY_SERIALDEVICE.read();
// if the incoming character is a newline, set a flag
// so the main loop can do something about it:
if (_serialInputPos < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
if (inChar == '\n') {
_serialInputString[_serialInputPos] = 0;
bool ok = protocolParse(_serialMsg, _serialInputString);
if (ok)
{
setIndication(INDICATION_GW_RX);
}
_serialInputPos = 0;
return ok;
} else {
// add it to the inputString:
_serialInputString[_serialInputPos] = inChar;
_serialInputPos++;
}
} else {
// Incoming message too long. Throw away
_serialInputPos = 0;
}
}
return false;
}
MyMessage & gatewayTransportReceive() {
// Return the last parsed message
return _serialMsg;
}
+114
View File
@@ -0,0 +1,114 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyHw.h
*
* MySensors hardware abstraction layer
*/
#ifndef MyHw_h
#define MyHw_h
#include "MyConfig.h"
#include "MyMessage.h"
#ifdef __cplusplus
#include <Arduino.h>
#endif
// Implement these as functions or macros
/*
#define hwDigitalWrite(__pin, __value)
#define hwInit() MY_SERIALDEVICE.begin(BAUD_RATE)
#define hwWatchdogReset() wdt_reset()
#define hwReboot() wdt_enable(WDTO_15MS); while (1)
#define hwMillis() millis()
void hwReadConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfig(int adr, uint8_t value);
uint8_t hwReadConfig(int adr);
*/
/**
* Sleep for a defined time, using minimum power.
* @param ms Time to sleep, in [ms].
* @return Nonsense, please ignore.
*/
int8_t hwSleep(unsigned long ms);
/**
* Sleep for a defined time, using minimum power, or until woken by interrupt.
* @param interrupt Interrupt number, which can wake the mcu from sleep.
* @param mode Interrupt mode, as passed to attachInterrupt.
* @param ms Time to sleep, in [ms].
* @return -1 when woken by timer, or interrupt number when woken by interrupt.
*/
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms);
/**
* Sleep for a defined time, using minimum power, or until woken by one of the interrupts.
* @param interrupt1 Interrupt1 number, which can wake the mcu from sleep.
* @param mode1 Interrupt1 mode, as passed to attachInterrupt.
* @param interrupt2 Interrupt2 number, which can wake the mcu from sleep.
* @param mode2 Interrupt2 mode, as passed to attachInterrupt.
* @param ms Time to sleep, in [ms].
* @return -1 when woken by timer, or interrupt number when woken by interrupt.
*/
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms);
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... );
#endif
/**
* @def MY_CRITICAL_SECTION
* @brief Creates a block of code that is guaranteed to be executed atomically.
* Upon entering the block all interrupts are disabled, and re-enabled upon
* exiting the block from any exit path.
* A typical example that requires atomic access is a 16 (or more) bit variable
* that is shared between the main execution path and an ISR, on an 8-bit
* platform (e.g AVR):
* @code
* volatile uint16_t val = 0;
*
* void interrupHandler()
* {
* val = ~val;
* }
*
* void loop()
* {
* uint16_t copy_val;
* MY_CRITICAL_SECTION
* {
* copy_val = val;
* }
* }
* @endcode
* All code within the MY_CRITICAL_SECTION block will be protected from being
* interrupted during execution.
*/
#ifdef DOXYGEN
#define MY_CRITICAL_SECTION
#endif /* DOXYGEN */
#endif // #ifdef MyHw_h
+241
View File
@@ -0,0 +1,241 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifdef ARDUINO_ARCH_AVR
#include "MyHwATMega328.h"
#define INVALID_INTERRUPT_NUM (0xFFu)
volatile uint8_t _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; // Interrupt number that woke the mcu.
volatile uint8_t _wakeUp1Interrupt = INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp1-callback.
volatile uint8_t _wakeUp2Interrupt = INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback.
void wakeUp1() //place to send the interrupts
{
detachInterrupt(_wakeUp1Interrupt);
if (_wakeUp2Interrupt != INVALID_INTERRUPT_NUM)
{
detachInterrupt(_wakeUp2Interrupt);
}
_wokeUpByInterrupt = _wakeUp1Interrupt;
}
void wakeUp2() //place to send the second interrupts
{
detachInterrupt(_wakeUp2Interrupt);
if (_wakeUp1Interrupt != INVALID_INTERRUPT_NUM)
{
detachInterrupt(_wakeUp1Interrupt);
}
_wokeUpByInterrupt = _wakeUp2Interrupt;
}
bool interruptWakeUp()
{
return _wokeUpByInterrupt != INVALID_INTERRUPT_NUM;
}
// Watchdog Timer interrupt service routine. This routine is required
// to allow automatic WDIF and WDIE bit clearance in hardware.
ISR (WDT_vect)
{
}
void hwPowerDown(period_t period) {
// disable ADC for power saving
ADCSRA &= ~(1 << ADEN);
// save WDT settings
uint8_t WDTsave = WDTCSR;
if (period != SLEEP_FOREVER)
{
wdt_enable(period);
// enable WDT interrupt before system reset
WDTCSR |= (1 << WDCE) | (1 << WDIE);
} else {
// if sleeping forever, disable WDT
wdt_disable();
}
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli();
sleep_enable();
#if defined __AVR_ATmega328P__
sleep_bod_disable();
#endif
// Enable interrupts & sleep until WDT or ext. interrupt
sei();
// Directly sleep CPU, to prevent race conditions! (see chapter 7.7 of ATMega328P datasheet)
sleep_cpu();
sleep_disable();
// restore previous WDT settings
cli();
wdt_reset();
// enable WDT changes
WDTCSR |= (1 << WDCE) | (1 << WDE);
// restore saved WDT settings
WDTCSR = WDTsave;
sei();
// enable ADC
ADCSRA |= (1 << ADEN);
}
void hwInternalSleep(unsigned long ms) {
// Let serial prints finish (debug, log etc)
#ifndef MY_DISABLED_SERIAL
MY_SERIALDEVICE.flush();
#endif
while (!interruptWakeUp() && ms >= 8000) { hwPowerDown(SLEEP_8S); ms -= 8000; }
if (!interruptWakeUp() && ms >= 4000) { hwPowerDown(SLEEP_4S); ms -= 4000; }
if (!interruptWakeUp() && ms >= 2000) { hwPowerDown(SLEEP_2S); ms -= 2000; }
if (!interruptWakeUp() && ms >= 1000) { hwPowerDown(SLEEP_1S); ms -= 1000; }
if (!interruptWakeUp() && ms >= 500) { hwPowerDown(SLEEP_500MS); ms -= 500; }
if (!interruptWakeUp() && ms >= 250) { hwPowerDown(SLEEP_250MS); ms -= 250; }
if (!interruptWakeUp() && ms >= 125) { hwPowerDown(SLEEP_120MS); ms -= 120; }
if (!interruptWakeUp() && ms >= 64) { hwPowerDown(SLEEP_60MS); ms -= 60; }
if (!interruptWakeUp() && ms >= 32) { hwPowerDown(SLEEP_30MS); ms -= 30; }
if (!interruptWakeUp() && ms >= 16) { hwPowerDown(SLEEP_15MS); ms -= 15; }
}
int8_t hwSleep(unsigned long ms) {
hwInternalSleep(ms);
return MY_WAKE_UP_BY_TIMER;
}
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
return hwSleep(interrupt,mode,INVALID_INTERRUPT_NUM,0u,ms);
}
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
// Disable interrupts until going to sleep, otherwise interrupts occurring between attachInterrupt()
// and sleep might cause the ATMega to not wakeup from sleep as interrupt has already be handled!
cli();
// attach interrupts
_wakeUp1Interrupt = interrupt1;
_wakeUp2Interrupt = interrupt2;
if (interrupt1 != INVALID_INTERRUPT_NUM) attachInterrupt(interrupt1, wakeUp1, mode1);
if (interrupt2 != INVALID_INTERRUPT_NUM) attachInterrupt(interrupt2, wakeUp2, mode2);
if (ms>0) {
// sleep for defined time
hwInternalSleep(ms);
} else {
// sleep until ext interrupt triggered
hwPowerDown(SLEEP_FOREVER);
}
// Assure any interrupts attached, will get detached when they did not occur.
if (interrupt1 != INVALID_INTERRUPT_NUM) detachInterrupt(interrupt1);
if (interrupt2 != INVALID_INTERRUPT_NUM) detachInterrupt(interrupt2);
// Return what woke the mcu.
int8_t ret = MY_WAKE_UP_BY_TIMER; // default: no interrupt triggered, timer wake up
if (interruptWakeUp()) ret = static_cast<int8_t>(_wokeUpByInterrupt);
// Clear woke-up-by-interrupt flag, so next sleeps won't return immediately.
_wokeUpByInterrupt = INVALID_INTERRUPT_NUM;
return ret;
}
uint16_t hwCPUVoltage() {
// Measure Vcc against 1.1V Vref
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = (_BV(MUX5) | _BV(MUX0));
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = (_BV(MUX3) | _BV(MUX2));
#else
ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#endif
// Vref settle
delay(70);
// Do conversion
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA,ADSC));
// return Vcc in mV
return (1125300UL) / ADC;
}
uint16_t hwCPUFrequency() {
cli();
// setup timer1
TIFR1 = 0xFF;
TCNT1 = 0;
TCCR1A = 0;
TCCR1C = 0;
// save WDT settings
uint8_t WDTsave = WDTCSR;
wdt_enable(WDTO_500MS);
// enable WDT interrupt mode => first timeout WDIF, 2nd timeout reset
WDTCSR |= (1 << WDIE);
wdt_reset();
// start timer1 with 1024 prescaling
TCCR1B = _BV(CS12) | _BV(CS10);
// wait until wdt interrupt
while (bit_is_clear(WDTCSR,WDIF));
// stop timer
TCCR1B = 0;
// restore WDT settings
wdt_reset();
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = WDTsave;
sei();
// return frequency in 1/10MHz (accuracy +- 10%)
return TCNT1 * 2048UL / 100000UL;
}
uint16_t hwFreeMem() {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... ) {
char fmtBuffer[MY_DEBUG_BUFFER_SIZE];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(fmtBuffer);
#else
// prepend timestamp (AVR nodes)
MY_SERIALDEVICE.print(hwMillis());
MY_SERIALDEVICE.print(" ");
#endif
va_list args;
va_start (args, fmt );
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
vsnprintf_P(fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, fmt, args);
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1] = '\n';
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH] = '\0';
#else
vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args);
#endif
va_end (args);
MY_SERIALDEVICE.print(fmtBuffer);
MY_SERIALDEVICE.flush();
//MY_SERIALDEVICE.write(freeRam());
}
#endif
#endif // #ifdef ARDUINO_ARCH_AVR
+108
View File
@@ -0,0 +1,108 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyHwATMega328_h
#define MyHwATMega328_h
#include "MyHw.h"
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/atomic.h>
#ifdef __cplusplus
#include <Arduino.h>
#endif
#define MY_SERIALDEVICE Serial
#define MY_DEBUG_BUFFER_SIZE 300
#if defined __AVR_ATmega328P__
#ifndef sleep_bod_disable
#define sleep_bod_disable() \
do { \
unsigned char tempreg; \
__asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \
"ori %[tempreg], %[bods_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" "\n\t" \
"andi %[tempreg], %[not_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" \
: [tempreg] "=&d" (tempreg) \
: [mcucr] "I" _SFR_IO_ADDR(MCUCR), \
[bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \
[not_bodse] "i" (~_BV(BODSE))); \
} while (0)
#endif
#endif
// Define these as macros to save valuable space
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
#if defined(MY_DISABLED_SERIAL)
#define hwInit()
#else
#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE)
#endif
#define hwWatchdogReset() wdt_reset()
#define hwReboot() wdt_enable(WDTO_15MS); while (1)
#define hwMillis() millis()
#define hwRandomNumberInit() randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN))
#define hwReadConfig(__pos) (eeprom_read_byte((uint8_t*)(__pos)))
#ifndef eeprom_update_byte
#define hwWriteConfig(loc, val) if((uint8_t)(val) != eeprom_read_byte((uint8_t*)(loc))) { eeprom_write_byte((uint8_t*)(loc), (val)); }
#else
#define hwWriteConfig(__pos, __value) (eeprom_update_byte((uint8_t*)(__pos), (__value)))
#endif
//
#define hwReadConfigBlock(__buf, __pos, __length) (eeprom_read_block((__buf), (void*)(__pos), (__length)))
#define hwWriteConfigBlock(__pos, __buf, __length) (eeprom_write_block((void*)(__pos), (void*)__buf, (__length)))
enum period_t
{
SLEEP_15MS,
SLEEP_30MS,
SLEEP_60MS,
SLEEP_120MS,
SLEEP_250MS,
SLEEP_500MS,
SLEEP_1S,
SLEEP_2S,
SLEEP_4S,
SLEEP_8S,
SLEEP_FOREVER
};
void hwInternalSleep(unsigned long ms);
#ifndef DOXYGEN
#define MY_CRITICAL_SECTION ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
#endif /* DOXYGEN */
#endif
+157
View File
@@ -0,0 +1,157 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyHwESP8266.h"
#include <EEPROM.h>
/*
int8_t pinIntTrigger = 0;
void wakeUp() //place to send the interrupts
{
pinIntTrigger = 1;
}
void wakeUp2() //place to send the second interrupts
{
pinIntTrigger = 2;
}
// Watchdog Timer interrupt service routine. This routine is required
// to allow automatic WDIF and WDIE bit clearance in hardware.
ISR (WDT_vect)
{
// WDIE & WDIF is cleared in hardware upon entering this ISR
wdt_disable();
}
*/
static void hwInitConfigBlock( size_t length = 1024 /*ATMega328 has 1024 bytes*/ )
{
static bool initDone = false;
if (!initDone)
{
EEPROM.begin(length);
initDone = true;
}
}
void hwReadConfigBlock(void* buf, void* adr, size_t length)
{
hwInitConfigBlock();
uint8_t* dst = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
*dst++ = EEPROM.read(offs++);
}
}
void hwWriteConfigBlock(void* buf, void* adr, size_t length)
{
hwInitConfigBlock();
uint8_t* src = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
EEPROM.write(offs++, *src++);
}
EEPROM.commit();
}
uint8_t hwReadConfig(int adr)
{
uint8_t value;
hwReadConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
return value;
}
void hwWriteConfig(int adr, uint8_t value)
{
uint8_t curr = hwReadConfig(adr);
if (curr != value)
{
hwWriteConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
}
}
int8_t hwSleep(unsigned long ms) {
// TODO: Not supported!
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
// TODO: Not supported!
(void)interrupt;
(void)mode;
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
// TODO: Not supported!
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
ADC_MODE(ADC_VCC);
uint16_t hwCPUVoltage() {
// in mV
return ESP.getVcc();
}
uint16_t hwCPUFrequency() {
// in 1/10Mhz
return ESP.getCpuFreqMHz()*10;
}
uint16_t hwFreeMem() {
return ESP.getFreeHeap();
}
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... ) {
char fmtBuffer[MY_DEBUG_BUFFER_SIZE];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(fmtBuffer);
#endif
va_list args;
va_start (args, fmt );
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
vsnprintf_P(fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, fmt, args);
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1] = '\n';
fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH] = '\0';
#else
vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args);
#endif
va_end (args);
MY_SERIALDEVICE.print(fmtBuffer);
MY_SERIALDEVICE.flush();
//MY_SERIALDEVICE.write(freeRam());
}
#endif
+68
View File
@@ -0,0 +1,68 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyHwESP8266_h
#define MyHwESP8266_h
#include "MyHw.h"
#ifdef __cplusplus
#include <Arduino.h>
#endif
#define MY_SERIALDEVICE Serial
#define MY_DEBUG_BUFFER_SIZE 300
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
// Define these as macros to save valuable space
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
#if defined(MY_DISABLED_SERIAL)
#define hwInit()
#else
#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE, SERIAL_8N1, MY_ESP8266_SERIAL_MODE, 1); MY_SERIALDEVICE.setDebugOutput(true)
#endif
#define hwWatchdogReset() wdt_reset()
#define hwReboot() ESP.restart()
#define hwMillis() millis()
#define hwRandomNumberInit() randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN))
void hwReadConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfig(int adr, uint8_t value);
uint8_t hwReadConfig(int adr);
/**
* Restore interrupt state.
* Helper function for MY_CRITICAL_SECTION.
*/
static __inline__ void __psRestore(const uint32_t *__s)
{
xt_wsr_ps( *__s );
}
#ifndef DOXYGEN
#define MY_CRITICAL_SECTION for ( uint32_t __psSaved __attribute__((__cleanup__(__psRestore))) = xt_rsil(15), __ToDo = 1; __ToDo ; __ToDo = 0 )
#endif /* DOXYGEN */
#endif // #ifdef ARDUINO_ARCH_ESP8266
+190
View File
@@ -0,0 +1,190 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Marcelo Aquino <marceloaqno@gmail.org>
* Copyright (C) 2016 Marcelo Aquino
* Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyHwLinuxGeneric.h"
#include <stdarg.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <time.h>
#include "log.h"
static const char* CONFIG_FILE = MY_LINUX_CONFIG_FILE;
static const size_t _length = 1024; // ATMega328 has 1024 bytes
static uint8_t _config[_length];
bool CheckConfigFile()
{
struct stat fileInfo;
if (stat(CONFIG_FILE, &fileInfo) != 0) {
//File does not exist. Create it.
debug("Config file %s does not exist, creating new config file.\n", CONFIG_FILE);
std::ofstream myFile(CONFIG_FILE, std::ios::out | std::ios::binary);
if (!myFile) {
debug("Unable to create config file %s.\n", CONFIG_FILE);
return false;
}
myFile.write((const char*)_config, _length);
myFile.close();
} else if (fileInfo.st_size < 0 || (size_t)fileInfo.st_size != _length) {
debug("Config file %s is not the correct size of %i. Please remove the file and a new one will be created.\n", CONFIG_FILE, _length);
return false;
} else {
//Read config into local memory.
std::ifstream myFile(CONFIG_FILE, std::ios::in | std::ios::binary);
if (!myFile) {
debug("Unable to open config to file %s for reading.\n", CONFIG_FILE);
return false;
}
myFile.read((char*)_config, _length);
myFile.close();
}
return true;
}
void hwInit()
{
for (size_t i = 0; i < _length; i++) {
_config[i] = 0xFF;
}
if (!CheckConfigFile()) {
exit(1);
}
#ifdef MY_GATEWAY_SERIAL
MY_SERIALDEVICE.begin(MY_BAUD_RATE);
#ifdef MY_LINUX_SERIAL_GROUPNAME
if (!MY_SERIALDEVICE.setGroupPerm(MY_LINUX_SERIAL_GROUPNAME)) {
debug("Unable to change permission for serial port device.\n");
exit(1);
}
#endif
#endif
}
void hwReadConfigBlock(void* buf, void* addr, size_t length)
{
unsigned long int offs = reinterpret_cast<unsigned long int>(addr);
if (length && offs + length <= _length) {
memcpy(buf, _config+offs, length);
}
}
void hwWriteConfigBlock(void* buf, void* addr, size_t length)
{
unsigned long int offs = reinterpret_cast<unsigned long int>(addr);
if (length && offs + length <= _length) {
memcpy(_config+offs, buf, length);
std::ofstream myFile(CONFIG_FILE, std::ios::out | std::ios::in | std::ios::binary);
if (!myFile) {
debug("Unable to write config to file %s.\n", CONFIG_FILE);
return;
}
myFile.seekp(offs);
myFile.write((const char*)buf, length);
myFile.close();
}
}
uint8_t hwReadConfig(int adr)
{
uint8_t value = 0xFF;
hwReadConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
return value;
}
void hwWriteConfig(int adr, uint8_t value)
{
uint8_t curr = hwReadConfig(adr);
if (curr != value) {
hwWriteConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
}
}
void hwRandomNumberInit()
{
randomSeed(time(NULL));
}
// Not supported!
int8_t hwSleep(unsigned long ms)
{
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
// Not supported!
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms)
{
(void)interrupt;
(void)mode;
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
// Not supported!
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms)
{
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
uint16_t hwCPUVoltage()
{
// TODO: Not supported!
return 0;
}
uint16_t hwCPUFrequency()
{
// TODO: Not supported!
return 0;
}
uint16_t hwFreeMem()
{
// TODO: Not supported!
return 0;
}
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
mys_log_v(LOG_DEBUG, fmt, args);
va_end(args);
}
#endif
+84
View File
@@ -0,0 +1,84 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Marcelo Aquino <marceloaqno@gmail.org>
* Copyright (C) 2016 Marcelo Aquino
* Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyHwLinuxGeneric_h
#define MyHwLinuxGeneric_h
#include <cstdlib>
#include <pthread.h>
#include "MyHw.h"
#include "SerialPort.h"
#ifdef MY_IS_SERIAL_PTY
SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PTY, true);
#else
SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PORT);
#endif
#define MY_SERIALDEVICE Serial
// Define these as macros (do nothing)
#define hwDigitalWrite(__pin, __value)
#define hwWatchdogReset()
#define hwReboot()
#define hwMillis() millis()
void hwInit();
void hwReadConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfigBlock(void* buf, void* adr, size_t length);
uint8_t hwReadConfig(int adr);
void hwWriteConfig(int adr, uint8_t value);
void hwRandomNumberInit();
#ifdef MY_RF24_IRQ_PIN
static pthread_mutex_t hw_mutex = PTHREAD_MUTEX_INITIALIZER;
static __inline__ void __hwUnlock(const uint8_t *__s) {
pthread_mutex_unlock(&hw_mutex);
(void)__s;
}
static __inline__ void __hwLock() {
pthread_mutex_lock(&hw_mutex);
}
#endif
#if defined(DOXYGEN)
#define ATOMIC_BLOCK_CLEANUP
#elif defined(MY_RF24_IRQ_PIN)
#define ATOMIC_BLOCK_CLEANUP uint8_t __atomic_loop \
__attribute__((__cleanup__( __hwUnlock ))) = 1
#else
#define ATOMIC_BLOCK_CLEANUP
#endif /* DOXYGEN */
#if defined(DOXYGEN)
#define ATOMIC_BLOCK
#elif defined(MY_RF24_IRQ_PIN)
#define ATOMIC_BLOCK for ( ATOMIC_BLOCK_CLEANUP, __hwLock(); \
__atomic_loop ; __atomic_loop = 0 )
#else
#define ATOMIC_BLOCK
#endif /* DOXYGEN */
#ifndef DOXYGEN
#define MY_CRITICAL_SECTION ATOMIC_BLOCK
#endif /* DOXYGEN */
#endif
+182
View File
@@ -0,0 +1,182 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyHwSAMD.h"
/*
int8_t pinIntTrigger = 0;
void wakeUp() //place to send the interrupts
{
pinIntTrigger = 1;
}
void wakeUp2() //place to send the second interrupts
{
pinIntTrigger = 2;
}
// Watchdog Timer interrupt service routine. This routine is required
// to allow automatic WDIF and WDIE bit clearance in hardware.
ISR (WDT_vect)
{
// WDIE & WDIF is cleared in hardware upon entering this ISR
wdt_disable();
}
*/
void i2c_eeprom_write_byte(unsigned int eeaddress, byte data ) {
int rdata = data;
Wire.beginTransmission(I2C_EEP_ADDRESS);
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.write(rdata);
Wire.endTransmission();
}
byte i2c_eeprom_read_byte(unsigned int eeaddress ) {
byte rdata = 0xFF;
Wire.beginTransmission(I2C_EEP_ADDRESS);
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.endTransmission();
Wire.requestFrom(I2C_EEP_ADDRESS,1);
if (Wire.available()) rdata = Wire.read();
return rdata;
}
void hwReadConfigBlock(void* buf, void* adr, size_t length)
{
uint8_t* dst = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
*dst++ = i2c_eeprom_read_byte(offs++);
}
}
void hwWriteConfigBlock(void* buf, void* adr, size_t length)
{
uint8_t* src = static_cast<uint8_t*>(buf);
int offs = reinterpret_cast<int>(adr);
while (length-- > 0)
{
i2c_eeprom_write_byte(offs++, *src++);
}
}
uint8_t hwReadConfig(int adr)
{
uint8_t value;
hwReadConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
return value;
}
void hwWriteConfig(int adr, uint8_t value)
{
uint8_t curr = hwReadConfig(adr);
if (curr != value)
{
hwWriteConfigBlock(&value, reinterpret_cast<void*>(adr), 1);
}
}
void hwInit() {
MY_SERIALDEVICE.begin(MY_BAUD_RATE);
#if defined(MY_GATEWAY_SERIAL)
while (!MY_SERIALDEVICE) {}
#endif
Wire.begin();
}
void hwWatchdogReset() {
// TODO: Not supported!
}
void hwReboot() {
// TODO: Not supported!
}
int8_t hwSleep(unsigned long ms) {
// TODO: Not supported!
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
// TODO: Not supported!
(void)interrupt;
(void)mode;
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
// TODO: Not supported!
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
(void)ms;
return MY_SLEEP_NOT_POSSIBLE;
}
uint16_t hwCPUVoltage() {
// TODO: Not supported!
return 0;
}
uint16_t hwCPUFrequency() {
// TODO: Not supported!
return 0;
}
uint16_t hwFreeMem() {
// TODO: Not supported!
return 0;
}
#ifdef MY_DEBUG
void hwDebugPrint(const char *fmt, ... ) {
if (MY_SERIALDEVICE) {
char fmtBuffer[MY_DEBUG_BUFFER_SIZE];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(fmtBuffer);
#endif
va_list args;
va_start (args, fmt );
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
vsnprintf(fmtBuffer, 60, fmt, args);
fmtBuffer[59] = '\n';
fmtBuffer[60] = '\0';
#else
vsnprintf(fmtBuffer, sizeof(fmtBuffer), fmt, args);
#endif
va_end (args);
MY_SERIALDEVICE.print(fmtBuffer);
// MY_SERIALDEVICE.flush();
//MY_SERIALDEVICE.write(freeRam());
}
}
#endif
+87
View File
@@ -0,0 +1,87 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyHwSAMD_h
#define MyHwSAMD_h
#include "MyHw.h"
#include <Wire.h>
#ifdef __cplusplus
#include <Arduino.h>
//#include <SPI.h>
#endif
#include <avr/dtostrf.h>
#define I2C_EEP_ADDRESS 0x50
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define snprintf_P(s, f, ...) snprintf((s), (f), __VA_ARGS__)
// Define these as macros to save valuable space
uint8_t configBlock[1024];
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
void hwInit();
void hwWatchdogReset();
void hwReboot();
#define hwMillis() millis()
#define hwRandomNumberInit() randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN))
void hwReadConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfigBlock(void* buf, void* adr, size_t length);
void hwWriteConfig(int adr, uint8_t value);
uint8_t hwReadConfig(int adr);
#define MY_SERIALDEVICE SerialUSB
#define MY_DEBUG_BUFFER_SIZE 300
/*
#define hwReadConfigBlock(__buf, __adr, __length) ( __length = __length)
#define hwWriteConfigBlock(__buf, __adr, __length) ( __length = __length)
#define hwWriteConfig(__adr, __value) ( __value = __value)
#define hwReadConfig(__adr) (0)
*/
/**
* Disable all interrupts.
* Helper function for MY_CRITICAL_SECTION.
*/
static __inline__ uint8_t __disableIntsRetVal(void)
{
__disable_irq();
return 1;
}
/**
* Restore priority mask register.
* Helper function for MY_CRITICAL_SECTION.
*/
static __inline__ void __priMaskRestore(const uint32_t *priMask)
{
__set_PRIMASK(*priMask);
}
#ifndef DOXYGEN
#define MY_CRITICAL_SECTION for ( uint32_t __savePriMask __attribute__((__cleanup__(__priMaskRestore))) = __get_PRIMASK(), __ToDo = __disableIntsRetVal(); __ToDo ; __ToDo = 0 )
#endif /* DOXYGEN */
#endif // #ifdef ARDUINO_ARCH_SAMD
+62
View File
@@ -0,0 +1,62 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyInclusionMode.h"
// global variables
extern MyMessage _msgTmp;
unsigned long _inclusionStartTime;
bool _inclusionMode;
inline void inclusionInit() {
_inclusionMode = false;
#if defined(MY_INCLUSION_BUTTON_FEATURE)
// Setup digital in that triggers inclusion mode
pinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT);
digitalWrite(MY_INCLUSION_MODE_BUTTON_PIN, HIGH);
#endif
}
void inclusionModeSet(bool newMode) {
if (newMode != _inclusionMode) {
_inclusionMode = newMode;
// Send back mode change to controller
gatewayTransportSend(buildGw(_msgTmp, I_INCLUSION_MODE).set((uint8_t)(_inclusionMode?1:0)));
if (_inclusionMode) {
_inclusionStartTime = hwMillis();
}
}
}
inline void inclusionProcess() {
#ifdef MY_INCLUSION_BUTTON_FEATURE
if (!_inclusionMode && digitalRead(MY_INCLUSION_MODE_BUTTON_PIN) == MY_INCLUSION_BUTTON_PRESSED) {
// Start inclusion mode
inclusionModeSet(true);
}
#endif
if (_inclusionMode && hwMillis()-_inclusionStartTime>MY_INCLUSION_MODE_DURATION*1000L) {
// inclusionTimeInMinutes minute(s) has passed.. stop inclusion mode
inclusionModeSet(false);
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyInclusionMode_h
#define MyInclusionMode_h
#include "MySensorsCore.h"
extern bool gatewayTransportSend(MyMessage &message);
void inclusionInit();
void inclusionModeSet(bool newMode);
void inclusionProcess();
#endif
+48
View File
@@ -0,0 +1,48 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyIndication.h"
#if defined(MY_DEFAULT_TX_LED_PIN)|| defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#include "MyLeds.h"
#endif
void setIndication( const indication_t ind )
{
#if defined(MY_DEFAULT_TX_LED_PIN)
if ((INDICATION_TX == ind) || (INDICATION_GW_TX == ind))
{
ledsBlinkTx(1);
} else
#endif
#if defined(MY_DEFAULT_RX_LED_PIN)
if ((INDICATION_RX == ind) || (INDICATION_GW_RX == ind))
{
ledsBlinkRx(1);
} else
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
if (ind > INDICATION_ERR_START)
{
// Number of blinks indicates which error occurred.
ledsBlinkErr(ind-INDICATION_ERR_START);
}
#endif
if (indication)
indication(ind);
}
+78
View File
@@ -0,0 +1,78 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyIndication_h
#define MyIndication_h
/**
* Indication type
*/
typedef enum {
INDICATION_TX = 0, //!< Sent a message.
INDICATION_RX, //!< Received a message.
INDICATION_GW_TX, //!< Gateway transmit message.
INDICATION_GW_RX, //!< Gateway receive message.
INDICATION_FIND_PARENT, //!< Start finding parent node.
INDICATION_GOT_PARENT, //!< Found parent node.
INDICATION_REQ_NODEID, //!< Request node ID.
INDICATION_GOT_NODEID, //!< Got a node ID.
INDICATION_CHECK_UPLINK, //!< Check uplink
INDICATION_REQ_REGISTRATION, //!< Request node registration.
INDICATION_GOT_REGISTRATION, //!< Got registration reponse.
INDICATION_REBOOT, //!< Rebooting node.
INDICATION_PRESENT, //!< Presenting node to gateway.
INDICATION_CLEAR_ROUTING, //!< Clear routing table requested.
INDICATION_SLEEP, //!< Node goes to sleep.
INDICATION_WAKEUP, //!< Node just woke from sleep.
INDICATION_FW_UPDATE_START, //!< Start of OTA firmware update process.
INDICATION_FW_UPDATE_RX, //!< Received a piece of firmware data.
INDICATION_ERR_START = 100,
INDICATION_ERR_TX, //!< Failed to transmit message.
INDICATION_ERR_TRANSPORT_FAILURE, //!< Transport failure.
INDICATION_ERR_INIT_TRANSPORT, //!< MySensors transport hardware (radio) init failure.
INDICATION_ERR_FIND_PARENT, //!< Failed to find parent node.
INDICATION_ERR_GET_NODEID, //!< Failed to receive node ID.
INDICATION_ERR_CHECK_UPLINK, //!< Failed to check uplink
INDICATION_ERR_SIGN, //!< Error signing.
INDICATION_ERR_LENGTH, //!< Invalid message length.
INDICATION_ERR_VERSION, //!< Protocol version mismatch.
INDICATION_ERR_NET_FULL, //!< Network full. All node ID's are taken.
INDICATION_ERR_INIT_GWTRANSPORT, //!< Gateway transport hardware init failure.
INDICATION_ERR_LOCKED, //!< Node is locked.
INDICATION_ERR_FW_FLASH_INIT, //!< Firmware update flash initialisation failure.
INDICATION_ERR_FW_TIMEOUT, //!< Firmware update timeout.
INDICATION_ERR_FW_CHECKSUM, //!< Firmware update checksum mismatch.
INDICATION_ERR_END
} indication_t;
/**
* Function which is called when something changes about the internal state of MySensors.
* @param ind Event indication of what happened.
*/
void setIndication( const indication_t ind );
/**
* Allow user to define their own indication handler.
*/
void indication( const indication_t ) __attribute__((weak));
#endif
+92
View File
@@ -0,0 +1,92 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyLeds.h"
#define LED_ON_OFF_RATIO (4) // Power of 2 please
#define LED_PROCESS_INTERVAL_MS (MY_DEFAULT_LED_BLINK_PERIOD/LED_ON_OFF_RATIO)
// these variables don't need to be volatile, since we are not using interrupts
static uint8_t countRx;
static uint8_t countTx;
static uint8_t countErr;
static unsigned long prevTime = hwMillis() - LED_PROCESS_INTERVAL_MS; // Substract some, to make sure leds gets updated on first run.
inline void ledsInit()
{
// initialize counters
countRx = 0;
countTx = 0;
countErr = 0;
// Setup led pins
#if defined(MY_DEFAULT_RX_LED_PIN)
pinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT);
#endif
#if defined(MY_DEFAULT_TX_LED_PIN)
pinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT);
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
pinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT);
#endif
ledsProcess();
}
void ledsProcess() {
// Just return if it is not the time...
if ((hwMillis() - prevTime) < LED_PROCESS_INTERVAL_MS)
return;
prevTime += LED_PROCESS_INTERVAL_MS;
uint8_t state;
// For an On/Off ratio of 4, the pattern repeated will be [on, on, on, off]
// until the counter becomes 0.
#if defined(MY_DEFAULT_RX_LED_PIN)
state = (countRx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, state);
if (countRx) --countRx;
#endif
#if defined(MY_DEFAULT_TX_LED_PIN)
state = (countTx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, state);
if (countTx) --countTx;
#endif
#if defined(MY_DEFAULT_ERR_LED_PIN)
state = (countErr & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, state);
if (countErr) --countErr;
#endif
}
void ledsBlinkRx(uint8_t cnt) {
if (!countRx) { countRx = cnt*LED_ON_OFF_RATIO; }
}
void ledsBlinkTx(uint8_t cnt) {
if(!countTx) { countTx = cnt*LED_ON_OFF_RATIO; }
}
void ledsBlinkErr(uint8_t cnt) {
if(!countErr) { countErr = cnt*LED_ON_OFF_RATIO; }
}
+54
View File
@@ -0,0 +1,54 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyLeds_h
#define MyLeds_h
#ifdef MY_WITH_LEDS_BLINKING_INVERSE
#define LED_ON 0x1
#define LED_OFF 0x0
#else
#define LED_ON 0x0
#define LED_OFF 0x1
#endif
#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
#define ledBlinkTx(x,...) ledsBlinkTx(x)
#define ledBlinkRx(x,...) ledsBlinkRx(x)
#define ledBlinkErr(x,...) ledsBlinkErr(x)
/**
* Blink with LEDs
* @param cnt how many blink cycles to keep the LED on. Default cycle is 300ms
*/
void ledsInit();
void ledsBlinkRx(uint8_t cnt);
void ledsBlinkTx(uint8_t cnt);
void ledsBlinkErr(uint8_t cnt);
void ledsProcess(); // do the actual blinking
#else
// Remove led functions if feature is disabled
#define ledBlinkTx(x,...)
#define ledBlinkRx(x,...)
#define ledBlinkErr(x,...)
#endif
#endif
+19
View File
@@ -0,0 +1,19 @@
// Initialize library and handle sketch functions like we want to
int main(void) {
init();
#if defined(USBCON)
#if defined(ARDUINO_ARCH_SAMD)
USBDevice.init();
#endif
USBDevice.attach();
#endif
_begin(); // Startup MySensors library
for(;;) {
_process(); // Process incoming data
if (loop) loop(); // Call sketch loop
if (serialEventRun) serialEventRun();
}
return 0;
}
+147
View File
@@ -0,0 +1,147 @@
//This may be used to change user task stack size:
//#define CONT_STACKSIZE 4096
#include "Schedule.h"
extern "C" {
#include "ets_sys.h"
#include "os_type.h"
#include "osapi.h"
#include "mem.h"
#include "user_interface.h"
#include "cont.h"
}
#include <core_version.h>
#define LOOP_TASK_PRIORITY 1
#define LOOP_QUEUE_SIZE 1
#define OPTIMISTIC_YIELD_TIME_US 16000
struct rst_info resetInfo;
extern "C" {
extern const uint32_t __attribute__((section(".ver_number"))) core_version = ARDUINO_ESP8266_GIT_VER;
const char* core_release =
#ifdef ARDUINO_ESP8266_RELEASE
ARDUINO_ESP8266_RELEASE;
#else
NULL;
#endif
} // extern "C"
int atexit(void(*func)()) {
(void)func;
return 0;
}
extern "C" void ets_update_cpu_frequency(int freqmhz);
void initVariant() __attribute__((weak));
void initVariant() {
}
void preloop_update_frequency() __attribute__((weak));
void preloop_update_frequency() {
#if defined(F_CPU) && (F_CPU == 160000000L)
REG_SET_BIT(0x3ff00014, BIT(0));
ets_update_cpu_frequency(160);
#endif
}
extern void(*__init_array_start)(void);
extern void(*__init_array_end)(void);
cont_t g_cont __attribute__((aligned(16)));
static os_event_t g_loop_queue[LOOP_QUEUE_SIZE];
static uint32_t g_micros_at_task_start;
extern "C" void esp_yield() {
if (cont_can_yield(&g_cont)) {
cont_yield(&g_cont);
}
}
extern "C" void esp_schedule() {
ets_post(LOOP_TASK_PRIORITY, 0, 0);
}
extern "C" void __yield() {
if (cont_can_yield(&g_cont)) {
esp_schedule();
esp_yield();
}
else {
panic();
}
}
extern "C" void yield(void) __attribute__((weak, alias("__yield")));
extern "C" void optimistic_yield(uint32_t interval_us) {
if (cont_can_yield(&g_cont) &&
(system_get_time() - g_micros_at_task_start) > interval_us)
{
yield();
}
}
static void loop_wrapper() {
static bool setup_done = false;
preloop_update_frequency();
if (!setup_done) {
_begin(); // Startup MySensors library
setup_done = true;
}
_process(); // Process incoming data
loop();
run_scheduled_functions();
esp_schedule();
}
static void loop_task(os_event_t *events) {
(void)events;
g_micros_at_task_start = system_get_time();
cont_run(&g_cont, &loop_wrapper);
if (cont_check(&g_cont) != 0) {
panic();
}
}
static void do_global_ctors(void) {
void(**p)(void) = &__init_array_end;
while (p != &__init_array_start)
(*--p)();
}
extern "C" void __gdb_init() {}
extern "C" void gdb_init(void) __attribute__((weak, alias("__gdb_init")));
extern "C" void __gdb_do_break() {}
extern "C" void gdb_do_break(void) __attribute__((weak, alias("__gdb_do_break")));
void init_done() {
system_set_os_print(1);
gdb_init();
do_global_ctors();
esp_schedule();
}
extern "C" void user_init(void) {
struct rst_info *rtc_info_ptr = system_get_rst_info();
memcpy((void *)&resetInfo, (void *)rtc_info_ptr, sizeof(resetInfo));
uart_div_modify(0, UART_CLK_FREQ / (115200));
init();
initVariant();
cont_init(&g_cont);
ets_task(loop_task,
LOOP_TASK_PRIORITY, g_loop_queue,
LOOP_QUEUE_SIZE);
system_init_done_cb(&init_done);
}
+135
View File
@@ -0,0 +1,135 @@
// Initialize library and handle sketch functions like we want to
#include <stdio.h>
#include <csignal>
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include "log.h"
#include "MySensorsCore.h"
void handle_sigint(int sig)
{
if (sig == SIGINT) {
mys_log(LOG_NOTICE, "Received SIGINT\n\n");
} else if (sig == SIGTERM) {
mys_log(LOG_NOTICE, "Received SIGTERM\n\n");
} else {
return;
}
#ifdef MY_RF24_IRQ_PIN
detachInterrupt(MY_RF24_IRQ_PIN);
#endif
#if defined(MY_GATEWAY_SERIAL)
MY_SERIALDEVICE.end();
#endif
exit(0);
}
static int daemonize(void)
{
pid_t pid, sid;
/* Fork off the parent process */
pid = fork();
if (pid < 0) {
mys_log(LOG_ERR, "fork: %s", strerror(errno));
return -1;
}
/* If we got a good PID, then we can exit the parent process. */
if (pid > 0) {
exit(EXIT_SUCCESS);
}
/* At this point we are executing as the child process */
/* Change the file mode mask */
umask(0);
/* Create a new SID for the child process */
sid = setsid();
if (sid < 0) {
mys_log(LOG_ERR, "setsid: %s", strerror(errno));
return -1;
}
/* Change the current working directory. This prevents the current
directory from being locked; hence not being able to remove it. */
if ((chdir("/")) < 0) {
mys_log(LOG_ERR, "chdir(\"/\"): %s", strerror(errno));
return -1;
}
freopen( "/dev/null", "r", stdin);
freopen( "/dev/null", "r", stdout);
freopen( "/dev/null", "r", stderr);
return 0;
}
void print_usage()
{
printf("Usage: mysGateway [options]\n\n" \
"Options:\n" \
" -h Display a short summary of all program options.\n" \
" -d Enable debug.\n" \
" -b Become a daemon.\n");
}
int main(int argc, char *argv[])
{
int opt, log_opts, debug = 0, foreground = 1;
/* register the signal handler */
signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint);
while ((opt = getopt(argc, argv, "hdb")) != -1) {
switch (opt) {
case 'h':
print_usage();
exit(0);
case 'd':
debug = 1;
break;
case 'b':
foreground = 0;
break;
default:
print_usage();
exit(0);
}
}
log_opts = LOG_CONS;
if (foreground && isatty(STDIN_FILENO)) {
// Also print syslog to stderror
log_opts |= LOG_PERROR;
}
if (!debug) {
setlogmask(LOG_UPTO (LOG_INFO));
}
openlog(NULL, log_opts, LOG_USER);
if (!foreground && !debug) {
if (daemonize() != 0) {
exit(EXIT_FAILURE);
}
}
mys_log(LOG_INFO, "Starting gateway...\n");
mys_log(LOG_INFO, "Protocol version - %s\n", MYSENSORS_LIBRARY_VERSION);
_begin(); // Startup MySensors library
for (;;) {
_process(); // Process incoming data
if (loop) loop(); // Call sketch loop
}
return 0;
}
+76 -22
View File
@@ -1,22 +1,63 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyMessage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
MyMessage::MyMessage() {
destination = 0; // Gateway is default destination
MyMessage::MyMessage()
{
clear();
}
MyMessage::MyMessage(uint8_t _sensor, uint8_t _type) {
destination = 0; // Gateway is default destination
MyMessage::MyMessage(uint8_t _sensor, uint8_t _type)
{
clear();
sensor = _sensor;
type = _type;
type = _type;
}
void MyMessage::clear()
{
last = 0u;
sender = 0u;
destination = 0u; // Gateway is default destination
version_length = 0u;
command_ack_payload = 0u;
type = 0u;
sensor = 0u;
(void)memset(data, 0u, sizeof(data));
// set message protocol version
miSetVersion(PROTOCOL_VERSION);
}
bool MyMessage::isAck() const {
return miGetAck();
}
uint8_t MyMessage::getCommand() const {
return miGetCommand();
}
/* Getters for payload converted to desired form */
void* MyMessage::getCustom() const {
return (void *)data;
@@ -76,7 +117,7 @@ char* MyMessage::getString(char *buffer) const {
} else if (payloadType == P_ULONG32) {
ultoa(ulValue, buffer, 10);
} else if (payloadType == P_FLOAT32) {
dtostrf(fValue,2,fPrecision,buffer);
dtostrf(fValue,2,min(fPrecision, (uint8_t)8),buffer);
} else if (payloadType == P_CUSTOM) {
return getCustomString(buffer);
}
@@ -86,6 +127,10 @@ char* MyMessage::getString(char *buffer) const {
}
}
bool MyMessage::getBool() const {
return getByte();
}
uint8_t MyMessage::getByte() const {
if (miGetPayloadType() == P_BYTE) {
return data[0];
@@ -96,9 +141,6 @@ uint8_t MyMessage::getByte() const {
}
}
bool MyMessage::getBool() const {
return getInt();
}
float MyMessage::getFloat() const {
if (miGetPayloadType() == P_FLOAT32) {
@@ -110,7 +152,7 @@ float MyMessage::getFloat() const {
}
}
long MyMessage::getLong() const {
int32_t MyMessage::getLong() const {
if (miGetPayloadType() == P_LONG32) {
return lValue;
} else if (miGetPayloadType() == P_STRING) {
@@ -120,7 +162,7 @@ long MyMessage::getLong() const {
}
}
unsigned long MyMessage::getULong() const {
uint32_t MyMessage::getULong() const {
if (miGetPayloadType() == P_ULONG32) {
return ulValue;
} else if (miGetPayloadType() == P_STRING) {
@@ -130,7 +172,7 @@ unsigned long MyMessage::getULong() const {
}
}
int MyMessage::getInt() const {
int16_t MyMessage::getInt() const {
if (miGetPayloadType() == P_INT16) {
return iValue;
} else if (miGetPayloadType() == P_STRING) {
@@ -140,7 +182,7 @@ int MyMessage::getInt() const {
}
}
unsigned int MyMessage::getUInt() const {
uint16_t MyMessage::getUInt() const {
if (miGetPayloadType() == P_UINT16) {
return uiValue;
} else if (miGetPayloadType() == P_STRING) {
@@ -168,17 +210,29 @@ MyMessage& MyMessage::setDestination(uint8_t _destination) {
// Set payload
MyMessage& MyMessage::set(void* value, uint8_t length) {
uint8_t payloadLength = value == NULL ? 0 : min(length, (uint8_t)MAX_PAYLOAD);
miSetLength(payloadLength);
miSetPayloadType(P_CUSTOM);
miSetLength(length);
memcpy(data, value, min(length, MAX_PAYLOAD));
memcpy(data, value, payloadLength);
return *this;
}
MyMessage& MyMessage::set(const char* value) {
uint8_t length = min(strlen(value), MAX_PAYLOAD);
uint8_t length = value == NULL ? 0 : min(strlen(value), (size_t)MAX_PAYLOAD);
miSetLength(length);
miSetPayloadType(P_STRING);
strncpy(data, value, length);
if (length) {
strncpy(data, value, length);
}
// null terminate string
data[length] = 0;
return *this;
}
MyMessage& MyMessage::set(bool value) {
miSetLength(1);
miSetPayloadType(P_BYTE);
data[0] = value;
return *this;
}
@@ -197,28 +251,28 @@ MyMessage& MyMessage::set(float value, uint8_t decimals) {
return *this;
}
MyMessage& MyMessage::set(unsigned long value) {
MyMessage& MyMessage::set(uint32_t value) {
miSetPayloadType(P_ULONG32);
miSetLength(4);
ulValue = value;
return *this;
}
MyMessage& MyMessage::set(long value) {
MyMessage& MyMessage::set(int32_t value) {
miSetPayloadType(P_LONG32);
miSetLength(4);
lValue = value;
return *this;
}
MyMessage& MyMessage::set(unsigned int value) {
MyMessage& MyMessage::set(uint16_t value) {
miSetPayloadType(P_UINT16);
miSetLength(2);
uiValue = value;
return *this;
}
MyMessage& MyMessage::set(int value) {
MyMessage& MyMessage::set(int16_t value) {
miSetPayloadType(P_INT16);
miSetLength(2);
iValue = value;
+381
View File
@@ -0,0 +1,381 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyMessage.h
*
* @brief API and type declarations for MySensors messages
* @defgroup MyMessagegrp MyMessage
* @{
*
* @brief Here you can find all message types used by the MySensors protocol as well as macros for
* parsing and manipulating messages.
*/
#ifndef MyMessage_h
#define MyMessage_h
#ifdef __cplusplus
#include <Arduino.h>
#include <stdint.h>
#endif
#define PROTOCOL_VERSION (2u) //!< The version of the protocol
#define MAX_MESSAGE_LENGTH (32u) //!< The maximum size of a message (including header)
#define HEADER_SIZE (7u) //!< The size of the header
#define MAX_PAYLOAD (MAX_MESSAGE_LENGTH - HEADER_SIZE) //!< The maximum size of a payload depends on #MAX_MESSAGE_LENGTH and #HEADER_SIZE
/// @brief The command field (message-type) defines the overall properties of a message
typedef enum {
C_PRESENTATION = 0, //!< Sent by a node when they present attached sensors. This is usually done in presentation() at startup.
C_SET = 1, //!< This message is sent from or to a sensor when a sensor value should be updated.
C_REQ = 2, //!< Requests a variable value (usually from an actuator destined for controller).
C_INTERNAL = 3, //!< Internal MySensors messages (also include common messages provided/generated by the library).
C_STREAM = 4 //!< For firmware and other larger chunks of data that need to be divided into pieces.
} mysensor_command;
/// @brief Type of sensor (used when presenting sensors)
typedef enum {
S_DOOR = 0, //!< Door sensor, V_TRIPPED, V_ARMED
S_MOTION = 1, //!< Motion sensor, V_TRIPPED, V_ARMED
S_SMOKE = 2, //!< Smoke sensor, V_TRIPPED, V_ARMED
S_BINARY = 3, //!< Binary light or relay, V_STATUS, V_WATT
S_LIGHT = 3, //!< \deprecated Same as S_BINARY, **** DEPRECATED, DO NOT USE ****
S_DIMMER = 4, //!< Dimmable light or fan device, V_STATUS (on/off), V_PERCENTAGE (dimmer level 0-100), V_WATT
S_COVER = 5, //!< Blinds or window cover, V_UP, V_DOWN, V_STOP, V_PERCENTAGE (open/close to a percentage)
S_TEMP = 6, //!< Temperature sensor, V_TEMP
S_HUM = 7, //!< Humidity sensor, V_HUM
S_BARO = 8, //!< Barometer sensor, V_PRESSURE, V_FORECAST
S_WIND = 9, //!< Wind sensor, V_WIND, V_GUST
S_RAIN = 10, //!< Rain sensor, V_RAIN, V_RAINRATE
S_UV = 11, //!< Uv sensor, V_UV
S_WEIGHT = 12, //!< Personal scale sensor, V_WEIGHT, V_IMPEDANCE
S_POWER = 13, //!< Power meter, V_WATT, V_KWH, V_VAR, V_VA, V_POWER_FACTOR
S_HEATER = 14, //!< Header device, V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP
S_DISTANCE = 15, //!< Distance sensor, V_DISTANCE
S_LIGHT_LEVEL = 16, //!< Light level sensor, V_LIGHT_LEVEL (uncalibrated in percentage), V_LEVEL (light level in lux)
S_ARDUINO_NODE = 17, //!< Used (internally) for presenting a non-repeating Arduino node
S_ARDUINO_REPEATER_NODE = 18, //!< Used (internally) for presenting a repeating Arduino node
S_LOCK = 19, //!< Lock device, V_LOCK_STATUS
S_IR = 20, //!< IR device, V_IR_SEND, V_IR_RECEIVE
S_WATER = 21, //!< Water meter, V_FLOW, V_VOLUME
S_AIR_QUALITY = 22, //!< Air quality sensor, V_LEVEL
S_CUSTOM = 23, //!< Custom sensor
S_DUST = 24, //!< Dust sensor, V_LEVEL
S_SCENE_CONTROLLER = 25, //!< Scene controller device, V_SCENE_ON, V_SCENE_OFF.
S_RGB_LIGHT = 26, //!< RGB light. Send color component data using V_RGB. Also supports V_WATT
S_RGBW_LIGHT = 27, //!< RGB light with an additional White component. Send data using V_RGBW. Also supports V_WATT
S_COLOR_SENSOR = 28, //!< Color sensor, send color information using V_RGB
S_HVAC = 29, //!< Thermostat/HVAC device. V_HVAC_SETPOINT_HEAT, V_HVAC_SETPOINT_COLD, V_HVAC_FLOW_STATE, V_HVAC_FLOW_MODE, V_TEMP
S_MULTIMETER = 30, //!< Multimeter device, V_VOLTAGE, V_CURRENT, V_IMPEDANCE
S_SPRINKLER = 31, //!< Sprinkler, V_STATUS (turn on/off), V_TRIPPED (if fire detecting device)
S_WATER_LEAK = 32, //!< Water leak sensor, V_TRIPPED, V_ARMED
S_SOUND = 33, //!< Sound sensor, V_TRIPPED, V_ARMED, V_LEVEL (sound level in dB)
S_VIBRATION = 34, //!< Vibration sensor, V_TRIPPED, V_ARMED, V_LEVEL (vibration in Hz)
S_MOISTURE = 35, //!< Moisture sensor, V_TRIPPED, V_ARMED, V_LEVEL (water content or moisture in percentage?)
S_INFO = 36, //!< LCD text device / Simple information device on controller, V_TEXT
S_GAS = 37, //!< Gas meter, V_FLOW, V_VOLUME
S_GPS = 38, //!< GPS Sensor, V_POSITION
S_WATER_QUALITY = 39 //!< V_TEMP, V_PH, V_ORP, V_EC, V_STATUS
} mysensor_sensor;
/// @brief Type of sensor data (for set/req/ack messages)
typedef enum {
V_TEMP = 0, //!< S_TEMP. Temperature S_TEMP, S_HEATER, S_HVAC
V_HUM = 1, //!< S_HUM. Humidity
V_STATUS = 2, //!< S_BINARY, S_DIMMER, S_SPRINKLER, S_HVAC, S_HEATER. Used for setting/reporting binary (on/off) status. 1=on, 0=off
V_LIGHT = 2, //!< \deprecated Same as V_STATUS, **** DEPRECATED, DO NOT USE ****
V_PERCENTAGE = 3, //!< S_DIMMER. Used for sending a percentage value 0-100 (%).
V_DIMMER = 3, //!< \deprecated Same as V_PERCENTAGE, **** DEPRECATED, DO NOT USE ****
V_PRESSURE = 4, //!< S_BARO. Atmospheric Pressure
V_FORECAST = 5, //!< S_BARO. Whether forecast. string of "stable", "sunny", "cloudy", "unstable", "thunderstorm" or "unknown"
V_RAIN = 6, //!< S_RAIN. Amount of rain
V_RAINRATE = 7, //!< S_RAIN. Rate of rain
V_WIND = 8, //!< S_WIND. Wind speed
V_GUST = 9, //!< S_WIND. Gust
V_DIRECTION = 10, //!< S_WIND. Wind direction 0-360 (degrees)
V_UV = 11, //!< S_UV. UV light level
V_WEIGHT = 12, //!< S_WEIGHT. Weight(for scales etc)
V_DISTANCE = 13, //!< S_DISTANCE. Distance
V_IMPEDANCE = 14, //!< S_MULTIMETER, S_WEIGHT. Impedance value
V_ARMED = 15, //!< S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER. Armed status of a security sensor. 1 = Armed, 0 = Bypassed
V_TRIPPED = 16, //!< S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER, S_WATER_LEAK, S_SOUND, S_VIBRATION, S_MOISTURE. Tripped status of a security sensor. 1 = Tripped, 0
V_WATT = 17, //!< S_POWER, S_BINARY, S_DIMMER, S_RGB_LIGHT, S_RGBW_LIGHT. Watt value for power meters
V_KWH = 18, //!< S_POWER. Accumulated number of KWH for a power meter
V_SCENE_ON = 19, //!< S_SCENE_CONTROLLER. Turn on a scene
V_SCENE_OFF = 20, //!< S_SCENE_CONTROLLER. Turn of a scene
V_HVAC_FLOW_STATE = 21, //!< S_HEATER, S_HVAC. HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver")
V_HEATER = 21, //!< \deprecated Same as V_HVAC_FLOW_STATE, **** DEPRECATED, DO NOT USE ****
V_HVAC_SPEED = 22, //!< S_HVAC, S_HEATER. HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto")
V_LIGHT_LEVEL = 23, //!< S_LIGHT_LEVEL. Uncalibrated light level. 0-100%. Use V_LEVEL for light level in lux
V_VAR1 = 24, //!< VAR1
V_VAR2 = 25, //!< VAR2
V_VAR3 = 26, //!< VAR3
V_VAR4 = 27, //!< VAR4
V_VAR5 = 28, //!< VAR5
V_UP = 29, //!< S_COVER. Window covering. Up
V_DOWN = 30, //!< S_COVER. Window covering. Down
V_STOP = 31, //!< S_COVER. Window covering. Stop
V_IR_SEND = 32, //!< S_IR. Send out an IR-command
V_IR_RECEIVE = 33, //!< S_IR. This message contains a received IR-command
V_FLOW = 34, //!< S_WATER. Flow of water (in meter)
V_VOLUME = 35, //!< S_WATER. Water volume
V_LOCK_STATUS = 36, //!< S_LOCK. Set or get lock status. 1=Locked, 0=Unlocked
V_LEVEL = 37, //!< S_DUST, S_AIR_QUALITY, S_SOUND (dB), S_VIBRATION (hz), S_LIGHT_LEVEL (lux)
V_VOLTAGE = 38, //!< S_MULTIMETER
V_CURRENT = 39, //!< S_MULTIMETER
V_RGB = 40, //!< S_RGB_LIGHT, S_COLOR_SENSOR. Sent as ASCII hex: RRGGBB (RR=red, GG=green, BB=blue component)
V_RGBW = 41, //!< S_RGBW_LIGHT. Sent as ASCII hex: RRGGBBWW (WW=white component)
V_ID = 42, //!< Used for sending in sensors hardware ids (i.e. OneWire DS1820b).
V_UNIT_PREFIX = 43, //!< Allows sensors to send in a string representing the unit prefix to be displayed in GUI, not parsed by controller! E.g. cm, m, km, inch.
V_HVAC_SETPOINT_COOL = 44, //!< S_HVAC. HVAC cool setpoint (Integer between 0-100)
V_HVAC_SETPOINT_HEAT = 45, //!< S_HEATER, S_HVAC. HVAC/Heater setpoint (Integer between 0-100)
V_HVAC_FLOW_MODE = 46, //!< S_HVAC. Flow mode for HVAC ("Auto", "ContinuousOn", "PeriodicOn")
V_TEXT = 47, //!< S_INFO. Text message to display on LCD or controller device
V_CUSTOM = 48, //!< Custom messages used for controller/inter node specific commands, preferably using S_CUSTOM device type.
V_POSITION = 49, //!< GPS position and altitude. Payload: latitude;longitude;altitude(m). E.g. "55.722526;13.017972;18"
V_IR_RECORD = 50, //!< Record IR codes S_IR for playback
V_PH = 51, //!< S_WATER_QUALITY, water PH
V_ORP = 52, //!< S_WATER_QUALITY, water ORP : redox potential in mV
V_EC = 53, //!< S_WATER_QUALITY, water electric conductivity μS/cm (microSiemens/cm)
V_VAR = 54, //!< S_POWER, Reactive power: volt-ampere reactive (var)
V_VA = 55, //!< S_POWER, Apparent power: volt-ampere (VA)
V_POWER_FACTOR = 56, //!< S_POWER, Ratio of real power to apparent power: floating point value in the range [-1,..,1]
} mysensor_data;
/// @brief Type of internal messages (for internal messages)
typedef enum {
I_BATTERY_LEVEL = 0, //!< Battery level
I_TIME = 1, //!< Time (request/response)
I_VERSION = 2, //!< Version
I_ID_REQUEST = 3, //!< ID request
I_ID_RESPONSE = 4, //!< ID response
I_INCLUSION_MODE = 5, //!< Inclusion mode
I_CONFIG = 6, //!< Config (request/response)
I_FIND_PARENT_REQUEST = 7, //!< Find parent
I_FIND_PARENT_RESPONSE = 8, //!< Find parent response
I_LOG_MESSAGE = 9, //!< Log message
I_CHILDREN = 10, //!< Children
I_SKETCH_NAME = 11, //!< Sketch name
I_SKETCH_VERSION = 12, //!< Sketch version
I_REBOOT = 13, //!< Reboot request
I_GATEWAY_READY = 14, //!< Gateway ready
I_SIGNING_PRESENTATION = 15, //!< Provides signing related preferences (first byte is preference version)
I_NONCE_REQUEST = 16, //!< Request for a nonce
I_NONCE_RESPONSE = 17, //!< Payload is nonce data
I_HEARTBEAT_REQUEST = 18, //!< Heartbeat request
I_PRESENTATION = 19, //!< Presentation message
I_DISCOVER_REQUEST = 20, //!< Discover request
I_DISCOVER_RESPONSE = 21, //!< Discover response
I_HEARTBEAT_RESPONSE = 22, //!< Heartbeat response
I_LOCKED = 23, //!< Node is locked (reason in string-payload)
I_PING = 24, //!< Ping sent to node, payload incremental hop counter
I_PONG = 25, //!< In return to ping, sent back to sender, payload incremental hop counter
I_REGISTRATION_REQUEST = 26, //!< Register request to GW
I_REGISTRATION_RESPONSE = 27, //!< Register response from GW
I_DEBUG = 28 //!< Debug message
} mysensor_internal;
/// @brief Type of data stream (for streamed message)
typedef enum {
ST_FIRMWARE_CONFIG_REQUEST = 0, //!< Request new FW, payload contains current FW details
ST_FIRMWARE_CONFIG_RESPONSE = 1, //!< New FW details to initiate OTA FW update
ST_FIRMWARE_REQUEST = 2, //!< Request FW block
ST_FIRMWARE_RESPONSE = 3, //!< Response FW block
ST_SOUND = 4, //!< Sound
ST_IMAGE = 5 //!< Image
} mysensor_stream;
/// @brief Type of payload
typedef enum {
P_STRING = 0, //!< Payload type is string
P_BYTE = 1, //!< Payload type is byte
P_INT16 = 2, //!< Payload type is INT16
P_UINT16 = 3, //!< Payload type is UINT16
P_LONG32 = 4, //!< Payload type is INT32
P_ULONG32 = 5, //!< Payload type is UINT32
P_CUSTOM = 6, //!< Payload type is binary
P_FLOAT32 = 7 //!< Payload type is float32
} mysensor_payload;
#ifndef BIT
#define BIT(n) ( 1<<(n) ) //!< Bit indexing macro
#endif
#define BIT_MASK(len) ( BIT(len)-1 ) //!< Create a bitmask of length 'len'
#define BF_MASK(start, len) ( BIT_MASK(len)<<(start) ) //!< Create a bitfield mask of length starting at bit 'start'
#define BF_PREP(x, start, len) ( ((x)&BIT_MASK(len)) << (start) ) //!< Prepare a bitmask for insertion or combining
#define BF_GET(y, start, len) ( ((y)>>(start)) & BIT_MASK(len) ) //!< Extract a bitfield of length 'len' starting at bit 'start' from 'y'
#define BF_SET(y, x, start, len) ( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) ) //!< Insert a new bitfield value 'x' into 'y'
// Getters/setters for special bit fields in header
#define mSetVersion(_message,_version) BF_SET(_message.version_length, _version, 0, 2) //!< Set version field
#define mGetVersion(_message) ((uint8_t)BF_GET(_message.version_length, 0, 2)) //!< Get version field
#define mSetSigned(_message,_signed) BF_SET(_message.version_length, _signed, 2, 1) //!< Set signed field
#define mGetSigned(_message) ((bool)BF_GET(_message.version_length, 2, 1)) //!< Get versignedsion field
#define mSetLength(_message,_length) BF_SET(_message.version_length, _length, 3, 5) //!< Set length field
#define mGetLength(_message) ((uint8_t)BF_GET(_message.version_length, 3, 5)) //!< Get length field
#define mSetCommand(_message,_command) BF_SET(_message.command_ack_payload, _command, 0, 3) //!< Set command field
#define mGetCommand(_message) ((uint8_t)BF_GET(_message.command_ack_payload, 0, 3)) //!< Get command field
#define mSetRequestAck(_message,_rack) BF_SET(_message.command_ack_payload, _rack, 3, 1) //!< Set ack-request field
#define mGetRequestAck(_message) ((bool)BF_GET(_message.command_ack_payload, 3, 1)) //!< Get ack-request field
#define mSetAck(_message,_ackMsg) BF_SET(_message.command_ack_payload, _ackMsg, 4, 1) //!< Set ack field
#define mGetAck(_message) ((bool)BF_GET(_message.command_ack_payload, 4, 1)) //!< Get ack field
#define mSetPayloadType(_message, _pt) BF_SET(_message.command_ack_payload, _pt, 5, 3) //!< Set payload type field
#define mGetPayloadType(_message) ((uint8_t)BF_GET(_message.command_ack_payload, 5, 3)) //!< Get payload type field
// internal access for special fields
#define miGetCommand() ((uint8_t)BF_GET(command_ack_payload, 0, 3)) //!< Internal getter for command field
#define miSetLength(_length) BF_SET(version_length, _length, 3, 5) //!< Internal setter for length field
#define miGetLength() ((uint8_t)BF_GET(version_length, 3, 5)) //!< Internal getter for length field
#define miSetVersion(_version) BF_SET(version_length, _version, 0, 2) //!< Internal setter for version field
#define miGetVersion() ((uint8_t)BF_GET(version_length, 0, 2)) //!< Internal getter for version field
#define miSetRequestAck(_rack) BF_SET(command_ack_payload, _rack, 3, 1) //!< Internal setter for ack-request field
#define miGetRequestAck() ((bool)BF_GET(command_ack_payload, 3, 1)) //!< Internal getter for ack-request field
#define miSetAck(_ack) BF_SET(command_ack_payload, _ack, 4, 1) //!< Internal setter for ack field
#define miGetAck() ((bool)BF_GET(command_ack_payload, 4, 1)) //!< Internal getter for ack field
#define miSetPayloadType(_pt) BF_SET(command_ack_payload, _pt, 5, 3) //!< Internal setter for payload type field
#define miGetPayloadType() (uint8_t)BF_GET(command_ack_payload, 5, 3) //!< Internal getter for payload type field
#if !DOXYGEN
#ifdef __cplusplus
class MyMessage
{
private:
char* getCustomString(char *buffer) const;
public:
// Constructors
MyMessage();
MyMessage(uint8_t sensor, uint8_t type);
char i2h(uint8_t i) const;
/**
* Clear message contents.
*/
void clear();
/**
* If payload is something else than P_STRING you can have the payload value converted
* into string representation by supplying a buffer with the minimum size of
* 2*MAX_PAYLOAD+1. This is to be able to fit hex-conversion of a full binary payload.
*/
char* getStream(char *buffer) const;
char* getString(char *buffer) const;
const char* getString() const;
void* getCustom() const;
bool getBool() const;
uint8_t getByte() const;
float getFloat() const;
int16_t getInt() const;
uint16_t getUInt() const;
int32_t getLong() const;
uint32_t getULong() const;
// Getter for command type
uint8_t getCommand() const;
// Getter for ack-flag. True if this is an ack message.
bool isAck() const;
// Setters for building message "on the fly"
MyMessage& setType(uint8_t type);
MyMessage& setSensor(uint8_t sensor);
MyMessage& setDestination(uint8_t destination);
// Setters for payload
MyMessage& set(void* payload, uint8_t length);
MyMessage& set(const char* value);
MyMessage& set(float value, uint8_t decimals);
MyMessage& set(bool value);
MyMessage& set(uint8_t value);
MyMessage& set(uint32_t value);
MyMessage& set(int32_t value);
MyMessage& set(uint16_t value);
MyMessage& set(int16_t value);
#else
typedef union {
struct
{
#endif
uint8_t last; // 8 bit - Id of last node this message passed
uint8_t sender; // 8 bit - Id of sender node (origin)
uint8_t destination; // 8 bit - Id of destination node
uint8_t version_length; // 2 bit - Protocol version
// 1 bit - Signed flag
// 5 bit - Length of payload
uint8_t command_ack_payload; // 3 bit - Command type
// 1 bit - Request an ack - Indicator that receiver should send an ack back.
// 1 bit - Is ack messsage - Indicator that this is the actual ack message.
// 3 bit - Payload data type
uint8_t type; // 8 bit - Type varies depending on command
uint8_t sensor; // 8 bit - Id of sensor that this message concerns.
// Each message can transfer a payload. We add one extra byte for string
// terminator \0 to be "printable" this is not transferred OTA
// This union is used to simplify the construction of the binary data types transferred.
union {
uint8_t bValue;
uint16_t uiValue;
int16_t iValue;
uint32_t ulValue;
int32_t lValue;
struct { // Float messages
float fValue;
uint8_t fPrecision; // Number of decimals when serializing
};
struct { // Presentation messages
uint8_t version; // Library version
uint8_t sensorType; // Sensor type hint for controller, see table above
};
char data[MAX_PAYLOAD + 1];
} __attribute__((packed));
#ifdef __cplusplus
} __attribute__((packed));
#else
};
uint8_t array[HEADER_SIZE + MAX_PAYLOAD + 1];
} __attribute__((packed)) MyMessage;
#endif
#endif
#endif
/** @}*/
+156
View File
@@ -0,0 +1,156 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyOTAFirmwareUpdate.h"
// global variables
extern MyMessage _msg;
extern MyMessage _msgTmp;
extern NodeConfig _nc;
// local variables
SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID);
NodeFirmwareConfig _fc;
bool _fwUpdateOngoing;
unsigned long _fwLastRequestTime;
uint16_t _fwBlock;
uint8_t _fwRetry;
void readFirmwareSettings() {
hwReadConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
}
void firmwareOTAUpdateRequest() {
unsigned long enter = hwMillis();
if (_fwUpdateOngoing && (enter - _fwLastRequestTime > MY_OTA_RETRY_DELAY)) {
if (!_fwRetry) {
setIndication(INDICATION_ERR_FW_TIMEOUT);
OTA_DEBUG(PSTR("!OTA:FRQ:FW UPD FAIL\n")); // fw update failed
// Give up. We have requested MY_OTA_RETRY times without any packet in return.
_fwUpdateOngoing = false;
return;
}
_fwRetry--;
_fwLastRequestTime = enter;
// Time to (re-)request firmware block from controller
RequestFWBlock firmwareRequest;
firmwareRequest.type = _fc.type;
firmwareRequest.version = _fc.version;
firmwareRequest.block = (_fwBlock - 1);
OTA_DEBUG(PSTR("OTA:FRQ:FW REQ,T=%04X,V=%04X,B=%04X\n"),_fc.type,_fc.version,_fwBlock - 1); // request FW update block
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST, false).set(&firmwareRequest,sizeof(RequestFWBlock)));
}
}
bool firmwareOTAUpdateProcess() {
if (_msg.type == ST_FIRMWARE_CONFIG_RESPONSE) {
NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)_msg.data;
// compare with current node configuration, if they differ, start fw fetch process
if (memcmp(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) {
setIndication(INDICATION_FW_UPDATE_START);
OTA_DEBUG(PSTR("OTA:FWP:UPDATE\n")); // FW update initiated
// copy new FW config
memcpy(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig));
// Init flash
if (!_flash.initialize()) {
setIndication(INDICATION_ERR_FW_FLASH_INIT);
OTA_DEBUG(PSTR("!OTA:FWP:FLASH INIT FAIL\n")); // failed to initialise flash
_fwUpdateOngoing = false;
} else {
// erase lower 32K -> max flash size for ATMEGA328
_flash.blockErase32K(0);
// wait until flash erased
while ( _flash.busy() );
_fwBlock = _fc.blocks;
_fwUpdateOngoing = true;
// reset flags
_fwRetry = MY_OTA_RETRY+1;
_fwLastRequestTime = 0;
}
return true;
}
OTA_DEBUG(PSTR("OTA:FWP:UPDATE SKIPPED\n")); // FW update skipped, no newer version available
} else if (_msg.type == ST_FIRMWARE_RESPONSE) {
if (_fwUpdateOngoing) {
// Save block to flash
setIndication(INDICATION_FW_UPDATE_RX);
OTA_DEBUG(PSTR("OTA:FWP:RECV B=%04X\n"), _fwBlock); // received FW block
// extract FW block
ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)_msg.data;
// write to flash
_flash.writeBytes( ((_fwBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
// wait until flash written
while ( _flash.busy() );
_fwBlock--;
if (!_fwBlock) {
// We're finished! Do a checksum and reboot.
OTA_DEBUG(PSTR("OTA:FWP:FW END\n")); // received FW block
_fwUpdateOngoing = false;
if (transportIsValidFirmware()) {
OTA_DEBUG(PSTR("OTA:FWP:CRC OK\n")); // FW checksum ok
// All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it)
uint16_t fwsize = FIRMWARE_BLOCK_SIZE * _fc.blocks;
uint8_t OTAbuffer[10] = {'F','L','X','I','M','G',':',(uint8_t)(fwsize >> 8),(uint8_t)(fwsize & 0xff),':'};
_flash.writeBytes(0, OTAbuffer, 10);
// Write the new firmware config to eeprom
hwWriteConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
hwReboot();
} else {
setIndication(INDICATION_ERR_FW_CHECKSUM);
OTA_DEBUG(PSTR("!OTA:FWP:CRC FAIL\n"));
}
}
// reset flags
_fwRetry = MY_OTA_RETRY+1;
_fwLastRequestTime = 0;
} else {
OTA_DEBUG(PSTR("!OTA:FWP:NO UPDATE\n"));
}
return true;
}
return false;
}
void presentBootloaderInformation(){
RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)_msgTmp.data;
mSetLength(_msgTmp, sizeof(RequestFirmwareConfig));
mSetCommand(_msgTmp, C_STREAM);
mSetPayloadType(_msgTmp,P_CUSTOM);
// copy node settings to reqFWConfig
memcpy(reqFWConfig,&_fc,sizeof(NodeFirmwareConfig));
// add bootloader information
reqFWConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION;
_fwUpdateOngoing = false;
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false));
}
// do a crc16 on the whole received firmware
bool transportIsValidFirmware() {
// init crc
uint16_t crc = ~0;
for (uint16_t i = 0; i < _fc.blocks * FIRMWARE_BLOCK_SIZE; ++i) {
crc ^= _flash.readByte(i + FIRMWARE_START_OFFSET);
for (int8_t j = 0; j < 8; ++j) {
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc = (crc >> 1);
}
}
return crc == _fc.crc;
}
+141
View File
@@ -0,0 +1,141 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyOTAFirmwareUpdate.h
*
* @defgroup MyOTAFirmwaregrp MyOTAFirmwareUpdate
* @ingroup internals
* @{
*
* MyOTAFirmwareUpdate-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error or warning
* - SYSTEM:
* - <b>OTA</b> messages emitted by MyOTAFirmwareUpdate
* - SUB SYSTEMS:
* - OTA:<b>FRQ</b> from @ref firmwareOTAUpdateRequest()
* - OTA:<b>FWP</b> from @ref firmwareOTAUpdateProcess()
*
* MyOTAFirmwareUpdate debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|------|-------|-------------------------------------------|----------------------------------------------------------------------------
* | | OTA | FWP | UPDATE | FW update initiated
* |!| OTA | FWP | FLASH INIT FAIL | Failed to initialise flash
* | | OTA | FWP | UPDATE SKIPPED | FW update skipped, no newer version available
* | | OTA | FWP | RECV B=%04X | Received FW block (B)
* | | OTA | FWP | FW END | FW received, proceed to CRC verification
* | | OTA | FWP | CRC OK | FW CRC verification OK
* |!| OTA | FWP | CRC FAIL | FW CRC verification failed
* | | OTA | FRQ | FW REQ,T=%04X,V=%04X,B=%04X | Request FW update, FW type (T), version (V), block (B)
* |!| OTA | FRQ | FW UPD FAIL | FW update failed
*
*
* @brief API declaration for MyOTAFirmwareUpdate
*/
#ifndef MyOTAFirmwareUpdate_h
#define MyOTAFirmwareUpdate_h
#include "MySensorsCore.h"
#define FIRMWARE_BLOCK_SIZE (16) //!< Size of each firmware block
#define FIRMWARE_MAX_REQUESTS (5) //!< Number of times a firmware block should be requested before giving up
#define MY_OTA_RETRY (5) //!< Number of times to request a fw block before giving up
#define MY_OTA_RETRY_DELAY (500) //!< Number of milliseconds before re-requesting a FW block
#define FIRMWARE_START_OFFSET (10) //!< Start offset for firmware in flash (DualOptiboot wants to keeps a signature first)
#define MY_OTA_BOOTLOADER_MAJOR_VERSION (3) //!< Bootloader version major
#define MY_OTA_BOOTLOADER_MINOR_VERSION (0) //!< Bootloader version minor
#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) //!< Bootloader version
#define OTA_DEBUG(x,...) debug(x, ##__VA_ARGS__) //!< debug
/**
* @brief FW config structure, stored in eeprom
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t blocks; //!< Number of blocks
uint16_t crc; //!< CRC of block data
} __attribute__((packed)) NodeFirmwareConfig;
/**
* @brief FW config request structure
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t blocks; //!< Number of blocks
uint16_t crc; //!< CRC of block data
uint16_t BLVersion; //!< Bootloader version
} __attribute__((packed)) RequestFirmwareConfig;
/**
* @brief FW block request structure
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
} __attribute__((packed)) RequestFWBlock;
/**
* @brief FW block reply structure
*/
typedef struct {
uint16_t type; //!< Type of config
uint16_t version; //!< Version of config
uint16_t block; //!< Block index
uint8_t data[FIRMWARE_BLOCK_SIZE]; //!< Block data
} __attribute__((packed)) ReplyFWBlock;
/**
* @brief Read firmware settings from EEPROM
*
* Current firmware settings (type, version, crc, blocks) are read into _fc
*/
void readFirmwareSettings();
/**
* @brief Handle OTA FW update requests
*/
void firmwareOTAUpdateRequest();
/**
* @brief Handle OTA FW update responses
*
* This function handles incoming OTA FW packets and stores them to external flash (Sensebender)
*/
bool firmwareOTAUpdateProcess();
/**
* @brief Validate uploaded FW CRC
*
* This function verifies if uploaded FW CRC is valid
*/
bool transportIsValidFirmware();
/**
* @brief Present bootloader/FW information upon startup
*/
void presentBootloaderInformation();
#endif
/** @}*/
+61
View File
@@ -0,0 +1,61 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/*
* This file contains settings that for various reasons are unsuitable to store in git
* Rename it by removing the .sample extension and place it in the sketch folder and include
* it in the sketch:
* #include "MyPrivateConfig.h"
* Make sure to include it before
* #include <MySensors.h>
* Then adapt the contents to your personal preference.
*/
#ifndef MyPrivateConfig_h
#define MyPrivateConfig_h
/**********************************
* Node identification Settings
***********************************/
//#define MY_NODE_ID 1
/**********************************
* Message Signing Settings
***********************************/
// Pin used for random generation in soft signing (do not connect anything to this when enabled)
#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7 // A7 -
// Enable node whitelisting
//#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}}
/**********************************
* NRF24L01 Driver Defaults
***********************************/
// Enables RF24 encryption (all nodes and gateway must have this enabled)
//#define MY_RF24_ENABLE_ENCRYPTION
/**********************************
* RFM69 Driver Defaults
***********************************/
// Default network id. Use the same for all nodes that will talk to each other
#define MY_RFM69_NETWORKID 100
// Enable this for encryption of packets
//#define MY_RFM69_ENABLE_ENCRYPTION
#endif
+34
View File
@@ -0,0 +1,34 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyProtocol_h
#define MyProtocol_h
#include "MySensorsCore.h"
// parse(message, inputString)
// parse a string into a message element
// returns true if successfully parsed the input string
bool protocolParse(MyMessage &message, char *inputString);
// Format MyMessage to the protocol represenataion
char *protocolFormat(MyMessage &message);
#endif
+200
View File
@@ -0,0 +1,200 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyTransport.h"
#include "MyProtocol.h"
#include <string.h>
uint8_t protocolH2i(char c);
char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH];
char _convBuffer[MAX_PAYLOAD*2+1];
bool protocolParse(MyMessage &message, char *inputString) {
char *str, *p, *value=NULL;
uint8_t bvalue[MAX_PAYLOAD];
uint8_t blen = 0;
int i = 0;
uint8_t command = 0;
// Extract command data coming on serial line
for (str = strtok_r(inputString, ";", &p); // split using semicolon
str && i < 6; // loop while str is not null an max 5 times
str = strtok_r(NULL, ";", &p) // get subsequent tokens
) {
switch (i) {
case 0: // Radioid (destination)
message.destination = atoi(str);
break;
case 1: // Childid
message.sensor = atoi(str);
break;
case 2: // Message type
command = atoi(str);
mSetCommand(message, command);
break;
case 3: // Should we request ack from destination?
mSetRequestAck(message, atoi(str)?1:0);
break;
case 4: // Data type
message.type = atoi(str);
break;
case 5: // Variable value
if (command == C_STREAM) {
blen = 0;
uint8_t val;
while (*str) {
val = protocolH2i(*str++) << 4;
val += protocolH2i(*str++);
bvalue[blen] = val;
blen++;
}
} else {
value = str;
// Remove trailing carriage return and newline character (if it exists)
uint8_t lastCharacter = strlen(value)-1;
if (value[lastCharacter] == '\r')
value[lastCharacter] = 0;
if (value[lastCharacter] == '\n')
value[lastCharacter] = 0;
}
break;
}
i++;
}
//debug(PSTR("Received %d"), i);
// Check for invalid input
if (i < 5)
return false;
message.sender = GATEWAY_ADDRESS;
message.last = GATEWAY_ADDRESS;
mSetAck(message, false);
if (command == C_STREAM)
message.set(bvalue, blen);
else
message.set(value);
return true;
}
char * protocolFormat(MyMessage &message) {
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%d;%d;%d;%d;%d;%s\n"), message.sender, message.sensor, (uint8_t)mGetCommand(message), (uint8_t)mGetAck(message), message.type, message.getString(_convBuffer));
return _fmtBuffer;
}
char * protocolFormatMQTTTopic(const char* prefix, MyMessage &message) {
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/%d/%d/%d/%d/%d"), prefix, message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type);
return _fmtBuffer;
}
char * protocolFormatMQTTSubscribe(const char* prefix) {
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/+/+/+/+/+"), prefix);
return _fmtBuffer;
}
#ifdef MY_GATEWAY_MQTT_CLIENT
bool protocolMQTTParse(MyMessage &message, char* topic, uint8_t* payload, unsigned int length) {
char *str, *p;
uint8_t i = 0;
uint8_t bvalue[MAX_PAYLOAD];
uint8_t blen = 0;
uint8_t command = 0;
for (str = strtok_r(topic, "/", &p); str && i <= 5;
str = strtok_r(NULL, "/", &p)) {
switch (i) {
case 0: {
// Topic prefix
if (strcmp(str, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) != 0) {
// Message not for us or malformed!
return false;
}
break;
}
case 1: {
// Node id
message.destination = atoi(str);
break;
}
case 2: {
// Sensor id
message.sensor = atoi(str);
break;
}
case 3: {
// Command type
command = atoi(str);
mSetCommand(message, command);
break;
}
case 4: {
// Ack flag
mSetRequestAck(message, atoi(str)?1:0);
break;
}
case 5: {
// Sub type
message.type = atoi(str);
break;
}
}
i++;
}
if (i != 6)
return false;
message.sender = GATEWAY_ADDRESS;
message.last = GATEWAY_ADDRESS;
mSetAck(message, false);
// Add payload
if (command == C_STREAM) {
blen = 0;
uint8_t val;
while (*payload) {
val = protocolH2i(*payload++) << 4;
val += protocolH2i(*payload++);
bvalue[blen] = val;
blen++;
}
message.set(bvalue, blen);
} else {
char* ca;
ca = (char *) payload;
ca += length;
*ca = '\0';
message.set((const char*) payload);
}
return true;
}
#endif
uint8_t protocolH2i(char c) {
uint8_t i = 0;
if (c <= '9')
i += c - '0';
else if (c >= 'a')
i += c - 'a' + 10;
else
i += c - 'A' + 10;
return i;
}
+589
View File
@@ -0,0 +1,589 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MySensorsCore.h"
ControllerConfig _cc; // Configuration coming from controller
NodeConfig _nc; // Essential settings for node to work
MyMessage _msg; // Buffer for incoming messages.
MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others).
bool _nodeRegistered = false;
#if defined(MY_DEBUG)
char _convBuf[MAX_PAYLOAD*2+1];
#endif
void (*_timeCallback)(unsigned long); // Callback for requested time messages
void _process() {
hwWatchdogReset();
#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
ledsProcess();
#endif
#if defined(MY_INCLUSION_MODE_FEATURE)
inclusionProcess();
#endif
#if defined(MY_GATEWAY_FEATURE)
gatewayTransportProcess();
#endif
#if defined(MY_RADIO_FEATURE)
transportProcess();
#endif
#if defined(__linux__)
// To avoid high cpu usage
usleep(10000); // 10ms
#endif
}
void _infiniteLoop() {
while(1) {
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
ledsProcess();
#endif
#if defined(__linux__)
exit(1);
#endif
}
}
void _begin() {
hwWatchdogReset();
if (preHwInit)
preHwInit();
hwInit();
debug(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",VER=" MYSENSORS_LIBRARY_VERSION "\n"));
// Call before() in sketch (if it exists)
if (before) {
debug(PSTR("MCO:BGN:BFR\n")); // before callback
before();
}
#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN)
ledsInit();
#endif
signerInit();
// Read latest received controller configuration from EEPROM
// Note: _cc.isMetric is bool, hence empty EEPROM (=0xFF) evaluates to true (default)
hwReadConfigBlock((void*)&_cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig));
#if defined(MY_OTA_FIRMWARE_FEATURE)
// Read firmware config from EEPROM, i.e. type, version, CRC, blocks
readFirmwareSettings();
#endif
#if defined(MY_RADIO_FEATURE)
// Save static parent id in eeprom (used by bootloader)
hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, MY_PARENT_NODE_ID);
transportInitialise();
while (!isTransportReady()) {
hwWatchdogReset();
transportProcess();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
}
#endif
#ifdef MY_NODE_LOCK_FEATURE
// Check if node has been locked down
if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0) {
// Node is locked, check if unlock pin is asserted, else hang the node
pinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP);
// Make a short delay so we are sure any large external nets are fully pulled
unsigned long enter = hwMillis();
while (hwMillis() - enter < 2);
if (digitalRead(MY_NODE_UNLOCK_PIN) == 0) {
// Pin is grounded, reset lock counter
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX);
// Disable pullup
pinMode(MY_NODE_UNLOCK_PIN, INPUT);
setIndication(INDICATION_ERR_LOCKED);
debug(PSTR("MCO:BGN:NODE UNLOCKED\n"));
} else {
// Disable pullup
pinMode(MY_NODE_UNLOCK_PIN, INPUT);
nodeLock("LDB"); //Locked during boot
}
} else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0xFF) {
// Reset walue
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX);
}
#endif
#if defined(MY_GATEWAY_FEATURE)
#if defined(MY_INCLUSION_BUTTON_FEATURE)
inclusionInit();
#endif
// initialise the transport driver
if (!gatewayTransportInit()) {
setIndication(INDICATION_ERR_INIT_GWTRANSPORT);
debug(PSTR("!MCO:BGN:TSP FAIL\n"));
// Nothing more we can do
_infiniteLoop();
}
#endif
#if !defined(MY_GATEWAY_FEATURE)
presentNode();
#endif
// register node
_registerNode();
// Call sketch setup
if (setup) {
debug(PSTR("MCO:BGN:STP\n")); // setup callback
setup();
}
debug(PSTR("MCO:BGN:INIT OK,ID=%d,PAR=%d,DIS=%d,REG=%d\n"), _nc.nodeId, _nc.parentNodeId, _nc.distance, _nodeRegistered);
hwWatchdogReset();
}
void _registerNode() {
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
debug(PSTR("MCO:REG:REQ\n")); // registration request
setIndication(INDICATION_REQ_REGISTRATION);
_nodeRegistered = MY_REGISTRATION_DEFAULT;
uint8_t counter = MY_REGISTRATION_RETRIES;
// only proceed if register response received or retries exceeded
do {
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_REGISTRATION_REQUEST, false).set(MY_CORE_VERSION));
} while (!wait(2000, C_INTERNAL, I_REGISTRATION_RESPONSE) && counter--);
#else
_nodeRegistered = true;
debug(PSTR("MCO:REG:NOT NEEDED\n"));
#endif
}
void presentNode() {
setIndication(INDICATION_PRESENT);
// Present node and request config
#if defined(MY_GATEWAY_FEATURE)
// Send presentation for this gateway device
#if defined(MY_REPEATER_FEATURE)
present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
present(NODE_SENSOR_ID, S_ARDUINO_NODE);
#endif
#else
#if defined(MY_OTA_FIRMWARE_FEATURE)
presentBootloaderInformation();
#endif
// Send signing preferences for this node to the GW
signerPresentation(_msgTmp, GATEWAY_ADDRESS);
// Send presentation for this radio node
#if defined(MY_REPEATER_FEATURE)
present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE);
#else
present(NODE_SENSOR_ID, S_ARDUINO_NODE);
#endif
// Send a configuration exchange request to controller
// Node sends parent node. Controller answers with latest node configuration
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CONFIG, false).set(_nc.parentNodeId));
// Wait configuration reply.
wait(2000, C_INTERNAL, I_CONFIG);
#endif
if (presentation)
presentation();
}
uint8_t getNodeId() {
return _nc.nodeId;
}
uint8_t getParentNodeId() {
return _nc.parentNodeId;
}
ControllerConfig getConfig() {
return _cc;
}
bool _sendRoute(MyMessage &message) {
#if defined(MY_CORE_ONLY)
(void)message;
#endif
#if defined(MY_GATEWAY_FEATURE)
if (message.destination == _nc.nodeId) {
// This is a message sent from a sensor attached on the gateway node.
// Pass it directly to the gateway transport layer.
return gatewayTransportSend(message);
}
#endif
#if defined(MY_RADIO_FEATURE)
return transportSendRoute(message);
#else
return false;
#endif
}
bool send(MyMessage &message, bool enableAck) {
message.sender = _nc.nodeId;
mSetCommand(message, C_SET);
mSetRequestAck(message, enableAck);
#if defined(MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
if (_nodeRegistered) {
return _sendRoute(message);
}
else {
debug(PSTR("!MCO:SND:NODE NOT REG\n")); // node not registered
return false;
}
#else
return _sendRoute(message);
#endif
}
void sendBatteryLevel(uint8_t value, bool enableAck) {
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL, enableAck).set(value));
}
void sendHeartbeat(void) {
#if defined(MY_RADIO_FEATURE)
uint32_t heartbeat = transportGetHeartbeat();
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE, false).set(heartbeat));
#endif
}
void present(uint8_t childSensorId, uint8_t sensorType, const char *description, bool enableAck) {
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType, enableAck).set(childSensorId==NODE_SENSOR_ID?MYSENSORS_LIBRARY_VERSION:description));
}
void sendSketchInfo(const char *name, const char *version, bool enableAck) {
if (name) _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME, enableAck).set(name));
if (version) _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION, enableAck).set(version));
}
void request(uint8_t childSensorId, uint8_t variableType, uint8_t destination) {
_sendRoute(build(_msgTmp, destination, childSensorId, C_REQ, variableType, false).set(""));
}
void requestTime() {
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME, false).set(""));
}
// Message delivered through _msg
bool _processInternalMessages() {
uint8_t type = _msg.type;
if (_msg.sender == GATEWAY_ADDRESS) {
if (type == I_REBOOT) {
#if !defined(MY_DISABLE_REMOTE_RESET)
// Requires MySensors or other bootloader with watchdogs enabled
setIndication(INDICATION_REBOOT);
hwReboot();
#endif
}
else if (type == I_REGISTRATION_RESPONSE) {
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
_nodeRegistered = _msg.getBool();
setIndication(INDICATION_GOT_REGISTRATION);
debug(PSTR("MCO:PIM:NODE REG=%d\n"), _nodeRegistered); // node registration
#endif
}
else if (type == I_CONFIG) {
// Pick up configuration from controller (currently only metric/imperial) and store it in eeprom if changed
_cc.isMetric = _msg.data[0] == 0x00 || _msg.data[0] == 'M'; // metric if null terminated or M
hwWriteConfig(EEPROM_CONTROLLER_CONFIG_ADDRESS, _cc.isMetric);
}
else if (type == I_PRESENTATION) {
// Re-send node presentation to controller
presentNode();
}
else if (type == I_HEARTBEAT_REQUEST) {
sendHeartbeat();
}
else if (type == I_TIME) {
// Deliver time to callback
if (receiveTime)
receiveTime(_msg.getULong());
}
else if (type == I_CHILDREN) {
#if defined(MY_REPEATER_FEATURE)
if (_msg.data[0] == 'C') {
// Clears child relay data for this node
setIndication(INDICATION_CLEAR_ROUTING);
transportClearRoutingTable();
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN, false).set("ok"));
}
#endif
}
else if (type == I_DEBUG) {
#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG)
const char debug_msg = _msg.data[0];
if (debug_msg == 'R') { // routing table
#if defined(MY_REPEATER_FEATURE)
for (uint16_t cnt = 0; cnt < SIZE_ROUTES; cnt++) {
const uint8_t route = transportGetRoute(cnt);
if (route != BROADCAST_ADDRESS) {
debug(PSTR("MCO:PIM:ROUTE N=%d,R=%d\n"), cnt, route);
uint8_t outBuf[2] = { (uint8_t)cnt,route };
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(outBuf, 2));
wait(200);
}
}
#endif
}
else if (debug_msg == 'V') { // CPU voltage
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwCPUVoltage()));
}
else if (debug_msg == 'F') { // CPU frequency in 1/10Mhz
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwCPUFrequency()));
}
else if (debug_msg == 'M') { // free memory
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwFreeMem()));
}
else if (debug_msg == 'E') { // clear MySensors eeprom area and reboot
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set("ok"));
for (int i = EEPROM_START; i<EEPROM_LOCAL_CONFIG_ADDRESS; i++) hwWriteConfig(i, 0xFF);
setIndication(INDICATION_REBOOT);
hwReboot();
}
#endif
}
else return false;
}
else {
// sender is a node
if (type == I_REGISTRATION_REQUEST) {
#if defined(MY_GATEWAY_FEATURE)
// registeration requests are exclusively handled by GW/Controller
#if !defined(MY_REGISTRATION_CONTROLLER)
// auto registration if version compatible
bool approveRegistration = true;
#if defined(MY_CORE_COMPATIBILITY_CHECK)
approveRegistration = (_msg.getByte() >= MY_CORE_MIN_VERSION);
#endif
#if (F_CPU>16000000)
// delay for fast GW and slow nodes
delay(5);
#endif
_sendRoute(build(_msgTmp, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REGISTRATION_RESPONSE, false).set(approveRegistration));
#else
return false; // processing of this request via controller
#endif
#endif
}
else return false;
}
return true;
}
void saveState(uint8_t pos, uint8_t value) {
hwWriteConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos, value);
}
uint8_t loadState(uint8_t pos) {
return hwReadConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos);
}
void wait(unsigned long ms) {
unsigned long enter = hwMillis();
while (hwMillis() - enter < ms) {
_process();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
}
}
bool wait(unsigned long ms, uint8_t cmd, uint8_t msgtype) {
unsigned long enter = hwMillis();
// invalidate msg type
_msg.type = !msgtype;
bool expectedResponse = false;
while ( (hwMillis() - enter < ms) && !expectedResponse ) {
_process();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgtype);
}
return expectedResponse;
}
int8_t _sleep(const uint32_t ms, const bool smartSleep, const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2) {
debug(PSTR("MCO:SLP:MS=%lu,SMS=%d,I1=%d,M1=%d,I2=%d,M2=%d\n"), ms, smartSleep, interrupt1, mode1, interrupt2, mode2);
// OTA FW feature: do not sleep if FW update ongoing
#if defined(MY_OTA_FIRMWARE_FEATURE)
if (_fwUpdateOngoing) {
debug(PSTR("!MCO:SLP:FWUPD\n")); // sleeping not possible, FW update ongoing
wait(ms);
return MY_SLEEP_NOT_POSSIBLE;
}
#endif
// repeater feature: sleeping not possible
#if defined(MY_REPEATER_FEATURE)
(void)smartSleep;
(void)interrupt1;
(void)mode1;
(void)interrupt2;
(void)mode2;
debug(PSTR("!MCO:SLP:REP\n")); // sleeping not possible, repeater feature enabled
wait(ms);
return MY_SLEEP_NOT_POSSIBLE;
#else
uint32_t sleepingTime = ms;
#if defined(MY_RADIO_FEATURE)
// Do not sleep if transport not ready
if (!isTransportReady()) {
debug(PSTR("!MCO:SLP:TNR\n")); // sleeping not possible, transport not ready
uint32_t sleepEnter = hwMillis();
uint32_t sleepDelta = 0;
while (!isTransportReady() && (sleepDelta < sleepingTime) && (sleepDelta < MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS)) {
_process();
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
sleepDelta = hwMillis() - sleepEnter;
}
// sleep remainder
if (sleepDelta < sleepingTime) {
sleepingTime -= sleepDelta; // calculate remaining sleeping time
debug(PSTR("MCO:SLP:MS=%lu\n"), sleepingTime);
}
else {
// no sleeping time left
return MY_SLEEP_NOT_POSSIBLE;
}
}
#endif
if (smartSleep) {
// notify controller about going to sleep
sendHeartbeat();
wait(MY_SMART_SLEEP_WAIT_DURATION_MS); // listen for incoming messages
}
#if defined(MY_RADIO_FEATURE)
debug(PSTR("MCO:SLP:TPD\n")); // sleep, power down transport
transportPowerDown();
#endif
setIndication(INDICATION_SLEEP);
int8_t res = MY_SLEEP_NOT_POSSIBLE; // default
if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 != INTERRUPT_NOT_DEFINED) {
// both IRQs
res = hwSleep(interrupt1, mode1, interrupt2, mode2, sleepingTime);
}
else if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) {
// one IRQ
res = hwSleep(interrupt1, mode1, sleepingTime);
}
else if (interrupt1 == INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) {
// no IRQ
res = hwSleep(sleepingTime);
}
setIndication(INDICATION_WAKEUP);
debug(PSTR("MCO:SLP:WUP=%d\n"), res); // sleep wake-up
return res;
#endif
}
// sleep functions
int8_t sleep(const uint32_t ms, const bool smartSleep) {
return _sleep(ms, smartSleep);
}
int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t ms, const bool smartSleep) {
return _sleep(ms, smartSleep, interrupt, mode);
}
int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2, const uint32_t ms, const bool smartSleep) {
return _sleep(ms, smartSleep, interrupt1, mode1, interrupt2, mode2);
}
// deprecated smartSleep() functions
int8_t smartSleep(const uint32_t ms) {
// compatibility
return _sleep(ms, true);
}
int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t ms) {
// compatibility
return _sleep(ms, true, interrupt, mode);
}
int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2, const uint32_t ms) {
// compatibility
return _sleep(ms, true, interrupt1, mode1, interrupt2, mode2);
}
#ifdef MY_NODE_LOCK_FEATURE
void nodeLock(const char* str) {
// Make sure EEPROM is updated to locked status
hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, 0);
while (1) {
setIndication(INDICATION_ERR_LOCKED);
debug(PSTR("MCO:NLK:NODE LOCKED. TO UNLOCK, GND PIN %d AND RESET\n"), MY_NODE_UNLOCK_PIN);
#if defined(ARDUINO_ARCH_ESP8266)
yield();
#endif
_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID,C_INTERNAL, I_LOCKED, false).set(str));
#if defined(MY_RADIO_FEATURE)
transportPowerDown();
debug(PSTR("MCO:NLK:TPD\n")); // power down transport
#endif
setIndication(INDICATION_SLEEP);
(void)hwSleep((unsigned long)1000*60*30); // Sleep for 30 min before resending LOCKED message
setIndication(INDICATION_WAKEUP);
}
}
#endif
+428
View File
@@ -0,0 +1,428 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MySensorsCore.h
*
* @defgroup MySensorsCoregrp MySensorsCore
* @ingroup internals
* @{
*
* MySensorsCore-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error or warning
* - SYSTEM:
* - <b>MCO</b> messages emitted by MySensorsCore
* - SUB SYSTEMS:
* - MCO:<b>BGN</b> from @ref _begin()
* - MCO:<b>REG</b> from @ref _registerNode()
* - MCO:<b>SND</b> from @ref send()
* - MCO:<b>PIM</b> from @ref _processInternalMessages()
* - MCO:<b>NLK</b> from nodeLock()
*
* MySensorsCore debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|------|-------|-----------------------------------------------|----------------------------------------------------------------------------
* | | MCO | BGN | INIT %%s,CP=%%s,LIB=%%s | Core initialization, capabilities (CP), library version (VER)
* | | MCO | BGN | BFR | Callback before()
* | | MCO | BGN | STP | Callback setup()
* | | MCO | BGN | INIT OK,ID=%%d,PAR=%%d,DIS=%%d,REG=%%d | Core initialized, parent ID (PAR), distance to GW (DIS), registration (REG)
* | | MCO | BGN | NODE UNLOCKED | Node successfully unlocked (see signing chapter)
* |!| MCO | BGN | TSP FAIL | Transport initialization failed
* | | MCO | REG | REQ | Registration request
* | | MCO | REG | NOT NEEDED | No registration needed (i.e. GW)
* |!| MCO | SND | NODE NOT REG | Node is not registered, cannot send message
* | | MCO | PIM | NODE REG=%%d | Registration response received, registration status (REG)
* | | MCO | PIM | ROUTE N=%%d,R=%%d | Routing table, messages to node (N) are routed via node (R)
* | | MCO | SLP | MS=%%lu,SMS=%%d,I1=%%d,M1=%%d,I2=%%d,M2=%%d | Sleep node, time (MS), smartSleep (SMS), Int1/M1, Int2/M2
* | | MCO | SLP | TPD | Sleep node, powerdown transport
* | | MCO | SLP | WUP=%%d | Node woke-up, reason/IRQ (WUP)
* |!| MCO | SLP | FWUPD | Sleeping not possible, FW update ongoing
* |!| MCO | SLP | REP | Sleeping not possible, repeater feature enabled
* | | MCO | NLK | NODE LOCKED. UNLOCK: GND PIN %%d AND RESET | Node locked during booting, see signing chapter for additional information
* | | MCO | NLK | TPD | Powerdown transport
*
*
* @brief API declaration for MySensorsCore
*/
#ifndef MySensorsCore_h
#define MySensorsCore_h
#include "Version.h" // Auto generated by bot
#include "MyConfig.h"
#include "MyEepromAddresses.h"
#include "MyMessage.h"
#include <stddef.h>
#include <stdarg.h>
#define GATEWAY_ADDRESS ((uint8_t)0) //!< Node ID for GW sketch
#define NODE_SENSOR_ID ((uint8_t)255) //!< Node child is always created/presented when a node is started
#define MY_CORE_VERSION ((uint8_t)2) //!< core version
#define MY_CORE_MIN_VERSION ((uint8_t)2) //!< min core version required for compatibility
#define MY_WAKE_UP_BY_TIMER ((int8_t)-1) //!< Sleeping wake up by timer
#define MY_SLEEP_NOT_POSSIBLE ((int8_t)-2) //!< Sleeping not possible
#define INTERRUPT_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no interrupt defined
#define MODE_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no mode defined
#ifdef MY_DEBUG
#define debug(x,...) hwDebugPrint(x, ##__VA_ARGS__) //!< debug
#else
#define debug(x,...) //!< debug NULL
#endif
/**
* @brief Node configuration
*
* This structure stores node-related configurations
*/
struct NodeConfig {
uint8_t nodeId; //!< Current node id
uint8_t parentNodeId; //!< Where this node sends its messages
uint8_t distance; //!< This nodes distance to sensor net gateway (number of hops)
};
/**
* @brief Controller configuration
*
* This structure stores controllerrelated configurations
*/
struct ControllerConfig {
uint8_t isMetric; //!< Flag indicating if metric or imperial measurements are used
};
extern NodeConfig _nc; //!< Node config
extern MyMessage _msg; //!< Buffer for incoming messages
extern MyMessage _msgTmp; //!< Buffer for temporary messages (acks and nonces among others)
#ifdef MY_DEBUG
extern char _convBuf[MAX_PAYLOAD * 2 + 1];
#endif
// **** public functions ********
/**
* Return this nodes id.
*/
uint8_t getNodeId();
/**
* Return the parent node id.
*/
uint8_t getParentNodeId();
/**
* Sends node information to the gateway.
*/
void presentNode();
/**
* Each node must present all attached sensors before any values can be handled correctly by the controller.
* It is usually good to present all attached sensors after power-up in setup().
*
* @param sensorId Select a unique sensor id for this sensor. Choose a number between 0-254.
* @param sensorType The sensor type. See sensor typedef in MyMessage.h.
* @param description A textual description of the sensor.
* @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack.
* @param description A textual description of the sensor.
*/
void present(uint8_t sensorId, uint8_t sensorType, const char *description="", bool ack=false);
/**
* Sends sketch meta information to the gateway. Not mandatory but a nice thing to do.
* @param name String containing a short Sketch name or NULL if not applicable
* @param version String containing a short Sketch version or NULL if not applicable
* @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack.
*
*/
void sendSketchInfo(const char *name, const char *version, bool ack=false);
/**
* Sends a message to gateway or one of the other nodes in the radio network
*
* @param msg Message to send
* @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack.
* @return true Returns true if message reached the first stop on its way to destination.
*/
bool send(MyMessage &msg, bool ack=false);
/**
* Send this nodes battery level to gateway.
* @param level Level between 0-100(%)
* @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack.
*
*/
void sendBatteryLevel(uint8_t level, bool ack=false);
/**
* Send a heartbeat message (I'm alive!) to the gateway/controller.
* The payload will be an incremental 16 bit integer value starting at 1 when sensor is powered on.
*/
void sendHeartbeat(void);
/**
* Requests a value from gateway or some other sensor in the radio network.
* Make sure to add callback-method in begin-method to handle request responses.
*
* @param childSensorId The unique child id for the different sensors connected to this Arduino. 0-254.
* @param variableType The variableType to fetch
* @param destination The nodeId of other node in radio network. Default is gateway
*/
void request(uint8_t childSensorId, uint8_t variableType, uint8_t destination=GATEWAY_ADDRESS);
/**
* Requests time from controller. Answer will be delivered to receiveTime function in sketch.
*
*/
void requestTime();
/**
* Returns the most recent node configuration received from controller
*/
ControllerConfig getConfig();
/**
* Save a state (in local EEPROM). Good for actuators to "remember" state between
* power cycles.
*
* You have 256 bytes to play with. Note that there is a limitation on the number
* of writes the EEPROM can handle (~100 000 cycles on ATMega328).
*
* @param pos The position to store value in (0-255)
* @param value to store in position
*/
void saveState(uint8_t pos, uint8_t value);
/**
* Load a state (from local EEPROM).
*
* @param pos The position to fetch value from (0-255)
* @return Value to store in position
*/
uint8_t loadState(uint8_t pos);
/**
* Wait for a specified amount of time to pass. Keeps process()ing.
* This does not power-down the radio nor the Arduino.
* Because this calls process() in a loop, it is a good way to wait
* in your loop() on a repeater node or sensor that listens to messages.
* @param ms Number of milliseconds to sleep.
*/
void wait(unsigned long ms);
/**
* Wait for a specified amount of time to pass or until specified message received. Keeps process()ing.
* This does not power-down the radio nor the Arduino.
* Because this calls process() in a loop, it is a good way to wait
* in your loop() on a repeater node or sensor that listens to messages.
* @param ms Number of milliseconds to sleep.
* @param cmd Command of incoming message.
* @param msgtype Message type.
* @return True if specified message received
*/
bool wait(unsigned long ms, uint8_t cmd, uint8_t msgtype);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer.
* @param ms Number of milliseconds to sleep.
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep.
* @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint32_t ms, const bool smartSleep = false);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param interrupt Interrupt that should trigger the wakeup
* @param mode RISING, FALLING, CHANGE
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t ms=0, const bool smartSleep = false);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change for two separate interrupts.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param interrupt1 First interrupt that should trigger the wakeup
* @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 Second interrupt that should trigger the wakeup
* @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE)
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep.
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2, const uint32_t ms=0, const bool smartSleep = false);
/**
* \deprecated Use sleep(ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining MY_SMART_SLEEP_WAIT_DURATION to a time (ms).
* @param ms Number of milliseconds to sleep.
* @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint32_t ms);
/**
* \deprecated Use sleep(interrupt, mode, ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining MY_SMART_SLEEP_WAIT_DURATION to a time (ms).
* @param interrupt Interrupt that should trigger the wakeup
* @param mode RISING, FALLING, CHANGE
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t ms = 0);
/**
* \deprecated Use sleep(interrupt1, mode1, interrupt2, mode2, ms, true) instead
* Same as sleep(), send heartbeat and process incoming messages before going to sleep.
* Specify the time to wait for incoming messages by defining MY_SMART_SLEEP_WAIT_DURATION to a time (ms).
* @param interrupt1 First interrupt that should trigger the wakeup
* @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 Second interrupt that should trigger the wakeup
* @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE)
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2, const uint32_t ms=0);
/**
* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change for two separate interrupts.
* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin
* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3
* @param ms Number of milliseconds to sleep or 0 to sleep forever
* @param interrupt1 (optional) First interrupt that should trigger the wakeup
* @param mode1 (optional) Mode for first interrupt (RISING, FALLING, CHANGE)
* @param interrupt2 (optional) Second interrupt that should trigger the wakeup
* @param mode2 (optional) Mode for second interrupt (RISING, FALLING, CHANGE)
* @param smartSleep (optional) Set True if sending heartbeat and process incoming messages before going to sleep.
* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update)
*/
int8_t _sleep(const uint32_t ms, const bool smartSleep = false, const uint8_t interrupt1 = INTERRUPT_NOT_DEFINED, const uint8_t mode1 = MODE_NOT_DEFINED, const uint8_t interrupt2 = INTERRUPT_NOT_DEFINED, const uint8_t mode2 = MODE_NOT_DEFINED);
#ifdef MY_NODE_LOCK_FEATURE
/**
* @ingroup MyLockgrp
* @ingroup internals
* @brief Lock a node and transmit provided message with 30m intervals
*
* This function is called if suspicious activity has exceeded the threshold (see
* @ref ATTACK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM
* while unlocking with a custom bootloader require holding @ref UNLOCK_PIN low during power on/reset.
*
* @param str The string to transmit.
*/
void nodeLock(const char* str);
#endif
// **** private functions ********
/**
* @brief Node initialisation
*/
void _begin();
/**
* @brief Main framework process
*/
void _process(void);
/**
* @brief Processes internal messages
* @return True if received message requires further processing
*/
bool _processInternalMessages();
/**
* @brief Puts node to a infinite loop if unrecoverable situation detected
*/
void _infiniteLoop();
/**
* @brief Handles registration request
*/
void _registerNode();
/**
* @brief Sends message according to routing table
* @param message
* @return True if message successfully sent to next node
*/
bool _sendRoute(MyMessage &message);
/**
* @brief Callback for incoming messages
* @param message
*/
void receive(const MyMessage &message) __attribute__((weak));
/**
* @brief Callback for incoming time messages
*/
void receiveTime(unsigned long) __attribute__((weak));
/**
* @brief Node presenation
*/
void presentation() __attribute__((weak));
/**
* @brief Called before node initialises
*/
void before() __attribute__((weak));
/**
* @brief Called before any hwInitialization is done
*/
void preHwInit() __attribute__((weak));
/**
* @brief Called after node initialises but before main loop
*/
void setup() __attribute__((weak));
/**
* @brief Main loop
*/
void loop() __attribute__((weak));
// Inline function and macros
static inline MyMessage& build(MyMessage &msg, const uint8_t destination, const uint8_t sensor, const uint8_t command, const uint8_t type, const bool enableAck) {
msg.sender = _nc.nodeId;
msg.destination = destination;
msg.sensor = sensor;
msg.type = type;
mSetCommand(msg,command);
mSetRequestAck(msg,enableAck);
mSetAck(msg,false);
return msg;
}
static inline MyMessage& buildGw(MyMessage &msg, const uint8_t type) {
msg.sender = GATEWAY_ADDRESS;
msg.destination = GATEWAY_ADDRESS;
msg.sensor = NODE_SENSOR_ID;
msg.type = type;
mSetCommand(msg, C_INTERNAL);
mSetRequestAck(msg, false);
mSetAck(msg, false);
return msg;
}
#endif
/** @}*/
+448
View File
@@ -0,0 +1,448 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MySigning.h"
#include "drivers/ATSHA204/sha256.h"
#define SIGNING_PRESENTATION_VERSION_1 1
#define SIGNING_PRESENTATION_REQUIRE_SIGNATURES (1 << 0)
#define SIGNING_PRESENTATION_REQUIRE_WHITELISTING (1 << 1)
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) debug(x, ##__VA_ARGS__)
#else
#define SIGN_DEBUG(x,...)
#endif
#if defined(MY_SIGNING_REQUEST_SIGNATURES) && (!defined(MY_SIGNING_ATSHA204) && !defined(MY_SIGNING_SOFT))
#error You have to pick either MY_SIGNING_ATSHA204 or MY_SIGNING_SOFT in order to require signatures!
#endif
#ifdef MY_SIGNING_FEATURE
uint8_t _doSign[32]; // Bitfield indicating which sensors require signed communication
uint8_t _doWhitelist[32]; // Bitfield indicating which sensors require serial salted signatures
MyMessage _msgSign; // Buffer for message to sign.
uint8_t _signingNonceStatus;
#ifdef MY_NODE_LOCK_FEATURE
static uint8_t nof_nonce_requests = 0;
static uint8_t nof_failed_verifications = 0;
#endif
// Status when waiting for signing nonce in signerProcessInternal
enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 };
// Macros for manipulating signing requirement table
#define DO_SIGN(node) (~_doSign[node>>3]&(1<<node%8))
#define SET_SIGN(node) (_doSign[node>>3]&=~(1<<node%8))
#define CLEAR_SIGN(node) (_doSign[node>>3]|=(1<<node%8))
/*
* Stores signing identifier and a new nonce in provided message for signing operations.
*
* All space in message payload buffer is used for signing identifier and nonce.
* Returns false if subsystem is busy processing an ongoing signing operation.
* If successful, this marks the start of a signing operation at the receiving side so
* implementation is expected to do any necessary initializations within this call.
* This function is typically called as action when receiving a I_NONCE_REQUEST message.
*/
#if defined(MY_SIGNING_SOFT)
extern void signerAtsha204SoftInit(void);
extern bool signerAtsha204SoftCheckTimer(void);
extern bool signerAtsha204SoftGetNonce(MyMessage &msg);
extern void signerAtsha204SoftPutNonce(MyMessage &msg);
extern bool signerAtsha204SoftVerifyMsg(MyMessage &msg);
extern bool signerAtsha204SoftSignMsg(MyMessage &msg);
#endif
#if defined(MY_SIGNING_ATSHA204)
extern void signerAtsha204Init(void);
extern bool signerAtsha204CheckTimer(void);
extern bool signerAtsha204GetNonce(MyMessage &msg);
extern void signerAtsha204PutNonce(MyMessage &msg);
extern bool signerAtsha204VerifyMsg(MyMessage &msg);
extern bool signerAtsha204SignMsg(MyMessage &msg);
#endif
// Helper function to centralize signing/verification exceptions
static bool skipSign(MyMessage &msg) {
if mGetAck(msg) {
SIGN_DEBUG(PSTR("Skipping security for ACK on command %d type %d\n"), mGetCommand(msg), msg.type);
return true;
} else if (mGetCommand(msg) == C_INTERNAL &&
(msg.type == I_NONCE_REQUEST || msg.type == I_NONCE_RESPONSE || msg.type == I_SIGNING_PRESENTATION ||
msg.type == I_ID_REQUEST || msg.type == I_ID_RESPONSE ||
msg.type == I_FIND_PARENT_REQUEST || msg.type == I_FIND_PARENT_RESPONSE ||
msg.type == I_HEARTBEAT_REQUEST || msg.type == I_HEARTBEAT_RESPONSE ||
msg.type == I_PING || msg.type == I_PONG ||
msg.type == I_REGISTRATION_REQUEST )) {
SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
return true;
} else if (mGetCommand(msg) == C_STREAM &&
(msg.type == ST_FIRMWARE_REQUEST || msg.type == ST_FIRMWARE_RESPONSE ||
msg.type == ST_SOUND || msg.type == ST_IMAGE)) {
SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
return true;
} else {
return false;
}
}
#endif // MY_SIGNING_FEATURE
// Helper to prepare a signing presentation message
static void prepareSigningPresentation(MyMessage &msg, uint8_t destination) {
// Only supports version 1 for now
build(msg, destination, NODE_SENSOR_ID, C_INTERNAL, I_SIGNING_PRESENTATION, false).set("");
mSetLength(msg, 2);
mSetPayloadType(msg, P_CUSTOM); // displayed as hex
msg.data[0] = SIGNING_PRESENTATION_VERSION_1;
msg.data[1] = 0;
}
void signerInit(void) {
#if defined(MY_SIGNING_FEATURE)
// Read out the signing requirements from EEPROM
hwReadConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doSign));
// Read out the whitelist requirements from EEPROM
hwReadConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doWhitelist));
#if defined(MY_SIGNING_SOFT)
signerAtsha204SoftInit();
#endif
#if defined(MY_SIGNING_ATSHA204)
signerAtsha204Init();
#endif
#endif
}
void signerPresentation(MyMessage &msg, uint8_t destination) {
prepareSigningPresentation(msg, destination);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
SIGN_DEBUG(PSTR("Signing required\n"));
#endif
#if defined(MY_SIGNING_NODE_WHITELISTING)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
SIGN_DEBUG(PSTR("Whitelisting required\n"));
#endif
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("Failed to transmit signing presentation!"));
}
#if defined(MY_SIGNING_FEATURE)
// If we do support signing, wait for the gateway to tell us how it prefer us to transmit our messages
if (destination == GATEWAY_ADDRESS) {
SIGN_DEBUG(PSTR("Waiting for GW to send signing preferences...\n"));
wait(2000, C_INTERNAL, I_SIGNING_PRESENTATION);
}
#endif
}
bool signerProcessInternal(MyMessage &msg) {
uint8_t sender = msg.sender;
(void)sender;
if (mGetCommand(msg) == C_INTERNAL) {
#if !defined(MY_SIGNING_FEATURE) && defined(MY_GATEWAY_FEATURE)
// If we act as gateway and do not have the signing feature and receive a signing request we still
// need to do make sure the requester does not believe the gateway still require signatures
if (msg.type == I_SIGNING_PRESENTATION) {
prepareSigningPresentation(msg, sender);
SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures because we do not support it\n"),
sender);
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("Failed to transmit signing presentation!"));
}
return true; // No need to further process I_SIGNING_PRESENTATION in this case
}
#elif defined(MY_SIGNING_FEATURE)
if (msg.type == I_NONCE_REQUEST) {
#if defined(MY_NODE_LOCK_FEATURE)
nof_nonce_requests++;
SIGN_DEBUG(PSTR("Nonce requests left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests);
if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) {
nodeLock("TMNR"); //Too many nonces requested
}
#endif
#if defined(MY_SIGNING_SOFT)
if (signerAtsha204SoftGetNonce(msg)) {
#endif
#if defined(MY_SIGNING_ATSHA204)
if (signerAtsha204GetNonce(msg)) {
#endif
if (!_sendRoute(build(msg, msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_NONCE_RESPONSE, false))) {
SIGN_DEBUG(PSTR("Failed to transmit nonce!\n"));
} else {
SIGN_DEBUG(PSTR("Transmitted nonce\n"));
}
} else {
SIGN_DEBUG(PSTR("Failed to generate nonce!\n"));
}
return true; // No need to further process I_NONCE_REQUEST
} else if (msg.type == I_SIGNING_PRESENTATION) {
if (msg.data[0] != SIGNING_PRESENTATION_VERSION_1) {
SIGN_DEBUG(PSTR("Unsupported signing presentation version (%d)!\n"), msg.data[0]);
return true; // Just drop this presentation message
}
// We only handle version 1 here...
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
// We received an indicator that the sender require us to sign all messages we send to it
SIGN_DEBUG(PSTR("Mark node %d as one that require signed messages\n"), msg.sender);
SET_SIGN(msg.sender);
} else {
// We received an indicator that the sender does not require us to sign all messages we send to it
SIGN_DEBUG(PSTR("Mark node %d as one that do not require signed messages\n"), msg.sender);
CLEAR_SIGN(msg.sender);
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
// We received an indicator that the sender require us to salt signatures with serial
SIGN_DEBUG(PSTR("Mark node %d as one that require whitelisting\n"), msg.sender);
SET_WHITELIST(msg.sender);
} else {
// We received an indicator that the sender does not require us to sign all messages we send to it
SIGN_DEBUG(PSTR("Mark node %d as one that do not require whitelisting\n"), msg.sender);
CLEAR_WHITELIST(msg.sender);
}
// Save updated tables
hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doSign));
hwWriteConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS,
sizeof(_doWhitelist));
// Inform sender about our preference if we are a gateway, but only require signing if the sender
// required signing
// We do not want a gateway to require signing from all nodes in a network just because it wants one node
// to sign it's messages
#if defined(MY_GATEWAY_FEATURE)
prepareSigningPresentation(msg, sender);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
if (DO_SIGN(sender)) {
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
}
#endif
#if defined(MY_SIGNING_NODE_WHITELISTING)
if (DO_WHITELIST(sender)) {
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
}
#endif
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
SIGN_DEBUG(PSTR("Informing node %d that we require signatures\n"), sender);
} else {
SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures\n"), sender);
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
SIGN_DEBUG(PSTR("Informing node %d that we require whitelisting\n"), sender);
} else {
SIGN_DEBUG(PSTR("Informing node %d that we do not require whitelisting\n"), sender);
}
if (!_sendRoute(msg)) {
SIGN_DEBUG(PSTR("Failed to transmit signing presentation!"));
}
#endif // MY_GATEWAY_FEATURE
return true; // No need to further process I_SIGNING_PRESENTATION
} else if (msg.type == I_NONCE_RESPONSE) {
// Proceed with signing if nonce has been received
SIGN_DEBUG(PSTR("Nonce received from %d. Proceeding with signing...\n"), sender);
if (sender != _msgSign.destination) {
SIGN_DEBUG(PSTR("Nonce did not come from the destination (%d) of the message to be signed! "
"It came from %d.\n"), _msgSign.destination, sender);
SIGN_DEBUG(PSTR("Silently discarding this nonce\n"));
return true; // No need to further process I_NONCE_RESPONSE
}
#if defined(MY_SIGNING_SOFT)
signerAtsha204SoftPutNonce(msg);
#endif
#if defined(MY_SIGNING_ATSHA204)
signerAtsha204PutNonce(msg);
#endif
#if defined(MY_SIGNING_SOFT)
if (!signerAtsha204SoftSignMsg(_msgSign)) {
#endif
#if defined(MY_SIGNING_ATSHA204)
if (!signerAtsha204SignMsg(_msgSign)) {
#endif
SIGN_DEBUG(PSTR("Failed to sign message!\n"));
} else {
SIGN_DEBUG(PSTR("Message signed\n"));
_signingNonceStatus = SIGN_OK; // _msgSign now contains the signed message pending transmission
}
return true; // No need to further process I_NONCE_RESPONSE
}
#endif // MY_SIGNING_FEATURE
}
return false;
}
bool signerCheckTimer(void) {
#if defined(MY_SIGNING_SOFT)
return signerAtsha204SoftCheckTimer();
#elif defined(MY_SIGNING_ATSHA204)
return signerAtsha204CheckTimer();
#else
return true; // Without a configured backend, we always give "positive" results
#endif
}
bool signerSignMsg(MyMessage &msg) {
#if defined(MY_SIGNING_FEATURE)
// If destination is known to require signed messages and we are the sender,
// sign this message unless it is a handshake message
if (DO_SIGN(msg.destination) && msg.sender == _nc.nodeId) {
if (skipSign(msg)) {
return true;
} else {
// Send nonce-request
_signingNonceStatus=SIGN_WAITING_FOR_NONCE;
if (!_sendRoute(build(_msgSign, msg.destination, msg.sensor, C_INTERNAL, I_NONCE_REQUEST, false).set(""))) {
SIGN_DEBUG(PSTR("Failed to transmit nonce request!\n"));
return false;
}
SIGN_DEBUG(PSTR("Nonce requested from %d. Waiting...\n"), msg.destination);
// We have to wait for the nonce to arrive before we can sign our original message
// Other messages could come in-between. We trust _process() takes care of them
unsigned long enter = hwMillis();
_msgSign = msg; // Copy the message to sign since message buffer might be touched in _process()
while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS && _signingNonceStatus==SIGN_WAITING_FOR_NONCE) {
_process();
}
if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) {
SIGN_DEBUG(PSTR("Timeout waiting for nonce!\n"));
return false;
}
if (_signingNonceStatus == SIGN_OK) {
// process() received a nonce and signerProcessInternal successfully signed the message
msg = _msgSign; // Write the signed message back
SIGN_DEBUG(PSTR("Message to send has been signed\n"));
} else {
SIGN_DEBUG(PSTR("Message to send could not be signed!\n"));
return false;
}
// After this point, only the 'last' member of the message structure is allowed to be altered if the
// message has been signed, or signature will become invalid and the message rejected by the receiver
}
} else if (_nc.nodeId == msg.sender) {
mSetSigned(msg, 0); // Message is not supposed to be signed, make sure it is marked unsigned
}
#else
(void)msg;
#endif // MY_SIGNING_FEATURE
return true;
}
bool signerVerifyMsg(MyMessage &msg) {
bool verificationResult = true;
// Before processing message, reject unsigned messages if signing is required and check signature
// (if it is signed and addressed to us)
// Note that we do not care at all about any signature found if we do not require signing
#if defined(MY_SIGNING_FEATURE) && defined(MY_SIGNING_REQUEST_SIGNATURES)
// If we are a node, or we are a gateway and the sender require signatures
// and we are the destination...
if ((!MY_IS_GATEWAY || DO_SIGN(msg.sender)) && msg.destination == _nc.nodeId) {
// Internal messages of certain types are not verified
if (skipSign(msg)) {
verificationResult = true;
}
else if (!mGetSigned(msg)) {
// Got unsigned message that should have been signed
SIGN_DEBUG(PSTR("Message is not signed, but it should have been!\n"));
verificationResult = false;
} else {
#if defined(MY_SIGNING_SOFT)
verificationResult = signerAtsha204SoftVerifyMsg(msg);
#endif
#if defined(MY_SIGNING_ATSHA204)
verificationResult = signerAtsha204VerifyMsg(msg);
#endif
if (!verificationResult) {
SIGN_DEBUG(PSTR("Signature verification failed!\n"));
}
#if defined(MY_NODE_LOCK_FEATURE)
if (verificationResult) {
// On successful verification, clear lock counters
nof_nonce_requests = 0;
nof_failed_verifications = 0;
} else {
nof_failed_verifications++;
SIGN_DEBUG(PSTR("Failed verification attempts left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications);
if (nof_failed_verifications >= MY_NODE_LOCK_COUNTER_MAX) {
nodeLock("TMFV"); //Too many failed verifications
}
}
#endif
mSetSigned(msg,0); // Clear the sign-flag now as verification is completed
}
}
#else
(void)msg;
#endif // MY_SIGNING_REQUEST_SIGNATURES
return verificationResult;
}
static uint8_t sha256_hash[32];
Sha256Class _soft_sha256;
void signerSha256Init(void) {
memset(sha256_hash, 0, 32);
_soft_sha256.init();
}
void signerSha256Update(const uint8_t* data, size_t sz) {
for (size_t i = 0; i < sz; i++) {
_soft_sha256.write(data[i]);
}
}
uint8_t* signerSha256Final(void) {
memcpy(sha256_hash, _soft_sha256.result(), 32);
return sha256_hash;
}
int signerMemcmp(const void* a, const void* b, size_t sz) {
int retVal;
size_t i;
int done = 0;
const uint8_t* ptrA = (const uint8_t*)a;
const uint8_t* ptrB = (const uint8_t*)b;
for (i=0; i < sz; i++) {
if (ptrA[i] == ptrB[i]) {
if (done > 0) {
done = 1;
} else {
done = 0;
}
} else {
if (done > 0) {
done = 2;
} else {
done = 3;
}
}
}
if (done > 0) {
retVal = -1;
} else {
retVal = 0;
}
return retVal;
}
+715
View File
@@ -0,0 +1,715 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @defgroup MySigninggrp MySigning
* @ingroup internals
* @{
*
* @brief The signing driver provides a generic API for various signing backends to offer
* signing of MySensors messages.
*
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
*
* @section MySigninggrpbackground Background and concepts
*
* Suppose two participants, Alice and Bob, wants to exchange a message. Alice sends a message to Bob. In MySensors “language” Alice
* could be a gateway and Bob an actuator (light swich, electronic lock, etc). But to be generic, we will substitute the term
* “gateway” with Alice and a “node” with Bob (although the reverse relationship is also supported).
*
* Alice sends a message to Bob. This message can be heard by anyone who wants to listen (and also by anyone that is within “hearing”
* distance). Normally, this is perhaps not a big issue. Nothing Alice says to Bob may be secret or sensitive in any way.
* However, sometimes (or perhaps always) Bob want to be sure that the message Bob receives actually came from Alice. In cryptography,
* this is known as <i>authenticity</i>. Bob needs some way of determining that the message is authentic from Alice, when Bob receives it.
* This prevents an eavesdropper, Eve, to trick Bob into thinking it was Alice that sent a message Eve in fact transmitted. Bob also needs
* to know how to determine if the message has been repeated. Eve could record a message sent by Alice that Bob accepted and then send
* the same message again. Eve could also in some way prevent Bob from receiving the message and delay it in order to permit the message
* to arrive to Bob at a time Eve chooses, instead of Alice. Such an attack is known as a <b>replay attack</b>.<br>
* <i>Authenticity</i> permits Bob to determine if Alice is the true sender of a message.
* @image html MySigning/alicenfriends.png
* @image latex MySigning/alicenfriends.eps "Alice with Eve and Bob" width=6cm
*
* It can also be interesting for Bob to know that the message Alice sent has not been tampered with in any way. This is the
* <i>integrity</i> of the message. We now introduce Mallory, who could be intercepting the communication between Alice and Bob and
* replace some parts of the message but keeping the parts that authenticate the message. That way, Bob still trusts Alice to be the
* source, but the contents of the message was not the content Alice sent. Bob needs to be able to determine that the contents of the
* message was not altered after Alice sent it.<br>
* Mallory would in this case be a <b>man-in-the-middle</b> attacker.<br>
* <i>Integrity</i> permits Bob to verify that the messages received from Alice has not been tampered with.<br>
* This is achieved by adding a <i>signature</i> to the message, which Bob can inspect to validate that Alice is the author.
* @image html MySigning/alicenfriends2.png
* @image latex MySigning/alicenfriends2.eps "Alice with Mallory and Bob" width=9cm
*
* The signing scheme used, needs to address both these attack scenarios. Neither Eve nor Mallory must be permitted to interfere with the
* message exchange between Alice and Bob.
*
* The key challenge to implementing a secure signing scheme is to ensure that every signature is different, even if the message is not.
* If not, <b>replay attacks</b> would be very hard to prevent.<br>
* One way of doing this is to increment some counter on the sender side and include it in the signature. This is however predictable.<br>
* A better option would be to introduce a random number to the signature. That way, it is impossible to predict what the signature will be.
* The problem is, that also makes it impossible for the receiver (Bob) to verify that the signature is valid.<br>
* A solution to this is to let Bob generate the random number, keep it in memory and send it to Alice. Alice can then use the random number
* in the signature calculation and send the signed message back to Bob who can validate the signature with the random number used.
* This random number is in cryptography known as a <a href="https://en.wikipedia.org/wiki/Cryptographic_nonce">nonce</a> or
* <a href="https://en.wikipedia.org/wiki/Salt_%28cryptography%29">salt</a>.
*
* However, Mallory might be eavesdropping on the communication and snoop up the nonce in order to generate a new valid signature for a
* different message. To counter this, both Alice and Bob keep a secret that only they know. This secret is never transmitted over the air,
* nor is it revealed to anybody. This secret is known as a <a href="https://en.wikipedia.org/wiki/Pre-shared_key"> pre-shared key</a> (PSK).
*
* If Eve or Mallory are really sophisticated, he/she might use a <b>delayed replay attack</b>. This can be done by allowing Bob to transmit
* a nonce to Alice. But when Alice transmits the uniquely signed message, Mallory prevents Bob from receiving it, to a point when Mallory
* decides Bob should receive it. An example of such an attack is described
* <a href="http://spencerwhyte.blogspot.se/2014/03/delay-attack-jam-intercept-and-replay.html">here</a>.<br>
* This needs to be addressed as well, and one way of doing this is to have Bob keep track of time between a transmitted nonce and a signed
* message to verify. If Bob is asked for a nonce, Bob knows that a signed message is going to arrive “soon”. Bob can then decide that if the
* signed message does not arrive within a predefined timeframe, Bob throws away the generated nonce and thus makes it impossible to verify
* the message if it arrives late.
*
* The flow can be described like this:
* @image html MySigning/alicenbob.png
* @image latex MySigning/alicenbob.eps "Alice and Bob" width=8cm
* The benefits for MySensors to support this are obvious. Nobody wants others to be able to control or manipulate any actuators in their home.
*
* @section MySigninggrpencryption Why encryption is not part of this
*
* Well, some could be uncomfortable with somebody being able to snoop temperatures, motion or the state changes of locks in the environment.
* Signing does <b>not</b> address these issues. Encryption is needed to prevent this.<br>
* It is my personal standpoint that encryption should not be part of the MySensors “protocol”. The reason is that a gateway and a node does
* not really care about messages being readable or not by “others”. It makes more sense that such guarantees are provided by the underlying
* transmission layer (RF solution in this case). It is the information transmitted over the air that needs to be secret (if user so desires).
* The “trust” level on the other hand needs to go all the way into the sketches (who might have different requirements of trust depending on
* the message participant), and for this reason, it is more important (and less complicated) to ensure authenticity and <i>integrity</i> at
* protocol-level as message contents is still readable throughout the protocol stack. But as soon as the message leaves the “stack” it can be
* scramble into “garbage” when transmitted over the air and then reassembled by a receiving node before being fed in “the clear” up the stack
* at the receiving end.
*
* There are methods and possibilities to provide encryption also in software, but if this is done, it is my recommendation that this is done
* after integrity- and authentication information has been provided to the message (if this is desired). Integrity and authentication is of
* course not mandatory and some might be happy with only having encryption to cover their need for security. I, however, have only focused on
* <i>integrity</i> and <i>authenticity</i> while at the same time keeping the current message routing mechanisms intact and therefore leave
* the matter of <i>secrecy</i> to be implemented in the “physical” transport layer. With the <i>integrity</i> and <i>authenticity</i> handled
* in the protocol it ought to be enough for a simple encryption (nonce-less AES with a PSK for instance) on the message as it is sent to the
* RF backend. Atmel does provide such circuits as well but I have not investigated the matter further as it given the current size of the
* ethernet gateway sketch is close to the size limit on an Arduino Nano, so it will be difficult to fit this into some existing gateway
* designs.<br>
* Also it is worth to consider that the state of a lock can just as readily be determined by simply looking at the door in question or
* attempting to open it, so obfuscating this information will not necessarily deter an attacker in any way.<br>
* Nevertheless, I do acknowledge that people find the fact that all information is sent “in the clear” even if it require some technical
* effort for an intruder to obtain and inspect this information. So I do encourage the use of encrypting transport layers.<br>
* This is however not covered by this implementation.
*
* @section MySigninggrphow How this is done
*
* There exist many forms of message signature solutions to combat Eve and Mallory.<br>
* Most of these solutions are quite complex in term of computations, so I elected to use an algorithm that an external circuit is able to
* process. This has the added benefit of protecting any keys and intermediate data used for calculating the signature so that even if
* someone were to actually steal a sensor and disassembled it, they would not be able to extract the keys and other information from the
* device.<br>
* A common scheme for message signing (authenticity and integrity) is implemented using
* <a href="http://en.wikipedia.org/wiki/Hash-based_message_authentication_code">HMAC</a> which in combination with a strong
* <a href="http://en.wikipedia.org/wiki/Hash_function"> hash function</a> provides a very strong level of protection.<br>
* The <a href="http://www.atmel.com/devices/ATSHA204A.aspx">Atmel ATSHA204A</a> is a low-cost, low-voltage/current circuit that provides
* HMAC calculation capabilities with SHA256 hashing which is a (currently) virtually unbreakable combination. If SHA256 were to be hacked,
* a certain <a href="http://en.wikipedia.org/wiki/Bitcoin">cryptocurrency</a> would immediately be rendered worthless.<br>
* The ATSHA device also contain a random number generator (RNG) which enables the generation of a good nonce, as in,
* <i>non-predictable</i>.<br>
* As I acknowledge that some might not want to use an additional external circuit, I have also implemented a software version of the ATSHA
* device, capable of generating the same signatures as the ATSHA device does. Because it is pure-software however, it does not provide as
* good nonces (it uses the <a href="http://arduino.cc/en/reference/random">Arduino pseudo-random generator</a>) and the HMAC key is stored
* in SW and is therefore readable if the memory is dumped. It also naturally claims more flash space due to the more complex software. But
* for indoor sensors/actuators this might be good enough for most people.
*
* @section MySigninggrphowuse How to use this
*
* Before we begin with the details, I just want to emphasize that signing is completely optional and not enabled by default. If you do want
* the additional security layer signing provides, you pick the backend of your choise in your sketch. Currently, two compatible backends are
* supported; @ref MY_SIGNING_ATSHA204 (hardware backed) and @ref MY_SIGNING_SOFT (software backed). You can either enable these globally in
* @ref MyConfig.h or in your sketch for sketch specific/local use.
*
* <b>Firstly</b>, you need to make sure to pick a backend to use as described above.
* @code{.cpp}
* //#define MY_SIGNING_SOFT
* #define MY_SIGNING_ATSHA204
* #include <MySensors.h>
* ...
* @endcode
* Make sure to set the define before the inclusion of MySensors.h.
* It is legal to mix hardware- and software-based backends in a network. They work together.
*
* You also need to decide if the node (or gateway) in question require and verify signatures in addition to calculating them.
* This has to be set by at least one of the node in a "pair" or nobody will actually start calculating a signature for a message.
* Just set the flag @ref MY_SIGNING_REQUEST_SIGNATURES and the node will inform the gateway that it expects the gateway to sign all
* messages sent to the node. If this is set in a gateway, it will @b NOT force all nodes to sign messages to it. It will only require
* signatures from nodes that in turn require signatures.<br>
* If you want to have two nodes communicate securely directly with each other, the nodes that require signatures must send a presentation
* message to all nodes it expect signed messages from (only the gateway is informed automatically). See @ref signerPresentation().<br>
* A node can have three "states" with respect to signing:
* 1. Node does not support signing in any way (neither @ref MY_SIGNING_ATSHA204 nor @ref MY_SIGNING_SOFT is set)
* 2. Node does support signing but don't require messages sent to it to be signed (@ref MY_SIGNING_REQUEST_SIGNATURES is not set)
* 3. Node does support signing and require messages sent to it to be signed (@ref MY_SIGNING_REQUEST_SIGNATURES is set)
*
* <b>Secondly</b>, you need to verify the configuration for the backend.<br>
* For hardware backed signing it is the pin the device is connected to. In MyConfig.h there are defaults which you might need to adjust to
* match your personal build. The setting is defined using @ref MY_SIGNING_ATSHA204_PIN and the default is to use pin A3.<br>
* Similar to picking your backend, this can also be set in your sketch:
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_ATSHA204_PIN 4
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
* For the software backed signingbackend, an unconnected analog pin is required to set a random seed for the pseudo-random generator.
* It is important that the pin is floating, or the output of the pseudo-random generator will be predictable, and thus compromise the
* signatures. The setting is defined using @ref MY_SIGNING_SOFT_RANDOMSEED_PIN and the default is to use pin A7. The same configuration
* possibilities exist as with the other configuration options.
*
* <b>Thirdly</b>, if you use the software backend, you need to personalize the node (see @ref personalization).
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
*
* An example of a node that require signatures is available in @ref SecureActuator.ino.
*
* @anchor personalization If you use the “real” ATSHA204A, before any signing operations can be done, the device needs to be <i>personalized</i>.
* This can be a daunting process as it involves irreversibly writing configurations to the device, which cannot be undone. I have however tried
* to simplify the process as much as possibly by creating a helper-sketch specifically for this purpose in @ref SecurityPersonalizer.ino
* Note that you also need to do personalization for software signing, but then the values are stored in EEPROM (described below).
*
* The process of personalizing the ATSHA204A involves
* - Writing and locking chip configuration
* - (optionally) Generating and (mandatory) writing HMAC key
* - (optionally) Locking data sections
*
* First execute the sketch without modifications to make sure communications with the device can be established. It is highly recommended that
* the first time this is done, a device with serial debug possibilities is used.<br>
* When this has been confirmed, it is time to decide what type of personalization is desired.<br>
* There are a few options here.<br>
* Firstly, enable @ref LOCK_CONFIGURATION to allow the sketch to lock the chip configuration. The sketch will write the default settings to the
* chip because these are fine for our purposes. This also enables the RNG which is required to allow the sketch to automatically generate a PSK
* if this is desired.<br>
* Now it is possible to execute the sketch to lock the configuration and enable the RNG.<br>
* Next step is to decide if a new key should be generated or an existing key should be stored to the device. This is determined using
* @ref USER_KEY, which, if defined, will use the data in the variable user_key_data.<br>
* If @ref USER_KEY is disabled, the RNG will be used to generate a key. This key obviously need to be made available to you so you can use
* it in other devices in the network, and this key is therefore also printed on the serial console when it has been generated.<br>
* The key (generated or provided) will be written to the device unless @ref SKIP_KEY_STORAGE is set. As long as the data zone is kept unlocked
* the key can be replaced at any time. However, Atmel suggests the data region to be locked for maximum security. On the other hand, they also
* claim that the key is not readable from the device even if the data zone remains unlocked so the need for locking the data region is optional
* for MySensors usage.<br>
* For devices that does not have serial debug possibilities, it is possible to set @ref SKIP_UART_CONFIRMATION, but it is required to set
* @ref USER_KEY if this option is enabled since a generated and potentially unknown key could be written to the device and thus rendering
* it useless (if the data zone is also locked).<br>
* For devices with serial debug possibilities it is recommended to not use @ref SKIP_UART_CONFIRMATION as the sketch without that setting will
* ask user to send a ‘space’ character on the serial terminal before any locking operations are executed as an additional confirmation that
* this irreversible operation is done. However, if a number of nodes are to undergo personalization, this option can be enabled to streamline
* the personalization.<br>
* This is a condensed description of settings to fully personalize and lock down a set of sensors (and gateways):
* - Pick a “master” device with serial debug port.
* - Set the following sketch configuration of the personalizer:
* -# Enable @ref LOCK_CONFIGURATION
* -# Disable @ref LOCK_DATA
* -# Enable @ref SKIP_KEY_STORAGE
* -# Disable @ref SKIP_UART_CONFIRMATION
* -# Disable @ref USER_KEY
* - Execute the sketch on the “master” device to obtain a randomized key. Save this key to a secure location and keep it confidential so that\n
you can retrieve it if you need to personalize more devices later on.
* - Now reconfigure the sketch with these settings:
* -# Enable @ref LOCK_CONFIGURATION
* -# Enable @ref LOCK_DATA (if you are sure you do not need to replace/revoke the key, this is the most secure option to protect from key readout\n
* according to Atmel, but they also claim that key is not readable even if data region remains unlocked from the slot we are using)
* -# Disable @ref SKIP_KEY_STORAGE
* -# Enable @ref SKIP_UART_CONFIRMATION
* -# Enable @ref USER_KEY
* - Put the saved key in the user_key_data variable.
* - Now execute the sketch on all devices you want to personalize with this secret key.
*
* For software signing the personalization procedure looks slightly different.<br>
* - If you do not have the ATSHA204A device and need to generate random keys:
* -# Enable @ref USE_SOFT_SIGNING
* - If you do have the ATSHA204A and want to use it to generate random keys for soft signing:
* -# Disable @ref USE_SOFT_SIGNING
* - If you want to review existing EEPROM configuration to determine if anything needs to be updated:
* -# Make sure to disable any ATSHA204A update features if you use it (enable @ref SKIP_KEY_STORAGE, disable @ref LOCK_CONFIGURATION and @ref LOCK_DATA)
* -# Disable @ref STORE_SOFT_KEY
* -# Disable @ref STORE_SOFT_SERIAL
* -# Disable @ref STORE_AES_KEY
* - Execute the sketch and the EEPROM configuration will be printed on the serial console.
* - Decide what you want to update and enable the corresponding flags (@ref STORE_SOFT_KEY, @ref STORE_SOFT_SERIAL, @ref STORE_AES_KEY).
* - If you have existing keys and want to use them instead of having the values randomized:
* - Enable the corresponding flag (@ref USER_SOFT_KEY, @ref USER_SOFT_SERIAL, @ref USER_AES_KEY) and also set the appropriate values
* (@ref user_soft_key_data, @ref user_soft_serial, @ref user_aes_key)
*
* The personalizer also takes care of generation and storage of the AES key used for RF traffic encryption.
* See the software signing personalization flow for how to manage it.
*
* That’s it. Personalization is done and the device is ready to execute signing operations which are valid only on your personal network.
*
* If a node does require signing, any unsigned message sent to the node will be rejected.<br>
* This also applies to the gateway. However, the difference is that the gateway will only require signed messages from nodes it knows in turn
* require signed messages.<br>
* A node can also inform a different node that it expects to receive signed messages from it. This is done by transmitting an internal message
* of type @ref I_SIGNING_PRESENTATION and provide flags as payload that inform the receiver of the signing preferences of the sender.<br>
* All nodes and gateways in a network maintain a table where the signing preferences of all nodes are stored. This is also stored in EEPROM so
* if the gateway reboots, the nodes does not have to retransmit a signing presentation to the gateway for the gateway to realize that the node
* expect signed messages.<br>
* Also, the nodes that do not require signed messages will also inform gateway of this, so if you reprogram a node to stop require signing,
* the gateway will adhere to this as soon as the new node has presented itself to the gateway.
*
* The following sequence diagram illustrate how messages are passed in a MySensors network with respect to signing:
* @image html MySigning/signingsequence.png
* @image latex MySigning/signingsequence.eps "Signing sequence diagram" width=9cm
*
* None of this activity is “visible” to you (as the sensor sketch implementor). All you need to do is to set your preferences in MyConfig.h or
* in your sketch. Depending on chosen backend, do personalization or key configurations and set @ref MY_SIGNING_REQUEST_SIGNATURES on the node that
* you want protected. That is enough to enable protection from both Eve and Mallory in your network (although if you do not also enable encryption,
* Eve can eavesdrop, but not do anything about, your messages).
*
* @section MySigningwhitelisting Whitelisting and node revocation
*
* Consider the situation when you have set up your secure topology. We use the remotely operated garage door as an example:
* - You have a node inside your garage (therefore secure and out of reach from prying eyes) that controls your garage door motor.\n
* This node requires signing since you do not want an unauthorized person sending it orders to open the door.
* - You have a keyfob node with a signing backend that uses the same PSK as your door opener node.
*
* In this setup, your keyfob can securely transmit messages to your door node since the keyfob will sign the messages it sends and the door node
* will verify that these were sent from a trusted node (since it used the correct PSK). If the keyfob does not sign the messages, the door node
* will not accept them.
*
* One day your keyfob gets stolen or you lost it or it simply broke down.
*
* You now end up with a problem; you need some way of telling your door node that the keyfob in question cannot be trusted any more. Furthermore,
* you maybe locked the data region in your door nodes ATSHA device and is not able to revoke/change your PSK, or you have some other reason for
* not wanting to replace the PSK. How do you make sure that the "rogue" keyfob can be removed from the "trusted chain"?
*
* The answer to this is whitelisting. You let your door node keep a whitelist of all nodes it trusts. If you stop trusting a particular node, you
* remove it from the nodes whitelist, and it will no longer be able to communicate signed messages to the door node.
*
* In case you want to be able to "whitelist" trusted nodes (in order to be able to revoke them in case they are lost) you also need to take note
* of the serial number of the ATSHA device or the software value stored in EEPROM. This is unique for each device. The serial number is printed
* in a copy+paste friendly format by the personalizer for this purpose.<br>
* The whitelist is stored on the node that require signatures. When a received message is verified, the serial of the sender is looked up in a
* list stored on the receiving node, and the corresponding serial stored in the list for that sender is then included in the signature verification
* process. The list is stored as the value of the flag that enables whitelisting, @ref MY_SIGNING_NODE_WHITELISTING.<br>
*
* Whitelisting is achieved by 'salting' the signature with some node-unique information known to the receiver. In the case of ATSHA204A this is the
* unique serial number programmed into the circuit. This unique number is never transmitted over the air in clear text, so Eve will not be able to
* figure out a "trusted" serial by snooping on the traffic.<br>
* Instead the value is hashed together with the senders NodeId into the HMAC signature to produce the final signature. The receiver will then take
* the originating NodeId of the signed message and do the corresponding calculation with the serial it has stored in it's whitelist if it finds a
* matching entry in it's whitelist.
*
* Whitelisting is an optional alternative because it adds some code and configuration options which might not be desirable for every user. So if
* you want the ability to use whitelists, you need to enable @ref MY_SIGNING_NODE_WHITELISTING.<br>
* The whitelist is provided as value of the flag that enable it as follows (example is a node that require signing as well):
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}},{.nodeId = 2,.serial = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}}}
* #include <MySensors.h>
* ...
* @endcode
* In this example, there are two nodes in the whitelist; the gateway, and a separate node that communicates directly with this node (with signed
* messages). You do not need to do anything special for the sending nodes, apart from making sure they support signing.
*
* The "soft" backend of course also support whitelisting. Example:
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}},{.nodeId = 2,.serial = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}}}
* #include <MySensors.h>
* ...
* @endcode
*
* For a node that should transmit whitelisted messages but not receive whitelisted messages, you do not need any special configurations:
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* @endcode
* Remember that you always need to select a signing backend for all nodes that communicate to a node that require whitelisting.
* Also, note that a node that use whitelisting will not accept messages from nodes that are not present in it's whitelist.
* And you have to personalize all nodes that use signing with a common HMAC key but different serial numbers (ATSHA204A always has unique serials).
*
* @section MySigningtechnical The technical stuff
*
* How are the messages actually affected by the signing?<br>
* The following illustration shows what part of the message is signed, and where the signature is stored:
* @image html MySigning/signingillustrated1.png
* @image latex MySigning/signingillustrated1.eps "Message structure" width=11cm
*
* The first byte of the header is not covered by the signature, because in the network, this byte is used to track hops in the network and therefore
* might change if the message is passing a relay node. So it cannot be part of the signature, or the signature would be invalid when it arrives to
* its destination. The signature also carries a byte with a signing identifier to prevent false results from accidental mixing of incompatible
* signing backends in the network. Thus, the maximum size for a payload is 29-7 bytes. Larger payloads are not possible to sign. Another thing to
* consider is that the strength of the signature is inversely proportional to the payload size.
*
* As for the software backend, it turns out that the ATSHA does not do “vanilla” HMAC processing. Fortunately, Atmel has documented exactly how
* the circuit processes the data and hashes thus making it possible to generate signatures that are identical to signatures generated by the circuit.
*
* The signatures are calculates in the following way:
* @image html MySigning/signingillustrated2.png
* @image latex MySigning/signingillustrated2.eps "HMAC processing" width=10cm
*
* Exactly how this is done can be reviewd in the source for the ATSHA204SOFT backend and the ATSHA204A
* <a href="http://www.atmel.com/Images/Atmel-8885-CryptoAuth-ATSHA204A-Datasheet.pdf">datasheet</a>.
* In the MySensors protocol, the following internal messagetypes handles signature requirements and nonce requests:<br>
* @ref I_SIGNING_PRESENTATION <br>
* @ref I_NONCE_REQUEST <br>
* @ref I_NONCE_RESPONSE <br>
*
* Also, the version field in the header has been reduced from 3 to 2 bits in order to fit a single bit to indicate that a message is signed.
*
* @section MySigninglimitations Known limitations
*
* Due to the limiting factor of our cheapest Arduino nodes, the use of diversified keys is not implemented. That mean that all nodes in your
* network share the same PSK (at least the ones that are supposed to exchange signed data). It is important to understand the implications of
* this, and that is covered in the "Typical use cases" chapter below.<br>
* Also be reminded that the strength of the signature is inversely proportional to the size of the message. The larger the message, the weaker
* the signature.
*
* @section MySigningusecases Typical use cases
*
* "Securely located" in this context mean a node which is not physically publicly accessible. Typically at least your gateway.<br>
* "Public" in this context mean a node that is located outside your "trusted environment". This includes sensors located outdoors, keyfobs etc.
*
* @subsection MySigninglock Securely located lock
*
* You have a securely located gateway and a lock somewhere inside your "trusted environment" (e.g. inside your house door, the door to your
* dungeon or similar).<br>
* You should then keep the data section of your gateway and your lock node unlocked. Locking the data (and therefore the PSK) will require you
* to replace at least the signing circuit in case you need to revoke the PSK because some other node in your network gets compromised.<br>
* You need to make your node require signed messages but you do not necessarily need to make your gateway require signed messsages (unless you
* are concerned that someone might spoof the lock status of your lock).<br>
* Configuration example for the secure lock node:<br>
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
* If you do also want your gateway to require signatures from your lock you just enable the same (or similar if using software signing) settings
* in the gateway.
*
* @subsection MySigningpatio Patio motion sensor
*
* Your gateway is securely located inside your house, but your motion sensor is located outside your house. You have for some reason elected
* that this node should sign the messages it send to your gateway. You should lock the data (PSK) in this node then, because if someone were
* to steal your patio motion sensor, they could rewrite the firmware and spoof your gateway to use it to transmit a correctly signed message
* to your secure lock inside your house. But if you revoke your gateway (and lock) PSK the outside sensor cannot be used for this anymore.
* Nor can it be changed in order to do it in the future. You can also use whitelisting to revoke your lost node.<br>
* This is an unlikely use case because there really is no reason to sign sensor values. If you for some reason want to obfuscate sensor data,
* encryption is a better alternative.<br>
* Configuration example for a motion sensor:<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #include <MySensors.h>
* ...
* @endcode
*
* The gateway needs to configured with a whitelist (and it have to have an entry for all nodes that send and/or require signed messages):<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = MOTION_SENSOR_ID,.serial = {0x12,0x34,0x56,0x78,0x90,0x12,0x34,0x56,0x78}}}
* #include <MySensors.h>
* ...
* @endcode
* @subsection MySigningkeyfob Keyfob for garage door opener
*
* Perhaps the most typical usecase for signed messages. Your keyfob should be totally locked down. If the garage door opener is secured
* (and it should be) it can be unlocked. That way, if you loose your keyfob, you can revoke the PSK in both the opener and your gateway,
* thus rendering the keyfob useless without having to replace your nodes. You can also use whitelisting to revoke your lost keyfob.<br>
* Configuration example for the keyfob (keyfob will only transmit to another node and not receive anything):<br>
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #include <MySensors.h>
* ...
* @endcode
*
* Configuration example for the door controller node (should require signing from anyone who wants to control it):<br>
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* #define MY_SIGNING_REQUEST_SIGNATURES
* #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88}},{.nodeId = KEYFOB_ID,.serial = {<FROM ATSHA ON KEYFOB>}}}
* #include <MySensors.h>
* ...
* @endcode
*
* @subsection MySigningsketches Relevant sketches
*
* - @ref SecureActuator.ino
* - @ref SecurityPersonalizer.ino
*/
/**
* @file MySigning.h
*
* @brief API declaration for MySigning signing backend
*/
#ifndef MySigning_h
#define MySigning_h
#include "MySensorsCore.h"
#include "drivers/ATSHA204/ATSHA204.h"
#ifdef MY_SIGNING_NODE_WHITELISTING
typedef struct {
uint8_t nodeId; /**< @brief The ID of the node */
uint8_t serial[SHA204_SERIAL_SZ]; /**< @brief Node specific serial number */
} whitelist_entry_t;
#endif
/** @brief Helper macro to determine the number of elements in a array */
#define NUM_OF(x) (sizeof(x)/sizeof(x[0]))
/** @brief Helper macro to determine if node require serial salted signatures */
#define DO_WHITELIST(node) (~_doWhitelist[node>>3]&(1<<node%8))
/** @brief Helper macro to set that node require serial salted signatures */
#define SET_WHITELIST(node) (_doWhitelist[node>>3]&=~(1<<node%8))
/** @brief Helper macro to set that node does not require serial salted signatures */
#define CLEAR_WHITELIST(node) (_doWhitelist[node>>3]|=(1<<node%8))
/**
* @brief Initializes signing infrastructure and associated backend.
*
* This function makes sure that the internal states of the signing infrastructure
* is set to a known initial state.
* \n@b Usage: This fuction should be called before any signing related operations take place.
*/
void signerInit(void);
/**
* @brief Does signing specific presentation for a node.
*
* This function makes sure any signing related presentation info is shared with the other part.
* The presentation of the gateways signing preferences is done in @ref signerProcessInternal().
* \n@b Usage: This function should be called by the presentation routine of the mysensors library.
* You only need to call this directly from a sketch to set up a node to node signed message exchange.
* If you do call this directly from a sketch, and you at some point change your sketch to go from
* requireing signing to not requireing signatures, you need to present this change to the node at least
* once, so it can update its requirements tables accordingly. Or it will keep believing that this node
* require signatures and attempt to send signed messages to it.
*
* @param msg Message buffer to use.
* @param destination Node ID of the destination.
*/
void signerPresentation(MyMessage &msg, uint8_t destination);
/**
* @brief Manages internal signing message handshaking.
*
* This function takes care of signing related message handshaknig such as nonce exchange.
* \n@b Usage: This function should be called by the incoming message handler before any further message
* processing is performed on internal messages. This function should only be called for @ref C_INTERNAL class
* messages.
*
* @param msg Message buffer to process.
* @returns @c true if caller should stop further message processing.
*/
bool signerProcessInternal(MyMessage &msg);
/**
* @brief Check timeout of verification session.
*
* Nonce will be purged if it takes too long for a signed message to be sent to the receiver.
* \n@b Usage: This function should be called on regular intervals, typically within some process loop.
*
* @returns @c true if session is still valid.
*/
bool signerCheckTimer(void);
/**
* @brief Get nonce from provided message and store for signing operations.
*
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if signing identifier found in message is not supported by the used backend.<br>
* If successful, this marks the start of a signing operation at the sending side so
* implementation is expected to do any necessary initializations within this call.
* \n@b Usage: This function is typically called as action when receiving a @ref I_NONCE_RESPONSE
* message.
*
* @param msg The message to get the nonce from.
* @returns @c true if successful, else @c false.
*/
bool signerPutNonce(MyMessage &msg);
/**
* @brief Signs provided message. All remaining space in message payload buffer is used for
* signing identifier and signature.
*
* Nonce used for signature calculation is the one generated previously within @ref signerProcessInternal().<br>
* Nonce will be cleared when this function is called to prevent re-use of nonce.<br>
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if not two bytes or more of free payload space is left in provided message.<br>
* This ends a signing operation at the sending side so implementation is expected to do any
* deinitializations and enter a power saving state within this call.
* \n@b Usage: This function is typically called as action when receiving a @ref I_NONCE_RESPONSE
* message and after @ref signerPutNonce() has successfully been executed.
*
* @param msg The message to sign.
* @returns @c true if successful, else @c false.
*/
bool signerSignMsg(MyMessage &msg);
/**
* @brief Verifies signature in provided message.
*
* Nonce used for verification is the one previously set using @ref signerPutNonce().<br>
* Nonce will be cleared when this function is called to prevent re-use of nonce.<br>
* Returns @c false if subsystem is busy processing an ongoing signing operation.<br>
* Returns @c false if signing identifier found in message is not supported by the used backend.<br>
* This ends a signing operation at the receiving side so implementation is expected to do any
* deinitializations and enter a power saving state within this call.
* \n@b Usage: This function is typically called when receiving a message that is flagged as signed
* and @ref MY_SIGNING_REQUEST_SIGNATURES is activated.
*
* @param msg The message to verify.
* @returns @c true if successful, else @c false.
*/
bool signerVerifyMsg(MyMessage &msg);
/**
* @brief Initialize a hash calculation session.
*
* Any ongoing calculation session will be terminated/reset.
*/
void signerSha256Init(void);
/**
* @brief Add data to an ongoing hash calculation session.
*
* More data can be added by repeated calls to this function.
*
* @param data Buffer with data to add.
* @param sz Size of data buffer.
*/
void signerSha256Update(const uint8_t* data, size_t sz);
/**
* @brief Finalize an ongong hash calculation session and return the hash.
*
* The returned hash size is always 32 bytes. Buffer can be assumed to be valid
* until @ref signerSha256Init() is called again.
*
* @returns Buffer with 32-byte hash.
*/
uint8_t* signerSha256Final(void);
/**
* @brief Do a timing neutral memory comparison.
*
* The function behaves similar to memcmp with the difference that it will
* always use the same number of instructions for a given number of bytes,
* no matter how the two buffers differ and the response is either 0 or -1.
*
* @param a First buffer for comparison.
* @param b Second buffer for comparison.
* @param sz The number of bytes to compare.
* @returns 0 if buffers match, -1 if they do not.
*/
int signerMemcmp(const void* a, const void* b, size_t sz);
#endif
/** @}*/
/**
* @defgroup MySigningTroubleshootinggrp Troubleshooting
* @ingroup MySigninggrp
* @{
*
* @section MySigningTroubleshootingSymptoms Symptoms and solutions
*
* The first thing to do if you suspect signing is causing problems, is to enable the verbose debug
* flag for the signing backend. @see MY_DEBUG_VERBOSE_SIGNING
*
* If you are having trouble getting signing to work, please see the following troubleshooting tips.
*
* @subsection MySigningTroubleshootingSymptomStFail Signing fails and logs show st=fail on transmissions
*
* This is actually not a signing problem, although ofthen st=fail becomes st=ok when signing is disabled.
* This is by far the most commonly reported problem with signing, but the problems is not with signing,
* it is with radio performence.<br>
* This is a typical log which might look like a signing related issue but isn't:
* @code{.unparsed}
* 0;255;3;0;9;Skipping security for command 3 type 16
* 0;255;3;0;9;read: 3-3-0 s=255,c=3,t=16,pt=0,l=0,sg=0:
* 0;255;3;0;9;Signing backend: ATSHA204Soft
* 0;255;3;0;9;SHA256: 86DEAE1DAF50D577A4E2262B33ABF9DEE05DD8FAF84F94F50900000000000000
* 0;255;3;0;9;Skipping security for command 3 type 17
* 0;255;3;0;9;send: 0-0-3-3 s=255,c=3,t=17,pt=6,l=25,sg=0,st=fail:86DEAE1DAF50D577A4E2262B33ABF9DEE05DD8FAF84F94F509
* 0;255;3;0;9;Failed to transmit nonce!
* 0;255;3;0;9;Message is not signed, but it should have been!
* 0;255;3;0;9;verify fail
* @endcode
*
* The reason for this is that when signing is used, the messages transmitted become relatively large.<br>
* Because of this, the message is more sensitive to noise, and the chance for a message to get scrambled
* increase with the message size. Please refer to the troubleshooting section at the MySensors forum for
* information on how to improve radio performence.<br>
* This is a good place to start: https://forum.mysensors.org/topic/666/debug-faq-and-how-ask-for-help
*
* @subsection MySigningTroubleshootingSymptomNonce Failed to generate nonce
*
* The signing backend failed to generate the nonce needed to sign a message. This indicate a hardware
* problem. Please post the debug info on the forum together with a description of your hardware setup.
*
* @subsection MySigningTroubleshootingSymptomSign Failed to sign message
*
* The signing backend failed to sign the message. Typically this happens if your message is so large,
* that there is no room left in the buffer to store a signature.
*
* @subsection MySigningTroubleshootingSymptomWrongSource Nonce did not come from the destination (XX) of the message to be signed! It came from YY
*
* This should not really happen. The reason for this message is that the signing backend is only capable
* of handling one signed message session at any time. If for some reason multiple nodes send a nonce message to
* the same node, only the nonce from a node that is the destination of the current message signing session will be
* accepted. Any other nonces will be dropped. This should not happen as no node should send a nonce unless asked to,
* and a node will only ask for a nonce to one destination for every signing session.<br>
* If you see this message, please post the debugging details on the MySensors forum so it can be investigated further
* together with a description of your setup.
*
* @subsection MySigningTroubleshootingSymptomNotSigned Message is not signed, but it should have been
*
* A node has failed to comply with the signing preferences of this node. Check that the node has received a
* signing presentation message from this node. This is automatically transmitted to gateways. For other nodes,
* you need to transmit the presentation from the sketch. @see signerPresentation
*
* @subsection MySigningTroubleshootingSymptomNotSignedGeneral "Messages do not appear to be signed but I think they should be..."
*
* Make sure you have enabled the flag to require signatures to require signatures and have enabled one of the signing
* backends. @see MY_SIGNING_REQUEST_SIGNATURES @see MY_SIGNING_ATSHA204 @see MY_SIGNING_SOFT
*
* @subsection MySigningTroubleshootingSymptomNotWorkingWhitelisting Signature verification failed!
*
* Make sure both source and destination of the signed message has undergone @ref personalization with the same HMAC key.<br>
* Also, if whitelisting is used, make sure the proper serial is paired with the proper node ID at the destination.
* Whitelisting preferences are communicated with the signing presentation (done automatically from nodes to gateway but
* has to be explicitly done by sketch for node to node communication). @see signerPresentation
*/
/** @}*/
+291
View File
@@ -0,0 +1,291 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
*******************************
*
* DESCRIPTION
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
* ATSHA204 signing backend. The Atmel ATSHA204 offers true random number generation and
* HMAC-SHA256 authentication with a readout-protected key.
*
*/
#include "MySigning.h"
#define SIGNING_IDENTIFIER (1)
// Define MY_DEBUG_VERBOSE_SIGNING in your sketch to enable signing backend debugprints
unsigned long _signing_timestamp;
bool _signing_verification_ongoing = false;
uint8_t _signing_verifying_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH+SHA204_SERIAL_SZ+1];
uint8_t _signing_signing_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH+SHA204_SERIAL_SZ+1];
uint8_t _signing_temp_message[SHA_MSG_SIZE];
uint8_t _signing_rx_buffer[SHA204_RSP_SIZE_MAX];
uint8_t _signing_tx_buffer[SHA204_CMD_SIZE_MAX];
extern uint8_t _doWhitelist[32];
#ifdef MY_SIGNING_NODE_WHITELISTING
const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static void signerCalculateSignature(MyMessage &msg, bool signing);
static uint8_t* signerSha256(const uint8_t* data, size_t sz);
#ifdef MY_DEBUG_VERBOSE_SIGNING
static char i2h(uint8_t i)
{
uint8_t k = i & 0x0F;
if (k <= 9)
return '0' + k;
else
return 'A' + k - 10;
}
static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, uint8_t sz) {
static char printBuffer[300];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(printBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(printBuffer);
#endif
for (int i=0; i<sz; i++)
{
printBuffer[i * 2] = i2h(buf[i] >> 4);
printBuffer[(i * 2) + 1] = i2h(buf[i]);
}
printBuffer[sz * 2] = '\0';
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
printBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1-strlen_P((const char*)str)] = '\0';
#endif
MY_SERIALDEVICE.print(str);
if (sz > 0)
{
MY_SERIALDEVICE.print(printBuffer);
}
MY_SERIALDEVICE.println("");
}
#else
#define DEBUG_SIGNING_PRINTBUF(str, buf, sz)
#endif
void signerAtsha204Init(void) {
atsha204_init(MY_SIGNING_ATSHA204_PIN);
}
bool signerAtsha204CheckTimer(void) {
if (_signing_verification_ongoing) {
if (hwMillis() < _signing_timestamp || hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
DEBUG_SIGNING_PRINTBUF(F("Verification timeout"), NULL, 0);
// Purge nonce
memset(_signing_signing_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
memset(_signing_verifying_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204GetNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204"), NULL, 0);
// Generate random number for use as nonce
// We used a basic whitening technique that XORs each byte in a 32byte random value with current hwMillis() counter
// This 32-byte random value is then hashed (SHA256) to produce the resulting nonce
(void)atsha204_wakeup(_signing_temp_message);
if (atsha204_execute(SHA204_RANDOM, RANDOM_SEED_UPDATE, 0, 0, NULL,
RANDOM_COUNT, _signing_tx_buffer, RANDOM_RSP_SIZE, _signing_rx_buffer) != SHA204_SUCCESS) {
DEBUG_SIGNING_PRINTBUF(F("Failed to generate nonce"), NULL, 0);
return false;
}
for (int i = 0; i < 32; i++) {
_signing_verifying_nonce[i] = _signing_rx_buffer[SHA204_BUFFER_POS_DATA+i] ^ (hwMillis()&0xFF);
}
memcpy(_signing_verifying_nonce, signerSha256(_signing_verifying_nonce, 32), MAX_PAYLOAD);
atsha204_idle(); // We just idle the chip now since we expect to use it soon when the signed message arrives
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_verifying_nonce)-MAX_PAYLOAD);
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, MAX_PAYLOAD);
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
// Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
// Note that if message is "too" quick, and arrives before turnover, it will be rejected
// but this is consider such a rare case that it is accepted and rejects are 'safe'
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) _signing_timestamp = 0;
return true;
}
void signerAtsha204PutNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204"), NULL, 0);
memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD);
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD);
}
bool signerAtsha204SignMsg(MyMessage &msg) {
// If we cannot fit any signature in the message, refuse to sign it
if (mGetLength(msg) > MAX_PAYLOAD-2) {
DEBUG_SIGNING_PRINTBUF(F("Message too large"), NULL, 0);
return false;
}
// Calculate signature of message
mSetSigned(msg, 1); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
if (DO_WHITELIST(msg.destination)) {
// Salt the signature with the senders nodeId and the unique serial of the ATSHA device
memcpy(_signing_signing_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); // We can reuse the nonce buffer now since it is no longer needed
_signing_signing_nonce[32] = msg.sender;
atsha204_getSerialNumber(&_signing_signing_nonce[33]);
(void)signerSha256(_signing_signing_nonce, 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place
DEBUG_SIGNING_PRINTBUF(F("Signature salted with serial"), NULL, 0);
}
// Put device back to sleep
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
_signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER;
// Transfer as much signature data as the remaining space in the message permits
memcpy(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg));
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
return true;
}
bool signerAtsha204VerifyMsg(MyMessage &msg) {
if (!_signing_verification_ongoing) {
DEBUG_SIGNING_PRINTBUF(F("No active verification session"), NULL, 0);
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) {
DEBUG_SIGNING_PRINTBUF(F("Incorrect signing identifier"), NULL, 0);
return false;
}
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
signerCalculateSignature(msg, false); // Get signature of message
#ifdef MY_SIGNING_NODE_WHITELISTING
// Look up the senders nodeId in our whitelist and salt the signature with that data
size_t j;
for (j=0; j < NUM_OF(_signing_whitelist); j++) {
if (_signing_whitelist[j].nodeId == msg.sender) {
DEBUG_SIGNING_PRINTBUF(F("Sender found in whitelist"), NULL, 0);
memcpy(_signing_verifying_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); // We can reuse the nonce buffer now since it is no longer needed
_signing_verifying_nonce[32] = msg.sender;
memcpy(&_signing_verifying_nonce[33], _signing_whitelist[j].serial, SHA204_SERIAL_SZ);
(void)signerSha256(_signing_verifying_nonce, 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
DEBUG_SIGNING_PRINTBUF(F("Sender not found in whitelist, message rejected!"), NULL, 0);
// Put device back to sleep
atsha204_sleep();
return false;
}
#endif
// Put device back to sleep
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
_signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER;
// Compare the caluclated signature with the provided signature
if (signerMemcmp(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg))) {
DEBUG_SIGNING_PRINTBUF(F("Signature bad: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg));
#ifdef MY_SIGNING_NODE_WHITELISTING
DEBUG_SIGNING_PRINTBUF(F("Is the sender whitelisted and serial correct?"), NULL, 0);
#endif
return false;
} else {
DEBUG_SIGNING_PRINTBUF(F("Signature OK"), NULL, 0);
return true;
}
}
}
// Helper to calculate signature of msg (returned in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
static void signerCalculateSignature(MyMessage &msg, bool signing) {
(void)atsha204_wakeup(_signing_temp_message);
memset(_signing_temp_message, 0, 32);
memcpy(_signing_temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
// Program the data to sign into the ATSHA204
DEBUG_SIGNING_PRINTBUF(F("Message to process: "), (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
DEBUG_SIGNING_PRINTBUF(F("Current nonce: "), signing ? _signing_signing_nonce : _signing_verifying_nonce, 32);
(void)atsha204_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32, _signing_temp_message,
WRITE_COUNT_LONG, _signing_tx_buffer, WRITE_RSP_SIZE, _signing_rx_buffer);
// Program the nonce to use for the signature (has to be done just before GENDIG due to chip limitations)
(void)atsha204_execute(SHA204_NONCE, NONCE_MODE_PASSTHROUGH, 0, NONCE_NUMIN_SIZE_PASSTHROUGH,
signing ? _signing_signing_nonce : _signing_verifying_nonce,
NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT, _signing_rx_buffer);
// Purge nonce when used
memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
// Generate digest of data and nonce
(void)atsha204_execute(SHA204_GENDIG, GENDIG_ZONE_DATA, 8, 0, NULL,
GENDIG_COUNT_DATA, _signing_tx_buffer, GENDIG_RSP_SIZE, _signing_rx_buffer);
// Calculate HMAC of message+nonce digest and secret key
(void)atsha204_execute(SHA204_HMAC, HMAC_MODE_SOURCE_FLAG_MATCH, 0, 0, NULL,
HMAC_COUNT, _signing_tx_buffer, HMAC_RSP_SIZE, _signing_rx_buffer);
DEBUG_SIGNING_PRINTBUF(F("HMAC: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32);
}
// Helper to calculate a generic SHA256 digest of provided buffer (only supports one block)
// The pointer to the hash is returned, but the hash is also stored in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
static uint8_t* signerSha256(const uint8_t* data, size_t sz) {
// Initiate SHA256 calculator
(void)atsha204_execute(SHA204_SHA, SHA_INIT, 0, 0, NULL,
SHA_COUNT_SHORT, _signing_tx_buffer, SHA_RSP_SIZE_SHORT, _signing_rx_buffer);
// Calculate a hash
memset(_signing_temp_message, 0x00, SHA_MSG_SIZE);
memcpy(_signing_temp_message, data, sz);
_signing_temp_message[sz] = 0x80;
// Write length data to the last bytes
_signing_temp_message[SHA_MSG_SIZE-2] = (sz >> 5);
_signing_temp_message[SHA_MSG_SIZE-1] = (sz << 3);
(void)atsha204_execute(SHA204_SHA, SHA_CALC, 0, SHA_MSG_SIZE, _signing_temp_message,
SHA_COUNT_LONG, _signing_tx_buffer, SHA_RSP_SIZE_LONG, _signing_rx_buffer);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32);
return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
+303
View File
@@ -0,0 +1,303 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
*******************************
*
* DESCRIPTION
* Signing support created by Patrick "Anticimex" Fallberg <patrick@fallberg.net>
* ATSHA204 emulated signing backend. The emulated ATSHA204 implementation offers pseudo random
* number generation and HMAC-SHA256 authentication compatible with a "physical" ATSHA204.
* NOTE: Key is stored in clear text in the Arduino firmware. Therefore, the use of this back-end
* could compromise the key used in the signed message infrastructure if device is lost and its memory
* dumped.
*
*/
#include "MySigning.h"
#include "drivers/ATSHA204/sha256.h"
#define SIGNING_IDENTIFIER (1)
// Define MY_DEBUG_VERBOSE_SIGNING in your sketch to enable signing backend debugprints
Sha256Class _signing_sha256;
unsigned long _signing_timestamp;
bool _signing_verification_ongoing = false;
uint8_t _signing_verifying_nonce[32];
uint8_t _signing_signing_nonce[32];
uint8_t _signing_temp_message[32];
static uint8_t _signing_hmac_key[32];
uint8_t _signing_hmac[32];
extern uint8_t _doWhitelist[32];
static uint8_t _signing_node_serial_info[9];
#ifdef MY_SIGNING_NODE_WHITELISTING
const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
static void signerCalculateSignature(MyMessage &msg, bool signing);
#ifdef MY_DEBUG_VERBOSE_SIGNING
static char i2h(uint8_t i)
{
uint8_t k = i & 0x0F;
if (k <= 9)
return '0' + k;
else
return 'A' + k - 10;
}
static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, uint8_t sz) {
static char printBuffer[300];
#ifdef MY_GATEWAY_FEATURE
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
snprintf_P(printBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
MY_SERIALDEVICE.print(printBuffer);
#endif
for (int i=0; i<sz; i++)
{
printBuffer[i * 2] = i2h(buf[i] >> 4);
printBuffer[(i * 2) + 1] = i2h(buf[i]);
}
printBuffer[sz * 2] = '\0';
#ifdef MY_GATEWAY_FEATURE
// Truncate message if this is gateway node
printBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1-strlen_P((const char*)str)] = '\0';
#endif
MY_SERIALDEVICE.print(str);
if (sz > 0)
{
MY_SERIALDEVICE.print(printBuffer);
}
MY_SERIALDEVICE.println("");
}
#else
#define DEBUG_SIGNING_PRINTBUF(str, buf, sz)
#endif
void signerAtsha204SoftInit(void) {
// initialize pseudo-RNG
hwRandomNumberInit();
// Set secrets
hwReadConfigBlock((void*)_signing_hmac_key, (void*)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS, 32);
hwReadConfigBlock((void*)_signing_node_serial_info, (void*)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS, 9);
}
bool signerAtsha204SoftCheckTimer(void) {
if (_signing_verification_ongoing) {
if (hwMillis() < _signing_timestamp || hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
DEBUG_SIGNING_PRINTBUF(F("Verification timeout"), NULL, 0);
// Purge nonce
memset(_signing_signing_nonce, 0xAA, 32);
memset(_signing_verifying_nonce, 0xAA, 32);
_signing_verification_ongoing = false;
return false;
}
}
return true;
}
bool signerAtsha204SoftGetNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204Soft"), NULL, 0);
// We used a basic whitening technique that XORs a random byte with the current hwMillis() counter and then the byte is
// hashed (SHA256) to produce the resulting nonce
_signing_sha256.init();
for (int i = 0; i < 32; i++) {
_signing_sha256.write(random(256) ^ (hwMillis()&0xFF));
}
memcpy(_signing_verifying_nonce, _signing_sha256.result(), MAX_PAYLOAD);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), _signing_verifying_nonce, 32);
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_verifying_nonce)-MAX_PAYLOAD);
// Transfer the first part of the nonce to the message
msg.set(_signing_verifying_nonce, MAX_PAYLOAD);
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
// Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
// Note that if message is "too" quick, and arrives before turnover, it will be rejected
// but this is consider such a rare case that it is accepted and rejects are 'safe'
if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) _signing_timestamp = 0;
return true;
}
void signerAtsha204SoftPutNonce(MyMessage &msg) {
DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204Soft"), NULL, 0);
memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD);
// We set the part of the 32-byte nonce that does not fit into a message to 0xAA
memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD);
}
bool signerAtsha204SoftSignMsg(MyMessage &msg) {
// If we cannot fit any signature in the message, refuse to sign it
if (mGetLength(msg) > MAX_PAYLOAD-2) {
DEBUG_SIGNING_PRINTBUF(F("Message too large"), NULL, 0);
return false;
}
// Calculate signature of message
mSetSigned(msg, 1); // make sure signing flag is set before signature is calculated
signerCalculateSignature(msg, true);
if (DO_WHITELIST(msg.destination)) {
// Salt the signature with the senders nodeId and the (hopefully) unique serial The Creator has provided
_signing_sha256.init();
for (int i=0; i<32; i++) _signing_sha256.write(_signing_hmac[i]);
_signing_sha256.write(msg.sender);
for (int i=0; i<SHA204_SERIAL_SZ; i++) _signing_sha256.write(_signing_node_serial_info[i]);
memcpy(_signing_hmac, _signing_sha256.result(), 32);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), _signing_hmac, 32);
DEBUG_SIGNING_PRINTBUF(F("Signature salted with serial"), NULL, 0);
}
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Transfer as much signature data as the remaining space in the message permits
memcpy(&msg.data[mGetLength(msg)], _signing_hmac, MAX_PAYLOAD-mGetLength(msg));
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
return true;
}
bool signerAtsha204SoftVerifyMsg(MyMessage &msg) {
if (!_signing_verification_ongoing) {
DEBUG_SIGNING_PRINTBUF(F("No active verification session"), NULL, 0);
return false;
} else {
// Make sure we have not expired
if (!signerCheckTimer()) {
return false;
}
_signing_verification_ongoing = false;
if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) {
DEBUG_SIGNING_PRINTBUF(F("Incorrect signing identifier"), NULL, 0);
return false;
}
// Get signature of message
DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg));
signerCalculateSignature(msg, false);
#ifdef MY_SIGNING_NODE_WHITELISTING
// Look up the senders nodeId in our whitelist and salt the signature with that data
size_t j;
for (j=0; j < NUM_OF(_signing_whitelist); j++) {
if (_signing_whitelist[j].nodeId == msg.sender) {
DEBUG_SIGNING_PRINTBUF(F("Sender found in whitelist"), NULL, 0);
_signing_sha256.init();
for (int i=0; i<32; i++) _signing_sha256.write(_signing_hmac[i]);
_signing_sha256.write(msg.sender);
for (int i=0; i<SHA204_SERIAL_SZ; i++) _signing_sha256.write(_signing_whitelist[j].serial[i]);
memcpy(_signing_hmac, _signing_sha256.result(), 32);
DEBUG_SIGNING_PRINTBUF(F("SHA256: "), _signing_hmac, 32);
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
DEBUG_SIGNING_PRINTBUF(F("Sender not found in whitelist, message rejected!"), NULL, 0);
return false;
}
#endif
// Overwrite the first byte in the signature with the signing identifier
_signing_hmac[0] = SIGNING_IDENTIFIER;
// Compare the caluclated signature with the provided signature
if (signerMemcmp(&msg.data[mGetLength(msg)], _signing_hmac, MAX_PAYLOAD-mGetLength(msg))) {
DEBUG_SIGNING_PRINTBUF(F("Signature bad: "), _signing_hmac, MAX_PAYLOAD-mGetLength(msg));
#ifdef MY_SIGNING_NODE_WHITELISTING
DEBUG_SIGNING_PRINTBUF(F("Is the sender whitelisted and serial correct?"), NULL, 0);
#endif
return false;
} else {
DEBUG_SIGNING_PRINTBUF(F("Signature OK"), NULL, 0);
return true;
}
}
}
// Helper to calculate signature of msg (returned in hmac)
static void signerCalculateSignature(MyMessage &msg, bool signing) {
memset(_signing_temp_message, 0, 32);
memcpy(_signing_temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
DEBUG_SIGNING_PRINTBUF(F("Message to process: "), (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
DEBUG_SIGNING_PRINTBUF(F("Current nonce: "), signing ? _signing_signing_nonce : _signing_verifying_nonce, 32);
// ATSHA204 calculates the HMAC with a PSK and a SHA256 digest of the following data:
// 32 bytes zeroes
// 32 bytes digest,
// 1 byte OPCODE (0x11)
// 1 byte Mode (0x04)
// 2 bytes SlotID (0x0000)
// 11 bytes zeroes
// SN[8] (0xEE)
// 4 bytes zeroes
// SN[0:1] (0x0123)
// 2 bytes zeroes
// The digest is calculated as a SHA256 digest of the following:
// 32 bytes message
// 1 byte OPCODE (0x15)
// 1 byte param1 (0x02)
// 2 bytes param2 (0x0800)
// SN[8] (0xEE)
// SN[0:1] (0x0123)
// 25 bytes zeroes
// 32 bytes nonce
// Calculate message digest first
_signing_sha256.init();
for (int i=0; i<32; i++) _signing_sha256.write(_signing_temp_message[i]);
_signing_sha256.write(0x15); // OPCODE
_signing_sha256.write(0x02); // param1
_signing_sha256.write(0x08); // param2(1)
_signing_sha256.write(0x00); // param2(2)
_signing_sha256.write(0xEE); // SN[8]
_signing_sha256.write(0x01); // SN[0]
_signing_sha256.write(0x23); // SN[1]
for (int i=0; i<25; i++) _signing_sha256.write(0x00);
for (int i=0; i<32; i++) _signing_sha256.write(signing ? _signing_signing_nonce[i] : _signing_verifying_nonce[i]);
// Purge nonce when used
memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0xAA, 32);
memcpy(_signing_temp_message, _signing_sha256.result(), 32);
// Feed "message" to HMAC calculator
_signing_sha256.initHmac(_signing_hmac_key,32); // Set the key to use
for (int i=0; i<32; i++) _signing_sha256.write(0x00); // 32 bytes zeroes
for (int i=0; i<32; i++) _signing_sha256.write(_signing_temp_message[i]); // 32 bytes digest
_signing_sha256.write(0x11); // OPCODE
_signing_sha256.write(0x04); // Mode
_signing_sha256.write(0x00); // SlotID(1)
_signing_sha256.write(0x00); // SlotID(2)
for (int i=0; i<11; i++) _signing_sha256.write(0x00); // 11 bytes zeroes
_signing_sha256.write(0xEE); // SN[8]
for (int i=0; i<4; i++) _signing_sha256.write(0x00); // 4 bytes zeroes
_signing_sha256.write(0x01); // SN[0]
_signing_sha256.write(0x23); // SN[1]
for (int i=0; i<2; i++) _signing_sha256.write(0x00); // 2 bytes zeroes
memcpy(_signing_hmac, _signing_sha256.resultHmac(), 32);
DEBUG_SIGNING_PRINTBUF(F("HMAC: "), _signing_hmac, 32);
}
+878
View File
@@ -0,0 +1,878 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyTransport.h"
// SM: transitions and update states
static transportState stInit = { stInitTransition, stInitUpdate };
static transportState stParent = { stParentTransition, stParentUpdate };
static transportState stID = { stIDTransition, stIDUpdate };
static transportState stUplink = { stUplinkTransition, stUplinkUpdate };
static transportState stReady = { stReadyTransition, stReadyUpdate };
static transportState stFailure = { stFailureTransition, stFailureUpdate };
// transport SM variables
static transportSM _transportSM;
// global variables
extern MyMessage _msg; // incoming message
extern MyMessage _msgTmp; // outgoing message
extern NodeConfig _nc; // node config
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
static routingTable _transportRoutingTable; //!< routing table
static uint32_t _lastRoutingTableSave; //!< last routing table dump
#endif
// regular sanity check, activated by default on GW and repeater nodes
#if defined(MY_TRANSPORT_SANITY_CHECK)
static uint32_t _lastSanityCheck; //!< last sanity check
#endif
// regular network discovery, sends I_DISCOVER_REQUESTS to update routing table
// sufficient to have GW triggering requests to also update repeater nodes
#if defined(MY_GATEWAY_FEATURE)
static uint32_t _lastNetworkDiscovery; //! last network discovery
#endif
// stInit: initialise transport HW
void stInitTransition(void) {
TRANSPORT_DEBUG(PSTR("TSM:INIT\n"));
// initialise status variables
_transportSM.pingActive = false;
_transportSM.transportActive = false;
_transportSM.lastUplinkCheck = 0ul;
#if defined(MY_TRANSPORT_SANITY_CHECK)
_lastSanityCheck = hwMillis();
#endif
#if defined(MY_GATEWAY_FEATURE)
_lastNetworkDiscovery = 0ul;
#endif
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
_lastRoutingTableSave = hwMillis();
#endif
// Read node settings (ID, parent ID, GW distance) from EEPROM
hwReadConfigBlock((void*)&_nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig));
}
void stInitUpdate(void) {
#if defined(MY_TRANSPORT_DONT_CARE_MODE)
TRANSPORT_DEBUG(PSTR("TSM:INIT:TDC\n")); // transport don't care mode
#endif
// initialise radio
if (!transportInit()) {
TRANSPORT_DEBUG(PSTR("!TSM:INIT:TSP FAIL\n"));
setIndication(INDICATION_ERR_INIT_TRANSPORT);
transportSwitchSM(stFailure);
}
else {
TRANSPORT_DEBUG(PSTR("TSM:INIT:TSP OK\n"));
_transportSM.transportActive = true;
#if defined(MY_GATEWAY_FEATURE)
// Set configuration for gateway
TRANSPORT_DEBUG(PSTR("TSM:INIT:GW MODE\n"));
_nc.parentNodeId = GATEWAY_ADDRESS;
_nc.distance = 0u;
_nc.nodeId = GATEWAY_ADDRESS;
transportSetAddress(GATEWAY_ADDRESS);
// GW mode: skip FPAR,ID,UPL states
transportSwitchSM(stReady);
#else
if ((uint8_t)MY_NODE_ID != AUTO) {
TRANSPORT_DEBUG(PSTR("TSM:INIT:STATID=%d\n"),(uint8_t)MY_NODE_ID);
// Set static ID
_nc.nodeId = (uint8_t)MY_NODE_ID;
// Save static ID to eeprom (for bootloader)
hwWriteConfig(EEPROM_NODE_ID_ADDRESS, (uint8_t)MY_NODE_ID);
}
// assign ID if set
if (_nc.nodeId == AUTO || transportAssignNodeID(_nc.nodeId)) {
// if node ID valid (>0 and <255), proceed to next state
transportSwitchSM(stParent);
}
else {
// ID invalid (0 or 255)
transportSwitchSM(stFailure);
}
#endif
}
}
// stParent: find parent
void stParentTransition(void) {
TRANSPORT_DEBUG(PSTR("TSM:FPAR\n")); // find parent
setIndication(INDICATION_FIND_PARENT);
_transportSM.uplinkOk = false;
_transportSM.preferredParentFound = false;
#if defined(MY_PARENT_NODE_IS_STATIC)
TRANSPORT_DEBUG(PSTR("TSM:FPAR:STATP=%d\n"), (uint8_t)MY_PARENT_NODE_ID); // static parent
_transportSM.findingParentNode = false;
_nc.distance = 1u; // assumption, CHKUPL:GWDC will update this variable
_nc.parentNodeId = (uint8_t)MY_PARENT_NODE_ID;
// save parent ID to eeprom (for bootloader)
hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, (uint8_t)MY_PARENT_NODE_ID);
#else
_transportSM.findingParentNode = true;
_nc.distance = DISTANCE_INVALID; // Set distance to max and invalidate parent node ID
_nc.parentNodeId = AUTO;
// Broadcast find parent request
(void)transportRouteMessage(build(_msgTmp, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_REQUEST, false).set(""));
#endif
}
// stParentUpdate
void stParentUpdate(void) {
#if defined(MY_PARENT_NODE_IS_STATIC)
// skipping find parent
setIndication(INDICATION_GOT_PARENT);
transportSwitchSM(stID);
#else
if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS || _transportSM.preferredParentFound) {
// timeout or preferred parent found
if (_nc.parentNodeId != AUTO) {
// parent assigned
TRANSPORT_DEBUG(PSTR("TSM:FPAR:OK\n")); // find parent ok
_transportSM.findingParentNode = false;
setIndication(INDICATION_GOT_PARENT);
// go to next state
transportSwitchSM(stID);
}
else {
// timeout w/o reply or valid parent
if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) {
// retries left
TRANSPORT_DEBUG(PSTR("!TSM:FPAR:NO REPLY\n")); // find parent, no reply
// reenter state
transportSwitchSM(stParent);
}
else {
// no retries left, finding parent failed
TRANSPORT_DEBUG(PSTR("!TSM:FPAR:FAIL\n"));
setIndication(INDICATION_ERR_FIND_PARENT);
transportSwitchSM(stFailure);
}
}
}
#endif
}
// stID: verify and request ID if necessary
void stIDTransition(void) {
TRANSPORT_DEBUG(PSTR("TSM:ID\n")); // verify/request node ID
if (_nc.nodeId == AUTO) {
// send ID request
setIndication(INDICATION_REQ_NODEID);
TRANSPORT_DEBUG(PSTR("TSM:ID:REQ\n")); // request node ID
(void)transportRouteMessage(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_ID_REQUEST, false).set(""));
}
}
void stIDUpdate(void) {
if (_nc.nodeId != AUTO) {
// current node ID is valid
TRANSPORT_DEBUG(PSTR("TSM:ID:OK\n"));
setIndication(INDICATION_GOT_NODEID);
// proceed to next state
transportSwitchSM(stUplink);
}
else if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS) {
// timeout
if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) {
// retries left: reenter state
transportSwitchSM(stID);
}
else {
// no retries left
TRANSPORT_DEBUG(PSTR("!TSM:ID:FAIL\n"));
setIndication(INDICATION_ERR_GET_NODEID);
transportSwitchSM(stFailure);
}
}
}
void stUplinkTransition(void) {
#if !defined(MY_TRANSPORT_UPLINK_CHECK_DISABLED)
TRANSPORT_DEBUG(PSTR("TSM:UPL\n"));
setIndication(INDICATION_CHECK_UPLINK);
_transportSM.pingResponse = INVALID_HOPS;
_transportSM.pingActive = true;
(void)transportRouteMessage(build(_msgTmp,GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_PING, false).set((uint8_t)1));
#endif
}
void stUplinkUpdate(void) {
#if !defined(MY_TRANSPORT_UPLINK_CHECK_DISABLED)
if (_transportSM.pingResponse != INVALID_HOPS) {
_transportSM.lastUplinkCheck = hwMillis();
// uplink ok, i.e. GW replied
TRANSPORT_DEBUG(PSTR("TSM:UPL:OK\n")); // uplink ok
//_transportSM.pingActive = false; ==> set false upon receiving I_PONG
if (_transportSM.pingResponse != _nc.distance) {
TRANSPORT_DEBUG(PSTR("TSM:UPL:DGWC,O=%d,N=%d\n"), _nc.distance, _transportSM.pingResponse); // distance to GW changed
_nc.distance = _transportSM.pingResponse;
}
transportSwitchSM(stReady); // proceed to next state
}
else if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS) {
// timeout
if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) {
// retries left: reenter state
transportSwitchSM(stUplink);
}
else {
// no retries left
TRANSPORT_DEBUG(PSTR("!TSM:UPL:FAIL\n")); // uplink check failed
_transportSM.pingActive = false;
setIndication(INDICATION_ERR_CHECK_UPLINK);
transportSwitchSM(stParent); // go back to stParent
}
}
#else
TRANSPORT_DEBUG(PSTR("TSM:UPL:DISABLED\n")); // uplink check disabled
transportSwitchSM(stReady);
#endif
}
void stReadyTransition(void) {
// transport is ready and fully operational
TRANSPORT_DEBUG(PSTR("TSM:READY\n")); // transport is ready
_transportSM.uplinkOk = true;
_transportSM.failureCounter = 0u; // reset failure counter
_transportSM.failedUplinkTransmissions = 0u; // reset failed uplink TX counter
}
// stReadyUpdate: monitors link
void stReadyUpdate(void) {
#if defined(MY_GATEWAY_FEATURE)
if (hwMillis() - _lastNetworkDiscovery > MY_TRANSPORT_DISCOVERY_INTERVAL_MS) {
_lastNetworkDiscovery = hwMillis();
TRANSPORT_DEBUG(PSTR("TSM:READY:NWD REQ\n")); // send transport network discovery
(void)transportRouteMessage(build(_msgTmp, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DISCOVER_REQUEST, false).set(""));
}
#else
if (_transportSM.failedUplinkTransmissions > MY_TRANSPORT_MAX_TX_FAILURES) {
// too many uplink transmissions failed, find new parent (if non-static)
#if !defined(MY_PARENT_NODE_IS_STATIC)
TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,SNP\n")); // uplink failed, search new parent
transportSwitchSM(stParent);
#else
TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,STATP\n")); // uplink failed, static parent
// reset counter
_transportSM.failedUplinkTransmissions = 0u;
#endif
}
#endif
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
if (hwMillis() - _lastRoutingTableSave > MY_ROUTING_TABLE_SAVE_INTERVAL_MS) {
_lastRoutingTableSave = hwMillis();
transportSaveRoutingTable();
}
#endif
}
// stFailure: entered upon HW init failure or max retries exceeded
void stFailureTransition(void) {
if (_transportSM.failureCounter < MY_TRANSPORT_MAX_TSM_FAILURES) {
_transportSM.failureCounter++; // increment consecutive TSM failure counter
}
TRANSPORT_DEBUG(PSTR("TSM:FAIL:CNT=%d\n"),_transportSM.failureCounter);
_transportSM.uplinkOk = false; // uplink nok
_transportSM.transportActive = false; // transport inactive
setIndication(INDICATION_ERR_INIT_TRANSPORT);
#if defined(MY_RADIO_FEATURE)
TRANSPORT_DEBUG(PSTR("TSM:FAIL:PDT\n")); // power down transport, no need until re-init
transportPowerDown();
#endif
}
void stFailureUpdate(void) {
if (transportTimeInState() > ( isTransportExtendedFailure()? MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE: MY_TRANSPORT_TIMEOUT_FAILURE_STATE) ) {
TRANSPORT_DEBUG(PSTR("TSM:FAIL:RE-INIT\n")); // attempt to re-initialise transport
transportSwitchSM(stInit);
}
}
void transportSwitchSM(transportState& newState) {
if (_transportSM.currentState != &newState) {
_transportSM.stateRetries = 0u; // state change, reset retry counter
_transportSM.currentState = &newState; // change state
}
else {
_transportSM.stateRetries++; // increment retries
}
if (_transportSM.currentState->Transition) _transportSM.currentState->Transition(); // State transition
_transportSM.stateEnter = hwMillis(); // save time
}
uint32_t transportTimeInState(void) {
return hwMillis() - _transportSM.stateEnter;
}
void transportUpdateSM(void) {
if (_transportSM.currentState->Update) _transportSM.currentState->Update();
}
bool isTransportReady(void) {
return _transportSM.uplinkOk;
}
bool isTransportExtendedFailure(void) {
return _transportSM.failureCounter == MY_TRANSPORT_MAX_TSM_FAILURES;
}
bool isTransportSearchingParent(void) {
return _transportSM.findingParentNode;
}
bool isMessageReceived(void) {
return _transportSM.MsgReceived;
}
void resetMessageReceived(void) {
_transportSM.MsgReceived = false;
}
void transportInitialise(void) {
_transportSM.failureCounter = 0u; // reset failure counter
transportLoadRoutingTable(); // load routing table to RAM (if feature enabled)
// intial state
_transportSM.currentState = NULL;
transportSwitchSM(stInit);
}
// update TSM and process incoming messages
void transportProcess(void) {
// update state machine
transportUpdateSM();
// process transport FIFO
transportProcessFIFO();
}
bool transportCheckUplink(const bool force) {
if (!force && (hwMillis() - _transportSM.lastUplinkCheck) < MY_TRANSPORT_CHKUPL_INTERVAL_MS) {
TRANSPORT_DEBUG(PSTR("TSF:CKU:OK,FCTRL\n")); // flood control
return true;
}
// ping GW
const uint8_t hopsCount = transportPingNode(GATEWAY_ADDRESS);
// verify hops
if (hopsCount != INVALID_HOPS) {
// update
_transportSM.lastUplinkCheck = hwMillis();
TRANSPORT_DEBUG(PSTR("TSF:CKU:OK\n"));
// did distance to GW change upstream, eg. re-routing of uplink nodes
if (hopsCount != _nc.distance) {
TRANSPORT_DEBUG(PSTR("TSF:CKU:DGWC,O=%d,N=%d\n"), _nc.distance, hopsCount); // distance to GW changed
_nc.distance = hopsCount;
}
return true;
}
else {
TRANSPORT_DEBUG(PSTR("TSF:CKU:FAIL\n"));
return false;
}
}
bool transportAssignNodeID(const uint8_t newNodeId) {
// verify if ID valid
if (newNodeId != GATEWAY_ADDRESS && newNodeId != AUTO) {
_nc.nodeId = newNodeId;
transportSetAddress(newNodeId);
// Write ID to EEPROM
hwWriteConfig(EEPROM_NODE_ID_ADDRESS, newNodeId);
TRANSPORT_DEBUG(PSTR("TSF:SID:OK,ID=%d\n"),newNodeId); // Node ID assigned
return true;
}
else {
TRANSPORT_DEBUG(PSTR("!TSF:SID:FAIL,ID=%d\n"),newNodeId); // ID is invalid, cannot assign ID
setIndication(INDICATION_ERR_NET_FULL);
_nc.nodeId = AUTO;
return false;
}
}
bool transportRouteMessage(MyMessage &message) {
const uint8_t destination = message.destination;
uint8_t route = _nc.parentNodeId; // by default, all traffic is routed via parent node
if (_transportSM.findingParentNode && destination != BROADCAST_ADDRESS) {
TRANSPORT_DEBUG(PSTR("!TSF:RTE:FPAR ACTIVE\n")); // find parent active, message not sent
// request to send a non-BC message while finding parent active, abort
return false;
}
if (destination == GATEWAY_ADDRESS) {
route = _nc.parentNodeId; // message to GW always routes via parent
}
else if (destination == BROADCAST_ADDRESS) {
route = BROADCAST_ADDRESS; // message to BC does not require routing
}
else {
#if defined(MY_REPEATER_FEATURE)
// destination not GW & not BC, get route
route = transportGetRoute(destination);
if (route == AUTO) {
TRANSPORT_DEBUG(PSTR("!TSF:RTE:%d UNKNOWN\n"), destination); // route unknown
#if !defined(MY_GATEWAY_FEATURE)
if (message.last != _nc.parentNodeId) {
// message not from parent, i.e. child node - route it to parent
route = _nc.parentNodeId;
}
else {
// route unknown and msg received from parent, send it to destination assuming in rx radius
route = destination;
}
#else
// if GW, all unknown destinations are directly addressed
route = destination;
#endif
}
#else
route = _nc.parentNodeId; // not a repeater, all traffic routed via parent
#endif
}
// send message
const bool result = transportSendWrite(route, message);
#if !defined(MY_GATEWAY_FEATURE)
// update counter
if (route == _nc.parentNodeId) {
if (!result) {
setIndication(INDICATION_ERR_TX);
_transportSM.failedUplinkTransmissions++;
}
else _transportSM.failedUplinkTransmissions = 0u;
}
#else
if(!result) setIndication(INDICATION_ERR_TX);
#endif
return result;
}
bool transportSendRoute(MyMessage &message) {
bool result = false;
if (isTransportReady()) {
result = transportRouteMessage(message);
}
else {
// TNR: transport not ready
TRANSPORT_DEBUG(PSTR("!TSF:SND:TNR\n"));
}
return result;
}
// only be used inside transport
bool transportWait(const uint32_t ms, const uint8_t cmd, const uint8_t msgtype){
const uint32_t enter = hwMillis();
// invalidate msg type
_msg.type = !msgtype;
bool expectedResponse = false;
while ((hwMillis() - enter < ms) && !expectedResponse) {
// process incoming messages
transportProcessFIFO();
yield(); // process esp8266 stack, ignored for AVR
expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgtype);
}
return expectedResponse;
}
uint8_t transportPingNode(const uint8_t targetId) {
if(!_transportSM.pingActive){
TRANSPORT_DEBUG(PSTR("TSF:PNG:SEND,TO=%d\n"), targetId);
if(targetId == _nc.nodeId) {
// pinging self
_transportSM.pingResponse = 0u;
}
else {
_transportSM.pingActive = true;
_transportSM.pingResponse = INVALID_HOPS;
(void)transportRouteMessage(build(_msgTmp, targetId, NODE_SENSOR_ID, C_INTERNAL, I_PING, false).set((uint8_t)1));
// Wait for ping reply or timeout
(void)transportWait(2000, C_INTERNAL, I_PONG);
}
// make sure missing I_PONG msg does not block pinging function by leaving pignActive=true
_transportSM.pingActive = false;
return _transportSM.pingResponse;
}
else {
TRANSPORT_DEBUG(PSTR("!TSF:PNG:ACTIVE\n")); // ping active, cannot start new ping
return INVALID_HOPS;
}
}
uint32_t transportGetHeartbeat(void) {
return transportTimeInState();
}
void transportProcessMessage(void) {
// Manage signing timeout
(void)signerCheckTimer();
// receive message
setIndication(INDICATION_RX);
const uint8_t payloadLength = transportReceive((uint8_t *)&_msg);
// get message length and limit size
const uint8_t msgLength = min(mGetLength(_msg), (uint8_t)MAX_PAYLOAD);
// calculate expected length
const uint8_t expectedMessageLength = HEADER_SIZE + (mGetSigned(_msg) ? MAX_PAYLOAD : msgLength);
const uint8_t command = mGetCommand(_msg);
const uint8_t type = _msg.type;
const uint8_t sender = _msg.sender;
const uint8_t last = _msg.last;
const uint8_t destination = _msg.destination;
TRANSPORT_DEBUG(PSTR("TSF:MSG:READ,%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"),
sender, last, destination, _msg.sensor, command, type, mGetPayloadType(_msg), msgLength, mGetSigned(_msg), _msg.getString(_convBuf));
// Reject payloads with incorrect length
if (payloadLength != expectedMessageLength) {
setIndication(INDICATION_ERR_LENGTH);
TRANSPORT_DEBUG(PSTR("!TSF:MSG:LEN,%d!=%d\n"), payloadLength, expectedMessageLength); // invalid payload length
return;
}
// Reject messages with incorrect protocol version
if (mGetVersion(_msg) != PROTOCOL_VERSION) {
setIndication(INDICATION_ERR_VERSION);
TRANSPORT_DEBUG(PSTR("!TSF:MSG:PVER,%d=%d\n"), mGetVersion(_msg), PROTOCOL_VERSION); // protocol version mismatch
return;
}
// Reject messages that do not pass verification
if (!signerVerifyMsg(_msg)) {
setIndication(INDICATION_ERR_SIGN);
TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN VERIFY FAIL\n"));
return;
}
// update routing table if msg not from parent
#if defined(MY_REPEATER_FEATURE)
#if !defined(MY_GATEWAY_FEATURE)
if (last != _nc.parentNodeId) {
#else
// GW doesn't have parent
{
#endif
// Message is from one of the child nodes. Add it to routing table.
transportSetRoute(sender, last);
}
#endif
// set message received flag
_transportSM.MsgReceived = true;
// Is message addressed to this node?
if (destination == _nc.nodeId) {
// prevent buffer overflow by limiting max. possible message length (5 bits=31 bytes max) to MAX_PAYLOAD (25 bytes)
mSetLength(_msg, min(mGetLength(_msg),(uint8_t)MAX_PAYLOAD));
// null terminate data
_msg.data[msgLength] = 0u;
// Check if sender requests an ack back.
if (mGetRequestAck(_msg)) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:ACK REQ\n")); // ACK requested
_msgTmp = _msg; // Copy message
mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop)
mSetAck(_msgTmp, true); // set ACK flag
_msgTmp.sender = _nc.nodeId;
_msgTmp.destination = sender;
// send ACK, use transportSendRoute since ACK reply is not internal, i.e. if !transportOK do not reply
(void)transportSendRoute(_msgTmp);
}
if(!mGetAck(_msg)) {
// only process if not ACK
if (command == C_INTERNAL) {
// Process signing related internal messages
if (signerProcessInternal(_msg)) {
return; // Signer processing indicated no further action needed
}
#if !defined(MY_GATEWAY_FEATURE)
if (type == I_ID_RESPONSE) {
#if (MY_NODE_ID == AUTO)
// only active if node ID dynamic
(void)transportAssignNodeID(_msg.getByte());
#endif
return; // no further processing required
}
if (type == I_FIND_PARENT_RESPONSE) {
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_PARENT_NODE_IS_STATIC)
if (_transportSM.findingParentNode) { // only process if find parent active
// Reply to a I_FIND_PARENT_REQUEST message. Check if the distance is shorter than we already have.
uint8_t distance = _msg.getByte();
if (isValidDistance(distance)) {
distance++; // Distance to gateway is one more for us w.r.t. parent
// update settings if distance shorter or preferred parent found
if (((isValidDistance(distance) && distance < _nc.distance) || (!_autoFindParent && sender == (uint8_t)MY_PARENT_NODE_ID)) && !_transportSM.preferredParentFound) {
// Found a neighbor closer to GW than previously found
if (!_autoFindParent && sender == (uint8_t)MY_PARENT_NODE_ID) {
_transportSM.preferredParentFound = true;
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR PREF\n")); // find parent, preferred parent found
}
_nc.distance = distance;
_nc.parentNodeId = sender;
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR OK,ID=%d,D=%d\n"), _nc.parentNodeId, _nc.distance);
}
}
}
else {
TRANSPORT_DEBUG(PSTR("!TSF:MSG:FPAR INACTIVE\n")); // find parent response received, but inactive
}
return;
#endif
}
#endif
// general
if (type == I_PING) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:PINGED,ID=%d,HP=%d\n"), sender, _msg.getByte()); // node pinged
#if defined(MY_GATEWAY_FEATURE) && (F_CPU>16000000)
// delay for fast GW and slow nodes
delay(5);
#endif
(void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL, I_PONG, false).set((uint8_t)1));
return; // no further processing required
}
if (type == I_PONG) {
if (_transportSM.pingActive) {
_transportSM.pingActive = false;
_transportSM.pingResponse = _msg.getByte();
TRANSPORT_DEBUG(PSTR("TSF:MSG:PONG RECV,HP=%d\n"), _transportSM.pingResponse); // pong received
}
else {
TRANSPORT_DEBUG(PSTR("!TSF:MSG:PONG RECV,INACTIVE\n")); // pong received, but !pingActive
}
return; // no further processing required
}
if (_processInternalMessages()) {
return; // no further processing required
}
} else if (command == C_STREAM) {
#if defined(MY_OTA_FIRMWARE_FEATURE)
if(firmwareOTAUpdateProcess()){
return; // OTA FW update processing indicated no further action needed
}
#endif
}
}
else {
TRANSPORT_DEBUG(PSTR("TSF:MSG:ACK\n")); // received message is ACK, no internal processing, handover to msg callback
}
#if defined(MY_GATEWAY_FEATURE)
// Hand over message to controller
(void)gatewayTransportSend(_msg);
#endif
// Call incoming message callback if available
if (receive) {
receive(_msg);
}
}
else if (destination == BROADCAST_ADDRESS) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:BC\n")); // broadcast msg
if (command == C_INTERNAL) {
if (isTransportReady()) {
// only reply if node is fully operational
if (type == I_FIND_PARENT_REQUEST) {
#if defined(MY_REPEATER_FEATURE)
if (sender != _nc.parentNodeId) { // no circular reference
TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR REQ,ID=%d\n"), sender); // FPAR: find parent request
// check if uplink functional - node can only be parent node if link to GW functional
// this also prevents circular references in case GW ooo
if (transportCheckUplink()) {
_transportSM.lastUplinkCheck = hwMillis();
TRANSPORT_DEBUG(PSTR("TSF:MSG:GWL OK\n")); // GW uplink ok
// random delay minimizes collisions
delay(hwMillis() & 0x3ff);
(void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(_nc.distance));
}
else {
TRANSPORT_DEBUG(PSTR("!TSF:MSG:GWL FAIL\n")); // GW uplink fail, do not respond to parent request
}
}
#endif
return; // no further processing required, do not forward
}
} // isTransportReady
if (type == I_FIND_PARENT_RESPONSE) {
return; // no further processing required, do not forward
}
#if !defined(MY_GATEWAY_FEATURE)
if (type == I_DISCOVER_REQUEST) {
if (last == _nc.parentNodeId) {
// random wait to minimize collisions
delay(hwMillis() & 0x3ff);
(void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL, I_DISCOVER_RESPONSE, false).set(_nc.parentNodeId));
// no return here (for fwd if repeater)
}
}
#endif
}
// controlled BC relay
#if defined(MY_REPEATER_FEATURE)
// controlled BC repeating: forward only if message received from parent and sender not self to prevent circular fwds
if(last == _nc.parentNodeId && sender != _nc.nodeId && isTransportReady()){
TRANSPORT_DEBUG(PSTR("TSF:MSG:FWD BC MSG\n")); // controlled broadcast msg forwarding
(void)transportRouteMessage(_msg);
}
#endif
// Callback for BC, only for non-internal messages
if (command != C_INTERNAL) {
#if !defined(MY_GATEWAY_FEATURE)
// only proceed if message received from parent
if (last != _nc.parentNodeId) return;
#endif
#if defined(MY_GATEWAY_FEATURE)
// Hand over message to controller
(void)gatewayTransportSend(_msg);
#endif
if (receive) {
receive(_msg);
}
}
}
else {
// msg not to us and not BC, relay msg
#if defined(MY_REPEATER_FEATURE)
if (isTransportReady()) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:REL MSG\n")); // relay msg
if (command == C_INTERNAL) {
if (type == I_PING || type == I_PONG) {
uint8_t hopsCnt = _msg.getByte();
if (hopsCnt != MAX_HOPS) {
TRANSPORT_DEBUG(PSTR("TSF:MSG:REL PxNG,HP=%d\n"), hopsCnt);
hopsCnt++;
_msg.set(hopsCnt);
}
}
}
// Relay this message to another node
(void)transportRouteMessage(_msg);
}
#else
TRANSPORT_DEBUG(PSTR("!TSF:MSG:REL MSG,NREP\n")); // message relaying request, but not a repeater
#endif
}
}
void transportInvokeSanityCheck(void) {
if (!transportSanityCheck()) {
TRANSPORT_DEBUG(PSTR("!TSF:SNK:FAIL\n")); // sanity check fail
transportSwitchSM(stFailure);
}
else {
TRANSPORT_DEBUG(PSTR("TSF:SNK:OK\n")); // sanity check ok
}
}
void transportProcessFIFO(void) {
if (!_transportSM.transportActive) {
// transport not active, no further processing required
return;
}
#if defined(MY_TRANSPORT_SANITY_CHECK)
if (hwMillis() - _lastSanityCheck > MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS) {
_lastSanityCheck = hwMillis();
transportInvokeSanityCheck();
}
#endif
uint8_t _processedMessages = MAX_SUBSEQ_MSGS;
// process all msgs in FIFO or counter exit
while (transportAvailable() && _processedMessages--) {
transportProcessMessage();
}
#if defined(MY_OTA_FIRMWARE_FEATURE)
if (isTransportReady()) {
// only process if transport ok
firmwareOTAUpdateRequest();
}
#endif
}
bool transportSendWrite(const uint8_t to, MyMessage &message) {
message.last = _nc.nodeId; // Update last
// sign message if required
if (!signerSignMsg(message)) {
TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN FAIL\n"));
setIndication(INDICATION_ERR_SIGN);
return false;
}
// msg length changes if signed
const uint8_t totalMsgLength = HEADER_SIZE + ( mGetSigned(message) ? MAX_PAYLOAD : mGetLength(message) );
// send
setIndication(INDICATION_TX);
bool result = transportSend(to, &message, min((uint8_t)MAX_MESSAGE_LENGTH, totalMsgLength));
// broadcasting (workaround counterfeits)
result |= (to == BROADCAST_ADDRESS);
TRANSPORT_DEBUG(PSTR("%sTSF:MSG:SEND,%d-%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,ft=%d,st=%s:%s\n"),
(result ? "" : "!"), message.sender, message.last, to, message.destination, message.sensor, mGetCommand(message), message.type,
mGetPayloadType(message), mGetLength(message), mGetSigned(message), _transportSM.failedUplinkTransmissions, (result ? "OK" : "NACK"), message.getString(_convBuf));
return result;
}
void transportClearRoutingTable(void) {
for (uint16_t i = 0; i < SIZE_ROUTES; i++) {
transportSetRoute((uint8_t)i, BROADCAST_ADDRESS);
}
transportSaveRoutingTable(); // save cleared routing table to EEPROM (if feature enabled)
TRANSPORT_DEBUG(PSTR("TSF:CRT:OK\n")); // clear routing table
}
void transportLoadRoutingTable(void) {
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
hwReadConfigBlock((void*)&_transportRoutingTable.route, (void*)EEPROM_ROUTES_ADDRESS, SIZE_ROUTES);
TRANSPORT_DEBUG(PSTR("TSF:LRT:OK\n")); // load routing table
#endif
}
void transportSaveRoutingTable(void) {
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
hwWriteConfigBlock((void*)EEPROM_ROUTES_ADDRESS, (void*)&_transportRoutingTable.route, SIZE_ROUTES);
TRANSPORT_DEBUG(PSTR("TSF:SRT:OK\n")); // save routing table
#endif
}
void transportSetRoute(const uint8_t node, const uint8_t route) {
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
_transportRoutingTable.route[node] = route;
#else
hwWriteConfig(EEPROM_ROUTES_ADDRESS + node, route);
#endif
}
uint8_t transportGetRoute(const uint8_t node) {
uint8_t result;
#if defined(MY_RAM_ROUTING_TABLE_ENABLED)
result = _transportRoutingTable.route[node];
#else
result = hwReadConfig(EEPROM_ROUTES_ADDRESS + node);
#endif
return result;
}
// EOF MyTransport.cpp
+488
View File
@@ -0,0 +1,488 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2016 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
/**
* @file MyTransport.h
*
* @defgroup MyTransportgrp MyTransport
* @ingroup internals
* @{
*
* Transport-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE
* - [!] Exclamation mark is prepended in case of error
* - SYSTEM:
* - <b>TSM</b>: messages emitted by the transport state machine
* - <b>TSF</b>: messages emitted by transport support functions
* - SUB SYSTEMS:
* - Transport state machine (<b>TSM</b>)
* - TSM:INIT from <b>stInit</b> Initialize transport and radio
* - TSM:FPAR from <b>stParent</b> Find parent
* - TSM:ID from <b>stID</b> Check/request node ID, if dynamic node ID set
* - TSM:UPL from <b>stUplink</b> Verify uplink connection by pinging GW
* - TSM:READY from <b>stReady</b> Transport is ready and fully operational
* - TSM:FAIL from <b>stFailure</b> Failure in transport link or transport HW
* - Transport support function (<b>TSF</b>)
* - TSF:CHKUPL from @ref transportCheckUplink(), checks connection to GW
* - TSF:ASID from @ref transportAssignNodeID(), assigns node ID
* - TSF:PING from @ref transportPingNode(), pings a node
* - TSF:CRT from @ref transportClearRoutingTable(), clears routing table stored in EEPROM
* - TSF:LRT from @ref transportLoadRoutingTable(), loads RAM routing table from EEPROM (only GW/repeaters)
* - TSF:SRT from @ref transportSaveRoutingTable(), saves RAM routing table to EEPROM (only GW/repeaters)
* - TSF:MSG from @ref transportProcessMessage(), processes incoming message
* - TSF:SANCHK from @ref transportInvokeSanityCheck(), calls transport-specific sanity check
* - TSF:ROUTE from @ref transportRouteMessage(), sends message
* - TSF:SEND from @ref transportSendRoute(), sends message if transport is ready (exposed)
*
* Transport debug log messages:
*
* |E| SYS | SUB | Message | Comment
* |-|------|-----------|-----------------------|---------------------------------------------------------------------
* | | TSM | INIT | | <b>Transition to stInit state</b>
* | | TSM | INIT | TDC | Transport don't care mode
* | | TSM | INIT | STATID=%%d | Node ID is static
* | | TSM | INIT | TSP OK | Transport device configured and fully operational
* | | TSM | INIT | GW MODE | Node is set up as GW, thus omitting ID and findParent states
* |!| TSM | INIT | TSP FAIL | Transport device initialization failed
* | | TSM | FPAR | | <b>Transition to stParent state</b>
* | | TSM | FPAR | STATP=%%d | Static parent set, skip finding parent
* | | TSM | FPAR | OK | Parent node identified
* |!| TSM | FPAR | NO REPLY | No potential parents replied to find parent request
* |!| TSM | FPAR | FAIL | Finding parent failed
* | | TSM | ID | | <b>Transition to stID state</b>
* | | TSM | ID | OK,ID=%%d | Node ID is valid
* | | TSM | ID | REQ | Request node ID from controller
* |!| TSM | ID | FAIL,ID=%%d | ID verification failed, ID invalid
* | | TSM | UPL | | <b>Transition to stUplink state</b>
* | | TSM | UPL | OK | Uplink OK, GW returned ping
* | | TSF | UPL | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N)
* |!| TSM | UPL | FAIL | Uplink check failed, i.e. GW could not be pinged
* | | TSM | READY | | <b>Transition to stReady</b>, i.e. transport is ready and fully operational
* | | TSM | READY | SRT | Save routing table
* |!| TSM | READY | UPL FAIL,SNP | Too many failed uplink transmissions, search new parent
* |!| TSM | READY | FAIL,STATP | Too many failed uplink transmissions, static parent enforced
* | | TSM | FAIL | CNT=%%d | <b>Transition to stFailure state</b>, consecutive failure counter (CNT)
* | | TSM | FAIL | PDT | Power-down transport
* | | TSM | FAIL | RE-INIT | Attempt to re-initialize transport
* | | TSF | CHKUPL | OK | Uplink OK
* | | TSF | CHKUPL | OK,FCTRL | Uplink OK, flood control prevents pinging GW in too short intervals
* | | TSF | CHKUPL | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N)
* | | TSF | CHKUPL | FAIL | No reply received when checking uplink
* | | TSF | ASID | OK,ID=%%d | Node ID assigned
* |!| TSF | ASID | FAIL,ID=%%d | Assigned ID is invalid
* | | TSF | PING | SEND,TO=%%d | Send ping to destination (TO)
* | | TSF | MSG | ACK REQ | ACK message requested
* | | TSF | MSG | ACK | ACK message, do not proceed but forward to callback
* | | TSF | MSG | FPAR RES,ID=%%d,D=%%d | Response to find parent received from node (ID) with distance (D) to GW
* | | TSF | MSG | FPAR PREF FOUND | Preferred parent found, i.e. parent defined via MY_PARENT_NODE_ID
* | | TSF | MSG | FPAR OK,ID=%%d,D=%%d | Find parent response from node (ID) is valid, distance (D) to GW
* | | TSF | MSG | FPAR INACTIVE | Find parent response received, but no find parent request active, skip response
* | | TSF | MSG | FPAR REQ,ID=%%d | Find parent request from node (ID)
* | | TSF | MSG | PINGED,ID=%%d,HP=%%d | Node pinged by node (ID) with (HP) hops
* | | TSF | MSG | PONG RECV,HP=%%d | Pinged node replied with (HP) hops
* | | TSF | MSG | BC | Broadcast message received
* | | TSF | MSG | GWL OK | Link to GW ok
* | | TSF | MSG | FWD BC MSG | Controlled broadcast message forwarding
* | | TSF | MSG | REL MSG | Relay message
* | | TSF | MSG | REL PxNG,HP=%%d | Relay PING/PONG message, increment hop counter (HP)
* |!| TSF | MSG | LEN,%%d!=%%d | Invalid message length, (actual!=expected)
* |!| TSF | MSG | PVER,%%d!=%%d | Message protocol version mismatch (actual!=expected)
* |!| TSF | MSG | SIGN VERIFY FAIL | Signing verification failed
* |!| TSF | MSG | REL MSG,NORP | Node received a message for relaying, but node is not a repeater, message skipped
* |!| TSF | MSG | SIGN FAIL | Signing message failed
* |!| TSF | MSG | GWL FAIL | GW uplink failed
* | | TSF | SANCHK | OK | Sanity check passed
* |!| TSF | SANCHK | FAIL | Sanity check failed, attempt to re-initialize radio
* | | TSF | CRT | OK | Clearing routing table successful
* | | TSF | LRT | OK | Loading routing table successful
* | | TSF | SRT | OK | Saving routing table successful
* |!| TSF | ROUTE | FPAR ACTIVE | Finding parent active, message not sent
* |!| TSF | ROUTE | DST %%d UNKNOWN | Routing for destination (DST) unknown, send message to parent
* |!| TSF | SEND | TNR | Transport not ready, message cannot be sent
*
* Incoming / outgoing messages:
*
* See <a href="https://www.mysensors.org/download/serial_api_20">here</a> for more detail on the format and definitons.
*
* Receiving a message
* - TSF:MSG:READ,sender-last-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d:%%s
*
* Sending a message
* - [!]TSF:MSG:SEND,sender-last-next-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d,ft=%%d,st=%%s:%%s
*
* Message fields:
* - <b>s</b>=sensor ID
* - <b>c</b>=command
* - <b>t</b>=msg type
* - <b>pt</b>=payload type
* - <b>l</b>=length
* - <b>sg</b>=signing flag
* - <b>ft</b>=failed uplink transmission counter
* - <b>st</b>=send status, OK=success, NACK=no radio ACK received
*
* @brief API declaration for MyTransport
*
*/
#ifndef MyTransport_h
#define MyTransport_h
#include "MySensorsCore.h"
// debug
#if defined(MY_DEBUG)
#define TRANSPORT_DEBUG(x,...) debug(x, ##__VA_ARGS__) //!< debug
#else
#define TRANSPORT_DEBUG(x,...) //!< debug NULL
#endif
#if defined(MY_REPEATER_FEATURE)
#define MY_TRANSPORT_MAX_TX_FAILURES (10u) //!< search for a new parent node after this many transmission failures, higher threshold for repeating nodes
#else
#define MY_TRANSPORT_MAX_TX_FAILURES (5u) //!< search for a new parent node after this many transmission failures, lower threshold for non-repeating nodes
#endif
#define MY_TRANSPORT_MAX_TSM_FAILURES (7u) //!< Max. number of consecutive TSM failure state entries (3bits)
#ifndef MY_TRANSPORT_TIMEOUT_FAILURE_STATE
#define MY_TRANSPORT_TIMEOUT_FAILURE_STATE (10*1000ul) //!< Duration failure state (in ms)
#endif
#ifndef MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE
#define MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE (60*1000ul) //!< Duration extended failure state (in ms)
#endif
#ifndef MY_TRANSPORT_STATE_TIMEOUT_MS
#define MY_TRANSPORT_STATE_TIMEOUT_MS (2*1000ul) //!< general state timeout (in ms)
#endif
#ifndef MY_TRANSPORT_CHKUPL_INTERVAL_MS
#define MY_TRANSPORT_CHKUPL_INTERVAL_MS (10*1000ul) //!< Interval to re-check uplink (in ms)
#endif
#ifndef MY_TRANSPORT_STATE_RETRIES
#define MY_TRANSPORT_STATE_RETRIES (3u) //!< retries before switching to FAILURE
#endif
#define AUTO (255u) //!< ID 255 is reserved
#define BROADCAST_ADDRESS (255u) //!< broadcasts are addressed to ID 255
#define DISTANCE_INVALID (255u) //!< invalid distance when searching for parent
#define MAX_HOPS (254u) //!< maximal mumber of hops for ping/pong
#define INVALID_HOPS (255u) //!< invalid hops
#define MAX_SUBSEQ_MSGS (5u) //!< Maximum number of subsequentially processed messages in FIFO (to prevent transport deadlock if HW issue)
// parent node check
#if defined(MY_PARENT_NODE_IS_STATIC) && !defined(MY_PARENT_NODE_ID)
#error MY_PARENT_NODE_IS_STATIC but no MY_PARENT_NODE_ID defined!
#endif
#define _autoFindParent (bool)(MY_PARENT_NODE_ID == AUTO) //!< returns true if static parent id is undefined
#define isValidDistance(distance) (bool)(distance!=DISTANCE_INVALID) //!< returns true if distance is valid
#define isValidParent(parent) (bool)(parent != AUTO) //!< returns true if parent is valid
// RX queue
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
#if defined(MY_RADIO_RFM69)
#error Receive message buffering not supported for RFM69!
#endif
#if defined(MY_RS485)
#error Receive message buffering not supported for RS485!
#endif
#elif !defined(MY_RX_MESSAGE_BUFFER_FEATURE) && defined(MY_RX_MESSAGE_BUFFER_SIZE)
#error Receive message buffering requires message buffering feature enabled!
#endif
/**
* @brief SM state
*
* This structure stores SM state definitions
*/
struct transportState {
void(*Transition)(); //!< state transition function
void(*Update)(); //!< state update function
};
/**
* @brief Status variables and SM state
*
* This structure stores transport status and SM variables
*/
typedef struct {
// SM variables
transportState* currentState; //!< pointer to current fsm state
uint32_t stateEnter; //!< state enter timepoint
// general transport variables
uint32_t lastUplinkCheck; //!< last uplink check, required to prevent GW flooding
// 8 bits
bool findingParentNode : 1; //!< flag finding parent node is active
bool preferredParentFound : 1; //!< flag preferred parent found
bool uplinkOk : 1; //!< flag uplink ok
bool pingActive : 1; //!< flag ping active
bool transportActive : 1; //!< flag transport active
uint8_t stateRetries : 3; //!< retries / state re-enter (max 7)
// 8 bits
uint8_t failedUplinkTransmissions : 4; //!< counter failed uplink transmissions (max 15)
uint8_t failureCounter : 3; //!< counter for TSM failures (max 7)
bool MsgReceived : 1; //!< flag message received
// 8 bits
uint8_t pingResponse; //!< stores I_PONG hops
} transportSM;
/**
* @brief RAM routing table
*/
typedef struct {
uint8_t route[SIZE_ROUTES]; //!< route for node
} routingTable;
// PRIVATE functions
/**
* @brief Initialise SM variables and transport HW
*/
void stInitTransition(void);
/**
* @brief Initialise transport
*/
void stInitUpdate(void);
/**
* @brief Find parent
*/
void stParentTransition(void);
/**
* @brief Verify find parent responses
*/
void stParentUpdate(void);
/**
* @brief Send ID request
*/
void stIDTransition(void);
/**
* @brief Verify ID response and GW link
*/
void stIDUpdate(void);
/**
* @brief Send uplink ping request
*/
void stUplinkTransition(void);
/**
* @brief Verify uplink response
*/
void stUplinkUpdate(void);
/**
* @brief Set transport OK
*/
void stReadyTransition(void);
/**
* @brief Monitor transport link
*/
void stReadyUpdate(void);
/**
* @brief Transport failure and power down radio
*/
void stFailureTransition(void);
/**
* @brief Re-initialize transport after timeout
*/
void stFailureUpdate(void);
/**
* @brief Switch SM state
* @param newState New state to switch SM to
*/
void transportSwitchSM(transportState& newState);
/**
* @brief Update SM state
*/
void transportUpdateSM(void);
/**
* @brief Request time in current SM state
* @return ms in current state
*/
uint32_t transportTimeInState(void);
/**
* @brief Call transport driver sanity check
*/
void transportInvokeSanityCheck(void);
/**
* @brief Process all pending messages in RX FIFO
*/
void transportProcessFIFO(void);
/**
* @brief Receive message from RX FIFO and process
*/
void transportProcessMessage(void);
/**
* @brief Assign node ID
* @param newNodeId New node ID
* @return true if node ID valid and successfully assigned
*/
bool transportAssignNodeID(const uint8_t newNodeId);
/**
* @brief Wait and process messages for a defined amount of time until specified message received
* @param ms Time to wait and process incoming messages in ms
* @param cmd Specific command
* @param msgtype Specific message type
* @return true if specified command received within waiting time
*/
bool transportWait(const uint32_t ms, const uint8_t cmd, const uint8_t msgtype);
/**
* @brief Ping node
* @param targetId Node to be pinged
* @return hops from pinged node or 255 if no answer received within 2000ms
*/
uint8_t transportPingNode(const uint8_t targetId);
/**
* @brief Send and route message according to destination
*
* This function is used in MyTransport and omits the transport state check, i.e. message can be sent even if transport is not ready
*
* @param message
* @return true if message sent successfully
*/
bool transportRouteMessage(MyMessage &message);
/**
* @brief Send and route message according to destination with transport state check
* @param message
* @return true if message sent successfully and false if sending error or transport !OK
*/
bool transportSendRoute(MyMessage &message);
/**
* @brief Send message to recipient
* @param to Recipient of message
* @param message
* @return true if message sent successfully
*/
bool transportSendWrite(const uint8_t to, MyMessage &message);
/**
* @brief Check uplink to GW, includes flooding control
* @param force to override flood control timer
* @return true if uplink ok
*/
bool transportCheckUplink(const bool force=false);
// PUBLIC functions
/**
* @brief Initialize transport and SM
*/
void transportInitialize(void);
/**
* @brief Process FIFO msg and update SM
*/
void transportProcess(void);
/**
* @brief Flag transport ready
* @return true if transport is initialize and ready
*/
bool isTransportReady(void);
/**
* @brief Flag searching parent ongoing
* @return true if transport is searching for parent
*/
bool isTransportSearchingParent(void);
/**
* @brief Flag TSM extended failure
* @return true if TSM had too many consecutive failure state entries
*/
bool isTransportExtendedFailure(void);
/**
* @brief Flag valid message received
* @return true if valid message received, needs to be reset if used
*/
bool isMessageReceived(void);
/**
* @brief Reset message received flag
*/
void resetMessageReceived(void);
/**
* @brief Clear routing table
*/
void transportClearRoutingTable(void);
/**
* @brief Return heart beat
* @return MS in current state
*/
uint32_t transportGetHeartbeat(void);
/**
* @brief Load routing table from EEPROM to RAM.
* Only for GW devices with enough RAM, i.e. ESP8266, RPI Sensebender GW, etc.
* Atmega328 has only limited amount of RAM
*/
void transportLoadRoutingTable(void);
/**
* @brief Save routing table to EEPROM.
*/
void transportSaveRoutingTable(void);
/**
* @brief Update routing table
* @param node
* @param route
*/
void transportSetRoute(const uint8_t node, const uint8_t route);
/**
* @brief Load route to node
* @param node
* @return route to node
*/
uint8_t transportGetRoute(const uint8_t node);
// interface functions for radio driver
/**
* @brief Initialize transport HW
* @return true if initialization successful
*/
bool transportInit();
/**
* @brief Set node address
*/
void transportSetAddress(uint8_t address);
/**
* @brief Retrieve node address
*/
uint8_t transportGetAddress();
/**
* @brief Send message
* @param to recipient
* @param data message to be sent
* @param len length of message (header + payload)
* @return true if message sent successfully
*/
bool transportSend(uint8_t to, const void* data, uint8_t len);
/**
* @brief Verify if RX FIFO has pending messages
* @return true if message available in RX FIFO
*/
bool transportAvailable();
/**
* @brief Sanity check for transport: is transport still responsive?
* @return true transport ok
*/
bool transportSanityCheck();
/**
* @brief Receive message from FIFO
* @return length of recevied message (header + payload)
*/
uint8_t transportReceive(void* data);
/**
* @brief Power down transport HW
*/
void transportPowerDown();
#endif // MyTransport_h
/** @}*/
+149
View File
@@ -0,0 +1,149 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyTransport.h"
#include "drivers/RF24/RF24.h"
#include "drivers/CircularBuffer/CircularBuffer.h"
#if defined(MY_RF24_ENABLE_ENCRYPTION)
#include "drivers/AES/AES.h"
#endif
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
typedef struct _transportQueuedMessage
{
uint8_t m_len; // Length of the data
uint8_t m_data[MAX_MESSAGE_LENGTH]; // The raw data
} transportQueuedMessage;
/** Buffer to store queued messages in. */
static transportQueuedMessage transportRxQueueStorage[MY_RX_MESSAGE_BUFFER_SIZE];
/** Circular buffer, which uses the transportRxQueueStorage and administers stored messages. */
static CircularBuffer<transportQueuedMessage> transportRxQueue(transportRxQueueStorage, MY_RX_MESSAGE_BUFFER_SIZE);
static volatile uint8_t transportLostMessageCount = 0;
static void transportRxCallback(void)
{
// Called for each message received by radio, from interrupt context.
// This function _must_ call RF24_readMessage() to de-assert interrupt line!
if (!transportRxQueue.full())
{
transportQueuedMessage* msg = transportRxQueue.getFront();
msg->m_len = RF24_readMessage(msg->m_data); // Read payload & clear RX_DR
(void)transportRxQueue.pushFront(msg);
} else {
// Queue is full. Discard message.
(void)RF24_readMessage(NULL); // Read payload & clear RX_DR
// Keep track of messages lost. Max 255, prevent wrapping.
if (transportLostMessageCount < 255)
{
++transportLostMessageCount;
}
}
}
#endif
#if defined(MY_RF24_ENABLE_ENCRYPTION)
AES _aes;
uint8_t _dataenc[32] = {0};
uint8_t _psk[16];
#endif
bool transportInit() {
#if defined(MY_RF24_ENABLE_ENCRYPTION)
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
//set up AES-key
_aes.set_key(_psk, 16);
// Make sure it is purged from memory when set
memset(_psk, 0, 16);
#endif
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
RF24_registerReceiveCallback( transportRxCallback );
#endif
return RF24_initialize();
}
void transportSetAddress(uint8_t address) {
RF24_setNodeAddress(address);
RF24_startListening();
}
uint8_t transportGetAddress() {
return RF24_getNodeID();
}
bool transportSend(uint8_t recipient, const void* data, uint8_t len) {
#if defined(MY_RF24_ENABLE_ENCRYPTION)
// copy input data because it is read-only
memcpy(_dataenc,data,len);
// has to be adjusted, WIP!
_aes.set_IV(0);
len = len > 16 ? 32 : 16;
//encrypt data
_aes.cbc_encrypt(_dataenc, _dataenc, len/16);
bool status = RF24_sendMessage( recipient, _dataenc, len );
#else
bool status = RF24_sendMessage( recipient, data, len );
#endif
return status;
}
bool transportAvailable() {
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
(void)RF24_isDataAvailable; // Prevent 'defined but not used' warning
return !transportRxQueue.empty();
#else
return RF24_isDataAvailable();
#endif
}
bool transportSanityCheck() {
return RF24_sanityCheck();
}
uint8_t transportReceive(void* data) {
uint8_t len = 0;
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
transportQueuedMessage* msg = transportRxQueue.getBack();
if (msg) {
len = msg->m_len;
(void)memcpy(data, msg->m_data, len);
(void)transportRxQueue.popBack();
}
#else
len = RF24_readMessage(data);
#endif
#if defined(MY_RF24_ENABLE_ENCRYPTION)
// has to be adjusted, WIP!
_aes.set_IV(0);
// decrypt data
_aes.cbc_decrypt((byte*)(data), (byte*)(data), len>16?2:1);
#endif
return len;
}
void transportPowerDown() {
RF24_powerDown();
}
+78
View File
@@ -0,0 +1,78 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyConfig.h"
#include "MyTransport.h"
#include <stdint.h>
#include "drivers/RFM69/RFM69.h"
RFM69 _radio(MY_RF69_SPI_CS, MY_RF69_IRQ_PIN, MY_RFM69HW, MY_RF69_IRQ_NUM);
uint8_t _address;
bool transportInit() {
// Start up the radio library (_address will be set later by the MySensors library)
if (_radio.initialize(MY_RFM69_FREQUENCY, _address, MY_RFM69_NETWORKID)) {
#ifdef MY_RFM69_ENABLE_ENCRYPTION
uint8_t _psk[16];
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
_radio.encrypt((const char*)_psk);
memset(_psk, 0, 16); // Make sure it is purged from memory when set
#endif
return true;
}
return false;
}
void transportSetAddress(uint8_t address) {
_address = address;
_radio.setAddress(address);
}
uint8_t transportGetAddress() {
return _address;
}
bool transportSend(uint8_t to, const void* data, uint8_t len) {
return _radio.sendWithRetry(to,data,len);
}
bool transportAvailable() {
return _radio.receiveDone();
}
bool transportSanityCheck() {
// not implemented yet
return true;
}
uint8_t transportReceive(void* data) {
memcpy(data,(const void *)_radio.DATA, _radio.DATALEN);
// save payload length
const uint8_t dataLen = _radio.DATALEN;
// Send ack back if this message wasn't a broadcast
if (_radio.TARGETID != RF69_BROADCAST_ADDR)
_radio.ACKRequested();
_radio.sendACK();
return dataLen;
}
void transportPowerDown() {
_radio.sleep();
}
+355
View File
@@ -0,0 +1,355 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* -------------------------------------------------------------------------------
*
* Copyright (c) 2013, Majenko Technologies and S.J.Hoeksma
* Copyright (c) 2015, LeoDesigner
* https://github.com/leodesigner/mysensors-serial-transport
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of Majenko Technologies.
********************************************************************************/
// transport(Serial,0,-1); // serial port, node, dePin (-1 disabled)
// Serial Transport
#include "MyConfig.h"
#include "MyTransport.h"
#include <stdint.h>
#include <Arduino.h>
#include "MyTransport.h"
#if defined(MY_RS485_DE_PIN)
#define assertDE() digitalWrite(MY_RS485_DE_PIN, HIGH); delayMicroseconds(5)
#define deassertDE() digitalWrite(MY_RS485_DE_PIN, LOW)
#else
#define assertDE()
#define deassertDE()
#endif
// We only use SYS_PACK in this application
#define ICSC_SYS_PACK 0x58
// Receiving header information
char _header[6];
// Reception state machine control and storage variables
unsigned char _recPhase;
unsigned char _recPos;
unsigned char _recCommand;
unsigned char _recLen;
unsigned char _recStation;
unsigned char _recSender;
unsigned char _recCS;
unsigned char _recCalcCS;
AltSoftSerial _dev;
unsigned char _nodeId;
char _data[MY_RS485_MAX_MESSAGE_LENGTH];
uint8_t _packet_len;
unsigned char _packet_from;
bool _packet_received;
// Packet wrapping characters, defined in standard ASCII table
#define SOH 1
#define STX 2
#define ETX 3
#define EOT 4
//Reset the state machine and release the data pointer
void _serialReset(){
_recPhase = 0;
_recPos = 0;
_recLen = 0;
_recCommand = 0;
_recCS = 0;
_recCalcCS = 0;
}
// This is the main reception state machine. Progress through the states
// is keyed on either special control characters, or counted number of bytes
// received. If all the data is in the right format, and the calculated
// checksum matches the received checksum, AND the destination station is
// our station ID, then look for a registered command that matches the
// command code. If all the above is true, execute the command's
// function.
bool _serialProcess()
{
char inch;
unsigned char i;
if (!_dev.available()) return false;
while(_dev.available()) {
inch = _dev.read();
switch(_recPhase) {
// Case 0 looks for the header. Bytes arrive in the serial interface and get
// shifted through a header buffer. When the start and end characters in
// the buffer match the SOH/STX pair, and the destination station ID matches
// our ID, save the header information and progress to the next state.
case 0:
memcpy(&_header[0],&_header[1],5);
_header[5] = inch;
if ((_header[0] == SOH) && (_header[5] == STX) && (_header[1] != _header[2])) {
_recCalcCS = 0;
_recStation = _header[1];
_recSender = _header[2];
_recCommand = _header[3];
_recLen = _header[4];
for (i=1; i<=4; i++) {
_recCalcCS += _header[i];
}
_recPhase = 1;
_recPos = 0;
//Check if we should process this message
//We reject the message if we are the sender
//We reject if we are not the receiver and message is not a broadcast
if ((_recSender == _nodeId) ||
(_recStation != _nodeId &&
_recStation != BROADCAST_ADDRESS)) {
_dev.print(" wrongid: ");
_dev.print(_recStation);
_dev.print(" - ");
_dev.println(_nodeId);
_serialReset();
break;
}
if (_recLen == 0) {
_recPhase = 2;
}
}
break;
// Case 1 receives the data portion of the packet. Read in "_recLen" number
// of bytes and store them in the _data array.
case 1:
_data[_recPos++] = inch;
_recCalcCS += inch;
if (_recPos == _recLen) {
_recPhase = 2;
}
break;
// After the data comes a single ETX character. Do we have it? If not,
// reset the state machine to default and start looking for a new header.
case 2:
// Packet properly terminated?
if (inch == ETX) {
_recPhase = 3;
} else {
_serialReset();
}
break;
// Next comes the checksum. We have already calculated it from the incoming
// data, so just store the incoming checksum byte for later.
case 3:
_recCS = inch;
_recPhase = 4;
break;
// The final state - check the last character is EOT and that the checksum matches.
// If that test passes, then look for a valid command callback to execute.
// Execute it if found.
case 4:
if (inch == EOT) {
if (_recCS == _recCalcCS) {
// First, check for system level commands. It is possible
// to register your own callback as well for system level
// commands which will be called after the system default
// hook.
switch (_recCommand) {
case ICSC_SYS_PACK:
_packet_from = _recSender;
_packet_len = _recLen;
_packet_received = true;
break;
}
}
}
//Clear the data
_serialReset();
//Return true, we have processed one command
return true;
break;
}
}
return true;
}
bool transportSend(uint8_t to, const void* data, uint8_t len)
{
const char *datap = static_cast<char const *>(data);
unsigned char i;
unsigned char cs = 0;
unsigned char del;
// This is how many times to try and transmit before failing.
unsigned char timeout = 10;
// Let's start out by looking for a collision. If there has been anything seen in
// the last millisecond, then wait for a random time and check again.
while (_serialProcess()) {
del = rand() % 20;
for (i = 0; i < del; i++) {
delay(1);
_serialProcess();
}
timeout--;
if (timeout == 0) {
// Failed to transmit!!!
return false;
}
}
#if defined(MY_RS485_DE_PIN)
digitalWrite(MY_RS485_DE_PIN, HIGH);
delayMicroseconds(5);
#endif
// Start of header by writing multiple SOH
for(byte w=0;w<1;w++) _dev.write(SOH);
_dev.write(to); // Destination address
cs += to;
_dev.write(_nodeId); // Source address
cs += _nodeId;
_dev.write(ICSC_SYS_PACK); // Command code
cs += ICSC_SYS_PACK;
_dev.write(len); // Length of text
cs += len;
_dev.write(STX); // Start of text
for(i=0; i<len; i++) {
_dev.write(datap[i]); // Text bytes
cs += datap[i];
}
_dev.write(ETX); // End of text
_dev.write(cs);
_dev.write(EOT);
#if defined(MY_RS485_DE_PIN)
#ifdef __PIC32MX__
// MPIDE has nothing yet for this. It uses the hardware buffer, which
// could be up to 8 levels deep. For now, let's just delay for 8
// characters worth.
delayMicroseconds((F_CPU/9600)+1);
#else
#if defined(ARDUINO) && ARDUINO >= 100
#if ARDUINO >= 104
// Arduino 1.0.4 and upwards does it right
_dev.flush();
#else
// Between 1.0.0 and 1.0.3 it almost does it - need to compensate
// for the hardware buffer. Delay for 2 bytes worth of transmission.
_dev.flush();
delayMicroseconds((20000000UL/9600)+1);
#endif
#endif
#endif
digitalWrite(MY_RS485_DE_PIN, LOW);
#endif
return true;
}
bool transportInit() {
// Reset the state machine
_dev.begin(MY_RS485_BAUD_RATE);
_serialReset();
#if defined(MY_RS485_DE_PIN)
pinMode(MY_RS485_DE_PIN, OUTPUT);
digitalWrite(MY_RS485_DE_PIN, LOW);
#endif
return true;
}
void transportSetAddress(uint8_t address) {
_nodeId = address;
}
uint8_t transportGetAddress() {
return _nodeId;
}
bool transportAvailable() {
_serialProcess();
return _packet_received;
}
bool transportSanityCheck() {
// not implemented yet
return true;
}
uint8_t transportReceive(void* data) {
if (_packet_received) {
memcpy(data,_data,_packet_len);
_packet_received = false;
return _packet_len;
}
else {
return (0);
}
}
void transportPowerDown() {
// Nothing to shut down here
}
+11
View File
@@ -0,0 +1,11 @@
/***
* This file defines the Sensor library version number
* Normally, contributors should not modify this directly
* as it is managed by the MySensors Bot.
*/
#ifndef Version_h
#define Version_h
#define MYSENSORS_LIBRARY_VERSION "2.0.1-beta"
#endif
+598
View File
@@ -0,0 +1,598 @@
#include "AES.h"
/*
---------------------------------------------------------------------------
Copyright (c) 1998-2008, Brian Gladman, Worcester, UK. All rights reserved.
LICENSE TERMS
The redistribution and use of this software (with or without changes)
is allowed without the payment of fees or royalties provided that:
1. source code distributions include the above copyright notice, this
list of conditions and the following disclaimer;
2. binary distributions include the above copyright notice, this list
of conditions and the following disclaimer in their documentation;
3. the name of the copyright holder is not used to endorse products
built using this software without specific written permission.
DISCLAIMER
This software is provided 'as is' with no explicit or implied warranties
in respect of its properties, including, but not limited to, correctness
and/or fitness for purpose.
---------------------------------------------------------------------------
Issue 09/09/2006
This is an AES implementation that uses only 8-bit byte operations on the
cipher state (there are options to use 32-bit types if available).
The combination of mix columns and byte substitution used here is based on
that developed by Karl Malbrain. His contribution is acknowledged.
*/
/* This version derived by Mark Tillotson 2012-01-23, tidied up, slimmed down
and tailored to 8-bit microcontroller abilities and Arduino datatypes.
The s-box and inverse s-box were retained as tables (0.5kB PROGMEM) but all
the other transformations are coded to save table space. Many efficiency
improvments to the routines mix_sub_columns() and inv_mix_sub_columns()
(mainly common sub-expression elimination).
Only the routines with precalculated subkey schedule are retained (together
with set_key() - this does however mean each AES object takes 240 bytes of
RAM, alas)
The CBC routines side-effect the iv argument (so that successive calls work
together correctly).
All the encryption and decryption routines work with plain == cipher for
in-place encryption, note.
*/
/* functions for finite field multiplication in the AES Galois field */
/* code was modified by george spanos <spaniakos@gmail.com>
* 16/12/14
*/
// GF(2^8) stuff
#define WPOLY 0x011B
#define DPOLY 0x008D
const static byte s_fwd [0x100] PROGMEM =
{
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,
} ;
const static byte s_inv [0x100] PROGMEM =
{
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
} ;
// times 2 in the GF(2^8)
#define f2(x) ((x) & 0x80 ? (x << 1) ^ WPOLY : x << 1)
#define d2(x) (((x) >> 1) ^ ((x) & 1 ? DPOLY : 0))
static byte s_box (byte x)
{
// return fwd_affine (pgm_read_byte (&inv [x])) ;
return pgm_read_byte (& s_fwd [x]) ;
}
// Inverse Sbox
static byte is_box (byte x)
{
// return pgm_read_byte (&inv [inv_affine (x)]) ;
return pgm_read_byte (& s_inv [x]) ;
}
static void xor_block (byte * d, byte * s)
{
for (byte i = 0 ; i < N_BLOCK ; i += 4)
{
*d++ ^= *s++ ; // some unrolling
*d++ ^= *s++ ;
*d++ ^= *s++ ;
*d++ ^= *s++ ;
}
}
static void copy_and_key (byte * d, byte * s, byte * k)
{
for (byte i = 0 ; i < N_BLOCK ; i += 4)
{
*d++ = *s++ ^ *k++ ; // some unrolling
*d++ = *s++ ^ *k++ ;
*d++ = *s++ ^ *k++ ;
*d++ = *s++ ^ *k++ ;
}
}
// #define add_round_key(d, k) xor_block (d, k)
/* SUB ROW PHASE */
static void shift_sub_rows (byte st [N_BLOCK])
{
st [0] = s_box (st [0]) ; st [4] = s_box (st [4]) ;
st [8] = s_box (st [8]) ; st [12] = s_box (st [12]) ;
byte tt = st [1] ;
st [1] = s_box (st [5]) ; st [5] = s_box (st [9]) ;
st [9] = s_box (st [13]) ; st [13] = s_box (tt) ;
tt = st[2] ; st [2] = s_box (st [10]) ; st [10] = s_box (tt) ;
tt = st[6] ; st [6] = s_box (st [14]) ; st [14] = s_box (tt) ;
tt = st[15] ;
st [15] = s_box (st [11]) ; st [11] = s_box (st [7]) ;
st [7] = s_box (st [3]) ; st [3] = s_box (tt) ;
}
static void inv_shift_sub_rows (byte st[N_BLOCK])
{
st [0] = is_box (st[0]) ; st [4] = is_box (st [4]);
st [8] = is_box (st[8]) ; st [12] = is_box (st [12]);
byte tt = st[13] ;
st [13] = is_box (st [9]) ; st [9] = is_box (st [5]) ;
st [5] = is_box (st [1]) ; st [1] = is_box (tt) ;
tt = st [2] ; st [2] = is_box (st [10]) ; st [10] = is_box (tt) ;
tt = st [6] ; st [6] = is_box (st [14]) ; st [14] = is_box (tt) ;
tt = st [3] ;
st [3] = is_box (st [7]) ; st [7] = is_box (st [11]) ;
st [11] = is_box (st [15]) ; st [15] = is_box (tt) ;
}
/* SUB COLUMNS PHASE */
static void mix_sub_columns (byte dt[N_BLOCK], byte st[N_BLOCK])
{
byte j = 5 ;
byte k = 10 ;
byte l = 15 ;
for (byte i = 0 ; i < N_BLOCK ; i += N_COL)
{
byte a = st [i] ;
byte b = st [j] ; j = (j+N_COL) & 15 ;
byte c = st [k] ; k = (k+N_COL) & 15 ;
byte d = st [l] ; l = (l+N_COL) & 15 ;
byte a1 = s_box (a), b1 = s_box (b), c1 = s_box (c), d1 = s_box (d) ;
byte a2 = f2(a1), b2 = f2(b1), c2 = f2(c1), d2 = f2(d1) ;
dt[i] = a2 ^ b2^b1 ^ c1 ^ d1 ;
dt[i+1] = a1 ^ b2 ^ c2^c1 ^ d1 ;
dt[i+2] = a1 ^ b1 ^ c2 ^ d2^d1 ;
dt[i+3] = a2^a1 ^ b1 ^ c1 ^ d2 ;
}
}
static void inv_mix_sub_columns (byte dt[N_BLOCK], byte st[N_BLOCK])
{
for (byte i = 0 ; i < N_BLOCK ; i += N_COL)
{
byte a1 = st [i] ;
byte b1 = st [i+1] ;
byte c1 = st [i+2] ;
byte d1 = st [i+3] ;
byte a2 = f2(a1), b2 = f2(b1), c2 = f2(c1), d2 = f2(d1) ;
byte a4 = f2(a2), b4 = f2(b2), c4 = f2(c2), d4 = f2(d2) ;
byte a8 = f2(a4), b8 = f2(b4), c8 = f2(c4), d8 = f2(d4) ;
byte a9 = a8 ^ a1,b9 = b8 ^ b1,c9 = c8 ^ c1,d9 = d8 ^ d1 ;
byte ac = a8 ^ a4,bc = b8 ^ b4,cc = c8 ^ c4,dc = d8 ^ d4 ;
dt[i] = is_box (ac^a2 ^ b9^b2 ^ cc^c1 ^ d9) ;
dt[(i+5)&15] = is_box (a9 ^ bc^b2 ^ c9^c2 ^ dc^d1) ;
dt[(i+10)&15] = is_box (ac^a1 ^ b9 ^ cc^c2 ^ d9^d2) ;
dt[(i+15)&15] = is_box (a9^a2 ^ bc^b1 ^ c9 ^ dc^d2) ;
}
}
/******************************************************************************/
AES::AES(){
byte ar_iv[8] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 };
memcpy(iv,ar_iv,8);
memcpy(iv+8,ar_iv,8);
arr_pad[0] = 0x01;
arr_pad[1] = 0x02;
arr_pad[2] = 0x03;
arr_pad[3] = 0x04;
arr_pad[4] = 0x05;
arr_pad[5] = 0x06;
arr_pad[6] = 0x07;
arr_pad[7] = 0x08;
arr_pad[8] = 0x09;
arr_pad[9] = 0x0a;
arr_pad[10] = 0x0b;
arr_pad[11] = 0x0c;
arr_pad[12] = 0x0d;
arr_pad[13] = 0x0e;
arr_pad[14] = 0x0f;
}
/******************************************************************************/
byte AES::set_key (byte key [], int keylen)
{
byte hi ;
switch (keylen)
{
case 16:
case 128:
keylen = 16; // 10 rounds
round = 10 ;
break;
case 24:
case 192:
keylen = 24; // 12 rounds
round = 12 ;
break;
case 32:
case 256:
keylen = 32; // 14 rounds
round = 14 ;
break;
default:
round = 0;
return AES_FAILURE;
}
hi = (round + 1) << 4 ;
copy_n_bytes (key_sched, key, keylen) ;
byte t[4] ;
byte next = keylen ;
for (byte cc = keylen, rc = 1 ; cc < hi ; cc += N_COL)
{
for (byte i = 0 ; i < N_COL ; i++)
t[i] = key_sched [cc-4+i] ;
if (cc == next)
{
next += keylen ;
byte ttt = t[0] ;
t[0] = s_box (t[1]) ^ rc ;
t[1] = s_box (t[2]) ;
t[2] = s_box (t[3]) ;
t[3] = s_box (ttt) ;
rc = f2 (rc) ;
}
else if (keylen == 32 && (cc & 31) == 16)
{
for (byte i = 0 ; i < 4 ; i++)
t[i] = s_box (t[i]) ;
}
byte tt = cc - keylen ;
for (byte i = 0 ; i < N_COL ; i++)
key_sched [cc + i] = key_sched [tt + i] ^ t[i] ;
}
return AES_SUCCESS ;
}
/******************************************************************************/
void AES::clean ()
{
for (byte i = 0 ; i < KEY_SCHEDULE_BYTES ; i++)
key_sched [i] = 0 ;
round = 0 ;
}
/******************************************************************************/
void AES::copy_n_bytes (byte * d, byte * s, byte nn)
{
while (nn >= 4)
{
*d++ = *s++ ; // some unrolling
*d++ = *s++ ;
*d++ = *s++ ;
*d++ = *s++ ;
nn -= 4 ;
}
while (nn--)
*d++ = *s++ ;
}
/******************************************************************************/
byte AES::encrypt (byte plain [N_BLOCK], byte cipher [N_BLOCK])
{
if (round)
{
byte s1 [N_BLOCK], r ;
copy_and_key (s1, plain, (byte*) (key_sched)) ;
for (r = 1 ; r < round ; r++)
{
byte s2 [N_BLOCK] ;
mix_sub_columns (s2, s1) ;
copy_and_key (s1, s2, (byte*) (key_sched + r * N_BLOCK)) ;
}
shift_sub_rows (s1) ;
copy_and_key (cipher, s1, (byte*) (key_sched + r * N_BLOCK)) ;
}
else
return AES_FAILURE ;
return AES_SUCCESS ;
}
/******************************************************************************/
byte AES::cbc_encrypt (byte * plain, byte * cipher, int n_block, byte iv [N_BLOCK])
{
while (n_block--)
{
xor_block (iv, plain) ;
if (encrypt (iv, iv) != AES_SUCCESS)
return AES_FAILURE ;
copy_n_bytes (cipher, iv, N_BLOCK) ;
plain += N_BLOCK ;
cipher += N_BLOCK ;
}
return AES_SUCCESS ;
}
/******************************************************************************/
byte AES::cbc_encrypt (byte * plain, byte * cipher, int n_block)
{
while (n_block--)
{
xor_block (iv, plain) ;
if (encrypt (iv, iv) != AES_SUCCESS)
return AES_FAILURE ;
copy_n_bytes (cipher, iv, N_BLOCK) ;
plain += N_BLOCK ;
cipher += N_BLOCK ;
}
return AES_SUCCESS ;
}
/******************************************************************************/
byte AES::decrypt (byte plain [N_BLOCK], byte cipher [N_BLOCK])
{
if (round)
{
byte s1 [N_BLOCK] ;
copy_and_key (s1, plain, (byte*) (key_sched + round * N_BLOCK)) ;
inv_shift_sub_rows (s1) ;
for (byte r = round ; --r ; )
{
byte s2 [N_BLOCK] ;
copy_and_key (s2, s1, (byte*) (key_sched + r * N_BLOCK)) ;
inv_mix_sub_columns (s1, s2) ;
}
copy_and_key (cipher, s1, (byte*) (key_sched)) ;
}
else
return AES_FAILURE ;
return AES_SUCCESS ;
}
/******************************************************************************/
byte AES::cbc_decrypt (byte * cipher, byte * plain, int n_block, byte iv [N_BLOCK])
{
while (n_block--)
{
byte tmp [N_BLOCK] ;
copy_n_bytes (tmp, cipher, N_BLOCK) ;
if (decrypt (cipher, plain) != AES_SUCCESS)
return AES_FAILURE ;
xor_block (plain, iv) ;
copy_n_bytes (iv, tmp, N_BLOCK) ;
plain += N_BLOCK ;
cipher += N_BLOCK;
}
return AES_SUCCESS ;
}
/******************************************************************************/
byte AES::cbc_decrypt (byte * cipher, byte * plain, int n_block)
{
while (n_block--)
{
byte tmp [N_BLOCK] ;
copy_n_bytes (tmp, cipher, N_BLOCK) ;
if (decrypt (cipher, plain) != AES_SUCCESS)
return AES_FAILURE ;
xor_block (plain, iv) ;
copy_n_bytes (iv, tmp, N_BLOCK) ;
plain += N_BLOCK ;
cipher += N_BLOCK;
}
return AES_SUCCESS ;
}
/*****************************************************************************/
void AES::set_IV(unsigned long long int IVCl){
memcpy(iv,&IVCl,8);
memcpy(iv+8,&IVCl,8);
IVC = IVCl;
}
/******************************************************************************/
void AES::iv_inc(){
IVC += 1;
memcpy(iv,&IVC,8);
memcpy(iv+8,&IVC,8);
}
/******************************************************************************/
int AES::get_size(){
return size;
}
/******************************************************************************/
void AES::set_size(int sizel){
size = sizel;
}
/******************************************************************************/
void AES::get_IV(byte *out){
memcpy(out,&IVC,8);
memcpy(out+8,&IVC,8);
}
/******************************************************************************/
void AES::calc_size_n_pad(int p_size){
int s_of_p = p_size - 1;
if ( s_of_p % N_BLOCK == 0){
size = s_of_p;
}else{
size = s_of_p + (N_BLOCK-(s_of_p % N_BLOCK));
}
pad = size - s_of_p;
}
/******************************************************************************/
void AES::padPlaintext(void* in,byte* out)
{
memcpy(out,in,size);
for (int i = size-pad; i < size; i++){;
out[i] = arr_pad[pad - 1];
}
}
/******************************************************************************/
bool AES::CheckPad(byte* in,int lsize){
if (in[lsize-1] <= 0x0f){
int lpad = (int)in[lsize-1];
for (int i = lsize - 1; i >= lsize-lpad; i--){
if (arr_pad[lpad - 1] != in[i]){
return false;
}
}
}else{
return true;
}
return true;
}
/******************************************************************************/
void AES::printArray(byte output[],bool p_pad)
{
uint8_t i,j;
uint8_t loops = size/N_BLOCK;
uint8_t outp = N_BLOCK;
for (j = 0; j < loops; j += 1){
if (p_pad && (j == (loops - 1)) ) { outp = N_BLOCK - pad; }
for (i = 0; i < outp; i++)
{
printf_P(PSTR("%c"),output[j*N_BLOCK + i]);
}
}
printf_P(PSTR("\n"));
}
/******************************************************************************/
void AES::printArray(byte output[],int sizel)
{
for (int i = 0; i < sizel; i++)
{
printf_P(PSTR("%x"),output[i]);
}
printf_P(PSTR("\n"));
}
/******************************************************************************/
void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits, byte ivl [N_BLOCK]){
calc_size_n_pad(size_p);
byte plain_p[get_size()];
padPlaintext(plain,plain_p);
int blocks = get_size() / N_BLOCK;
set_key (key, bits) ;
cbc_encrypt (plain_p, cipher, blocks, ivl);
}
/******************************************************************************/
void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits){
calc_size_n_pad(size_p);
byte plain_p[get_size()];
padPlaintext(plain,plain_p);
int blocks = get_size() / N_BLOCK;
set_key (key, bits) ;
cbc_encrypt (plain_p, cipher, blocks);
}
/******************************************************************************/
void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits, byte ivl [N_BLOCK]){
set_size(size_c);
int blocks = size_c / N_BLOCK;
set_key (key, bits);
cbc_decrypt (cipher,plain, blocks, ivl);
}
/******************************************************************************/
void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits){
set_size(size_c);
int blocks = size_c / N_BLOCK;
set_key (key, bits);
cbc_decrypt (cipher,plain, blocks);
}
/******************************************************************************/
#if defined(AES_LINUX)
double AES::millis(){
gettimeofday(&tv, NULL);
return (tv.tv_sec + 0.000001 * tv.tv_usec);
}
#endif
+416
View File
@@ -0,0 +1,416 @@
#ifndef __AES_H__
#define __AES_H__
#include "AES_config.h"
/*
---------------------------------------------------------------------------
Copyright (c) 1998-2008, Brian Gladman, Worcester, UK. All rights reserved.
LICENSE TERMS
The redistribution and use of this software (with or without changes)
is allowed without the payment of fees or royalties provided that:
1. source code distributions include the above copyright notice, this
list of conditions and the following disclaimer;
2. binary distributions include the above copyright notice, this list
of conditions and the following disclaimer in their documentation;
3. the name of the copyright holder is not used to endorse products
built using this software without specific written permission.
DISCLAIMER
This software is provided 'as is' with no explicit or implied warranties
in respect of its properties, including, but not limited to, correctness
and/or fitness for purpose.
---------------------------------------------------------------------------
Issue 09/09/2006
This is an AES implementation that uses only 8-bit byte operations on the
cipher state.
*/
/* code was modified by george spanos <spaniakos@gmail.com>
* 16/12/14
*/
/** AES class */
class AES
{
public:
/* The following calls are for a precomputed key schedule
NOTE: If the length_type used for the key length is an
unsigned 8-bit character, a key length of 256 bits must
be entered as a length in bytes (valid inputs are hence
128, 192, 16, 24 and 32).
*/
/** \fn AES()
* \brief AES constructor
*
* This function initialized an instance of AES.
*/
AES();
/** Set the cipher key for the pre-keyed version.
* @param key[] pointer to the key string.
* @param keylen Integer that indicates the length of the key.
* @note NOTE: If the length_type used for the key length is an unsigned 8-bit character,
* a key length of 256 bits must be entered as a length in bytes
* (valid inputs are hence 128, 192, 16, 24 and 32).
*
*/
byte set_key (byte key[], int keylen) ;
/** clean up subkeys after use.
*
*/
void clean () ; // delete key schedule after use
/** copying and xoring utilities.
* @param *AESt byte pointer of the AEStination array.
* @param *src byte pointer of the source array.
* @param n byte, indicating the sizeof the bytes to be copied.
* @note this is an alternative for memcpy(void *s1,const void *s2, site_t n),
* i have not updated the function in the implementation yet, but it is considered a future plan.
*
*/
void copy_n_bytes (byte * AESt, byte * src, byte n) ;
/** Encrypt a single block of 16 bytes .
* @param plain Array of the plaintext.
* @param cipher Array of the ciphertext.
* @note The N_BLOCK is defined in AES_config.h as,
* @code #define N_ROW 4
* #define N_COL 4
* #define N_BLOCK (N_ROW * N_COL)
* @endcode
* Changed to that will change the Block_size.
* @return 0 if SUCCESS or -1 if FAILURE
*
*/
byte encrypt (byte plain [N_BLOCK], byte cipher [N_BLOCK]) ;
/** CBC encrypt a number of blocks (input and return an IV).
*
* @param *plain Pointer, points to the plaintex.
* @param *cipher Pointer, points to the ciphertext that will be created.
* @param n_block integer, indicated the number of blocks to be ciphered.
* @param iv byte Array that holds the IV (initialization vector).
* @return 0 if SUCCESS or -1 if FAILURE
*
*/
byte cbc_encrypt (byte * plain, byte * cipher, int n_block, byte iv [N_BLOCK]) ;
/** CBC encrypt a number of blocks (input and return an IV).
*
* @param *plain Pointer, points to the plaintex.
* @param *cipher Pointer, points to the ciphertext that will be created.
* @param n_block integer, indicated the number of blocks to be ciphered.
* @return 0 if SUCCESS or -1 if FAILURE
*
*/
byte cbc_encrypt (byte * plain, byte * cipher, int n_block) ;
/** Decrypt a single block of 16 bytes
* @param cipher Array of the ciphertext.
* @param plain Array of the plaintext.
* @note The N_BLOCK is defined in AES_config.h as,
* @code #define N_ROW 4
* #define N_COL 4
* #define N_BLOCK (N_ROW * N_COL)
* @endcode
* Changed to that will change the Block_size.
* @return 0 if SUCCESS or -1 if FAILURE
*
*/
byte decrypt (byte cipher [N_BLOCK], byte plain [N_BLOCK]) ;
/** CBC decrypt a number of blocks (input and return an IV)
*
* @param *cipher Pointer, points to the ciphertext that will be created.
* @param *plain Pointer, points to the plaintex.
* @param n_block integer, indicated the number of blocks to be ciphered.
* @param iv byte Array that holds the IV (initialization vector).
* @return 0 if SUCCESS or -1 if FAILURE
*
*/
byte cbc_decrypt (byte * cipher, byte * plain, int n_block, byte iv [N_BLOCK]) ;
/** CBC decrypt a number of blocks (input and return an IV)
*
* @param *cipher Pointer, points to the ciphertext that will be created.
* @param *plain Pointer, points to the plaintex.
* @param n_block integer, indicated the number of blocks to be ciphered.
* @return 0 if SUCCESS or -1 if FAILURE
*
*/
byte cbc_decrypt (byte * cipher, byte * plain, int n_block) ;
/** Sets IV (initialization vector) and IVC (IV counter).
* This function changes the ivc and iv variables needed for AES.
*
* @param IVCl int or hex value of iv , ex. 0x0000000000000001
* @note example:
* @code unsigned long long int my_iv = 01234567; @endcode
*/
void set_IV(unsigned long long int IVCl);
/** increase the iv (initialization vector) and IVC (IV counter) by 1
*
* This function increased the VI by one step in order to have a different IV each time
*
*/
void iv_inc();
/** Getter method for size
*
* This function return the size
* @return an integer, that is the size of the of the padded plaintext,
* thus, the size of the ciphertext.
*/
int get_size();
/** Setter method for size
*
* This function sets the size of the plaintext+pad
*
*/
void set_size(int sizel);
/** Getter method for IV
*
* This function return the IV
* @param out byte pointer that gets the IV.
* @return none, the IV is writed to the out pointer.
*/
void get_IV(byte *out);
/** Calculates the size of the plaintext and the padding.
*
* Calculates the size of theplaintext with the padding
* and the size of the padding needed. Moreover it stores them in their class variables.
*
* @param p_size the size of the byte array ex sizeof(plaintext)
*/
void calc_size_n_pad(int p_size);
/** Pads the plaintext
*
* This function pads the plaintext and returns an char array with the
* plaintext and the padding in order for the plaintext to be compatible with
* 16bit size blocks required by AES
*
* @param in the string of the plaintext in a byte array
* @param out The string of the out array.
* @return no return, The padded plaintext is stored in the out pointer.
*/
void padPlaintext(void* in,byte* out);
/** Check the if the padding is correct.
*
* This functions checks the padding of the plaintext.
*
* @param in the string of the plaintext in a byte array
* @param size the size of the string
* @return true if correct / false if not
*/
bool CheckPad(byte* in,int size);
/** Prints the array given.
*
* This function prints the given array and pad,
* It is mainlly used for debugging purpuses or to output the string.
*
* @param output[] the string of the text in a byte array
* @param p_pad optional, used to print with out the padding characters
*/
void printArray(byte output[],bool p_pad = true);
/** Prints the array given.
*
* This function prints the given array in Hexadecimal.
*
* @param output[] the string of the text in a byte array
* @param sizel the size of the array.
*/
void printArray(byte output[],int sizel);
/** User friendly implementation of AES-CBC encryption.
*
* @param *plain pointer to the plaintext
* @param size_p size of the plaintext
* @param *cipher pointer to the ciphertext
* @param *key pointer to the key that will be used.
* @param bits bits of the encryption/decrpytion
* @param ivl the initialization vector IV that will be used for encryption.
* @note The key will be stored in class variable.
*/
void do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits, byte ivl [N_BLOCK]);
/** User friendly implementation of AES-CBC encryption.
*
* @param *plain pointer to the plaintext
* @param size_p size of the plaintext
* @param *cipher pointer to the ciphertext
* @param *key pointer to the key that will be used.
* @param bits bits of the encryption/decrpytion
* @note The key will be stored in class variable.
*/
void do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits);
/** User friendly implementation of AES-CBC decryption.
*
* @param *cipher pointer to the ciphertext
* @param size_c size of the ciphertext
* @param *plain pointer to the plaintext
* @param *key pointer to the key that will be used.
* @param bits bits of the encryption/decrpytion
* @param ivl the initialization vector IV that will be used for decryption.
* @note The key will be stored in class variable.
*/
void do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits, byte ivl [N_BLOCK]);
/** User friendly implementation of AES-CBC decryption.
*
* @param *cipher pointer to the ciphertext
* @param size_c size of the ciphertext
* @param *plain pointer to the plaintext
* @param *key pointer to the key that will be used.
* @param bits bits of the encryption/decrpytion
* @note The key will be stored in class variable.
*/
void do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits);
#if defined(AES_LINUX)
/**
* used in linux in order to retrieve the time in milliseconds.
*
* @return returns the milliseconds in a double format.
*/
double millis();
#endif
private:
int round ;/**< holds the number of rounds to be used. */
byte key_sched [KEY_SCHEDULE_BYTES] ;/**< holds the pre-computed key for the encryption/decrpytion. */
unsigned long long int IVC;/**< holds the initialization vector counter in numerical format. */
byte iv[16];/**< holds the initialization vector that will be used in the cipher. */
int pad;/**< holds the size of the padding. */
int size;/**< hold the size of the plaintext to be ciphered */
#if defined(AES_LINUX)
timeval tv;/**< holds the time value on linux */
byte arr_pad[15];/**< holds the hexadecimal padding values on linux */
#else
byte arr_pad[15];// = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f };/**< holds the hexadecimal padding values */
#endif
} ;
#endif
/**
* @example aes.pde
* <b>For Arduino</b><br>
* <b>Updated: spaniakos 2015 </b><br>
*
* This is an example of how to use AES in CBC mode easily.
* The text and keys can be either in HEX or String format.<br />
*/
/**
* @example aes.cpp
* <b>For Rasberry pi</b><br>
* <b>Updated: spaniakos 2015 </b><br>
*
* This is an example of how to use AES in CBC mode easily.
* The text and keys can be either in HEX or String format.<br />
*/
/**
* @example test_vectors.pde
* <b>For Arduino</b><br>
* <b>Updated: spaniakos 2015 </b><br>
*
* This is an example of monte carlo test vectors, in order to justify the effectiveness of the algorithm.<br />
* plus is a classical approach to the AES encryption library with out the easy to use add-on modifications.
*/
/**
* @example test_vectors.cpp
* <b>For Rasberry pi</b><br>
* <b>Updated: spaniakos 2015 </b><br>
*
* This is an example of monte carlo test vectors, in order to justify the effectiveness of the algorithm.<br />
* plus is a classical approach to the AES encryption library with out the easy to use add-on modifications.
*/
/**
* @defgroup aeslib AES library for Arduino and Raspberry pi
* @ingroup internals
*
* @section AesGoals design Goals
*
* This library is AESigned to be...
* @li Fast and efficient.
* @li Able to effectively encrypt and decrypt any size of string.
* @li Able to encrypt and decrypt using AES
* @li Able to encrypt and decrypt using AES-CBC
* @li Easy for the user to use in his programs.
*
* @section Acknowledgements Acknowledgements
* This is an AES library for the Arduino, based on tzikis's AES library, which you can find <a href= "https://github.com/tzikis/arduino">here:</a>.<br />
* Tzikis library was based on scottmac`s library, which you can find <a href="https://github.com/scottmac/arduino">here:</a><br />
*
* @section Installation Installation
* <h3>Arduino</h3>
* Create a folder named _AES_ in the _libraries_ folder inside your Arduino sketch folder. If the
* libraries folder doesn't exist, create it. Then copy everything inside. (re)launch the Arduino IDE.<br />
* You're done. Time for a mojito
*
* <h3>Raspberry pi</h3>
* <b>install</b><br /><br />
*
* sudo make install<br />
* cd examples_Rpi<br />
* make<br /><br />
*
* <b>What to do after changes to the library</b><br /><br />
* sudo make clean<br />
* sudo make install<br />
* cd examples_Rpi<br />
* make clean<br />
* make<br /><br />
* <b>What to do after changes to a sketch</b><br /><br />
* cd examples_Rpi<br />
* make \<sketch\><br /><br />
* or <br />
* make clean<br />
* make<br /><br /><br />
* <b>How to start a sketch</b><br /><br />
* cd examples_Rpi<br />
* sudo ./\<sketch\><br /><br />
*
* @section AesNews News
*
* If issues are discovered with the documentation, please report them <a href="https://github.com/spaniakos/spaniakos.github.io/issues"> here</a>
* @section AesUseful Useful References
*
* Please refer to:
*
* @li <a href="http://spaniakos.github.io/AES/classAES.html"><b>AES</b> Class Documentation</a>
* @li <a href="https://github.com/spaniakos/AES/archive/master.zip"><b>Download</b></a>
* @li <a href="https://github.com/spaniakos/AES/"><b>Source Code</b></a>
* @li <a href="http://spaniakos.github.io/">All spaniakos Documentation Main Page</a>
*
* @section AesBoard_Support Board Support
*
* Most standard Arduino based boards are supported:
* - Arduino
* - Intel Galileo support
* - Raspberry Pi Support
*
* - The library has not been tested to other boards, but it should suppport ATMega 328 based boards,Mega Boards,Arduino Due,ATTiny board
*/
+48
View File
@@ -0,0 +1,48 @@
/* code was modified by george spanos <spaniakos@gmail.com>
* 16/12/14
*/
#ifndef __AES_CONFIG_H__
#define __AES_CONFIG_H__
#if (defined(__linux) || defined(linux)) && !defined(__ARDUINO_X86__)
#define AES_LINUX
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#else
#include <Arduino.h>
#endif
#include <stdint.h>
#include <string.h>
#if defined(__ARDUINO_X86__) || (defined (__linux) || defined (linux))
#undef PROGMEM
#define PROGMEM __attribute__(( section(".progmem.data") ))
#define pgm_read_byte(p) (*(p))
typedef unsigned char byte;
#define printf_P printf
#define PSTR(x) (x)
#elif defined(ARDUINO_ARCH_ESP8266)
#include <pgmspace.h>
#elif defined(ARDUINO_ARCH_SAMD)
#define printf_P printf
#else
#include <avr/pgmspace.h>
#endif
#define N_ROW 4
#define N_COL 4
#define N_BLOCK (N_ROW * N_COL)
#define N_MAX_ROUNDS 14
#define KEY_SCHEDULE_BYTES ((N_MAX_ROUNDS + 1) * N_BLOCK)
#define AES_SUCCESS (0)
#define AES_FAILURE (-1)
#endif
+2372
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More