@@ -1,3 +1,6 @@
|
||||
stino.settings
|
||||
*~
|
||||
*bak
|
||||
MyPrivateConfig.h
|
||||
Documentation/html
|
||||
doxygen_sqlite3.db
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
*.o
|
||||
*.elf
|
||||
@@ -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)
|
||||
@@ -1,213 +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 (uint8_t j = 0; j < 20; j++) {
|
||||
wdt_reset();
|
||||
for (uint8_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);
|
||||
}
|
||||
|
||||
// Inform gateway that bootloader does not accept signed messages
|
||||
msg.type = I_REQUEST_SIGNING;
|
||||
mSetLength(msg, 1);
|
||||
msg.data[0] = 0;
|
||||
sendWrite(msg);
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,210 +0,0 @@
|
||||
:1070000011E0A0E0B1E0E8EFFCE702C005900D92CE
|
||||
:10701000A030B107D9F711E0A0E0B1E001C01D92A6
|
||||
:10702000A83DB107E1F711248091610090E08F70D5
|
||||
:107030009070909301018093000182E090E020E83D
|
||||
:107040000FB6F89420936100809361000FBE14BEC8
|
||||
:1070500029E288E190E00FB6F894A895809360004B
|
||||
:107060000FBE20936000A8951092CF011092D0011E
|
||||
:107070001092D1011092D2011092D3011092D4013A
|
||||
:107080001092D5011092D60184B18E6284B92498F1
|
||||
:1070900085B1857D85B92A9A8AE0B1D184E06FE512
|
||||
:1070A00015D281E06BE312D285E06CE40FD286E06A
|
||||
:1070B000DED1682F607D676286E008D280E0D7D19C
|
||||
:1070C000682F6C6080E002D287E060E7FFD12A98E9
|
||||
:1070D00081E095D180E5A2D183E7A0D12A9A81E011
|
||||
:1070E0008ED18DE1C4D1682F66608DE1EFD18CE146
|
||||
:1070F000BED1682F6F638CE1E9D182EEAAD181EE17
|
||||
:10710000A8D1F9D180E0B3D1682F6E7F80E0DED1C5
|
||||
:1071100082E00FEF1CEF21EE38EA48EA50E060E031
|
||||
:1071200070E0F6D1A89582E091E060E070E043E085
|
||||
:1071300050E02AEE39E369D5A89580910201809349
|
||||
:107140000E0180930D0143D22FEF20931301809104
|
||||
:10715000110192E0909310012093040188708360E4
|
||||
:107160008093110120930F0187E068E03ED38823CC
|
||||
:1071700009F41ED310920F01809102018F3F09F094
|
||||
:1071800038C081E00FEF1CEF21EE38EA48EA50E00A
|
||||
:1071900060E070E0BDD183E064E027D3882309F488
|
||||
:1071A000FBD280913201829586958770813019F4E7
|
||||
:1071B0008091350105C0882329F485E391E007D546
|
||||
:1071C00080930201809102018F3F09F4E5D2809102
|
||||
:1071D0000201F999FECF1FBA12BC11BC80BD0FB6D7
|
||||
:1071E000F894FA9AF99A0FBE80930E0180930D01DC
|
||||
:1071F000EED185E091E06BE171E048E050E02AEEED
|
||||
:1072000039E303D590911101987180911001877035
|
||||
:10721000806280931001946C90931101809105011C
|
||||
:1072200090910601909315018093140180910701BC
|
||||
:1072300090910801909317018093160180E061E01E
|
||||
:10724000D4D2882309F4B4D2209137013091380187
|
||||
:10725000809107019091080128173907C9F42091FE
|
||||
:10726000390130913A018091090190910A01281762
|
||||
:10727000390771F420913B0130913C0180910B0161
|
||||
:1072800090910C012817390719F451D2811190D22D
|
||||
:10729000409135015091360150930601409305010C
|
||||
:1072A0002091370130913801309308012093070174
|
||||
:1072B0008091390190913A0190930A0180930901DC
|
||||
:1072C00080913B0190913C0190930C0180930B01C4
|
||||
:1072D000F4E1EF2EF1E0FF2E8091100187708063C2
|
||||
:1072E00080931001509315014093140130931701BE
|
||||
:1072F00020931601C0E0D0E000E91FEFBB24B39457
|
||||
:10730000E5E0CE2E71E1D72E4DC0F701D583C483C1
|
||||
:1073100082E063E06AD2882309F43ED2902F90592C
|
||||
:107320009F77E92FF0E0E15BFE4FABE3B1E080E156
|
||||
:107330000D9001928150E1F7903709F030C03FB7CE
|
||||
:10734000F894F999FECF83E0F80180935700E8950F
|
||||
:1073500007B600FCFDCFA801AFE4B1E02C91119677
|
||||
:107360008C91119790E0982F8827820F911D12968B
|
||||
:10737000FA010C01B0925700E89511244E5F5F4F5F
|
||||
:10738000F1E0AF3CBF0751F7F801C0925700E89514
|
||||
:1073900007B600FCFDCFD0925700E8953FBF21967D
|
||||
:1073A000005F1F4F8091090190910A01C817D9070A
|
||||
:1073B00008F4ABCFA895BBD1882349F08BE191E0CD
|
||||
:1073C00065E071E048E050E021EF39E33AD4E4D1E0
|
||||
:1073D00080E090E0F999FECF92BD81BDF89A80B52A
|
||||
:1073E0000895F999FECF1FBA92BD81BD60BD0FB659
|
||||
:1073F000F894FA9AF99A0FBE08950000089590E063
|
||||
:107400000AC020E030E000002F5F3F4F41E0243F02
|
||||
:107410003407C9F79F5F9817A0F308950F931F9340
|
||||
:10742000182F00E0EADF17FD02C02B9801C02B9A4D
|
||||
:10743000E4DF2D9A83B1110F82958170182BDDDF67
|
||||
:107440002D98DBDF0F5F083069F7812F1F910F91B7
|
||||
:1074500008951F93182F2A9881E0D1DF812FDEDF56
|
||||
:10746000182F2A9A81E0CBDF812F1F9108951F9357
|
||||
:10747000182F2A9881E0C3DF812F8F71CFDF8FEF24
|
||||
:10748000CDDF182F2A9A81E0BADF812F1F9108954E
|
||||
:107490000F931F93CF93DF93182FEB01042F2A989C
|
||||
:1074A00081E0ADDF1F71812F8062B8DF182F03C02C
|
||||
:1074B0008991B4DF01500023D9F72A9A81E09FDF38
|
||||
:1074C000812FDF91CF911F910F9108950F931F93FB
|
||||
:1074D000182F062F2A9881E092DF1F71812F80627A
|
||||
:1074E0009DDF182F802F9ADF2A9A81E088DF812F75
|
||||
:1074F0001F910F91089580E0BADF81FD08C080E000
|
||||
:10750000B6DF682F626080E0E1DF85E078DF089514
|
||||
:107510007F928F929F92AF92BF92CF92DF92EF9223
|
||||
:10752000FF920F931F93DF93CF93CDB7DEB72897CA
|
||||
:107530000FB6F894DEBF0FBECDBF782E48015901BB
|
||||
:107540006A017B01882381F40093CF019092D001DE
|
||||
:107550002093D101B092D2014093D301D092D401B3
|
||||
:107560006093D501F092D60189829A82AB82BC8267
|
||||
:10757000CD82DE82EF82F88681E0871538F0872D94
|
||||
:10758000865FBE016F5F7F4F45E082DF82E06FDF85
|
||||
:1075900021E030E002C0220F331F7A94E2F7682F17
|
||||
:1075A000622B82E093DF28960FB6F894DEBF0FBE01
|
||||
:1075B000CDBFCF91DF911F910F91FF90EF90DF90A2
|
||||
:1075C000CF90BF90AF909F908F907F9008957F92C3
|
||||
:1075D0008F929F92AF92BF92CF92DF92EF92FF92E3
|
||||
:1075E0000F931F93DF93CF93CDB7DEB728970FB6D6
|
||||
:1075F000F894DEBF0FBECDBF8F3F09F483C0082FC4
|
||||
:1076000010E020E030E040E050E060E070E089838E
|
||||
:107610001A832B833C834D835E836F837887882412
|
||||
:107620003CEF932E21EEA22E98EAB92E88EAC82EBE
|
||||
:10763000DD24EE24FF24080D81E09981091708F06C
|
||||
:1076400080E0192D91E0AA819A1608F090E0E82FC9
|
||||
:10765000E10F81E0E11708F080E0982B1E2F2A2D22
|
||||
:1076600077247394BB81AB1608F07724920F81E0E6
|
||||
:10767000921708F080E0782A292F3B2D91E0EC81C9
|
||||
:10768000BE1608F090E0E72DE30F81E0E31708F065
|
||||
:1076900080E0982B3E2F4C2D77247394FD81CF16DC
|
||||
:1076A00008F07724940F81E0941708F080E0782A9E
|
||||
:1076B000492F5D2D91E08E81D81608F090E0E72DDE
|
||||
:1076C000E50F81E0E51708F080E0982B5E2F6E2D26
|
||||
:1076D00077247394AF81EA1608F07724960F81E03F
|
||||
:1076E000961708F080E0782A692F7F0C772D5901D2
|
||||
:1076F0006A01E92E80E00CDF81E09501A6016E2D84
|
||||
:10770000772D06DF28960FB6F894DEBF0FBECDBFEB
|
||||
:10771000CF91DF911F910F91FF90EF90DF90CF906D
|
||||
:10772000BF90AF909F908F907F9008951895409153
|
||||
:10773000090150910A0164E0440F551F6A95E1F771
|
||||
:10774000E0E0F0E02FEF3FEF19C084912827822F6F
|
||||
:1077500082958227082E869586958025082E869507
|
||||
:1077600080258770022E232F869507948795302DCC
|
||||
:10777000282706948795302528273196E417F507A2
|
||||
:1077800020F340E080910B0190910C0128173907FC
|
||||
:1077900009F441E0842F089528E088E190E00FB6D5
|
||||
:1077A000F894A895809360000FBE20936000FFCFEF
|
||||
:1077B000BEDF8823C1F088E10FB6F89480936000A3
|
||||
:1077C000109260000FBE809100019091010120E8AD
|
||||
:1077D0000FB6F89420936100809361000FBEE0E043
|
||||
:1077E000F0E009950895D8DF08952F923F924F92C7
|
||||
:1077F0005F926F927F928F929F92AF92BF92CF9241
|
||||
:10780000DF92EF92FF920F931F93DF93CF93CDB749
|
||||
:10781000DEB7E3970FB6F894DEBF0FBECDBF6BA700
|
||||
:10782000809312016624B9E02B2E312C2C0E3D1EC4
|
||||
:107830002E010894411C511CD101EDE0F1E081E2E0
|
||||
:1078400001900D928150E1F7109103017C842B850A
|
||||
:107850002AA751DE299881E0D2DD81EEFADD82EEA1
|
||||
:10786000F8DD80E004DE682F6E7F80E02FDE30E000
|
||||
:1078700040E050E060E070E080E090E01CA73DA7B1
|
||||
:107880004EA75FA768AB79AB8AAB9BAB8824ACEF04
|
||||
:107890009A2EF1EEAF2EE8EABE2E78EAC72EDD244E
|
||||
:1078A000EE24FF24012F080D81E0011708F080E08D
|
||||
:1078B000192D91E0EDA59E1608F090E0E82FE10F5C
|
||||
:1078C00081E0E11708F080E0982B1E2F2A2DE1E0DF
|
||||
:1078D000FEA5AF1608F0E0E0920F81E0921708F0E5
|
||||
:1078E00080E0E82B292F3B2D91E08FA5B81608F0FA
|
||||
:1078F00090E0E30F81E0E31708F080E0982B3E2F43
|
||||
:107900004C2DE1E0F8A9CF1608F0E0E0940F81E0FB
|
||||
:10791000941708F080E0E82B492F5D2D91E089A9AC
|
||||
:10792000D81608F090E0E50F81E0E51708F080E058
|
||||
:10793000982B5E2F6E2D81E0EAA9EE1608F080E00C
|
||||
:10794000E92FE60F91E0E61708F090E0892B8F0D04
|
||||
:1079500009831A832B833C834D835E83EF8388875F
|
||||
:107960008AE0B20145E094DD80E1B20145E090DDBE
|
||||
:10797000FAA5FF3F11F010EA01C010EB769476945F
|
||||
:10798000769427E0720E2A9881E039DD812F46DD5A
|
||||
:1079900081017101E70CF11C04C0F80181918F0194
|
||||
:1079A0003DDD0E151F05C9F72A9A81E028DD299AC9
|
||||
:1079B00081E025DD299881E022DD8FEF4ADD8073AB
|
||||
:1079C000E1F387E060E782DD84FF02C081EE41DD04
|
||||
:1079D00092DD80E04CDD682F616080E077DD87E03C
|
||||
:1079E00060E774DD0091CF011091D0012091D101A9
|
||||
:1079F0003091D2014091D3015091D4016091D501D1
|
||||
:107A00007091D601802F812B822B832B842B852B89
|
||||
:107A1000862B872B61F009831A832B833C834D834C
|
||||
:107A20005E836F8378878AE0B20145E031DD82EEC4
|
||||
:107A300010DD81EE0EDD299A81E0E1DCCC24A895F1
|
||||
:107A4000DD24A89587E113DD80FD96C08FEF01DD71
|
||||
:107A5000E82EE694F7E0EF22EF1609F48DC02A989D
|
||||
:107A600081E0CDDC80E6DADC8FEFD8DCF82E2A9AD4
|
||||
:107A700081E0C5DC20E22F1518F482EEEADCFF2459
|
||||
:107A80002A9881E0BCDC81E6C9DC0EE211E006C088
|
||||
:107A90008FEFC4DCF80181938F01FA94FF20C1F7C6
|
||||
:107AA0002A9A81E0ACDC87E060E710DD2A9881E06B
|
||||
:107AB000A6DC8E2D886AB2DC8E2DB0DC2A9A81E09D
|
||||
:107AC0009EDC809131018770823009F057C090911F
|
||||
:107AD000300180910201981709F04EC08091320167
|
||||
:107AE000682F70E0CB0187709070039709F034C065
|
||||
:107AF00080913301883009F02FC040913501242F47
|
||||
:107B0000332727FD30958091040190E001972817D5
|
||||
:107B1000390714F5242F2F5F2093040180912F0142
|
||||
:107B200080930301F999FECF1FBA41E050E052BDA6
|
||||
:107B300041BD80BD0FB6F894FA9AF99A0FBEF99933
|
||||
:107B4000FECF1FBA82E090E092BD81BD20BD0FB68E
|
||||
:107B5000F894FA9AF99A0FBE8091110190E0862765
|
||||
:107B6000972787709070892B39F4809133019BA5FA
|
||||
:107B7000891711F481E012C081E041DCD394E4E67E
|
||||
:107B8000DE1609F05ECFC394F4E1CF1609F057CFAB
|
||||
:107B900063942AE0621609F04FCE80E0E3960FB6B8
|
||||
:107BA000F894DEBF0FBECDBFCF91DF911F910F9133
|
||||
:107BB000FF90EF90DF90CF90BF90AF909F908F900D
|
||||
:107BC0007F906F905F904F903F902F900895FC01B1
|
||||
:107BD00088279927E89421912032E9F3293010F081
|
||||
:107BE0002E30C8F32B3241F02D3239F4689404C0A2
|
||||
:107BF0000E943D3E820F911D219120532A30C0F3F7
|
||||
:107C00001EF4909581959F4F0895A0E0B0E0EBE0C1
|
||||
:107C1000FEE30C944F3E7C01EB018A01690109C02F
|
||||
:107C2000CE012196F6010995F70181937F0101505C
|
||||
:107C3000104001151105A1F7CDB7DEB7E8E00C94AF
|
||||
:107C40006B3EA0E0B0E0E7E2FEE30C944F3EEC01B7
|
||||
:107C50007B018A01690109C0CE012196F70161917A
|
||||
:107C60007F01F60109950150104001151105A1F79A
|
||||
:107C7000CDB7DEB7E8E00C946B3E7AE0979F902D8D
|
||||
:107C8000879F802D910D112408952F923F924F923E
|
||||
:107C90005F926F927F928F929F92AF92BF92CF929C
|
||||
:107CA000DF92EF92FF920F931F93CF93DF93CDB7A5
|
||||
:107CB000DEB7CA1BDB0B0FB6F894DEBF0FBECDBF1D
|
||||
:107CC00009942A88398848885F846E847D848C84EE
|
||||
:107CD0009B84AA84B984C884DF80EE80FD800C81F7
|
||||
:107CE0001B81AA81B981CE0FD11D0FB6F894DEBFDA
|
||||
:087CF0000FBECDBFED010895A8
|
||||
:040000030000700089
|
||||
:00000001FF
|
||||
@@ -1,155 +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(uint8_t t) {
|
||||
uint8_t d;
|
||||
uint16_t 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
|
||||
@@ -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
|
||||
@@ -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/>
|
||||
uint16_t type;<br/>
|
||||
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/>
|
||||
uint16_t type;<br/>
|
||||
uint16_t version;<br/>
|
||||
uint16_t blocks;<br/>
|
||||
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/>
|
||||
uint16_t type;<br/>
|
||||
uint16_t version;<br/>
|
||||
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/>
|
||||
uint16_t type;<br/>
|
||||
uint16_t version;<br/>
|
||||
uint16_t block;<br/>
|
||||
uint8_t data[FIRMWARE_BLOCK_SIZE];<br/>
|
||||
} FirmwareResponse;
|
||||
</p>
|
||||
|
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	ImI^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
|
||||
|
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
|
||||
|
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?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
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@@ -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
|
||||
*/
|
||||
@@ -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  <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>
|
||||
@@ -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--> <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 -->
|
||||
@@ -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  <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>
|
||||
@@ -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--> <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 -->
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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_ */
|
||||
@@ -0,0 +1,705 @@
|
||||
/*
|
||||
* 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 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_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
|
||||
* @brief Interval (in ms) of transport sanity checks
|
||||
*/
|
||||
#ifndef MY_TRANSPORT_SANITY_CHECK_INTERVAL
|
||||
#define MY_TRANSPORT_SANITY_CHECK_INTERVAL ((uint32_t)60000)
|
||||
#endif
|
||||
/**
|
||||
* @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 3
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def MY_REGISTRATION_DEFAULT
|
||||
* @brief Node registration default - this applies if no registration response is recieved 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
|
||||
|
||||
// Enables repeater functionality (relays messages from other nodes)
|
||||
// #define MY_REPEATER_FEATURE
|
||||
|
||||
/**
|
||||
* @def MY_SMART_SLEEP_WAIT_DURATION
|
||||
* @brief The wait period 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
|
||||
#define MY_SMART_SLEEP_WAIT_DURATION 500
|
||||
#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 100
|
||||
#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 120
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def MY_GATEWAY_MAX_CLIENTS
|
||||
* @brief Max number of parallel clients (sever mode).
|
||||
*/
|
||||
#ifndef MY_GATEWAY_MAX_CLIENTS
|
||||
#define MY_GATEWAY_MAX_CLIENTS 1
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/**********************************
|
||||
* Information LEDs blinking
|
||||
***********************************/
|
||||
// This feature enables LEDs blinking on message receive, transmit
|
||||
// or if some error occurred. This was commonly used only in gateways,
|
||||
// but now can be used in any sensor node. Also the LEDs can now be
|
||||
// disabled in the gateway.
|
||||
|
||||
//#define MY_LEDS_BLINKING_FEATURE
|
||||
|
||||
// The following setting allows you to inverse the blinking feature MY_LEDS_BLINKING_FEATURE
|
||||
// When MY_WITH_LEDS_BLINKING_INVERSE is enabled LEDSs are normally turned on and switches
|
||||
// off when blinking
|
||||
|
||||
//#define MY_WITH_LEDS_BLINKING_INVERSE
|
||||
|
||||
// The following defines can be used to set the port pin, that the LED is connected to
|
||||
// If one of the following is defined here, or in the sketch, MY_LEDS_BLINKING_FEATURE will be
|
||||
// enabled by default. (Replace x with the pin number you have the LED on)
|
||||
//#define MY_DEFAULT_ERR_LED_PIN x
|
||||
//#define MY_DEFAULT_TX_LED_PIN x
|
||||
//#define MY_DEFAULT_RX_LED_PIN x
|
||||
|
||||
/**********************************************
|
||||
* 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
|
||||
#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
|
||||
#else
|
||||
#define MY_RF24_CS_PIN 10
|
||||
#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
|
||||
|
||||
/**
|
||||
* @def MY_RF24_SANITY_CHECK
|
||||
* @brief RF24 sanity check to verify functional RF module
|
||||
*
|
||||
* This reads back and compares configuration registers. Disable if using non-P modules
|
||||
*/
|
||||
#define MY_RF24_SANITY_CHECK
|
||||
|
||||
// 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
|
||||
|
||||
/**
|
||||
* @def MY_PORT
|
||||
* @brief The Ethernet TCP/UDP port to open on controller or gateway.
|
||||
*/
|
||||
#ifndef MY_PORT
|
||||
#define MY_PORT 5003
|
||||
#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
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
#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_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
|
||||
#endif
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* 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_MQTT_CLIENT)
|
||||
#define MY_GATEWAY_FEATURE
|
||||
#define MY_IS_GATEWAY (true)
|
||||
#define MY_NODE_TYPE "gateway"
|
||||
#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 "sensor"
|
||||
#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"
|
||||
#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
|
||||
|
||||
// Not necessary to include blinking feature if no LED's are defined!
|
||||
#if defined(MY_LEDS_BLINKING_FEATURE) && !defined(MY_DEFAULT_RX_LED_PIN) && !defined(MY_DEFAULT_TX_LED_PIN) && !defined(MY_ERR_LED_PIN)
|
||||
#undef MY_LEDS_BLINKING_FEATURE
|
||||
#endif
|
||||
|
||||
// Enable LED BLINKING FEATURE, if there are any LEDs defined.
|
||||
#if defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED) || defined(MY_DEFAULT_TX_LED_PIN)
|
||||
#define MY_LEDS_BLINKING_FEATURE
|
||||
#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
|
||||
/**
|
||||
* @def MY_DEFAULT_RX_LED_PIN
|
||||
* @brief The RX LED default pin.
|
||||
*/
|
||||
#ifndef MY_DEFAULT_RX_LED_PIN
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
#define MY_DEFAULT_RX_LED_PIN 8
|
||||
#else
|
||||
#define MY_DEFAULT_RX_LED_PIN 6
|
||||
#endif
|
||||
#endif
|
||||
/**
|
||||
* @def MY_DEFAULT_TX_LED_PIN
|
||||
* @brief The TX LED default pin.
|
||||
*/
|
||||
#ifndef MY_DEFAULT_TX_LED_PIN
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
#define MY_DEFAULT_TX_LED_PIN 9
|
||||
#else
|
||||
#define MY_DEFAULT_TX_LED_PIN 5
|
||||
#endif
|
||||
#endif
|
||||
/**
|
||||
* @def MY_DEFAULT_ERR_LED_PIN
|
||||
* @brief The Error LED default pin.
|
||||
*/
|
||||
#ifndef MY_DEFAULT_ERR_LED_PIN
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
#define MY_DEFAULT_ERR_LED_PIN 7
|
||||
#else
|
||||
#define MY_DEFAULT_ERR_LED_PIN 4
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(MY_LEDS_BLINKING_FEATURE)
|
||||
#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_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 only support MQTT Client using W5100 and ESP8266 at the moment
|
||||
#if !(defined(MY_CONTROLLER_URL_ADDRESS) || defined(MY_CONTROLLER_IP_ADDRESS))
|
||||
#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 "drivers/PubSubClient/PubSubClient.cpp"
|
||||
#include "core/MyGatewayTransport.cpp"
|
||||
#include "core/MyGatewayTransportMQTTClient.cpp"
|
||||
#elif defined(MY_GATEWAY_FEATURE)
|
||||
// GATEWAY - COMMON FUNCTIONS
|
||||
#include "core/MyGatewayTransport.cpp"
|
||||
|
||||
// We currently only support one protocol at the moment, enable it.
|
||||
#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_CONTROLLER_IP_ADDRESS)
|
||||
#define MY_GATEWAY_CLIENT_MODE
|
||||
#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_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
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
#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
|
||||
|
||||
// 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"
|
||||
#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
|
||||
#endif
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
:
|
||||
|
||||
@@ -1,713 +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()/1000;
|
||||
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');
|
||||
}
|
||||
setInterval(function() {
|
||||
sendTime(BROADCAST_ADDRESS, NODE_SENSOR_ID, gw);
|
||||
}, 5*60*1000);
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,24 @@
|
||||
Arduino
|
||||
=======
|
||||
|
||||
MySensors Arduino Library v1.5
|
||||
MySensors Arduino Library v2.0.0
|
||||
|
||||
Please visit www.mysensors.org for more information
|
||||
|
||||
Current build status of master branch: [](http://ci.mysensors.org/job/MySensorsArduino/branch/master/)
|
||||
Doxygen
|
||||
-------
|
||||
[master](https://ci.mysensors.org/job/MySensorsArduino/branch/master/Doxygen_HTML/index.html) [development](https://ci.mysensors.org/job/MySensorsArduino/branch/development/Doxygen_HTML/index.html)
|
||||
|
||||
Current build status of development branch: [](http://ci.mysensors.org/job/MySensorsArduino/branch/development/)
|
||||
CI statuses
|
||||
-----------
|
||||
Current build status of master branch: [](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/master/)
|
||||
|
||||
Current build status of development branch: [](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/development/)
|
||||
|
||||
Current build status of master branch (nightly build): [](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/master/)
|
||||
|
||||
Current build status of development branch (nightly build): [](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/development/)
|
||||
|
||||
Current build status of master branch (nightly build of Arduino IDE): [](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/master/)
|
||||
|
||||
Current build status of development branch (nightly build of Arduino IDE): [](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
|
||||
#define MY_CAPABILITIES MY_CAP_RESET MY_CAP_RADIO MY_CAP_OTA_FW MY_CAP_TYPE MY_CAP_ARCH MY_CAP_SIGN
|
||||
|
||||
#endif /* MyGatewayTransportEthernet_h */
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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 MyEepromAddresses_h
|
||||
#define MyEepromAddresses_h
|
||||
// EEPROM start address for mysensors library data
|
||||
#define EEPROM_START 0
|
||||
// EEPROM location of node id
|
||||
#define EEPROM_NODE_ID_ADDRESS EEPROM_START
|
||||
// EEPROM location of parent id
|
||||
#define EEPROM_PARENT_NODE_ID_ADDRESS (EEPROM_START+1)
|
||||
// EEPROM location of distance to gateway
|
||||
#define EEPROM_DISTANCE_ADDRESS (EEPROM_PARENT_NODE_ID_ADDRESS+1)
|
||||
#define EEPROM_ROUTES_ADDRESS (EEPROM_DISTANCE_ADDRESS+1) // Where to start storing routing information in EEPROM. Will allocate 256 bytes.
|
||||
#define EEPROM_CONTROLLER_CONFIG_ADDRESS (EEPROM_ROUTES_ADDRESS+256) // Location of controller sent configuration (we allow one payload of config data from controller)
|
||||
#define EEPROM_FIRMWARE_TYPE_ADDRESS (EEPROM_CONTROLLER_CONFIG_ADDRESS+24)
|
||||
#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS+2)
|
||||
#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS+2)
|
||||
#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS+2)
|
||||
#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2)
|
||||
#define EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS+32)
|
||||
#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS+32) // This is set with SecurityPersonalizer.ino
|
||||
#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS+32) // This is set with SecurityPersonalizer.ino
|
||||
#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS+9) // This is set with SecurityPersonalizer.ino
|
||||
#define EEPROM_NODE_LOCK_COUNTER (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS+16)
|
||||
#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER+1) // First free address for sketch static configuration
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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);
|
||||
extern MyMessage _msg;
|
||||
|
||||
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(_msg, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
// 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 */
|
||||
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#if defined(MY_CONTROLLER_IP_ADDRESS)
|
||||
IPAddress _ethernetControllerIP(MY_CONTROLLER_IP_ADDRESS);
|
||||
#endif
|
||||
|
||||
#if defined(MY_IP_ADDRESS)
|
||||
IPAddress _ethernetGatewayIP(MY_IP_ADDRESS);
|
||||
#endif
|
||||
byte _ethernetGatewayMAC[] = { MY_MAC_ADDRESS };
|
||||
uint16_t _ethernetGatewayPort = MY_PORT;
|
||||
MyMessage _ethernetMsg;
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
// gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
|
||||
|
||||
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
|
||||
static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS];
|
||||
#endif
|
||||
|
||||
#if defined(MY_USE_UDP)
|
||||
EthernetUDP _ethernetServer;
|
||||
#else
|
||||
EthernetServer _ethernetServer(_ethernetGatewayPort);
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(MY_GATEWAY_ESP8266)
|
||||
static EthernetClient clients[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
|
||||
(void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD);
|
||||
#ifdef MY_IP_ADDRESS
|
||||
WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp);
|
||||
#endif
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
delay(500);
|
||||
MY_SERIALDEVICE.print(".");
|
||||
yield();
|
||||
}
|
||||
MY_SERIALDEVICE.print(F("IP: "));
|
||||
MY_SERIALDEVICE.println(WiFi.localIP());
|
||||
#endif
|
||||
|
||||
#else
|
||||
#ifdef MY_IP_ADDRESS
|
||||
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP);
|
||||
#else
|
||||
// Get IP address from DHCP
|
||||
if (!Ethernet.begin(_ethernetGatewayMAC)) {
|
||||
MY_SERIALDEVICE.print("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
|
||||
|
||||
#ifdef MY_USE_UDP
|
||||
_ethernetServer.begin(_ethernetGatewayPort);
|
||||
#else
|
||||
// we have to use pointers due to the constructor of EthernetServer
|
||||
_ethernetServer.begin();
|
||||
#endif /* USE_UDP */
|
||||
_w5100_spi_en(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gatewayTransportSend(MyMessage &message)
|
||||
{
|
||||
bool ret = true;
|
||||
char *_ethernetMsg = protocolFormat(message);
|
||||
|
||||
setIndication(INDICATION_GW_TX);
|
||||
|
||||
_w5100_spi_en(true);
|
||||
#if defined(MY_CONTROLLER_IP_ADDRESS)
|
||||
#if defined(MY_USE_UDP)
|
||||
_ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT);
|
||||
_ethernetServer.write(_ethernetMsg, strlen(_ethernetMsg));
|
||||
// returns 1 if the packet was sent successfully
|
||||
ret = _ethernetServer.endPacket();
|
||||
#else
|
||||
EthernetClient client;
|
||||
#if defined(MY_CONTROLLER_URL_ADDRESS)
|
||||
if (client.connected() || client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
|
||||
#else
|
||||
if (client.connected() || client.connect(_ethernetControllerIP, MY_PORT)) {
|
||||
#endif
|
||||
client.write(_ethernetMsg, strlen(_ethernetMsg));
|
||||
}
|
||||
else {
|
||||
// connecting to the server failed!
|
||||
ret = false;
|
||||
}
|
||||
#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())
|
||||
{
|
||||
clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg));
|
||||
}
|
||||
}
|
||||
#else
|
||||
_ethernetServer.write(_ethernetMsg);
|
||||
#endif
|
||||
#endif
|
||||
_w5100_spi_en(false);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if defined(MY_GATEWAY_ESP8266)
|
||||
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);
|
||||
setIndication(INDICATION_GW_RX);
|
||||
#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);
|
||||
return 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);
|
||||
return protocolParse(_ethernetMsg, inputString.string);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
#if defined(MY_GATEWAY_ESP8266)
|
||||
// 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(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
|
||||
if (presentation)
|
||||
presentation();
|
||||
}
|
||||
}
|
||||
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(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
|
||||
_w5100_spi_en(true);
|
||||
if (presentation)
|
||||
presentation();
|
||||
}
|
||||
}
|
||||
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
|
||||
#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)
|
||||
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 /* IP_ADDRESS_DHCP */
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
|
||||
#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
|
||||
#else
|
||||
byte _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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
bool gatewayTransportSend(MyMessage &message) {
|
||||
if (!_MQTT_client.connected())
|
||||
return false;
|
||||
setIndication(INDICATION_GW_TX);
|
||||
char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH];
|
||||
char _convBuffer[MAX_PAYLOAD * 2 + 1];
|
||||
snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR(MY_MQTT_PUBLISH_TOPIC_PREFIX "/%d/%d/%d/%d/%d"), message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type);
|
||||
debug(PSTR("Sending message on topic: %s\n"), _fmtBuffer);
|
||||
return _MQTT_client.publish(_fmtBuffer, message.getString(_convBuffer));
|
||||
}
|
||||
|
||||
void incomingMQTT(char* topic, byte* payload, unsigned int length) {
|
||||
debug(PSTR("Message arrived on topic: %s\n"), topic);
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Node id
|
||||
_MQTT_msg.destination = atoi(str);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Sensor id
|
||||
_MQTT_msg.sensor = atoi(str);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Command type
|
||||
command = atoi(str);
|
||||
mSetCommand(_MQTT_msg, command);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Ack flag
|
||||
mSetRequestAck(_MQTT_msg, atoi(str) ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
// Sub type
|
||||
_MQTT_msg.type = atoi(str);
|
||||
// Add payload
|
||||
if (command == C_STREAM) {
|
||||
blen = 0;
|
||||
uint8_t val;
|
||||
while (*payload) {
|
||||
val = protocolH2i(*payload++) << 4;
|
||||
val += protocolH2i(*payload++);
|
||||
bvalue[blen] = val;
|
||||
blen++;
|
||||
}
|
||||
_MQTT_msg.set(bvalue, blen);
|
||||
}
|
||||
else {
|
||||
char* ca;
|
||||
ca = (char *)payload;
|
||||
ca += length;
|
||||
*ca = '\0';
|
||||
_MQTT_msg.set((const char*)payload);
|
||||
}
|
||||
_MQTT_available = true;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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"));
|
||||
// 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());
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
|
||||
char _serialInputString[MY_GATEWAY_MAX_RECEIVE_LENGTH]; // A buffer for incoming commands from serial interface
|
||||
int _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(_msg, I_GATEWAY_READY).set("Gateway startup complete."));
|
||||
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);
|
||||
_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;
|
||||
}
|
||||
@@ -15,37 +15,38 @@
|
||||
* 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>
|
||||
* Disabled signing backend. Does not provide any security measures.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef MySigningNone_h
|
||||
#define MySigningNone_h
|
||||
#ifndef MyHw_h
|
||||
#define MyHw_h
|
||||
|
||||
#include "MyConfig.h"
|
||||
#include "MySigning.h"
|
||||
#include <stdint.h>
|
||||
#include "MyMessage.h"
|
||||
|
||||
// The "none" signing driver that can be used for nodes
|
||||
// not requiring signing
|
||||
// It does check SIGNING_IDENTIFIER byte to avoid illegal mixing of signing back-ends in
|
||||
// the network (as seen by this node) and it does verify proper execution order on the API.
|
||||
// The "none" driver rejects all other back-ends.
|
||||
class MySigningNone : public MySigning
|
||||
{
|
||||
public:
|
||||
MySigningNone();
|
||||
bool getNonce(MyMessage &msg);
|
||||
bool checkTimer(void);
|
||||
bool putNonce(MyMessage &msg);
|
||||
bool signMsg(MyMessage &msg);
|
||||
bool verifyMsg(MyMessage &msg);
|
||||
};
|
||||
|
||||
#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);
|
||||
*/
|
||||
|
||||
int8_t hwSleep(unsigned long ms);
|
||||
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms);
|
||||
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
|
||||
|
||||
#endif // #ifdef MyHw_h
|
||||
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
volatile 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)
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
sei();
|
||||
// sleep until WDT or ext. interrupt
|
||||
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
|
||||
// reset interrupt trigger var
|
||||
pinIntTrigger = 0;
|
||||
while (!pinIntTrigger && ms >= 8000) { hwPowerDown(SLEEP_8S); ms -= 8000; }
|
||||
if (!pinIntTrigger && ms >= 4000) { hwPowerDown(SLEEP_4S); ms -= 4000; }
|
||||
if (!pinIntTrigger && ms >= 2000) { hwPowerDown(SLEEP_2S); ms -= 2000; }
|
||||
if (!pinIntTrigger && ms >= 1000) { hwPowerDown(SLEEP_1S); ms -= 1000; }
|
||||
if (!pinIntTrigger && ms >= 500) { hwPowerDown(SLEEP_500MS); ms -= 500; }
|
||||
if (!pinIntTrigger && ms >= 250) { hwPowerDown(SLEEP_250MS); ms -= 250; }
|
||||
if (!pinIntTrigger && ms >= 125) { hwPowerDown(SLEEP_120MS); ms -= 120; }
|
||||
if (!pinIntTrigger && ms >= 64) { hwPowerDown(SLEEP_60MS); ms -= 60; }
|
||||
if (!pinIntTrigger && ms >= 32) { hwPowerDown(SLEEP_30MS); ms -= 30; }
|
||||
if (!pinIntTrigger && ms >= 16) { hwPowerDown(SLEEP_15MS); ms -= 15; }
|
||||
}
|
||||
|
||||
int8_t hwSleep(unsigned long ms) {
|
||||
hwInternalSleep(ms);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
|
||||
return hwSleep(interrupt,mode,0xFF,0x00,ms);
|
||||
}
|
||||
|
||||
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
|
||||
// reset interrupt trigger var
|
||||
pinIntTrigger = 0;
|
||||
// attach interrupts
|
||||
attachInterrupt(interrupt1, wakeUp, mode1);
|
||||
if (interrupt2!=0xFF) attachInterrupt(interrupt2, wakeUp2, mode2);
|
||||
|
||||
if (ms>0) {
|
||||
// sleep for defined time
|
||||
hwInternalSleep(ms);
|
||||
} else {
|
||||
// sleep until ext interrupt triggered
|
||||
hwPowerDown(SLEEP_FOREVER);
|
||||
}
|
||||
|
||||
detachInterrupt(interrupt1);
|
||||
if (interrupt2!=0xFF) detachInterrupt(interrupt2);
|
||||
|
||||
// default: no interrupt triggered, timer wake up
|
||||
int8_t retVal = -1;
|
||||
|
||||
if (pinIntTrigger == 1) {
|
||||
retVal = (int8_t)interrupt1;
|
||||
} else if (pinIntTrigger == 2) {
|
||||
retVal = (int8_t)interrupt2;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
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() {
|
||||
noInterrupts();
|
||||
// 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;
|
||||
interrupts();
|
||||
// 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[300];
|
||||
#ifdef MY_GATEWAY_FEATURE
|
||||
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
|
||||
snprintf_P(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
|
||||
MY_SERIALDEVICE.print(fmtBuffer);
|
||||
#endif
|
||||
va_list args;
|
||||
va_start (args, fmt );
|
||||
va_end (args);
|
||||
#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, 299, fmt, args);
|
||||
#endif
|
||||
va_end (args);
|
||||
MY_SERIALDEVICE.print(fmtBuffer);
|
||||
MY_SERIALDEVICE.flush();
|
||||
|
||||
//MY_SERIALDEVICE.write(freeRam());
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // #ifdef ARDUINO_ARCH_AVR
|
||||
@@ -20,11 +20,7 @@
|
||||
#ifndef MyHwATMega328_h
|
||||
#define MyHwATMega328_h
|
||||
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
|
||||
#include "MyHw.h"
|
||||
#include "MyConfig.h"
|
||||
#include "MyMessage.h"
|
||||
#include <avr/eeprom.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include <avr/sleep.h>
|
||||
@@ -36,14 +32,14 @@
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#endif
|
||||
|
||||
#define MY_SERIALDEVICE Serial
|
||||
#if defined __AVR_ATmega328P__
|
||||
#ifndef sleep_bod_disable
|
||||
#define sleep_bod_disable() \
|
||||
do { \
|
||||
unsigned char tempreg; \
|
||||
unsigned char tempreg; \
|
||||
__asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \
|
||||
"ori %[tempreg], %[bods_bodse]" "\n\t" \
|
||||
"out %[mcucr], %[tempreg]" "\n\t" \
|
||||
@@ -60,28 +56,28 @@ do { \
|
||||
|
||||
// Define these as macros to save valuable space
|
||||
|
||||
#define hw_digitalWrite(__pin, __value) (digitalWrite(__pin, __value))
|
||||
#define hw_init() Serial.begin(BAUD_RATE)
|
||||
#define hw_watchdogReset() wdt_reset()
|
||||
#define hw_reboot() wdt_enable(WDTO_15MS); while (1)
|
||||
#define hw_millis() millis()
|
||||
#define hw_readConfig(__pos) (eeprom_read_byte((uint8_t*)(__pos)))
|
||||
#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value))
|
||||
#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE)
|
||||
#define hwWatchdogReset() wdt_reset()
|
||||
#define hwReboot() wdt_enable(WDTO_15MS); while (1)
|
||||
#define hwMillis() millis()
|
||||
#define hwReadConfig(__pos) (eeprom_read_byte((uint8_t*)(__pos)))
|
||||
|
||||
#ifndef eeprom_update_byte
|
||||
#define hw_writeConfig(loc, val) if((uint8_t)(val) != eeprom_read_byte((uint8_t*)(loc))) { eeprom_write_byte((uint8_t*)(loc), (val)); }
|
||||
#define hwWriteConfig(loc, val) if((uint8_t)(val) != eeprom_read_byte((uint8_t*)(loc))) { eeprom_write_byte((uint8_t*)(loc), (val)); }
|
||||
#else
|
||||
#define hw_writeConfig(__pos, __value) (eeprom_update_byte((uint8_t*)(__pos), (__value)))
|
||||
#define hwWriteConfig(__pos, __value) (eeprom_update_byte((uint8_t*)(__pos), (__value)))
|
||||
#endif
|
||||
|
||||
//
|
||||
#define hw_readConfigBlock(__buf, __pos, __length) (eeprom_read_block((__buf), (void*)(__pos), (__length)))
|
||||
#define hw_writeConfigBlock(__pos, __buf, __length) (eeprom_write_block((void*)(__pos), (void*)__buf, (__length)))
|
||||
#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_15MS,
|
||||
SLEEP_30MS,
|
||||
SLEEP_60MS,
|
||||
SLEEP_120MS,
|
||||
@@ -94,28 +90,6 @@ enum period_t
|
||||
SLEEP_FOREVER
|
||||
};
|
||||
|
||||
class MyHwATMega328 : public MyHw
|
||||
{
|
||||
public:
|
||||
MyHwATMega328();
|
||||
void hwInternalSleep(unsigned long ms);
|
||||
|
||||
/* void init();
|
||||
void watchdogReset();
|
||||
void reboot();
|
||||
unsigned long millis();
|
||||
uint8_t readConfig(uint8_t pos);
|
||||
void writeConfig(uint8_t pos, uint8_t value);
|
||||
void readConfigBlock(void* buf, void * pos, size_t length);
|
||||
void writeConfigBlock(void* pos, void* buf, size_t length); */
|
||||
|
||||
void sleep(unsigned long ms);
|
||||
bool sleep(uint8_t interrupt, uint8_t mode, unsigned long ms);
|
||||
uint8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms);
|
||||
#ifdef DEBUG
|
||||
void debugPrint(bool isGW, const char *fmt, ... );
|
||||
#endif
|
||||
private:
|
||||
void internalSleep(unsigned long ms);
|
||||
};
|
||||
#endif
|
||||
#endif // #ifdef ARDUINO_ARCH_AVR
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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 -2;
|
||||
}
|
||||
|
||||
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
|
||||
// TODO: Not supported!
|
||||
(void)interrupt;
|
||||
(void)mode;
|
||||
(void)ms;
|
||||
return -2;
|
||||
}
|
||||
|
||||
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 -2;
|
||||
}
|
||||
|
||||
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[300];
|
||||
#ifdef MY_GATEWAY_FEATURE
|
||||
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
|
||||
snprintf_P(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
|
||||
MY_SERIALDEVICE.print(fmtBuffer);
|
||||
#endif
|
||||
va_list args;
|
||||
va_start (args, fmt );
|
||||
va_end (args);
|
||||
#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, 299, fmt, args);
|
||||
#endif
|
||||
va_end (args);
|
||||
MY_SERIALDEVICE.print(fmtBuffer);
|
||||
MY_SERIALDEVICE.flush();
|
||||
|
||||
//MY_SERIALDEVICE.write(freeRam());
|
||||
}
|
||||
#endif
|
||||
@@ -16,37 +16,32 @@
|
||||
* 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
|
||||
|
||||
#ifndef MyTransportNRF24_h
|
||||
#define MyTransportNRF24_h
|
||||
|
||||
#include "MyConfig.h"
|
||||
#include "MyTransport.h"
|
||||
#include <stdint.h>
|
||||
#include "utility/RF24.h"
|
||||
#include "utility/RF24_config.h"
|
||||
|
||||
#define TO_ADDR(x) (RF24_BASE_RADIO_ID + x)
|
||||
|
||||
#define WRITE_PIPE ((uint8_t)0)
|
||||
#define CURRENT_NODE_PIPE ((uint8_t)1)
|
||||
#define BROADCAST_PIPE ((uint8_t)2)
|
||||
|
||||
class MyTransportNRF24 : public MyTransport
|
||||
{
|
||||
public:
|
||||
MyTransportNRF24(uint8_t ce=RF24_CE_PIN, uint8_t cs=RF24_CS_PIN, uint8_t paLevel=RF24_PA_LEVEL);
|
||||
bool init();
|
||||
void setAddress(uint8_t address);
|
||||
uint8_t getAddress();
|
||||
bool send(uint8_t to, const void* data, uint8_t len);
|
||||
bool available(uint8_t *to);
|
||||
uint8_t receive(void* data);
|
||||
void powerDown();
|
||||
private:
|
||||
RF24 rf24;
|
||||
uint8_t _address;
|
||||
uint8_t _paLevel;
|
||||
};
|
||||
#include "MyHw.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#define MY_SERIALDEVICE Serial
|
||||
#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))
|
||||
#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE); MY_SERIALDEVICE.setDebugOutput(true)
|
||||
#define hwWatchdogReset() wdt_reset()
|
||||
#define hwReboot() ESP.restart();
|
||||
#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);
|
||||
|
||||
|
||||
#endif // #ifdef ARDUINO_ARCH_ESP8266
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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);
|
||||
#ifndef MY_GATEWAY_W5100
|
||||
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 -2;
|
||||
}
|
||||
|
||||
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
|
||||
// TODO: Not supported!
|
||||
(void)interrupt;
|
||||
(void)mode;
|
||||
(void)ms;
|
||||
return -2;
|
||||
}
|
||||
|
||||
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 -2;
|
||||
}
|
||||
|
||||
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[300];
|
||||
#ifdef MY_GATEWAY_FEATURE
|
||||
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
|
||||
snprintf(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE);
|
||||
MY_SERIALDEVICE.print(fmtBuffer);
|
||||
#endif
|
||||
va_list args;
|
||||
va_start (args, fmt );
|
||||
va_end (args);
|
||||
#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, 299, fmt, args);
|
||||
#endif
|
||||
va_end (args);
|
||||
MY_SERIALDEVICE.print(fmtBuffer);
|
||||
// MY_SERIALDEVICE.flush();
|
||||
|
||||
//MY_SERIALDEVICE.write(freeRam());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.
|
||||
*/
|
||||
#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()
|
||||
|
||||
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 hwReadConfigBlock(__buf, __adr, __length) ( __length = __length)
|
||||
#define hwWriteConfigBlock(__buf, __adr, __length) ( __length = __length)
|
||||
#define hwWriteConfig(__adr, __value) ( __value = __value)
|
||||
#define hwReadConfig(__adr) (0)
|
||||
*/
|
||||
#endif // #ifdef ARDUINO_ARCH_SAMD
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
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(_msg, 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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* 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
|
||||
@@ -17,12 +17,17 @@
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "MySigning.h"
|
||||
#ifndef MyInclusionMode_h
|
||||
#define MyInclusionMode_h
|
||||
|
||||
MySigning::MySigning(bool requestSignatures) : _requestSignatures(requestSignatures) {
|
||||
}
|
||||
#include "MySensorsCore.h"
|
||||
|
||||
|
||||
bool MySigning::requestSignatures() {
|
||||
return _requestSignatures;
|
||||
}
|
||||
extern bool gatewayTransportSend(MyMessage &message);
|
||||
|
||||
void inclusionInit();
|
||||
void inclusionModeSet(bool newMode);
|
||||
void inclusionProcess();
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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"
|
||||
#ifdef MY_LEDS_BLINKING_FEATURE
|
||||
#include "MyLeds.h"
|
||||
#endif
|
||||
|
||||
void setIndication( const indication_t ind )
|
||||
{
|
||||
#ifdef MY_LEDS_BLINKING_FEATURE
|
||||
if ((INDICATION_TX == ind) || (INDICATION_GW_TX == ind))
|
||||
{
|
||||
ledsBlinkTx(1);
|
||||
} else if ((INDICATION_RX == ind) || (INDICATION_GW_RX == ind))
|
||||
{
|
||||
ledsBlinkRx(1);
|
||||
} else if (ind > INDICATION_ERR_START)
|
||||
{
|
||||
// Number of blinks indicates which error occurred.
|
||||
ledsBlinkErr(ind-INDICATION_ERR_START);
|
||||
}
|
||||
#endif
|
||||
if (indication)
|
||||
indication(ind);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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_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 rrouting 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_SIGN, //!< Error signing.
|
||||
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
|
||||
@@ -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 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
|
||||
pinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT);
|
||||
pinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT);
|
||||
pinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT);
|
||||
|
||||
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.
|
||||
state = (countRx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
|
||||
hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, state);
|
||||
if (countRx) --countRx;
|
||||
|
||||
state = (countTx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
|
||||
hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, state);
|
||||
if (countTx) --countTx;
|
||||
|
||||
state = (countErr & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF;
|
||||
hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, state);
|
||||
if (countErr) --countErr;
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -17,32 +17,38 @@
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef MyTransportRF69_h
|
||||
#define MyTransportRF69_h
|
||||
|
||||
#include "MyConfig.h"
|
||||
#include "MyTransport.h"
|
||||
#include <stdint.h>
|
||||
#include "utility/RFM69.h"
|
||||
#include <SPI.h>
|
||||
#ifndef MyLeds_h
|
||||
#define MyLeds_h
|
||||
|
||||
|
||||
class MyTransportRFM69 : public MyTransport
|
||||
{
|
||||
public:
|
||||
MyTransportRFM69(uint8_t freqBand=RFM69_FREQUENCY, uint8_t networkId=RFM69_NETWORKID, uint8_t slaveSelectPin=RF69_SPI_CS, uint8_t interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, uint8_t interruptNum=RF69_IRQ_NUM);
|
||||
bool init();
|
||||
void setAddress(uint8_t address);
|
||||
uint8_t getAddress();
|
||||
bool send(uint8_t to, const void* data, uint8_t len);
|
||||
bool available(uint8_t *to);
|
||||
uint8_t receive(void* data);
|
||||
void powerDown();
|
||||
private:
|
||||
RFM69 radio;
|
||||
uint8_t _address;
|
||||
uint8_t _freqBand;
|
||||
uint8_t _networkId;
|
||||
};
|
||||
#ifdef MY_WITH_LEDS_BLINKING_INVERSE
|
||||
#define LED_ON 0x1
|
||||
#define LED_OFF 0x0
|
||||
#else
|
||||
#define LED_ON 0x0
|
||||
#define LED_OFF 0x1
|
||||
#endif
|
||||
|
||||
#ifdef MY_LEDS_BLINKING_FEATURE
|
||||
#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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -37,6 +37,10 @@ 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;
|
||||
@@ -96,7 +100,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, 8),buffer);
|
||||
} else if (payloadType == P_CUSTOM) {
|
||||
return getCustomString(buffer);
|
||||
}
|
||||
@@ -106,6 +110,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];
|
||||
@@ -116,9 +124,6 @@ uint8_t MyMessage::getByte() const {
|
||||
}
|
||||
}
|
||||
|
||||
bool MyMessage::getBool() const {
|
||||
return getInt();
|
||||
}
|
||||
|
||||
float MyMessage::getFloat() const {
|
||||
if (miGetPayloadType() == P_FLOAT32) {
|
||||
@@ -130,7 +135,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) {
|
||||
@@ -140,7 +145,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) {
|
||||
@@ -150,7 +155,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) {
|
||||
@@ -160,7 +165,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) {
|
||||
@@ -195,10 +200,21 @@ MyMessage& MyMessage::set(void* value, uint8_t length) {
|
||||
}
|
||||
|
||||
MyMessage& MyMessage::set(const char* value) {
|
||||
uint8_t length = min(strlen(value), MAX_PAYLOAD);
|
||||
uint8_t length = value == NULL ? 0 : min(strlen(value), 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;
|
||||
}
|
||||
|
||||
@@ -217,28 +233,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;
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* 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 <string.h>
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#define PROTOCOL_VERSION 2 //!< The version of the protocol
|
||||
#define MAX_MESSAGE_LENGTH 32 //!< The maximum size of a message (including header)
|
||||
#define HEADER_SIZE 7 //!< 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
|
||||
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
|
||||
I_FIND_PARENT = 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 = 18, //!< Heartbeat request
|
||||
I_PRESENTATION = 19, //!< Presentation message
|
||||
I_DISCOVER = 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(_msg,_version) BF_SET(_msg.version_length, _version, 0, 2) //!< Set version field
|
||||
#define mGetVersion(_msg) ((uint8_t)BF_GET(_msg.version_length, 0, 2)) //!< Get version field
|
||||
|
||||
#define mSetSigned(_msg,_signed) BF_SET(_msg.version_length, _signed, 2, 1) //!< Set signed field
|
||||
#define mGetSigned(_msg) ((bool)BF_GET(_msg.version_length, 2, 1)) //!< Get versignedsion field
|
||||
|
||||
#define mSetLength(_msg,_length) BF_SET(_msg.version_length, _length, 3, 5) //!< Set length field
|
||||
#define mGetLength(_msg) ((uint8_t)BF_GET(_msg.version_length, 3, 5)) //!< Get length field
|
||||
|
||||
#define mSetCommand(_msg,_command) BF_SET(_msg.command_ack_payload, _command, 0, 3) //!< Set command field
|
||||
#define mGetCommand(_msg) ((uint8_t)BF_GET(_msg.command_ack_payload, 0, 3)) //!< Get command field
|
||||
|
||||
#define mSetRequestAck(_msg,_rack) BF_SET(_msg.command_ack_payload, _rack, 3, 1) //!< Set ack-request field
|
||||
#define mGetRequestAck(_msg) ((bool)BF_GET(_msg.command_ack_payload, 3, 1)) //!< Get ack-request field
|
||||
|
||||
#define mSetAck(_msg,_ackMsg) BF_SET(_msg.command_ack_payload, _ackMsg, 4, 1) //!< Set ack field
|
||||
#define mGetAck(_msg) ((bool)BF_GET(_msg.command_ack_payload, 4, 1)) //!< Get ack field
|
||||
|
||||
#define mSetPayloadType(_msg, _pt) BF_SET(_msg.command_ack_payload, _pt, 5, 3) //!< Set payload type field
|
||||
#define mGetPayloadType(_msg) ((uint8_t)BF_GET(_msg.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 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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
/** @}*/
|
||||
@@ -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-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"
|
||||
|
||||
SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID);
|
||||
NodeFirmwareConfig _fc;
|
||||
bool _fwUpdateOngoing;
|
||||
unsigned long _fwLastRequestTime;
|
||||
uint16_t _fwBlock;
|
||||
uint8_t _fwRetry;
|
||||
|
||||
inline void readFirmwareSettings() {
|
||||
hwReadConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
|
||||
}
|
||||
|
||||
inline void firmwareOTAUpdateRequest() {
|
||||
unsigned long enter = hwMillis();
|
||||
if (_fwUpdateOngoing && (enter - _fwLastRequestTime > MY_OTA_RETRY_DELAY)) {
|
||||
if (!_fwRetry) {
|
||||
setIndication(INDICATION_ERR_FW_TIMEOUT);
|
||||
debug(PSTR("fw upd fail\n"));
|
||||
// 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);
|
||||
debug(PSTR("req FW: T=%02X, V=%02X, B=%04X\n"),_fc.type,_fc.version,_fwBlock - 1);
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST, false).set(&firmwareRequest,sizeof(RequestFWBlock)));
|
||||
}
|
||||
}
|
||||
|
||||
inline 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);
|
||||
debug(PSTR("fw update\n"));
|
||||
// copy new FW config
|
||||
memcpy(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig));
|
||||
// Init flash
|
||||
if (!_flash.initialize()) {
|
||||
setIndication(INDICATION_ERR_FW_FLASH_INIT);
|
||||
debug(PSTR("flash init fail\n"));
|
||||
_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;
|
||||
}
|
||||
debug(PSTR("fw update skipped\n"));
|
||||
} else if (_msg.type == ST_FIRMWARE_RESPONSE) {
|
||||
if (_fwUpdateOngoing) {
|
||||
// Save block to flash
|
||||
setIndication(INDICATION_FW_UPDATE_RX);
|
||||
debug(PSTR("fw block %d\n"), _fwBlock);
|
||||
// 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.
|
||||
_fwUpdateOngoing = false;
|
||||
if (transportIsValidFirmware()) {
|
||||
debug(PSTR("fw checksum ok\n"));
|
||||
// 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);
|
||||
debug(PSTR("fw checksum fail\n"));
|
||||
}
|
||||
}
|
||||
// reset flags
|
||||
_fwRetry = MY_OTA_RETRY+1;
|
||||
_fwLastRequestTime = 0;
|
||||
} else {
|
||||
debug(PSTR("No fw update ongoing\n"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline 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, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false));
|
||||
}
|
||||
// do a crc16 on the whole received firmware
|
||||
inline 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;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 MyOTAFirmwareUpdate_h
|
||||
#define MyOTAFirmwareUpdate_h
|
||||
|
||||
#include "MySensorsCore.h"
|
||||
|
||||
// Size of each firmware block
|
||||
#define FIRMWARE_BLOCK_SIZE 16
|
||||
// Number of times a firmware block should be requested before giving up
|
||||
#define FIRMWARE_MAX_REQUESTS 5
|
||||
// Number of times to request a fw block before giving up
|
||||
#define MY_OTA_RETRY 5
|
||||
// Number of millisecons before re-request a fw block
|
||||
#define MY_OTA_RETRY_DELAY 500
|
||||
// Start offset for firmware in flash (DualOptiboot wants to keeps a signature first)
|
||||
#define FIRMWARE_START_OFFSET 10
|
||||
// Bootloader version
|
||||
#define MY_OTA_BOOTLOADER_MAJOR_VERSION 3
|
||||
#define MY_OTA_BOOTLOADER_MINOR_VERSION 0
|
||||
#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION)
|
||||
|
||||
|
||||
/// @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
|
||||
@@ -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
|
||||
@@ -17,18 +17,18 @@
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef MyParserSerial_h
|
||||
#define MyParserSerial_h
|
||||
#ifndef MyProtocol_h
|
||||
#define MyProtocol_h
|
||||
|
||||
#include "MyConfig.h"
|
||||
#include "MyParser.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);
|
||||
|
||||
class MyParserSerial : public MyParser
|
||||
{
|
||||
public:
|
||||
MyParserSerial();
|
||||
bool parse(MyMessage &message, char *inputString);
|
||||
private:
|
||||
uint8_t h2i(char c);
|
||||
};
|
||||
#endif
|
||||
@@ -17,13 +17,16 @@
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "MyParser.h"
|
||||
#include "MyParserSerial.h"
|
||||
#include "MyConfig.h"
|
||||
#include "MyTransport.h"
|
||||
#include "MyProtocol.h"
|
||||
|
||||
MyParserSerial::MyParserSerial() : MyParser() {}
|
||||
uint8_t protocolH2i(char c);
|
||||
|
||||
bool MyParserSerial::parse(MyMessage &message, char *inputString) {
|
||||
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;
|
||||
@@ -58,22 +61,25 @@ bool MyParserSerial::parse(MyMessage &message, char *inputString) {
|
||||
blen = 0;
|
||||
uint8_t val;
|
||||
while (*str) {
|
||||
val = h2i(*str++) << 4;
|
||||
val += h2i(*str++);
|
||||
val = protocolH2i(*str++) << 4;
|
||||
val += protocolH2i(*str++);
|
||||
bvalue[blen] = val;
|
||||
blen++;
|
||||
}
|
||||
} else {
|
||||
value = str;
|
||||
// Remove ending carriage return character (if it exists)
|
||||
// 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;
|
||||
@@ -89,7 +95,12 @@ bool MyParserSerial::parse(MyMessage &message, char *inputString) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t MyParserSerial::h2i(char c) {
|
||||
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;
|
||||
}
|
||||
|
||||
uint8_t protocolH2i(char c) {
|
||||
uint8_t i = 0;
|
||||
if (c <= '9')
|
||||
i += c - '0';
|
||||
@@ -99,3 +110,5 @@ uint8_t MyParserSerial::h2i(char c) {
|
||||
i += c - 'A' + 10;
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
/*
|
||||
* 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_LEDS_BLINKING_FEATURE)
|
||||
ledsProcess();
|
||||
#endif
|
||||
|
||||
#if defined(MY_INCLUSION_MODE_FEATURE)
|
||||
inclusionProcess();
|
||||
#endif
|
||||
|
||||
#if defined(MY_GATEWAY_FEATURE)
|
||||
gatewayTransportProcess();
|
||||
#endif
|
||||
|
||||
#if defined(MY_RADIO_FEATURE)
|
||||
transportProcess();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void _infiniteLoop() {
|
||||
while(1) {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
yield();
|
||||
#endif
|
||||
#if defined (MY_LEDS_BLINKING_FEATURE)
|
||||
ledsProcess();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void _begin() {
|
||||
#if !defined(MY_DISABLED_SERIAL)
|
||||
hwInit();
|
||||
#endif
|
||||
|
||||
// Call before() in sketch (if it exists)
|
||||
if (before)
|
||||
before();
|
||||
|
||||
debug(PSTR("Starting " MY_NODE_TYPE " (" MY_CAPABILITIES ", " MYSENSORS_LIBRARY_VERSION ")\n"));
|
||||
|
||||
#if defined(MY_LEDS_BLINKING_FEATURE)
|
||||
ledsInit();
|
||||
#endif
|
||||
|
||||
signerInit();
|
||||
|
||||
// Read latest received controller configuration from EEPROM
|
||||
hwReadConfigBlock((void*)&_cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig));
|
||||
// isMetric is bool, hence empty EEPROM (=0xFF) evaluates to true
|
||||
|
||||
#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);
|
||||
transportInitialize();
|
||||
while (!isTransportOK()) {
|
||||
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("Node is 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
|
||||
|
||||
// initialize the transport driver
|
||||
if (!gatewayTransportInit()) {
|
||||
setIndication(INDICATION_ERR_INIT_GWTRANSPORT);
|
||||
debug(PSTR("Transport driver init fail\n"));
|
||||
// Nothing more we can do
|
||||
_infiniteLoop();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Call sketch setup
|
||||
if (setup)
|
||||
setup();
|
||||
|
||||
#if defined(MY_RADIO_FEATURE)
|
||||
presentNode();
|
||||
#endif
|
||||
|
||||
// register node
|
||||
_registerNode();
|
||||
|
||||
debug(PSTR("Init complete, id=%d, parent=%d, distance=%d, registration=%d\n"), _nc.nodeId, _nc.parentNodeId, _nc.distance, _nodeRegistered);
|
||||
}
|
||||
|
||||
|
||||
void _registerNode() {
|
||||
#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE)
|
||||
debug(PSTR("Request registration...\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, _nc.nodeId, 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("No registration required\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, _nc.nodeId, 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("NODE:!REG\n"));
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return _sendRoute(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sendBatteryLevel(uint8_t value, bool enableAck) {
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL, enableAck).set(value));
|
||||
}
|
||||
|
||||
void sendHeartbeat(void) {
|
||||
#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RS485)
|
||||
uint32_t heartbeat = transportGetHeartbeat();
|
||||
#else
|
||||
uint32_t heartbeat = hwMillis();
|
||||
#endif
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE, false).set(heartbeat));
|
||||
}
|
||||
|
||||
void present(uint8_t childSensorId, uint8_t sensorType, const char *description, bool enableAck) {
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, 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, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME, enableAck).set(name));
|
||||
if (version) _sendRoute(build(_msgTmp, _nc.nodeId, 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, _nc.nodeId, destination, childSensorId, C_REQ, variableType, false).set(""));
|
||||
}
|
||||
|
||||
void requestTime() {
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, 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("Node registration=%d\n"), _nodeRegistered);
|
||||
#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
|
||||
#if defined(MY_RADIO_FEATURE)
|
||||
presentNode();
|
||||
#endif
|
||||
}
|
||||
else if (type == I_HEARTBEAT) {
|
||||
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, _nc.nodeId, 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)
|
||||
char debug_msg = _msg.data[0];
|
||||
if (debug_msg == 'R') { // routing table
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
for (uint8_t cnt = 0; cnt != 255; cnt++) {
|
||||
uint8_t route = hwReadConfig(EEPROM_ROUTES_ADDRESS + cnt);
|
||||
if (route != BROADCAST_ADDRESS) {
|
||||
debug(PSTR("ID: %d via %d\n"), cnt, route);
|
||||
uint8_t OutBuf[2] = { cnt,route };
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, 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, _nc.nodeId, 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, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwCPUFrequency()));
|
||||
}
|
||||
else if (debug_msg == 'M') { // free memory
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, 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, _nc.nodeId, 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)
|
||||
// register request are exclusively handled by GW/Controller
|
||||
// !!! eventually define if AUTO ACK or register request forwarded to controller
|
||||
#if !defined(MY_REGISTRATION_CONTROLLER)
|
||||
// auto register if version compatible
|
||||
bool approveRegistration = true;
|
||||
|
||||
#if defined(MY_CORE_COMPATIBILITY_CHECK)
|
||||
approveRegistration = (_msg.getByte() >= MY_CORE_MIN_VERSION);
|
||||
#endif
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, _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(unsigned long ms) {
|
||||
#if defined(MY_OTA_FIRMWARE_FEATURE)
|
||||
if (_fwUpdateOngoing) {
|
||||
// Do not sleep node while fw update is ongoing
|
||||
wait(ms);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
// if repeater, do not sleep
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
wait(ms);
|
||||
return -1;
|
||||
#else
|
||||
#if defined(MY_RADIO_FEATURE)
|
||||
transportPowerDown();
|
||||
#endif
|
||||
setIndication(INDICATION_SLEEP);
|
||||
const int8_t res = hwSleep(ms);
|
||||
setIndication(INDICATION_WAKEUP);
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
int8_t smartSleep(unsigned long ms) {
|
||||
int8_t ret = sleep(ms);
|
||||
// notifiy controller about wake up
|
||||
sendHeartbeat();
|
||||
// listen for incoming messages
|
||||
wait(MY_SMART_SLEEP_WAIT_DURATION);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int8_t sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
|
||||
#if defined(MY_OTA_FIRMWARE_FEATURE)
|
||||
if (_fwUpdateOngoing) {
|
||||
// not supported
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
// not supported
|
||||
(void)interrupt;
|
||||
(void)mode;
|
||||
(void)ms;
|
||||
return -2;
|
||||
#else
|
||||
#if defined(MY_RADIO_FEATURE)
|
||||
transportPowerDown();
|
||||
#endif
|
||||
setIndication(INDICATION_SLEEP);
|
||||
const int8_t res = hwSleep(interrupt, mode, ms);
|
||||
setIndication(INDICATION_WAKEUP);
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) {
|
||||
int8_t ret = sleep(interrupt, mode, ms);
|
||||
// notifiy controller about wake up
|
||||
sendHeartbeat();
|
||||
// listen for incoming messages
|
||||
wait(MY_SMART_SLEEP_WAIT_DURATION);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
|
||||
#if defined(MY_OTA_FIRMWARE_FEATURE)
|
||||
if (_fwUpdateOngoing) {
|
||||
// not supported
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
// not supported
|
||||
(void)interrupt1;
|
||||
(void)mode1;
|
||||
(void)interrupt2;
|
||||
(void)mode2;
|
||||
(void)ms;
|
||||
return -2;
|
||||
#else
|
||||
#if defined(MY_RADIO_FEATURE)
|
||||
transportPowerDown();
|
||||
#endif
|
||||
setIndication(INDICATION_SLEEP);
|
||||
const int8_t res = hwSleep(interrupt1, mode1, interrupt2, mode2, ms);
|
||||
setIndication(INDICATION_WAKEUP);
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) {
|
||||
int8_t ret = sleep(interrupt1, mode1, interrupt2, mode2, ms);
|
||||
// notifiy controller about wake up
|
||||
sendHeartbeat();
|
||||
// listen for incoming messages
|
||||
wait(MY_SMART_SLEEP_WAIT_DURATION);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#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("Node is locked. Ground pin %d and reset to unlock.\n"), MY_NODE_UNLOCK_PIN);
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
yield();
|
||||
#endif
|
||||
_sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID,C_INTERNAL, I_LOCKED, false).set(str));
|
||||
#if defined(MY_RADIO_FEATURE)
|
||||
transportPowerDown();
|
||||
#endif
|
||||
setIndication(INDICATION_SLEEP);
|
||||
(void)hwSleep((unsigned long)1000*60*30); // Sleep for 30 min before resending LOCKED message
|
||||
setIndication(INDICATION_WAKEUP);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* 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 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>
|
||||
|
||||
#ifdef MY_DEBUG
|
||||
#define debug(x,...) hwDebugPrint(x, ##__VA_ARGS__)
|
||||
#else
|
||||
#define debug(x,...)
|
||||
#endif
|
||||
|
||||
#define GATEWAY_ADDRESS ((uint8_t)0) //!< Node ID for GW sketch
|
||||
#define NODE_SENSOR_ID 0xFF //!< 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
|
||||
|
||||
|
||||
/**
|
||||
* @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
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
|
||||
*/
|
||||
int8_t sleep(unsigned long ms);
|
||||
int8_t smartSleep(unsigned long ms);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return Interrupt number wake up was triggered by pin change, -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
|
||||
*/
|
||||
int8_t sleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0);
|
||||
int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long 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 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 wake up was triggered by pin change, -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update)
|
||||
*/
|
||||
int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0);
|
||||
int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0);
|
||||
|
||||
#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 ********/
|
||||
|
||||
void _begin();
|
||||
|
||||
void _process(void);
|
||||
|
||||
bool _processInternalMessages();
|
||||
|
||||
void _infiniteLoop();
|
||||
|
||||
void _registerNode();
|
||||
|
||||
bool _sendRoute(MyMessage &message);
|
||||
|
||||
extern NodeConfig _nc;
|
||||
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
|
||||
void receive(const MyMessage &message) __attribute__((weak));
|
||||
void receiveTime(unsigned long) __attribute__((weak));
|
||||
void presentation() __attribute__((weak));
|
||||
void before() __attribute__((weak));
|
||||
void setup() __attribute__((weak));
|
||||
void loop() __attribute__((weak));
|
||||
|
||||
|
||||
// Inline function and macros
|
||||
static inline MyMessage& build(MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck) {
|
||||
msg.sender = sender;
|
||||
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, uint8_t type) {
|
||||
msg.sender = GATEWAY_ADDRESS;
|
||||
msg.destination = GATEWAY_ADDRESS;
|
||||
msg.sensor = 255;
|
||||
msg.type = type;
|
||||
mSetCommand(msg, C_INTERNAL);
|
||||
mSetRequestAck(msg, false);
|
||||
mSetAck(msg, false);
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -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 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 || msg.type == I_FIND_PARENT_RESPONSE ||
|
||||
msg.type == I_HEARTBEAT || 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, _nc.nodeId, 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, _nc.nodeId, 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, _nc.nodeId, 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;
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
/** @}*/
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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
|
||||
randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN));
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1,680 @@
|
||||
/*
|
||||
* 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 "MyTransport.h"
|
||||
|
||||
static uint32_t _transport_lastUplinkCheck; //!< last uplink check, to prevent GW flooding
|
||||
|
||||
// repeaters (this includes GW) should perform regular sanity checks for network reliability
|
||||
#if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE)
|
||||
uint32_t _transport_lastSanityCheck; //!< last sanity check
|
||||
#endif
|
||||
|
||||
// SM: transitions and update states
|
||||
static State stInit = { stInitTransition, NULL };
|
||||
static State stParent = { stParentTransition, stParentUpdate };
|
||||
static State stID = { stIDTransition, stIDUpdate };
|
||||
static State stUplink = { stUplinkTransition, NULL };
|
||||
static State stOK = { stOKTransition, stOKUpdate };
|
||||
static State stFailure = { stFailureTransition, stFailureUpdate };
|
||||
|
||||
static transportSM _transportSM;
|
||||
|
||||
// stInit: initialize transport HW
|
||||
void stInitTransition() {
|
||||
debug(PSTR("TSM:INIT\n"));
|
||||
// initialize status variables
|
||||
_transportSM.failedUplinkTransmissions = 0;
|
||||
_transportSM.pingActive = false;
|
||||
_transportSM.transportActive = false;
|
||||
#if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE)
|
||||
_transport_lastSanityCheck = hwMillis();
|
||||
#endif
|
||||
_transport_lastUplinkCheck = 0;
|
||||
// Read node settings (ID, parentId, GW distance) from EEPROM
|
||||
hwReadConfigBlock((void*)&_nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig));
|
||||
|
||||
// initialize radio
|
||||
if (!transportInit()) {
|
||||
debug(PSTR("!TSM:RADIO:FAIL\n"));
|
||||
setIndication(INDICATION_ERR_INIT_TRANSPORT);
|
||||
transportSwitchSM(stFailure);
|
||||
}
|
||||
else {
|
||||
debug(PSTR("TSM:RADIO:OK\n"));
|
||||
_transportSM.transportActive = true;
|
||||
#if defined(MY_GATEWAY_FEATURE)
|
||||
// Set configuration for gateway
|
||||
debug(PSTR("TSM:GW MODE\n"));
|
||||
_nc.parentNodeId = GATEWAY_ADDRESS;
|
||||
_nc.distance = 0;
|
||||
_nc.nodeId = GATEWAY_ADDRESS;
|
||||
transportSetAddress(GATEWAY_ADDRESS);
|
||||
transportSwitchSM(stOK);
|
||||
#else
|
||||
if (MY_NODE_ID != AUTO) {
|
||||
// Set static id
|
||||
_nc.nodeId = MY_NODE_ID;
|
||||
// Save static id in eeprom
|
||||
hwWriteConfig(EEPROM_NODE_ID_ADDRESS, MY_NODE_ID);
|
||||
}
|
||||
// set ID if static or set in EEPROM
|
||||
if(_nc.nodeId!=AUTO) transportAssignNodeID(_nc.nodeId);
|
||||
transportSwitchSM(stParent);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// stParent: find parent
|
||||
void stParentTransition() {
|
||||
debug(PSTR("TSM:FPAR\n")); // find parent
|
||||
_transportSM.preferredParentFound = false;
|
||||
_transportSM.findingParentNode = true;
|
||||
_transportSM.failedUplinkTransmissions = 0;
|
||||
_transportSM.uplinkOk = false;
|
||||
// Set distance to max and invalidate parent node id
|
||||
_nc.distance = DISTANCE_INVALID;
|
||||
_nc.parentNodeId = AUTO;
|
||||
// Broadcast find parent request
|
||||
setIndication(INDICATION_FIND_PARENT);
|
||||
transportRouteMessage(build(_msgTmp, _nc.nodeId, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT, false).set(""));
|
||||
}
|
||||
|
||||
// stParentUpdate
|
||||
void stParentUpdate() {
|
||||
if (transportTimeInState() > STATE_TIMEOUT || _transportSM.preferredParentFound) {
|
||||
// timeout or preferred parent found
|
||||
if (_nc.parentNodeId != AUTO) {
|
||||
debug(PSTR("TSM:FPAR:OK\n")); // find parent ok
|
||||
_transportSM.findingParentNode = false;
|
||||
setIndication(INDICATION_GOT_PARENT);
|
||||
transportSwitchSM(stID);
|
||||
}
|
||||
else if (transportTimeInState() > STATE_TIMEOUT) {
|
||||
if (_transportSM.retries < STATE_RETRIES) {
|
||||
// reenter if timeout and retries left
|
||||
transportSwitchSM(stParent);
|
||||
}
|
||||
else {
|
||||
debug(PSTR("!TSM:FPAR:FAIL\n")); // find parent fail
|
||||
setIndication(INDICATION_ERR_FIND_PARENT);
|
||||
transportSwitchSM(stFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stID: verify and request ID if necessary
|
||||
void stIDTransition() {
|
||||
debug(PSTR("TSM:ID\n"));
|
||||
if (_nc.nodeId == AUTO) {
|
||||
// send ID request
|
||||
setIndication(INDICATION_REQ_NODEID);
|
||||
transportRouteMessage(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_ID_REQUEST, false).set(""));
|
||||
}
|
||||
}
|
||||
|
||||
void stIDUpdate() {
|
||||
if (_nc.nodeId != AUTO) {
|
||||
// current node ID is valid, proceed to uplink check
|
||||
debug(PSTR("TSM:CHKID:OK (ID=%d)\n"), _nc.nodeId);
|
||||
setIndication(INDICATION_GOT_NODEID);
|
||||
// check uplink
|
||||
transportSwitchSM(stUplink);
|
||||
}
|
||||
else if (transportTimeInState() > STATE_TIMEOUT) {
|
||||
if (_transportSM.retries < STATE_RETRIES) {
|
||||
// re-enter if retries left
|
||||
transportSwitchSM(stID);
|
||||
}
|
||||
else {
|
||||
// fail
|
||||
debug(PSTR("!TSM:CHKID:FAIL (ID=%d)\n"), _nc.nodeId);
|
||||
setIndication(INDICATION_ERR_GET_NODEID);
|
||||
transportSwitchSM(stFailure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stUplinkTransition() {
|
||||
debug(PSTR("TSM:UPL\n"));
|
||||
if(transportCheckUplink(true)) {
|
||||
debug(PSTR("TSM:UPL:OK\n"));
|
||||
transportSwitchSM(stOK);
|
||||
}
|
||||
else {
|
||||
debug(PSTR("!TSM:UPL:FAIL\n"));
|
||||
transportSwitchSM(stParent);
|
||||
}
|
||||
}
|
||||
|
||||
void stOKTransition() {
|
||||
debug(PSTR("TSM:READY\n")); // transport is ready
|
||||
_transportSM.uplinkOk = true;
|
||||
}
|
||||
|
||||
// stOK update: monitors uplink failures
|
||||
void stOKUpdate() {
|
||||
#if !defined(MY_GATEWAY_FEATURE)
|
||||
if (_transportSM.failedUplinkTransmissions > TRANSMISSION_FAILURES) {
|
||||
// too many uplink transmissions failed, find new parent
|
||||
#if !defined(MY_PARENT_NODE_IS_STATIC)
|
||||
debug(PSTR("!TSM:UPL FAIL, SNP\n"));
|
||||
transportSwitchSM(stParent);
|
||||
#else
|
||||
debug(PSTR("!TSM:UPL FAIL, STATP\n"));
|
||||
_transportSM.failedUplinkTransmissions = 0;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// stFailure: entered upon HW init failure or max retries exceeded
|
||||
void stFailureTransition() {
|
||||
debug(PSTR("!TSM:FAILURE\n"));
|
||||
_transportSM.uplinkOk = false;
|
||||
_transportSM.transportActive = false;
|
||||
setIndication(INDICATION_ERR_INIT_TRANSPORT);
|
||||
// power down transport, no need until re-init
|
||||
debug(PSTR("TSM:PDT\n"));
|
||||
transportPowerDown();
|
||||
}
|
||||
|
||||
void stFailureUpdate() {
|
||||
if (transportTimeInState()> TIMEOUT_FAILURE_STATE) {
|
||||
transportSwitchSM(stInit);
|
||||
}
|
||||
}
|
||||
|
||||
void transportSwitchSM(State& newState) {
|
||||
if (_transportSM.currentState != &newState) {
|
||||
// state change, reset retry counter
|
||||
_transportSM.retries = 0;
|
||||
// change state
|
||||
_transportSM.currentState = &newState;
|
||||
}
|
||||
else {
|
||||
_transportSM.retries++; // increment retries
|
||||
}
|
||||
// Transition event
|
||||
if (_transportSM.currentState->Transition) _transportSM.currentState->Transition();
|
||||
// save time
|
||||
_transportSM.stateEnter = hwMillis();
|
||||
}
|
||||
|
||||
uint32_t transportTimeInState() {
|
||||
return hwMillis() - _transportSM.stateEnter;
|
||||
}
|
||||
|
||||
void transportUpdateSM(){
|
||||
if (_transportSM.currentState->Update) _transportSM.currentState->Update();
|
||||
}
|
||||
|
||||
bool isTransportOK() {
|
||||
return (_transportSM.uplinkOk);
|
||||
}
|
||||
|
||||
|
||||
void transportInitialize() {
|
||||
// intial state
|
||||
_transportSM.currentState = &stFailure;
|
||||
transportSwitchSM(stInit);
|
||||
}
|
||||
|
||||
// update TSM and process incoming messages
|
||||
void transportProcess() {
|
||||
// update state machine
|
||||
transportUpdateSM();
|
||||
// process transport FIFO
|
||||
transportProcessFIFO();
|
||||
}
|
||||
|
||||
|
||||
bool transportCheckUplink(bool force) {
|
||||
if (!force && (hwMillis() - _transport_lastUplinkCheck) < CHKUPL_INTERVAL) {
|
||||
debug(PSTR("TSP:CHKUPL:OK (FLDCTRL)\n")); // flood control
|
||||
return true;
|
||||
}
|
||||
// ping GW
|
||||
uint8_t hopsCount = transportPingNode(GATEWAY_ADDRESS);
|
||||
// verify hops
|
||||
if (hopsCount != INVALID_HOPS) {
|
||||
// update
|
||||
_transport_lastUplinkCheck = hwMillis();
|
||||
debug(PSTR("TSP:CHKUPL:OK\n"));
|
||||
// did distance to GW change upstream, i.e. re-routing of uplink nodes?
|
||||
if (hopsCount != _nc.distance) {
|
||||
debug(PSTR("TSP:CHKUPL:DGWC (old=%d,new=%d)"), _nc.distance, hopsCount); // distance to GW changed
|
||||
_nc.distance = hopsCount;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
debug(PSTR("TSP:CHKUPL:FAIL (hops=%d)\n"), hopsCount);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void transportAssignNodeID(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);
|
||||
debug(PSTR("TSP:ASSIGNID:OK (ID=%d)\n"),newNodeId);
|
||||
}
|
||||
else {
|
||||
debug(PSTR("!TSP:ASSIGNID:FAIL (ID=%d)\n"),newNodeId);
|
||||
setIndication(INDICATION_ERR_NET_FULL);
|
||||
// Nothing else we can do...
|
||||
transportSwitchSM(stFailure);
|
||||
}
|
||||
}
|
||||
|
||||
bool transportRouteMessage(MyMessage &message) {
|
||||
uint8_t destination = message.destination;
|
||||
uint8_t route;
|
||||
|
||||
if (_transportSM.findingParentNode && destination != BROADCAST_ADDRESS) {
|
||||
debug(PSTR("!TSP:FPAR:ACTIVE (msg not send)\n"));
|
||||
// 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 = hwReadConfig(EEPROM_ROUTES_ADDRESS + destination);
|
||||
if (route == AUTO) {
|
||||
// route unknown
|
||||
if (message.last != _nc.parentNodeId) {
|
||||
// message not from parent, i.e. child node - route it to parent
|
||||
debug(PSTR("!TSP:ROUTING:DEST UNKNOWN (dest=%d, STP=%d)\n"), destination, _nc.parentNodeId);
|
||||
route = _nc.parentNodeId;
|
||||
}
|
||||
else {
|
||||
// route unknown and msg received from parent, send it to destination assuming in rx radius
|
||||
route = destination;
|
||||
}
|
||||
}
|
||||
#else
|
||||
route = _nc.parentNodeId; // not a repeater, all traffic routed via parent
|
||||
#endif
|
||||
}
|
||||
// send message
|
||||
bool ok = transportSendWrite(route, message);
|
||||
#if !defined(MY_GATEWAY_FEATURE)
|
||||
// update counter
|
||||
if (route == _nc.parentNodeId) {
|
||||
if (!ok) {
|
||||
setIndication(INDICATION_ERR_TX);
|
||||
_transportSM.failedUplinkTransmissions++;
|
||||
}
|
||||
else _transportSM.failedUplinkTransmissions = 0;
|
||||
}
|
||||
#else
|
||||
if(!ok) setIndication(INDICATION_ERR_TX);
|
||||
|
||||
#endif
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool transportSendRoute(MyMessage &message) {
|
||||
if (isTransportOK()) {
|
||||
return transportRouteMessage(message);
|
||||
}
|
||||
else {
|
||||
// TNR: transport not ready
|
||||
debug(PSTR("!TSP:SEND:TNR\n"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// only be used inside transport
|
||||
bool transportWait(uint32_t ms, uint8_t cmd, uint8_t msgtype){
|
||||
uint32_t enter = hwMillis();
|
||||
// invalidate msg type
|
||||
_msg.type = !msgtype;
|
||||
bool expectedResponse = false;
|
||||
while ((hwMillis() - enter < ms) && !expectedResponse) {
|
||||
// process incoming messages
|
||||
transportProcessFIFO();
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
yield();
|
||||
#endif
|
||||
expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgtype);
|
||||
}
|
||||
return expectedResponse;
|
||||
}
|
||||
|
||||
uint8_t transportPingNode(uint8_t targetId) {
|
||||
if(!_transportSM.pingActive){
|
||||
if(targetId == _nc.nodeId) {
|
||||
// ping to ourself, pingActive remains false
|
||||
return 0;
|
||||
}
|
||||
_transportSM.pingActive = true;
|
||||
_transportSM.pingResponse = INVALID_HOPS;
|
||||
debug(PSTR("TSP:PING:SEND (dest=%d)\n"), targetId);
|
||||
transportRouteMessage(build(_msgTmp, _nc.nodeId, targetId, NODE_SENSOR_ID, C_INTERNAL, I_PING, false).set((uint8_t)0x01));
|
||||
// Wait for ping reply or timeout
|
||||
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 {
|
||||
return INVALID_HOPS;
|
||||
}
|
||||
}
|
||||
|
||||
void transportClearRoutingTable() {
|
||||
for (uint8_t i = 0; i != 255; i++) {
|
||||
hwWriteConfig(EEPROM_ROUTES_ADDRESS + i, BROADCAST_ADDRESS);
|
||||
}
|
||||
debug(PSTR("TSP:CRT:OK\n")); // clear routing table
|
||||
}
|
||||
|
||||
uint32_t transportGetHeartbeat() {
|
||||
return transportTimeInState();
|
||||
}
|
||||
|
||||
void transportProcessMessage() {
|
||||
(void)signerCheckTimer(); // Manage signing timeout
|
||||
|
||||
uint8_t payloadLength = transportReceive((uint8_t *)&_msg);
|
||||
(void)payloadLength; // currently not used, but good to test for CRC-ok but corrupt msgs
|
||||
|
||||
setIndication(INDICATION_RX);
|
||||
|
||||
uint8_t command = mGetCommand(_msg);
|
||||
uint8_t type = _msg.type;
|
||||
uint8_t sender = _msg.sender;
|
||||
uint8_t last = _msg.last;
|
||||
uint8_t destination = _msg.destination;
|
||||
|
||||
debug(PSTR("TSP:MSG:READ %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"),
|
||||
sender, last, destination, _msg.sensor, mGetCommand(_msg), type, mGetPayloadType(_msg), mGetLength(_msg), mGetSigned(_msg), _msg.getString(_convBuf));
|
||||
|
||||
// verify protocol version
|
||||
if(mGetVersion(_msg) != PROTOCOL_VERSION) {
|
||||
setIndication(INDICATION_ERR_VERSION);
|
||||
debug(PSTR("!TSP:MSG:PVER mismatch\n")); // protocol version
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject massages that do not pass verification
|
||||
if (!signerVerifyMsg(_msg)) {
|
||||
setIndication(INDICATION_ERR_SIGN);
|
||||
debug(PSTR("!TSP:MSG:SIGN verify fail\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (destination == _nc.nodeId) {
|
||||
// This message is addressed to this node
|
||||
// prevent buffer overflow by limiting max. possible message length (5 bits=31 bytes max) to MAX_PAYLOAD (25 bytes)
|
||||
mSetLength(_msg, min(mGetLength(_msg),MAX_PAYLOAD));
|
||||
// null terminate data
|
||||
_msg.data[mGetLength(_msg)] = 0x00;
|
||||
|
||||
// update routing table if msg not from parent
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
if (last != _nc.parentNodeId) {
|
||||
// Message is from one of the child nodes. Add it to routing table.
|
||||
hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check if sender requests an ack back.
|
||||
if (mGetRequestAck(_msg)) {
|
||||
_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
|
||||
debug(PSTR("TSP:MSG:ACK msg\n"));
|
||||
// use transportSendRoute since ACK reply is not internal, i.e. if !transportOK do not reply
|
||||
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
|
||||
transportAssignNodeID(_msg.getByte());
|
||||
#endif
|
||||
return; // no further processing required
|
||||
}
|
||||
if (type == I_FIND_PARENT_RESPONSE) {
|
||||
#if !defined(MY_GATEWAY_FEATURE)
|
||||
// Reply to a I_FIND_PARENT message. Check if the distance is shorter than we already have.
|
||||
uint8_t distance = _msg.getByte();
|
||||
debug(PSTR("TSP:MSG:FPAR RES (ID=%d, dist=%d)\n"), sender, distance);
|
||||
if (isValidDistance(distance)) {
|
||||
// Distance to gateway is one more for us w.r.t. parent
|
||||
distance++;
|
||||
// update settings if distance shorter or preferred parent found
|
||||
if (((isValidDistance(distance) && distance < _nc.distance) || (!_autoFindParent && sender == MY_PARENT_NODE_ID)) && !_transportSM.preferredParentFound) {
|
||||
// Found a neighbor closer to GW than previously found
|
||||
if (!_autoFindParent && sender == MY_PARENT_NODE_ID) {
|
||||
_transportSM.preferredParentFound = true;
|
||||
debug(PSTR("TSP:MSG:FPAR (PPAR FOUND)\n")); // preferred parent found
|
||||
}
|
||||
_nc.distance = distance;
|
||||
_nc.parentNodeId = sender;
|
||||
debug(PSTR("TSP:MSG:PAR OK (ID=%d, dist=%d)\n"), _nc.parentNodeId, _nc.distance);
|
||||
}
|
||||
}
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// general
|
||||
if (type == I_PING) {
|
||||
debug(PSTR("TSP:MSG:PINGED (ID=%d, hops=%d)\n"), sender, _msg.getByte());
|
||||
transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_PONG, false).set((uint8_t)0x01));
|
||||
return; // no further processing required
|
||||
}
|
||||
if (type == I_PONG) {
|
||||
if (_transportSM.pingActive) {
|
||||
_transportSM.pingActive = false;
|
||||
_transportSM.pingResponse = _msg.getByte();
|
||||
debug(PSTR("TSP:MSG:PONG RECV (hops=%d)\n"), _transportSM.pingResponse);
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
#if defined(MY_GATEWAY_FEATURE)
|
||||
// Hand over message to controller
|
||||
gatewayTransportSend(_msg);
|
||||
#else
|
||||
// Call incoming message callback if available
|
||||
if (receive) {
|
||||
receive(_msg);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
else if (destination == BROADCAST_ADDRESS) {
|
||||
// broadcast
|
||||
debug(PSTR("TSP:MSG:BC\n"));
|
||||
if (command == C_INTERNAL) {
|
||||
if (isTransportOK()) {
|
||||
// only reply if node is fully operational
|
||||
if (type == I_FIND_PARENT) {
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
if (sender != _nc.parentNodeId) { // no circular reference
|
||||
debug(PSTR("TSP:MSG:FPAR REQ (sender=%d)\n"), sender); // FPR: find parent request
|
||||
// node is in our range, update routing table - important if node has new repeater as parent
|
||||
hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, sender);
|
||||
// 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(false)){
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
_transport_lastUplinkCheck = hwMillis();
|
||||
#endif
|
||||
debug(PSTR("TSP:MSG:GWL OK\n")); // GW uplink ok
|
||||
// delay minimizes collisions
|
||||
delay(hwMillis() & 0x3ff);
|
||||
transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(_nc.distance));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
return; // no further processing required
|
||||
}
|
||||
}
|
||||
if (type == I_DISCOVER) {
|
||||
if (last == _nc.parentNodeId) {
|
||||
// random wait to minimize collisions
|
||||
delay(hwMillis() & 0x3ff);
|
||||
transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_DISCOVER_RESPONSE, false).set(_nc.parentNodeId));
|
||||
// no return here (for fwd if repeater)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 && isTransportOK()){
|
||||
debug(PSTR("TSP:MSG:FWD BC MSG\n")); // forward BC msg
|
||||
transportRouteMessage(_msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Call incoming message callback if available, but only if message received from parent
|
||||
if (command != C_INTERNAL && last == _nc.parentNodeId && receive) {
|
||||
receive(_msg);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
// msg not no us and not BC, relay msg
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
if (isTransportOK()) {
|
||||
debug(PSTR("TSP:MSG:REL MSG\n")); // relay msg
|
||||
// update routing table if message not received from parent
|
||||
if (last != _nc.parentNodeId) {
|
||||
hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, last);
|
||||
}
|
||||
if (command == C_INTERNAL) {
|
||||
if (type == I_PING || type == I_PONG) {
|
||||
uint8_t hopsCnt = _msg.getByte();
|
||||
if (hopsCnt != MAX_HOPS) {
|
||||
debug(PSTR("TSP:MSG:REL PxNG (hops=%d)\n"), hopsCnt);
|
||||
_msg.set((uint8_t)(hopsCnt + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Relay this message to another node
|
||||
transportRouteMessage(_msg);
|
||||
}
|
||||
#else
|
||||
debug(PSTR("!TSM:MSG:REL MSG, but not a repeater\n"));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
inline void transportProcessFIFO() {
|
||||
if (_transportSM.transportActive) {
|
||||
#if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE)
|
||||
if (hwMillis() > (_transport_lastSanityCheck + MY_TRANSPORT_SANITY_CHECK_INTERVAL)) {
|
||||
_transport_lastSanityCheck = hwMillis();
|
||||
if (!transportSanityCheck()) {
|
||||
debug(PSTR("!TSP:SANCHK:FAIL\n")); // sanity check fail
|
||||
transportSwitchSM(stFailure);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
debug(PSTR("TSP:SANCHK:OK\n")); // sanity check ok
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
// transport not active, nothing to be done
|
||||
return;
|
||||
}
|
||||
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 (isTransportOK()) {
|
||||
// only process if transport ok
|
||||
firmwareOTAUpdateRequest();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool transportSendWrite(uint8_t to, MyMessage &message) {
|
||||
// set protocol version and update last
|
||||
mSetVersion(message, PROTOCOL_VERSION);
|
||||
message.last = _nc.nodeId;
|
||||
|
||||
// sign message if required
|
||||
if (!signerSignMsg(message)) {
|
||||
debug(PSTR("!TSP:MSG:SIGN fail\n"));
|
||||
setIndication(INDICATION_ERR_SIGN);
|
||||
return false;
|
||||
}
|
||||
|
||||
// msg length changes if signed
|
||||
uint8_t length = mGetSigned(message) ? MAX_MESSAGE_LENGTH : mGetLength(message);
|
||||
|
||||
// send
|
||||
setIndication(INDICATION_TX);
|
||||
bool ok = transportSend(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length));
|
||||
|
||||
debug(PSTR("%sTSP:MSG:SEND %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,ft=%d,st=%s:%s\n"),
|
||||
(ok || to == BROADCAST_ADDRESS ? "" : "!"),message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type,
|
||||
mGetPayloadType(message), mGetLength(message), mGetSigned(message), _transportSM.failedUplinkTransmissions, to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(_convBuf));
|
||||
|
||||
return (ok || to==BROADCAST_ADDRESS);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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 MyTransport.h
|
||||
*
|
||||
* @brief API declaration for MyTransport
|
||||
*/
|
||||
#ifndef MyTransport_h
|
||||
#define MyTransport_h
|
||||
|
||||
#include "MySensorsCore.h"
|
||||
|
||||
#if defined(MY_REPEATER_FEATURE)
|
||||
#define TRANSMISSION_FAILURES 10 //!< search for a new parent node after this many transmission failures, higher threshold for repeating nodes
|
||||
#else
|
||||
#define TRANSMISSION_FAILURES 5 //!< search for a new parent node after this many transmission failures, lower threshold for non-repeating nodes
|
||||
#endif
|
||||
#define TIMEOUT_FAILURE_STATE 10000 //!< duration failure state
|
||||
#define STATE_TIMEOUT 2000 //!< general state timeout
|
||||
#define STATE_RETRIES 3 //!< retries before switching to FAILURE
|
||||
#define AUTO 255 //!< ID 255 is reserved for auto initialization of nodeId.
|
||||
#define BROADCAST_ADDRESS ((uint8_t)255) //!< broadcasts are addressed to ID 255
|
||||
#define DISTANCE_INVALID ((uint8_t)255) //!< invalid distance when searching for parent
|
||||
#define MAX_HOPS ((uint8_t)254) //!< maximal mumber of hops for ping/pong
|
||||
#define INVALID_HOPS ((uint8_t)255) //!< invalid hops
|
||||
#define MAX_SUBSEQ_MSGS 5 //!< Maximum number of subsequentially processed messages in FIFO (to prevent transport deadlock if HW issue)
|
||||
#define CHKUPL_INTERVAL ((uint32_t)10000) //!< Minimum time interval to re-check uplink
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* @brief SM state
|
||||
*
|
||||
* This structure stores SM state definitions
|
||||
*/
|
||||
struct State {
|
||||
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 {
|
||||
State* currentState; //!< pointer to current fsm state
|
||||
uint32_t stateEnter; //!< state enter timepoint
|
||||
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 reserved : 3; //!< reserved
|
||||
uint8_t retries : 4; //!< retries / state re-enter
|
||||
uint8_t failedUplinkTransmissions : 4; //!< counter failed uplink transmissions
|
||||
uint8_t pingResponse; //!< stores hops received in I_PONG
|
||||
} __attribute__((packed)) transportSM;
|
||||
|
||||
|
||||
// PRIVATE functions
|
||||
|
||||
/**
|
||||
* @brief Initialize SM variables and transport HW
|
||||
*/
|
||||
void stInitTransition();
|
||||
/**
|
||||
* @brief Find parent
|
||||
*/
|
||||
void stParentTransition();
|
||||
/**
|
||||
* @brief Verify find parent responses
|
||||
*/
|
||||
void stParentUpdate();
|
||||
/**
|
||||
* @brief Send ID request
|
||||
*/
|
||||
void stIDTransition();
|
||||
/**
|
||||
* @brief Verify ID response and GW link
|
||||
*/
|
||||
void stIDUpdate();
|
||||
/**
|
||||
* @brief Send uplink ping request
|
||||
*/
|
||||
void stUplinkTransition();
|
||||
/**
|
||||
* @brief Set transport OK
|
||||
*/
|
||||
void stOKTransition();
|
||||
/**
|
||||
* @brief Monitor transport link
|
||||
*/
|
||||
void stOKUpdate();
|
||||
/**
|
||||
* @brief Transport failure and power down radio
|
||||
*/
|
||||
void stFailureTransition();
|
||||
/**
|
||||
* @brief Re-initialize transport after timeout
|
||||
*/
|
||||
void stFailureUpdate();
|
||||
/**
|
||||
* @brief Switch SM state
|
||||
* @param newState New state to switch SM to
|
||||
*/
|
||||
void transportSwitchSM(State& newState);
|
||||
/**
|
||||
* @brief Update SM state
|
||||
*/
|
||||
void transportUpdateSM();
|
||||
/**
|
||||
* @brief Request time in current SM state
|
||||
* @return ms in current state
|
||||
*/
|
||||
uint32_t transportTimeInState();
|
||||
|
||||
/**
|
||||
* @brief Process all pending messages in RX FIFO
|
||||
*/
|
||||
void transportProcessFIFO();
|
||||
/**
|
||||
* @brief Receive message from RX FIFO and process
|
||||
*/
|
||||
void transportProcessMessage();
|
||||
/**
|
||||
* @brief Assign node ID
|
||||
* @param newNodeId New node ID
|
||||
*/
|
||||
void transportAssignNodeID(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(uint32_t ms, uint8_t cmd, 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(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 !OK
|
||||
*
|
||||
* @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(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(bool force);
|
||||
|
||||
// PUBLIC functions
|
||||
|
||||
/**
|
||||
* @brief Initialize transport and SM
|
||||
*/
|
||||
void transportInitialize();
|
||||
/**
|
||||
* @brief Process FIFO msg and update SM
|
||||
*/
|
||||
void transportProcess();
|
||||
/**
|
||||
* @brief Verify transport status
|
||||
* @return true if transport is initialize and ready
|
||||
*/
|
||||
bool isTransportOK();
|
||||
/**
|
||||
* @brief Clear routing table
|
||||
*/
|
||||
void transportClearRoutingTable();
|
||||
/**
|
||||
* @brief Return heart beat, i.e. ms in current state
|
||||
*/
|
||||
uint32_t transportGetHeartbeat();
|
||||
|
||||
// interface functions for radio driver
|
||||
|
||||
/**
|
||||
* @brief Initialize transport HW
|
||||
* @return true if initalization 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
|
||||
/** @}*/
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#if defined(MY_RF24_ENABLE_ENCRYPTION)
|
||||
#include "drivers/AES/AES.h"
|
||||
#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
|
||||
|
||||
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() {
|
||||
bool avail = RF24_isDataAvailable();
|
||||
return avail;
|
||||
}
|
||||
|
||||
bool transportSanityCheck() {
|
||||
return RF24_sanityCheck();
|
||||
}
|
||||
|
||||
uint8_t transportReceive(void* data) {
|
||||
uint8_t len = RF24_readMessage(data);
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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);
|
||||
// Send ack back if this message wasn't a broadcast
|
||||
if (_radio.TARGETID != RF69_BROADCAST_ADDR)
|
||||
_radio.ACKRequested();
|
||||
_radio.sendACK();
|
||||
return _radio.DATALEN;
|
||||
}
|
||||
|
||||
void transportPowerDown() {
|
||||
_radio.sleep();
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <WProgram.h>
|
||||
#endif
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/***
|
||||
* This file defines the Sensor library version number
|
||||
* 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 LIBRARY_VERSION "1.5.4"
|
||||
#define MYSENSORS_LIBRARY_VERSION "2.0.0"
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
@@ -0,0 +1,62 @@
|
||||
#############################################################################
|
||||
#
|
||||
# Makefile for AES on Raspberry Pi
|
||||
#
|
||||
# License: GPL (General Public License)
|
||||
# Author: Georgios Spanos (spaniakos) <spaniakos@gmail.com>
|
||||
# Date: 02/12/2014
|
||||
#
|
||||
# Description:
|
||||
# ------------
|
||||
# use make all and mak install to install the library
|
||||
# You can change the install directory by editing the LIBDIR line
|
||||
#
|
||||
PREFIX=/usr/local
|
||||
|
||||
# Library parameters
|
||||
# where to put the lib
|
||||
LIBDIR=$(PREFIX)/lib
|
||||
# lib name
|
||||
LIB=libAES
|
||||
# shared library name
|
||||
LIBNAME=$(LIB).so.1.0
|
||||
|
||||
# Where to put the header files
|
||||
HEADER_DIR=${PREFIX}/include/AES
|
||||
|
||||
# The recommended compiler flags for the Raspberry Pi
|
||||
CCFLAGS=-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
|
||||
|
||||
# make all
|
||||
# reinstall the library after each recompilation
|
||||
all: libAES
|
||||
|
||||
# Make the library
|
||||
libAES: AES.o
|
||||
g++ -shared -Wl,-soname,$@.so.1 ${CCFLAGS} -o ${LIBNAME} $^
|
||||
|
||||
# Library parts
|
||||
AES.o: AES.cpp
|
||||
g++ -Wall -fPIC ${CCFLAGS} -c $^
|
||||
|
||||
# clear build files
|
||||
clean:
|
||||
rm -rf *.o ${LIB}.*
|
||||
rm -rf ${LIBDIR}/${LIB}.*
|
||||
rm -rf ${HEADER_DIR}
|
||||
|
||||
install: all install-libs install-headers
|
||||
|
||||
# Install the library to LIBPATH
|
||||
install-libs:
|
||||
@echo "[Installing Libs]"
|
||||
@if ( test ! -d $(PREFIX)/lib ) ; then mkdir -p $(PREFIX)/lib ; fi
|
||||
@install -m 0755 ${LIBNAME} ${LIBDIR}
|
||||
@ln -sf ${LIBDIR}/${LIBNAME} ${LIBDIR}/${LIB}.so.1
|
||||
@ln -sf ${LIBDIR}/${LIBNAME} ${LIBDIR}/${LIB}.so
|
||||
@ldconfig
|
||||
|
||||
install-headers:
|
||||
@echo "[Installing Headers]"
|
||||
@if ( test ! -d ${HEADER_DIR} ) ; then mkdir -p ${HEADER_DIR} ; fi
|
||||
@install -m 0644 *.h ${HEADER_DIR}
|
||||
@@ -0,0 +1,91 @@
|
||||
### Arduino
|
||||
just run the sketched aftel loading the libraries.
|
||||
|
||||
### Raspberry pi
|
||||
install
|
||||
```
|
||||
sudo make install
|
||||
cd examples_Rpi
|
||||
make
|
||||
```
|
||||
|
||||
What to do after changes to the library
|
||||
```
|
||||
sudo make clean
|
||||
sudo make install
|
||||
cd examples_Rpi
|
||||
make clean
|
||||
make
|
||||
```
|
||||
|
||||
What to do after changes to a sketch
|
||||
```
|
||||
cd examples_Rpi
|
||||
make <sketch>
|
||||
|
||||
or
|
||||
make clean
|
||||
make
|
||||
```
|
||||
|
||||
How to start a sketch
|
||||
```
|
||||
cd examples_Rpi
|
||||
sudo ./<sketch>
|
||||
```
|
||||
|
||||
This is a derived work from Brian Gladman's AES implementation for byte-oriented
|
||||
processors, here's the licence terms:
|
||||
|
||||
/*
|
||||
---------------------------------------------------------------------------
|
||||
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.
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <AES.h>
|
||||
#include "./printf.h"
|
||||
|
||||
AES aes ;
|
||||
|
||||
byte *key = (unsigned char*)"0123456789010123";
|
||||
|
||||
byte plain[] = "Add NodeAdd NodeAdd NodeAdd NodeAdd Node";
|
||||
|
||||
//real iv = iv x2 ex: 01234567 = 0123456701234567
|
||||
unsigned long long int my_iv = 36753562;
|
||||
|
||||
void setup ()
|
||||
{
|
||||
Serial.begin (57600) ;
|
||||
printf_begin();
|
||||
delay(500);
|
||||
printf("\n===testng mode\n") ;
|
||||
|
||||
// otfly_test () ;
|
||||
// otfly_test256 () ;
|
||||
}
|
||||
|
||||
void loop ()
|
||||
{
|
||||
prekey_test () ;
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
void prekey (int bits)
|
||||
{
|
||||
aes.iv_inc();
|
||||
byte iv [N_BLOCK] ;
|
||||
byte plain_p[48];
|
||||
byte cipher [48] ;
|
||||
byte check [48] ;
|
||||
unsigned long ms = micros ();
|
||||
aes.set_IV(my_iv);
|
||||
aes.get_IV(iv);
|
||||
aes.do_aes_encrypt(plain,41,cipher,key,bits,iv);
|
||||
Serial.print("Encryption took: ");
|
||||
Serial.println(micros() - ms);
|
||||
ms = micros ();
|
||||
aes.set_IV(my_iv);
|
||||
aes.get_IV(iv);
|
||||
aes.do_aes_decrypt(cipher,48,check,key,bits,iv);
|
||||
Serial.print("Decryption took: ");
|
||||
Serial.println(micros() - ms);
|
||||
printf("\n\nPLAIN :");
|
||||
aes.printArray(plain,(bool)true);
|
||||
printf("\nCIPHER:");
|
||||
aes.printArray(cipher,(bool)false);
|
||||
printf("\nCHECK :");
|
||||
aes.printArray(check,(bool)true);
|
||||
printf("\nIV :");
|
||||
aes.printArray(iv,16);
|
||||
printf("\n============================================================\n");
|
||||
}
|
||||
|
||||
void prekey_test ()
|
||||
{
|
||||
prekey (128) ;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
Monte Carlo test vectors:
|
||||
http://csrc.nist.gov/groups/STM/cavp/documents/aes/aesmct.zip
|
||||
|
||||
(Compare the Monte Carlo output to the ECBMCT128.rsp file)
|
||||
|
||||
Known Answer Tests:
|
||||
http://csrc.nist.gov/groups/STM/cavp/documents/aes/KAT_AES.zip
|
||||
|
||||
- Compare the other test outputs to
|
||||
ECBVarTxt128.rsp
|
||||
ECBVarKey128.rsp
|
||||
ECBVarTxt192.rsp
|
||||
ECBVarKey192.rsp
|
||||
ECBVarTxt256.rsp
|
||||
ECBVarKey256.rsp
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
#include <AES.h>
|
||||
|
||||
AES aes ;
|
||||
|
||||
byte key [2*N_BLOCK] ;
|
||||
byte plain [N_BLOCK] ;
|
||||
byte iv [N_BLOCK] ;
|
||||
byte cipher [N_BLOCK] ;
|
||||
byte check [N_BLOCK] ;
|
||||
|
||||
void loop ()
|
||||
{}
|
||||
|
||||
|
||||
void setup ()
|
||||
{
|
||||
Serial.begin (57600) ;
|
||||
Serial.print ("AES library test vectors") ;
|
||||
|
||||
monte_carlo (128) ;
|
||||
|
||||
for (int keysize = 128 ; keysize <= 256 ; keysize += 64)
|
||||
{
|
||||
prekey_test_var_plaintext (keysize) ;
|
||||
prekey_test_var_key (keysize) ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void prekey_test_var_plaintext (int bits)
|
||||
{
|
||||
Serial.println () ;
|
||||
Serial.print ("ECB Varying Plaintext ") ;
|
||||
Serial.print (bits) ;
|
||||
Serial.println (" bits") ;
|
||||
Serial.println () ;
|
||||
|
||||
byte succ ;
|
||||
set_bits (bits, key, 0) ; // all zero key
|
||||
succ = aes.set_key (key, bits) ;
|
||||
if (succ != SUCCESS)
|
||||
Serial.println ("Failure set_key") ;
|
||||
|
||||
|
||||
for (int bitcount = 1 ; bitcount <= 128 ; bitcount++)
|
||||
{
|
||||
Serial.print ("COUNT = ") ; Serial.println (bitcount-1) ;
|
||||
print_value ("KEY = ", key, bits) ;
|
||||
set_bits (128, plain, bitcount) ;
|
||||
|
||||
print_value ("PLAINTEXT = ", plain, 128) ;
|
||||
|
||||
succ = aes.encrypt (plain, cipher) ;
|
||||
if (succ != SUCCESS)
|
||||
Serial.println ("Failure encrypt") ;
|
||||
|
||||
print_value ("CIPHERTEXT = ", cipher, 128) ;
|
||||
|
||||
succ = aes.decrypt (cipher, check) ;
|
||||
if (succ != SUCCESS)
|
||||
Serial.println ("Failure decrypt") ;
|
||||
|
||||
//print_value ("CHECK = ", check, 128) ;
|
||||
check_same (plain, check, 128) ;
|
||||
Serial.println () ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void prekey_test_var_key (int bits)
|
||||
{
|
||||
Serial.println () ;
|
||||
Serial.print ("ECB Varying Key ") ;
|
||||
Serial.print (bits) ;
|
||||
Serial.println (" bits") ;
|
||||
Serial.println () ;
|
||||
|
||||
byte succ ;
|
||||
set_bits (128, plain, 0) ;
|
||||
|
||||
for (int bitcount = 1 ; bitcount <= bits ; bitcount++)
|
||||
{
|
||||
set_bits (bits, key, bitcount) ; // all zero key
|
||||
succ = aes.set_key (key, bits) ;
|
||||
if (succ != SUCCESS)
|
||||
Serial.println ("Failure set_key") ;
|
||||
Serial.print ("COUNT = ") ; Serial.println (bitcount-1) ;
|
||||
print_value ("KEY = ", key, bits) ;
|
||||
|
||||
print_value ("PLAINTEXT = ", plain, 128) ;
|
||||
|
||||
succ = aes.encrypt (plain, cipher) ;
|
||||
if (succ != SUCCESS)
|
||||
Serial.println ("Failure encrypt") ;
|
||||
|
||||
print_value ("CIPHERTEXT = ", cipher, 128) ;
|
||||
|
||||
succ = aes.decrypt (cipher, check) ;
|
||||
if (succ != SUCCESS)
|
||||
Serial.println ("Failure decrypt") ;
|
||||
|
||||
check_same (plain, check, 128) ;
|
||||
Serial.println () ;
|
||||
}
|
||||
}
|
||||
|
||||
void set_bits (int bits, byte * a, int count)
|
||||
{
|
||||
bits >>= 3 ;
|
||||
byte bcount = count >> 3 ;
|
||||
for (byte i = 0 ; i < bcount ; i++)
|
||||
a [i] = 0xFF ;
|
||||
if ((count & 7) != 0)
|
||||
a [bcount++] = 0xFF & (0xFF00 >> (count & 7)) ;
|
||||
for (byte i = bcount ; i < bits ; i++)
|
||||
a [i] = 0x00 ;
|
||||
}
|
||||
|
||||
void check_same (byte * a, byte * b, int bits)
|
||||
{
|
||||
bits >>= 3 ;
|
||||
for (byte i = 0 ; i < bits ; i++)
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
Serial.println ("Failure plain != check") ;
|
||||
return ;
|
||||
}
|
||||
}
|
||||
|
||||
char hex[] = "0123456789abcdef" ;
|
||||
|
||||
|
||||
void print_value (const char * str, byte * a, int bits)
|
||||
{
|
||||
Serial.print (str) ;
|
||||
bits >>= 3 ;
|
||||
for (int i = 0 ; i < bits ; i++)
|
||||
{
|
||||
byte b = a[i] ;
|
||||
Serial.print (hex [b >> 4]) ;
|
||||
Serial.print (hex [b & 15]) ;
|
||||
}
|
||||
Serial.println () ;
|
||||
}
|
||||
|
||||
byte monteplain [] =
|
||||
{ 0xb9, 0x14, 0x5a, 0x76, 0x8b, 0x7d, 0xc4, 0x89,
|
||||
0xa0, 0x96, 0xb5, 0x46, 0xf4, 0x3b, 0x23, 0x1f } ;
|
||||
byte montekey [] =
|
||||
{ 0x13, 0x9a, 0x35, 0x42, 0x2f, 0x1d, 0x61, 0xde,
|
||||
0x3c, 0x91, 0x78, 0x7f, 0xe0, 0x50, 0x7a, 0xfd } ;
|
||||
|
||||
void monte_carlo (int bits)
|
||||
{
|
||||
Serial.println () ;
|
||||
Serial.print ("Monte Carlo ") ;
|
||||
Serial.print (bits) ;
|
||||
Serial.println (" bits") ;
|
||||
Serial.println () ;
|
||||
byte succ ;
|
||||
for (int i = 0 ; i < 16 ; i++)
|
||||
{
|
||||
plain [i] = monteplain [i] ;
|
||||
key [i] = montekey [i] ;
|
||||
}
|
||||
for (int i = 0 ; i < 100 ; i++)
|
||||
{
|
||||
Serial.print ("COUNT = ") ; Serial.println (i) ;
|
||||
print_value ("KEY = ", key, bits) ;
|
||||
print_value ("PLAINTEXT = ", plain, 128) ;
|
||||
succ = aes.set_key (key, bits) ;
|
||||
for (int j = 0 ; j < 1000 ; j++)
|
||||
{
|
||||
succ = aes.encrypt (plain, cipher) ;
|
||||
aes.copy_n_bytes (plain, cipher, 16) ;
|
||||
}
|
||||
print_value ("CIPHERTEXT = ", cipher, 128) ;
|
||||
Serial.println () ;
|
||||
if (bits == 128)
|
||||
{
|
||||
for (byte k = 0 ; k < 16 ; k++)
|
||||
key [k] ^= cipher [k] ;
|
||||
}
|
||||
else if (bits == 192)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||